2020-03-27
關鍵字:
在 Linux 內核開發,通常是嵌入式領域的內核開發過程中,難免會有需要訪問文件系統中的文件的需求。
但 Linux 內核中可沒有像在用戶態那樣有文件IO和標准IO可以直接對文件進行 open()/fopen() , read()/fread() , write()/fwrite() , close()/fclose() 操作。
不過所幸,在 ./kernel/include/linux/fs.h 中提供了有相對應的函數供我們對文件系統中的普通文件進行IO操作。
這些函數為:
1、filp_open()
2、filp_close()
3、vfs_read()
4、vfs_write()
可以將這套函數理解成是在內核態的“文件IO”接口。
1、filp_open()函數
函數原型如下:
struct file *filp_open(const char *, int, umode_t);
參數1是要打開的文件的路徑。直接填文件系統中的路徑就行了,最好填絕對路徑。
參數2是文件的讀寫模式。常用的值有 O_RDONLY , O_WDONLY , O_RDWR , O_CREAT。這個參數的值與文件IO中的一樣,它們被定義在 ./kernel/include/uapi/asm-generic/fcntl.h 中。
參數3則是文件的權限了,即 0666 , 0755 形式的八進制數值。如果是只讀模式,直接填 0 即可。
返回值是指向所打開文件的結構體指針。這個結構體被定義在 ./kernel/include/linux/fs.h 中。
2、filp_close()函數
函數原型如下:
int filp_close(struct file *, fl_owner_t id);
參數1就是filp_open()函數的返回值。
參數2一般填0即可。
返回值表示這個文件的關閉結果,值0表示成功關閉。
3、vfs_read()函數
函數原型如下:
ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *);
參數1是filp_open()函數的返回值。
參數2是用來存放所讀取的內容的數組。這里需要注意默認情況下這個參數是要用在用戶態下申請的字符數組的。如果非要用在內核態下申請的字符數組,則要進行另一番操作,這個操作在記錄在下面。
參數3表示期望讀取的最大大小。
參數4表示讀取位置,即用來記錄本次讀取時所讀過的數據長度的。可以將它理解成一個定位,一個標尺,是為了能在下一次讀取時接在上一次讀取的末尾而設定的。
返回值就是實際讀取到的數據大小。
如果參數2要直接使用在內核態申請的字符數組,則要在調用這個函數之前先執行一下以下代碼:
mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS);
並在讀取完畢后再執行一下以下代碼:
set_fs(old_fs);
如果不這樣做又直接給參數2傳遞在內核態申請的空間的話,vfs_read() 函數會直接返回一個 -14 的錯誤碼回來。這個錯誤碼被定義在 ./kernel/include/uapi/asm-generic/errno-base.h 中。一定要注意要在申請內存之前先執行 set_fs(KERNEL_DS),並在釋放了申請的內存以后再執行set_fs(old_fs)。
4、vfs_write()函數
函數原型如下:
ssize_t vfs_write(struct file *, const char __user *, size_t, loff_t *);
這些參數與返回值與 vfs_read() 是如出一轍的了,就不再贅述了。
在內核態讀取普通文件系統的實例:
以下貼出一個被編譯成 ko 形式的內核驅動程序讀取文件系統中的文件的演示代碼。
需要強調的是,這份代碼是以 ko 形式,在 Android 系統開發板穩定運行以后 insmod 到系統中運行的。筆者並沒有嘗試過將這份驅動程序直接打包到內核鏡像中隨系統啟動而運行的模式下是否能正常運行。
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/delay.h> #include <linux/string.h> #include <linux/uaccess.h> #include <linux/slab.h> static struct file *fp; static struct file *wfp; static int __init init() { printk("%s()\n", __FUNCTION__); #define FN "/sdcard/wanna" /* filp_open()是一個異步執行函數。它會異步打開指定的文件。 如果打開了文件后面沒干其它事直接就結束這個函數,那么就很有可能出現后面的打印不顯示的現象。 */ fp = filp_open("/sdcard/wanna", O_RDONLY, 0); //參數3的文件模式對於讀文件而言作用不大。 printk("fs file address:0x%p\n", fp); msleep(100); if(IS_ERR(fp))//要用這個 IS_ERR() 來檢查指針是否有效,而不能直接判斷指針是否為NULL。 { printk("cannot open fs.\n"); goto FS_END; } char *out_file_name; const int of_len = strlen(FN) + 5; out_file_name = kmalloc(of_len, GFP_KERNEL); if(out_file_name == NULL) { printk("cannot malloc.\n"); goto FS_END; } memset(out_file_name, 0, of_len); snprintf(out_file_name, of_len, "%s%s", FN, "_out"); printk("out_file_name:%s\n", out_file_name); wfp = filp_open(out_file_name, O_WRONLY|O_CREAT, 0666); msleep(100); if(IS_ERR(wfp)) { printk("cannot open the write file.\n"); wfp = NULL; } mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); int size = 0; char rbuf[6]; loff_t pos = 0; loff_t wpos = 0; while(1) { memset(rbuf, 0, 6); /* 參數2要求是 __user 空間的內存地址, 如果要直接使用內核中創建的數組, 則在使用之前應先將文件系統狀態切換到 KERNEL_DS 態。即上面的 set_fs(KERNEL_DS) 調用。 參數3是讀取指針的位置,對於普通文件而言,它必須傳入一個有效的 loff_t 指針以實現“斷點續讀”的功能。 */ size = vfs_read(fp, rbuf, 3, &pos); printk("read ret:%d, pos:%ld\n", size, pos); if(size < 1) { printk("read end.\n"); break; } printk("\t%s\n", rbuf); if(wfp) { //將前面讀出來的文件內容復制到另外一個文件中去。 size = vfs_write(wfp, rbuf, size, &wpos); printk("write ret:%d, pos:%ld\n", size, wpos); } } set_fs(old_fs); msleep(50); FS_END: return 0; } static void __exit exit() { if(!IS_ERR(fp)) { printk("closing fs file.\n"); int ret = filp_close(fp, NULL); printk("close ret:%d\n", ret); } if(wfp && !IS_ERR(wfp)) { printk("closing wfp.\n"); int ret = filp_close(wfp, 0); printk("close wfp ret:%d\n", ret); } msleep(100); } module_init(init); module_exit(exit); MODULE_LICENSE("GPL");
順便貼一下 Makefile:
obj-m += mymodule.o KDIR := /home/chorm/workspace/my_android_src/kernel PWD ?= $(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: