1. 程式人生 > >kernel module程式設計(八):讀取proc檔案之seq_file

kernel module程式設計(八):讀取proc檔案之seq_file

  本文也即《Linux Device Drivers》,LDD3的第四章Debuging Techniques的讀書筆記之三,但我們不限於此內容。

  在上次我們使用了read_proc的方式通過/proc檔案讀取kernel module的資訊。作者給的例子他自己說是ugly。而我們在讀取大量資料時發現,受到使用者buffer大小的限制(page的大小),可能需要讀取多次,不僅需要記錄上次讀取的位置,而且由於每次讀取我們申請了訊號量,讀取完釋放,那麼如果多次讀取的間隔中,如果訊號量被寫所獲取就好出現混亂。linux kernel提供seq_file更好的方式來解決這個問題,除非我們確定讀取的資訊量非常少,能夠在page中返回,我們應使用seq_file的方式而不是read_proc 。

  LDD3中介紹的方式,我覺得是典型的西方人和中國人思維方式的不同。在seq_file的介紹中,LDD3先從每個操作具體將其,然後到如何和proc檔案聯絡,最後到如何建立proc檔案,我喜歡反過來的方式,先建立proc,在一步步細化。老外是日月年,我們是年月日,嘿嘿。seq_file的處理方式開看有點發展,步驟有些多,但是安全,是規範的處理方式。

步驟一:建立proc檔案。

  通過一個struct proc_dir_entry的元素,在/proc中建立檔案,如下:

struct proc_dir_entry * entry = create_proc_entry(“scullseq”,0,NULL)。引數的內容和read_proc,第一個引數表示檔名,第二個引數表示檔案屬性,對於只讀方式為0,第三個引數表示檔案路徑,NULL表示預設路徑,即/proc。

步驟二:關聯proc的操作。

   需要對檔案進行操作,見過檔案和struct file_operations相關聯,我們注意到這個資料結構也用於模組操作關聯中。具體操作如下:

#ifdef SCULL_SEQ_FILE
/* 步驟二:2、定義proc檔案所關聯的檔案操作資料 */
static struct file_operations scull_proc_ops = { 
        .owner  = THIS_MODULE,  
        .open    = scull_proc_open,  //open通常這是我們唯一需要重新定義的函式,需要和特定的seq_file關聯起來。

        .read     = seq_read,               //採用系統的處理方式
        .llseek   = seq_lseek,              //採用系統的處理方式
        .release = seq_release,         //採用系統的處理方式
};

/* 步驟二:4、在前面的步驟二1~3中我們建立了proc檔案,關聯了proc檔案和file_operations,並進一步關聯了seq_file,這裡我們具體定義被關聯的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
        /* 步驟一:建立proc檔案*/
        entry = create_proc_entry ("scullseq", 0 ,NULL);
        /* 步驟二:1、將proc檔案和對應的檔案操作關聯起來*/
        if(entry)
                entry -> proc_fops = & scull_proc_ops ;
#endif
}
... ... 

static void __exit scull_exit(void)
{
#ifdef SCULL_SEQ_FILE

/* 我們在模組中建立的proc檔案,都應該模組cleanup模組的時候刪除,以防影響系統,另外,我們應該在刪除模組函式的開始執行這個操作,防止相關聯的資料已經刪除或者登出後再來處理,避免異常出現。 */
        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、具體實現proc檔案的open操作,目的與seq_file相關聯。*/
int scull_proc_open(struct inode * inode , struct file * file)
{
        return seq_open(file, & scull_seq_ops );
}

步驟三:處理seq_file操作過程。

  seq_file操作定義了四個操作,格式如下:

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);

  其中loff_t表示位置,這是由我們自己程式控制的,初始為0,在scull中我們依次讀取scull0-3,因此使用該偏移量來表示我們所讀取的裝置的序號。

  我們利用void * v來記錄裝置的入口位置。start根據編譯量,即我們的裝置的序號,返回scullx的入口位置,無論下一操作是next,stop,還是show,這個返回值會作為引數void *v輸入。next表示下一查詢,和start相似,只是多了void * v的輸入,同樣它的返回值也作為下一操作的引數void *v輸入。show用於通過/proc檔案輸出。stop表示一次讀取的結束。雖然在seq_file中和read_proc不一樣,不需要考慮每次可以輸出的buff的大小,但是實際讀取不會連續一片很大的資料輸出,在例子後面,我們將討論這些操作的執行。

  輸出方式非常簡單,一般可以使用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;
}

  我們描述一下處理的過程:當我們讀取proc檔案,例如cat /proc/scullseq時,我們假設scull0和scull1都有較多資訊輸入。

  一開始呼叫start,偏移量為0,返回scull0的入口,接著呼叫show,scull0的入口作為引數輸入,在show中,我們可以遍歷scull0的資料結構,通過seq_printf輸出。完成show後,由於輸出資訊多,進入stop,在例子中stop沒有實際操作,我們只是用來跟蹤處理的流程。

