您的位置:首页 > 运维架构 > Linux

在linux内核中操作文件的方法--使用get_fs()和set_fs(KERNEL_DS) .bin file write/read

2012-12-18 16:05 696 查看
例1:

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/fs.h>

#include <linux/string.h>

#include <linux/mm.h>

#include <linux/syscalls.h>

#include <asm/unistd.h>

#include <asm/uaccess.h>

#define MY_FILE "/root/LogFile"

char buf[128];

struct file *file = NULL;

static int __init init(void)

{

mm_segment_t old_fs;

printk("Hello, I'm the module that intends to write messages to file.\n");

if(file == NULL)

file = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);

if (IS_ERR(file)) {

printk("error occured while opening file %s, exiting...\n", MY_FILE);

return 0;

}

sprintf(buf,"%s", "The
Messages.");

old_fs = get_fs();

set_fs(KERNEL_DS);

file->f_op->write(file, (char *)buf, sizeof(buf), &file->f_pos);

set_fs(old_fs);

return 0;

}

static void __exit fini(void)

{

if(file != NULL)

filp_close(file, NULL);

}

module_init(init);

module_exit(fini);

MODULE_LICENSE("GPL");

其中:

typedef struct {

unsigned long seg;

} mm_segment_t;

#define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFFUL)

#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })

基本思想:

一个是要记得编译的时候加上-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中定义。

个人感觉这个办法比较简单。



另外就是用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进行比较,从而防止用户空间程序“蓄意”破坏内核空间;

而现在要在内核空间使用系统调用,此时传递给->write()的参数地址就是内核空间的地址了,在USER_DS之上(USER_DS ~ KERNEL_DS),如果不做任何其它处理,在write()函数中,会认为该地址超过了USER_DS范围,所以会认为是用户空间的“蓄意破坏”,从 而不允许进一步的执行; 为了解决这个问题; set_fs(KERNEL_DS);将其能访问的空间限制扩大到KERNEL_DS,这样就可以在内核顺利使用系统调用了!

补充:

我看了一下源码,在include/asm/uaccess.h中,有如下定义:

#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })

#define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFF)

#define USER_DS MAKE_MM_SEG(PAGE_OFFSET)

#define get_ds() (KERNEL_DS)

#define get_fs() (current->addr_limit)

#define set_fs(x) (current->addr_limit = (x))

而它的注释也很清楚:

/*

* The fs value determines whether argument validity checking should be

* performed or not. If get_fs() == USER_DS, checking is performed, with

* get_fs() == KERNEL_DS, checking is bypassed.

*

* For historical reasons, these macros are grossly misnamed.

*/

因此可以看到,fs的值是作为是否进行参数检查的标志。系统调用的参数要求必须来自用户空间,所以,当在内核中使用系统调用的时候,set_fs(get_ds())改变了用户空间的限制,即扩大了用户空间范围,因此即可使用在内核中的参数了。

例2:

将源文件中的内容读出,存入到数组;再将数组中内容写入到目标文件中,若目标文件和源文件内容相同,则文件读、写均正常。

#define
MY_FILE "/system/etc/aux.bin" //二进制源文件

#define CACL_DATA "/system/etc/aux_test.bin" //二进制目标文件

static unsigned char temp[94][1024];

struct file *filer=NULL,*filew=NULL;

mm_segment_t old_fs;

int index,ReadNum,WriteNum;

printk("aux.bin read start\n");

if(filer==NULL)

{

filer=filp_open(MY_FILE,O_RDWR,0644);

}

if(IS_ERR(filer))

{

printk("filer open failed\n");

return 0;

}

if(filew==NULL)

{

filew=filp_open(CACL_DATA,O_RDWR|O_APPEND|O_CREAT,0644);//O_APPEND定位到文件末尾;O_CREAT目标文件不存在,则创建

}

if(IS_ERR(filew))

{

printk("filew open failed\n");

return 0;

}

old_fs=get_fs();

set_fs(KERNEL_DS);

ReadNum = filer->f_op->read(filer,(char
*)temp,94*1024*sizeof(unsigned char),&filer->f_pos);//读出源文件

WriteNum = filew->f_op->write(filew,(char
*)temp,94*1024*sizeof(unsigned char),&filew->f_pos);//写入目标文件

printk("ReadNum:%d WriteNum:%d\n",ReadNum,WriteNum);

set_fs(old_fs);

firmware_update_test();//update
firware,升级成功,则打印"update OK"

实验结果为:

<4>[ 6.321000] aux.bin read start

<4>[ 6.349000] ReadNum:96256 WriteNum:96256 //读出源文件的字节数 == 写入目标文件的字节数

<4>[ 6.349000] firmware_update_test

<4>[ 16.741000] update OK

用BC比较/system/etc/aux.bin
和 /system/etc/aux_test.bin,它们二进制相同。即文件读取和写入成功。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: