内核态文件操作
在用户态,我们操作文件可以用C库函数:open()、read()、write()等,但是在内核态没有库函数可用,这时就需要用内核的一些函数:filp_openfilp_closevfs_readvfs_writeset_fsget_fs等函数,

在下列文件中声明:

/usr/lib/modules/3.10.0-514.el7.x86_64/build/include/linux/fs.h

/usr/lib/modules/3.10.0-514.el7.x86_64/build/include/asm-generic/uaccess.h

/usr/src/kernels/3.10.0-514.el7.x86_64/include/linux/err.h

filp_open

extern struct file *filp_open(const char *, int, umode_t);

参数说明:

第一个参数表明要打开或创建文件的名称(包括路径部分)。

第二个参数文件的打开方式,其取值与标准库中的open相应参数类似,可以取O_CREAT,O_RDWR,O_RDONLY等。

第三个参数创建文件时使用,设置创建文件的读写权限,其它情况可以设为0

该函数返回strcut file*结构指针,供后继函数操作使用,该返回值用IS_ERR()来检验其有效性。

filp_close

extern int filp_close(struct file *, fl_owner_t id);

参数说明:

第一个参数是filp_open返回的file结构体指针

第二个参数基本上都是NULL

vfs_read/write

extern ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *);
extern ssize_t vfs_write(struct file *, const char __user *, size_t, loff_t *);

参数说明:

第一个参数是filp_open返回的file结构体指针

第二个参数是buf,注意,这个参数有用__user修饰,表明buf指向用户空间的地址,如果传入内核空间的地址,就会报错,并返回-EFAULT,

但在kernel中,要使这两个读写函数使用kernel空间的buf指针也能正确工作,需要使用set_fs()

set_fs

static inline void set_fs(mm_segment_t fs)

该函数的作用是改变kernel对内存地址检查的处理方式,

其实该函数的参数fs只有两个取值:USER_DS,KERNEL_DS,分别代表用户空间和内核空间,

默认情况下,kernel取值为USER_DS,即对用户空间地址检查并做变换。

那么要在这种对内存地址做检查变换的函数中使用内核空间地址,就需要使用set_fs(KERNEL_DS)进行设置,

它的作用是取得当前的设置,这两个函数的一般用法为:

filp_open()
mm_segment_t old_fs;
old_fs = get_fs();
set_fs(KERNEL_DS);
...... //与内存有关的操作
set_fs(old_fs);
filp_close

第三个参数表明文件要读写的起始位置。

几点说明:(从网上查找的资料)

Linux Kernel组成员不赞成在kernel中独立的读写文件(这样做可能会影响到策略和安全问题),对内核需要操作的文件内容,最好由应用层配合完成。

这些函数的正确运行需要依赖于进程环境,因此,有些函数不能在中断的handle或Kernel中不属于任何进程的代码中执行,否则可能出现崩溃,要避免这种情况发生,可以在kernel中创建内核线程,将这些函数放在线程环境下执行。


#ifndef _LINUX_ERR_H
#define _LINUX_ERR_H
#include <linux/compiler.h>
#include <asm/errno.h>
/*
 * Kernel pointers have redundant information, so we can use a
 * scheme where we can return either an error code or a dentry
 * pointer with the same return value.
 *
 * This should be a per-architecture thing, to allow different
 * error and pointer decisions.
 */
#define MAX_ERRNO       4095

#ifndef __ASSEMBLY__

#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)

static inline void * __must_check ERR_PTR(long error)
{
        return (void *) error;
}

static inline long __must_check PTR_ERR(const void *ptr)
{
        return (long) ptr;
}

static inline long __must_check IS_ERR(const void *ptr)
{
        return IS_ERR_VALUE((unsigned long)ptr);
}

static inline long __must_check IS_ERR_OR_NULL(const void *ptr)
{
        return !ptr || IS_ERR_VALUE((unsigned long)ptr);
}

内核中的函数常常返回指针,问题是如果出错,也希望能够通过返回的指针体现出来。

总体来说,如果内核返回一个指针,那么有三种情况:合法指针,NULL指针和非法指针。

在linux中有很多错误,内核错误可以参考include/asm-generic/errno-base.h。

MAX_ERRNO定义了最大的错误号4095,刚好是4k-1,所以内核地址保留了0xfffffffffffff000~0xffffffffffffffff(64位系统)用来记录错误号,也就是说这段地址和Linux的错误号是一一对应的,可以用上面的内联函数相互转化。

比如说我们上面的filp_open函数返回值,用IS_ERR函数去检查,如果地址落在0xfffffffffffff000~0xffffffffffffffff范围,

表示filp_open函数失败,IS_ERR为1,同时filp_open返回的错误地址对应一个linux的错误号,

如果想知道是哪个错误号,就用PTR_ERR函数来转化。

错误的返回地址和错误号是可以使用 ERR_PTR、PTR_ERR 相互转化的。