再次呼叫start,偏移量步進1,即1,返回scull0的入口,接著呼叫show,scull1的入口作為引數輸入,在show中,我們可以遍歷scull1的資料結構,通過seq_printf輸出。完成show後,由於輸出資訊多,進入stop。

  再次呼叫start,偏移量步進1,即2,返回scull2的入口,接著呼叫show,scull2的入口作為引數輸入,在show中,我們可以遍歷scull2的資料結構,通過seq_printf輸出。完成show後,由於輸出資訊非常少,kernel認為可以繼續進行操作,而不需要stop,呼叫next(),在next引數中輸入的引數loff_t為2,next將其加一,為3,返回scull3的入口。接著呼叫show,scull3的入口作為引數輸入,在show中,我們可以遍歷scull3的資料結構,通過seq_printf輸出。完成show後,由於輸出資訊非常少,kernel認為可以繼續進行操作,而不需要stop,呼叫next()。由於已經全部資訊返回,在next中發現沒有資料,返回NULL。系統再次呼叫start,返回NULL,系統呼叫stop,結束這次輸出。

  再次呼叫start,返回NULL,表示已經沒有資料輸出,呼叫stop,結束所有的輸出。

  值得注意 seq_file 程式碼在呼叫 start 和 stop 之間不睡眠或者進行其他非原子性任務. 你也肯定會看到在呼叫 start 後馬上有一個 stop 呼叫. 因此, 對你的 start 方法來說請求訊號量或自旋鎖是安全的. 只要你的其他 seq_file 方法是原子的, 呼叫的整個序列是原子的.(http://www.deansys.com/doc/ldd3/ch04s03.html)

相關推薦

kernel module程式設計讀取proc檔案seq_file

  本文也即《Linux Device Drivers》,LDD3的第四章Debuging Techniques的讀書筆記之三,但我們不限於此內容。   在上次我們使用了read_proc的方式通過/proc檔案讀取kernel module的資訊。作者給的例子他自己說是ug

Windows網路程式設計非阻塞模式非同步模式

前面幾篇文章介紹的無論是TCP通訊還是UDP通訊都是阻塞式的,它們在執行recv或recvfrom時會線上程中等待,直到接收到資訊為止,所以在應用的時候一般都需要開闢子執行緒,在子執行緒裡專門做這類事情,不然它會影響主執行緒的執行。  系統提供三種網路模型

大學生學程式設計如何學好Java

Java是一種計算機程式語言,擁有跨平臺、面向物件、泛型程式設計的特性,廣泛應用於企業級Web應用開發和移動應用開發,是目前用的最廣的語言之一,在程式語言排行榜多次排在第一位。這門如此受歡迎的語言容易上手學習嗎?應該怎樣才能快速學習? 首先,Java語言很容易上手學習,對於沒有計算機基礎

Java高併發程式設計Java併發容器和框架

1. ConcurrentHashMap 1.1 ConcurrentHashMap的優勢 在併發程式設計中使用HashMap可能導致程式死迴圈。而使用執行緒安全的HashTable效率又非 常低下,基於以上兩個原因,便有了ConcurrentHashMap的登場機會。

x264代碼剖析encode()函數x264_encoder_close()函數

name sequence img float 通過 例如 b16 trac 不為 x264代碼剖析(八):encode()函數之x264_encoder_close()函數 encode()函數是x264的主幹函數。主要包含x264_enc

linux系統程式設計程序守護程序詳解及建立,daemon()使用

一,守護程序概述 Linux Daemon(守護程序)是執行在後臺的一種特殊程序。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。它不需要使用者輸入就能執行而且提供某種服務,不是對整個系統就是對某個使用者程式提供服務。Linux系統的大多數伺服器就是通過守護程序實現的。常見的守護程序包括系

PL真有意思其它程式設計模型

前言 在之前幾篇我們討論的語法、語義、命名、型別和抽象適用於所有語言。然而我們的注意力都主要集中在命令式語言上,現在這篇來看看其它正規化的語言。函式式和邏輯式語言是最主要的非命令式語言。 函式式語言 命名和作用域問題出現在各種模型中,還有型別、表示式和選擇與遞迴等控制流概念等等。所有語言都必須經過掃描、語法分

Hibernate基於外鍵映射的1-1關聯關系

hbm 初始化 inno oot type nat create getc source 背景: 一個部門只有一個一把手,這在程序開發中就會設計數據映射應該設置為一對一關聯。 在hibernate代碼開發中,實現這個業務有兩種方案: 1)基於外鍵映射的1-1關

