Linux核心實踐之序列檔案
作者:bullbat
seq_file機制提供了標準的例程,使得順序檔案的處理好不費力。小的檔案系統中的檔案,通常使用者層是從頭到尾讀取的,其內容可能是遍歷一些資料項建立的。Seq_file機制容許用最小代價實現此類檔案,無論名稱如何,但順序檔案是可以進行定為操作的,但其實現不怎麼高效。順序訪問,即逐個訪問讀取資料項,顯然是首選的訪問模式。某個方面具有優勢,通常會在其他方面付出代價。
下面我們一步一步來看看怎麼編寫序列檔案的處理程式。對於檔案、裝置相關驅動程式(其實裝置也是檔案)的操作,我們都知道需要提供一個struct file_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(): 主要實現初始化工作,在遍歷一個連結物件開始時,呼叫。返回一個連結物件的偏移或
stop(): 當所有連結物件遍歷結束時呼叫。主要完成一些清理工作。
next(): 用來在遍歷中尋找下一個連結物件。返回下一個連結物件或者NULL(遍歷結束)。
show(): 對遍歷物件進行操作的函式。主要是呼叫seq_printf(), seq_puts()之類的函式,打印出這個物件節點的資訊。
由於c語言中任何資料型別的資料塊都可以轉化為資料塊的記憶體基址(指標)+資料塊大小來傳遞,不難想到基於我們上面提供的函式,將我們操作的資料用於序列檔案的讀寫、定為、釋放等操作完全可以通用話。核心也為我們提供了這些用於讀寫、定位、釋放等操作的通用函式。當然這些操作需要資料結構的支援(比如讀取當前位置、資料大小等等),這就是在後面我們會看到的
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是緩衝區的另一個索引。他標記了核心向緩衝區寫入下一個新紀錄的起始位置。要注意的是,index和from的演變過程是不同的,因為從核心向緩衝區寫入資料,與將這些資料複製到使用者空間,這兩種操作是不同的。
一般情況,對於序列檔案,我們的檔案操作例項如下:
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操作的start、show、next、stop函式,直到讀完資料。而start返回的值傳入了next和stop函式(就是我們的序列檔案讀指標索引,在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檔案系統,下一步將對其進行分析學習。