next up previous contents index
Next: 和设备文件对话 Up: Linux 内核模块编程 Previous: /proc 文件系统

       
将 /proc 作为输入

迄今为止,我们有两中办法从内核模块中产生输出:我们可以登记一个设备驱动程序并 mknod 一个设备文件,或者我们可以创建一个/proc文件。这可以让内核模块告诉我们任何它可能告诉我们的事情。唯一的问题是这没有办法让我们告诉它。我们将输入发送给内核模块的第一个办法将是通过写回 /proc 文件。

因为 proc 文件系统主要是为了让内核报告它对进程的状态,所以对输入没有专门的预备。 proc_dir_entry结构没有包含一个指向输入函数的指针而包含输出函数的指针。为了向/proc 文件中写,我们需要使用标准的文件系统机制。    

在 Linux 中对文件系统登记有标准的机制。既然每个文件系统必须有它自己的处理节点和文件操作4.1的函数, 所以有一个特殊的结构保存所有这些函数的指针, inode_operations 结构, 包含一个指向 file_operations结构的指针。在 /proc 中,任何时候登记一个新文件我们都允许特别指定哪个 inode_operations 结构将用于访问它。这就是我们使用的机制, inode_operations 结构包含指向 file_operations 结构的指针,而它又包含指向我们的module_inputmodule_output 函数的指针。      

注意在内核中标准的读写的任务是颠倒的。读函数用作输出而写函数用于输入。造成这个局面的原因是读写是依据用户的观点--如果一个进程从内核中读什么,那么内核就需要输出它,而如果进程向内核中写什么,那么内核就需要将它作为输入接收。    

这儿另一个引起注意的地方是 module_permission 函数。这个函数在进程试图用 /proc文件做什么的时候被调用,并且它决定是否允许访问。现在它仅仅基于操作和当前用户的UID(就像 current 中的那样,一个指向包含当前运行进程的信息的结构的指针),但它也可以基于任何我们喜欢的东西,例如其他进程在用该文件做什么,时间,或者我们上次的接收的输入。        

使用 put_userget_user 的原因是在 Linux 中内存 (在Intel 架构下,在其他处理器下可能不同)是分段的。这意味着指针不能单独由它自己指向一个唯一的内存位置,只是在内存的段中的位置,你需要知道它可以使用哪个内存段。对内核只有一个内存段,其他进程也各有一个。        

进程只能访问自己的内存段,因此当写普通的作为进程运行的程序时我们不必为段操心。当你写内核模块时,通常你想访问内核的内存段,它由系统自动的处理。然而,当内存缓冲区中的内容需要在当前进程和内核中传递时,内核的函数收到的是指向位于进程的内存段中的内存缓冲区的指针。 put_userget_user 宏可以让你访问那些内存。

范例 procfs.c   

 
/* procfs.c -  创建一个可以输入输出的在 /proc 中的“文件”。 */

/* Copyright (C) 1998-1999 by Ori Pomerantz */


/* 必要头文件 */

/* 标准头文件 */
#include <linux/kernel.h>   /* 内核工作 */
#include <linux/module.h>   /* 明确指定是模块 */

/* 处理 CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include <linux/modversions.h>
#endif        

/* 使用 proc 文件系统所必要的 */
#include <linux/proc_fs.h>


/* 在 2.2.3 版/usr/include/linux/version.h 包含这个宏,但是
 * 在 2.0.35 版中不包含--因此加入这个以备需要。 */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif



#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
#include <asm/uaccess.h>  /* 得到 get_user 和 put_user */
#endif

/* 模块的文件函数 ********************** */


/* 在这儿保存上次收到的信息以证明我们可以处理我们的输入。 */
#define MESSAGE_LENGTH 80
static char Message[MESSAGE_LENGTH];


