本文也即《Linux Device Drivers》,LDD3的第四章Debuging Techniques的讀書筆記之三,但我們不限于此內(nèi)容。
在上次我們使用了read_proc的方式通過(guò)/proc文件讀取kernel module的信息。作者給的例子他自己說(shuō)是ugly。而我們?cè)谧x取大量數(shù)據(jù)時(shí)發(fā)現(xiàn),受到用戶buffer大小的限制(page的大?。?,可能需要讀取多次,不僅需要記錄上次讀取的位置,而且由于每次讀取我們申請(qǐng)了信號(hào)量,讀取完釋放,那么如果多次讀取的間隔中,如果信號(hào)量被寫所獲取就好出現(xiàn)混亂。linux kernel提供seq_file更好的方式來(lái)解決這個(gè)問(wèn)題,除非我們確定讀取的信息量非常少,能夠在page中返回,我們應(yīng)使用seq_file的方式而不是read_proc 。
LDD3中介紹的方式,我覺(jué)得是典型的西方人和中國(guó)人思維方式的不同。在seq_file的介紹中,LDD3先從每個(gè)操作具體將其,然后到如何和proc文件聯(lián)系,最后到如何創(chuàng)建proc文件,我喜歡反過(guò)來(lái)的方式,先創(chuàng)建proc,在一步步細(xì)化。老外是日月年,我們是年月日,嘿嘿。seq_file的處理方式開(kāi)看有點(diǎn)發(fā)展,步驟有些多,但是安全,是規(guī)范的處理方式。
步驟一:建立proc文件。
通過(guò)一個(gè)struct proc_dir_entry的元素,在/proc中建立文件,如下:
struct proc_dir_entry * entry = create_proc_entry(“scullseq”,0,NULL)。參數(shù)的內(nèi)容和read_proc,第一個(gè)參數(shù)表示文件名,第二個(gè)參數(shù)表示文件屬性,對(duì)于只讀方式為0,第三個(gè)參數(shù)表示文件路徑,NULL表示缺省路徑,即/proc。
步驟二:關(guān)聯(lián)proc的操作。
需要對(duì)文件進(jìn)行操作,見(jiàn)過(guò)文件和struct file_operations相關(guān)聯(lián),我們注意到這個(gè)數(shù)據(jù)結(jié)構(gòu)也用于模塊操作關(guān)聯(lián)中。具體操作如下:
#ifdef SCULL_SEQ_FILE
/* 步驟二:2、定義proc文件所關(guān)聯(lián)的文件操作數(shù)據(jù) */
static struct file_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open, //open通常這是我們唯一需要重新定義的函數(shù),需要和特定的seq_file關(guān)聯(lián)起來(lái)。
.read = seq_read, //采用系統(tǒng)的處理方式
.llseek = seq_lseek, //采用系統(tǒng)的處理方式
.release = seq_release, //采用系統(tǒng)的處理方式
};/* 步驟二:4、在前面的步驟二1~3中我們創(chuàng)建了proc文件,關(guān)聯(lián)了proc文件和file_operations,并進(jìn)一步關(guān)聯(lián)了seq_file,這里我們具體定義被關(guān)聯(lián)的seq_file */
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show,
};static struct proc_dir_entry * entry;
#endif
… …
static int __init scull_init(void)
{
… …
#ifdef SCULL_SEQ_FILE
/* 步驟一:創(chuàng)建proc文件*/
entry = create_proc_entry ("scullseq", 0 ,NULL);
/* 步驟二:1、將proc文件和對(duì)應(yīng)的文件操作關(guān)聯(lián)起來(lái)*/
if(entry)
entry -> proc_fops = & scull_proc_ops ;
#endif
}
... ...static void __exit scull_exit(void)
{
#ifdef SCULL_SEQ_FILE
/* 我們?cè)谀K中創(chuàng)建的proc文件,都應(yīng)該模塊cleanup模塊的時(shí)候刪除,以防影響系統(tǒng),另外,我們應(yīng)該在刪除模塊函數(shù)的開(kāi)始執(zhí)行這個(gè)操作,防止相關(guān)聯(lián)的數(shù)據(jù)已經(jīng)刪除或者注銷后再來(lái)處理,避免異常出現(xiàn)。 */
remove_proc_entry("scullseq",NULL );
#endif
if(is_get_dev < 0){
return ;
}else{
int i = 0;
for(i = 0; i < SCULL_DEV_NUM; i ++){
scull_trim(&mydev[i]);
cdev_del( & mydev[i].cdev );
}
unregister_chrdev_region(dev,SCULL_DEV_NUM);
WDEBUG(WEI_KERN_NOTICE,"Scull module exit\n");
}
}/* 步驟二:3、具體實(shí)現(xiàn)proc文件的open操作,目的與seq_file相關(guān)聯(lián)。*/
int scull_proc_open(struct inode * inode , struct file * file)
{
return seq_open(file, & scull_seq_ops );
}
步驟三:處理seq_file操作過(guò)程。
seq_file操作定義了四個(gè)操作,格式如下:
void * start(struct seq_file * s, loff_t * v);
void * next (struct seq_file * s, void * v, loff_t * pos);
void stop (struct seq_file * s, void * v);
int show (struct seq_file * s, void * v);
其中l(wèi)off_t表示位置,這是由我們自己程序控制的,初始為0,在scull中我們依次讀取scull0-3,因此使用該偏移量來(lái)表示我們所讀取的設(shè)備的序號(hào)。
我們利用void * v來(lái)記錄設(shè)備的入口位置。start根據(jù)編譯量,即我們的設(shè)備的序號(hào),返回scullx的入口位置,無(wú)論下一操作是next,stop,還是show,這個(gè)返回值會(huì)作為參數(shù)void *v輸入。next表示下一查詢,和start相似,只是多了void * v的輸入,同樣它的返回值也作為下一操作的參數(shù)void *v輸入。show用于通過(guò)/proc文件輸出。stop表示一次讀取的結(jié)束。雖然在seq_file中和read_proc不一樣,不需要考慮每次可以輸出的buff的大小,但是實(shí)際讀取不會(huì)連續(xù)一片很大的數(shù)據(jù)輸出,在例子后面,我們將討論這些操作的執(zhí)行。
輸出方式非常簡(jiǎn)單,一般可以使用seq_printf,另外還有seq_putc,seq_puts,seq_escape。例子如下:
#ifdef SCULL_SEQ_FILE
void * scull_seq_start (struct seq_file * s, loff_t * pos)
{
printk("==scull_seq_start() enter %p %p %lli\n", s , pos , * pos);
if( * pos >= SCULL_DEV_NUM)
return NULL;
else
return mydev + * pos;
}void * scull_seq_next (struct seq_file * s, void * v, loff_t * pos)
{
printk("==scull_seq_next() enter %p %p %p %lli\n", s , v, pos , * pos);
(* pos ) ++;
return scull_seq_start(s, pos);
}void scull_seq_stop (struct seq_file * s, void * v)
{
printk("==scull_seq_stop() enter\n");
return ;
}int scull_seq_show (struct seq_file * s, void * v)
{
struct scull_dev * dev = (struct scull_dev *) v;
struct scull_qset * qs = NULL;
int j = 0;
printk("==scull_seq_show() enter\n");if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
seq_printf (s, "\n Device Scull%d: qset %i, q %i sz %li\n", (int) (dev - mydev),dev->qset, dev->quantum, dev->size);
printk("\n Device Scull%d: qset %i, q %i sz %li\n", (int) (dev - mydev),dev->qset, dev->quantum, dev->size);for(qs = dev->data ; qs ; qs=qs->next){
seq_printf ( s," item at %p, qset at %p\n", qs, qs->data);
printk(" item at %p, qset at %p\n", qs, qs->data);
if(qs->data ){
for(; j < dev->qset /* && qs->data[ j ] */ ;j++){
seq_printf (s ,"\t%4i:%8p\n", j,qs->data[ j ]);
printk("\t%4i:%8p\n", j,qs->data[ j ]);
}
}
}
up(&dev->sem);
return 0;
}
我們描述一下處理的過(guò)程:當(dāng)我們讀取proc文件,例如cat /proc/scullseq時(shí),我們假設(shè)scull0和scull1都有較多信息輸入。
一開(kāi)始調(diào)用start,偏移量為0,返回scull0的入口,接著調(diào)用show,scull0的入口作為參數(shù)輸入,在show中,我們可以遍歷scull0的數(shù)據(jù)結(jié)構(gòu),通過(guò)seq_printf輸出。完成show后,由于輸出信息多,進(jìn)入stop,在例子中stop沒(méi)有實(shí)際操作,我們只是用來(lái)跟蹤處理的流程。
再次調(diào)用start,偏移量步進(jìn)1,即1,返回scull0的入口,接著調(diào)用show,scull1的入口作為參數(shù)輸入,在show中,我們可以遍歷scull1的數(shù)據(jù)結(jié)構(gòu),通過(guò)seq_printf輸出。完成show后,由于輸出信息多,進(jìn)入stop。
再次調(diào)用start,偏移量步進(jìn)1,即2,返回scull2的入口,接著調(diào)用show,scull2的入口作為參數(shù)輸入,在show中,我們可以遍歷scull2的數(shù)據(jù)結(jié)構(gòu),通過(guò)seq_printf輸出。完成show后,由于輸出信息非常少,kernel認(rèn)為可以繼續(xù)進(jìn)行操作,而不需要stop,調(diào)用next(),在next參數(shù)中輸入的參數(shù)loff_t為2,next將其加一,為3,返回scull3的入口。接著調(diào)用show,scull3的入口作為參數(shù)輸入,在show中,我們可以遍歷scull3的數(shù)據(jù)結(jié)構(gòu),通過(guò)seq_printf輸出。完成show后,由于輸出信息非常少,kernel認(rèn)為可以繼續(xù)進(jìn)行操作,而不需要stop,調(diào)用next()。由于已經(jīng)全部信息返回,在next中發(fā)現(xiàn)沒(méi)有數(shù)據(jù),返回NULL。系統(tǒng)再次調(diào)用start,返回NULL,系統(tǒng)調(diào)用stop,結(jié)束這次輸出。
再次調(diào)用start,返回NULL,表示已經(jīng)沒(méi)有數(shù)據(jù)輸出,調(diào)用stop,結(jié)束所有的輸出。
值得注意 seq_file 代碼在調(diào)用 start 和 stop 之間不睡眠或者進(jìn)行其他非原子性任務(wù). 你也肯定會(huì)看到在調(diào)用 start 后馬上有一個(gè) stop 調(diào)用. 因此, 對(duì)你的 start 方法來(lái)說(shuō)請(qǐng)求信號(hào)量或自旋鎖是安全的. 只要你的其他 seq_file 方法是原子的, 調(diào)用的整個(gè)序列是原子的.(http://www.deansys.com/doc/ldd3/ch04s03.html)
聯(lián)系客服