1. 程式人生 > >Linux核心實踐之序列檔案

Linux核心實踐之序列檔案

作者:bullbat

        seq_file機制提供了標準的例程,使得順序檔案的處理好不費力。小的檔案系統中的檔案,通常使用者層是從頭到尾讀取的,其內容可能是遍歷一些資料項建立的。Seq_file機制容許用最小代價實現此類檔案,無論名稱如何,但順序檔案是可以進行定為操作的,但其實現不怎麼高效。順序訪問,即逐個訪問讀取資料項,顯然是首選的訪問模式。某個方面具有優勢,通常會在其他方面付出代價。

下面我們一步一步來看看怎麼編寫序列檔案的處理程式。對於檔案、裝置相關驅動程式(其實裝置也是檔案)的操作,我們都知道需要提供一個struct file_operations的例項。對於這裡序列檔案的操作,核心中附加提供了一個

struct seq_operations結構,該結構很簡單:

struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};

start()         主要實現初始化工作,在遍歷一個連結物件開始時,呼叫。返回一個連結物件的偏移或

SEQ_START_TOKEN(表徵這是所有迴圈的開始)。出錯返回ERR_PTR
stop
():        當所有連結物件遍歷結束時呼叫。主要完成一些清理工作。
next
():       用來在遍歷中尋找下一個連結物件。返回下一個連結物件或者NULL(遍歷結束)。
show
():        對遍歷物件進行操作的函式。主要是呼叫seq_printf(), seq_puts()之類的函式,打印出這個物件節點的資訊。

        由於c語言中任何資料型別的資料塊都可以轉化為資料塊的記憶體基址(指標)+資料塊大小來傳遞,不難想到基於我們上面提供的函式,將我們操作的資料用於序列檔案的讀寫、定為、釋放等操作完全可以通用話。核心也為我們提供了這些用於讀寫、定位、釋放等操作的通用函式。當然這些操作需要資料結構的支援(比如讀取當前位置、資料大小等等),這就是在後面我們會看到的

struct seq_file結構。由於我們讀寫的是檔案,在核心中必須提供一個struct file_operations結構的例項,我們可以直接用核心為我們提供的上述函式,並且重寫file_operatios結構的open方法,用該方法將虛擬檔案系統關聯到我們處理的序列檔案,那麼那些通用的讀寫函式就可以正常工作了。原理基本上是這樣的,下面我們看怎麼用file_operatios結構的open方法將我們的序列檔案關聯到虛擬檔案系統。在此之前,我們看看序列檔案的表示結構struct seq_file

struct seq_file {
	char *buf;
	size_t size;
	size_t from;
	size_t count;
	loff_t index;
	loff_t read_pos;
	u64 version;
	struct mutex lock;
	const struct seq_operations *op;
	void *private;
};

        Buf指向一個記憶體緩衝區,用於構建傳輸給使用者層的資料。Count指定了需要傳輸到使用者層的剩餘的位元組數。複製操作的起始位置由from指定,而size給出了緩衝區總的位元組數。Index是緩衝區的另一個索引。他標記了核心向緩衝區寫入下一個新紀錄的起始位置。要注意的是,indexfrom的演變過程是不同的,因為從核心向緩衝區寫入資料,與將這些資料複製到使用者空間,這兩種操作是不同的。

一般情況,對於序列檔案,我們的檔案操作例項如下:

static struct file_operations my_operations={
	.open	=my_open,
	.read	=seq_read,
	.llseek	=seq_lseek,
	.release	=seq_release,
};

        其中,my_open函式需要我們重寫的,也是我們將其用於關聯我們的序列檔案。其他都是核心為我們實現好的,在後面我們會詳細介紹。

static int my_open(struct inode *inode,struct file *filp)
{
	return seq_open(filp,&my_seq_operations);
}

我們這裡呼叫seq_open函式建立這種關聯。

