1. 程式人生 > >Linux 核心驅動中對檔案的讀寫

Linux 核心驅動中對檔案的讀寫

有時候需要在Linux kernel–大多是在需要除錯的驅動程式–中讀寫檔案資料。在kernel中操作檔案沒有標準庫可用,需要利用kernel的一些函式,這些函式主 要有: filp_open() filp_close(), vfs_read() vfs_write(),set_fs(),get_fs()等,這些函式在linux/fs.h和asm/uaccess.h標頭檔案中宣告。下面介紹主要步驟

1. 開啟檔案

filp_open()在kernel中可以開啟檔案,其原形如下:

strcut file* filp_open(const char* filename, int open_mode, int mode);

該函式返回strcut file*結構指標,供後繼函式操作使用,該返回值用IS_ERR()來檢驗其有效性。

引數說明

filename: 表明要開啟或建立檔案的名稱(包括路徑部分)。在核心中開啟的檔案時需要注意開啟的時機,很容易出現需要開啟檔案的驅動很早就載入並開啟檔案,但需要開啟的檔案所在裝置還不有掛載到檔案系統中,而導致開啟失敗。

open_mode: 檔案的開啟方式,其取值與標準庫中的open相應引數類似,可以取O_CREAT,O_RDWR,O_RDONLY等。

mode: 建立檔案時使用,設定建立檔案的讀寫許可權,其它情況可以匆略設為0

2. 讀寫檔案

kernel中檔案的讀寫操作可以使用vfs_read()和vfs_write,在使用這兩個函式前需要說明一下get_fs()和 set_fs()這兩個函式。

vfs_read() vfs_write()兩函式的原形如下:

ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos);

ssize_t vfs_write(struct file* filp, const char __user* buffer, size_t len, loff_t* pos);

注意這兩個函式的第二個引數buffer,前面都有__user修飾符,這就要求這兩個buffer指標都應該指向使用者空間的記憶體,如果對該引數傳 遞kernel空間的指標,這兩個函式都會返回失敗-EFAULT。但在Kernel中,我們一般不容易生成使用者空間的指標,或者不方便獨立使用使用者空間 記憶體。要使這兩個讀寫函式使用kernel空間的buffer指標也能正確工作,需要使用set_fs()函式或巨集(set_fs()可能是巨集定義),如 果為函式,其原形如下:

void set_fs(mm_segment_t fs);

該函式的作用是改變kernel對記憶體地址檢查的處理方式,其實該函式的引數fs只有兩個取值:USER_DS,KERNEL_DS,分別代表 使用者空間和核心空間,預設情況下,kernel取值為USER_DS,即對使用者空間地址檢查並做變換。那麼要在這種對記憶體地址做檢查變換的函式中使用核心 空間地址,就需要使用set_fs(KERNEL_DS)進行設定。get_fs()一般也可能是巨集定義,它的作用是取得當前的設定,這兩個函式的一般用 法為:

mm_segment_t old_fs;

old_fs = get_fs();

set_fs(KERNEL_DS);

…… //與記憶體有關的操作

set_fs(old_fs);

還有一些其它的核心函式也有用__user修飾的引數,在kernel中需要用kernel空間的記憶體代替時,都可以使用類似辦法。

使用vfs_read()和vfs_write()最後需要注意的一點是最後的引數loff_t * pos,pos所指向的值要初始化,表明從檔案的什麼地方開始讀寫。

3. 關閉讀寫檔案

int filp_close(struct file*filp, fl_owner_t id);

該函式的使用很簡單,第二個引數一般傳遞NULL值,也有用current->files作為實參的。

使用以上函式的其它注意點:

1. 其實Linux Kernel組成員不贊成在kernel中獨立的讀寫檔案(這樣做可能會影響到策略和安全問題),對核心需要的檔案內容,最好由應用層配合完成。

2. 在可載入的kernel module中使用這種方式讀寫檔案可能使模組載入失敗,原因是核心可能沒有EXPORT你所需要的所有這些函式。

3. 分析以上某些函式的引數可以看出,這些函式的正確執行需要依賴於程序環境,因此,有些函式不能在中斷的handle或Kernel中不屬於任可程序的程式碼 中執行,否則可能出現崩潰,要避免這種情況發生,可以在kernel中建立核心執行緒,將這些函式放線上程環境下執行(建立核心執行緒的方式請引數 kernel_thread()函式)。

在VFS的支援下,使用者態程序讀寫 任何型別的檔案系統都可以使用read和write著兩個系統呼叫,但是在linux核心中沒有這樣的系統呼叫我們如何操作檔案呢?我們知道read和 write在進入核心態之後,實際執行的是sys_read和sys_write,但是檢視核心原始碼,發現這些操作檔案的函式都沒有匯出(使用 EXPORT_SYMBOL匯出),也就是說在核心模組中是不能使用的,那如何是好?

通過檢視sys_open的原始碼我 們發現,其主要使用了do_filp_open()函式,該函式在fs/namei.c中,而在改檔案中,filp_open函式也是呼叫了 do_filp_open函式,並且介面和sys_open函式極為相似,呼叫引數也和sys_open一樣,並且使用EXPORT_SYMBOL匯出 了,所以我們猜想該函式可以開啟檔案,功能和open一樣。使用同樣的查詢方法,我們找出了一組在核心中操作檔案的函式,如下:

功能 函式原型
開啟檔案 struct file *filp_open(const char *filename, int flags, int mode)
讀取檔案 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
寫檔案 ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
關閉檔案 int filp_close(struct file *filp, fl_owner_t id)

我們注意到在vfs_read和vfs_write函式中,其引數buf指向的使用者空間的記憶體地址,如果我們直接使用核心空間的指標,則會返回-EFALUT。所以我們需要使用

set_fs()和get_fs()巨集來改變核心對記憶體地址檢查的處理方式,所以在核心空間對檔案的讀寫流程為:

12345 mm_segment_t fs=get_fs();set_fs(KERNEL_FS);//vfs_write();vfs_read();set_fs(fs);

下面為一個在核心中對檔案操作的例子:

1234567891011121314151617181920212223242526272829303132333435363738 #include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/uaccess.h>staticcharbuf[]="你好";staticcharbuf1[10];int__init hello_init(void){structfile *fp;mm_segment_t fs;loff_t pos;printk("hello enter\n");fp=filp_open("/home/niutao/kernel_file",O_RDWR|O_CREAT,0644);if(IS_ERR(fp)){printk("create file error\n");return-1;}fs=get_fs();set_fs(KERNEL_DS);pos=0;vfs_write(fp,buf,sizeof(buf),&pos);pos=0;vfs_read(fp,buf1,sizeof(buf),&pos);printk("read: %s\n",buf1);filp_close(fp,NULL);set_fs(fs);return0;}void__exit hello_exit(void){printk("hello exit\n");}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");