Chinaunix首页 | 论坛 | 博客
  • 博客访问: 353583
  • 博文数量: 90
  • 博客积分: 2017
  • 博客等级: 大尉
  • 技术积分: 615
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-19 08:10
文章分类

全部博文(90)

文章存档

2012年(4)

2011年(74)

2010年(11)

2009年(1)

分类: LINUX

2011-03-17 15:06:21

#include

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #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  

  如果报错,很可能是因为使用的缓冲区超过了用户空间的地址范围。一般系统调用会要求你使用的缓冲区不能在内核区。这个可以用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())改变了用户空间的限制,即扩大了用户空间范围,因此即可使用在内核中的参数了。

  编辑特别推荐:

  

  

  

  

阅读(955) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~