Linux内核态文件读写相关函数API
1、前言
Linux系统中的文件系统由两层结构进行构建:第一层为虚拟文件系统(VFS),第二层则是各种不同的具体的文件系统。VFS则是将各种具体的文件系统的公共部分抽取出来,从而形成一个抽象层,是Linux系统内核的一部分,它位于用户程序和具体的文件系统之间,对用户提供了标准的文件系统调用接口,对于具体的文件系统,通过一系列的对不同文件系统公用的函数指针来实际调用具体文件系统的函数,完成实际的各种差异操作。对于用户,对文件的读写操作时,可以使用函数open()、read()、write()和close()等,当在Linux内核中,肯定是没有这些函数可以使用,这个时候,可以使用内核的一些函数filp_open()、vfs_read()、vfs_write()、和filp_close()等去完成文件的读写。
2、常用API接口
接下来,简单介绍相关的内核API接口是如何使用的,对于filp_open()、filp_close()、vfs_read()、vfs_write()函数的声名在文件linux/fs.h中。
(1)文件打开filp_open
函数原型如下:
struct file *filp_open(const char *filename, int flags, umode_t mode);
参数说明:
filename:要打开或创建文件的字符串名称,包括路径部分;
flags:文件的打开方式,该取值与open()函数类似,可以取O_CREATE、O_RDWR、O_RDONLY;
mode:创建文件时使用该参数,设置文件的读写权限,其它情况可以设置为0。
返回值:
文件打开成功返回正确的struct file *指针,失败返回错误的指针。
注意:
函数filp_open()将返回struct file *结构指针,将会提供给后继的函数进行使用,需要使用IS_ERR()来校验指针的有效性。
(2)文件关闭filp_close
函数的原型如下:
int filp_close(struct file *filp, fl_owner_t id);
参数说明:
filp:使用filp_opne()函数返回的struct file *结构指针;
id:一般设置为NULL。
返回值:
文件成功关闭时返回0,失败时返回负的错误号。
(3)文件读取vfs_read
函数的原型如下:
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos);
参数说明:
file:函数filp_open()调用后返回的struct file *结构指针;
buf:buf缓冲区,用来存储读取到的数据,该参数具有__user进行修饰,表明buf指向用户空间地址,如果传入内核空间地址时,就会出错,并返回-EFAULT;
count:要读取的数据字节个数;
pos:返回数据读取后的文件指针。
返回值:
文件读取成功时,返回读取到字节数,如果失败,返回负的错误号。
(4)文件写入vfs_write
函数的原型如下:
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
参数说明:
file:函数filp_open()调用后返回的struct file *结构指针;
buf:buf缓冲区,用来存储要写入文件中的数据,该参数具有__user进行修饰,表明buf指向用户空间地址,如果传入内核空间地址时,就会出错,并返回-EFAULT;
count:要写入的数据字节个数;
pos:返回数据写入后的文件指针。
返回值:
文件写入成功时,返回写入到的字节个数,如果失败,返回负的错误号。
注意:
对于vfs_read()和vfs_write()函数,参数buf是使用__user进行修饰的,表明buf指向用户空间地址,由于函数是在内核态中使用,因此,需要使用函数改变kernel对内存地址检查的处理方式,可以使用下面的函数:
static inline void set_fs(mm_segment_t fs)
对于set_fs()函数中的参数fs,具有两个取值,分别是USER_DS和KERNEL_DS,分别代表了用户空间和内核空间,在默认情况下,内核取值为USER_FS,也就是对用户空间地址检查并变换,因此,需要使用
set_fs(KERNEL_DS)
进行转换,改变kernel对内核空间地址检查,防止函数调用时出错,该函数的一般用法如下:
filp_open(); mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); ... ... /* 内存相关的操作 */ ... set_fs(old_fs);
以上就是,在内核态进行文件操作的相关操作API接口,对于函数的具体实现,可以查看内核源码。
3、驱动模块实例
接下来,将进行一个简单的实例进行说明上面介绍的函数API接口使用,编写一个简单的内核模块filp_open,在模块加载的时候,在内核态中完成一个test,cfg文件的创建和读写操作,新建filp_open.c文件,代码如下:
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/string.h> #include <linux/sched.h> #include <linux/dcache.h> #include <asm/fcntl.h> #include <asm/processor.h> #include <asm/uaccess.h> static int __init hello_init(void) { int ret; struct file *fp; mm_segment_t old_fs; loff_t pos; char string1[15] = "hello world,"; char string2[15] = "kernel file."; char buf[30]; int len; printk(KERN_INFO "=====hello_init=====\n"); fp = filp_open("/home/hly/test.cfg", O_RDWR | O_CREAT, 0644); if (IS_ERR(fp)) { ret = PTR_ERR(fp); printk(KERN_INFO "/hoem/hly/test.cfg open failed,err = %d\n", ret); return ret; } old_fs = get_fs(); set_fs(KERNEL_DS); pos = fp->f_pos; vfs_write(fp, string1, strlen(string1), &pos); fp->f_pos = pos; pos = fp->f_pos; vfs_write(fp, string2, strlen(string2), &pos); fp->f_pos = pos; memset(buf, 0, sizeof(buf)); len = strlen(string1) + strlen(string2); pos = 0; ret = vfs_read(fp, buf, len, &pos); if (ret < len) { ret = -EINVAL; printk(KERN_INFO "vfs read failed\n"); return ret; } printk(KERN_INFO "f_pos = %lld,pos = %lld,buf = %s\n", fp->f_pos, pos, buf); set_fs(old_fs); filp_close(fp, NULL); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO "hello_exit\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("HLY"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("A simple filp_open() test Module.");
模块比较简单,不介绍了,接下来,为该模块编写Makefile文件,如下:
# Makefile for driver obj-m += filp_open.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
使用命令进行编译,并将模块加载到内核中运行:
$ make $ sudo insmod filp_open.ko
模块加载后,会创建/home/hly/test.cfg文件,并在文件中写入字符串"hello world,kernel file.",将该字符串读取出来并打印效果如下:
到此,关于该测试模块的相关介绍完毕。
4、小结
本文主要简单介绍了Linux内核态中对文件操作的filp_open()、filp_close()、vfs_read()和vfs_write()的相关API接口使用,并给出了一个简单的测试模块。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?