内核读写磁盘文件

在用户态,即我们一般编写的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

评论

此博客中的热门博文

I/O映射之I/O端口

通过Netlink检测网线插拔

使用seq_file