int seq_open(struct file *file, const struct seq_operations *op)
{
	struct seq_file *p = file->private_data;/*p為seq_file結構例項*/

	if (!p) {
		p = kmalloc(sizeof(*p), GFP_KERNEL);
		if (!p)
			return -ENOMEM;
		file->private_data = p;/*放到file的private_data中*/
	}
	memset(p, 0, sizeof(*p));
	mutex_init(&p->lock);
	p->op = op;/*設定seq_file的operation為op*/

	/*
	 * Wrappers around seq_open(e.g. swaps_open) need to be
	 * aware of this. If they set f_version themselves, they
	 * should call seq_open first and then set f_version.
	 */
	file->f_version = 0;

	/*
	 * seq_files support lseek() and pread().  They do not implement
	 * write() at all, but we clear FMODE_PWRITE here for historical
	 * reasons.
	 *
	 * If a client of seq_files a) implements file.write() and b) wishes to
	 * support pwrite() then that client will need to implement its own
	 * file.open() which calls seq_open() and then sets FMODE_PWRITE.
	 */
	file->f_mode &= ~FMODE_PWRITE;
	return 0;
}

      可以看到,我們的seq_file結構以file的私有資料欄位傳入虛擬檔案系統,同時在open函式中設定了seq_file的操作例項。

我們看下面這個簡單的例子:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>


#define MAX_SIZE 10


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Feng");

/*用於操作的資料*/
struct my_data
{
	int data;
};

/*全域性變數*/
struct my_data *md;

/*資料的申請*/
struct my_data* my_data_init(void)
{
	int i;
	md=(struct my_data*)kmalloc(MAX_SIZE*sizeof(struct my_data),GFP_KERNEL);

	for(i=0;i<MAX_SIZE;i++)
		(md+i)->data=i;

	return md;
}

/*seq的start函式,僅僅做越界判斷然後返回pos*/
void *my_seq_start(struct seq_file *file,loff_t *pos)
{
	return (*pos<MAX_SIZE)? pos :NULL;
}

/*seq的next函式,僅僅做越界判斷然後pos遞增*/
void *my_seq_next(struct seq_file *p,void *v,loff_t *pos)
{
	(*pos)++;
	if(*pos>=MAX_SIZE)
		return NULL;

	return pos;
}

/*seq的show函式,讀資料的顯示*/
int my_seq_show(struct seq_file *file,void *v)
{
	unsigned int i=*(loff_t*)v;
	seq_printf(file,"The %d data is:%d\n",i,(md+i)->data);
	
	return 0;
}

/*seq的stop函式,什麼也不做*/
void my_seq_stop(struct seq_file *file,void *v)
{

}


/*operations of seq_file */
static const struct seq_operations my_seq_ops={
	.start	=my_seq_start,
	.next	=my_seq_next,
	.stop	=my_seq_stop,
	.show	=my_seq_show,
};

/*file的open函式,用於seq檔案與虛擬檔案聯絡*/
static int my_open(struct inode *inode,struct file *filp)
{
	return seq_open(filp,&my_seq_ops);
}

/*file操作*/
static const struct file_operations my_file_ops={
	.open	=my_open,
	.read	=seq_read,
	.llseek	=seq_lseek,
	.release=seq_release,
	.owner	=THIS_MODULE,
};

static __init int my_seq_init(void)
{
	struct proc_dir_entry *p;
	my_data_init();
	p=create_proc_entry("my_seq",0,NULL);
	if(p)
	{
		p->proc_fops=&my_file_ops;
	}

	return 0;
	
}

static void my_seq_exit(void)
{
	remove_proc_entry("my_seq",NULL);
}

module_init(my_seq_init);
module_exit(my_seq_exit);

實驗與結果:

       你可能會好奇,上面的結果是怎麼得到的。當我們用命令cat /proc/my_seq時,即是讀取檔案/proc/my_seq,而在我們的程式中,my_seq檔案繫結到了我們給定的檔案操作(p->proc_fops=&my_file_ops;)。那麼很自然想到,他是呼叫my_file_ops中的.read函式,即seq_read函式,我們看看這個函式在核心中是怎麼實現的(<fs/seq_file.c>)

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)

