在用户态,即我们一般编写的C语言程序中,可以使用open、close、read、write等系统调用对磁盘文件进行操作,那么在内核态呢?同样有相应的函数可以使用,而这些函数却是open这些系统调用会使用到的,故而我们还是在内核面向应用的层级进入,实现对磁盘文件的操作。关于如何从open一层层往下找到我们即将要使用的函数过程就不说了,下面直接给出我们用到的函数定义或原型信息,都在include/linux/fs.h文件中有声明,分别在fs/open.c和fs/read_write.c实现下面两类操作函数:
1.打开、关闭文件
struct file *filp_open(const char *filename, int flags, umode_t mode) ;
其中,filename就是文件名(包含完整路径),flags必须包含O_RDONLY、O_WRONLY或O_RDWR其中之一,而mode只有在flags包含O_CREAT时才有作用,表示打开的文件不存在就进行创建时该文件的访问权限,可以是S_IRWXU、S_IRUSR或S_IWUSR等的组合,对于O_和S_开头的这些宏说明可使用man 2 open命令查看,而这些宏则分别在内核include/uapi/asm-generic/fcntl.h和include/uapi/linux/stat.h文件中定义。
int filp_close(struct file *filp, fl_owner_t id) ;
其中,filp指针就是filp_open的返回值,id是POSIX线程的ID,而我们在内核态中操作,暂不需要用到,故一般设置为NULL即可。
2.读写文件
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) ;
其中,file指针使用filp_open的返回值,buf是用户空间的内存(读写数据存放位置),count是读写多少单位(字节)的数据,pos表示当前文件读写所处于的偏移位置。
相信大家都注意到了,这儿存取用到的buf是用户空间的内存,但等会我们是在内核里调用到,并且传入的是在内核态申请的空间,这样操作肯定没权限,在内核include/asm-generic/uaccess.h文件里有如下定义:
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
#ifndef KERNEL_DS
#define KERNEL_DS MAKE_MM_SEG(~0UL)
#endif
#ifndef USER_DS
#define USER_DS MAKE_MM_SEG(TASK_SIZE - 1)
#endif
#ifndef get_fs
#define get_ds() (KERNEL_DS)
#define get_fs() (current_thread_info()->addr_limit)
static inline void set_fs(mm_segment_t fs)
{
current_thread_info()->addr_limit = fs;
}
#endif
其中, mm_segment_t在arch/x86/include/asm/processor.h文件中有如下定义:
typedef struct {
unsigned long seg;
} mm_segment_t;
我们从set_fs和get_fs可以看到其对当前线程的地址权限标志作了修改,所以等会我们在内核分配好空间,然后使用vfs_read或vfs_write进行操作时,要先使用get_fs保存状态,再使用set_fs(KERNEL_DS)修改下地址限制,而使用完成后再使用set_fs()恢复保存的状态,接下来通过实例学习下这操作的鬼魅吧:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#define SLAM_FILE_PATH "/home/xinu/slam.txt"
static char wbuf[] = "Hello slam-xinu";
static char rbuf[128];
static int vfs_operate_init(void)
{
struct file * fp;
mm_segment_t cur_mm_seg;
loff_t fpos = 0;
printk("<xinu>in %s!\n",__func__);
fp = filp_open(SLAM_FILE_PATH, O_RDWR | O_CREAT, 0644);
if (IS_ERR(fp)) {
printk("<xinu>filp_open error\n");
return -1;
}
cur_mm_seg = get_fs();
set_fs(KERNEL_DS);
vfs_write(fp, wbuf, sizeof(wbuf), &fpos);
fpos = 0;
vfs_read(fp, rbuf, sizeof(rbuf), &fpos);
printk("<xinu>read content: %s\n", rbuf);
set_fs(cur_mm_seg);
filp_close(fp, NULL);
return 0;
}
static void vfs_operate_exit(void)
{
printk("Bye %s!\n", __func__);
}
module_init(vfs_operate_init);
module_exit(vfs_operate_exit);
MODULE_LICENSE("GPL");
相应的Makefile文件内容如下:
obj-m += vfs_operate_example.o
CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6
all:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
对应的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/vfs_operate_example/
├── Makefile
└── vfs_operate_example.c
该模块在/home/xinu目录下创建slam.txt文件,并往里写入Hello slam-xinu字符串,然后读取打印出来,而该文件对应的信息如下:
xinu@slam:~/$ ls -l /home/xinu/slam.txt
-rw-r--r-- 1 root root 16 May 22 18:37 /home/xinu/slam.txt
xinu@slam:~/$ file /home/xinu/slam.txt
/home/xinu/slam.txt: ASCII text, with no line terminators
从上面显示该文件没有行终止符,所以打开文件时会看到怪异的结束符号。
参考网址:
http://blog.csdn.net/tommy_wxie/article/details/8193954
http://blog.csdn.net/tommy_wxie/article/details/8194276
http://blog.csdn.net/proware/article/details/5959842
http://blog.chinaunix.net/uid-712024-id-2678927.html
网友评论