关于用户态的文件操作函数我们知道有open、read、write这些。但是这些的实现都是依赖于库的实现,但是在内核态是没有库函数可用的。最近做测试,在内核态中,需要学习一下在内核态里面的文件操作函数。分为三对出现。
感谢前辈的优秀文章,参考链接在文末
这些的函数就在linux/fs.h和asm/uaccess.h中存在。
1、第一对:filp_open、filp_close–文件开关操作
filp_open
struct file* filp_open(const char* filename, int open_mode, int mode);
第一个参数表明要打开或创建文件的名称(包括路径部分)。 第二个参数文件的打开方式,其取值与标准库中的open相应参数类似,可以取O_CREAT,O_RDWR,O_RDONLY等。 第三个参数创建文件时使用,设置创建文件的读写权限,其它情况可以设为0
filp_close
int filp_close(struct file *filp, fl_owner_t id);
第一个参数是filp_open返回的file结构体指针 第二个参数基本上都是NULL
2、第二对: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);
filp:文件指针,由filp_open()函数返回。 buffer:缓冲区,从文件中读出的数据放到这个缓冲区,向文件中写入数据也在这个缓冲区。 len:从文件中读出或者写入文件的数据长度。 pos:为文件指针的位置,即从什么地方开始对文件数据进行操作。
3、第三队:set_fs、get_fs
在buffer前面都有一个__user修饰符,这要求buffer指针应该指向用户的空间地址。如果在内核中使用上述的两个函数直接进行文件操作,将内核空间的指针传入的时候,函数会返回失败EFAULT。但在Linux内核中,一般不容易生成用户空间的指针,或者不方便独立使用用户空间内存。为了使这两个函数能够正常工作,必须使得这两个函数能够处理内核空间的地址。
使用set_fs()函数可以指定上述两个函数对缓冲区地址的处理方式,原型如下:
- 这个函数改变内核对内存检查的处理方式,将内存地址的检查方式设置为用户指定的方式。参数fs取的值有两个:USER_DS和KERNEL_DS。分别代表用户空间和内核空间。
- 在默认情况下,内核对地址的检查方式为USER_DS,即按照用户空间进行地址检查并进行用户地址空间到内核地址空间的变换。如果函数中要使用内核地址空间,需要使用set_fs(KERNEL_DS)函数进行设置。与set_fs()函数对应,get_fs()函数获得当前的设置,在使用set_fs()之前先调用get_fs()函数获得之前的设置,对文件进行操作后,使用set_fs()函数还原之前的设置。
内核空间文件续写的框架为:
mm_segmen_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); ... set_fs(old_fs)
**注意:**使用vfs_read()和vfs_write()的时候,要注意最后的参数loff_t *pos,pos所指向的值必须要进行初始化,表明从文件的什么位置进行读写。使用此参数可以对续写文件的位置进行设定,这可以完成用户空间中lseek()函数的功能。
整个栗子
1、简单版
下面是一个使用内核空间的文件读函数从文件中读取数据的例子:
ssize_t ReadFile(struct file *filp, char __user *buffer, size_t len, loff_t *pos) { ssize_t count = 0; oldfs = get_fs(); set_fs(KERNEL_DS); count = file->f_op->read(filp, buf, len, &file->f_pos); set_fs(oldfs); return count; }
2、进阶版
源码
//kernel #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> #include <linux/netdevice.h> #include <linux/skbuff.h> #include <linux/netfilter_ipv4.h> #include <linux/inet.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/netlink.h> #include <linux/spinlock.h> #include <net/sock.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/icmp.h> #include <linux/igmp.h> #include <linux/ctype.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/proc_fs.h> #include <linux/string.h> #include <linux/vmalloc.h> #include <asm/uaccess.h> //user #define DBGPRINT printk //内核程序使用file_open来打开文件 struct file *SIPFW_OpenFile(const char *filename, int flags, int mode) { struct file *f = NULL; DBGPRINT("==>SIPFW_OpenFile\n"); f = filp_open(filename, flags, 0); if (!f || IS_ERR(f)) { f = NULL; } DBGPRINT("<==SIPFW_OpenFile\n"); return f; } ssize_t SIPFW_ReadLine(struct file *f, char *buf, size_t len) { #define EOF (-1) ssize_t count = -1; mm_segment_t oldfs; struct inode *inode; //DBGPRINT("==>SIPFW_ReadLine\n"); if (!f || IS_ERR(f) || !buf || len <= 0) { goto out_error; } if (!f || !f->f_inode) { goto out_error; } inode = f->f_inode; if (!(f->f_mode & FMODE_READ)) { goto out_error; } if (f->f_op /*&& f->f_op->read*/) { oldfs = get_fs(); set_fs(KERNEL_DS); count = 0; if (vfs_read(f, buf, 1, &f->f_pos) <= 0) { DBGPRINT("file read failure\n"); goto out; } if (*buf == EOF) { DBGPRINT("file EOF\n"); goto out; } count = 1; while (*buf != EOF && *buf != '\0' && *buf != '\n' && *buf != '\r' && count < len && f->f_pos <= inode->i_size) { buf += 1; count += 1; if (vfs_read(f, buf, 1, &f->f_pos) <= 0) { count -= 1; break; } } } else { DBGPRINT("goto out_error\n"); goto out_error; } if (*buf == '\r'|| *buf =='\n' ||*buf == EOF ) { *buf = '\0'; count -= 1; } else { buf += 1; *buf = '\0'; } out: set_fs(oldfs); out_error: //DBGPRINT("<==SIPFW_ReadLine %d\n", count); return count; } // ssize_t SIPFW_WriteLine(struct file *f, char *buf, size_t len) // { // ssize_t count = -1; // mm_segment_t oldfs; // struct inode *inode; // DBGPRINT("==>SIPFW_WriteLine\n"); // if (!f || IS_ERR(f) || !buf || len <= 0) // { // goto out_error; // } // if (!f || !f->f_dentry || !f->f_dentry->d_inode) // { // goto out_error; // } // inode = f->f_dentry->d_inode; // if (!(f->f_mode & FMODE_WRITE) || !(f->f_mode & FMODE_READ) ) // { // goto out_error; // } // if (f->f_op && f->f_op->read && f->f_op->write) // { // //f->f_pos = f->f_count; // oldfs = get_fs(); // set_fs(KERNEL_DS); // count = 0; // count = f->f_op->write(f, buf, len, &f->f_pos) ; // if (count == -1) // { // goto out; // } // } // else // { // goto out_error; // } // out: // set_fs(oldfs); // out_error: // DBGPRINT("<==SIPFW_WriteLine\n"); // return count; // } void SIPFW_CloseFile(struct file *f) { DBGPRINT("==>SIPFW_CloseFile\n"); if(!f) return; filp_close(f, current->files); DBGPRINT("<==SIPFW_CloseFile\n"); } int SIPFW_HandleConf(void) { int retval = 0,count; int l = 0; char *pos = NULL; struct file *f = NULL; char line[256] = { 0 }; DBGPRINT("==>SIPFW_HandleConf\n"); // 提前建一个/etc/sipfw.conf文件吧 f = SIPFW_OpenFile("/etc/sipfw.conf", O_RDWR, 0); if(f == NULL) { retval = -1; DBGPRINT("SIPFW_OpenFile called failure\n"); goto EXITSIPFW_HandleConf; } while((count = SIPFW_ReadLine(f, line, 256)) > 0) { pos = line; DBGPRINT("line = %d, data: %s\n", l, line);\ l++; memset(line, 0, sizeof(line)); } SIPFW_CloseFile(f); EXITSIPFW_HandleConf: DBGPRINT("<==SIPFW_HandleConf\n"); return retval; } static int __init SIPFW_Init(void) { int ret = -1; DBGPRINT("==>SIPFW_Init\n"); ret = SIPFW_HandleConf(); DBGPRINT("<==SIPFW_Init\n"); return ret; } static void __exit SIPFW_Exit(void) { DBGPRINT("==>SIPFW_Exit\n"); DBGPRINT("<==SIPFW_Exit\n"); } module_init(SIPFW_Init); module_exit(SIPFW_Exit); MODULE_LICENSE("GPL/BSD");
Makefile:
MODULE_NAME :=main_file obj-m :=$(MODULE_NAME).o KERNELDIR = /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
测试中读取的文件:(/etc/sipfw.conf)
运行效果:
https://www.freesion.com/images/776/0b01da97ef33a4353bbbd347575fd938.png