{

struct seq_file *m = (struct seq_file *)file->private_data;

……

/* we need at least one record in buffer */

pos = m->index;

p = m->op->start(m, &pos);

while (1) {

err = PTR_ERR(p);

if (!p || IS_ERR(p))

break;

err = m->op->show(m, p);

if (err < 0)

break;

if (unlikely(err))

m->count = 0;

if (unlikely(!m->count)) {

p = m->op->next(m, p, &pos);

m->index = pos;

continue;

}

if (m->count < m->size)

goto Fill;

m->op->stop(m, p);

kfree(m->buf);

m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);

if (!m->buf)

goto Enomem;

m->count = 0;

m->version = 0;

pos = m->index;

p = m->op->start(m, &pos);

}

m->op->stop(m, p);

m->count = 0;

goto Done;

……

}

       該函式程式碼比較長,我們只看while迴圈部分,也即迴圈列印的過程,我們從紅色程式碼部分可以看出程式迴圈呼叫seq_file操作的startshownextstop函式,直到讀完資料。而start返回的值傳入了nextstop函式(就是我們的序列檔案讀指標索引,在next中為void*型別)。

       除了上面的描述,核心還為我們提供了一系列輔助函式,比如single_open函式只需要我們重寫show函式即可,需要用的話可以檢視相關的程式碼,瞭解其定義。這裡,我們看看對於核心連結串列組織的資料seq_file是怎麼使用的。

程式檔案(list_seq.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

#define N 10

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Feng");

/*對核心連結串列操作需要加鎖*/
static struct mutex lock;

static struct list_head head;

struct my_data
{
	struct list_head list;
	int value;
};

/*連結串列的插入元素*/
struct list_head* insert_list(struct list_head *head,int value)
{
	struct my_data *md=NULL;
	mutex_lock(&lock);
	md=(struct my_data*)kmalloc(sizeof(struct my_data),GFP_KERNEL);
	if(md)
	{
		md->value=value;
		list_add(&md->list,head);
		
	}
	mutex_unlock(&lock);

	return head;
}
/*列印,傳入引數v為open函式返回的,連結串列需要操作的節點*/
static int list_seq_show(struct seq_file *file,void *v)
{
	struct list_head *list=(struct list_head*)v;

	struct my_data *md=list_entry(list,struct my_data,list);

	seq_printf(file,"The value of my data is:%d\n",md->value);

	return 0;
}
static void *list_seq_start(struct seq_file *file,loff_t *pos)
{
	/*加鎖*/
	mutex_lock(&lock);
	return seq_list_start(&head,*pos);
}

static void *list_seq_next(struct seq_file *file,void *v,loff_t *pos)
{
	return seq_list_next(v,&head,pos);
}
static void list_seq_stop(struct seq_file *file,void *v)
{
	/*解鎖*/
	mutex_unlock(&lock);
}
static struct seq_operations list_seq_ops=
{
	.start	=list_seq_start,
	.next	=list_seq_next,
	.stop	=list_seq_stop,
	.show	=list_seq_show,
};

static int list_seq_open(struct inode *inode,struct file *file)
{
	return seq_open(file,&list_seq_ops);
}

static struct file_operations my_file_ops=
{
	.open	=list_seq_open,
	.read	=seq_read,
	.write	=seq_write,
	.llseek	=seq_lseek,
	.release=seq_release,
	.owner	=THIS_MODULE,
};

static __init int list_seq_init(void)
{
	struct proc_dir_entry *entry;
	int i;
	mutex_init(&lock);
	INIT_LIST_HEAD(&head);

	for(i=0;i<N;i++)
		head=*(insert_list(&head,i));

	entry=create_proc_entry("list_seq",0,NULL);
	if(entry)
		entry->proc_fops=&my_file_ops;
	return 0;
}

static void list_seq_exit(void)
{
	struct my_data *md=NULL;
	remove_proc_entry("list_seq",NULL);

	while(!list_empty(&head))
	{
		md=list_entry((&head)->next,struct my_data,list);
		list_del(&md->list);
		kfree(md);
	}
	
}

module_init(list_seq_init);
module_exit(list_seq_exit);

測試試驗結果:

由於核心函式list_add為前插,所以打出的資料為倒序的。

序列檔案的實現基於proc檔案系統,下一步將對其進行分析學習。