filp_open/filp_close/vfs_read/vfs_write

时间:2024-10-19 08:06:21

Linux系统成功的关键因素之一就是具有与其他操作系统和谐共存的能力。Linux系统的文件系统由两层结构构建:第一层是虚拟文件系统(VFS),第二层是各种不同的具体的文件系统。 
VFS就是把各种具体的文件系统的公共部分抽取出来,形成一个抽象层,是系统内核的一部分,它位于用户程序和具体的文件系统之间。它对用户提供了标准的文件系统调用接口,对具体的文件系统(如EXT2、FAT32等),它通过一系列的对不同文件系统公用的函数指针来实际调用具体的文件系统函数,完成实际的各有差异的操作。任何使用文件系统的程序必须经过这层接口来使用它。通过这样的方式,VFS就对用户屏蔽了底层文件系统的实现细节和差异。 
在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一样。使用同样的查找方法,我们找出了一组在内核中操作文件的函数,如下:

filp_open/filp_close/vfs_read/vfs_write

我们注意到在vfs_read和vfs_write函数中,其参数buf指向的用户空间的内存地址,如果我们直接使用内核空间的指针,则会返回-EFALUT。所以我们需要使用set_fs()和get_fs()宏来改变内核对内存地址检查的处理方式。 
另外,使用vfs_read()和vfs_write()需要注意的一点是最后的参数loff_t *pos,pos所指向的值要初始化,表明从文件的什么地方开始读写。例如,可以使用 loff_t pos = fp->f_pos; 
实例代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <asm/fcntl.h>
#include <asm/processor.h>
#include <asm/uaccess.h> int __init hello_init(void)
{
unsigned char buf1[12]="hello world.";
unsigned char buf2[12]="kernel file."; struct file *fp;
mm_segment_t fs;
loff_t pos; printk("hello enter\n");
fp = filp_open("/home/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 = fp->f_pos;
vfs_write(fp, buf1, sizeof(buf1), &pos);
fp->f_pos = pos; pos = fp->f_pos;
vfs_write(fp, buf2, sizeof(buf2), &pos);
fp->f_pos = pos; set_fs(fs); filp_close(fp, NULL);
return 0;
} void __exit hello_exit(void)
{
printk("hello exit\n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL"); (转载自 网络 原创不详)