/* 既然我们使用了文件操作结构我们就不能使用预备的那个特殊的proc输出函数
 * 我们不得不使用标准的读函数,就是这个函数。*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t module_output(
    struct file *file,   /* 要读的文件 */
    char *buf, /* 要将数据放入的缓冲区(在用户内存段中) */
    size_t len,  /* 缓冲区长度 */
    loff_t *offset) /* 文件偏移量--忽略 */
#else
static int module_output(
    struct inode *inode, /* 要读的节点 */
    struct file *file,   /* 要读的文件 */
    char *buf, /* 要将数据放入的缓冲区(在用户内存段中) */
    int len)  /* 缓冲区长度 */
#endif
{
  static int finished = 0;
  int i;
  char message[MESSAGE_LENGTH+30];

  /* 在没有更多信息的时候返回0以指明文件尾。否则进程会从我们的无穷循环中不断的读。 */
  if (finished) {
    finished = 0;
    return 0;
  }

  /* 我们使用 put_user 将字符串从内核的内存段中拷贝到调用我们的文件的进程的内存段中。
   * 顺便说一下, get_user的用法相反。*/
  sprintf(message, "Last input:%s", Message);
  for(i=0; i<len && message[i]; i++) 
    put_user(message[i], buf+i);


  /* 注意,我们假设消息的长度小于 len,或者被截短。在真实的情况下,如果消息的长度小于
   * len 那么我们会返回 len 而在下次调用时将用消息的第 len+1 个字节开始填充。 */
  finished = 1; 

  return i;  /* 返回“读”到的字节 */
}


/* 当用户向/proc文件中写时这个函数接收从用户来的输入。 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t module_input(
    struct file *file,   /* 文件自己 */
    const char *buf,     /* 存有输入的缓冲区 */
    size_t length,       /* 缓冲区长度 */
    loff_t *offset)      /* 文件偏移量--忽略 */
#else
static int module_input(
    struct inode *inode, /* 文件节点 */
    struct file *file,   /* 文件自己 */
    const char *buf,     /* 存有输入的缓冲区 */
    int length)          /* 缓冲区长度 */
#endif
{
  int i;

  /* 将输入存入 Message,module_output 将在以后能使用它。 */
  for(i=0; i<MESSAGE_LENGTH-1 && i<length; i++)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    get_user(Message[i], buf+i);
  /* 在 2.2 版中 get_user 的语义改变了,它不再返回一个字符而是期待将一个变量作为它的第一参数
   * 和一个用户内存段的指针作为第二参数。
   *
   * 这个改变的原因是在 2.2 版中, get_user 也可以读短整型数或整数。它是通过使用sizeof来知道它将    * 读到何种变量的,为此,它需要那个变量自身。*/ 
#else 
    Message[i] = get_user(buf+i);
#endif
  Message[i] = '\0';  /* 标准的以0作终止符的字符串 */
  
  /* 我们需要返回使用的输入字节数。 */
  return i;
}



/* 这个函数决定是否允许一个操作(返回0允许,非0不允许同时指明为什么不允许)。
 *
 * 操作可以是下面的值:
 * 0 - 执行 (运行“文件”在我们的情况中是没有意义的)。
 * 2 - 写(向内核模块输入)。
 * 4 - 读(从内核模块输出)。
 *
 * 这是一个真实的检查文件权限的函数。由 ls -l 返回的权限只做参考并可以被忽略。 */
static int module_permission(struct inode *inode, int op)
{
  /* 我们可以允许任何人从我们的模块读但只有 root (UID为 0) 可以向它写 */ 
  if (op == 4 || (op == 2 && current->euid == 0))
    return 0; 

  /* 如果是其他值,访问被禁止 */
  return -EACCES;
}




/* 文件被打开--我们并不真正关心这个而是这意味着我们需要增加模块的引用计数。 */
int module_open(struct inode *inode, struct file *file)
{
  MOD_INC_USE_COUNT;
 
  return 0;
}


/* 文件被关闭--同样只关心引用计数 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
int module_close(struct inode *inode, struct file *file)
#else
void module_close(struct inode *inode, struct file *file)
#endif
{
  MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
  return 0;  /* success */
