有時候需要在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()巨集來改變核心對記憶體地址檢查的處理方式,所以在核心空間對檔案的讀寫流程為:
1 2 3 4 5 |
mm_segment_t fs = get_fs(); set_fs(KERNEL_FS); //vfs_write(); vfs_read(); set_fs(fs); |
下面為一個在核心中對檔案操作的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> static char buf[] = "你好"; static char buf1[10]; int __init hello_init(void) { struct file *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); return 0; } void __exit hello_exit(void) { printk("hello exit\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); |