一般可以用两种方法:
第一种是用系统调用。
第二种方法是filp->open()等函数。下面分别来说下这两种方法。
1 利用系统调用:
sys_open,sys_write,sys_read等。
其实分析过sys_open可以知道,最后调用的也是filp->open。
sys_open ==> do_sys_open ==> filp->open
其实sys_open最后也是调用了filp->open。
其实好像Linux2.6.20后面就不推荐使用sys_open,那我们这里就就后者进行详细的介绍
2 filp->open等函数。
在模块中,用户空间的open,read,write,llseek等函数都是不可以使用的。应该使用其在内核中对应的函数。可以使用filp->open配合struct file里的read/write来进行对文件的读写操作。
struct file
struct file结构体定义在include/linux/fs.h中定义。
文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。
它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。
在内核创建和驱动源码中,struct file的指针通常被命名为file或filp。如下所示:
struct file {
union {
struct list_head fu_list; 文件对象链表指针linux/include/linux/list.h
struct rcu_head fu_rcuhead; RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制
} f_u;
struct path f_path; 包含dentry和mnt两个成员,用于确定文件路径
#define f_dentry f_path.dentry f_path的成员之一,当前文件的dentry结构
#define f_vfsmnt f_path.mnt 表示当前文件所在文件系统的挂载根目录
const struct file_operations *f_op; 与该文件相关联的操作函数
atomic_t f_count; 文件的引用计数(有多少进程打开该文件)
unsigned int f_flags; 对应于open时指定的flag
mode_t f_mode; 读写模式:open的mod_t mode参数
off_t f_pos; 该文件在当前进程中的文件偏移量
struct fown_struct f_owner; 该结构的作用是通过信号进行I/O时间通知的数据。
unsigned int f_uid, f_gid; 文件所有者id,所有者组id
struct file_ra_state f_ra; 在linux/include/linux/fs.h中定义,文件预读相关
unsigned long f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
例子1:
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <asm/uaccess.h>
- #include <linux/mm.h>
- MODULE_AUTHOR("Kenthy@163.com.");
- MODULE_DESCRIPTION("Kernel study and test.");
- void fileread(const char * filename)
- {
- struct file *filp; // 文件结构体代表一个打开的文件
- struct inode *inode; //内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符
- mm_segment_t fs;
- /*其中:
typedef struct {
unsigned long seg;
} mm_segment_t;
*/
- off_t fsize;
- char *buf;
- unsigned long magic;
- printk("<1>start....\n");
- filp=filp_open(filename,O_RDONLY,0); //文件结构
- inode=filp->f_dentry->d_inode; //和具体文件联系一起
-
- magic=inode->i_sb->s_magic; //
- printk("<1>file system magic:%li \n",magic);
- printk("<1>super blocksize:%li \n",inode->i_sb->s_blocksize);
- printk("<1>inode %li \n",inode->i_ino);
- fsize=inode->i_size;
- printk("<1>file size:%i \n",(int)fsize);
- buf=(char *) kmalloc(fsize+1,GFP_ATOMIC); //
- fs=get_fs();
- set_fs(KERNEL_DS);
- filp->f_op->read(filp,buf,fsize,&(filp->f_pos));
- set_fs(fs);
- buf[fsize]='\0';
- printk("<1>The File Content is:\n");
- printk("<1>%s",buf);
- filp_close(filp,NULL);
- }
- void filewrite(char* filename, char* data)
- {
- struct file *filp;
- mm_segment_t fs;
- filp = filp_open(filename, O_RDWR|O_APPEND, 0644);
- if(IS_ERR(filp))
- {
- printk("open error...\n");
- return;
- }
- fs=get_fs();
- set_fs(KERNEL_DS);
- filp->f_op->write(filp, data, strlen(data),&filp->f_pos);
- set_fs(fs);
- filp_close(filp,NULL);
- }
- int init_module()
- {
- char *filename="/root/test1.c";
- printk("<1>Read File from Kernel.\n");
- fileread(filename);
- filewrite(filename, "kernel write test\n");
- return 0;
- }
- void cleanup_module()
- {
- printk("<1>Good,Bye!\n");
- }
基本思想:
一个是要记得编译的时候加上-D__KERNEL_SYSCALLS__
另外源文件里面要#include <linux/unistd.h>
如果报错,很可能是因为使用的缓冲区超过了用户空间的地址范围。
一般系统调用会要求你使用的缓冲区不能在内核区。这个可以用set_fs()、get_fs()来解决。
在读写文件前先得到当前fs:
mm_segment_t old_fs=get_fs();
并设置当前fs为内核fs:
set_fs(KERNEL_DS);
在读写文件后再恢复原先fs:
set_fs(old_fs);
set_fs()、get_fs()等相关宏在文件include/asm/uaccess.h中定义。
个人感觉这个办法比较简单。
在linux内核编程时,进行系统调用(如文件操作)时如果要访问用户空间的参数,可以用set_fs,get_ds等函数实现访问。
get_ds获得kernel的内存访问地址范围(IA32是4GB),
set_fs是设置当前的地址访问限制值,
get_fs是取得当前的地址访问限制值。
进程由用户态进入核态,linux进程的task_struct结构中的成员addr_limit也应该由0xBFFFFFFF变为0xFFFFFFFF(addr_limit规定了进程有用户态核内核态情况下的虚拟地址空间访问范围,在用户态,addr_limit成员值是0xBFFFFFFF也就是有3GB的虚拟内存空间,在核心态,是0xFFFFFFFF,范围扩展了1GB)。
使用这三个函数是为了安全性。为了保证用户态的地址所指向空间有效,函数会做一些检查工作。
如果set_fs(KERNEL_DS),函数将跳过这些检查。
另外就是用flip_open函数打开文件,得到struct file *的指针fp。使用指针fp进行相应操作,如读文件可以用fp->f_ops->read。最后用filp_close()函数关闭文件。 filp_open()、filp_close()函数在fs/open.c定义,在include/linux/fs.h中声明。
解释一点:
系统调用本来是提供给用户空间的程序访问的,所以,对传递给它的参数(比如上面的buf),它默认会认为来自用户空间,在->write()函数 中,为了保护内核空间,一般会用get_fs()得到的值来和USER_DS进行比较,从而防止用户空间程序“蓄意”破坏内核空间;
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
eg2:
- #include<linux/module.h>
- #include<linux/kernel.h>
- #include<linux/init.h>
- #include<linux/types.h>
- #include<linux/fs.h>
- #include<linux/string.h>
- #include<asm/uaccess.h> /* get_fs(),set_fs(),get_ds() */
- #define FILE_DIR "/root/test.txt"
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("kenthy@163.com");
- char *buff = "module read/write test";
- char tmp[100];
- static struct file *filp = NULL;
- static int __init wr_test_init(void)
- {
- mm_segment_t old_fs;
- ssize_t ret;
-
- filp = filp_open(FILE_DIR, O_RDWR | O_CREAT, 0644);
-
- // if(!filp)
- if(IS_ERR(filp))
- printk("open error...\n");
-
- old_fs = get_fs();
- set_fs(get_ds());
-
- filp->f_op->write(filp, buff, strlen(buff), &filp->f_pos);
-
- filp->f_op->llseek(filp,0,0);
- ret = filp->f_op->read(filp, tmp, strlen(buff), &filp->f_pos);
-
- set_fs(old_fs);
-
- if(ret > 0)
- printk("%s\n",tmp);
- else if(ret == 0)
- printk("read nothing.............\n");
- else
- {
- printk("read error\n");
- return -1;
- }
- return 0;
- }
- static void __exit wr_test_exit(void)
- {
- if(filp)
- filp_close(filp,NULL);
- }
- module_init(wr_test_init);
- module_exit(wr_test_exit);
3.Makefile
- obj-m := os_attack.o
- KDIR := /lib/modules/$(uname -r)/build/
- PWD := $(shell pwd)
- all:module
- module:
- $(MAKE) -C $(KDIR) M=$(PWD) modules
- clean:
- rm -rf *.ko *.mod.c *.o Module.* modules.* .*.cmd .tmp_versions
注意:
在调用filp->f_op->read和filp->f_op->write等对文件的操作之前,应该先设置FS。
默认情况下,filp->f_op->read或者filp->f_op->write会对传进来的参数buff进行指针检查。如果不是在用户空间会拒绝访问。因为是在内核模块中,所以buff肯定不在用户空间,所以要增大其寻址范围。
拿filp->f_op->write为例来说明:
filp->f_op->write最终会调用access_ok ==> range_ok.
而range_ok会判断访问的地址是否在0 ~ addr_limit之间。如果在,则ok,继续。如果不在,则禁止访问。而内核空间传过来的buff肯定大于addr_limit。所以要set_fs(get_ds())。
这些函数在asm/uaccess.h中定义。以下是这个头文件中的部分内容:
#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })
#define KERNEL_DS MAKE_MM_SEG(-1UL)
#define USER_DS MAKE_MM_SEG(PAGE_OFFSET)
#define get_ds() (KERNEL_DS)
#define get_fs() (current_thread_info()->addr_limit)
#define set_fs(x) (current_thread_info()->addr_limit = (x))
#define segment_eq(a, b) ((a).seg == (b).seg)
可以看到set_fs(get_ds())改变了addr_limit的值。这样就使得从模块中传递进去的参数也可以正常使用了。
在写测试模块的时候,要实现的功能是写进去什么,然后读出来放在tmp数组中。但写完了以后filp->f_ops已经在末尾了,这个时候读是什么也读不到的,如果想要读到数据,则应该改变filp->f-ops的值,这就要用到filp->f_op->llseek函数了。上网查了下,其中的参数需要记下笔记:
系统调用:
off_t sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
offset是偏移量。
若origin是SEEK_SET(0),则将该文件的位移量设置为距文件开始处offset 个字节。
若origin是SEEK_CUR(1),则将该文件的位移量设置为其当前值加offset, offset可为正或负。
若origin是SEEK_END(2),则将该文件的位移量设置为文件长度加offset, offset可为正或负。