#endif
}


/* 作为 /proc 文件登记的结构,含有所有相关函数的指针。 ********** */



/* 对我们的 proc 文件的操作。这是放当某人试图对我们的文件做什么的时候需要调用的函数指针的地方。 
 * NULL意味着我们不想处理什么事情。 */
static struct file_operations File_Ops_4_Our_Proc_File =
  {
    NULL,  /* lseek */
    module_output,  /* 从文件“读” */
    module_input,   /* 向文件“写” */
    NULL,  /* readdir */
    NULL,  /* select */
    NULL,  /* ioctl */
    NULL,  /* mmap */
    module_open,    /* 某人打开了文件 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    NULL,   /* 刷新,在 2.2 版中加到这儿*/
#endif
    module_close,    /* 某人关闭了文件 */
    /* 等等,等等。(在/usr/include/linux/fs.h中它们都被给出)。因为我们在这儿没有放任何东西,系      * 统将保持缺省的数据,在Unix 中为0 (当作为指针时为NULLs )。*/
  };



/* 对我们的proc文件的节点操作。我们需要它,因此我们将在某些地方指定我们想使用的文件操作的结构
 * 和用于权限的函数。当然指定任何其他对节点做什么时被调用的函数也是可能的。
 * (尽管我们不操心还是放置了 NULL)。 */
static struct inode_operations Inode_Ops_4_Our_Proc_File =
  {
    &File_Ops_4_Our_Proc_File,
    NULL, /* 创建 */
    NULL, /* 查找 */
    NULL, /* 连接 */
    NULL, /* 删除连接 */
    NULL, /* 符号连接 */
    NULL, /* 创建目录 */
    NULL, /* 删除目录 */
    NULL, /* 创建设备 */
    NULL, /* 更名 */
    NULL, /* 读连接 */
    NULL, /* 连接跟随 */
    NULL, /* 读页 */
    NULL, /* 写页 */
    NULL, /* bmap */
    NULL, /* 截短 */
    module_permission /* 权限检查 */
  };


/* 目录项 */
static struct proc_dir_entry Our_Proc_File = 
  {
    0, /* 节点数--忽略,将被 proc_register[_dynamic] 代替 */
    7, /* 文件名长度 */
    "rw_test", /* 文件名 */
    S_IFREG | S_IRUGO | S_IWUSR, 
    /* 文件模式--这是一个可以被拥有者,用户组以及其他人读的普通文件。
     * 当然,它的拥有者可以写。
     *
     * 实际上这个成员仅仅用于引用,它的 module_permission 做实际的检查。
     * 它可以使用这个成员,但我们的实现为了简单而没有用 */
    1,  /* 连接数(文件被引用的目录) */
    0, 0,  /* 文件的UID和GID -我们将它赋予 root */
    80, /* 由ls报告的文件大小。 */
    &Inode_Ops_4_Our_Proc_File, 
    /* 指向文件的节点结构的指针,如果我们需要。在我们的情况中我们需要因为我们需要写函数。*/
    NULL  
    /* 文件的读函数。不相关,因为我们将它放入上面的节点结构。 */
  }; 



/* 初始化模块和清除模块 ******************* */

/* 初始化模块-登记 proc 文件 */
int init_module()
{
  /* 如果 proc_register[_dynamic] 成功则成功,否则失败。 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
  /* 在 2.2版中,如果它在结构中的值为0, proc_register自动分配一个动态的节点数。因此这儿不再需要
   * proc_register_dynamic   */
  return proc_register(&proc_root, &Our_Proc_File);
#else
  return proc_register_dynamic(&proc_root, &Our_Proc_File);
#endif
}


/* 清除 - 从 /proc 中注销我们的文件 */
void cleanup_module()
{
  proc_unregister(&proc_root, Our_Proc_File.low_ino);
}


next up previous contents index
Next: 和设备文件对话 Up: Linux 内核模块编程 Previous: /proc 文件系统

1999-05-19