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()宏来改变内核对内存地址检查的处理⽅式,所以在内核空间对⽂件的读写流程为:
1. mm_segment_t fs = get_fs();
2. set_fs(KERNEL_FS);
3. //vfs_write();
4. vfs_read();
5. set_fs(fs);
下⾯为⼀个在内核中对⽂件操作的例⼦:
1. #include <linux/module.h>
2. #include <linux/init.h>
3. #include <linux/fs.h>
4. #include <linux/uaccess.h>
5. static char buf[] = "你好";
6. static char buf1[10];
7. int __init hello_init(void)
8. {
9.    struct file *fp;linux内核文件放在哪
10.    mm_segment_t fs;
11.    loff_t pos;
12.    printk("hello enter\n");
13.    fp = filp_open("/home/niutao/kernel_file", O_RDWR | O_CREAT, 0644);
14.    if (IS_ERR(fp)) {
15.        printk("create file error\n");
16. return -1;
17.    }
18.    fs = get_fs();
19.    set_fs(KERNEL_DS);
20.    pos = 0;
21.    vfs_write(fp, buf, sizeof(buf), &pos);
22.    pos = 0;
23.    vfs_read(fp, buf1, sizeof(buf), &pos);
24.    printk("read: %s\n", buf1);
25.    filp_close(fp, NULL);
26.    set_fs(fs);
27. return 0;
28. }
29. void __exit hello_exit(void)
30. {
31.    printk("hello exit\n");
32. }
33. module_init(hello_init);
34. module_exit(hello_exit);
35. MODULE_LICENSE("GPL");
36.
本⽂作者:佚名
来源:51CTO

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。