Java-NIODatagramChannel

reg lin mov div 數據 selector sca gist put Java NIO中的DatagramChannel是一個能收發UDP包的通道。操作步驟:  1)打開 DatagramChannel  2)接收/發送數據 同樣它也支持NIO的非阻塞模式操作

C#高性能大容量SOCKET並發通訊協議

pad 英文 透明 優勢 sock ase sha dev lac 協議種類 開發Socket程序有兩種協議類型,一種是用文本描述的,類似HTTP協議,定義字符集,好處是兼容性和調試方便,缺點是解析文本會損耗一些性能;一種是用Code加結構體,定義字節順序,好處是性能高,

國內物聯網平臺初探中移物聯網開放平臺OneNet

nco 國內 聯網 申請 nonce 行數 即時通信 不同 初始 平臺定位 OneNET是中移物聯網有限公司搭建的開放、共贏設備雲平臺,為各種跨平臺物聯網應用、行業解決方案,提供簡便的雲端接入、存儲、計算和展現,快速打造物聯網產品應用,降低開發成本。 IoT PaaS

Selenium2+Python3.6實戰定位下拉菜單出錯,如何解決?用select或xpath定位。

排查 會有 有時 ide 導入 python3 很好 沒有 元素 在登錄界面,有時候會有幾種不同的角色,針對不同角色定位到的信息是不一樣的。查詢資料知道定位下拉框的元素有兩種方式:Xpath和select。 但是使用xpath定位時,user定位到了,登錄的時候卻是調用的a

EF學習筆記讀取關聯數據

取數據 microsoft image zha 手動 模型 取數 foreach ret 總目錄:ASP.NET MVC5 及 EF6 學習筆記 - (目錄整理) 本篇參考原文鏈接:Reading Related Data 本章主要講述加載顯示關聯數據; 數據加載分為以下三

EF學習筆記更新關聯數據

tro rop es2017 net sage red ida string entity 學習筆記主目錄鏈接:ASP.NET MVC5 及 EF6 學習筆記 - (目錄整理) 上一篇鏈接:EF學習筆記(七):讀取關聯數據 本篇原文鏈接:Updating Related D

DockerDocker端口映射

oot gre 指定 con 指定端口 docker ner 查看 names 1、隨機映射   docker run -P -d --name mynginx1 nginx   [[email protected] ~]# docker ps -l     CO

java學習筆記繼承

this關鍵字 log implement java學習 方式 show 使用 類型 多繼承 繼承 子類擁有父類非private的屬性,方法。 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴展。 子類可以用自己的方式實現父類的方法。 Java的繼承

Java框架spring Boot學習筆記Spring相關概念

擴展 靜態 輕量級 想要 spring配置 核心 使用 oot 調用方法 Spring是開源、輕量級、一站式框架。 Spring核心主要兩部分 aop:面向切面編程,擴展功能不是修改源代碼實現 ioc:控制反轉,比如一個類,在類裏面有方法(不是靜態的方法),想要調用類

Python筆記web開發

自定義 服務器 gpo unix系統 運行 tps rom request 不知道 #本文是在Windows環境下,Unix系統應該還要設置2個東西 (一) 采用MVC設計web應用 遵循 模型-視圖-控制器(model-view-controlle) 模型:

Python+Selenium筆記操作下拉菜單

sel 字段 功能 options table 註冊 unit 生日 ted (一) Select類 Select類是selenium的一個特定的類,用來與下拉菜單和列表交互。 下拉菜單和列表是通過HTML的<select> 元素實現的。選擇項是通過<select>中的<o

Angular開發實踐 使用ng-content進行組件內容投射

特性 添加 容器 tor 我們 將在 而是 應用程序 期望 在Angular中,組件屬於特殊的指令,它的特殊之處在於它有自己的模板(html)和樣式(css)。因此使用組件可以使我們的代碼具有強解耦、可復用、易擴展等特性。通常的組件定義如下: demo.component.