Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1187237
  • 博文数量: 232
  • 博客积分: 7563
  • 博客等级: 少将
  • 技术积分: 1930
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-21 11:17
文章分类

全部博文(232)

文章存档

2011年(17)

2010年(90)

2009年(66)

2008年(59)

分类:

2008-05-21 11:43:37


以下内容只是记录的要点,详细看参考资料:

一、内核模块的编写:

1、内核模块的代码编写没有外部的函数库可以用,只能使用内核导出的函数;这点于应用程序是有区别的,应用程序习惯于使用外部的库函数,在编译的时候将程序与库函数链接在一起。比如说:内核模块中不能使用printf(),而只能使用printk()函数。

2、内核模块至少包含两个函数:模块加载函数、卸载函数;

   内核版本2.3.13以前,使用init_module()和cleanup_module作为模块的初始和结束函数; 在内核Linux 2.4中,你可以为你的模块的“开始”和“结束”函数起任意的名字了。它们不再必须使用 init_module()cleanup_module()的名字。这可以通过宏 module_init()module_exit()实现。这些宏在头文件kernel../include/linux/init.h定义。唯一需要注意的地方是函数必须在宏的使用前定义,否则会有编译 错误。

3、内核模块包含头文件及宏说明:

       #include:它定义了模块的 API、类型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的内核模块都必须包含这个头文件。

       #include:使用内核信息优先级时要包含这个文件,一般在使用printk函数时使用到优先级信息。

       #include头文件:module_init、module_exit等宏定义。

       #include: 在对/proc文件系统的文件目录进行操作时使用到的函数等在这个文件中定义,比如:create_proc_entry()在/proc下创建文件。

      

       接下来解释一下常用宏定义:

        MODULE_LICENSE 定义了这个模块使用的许可证。此处,我们定义的是 GPL,从而防止会污染到内核。

    

二、内核模块的编译(2.6系列内核):

      编译模块的时候,你可以将模块放在代码树中,用make modules来编译你的模块;你也可以将模块相关文件目录放在代码树以外的位置,用如下命令来编译模块:   

       make -C path/to/kernel/source M=$PWD modules

      参数说明: -C 指定代码树的位置;M=$PWD或者M=`PWD`告诉kbuild回到当前目录执行build操作。

       参考资料: 2.6内核Makefile简单语法与应用

                    http://blog.chinaunix.net/u/24474/showart_237820.html

    在我的机子上运行的内核版本是2.6.22-14-generic,编译内核模块mymodule的步骤如下

   内核模块的编译:

    1)写内核模块源文件(任意目录), 如mymodule.c

    2)在模块源文件目录编写Makefile,内容:obj-m+=mymodule.o

    3) 在终端执行: make -C   /usr/src/linux版本目录   SUBDIRS=$PWD modules

        网上搜到的命令是:

              make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules

       其中:linux-`uname -r`应该根据自己的实际情况进行调整,比如我的就是linux-headers-2.6.22-14-generic,也就是linux-headers-'uname -r'。

     说明:SUBDIRS=..和 M=..是一样的,

疑点:对kbuild的理解?

          2.6内核引入了kbuild,将外部的内核模块的编译和内核源码树的编译统一起来了。

三、插入模块:

      $insmod   ./mymodule.ko

    查看信息:demsg | tail -5 , 并且 cat /proc/modules 可看到插入的模块:mymodule。

    卸载模块:

     $rmmod ./mymodule.ko

     

参考资料:2.6内核模块的编写框架和编译方法

      http://hi.baidu.com/skie/blog/item/87ab59a9b45b46fc1f17a208.html

四、 模块的安装

安装模块。模块的默认安装目录是 /lib/modules/<内核-版本>

当你需要将模块安装到非默认位置的时候,你可以用INSTALL_MOD_PATH 指定一个前缀,如:

make INSTALL_MOD_PATH=/foo modules_install

模块将被安装到 /foo/lib/modules目录下。

四、内核模块的运行:

内核模块运行在内核空间,而应用程序在用户空间。应用程序的运行会形成新的进程,而内核模块一般不会。每当应用程序执行系统调用时,linux执行模式从用户空间切换到内核空间。

   

五、几个可以用来开发有用LKM(可加载内核模块)的内核API:
 
要使用/proc文件系统,首先一定要包含procfs的头文件:include(这些函数是在此文件中声明的),其次利用procfs提供的如下API函数。

首先值得一提的是:结构体proc_dir_entry,他的部分成员如下:

   struct proc_dir_entry {

const char *name; // virtual file name
mode_t mode; // mode permissions
uid_t uid; // File's user id
gid_t gid; // File's group id
struct inode_operations *proc_iops; // Inode operations functions
struct file_operations *proc_fops; // File operations functions
struct proc_dir_entry *parent; // Parent directory
...
read_proc_t *read_proc;
// /proc read function
write_proc_t *write_proc;
// /proc write function
void *data; // Pointer to private data
atomic_t count; // use count
...
};
为了创建可读可写的proc文件并指定该proc文件的写操作函数,必须设置下面那些创建proc文件的函数返回指针指向的struct proc_dir_entry结构的write_proc字段,并指定该proc文件的访问权限有写权限。
我们会使用 read_procwrite_proc 命令来插入对这个虚拟文件进行读写的函数。
读者可以通过cat和echo等文件操作函数来查看和设置这些proc文件。


  1. create_proc_entry:

    1)原型:
         struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,struct proc_dir_entry *parent );

        2)作用:该函数用于在/proc文件系统中创建一个虚拟文件(proc条目)。
        3)参数:
          name: 给出要创建的文件名称;
          mode:给出建立的该proc文件的访问权限;
          parent:指定建立的proc文件所在的目录;
          如果要在/proc根目录下建立proc文件,parent应当为NULL,否则他应当为proc_mkdir,也就是说我们也可以在自己创建的目录下面再创建虚拟文件.(详细解释见remove_proc_entry).
        4)返回struct proc_dir_entry结构的指针,我们可以使用这个返回的指针来配置这个虚拟文件的其他参数,比如对该文件执行读操作时应调用的函数。
           若返回NULL,说明create出错。      

  2. remove_proc_entry:
    1)原型:
    void remove_proc_entry( const char *name, struct proc_dir_entry *parent );

    2)作用:
    用于删除上面函数创建的proc文件。

    3)参数:
     name:给出要删除的proc文件的名称;

     parent:指定创建的proc文件所在的目录。

    parent参数可以是NULL(指/proc根目录),也可以取其他值,这取决于我们创建时将这个文件放到了什么地方。可以使用的其他一些值 proc_dir_entry:proc_root_fs、proc_net、proc_bus、proc_root_driver等。这些取值在linux/proc_fs.h中引入,如下:

         extern struct proc_dir_entry proc_root; (/proc目录)

         extern struct proc_dir_entry *proc_root_fs; (/proc/fs目录)

         extern struct proc_dir_entry *proc_net;   (/proc/net)

         extern struct proc_dir_entry *proc_bus;   (/proc/bus)

         extern struct proc_dir_entry *proc_root_driver; (/proc/driver)

         extern struct proc_dir_entry *proc_root_kcore; (/proc/kcore)

    注意:上面的定义中,proc_root和其他变量的类型不同,其他的都是指针,在使用时要注意。利用这些位置值,我们可在/proc文件系统的根目录(/proc)及其内部的目录下(如/proc/net、/proc/bus等)建立文件。除此之外,我们还可以用自己创建的目录(proc_mkdir)。

    删除目录和文件都是使用这一个函数。

  3. proc_mkdir:
    1)原型:
    struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir _entry *parent)

    2)作用:该函数用于创建一个proc目录。

    3)参数:

      name:指定要创建的目录的名称;
      parent:该proc目录所在的目录。

  4. proc_mkdir_mode:
    1)原型:
     extern struct proc_dir_entry *proc_mkdir_mode(const char *name,
     mode_t mode, struct proc_dir_entry *parent)
    2)作用:该函数用于以一定的模式创建proc目录。
    3)参数:
      name:
    指定要创建的目录的名称;
      mode:给出了建立该proc目录的访问权限;
      parent:
    该proc目录所在的目录。

  5. proc_symlink:
    1)原型:
    truct proc_dir_entry *proc_symlink(const char * name,struct proc_ dir_entry * parent, const char * dest)
    2)作用:该函数用于建立一个proc文件的符号连接。
    3)参数:
      name:给出要建立链接的proc文件的名称;
      parent:指定符号链接所在的目录;
      dest:指定链接到的proc文件名称。
  6. creat_proc_read_entry:
    1)原型:
    struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base,read_proc_t *read_proc, void * data)
    2)作用:该函数用于建立一个规则的只读proc文件
    3)参数:
      name:
    给出要创建的文件名称;
      mode:给出了建立该proc文件的访问权限;
      base:指定建立proc文件所在的目录(指定方式同上);
      read_proc:给出读取该文件的操作函数;
      data:为该proc文件的专用数据,它将保存在该proc文件对应的struct file结构中的
            private_data字段中。

  7. creat_proc_info_entry:
    1)原型:
     struct proc_dir_entry *create_proc_info_entry(const char *name,mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)
    2)作用:该函数用于创建一个info型的proc文件。
    3)参数:
     
    name: 给出要创建的proc文件名称;
      mode:给出了建立该proc文件的访问权限;
      base:指定建立proc文件所在的目录(指定方式同上);
      get_info:指定该proc文件的get_info操作函数。实际上get_info等同于read_proc,如果proc文件没有定义read_proc,对该文件的read操作将由
    get_info取代,因此它在功能上非常类似于函数creat_proc_read_entry.

  8. proc_net_creat:
    1)原型:
    struct proc_dir_entry *proc_net_create(const char *name, mode_t mode, get_info_t *get_info)
    2)作用:该函数用于在/proc/net目录下创建一个proc文件。
    3)参数:
      name:给出了要创建的proc文件名称;
      mode:
    给出了建立该proc文件的访问权限;
     
    get_info:指定该proc文件的get_info操作函数。

  9. proc_net_fops_create:
    1)原型:
    struct proc_dir_entry *proc_net_fops_create(const char *name, mode_t mode, struct file_operations *fops)
    2)作用:该函数也用于
    在/proc/net目录下创建一个proc文件,但是他同时指定了对该文件的文件操作函数。
  10. proc_net_remove:
    1)原型:void proc_net_remove(const char *name)
    2)作用:该函数用于删除前面两个函数在/proc/net目录下创建的文件。
    3)参数:
      name:指定要删除的proc文件;
     
  11. 回调函数:

         我们可以使用write_proc函数向/proc中写入一项。此函数原型:

         int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data );

        参数: filp 参数实际上是一个打开文件结构(我们可以忽略这个参数);

                   buff 参数是存放在缓冲区的要写入的字符串数据;

                   len 参数定义了在 buff 中有多少数据要被写入;

                   data 参数是一个指向私有数据的指针(proc_dir_entry中)。

        注意:缓冲区buff地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。Linux 提供了一组 API 来在用户空间和内核空间之间移动数据,我们使用了 copy_from_user 函数来维护用户空间的数据。



  12. 读回调函数:

        我们可以使用read_proc函数从一个/proc项中读取数据(从内核空间到用户空间)。此函数原型:

          int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data );

          参数:page 参数是这些数据写入到的位置;

                    count 定义了可以写入的最大字符数;

                   在返回多页数据(通常一页是 4KB)时,我们需要使用 startoff 参数;

                   当所有数据全部写入之后,就需要设置 eof(文件结束参数);

                   与 write 类似,data 表示的也是私有数据。

          注意:此处提供的page缓冲区在内核空间中,因此我们可以直接读出,而不用调用copy_to_user。

  13. 其他有用的/proc函数:

    对于只需要一个read函数的简单/proc项来说,可以使用create_proc_read_entry,这会创建一个/proc项,并在一个调用中对read_proc函数进行初始化。

    其他有用的/proc函数:

    copy_to_user/* Copy buffer to user-space from kernel-space */

    copy_from_user/* Copy buffer to kernel-space from user-space */

vmalloc/* Allocate a 'virtually' contiguous block of memory */

    vfree/* Free a vmalloc'd block of memory */

    EXPORT_SYMBOL( symbol )/* Export a symbol to the kernel (make it visible to the kernel)*/

     EXPORT_SYMTAB* Export all symbols in a file to the kernel (declare before module.h) */

5、参考文献:The Linux Kernel Module Programming Guide

* 每一个内核模块都需要包含linux/module.h;当需要使用printk()记录级别的宏扩展KERN_ALERT等时,需要包含linux/kernel.h。

*printk()函数中,当指定的优先级低于consle_loglevel时,信息会直接打印到终端上(不是在Xwindows下的虚拟终端下,而是 真正的字符界面下);如果同时syslogd和klogd都在运行,信息也同时添加在文件/var/log/messages中,不管是否显示在控制台 上。

    说明:printk()函数不是设计用来同用户交互的,而是为内核提供日志功能,记录内核信息或给出警告。

    注意,内核的输出进到了内核回环缓冲区中,而不是打印到 stdout 上,这是因为 stdout 是进程特有的环境。要查看内核回环缓冲区中的消息,可以使用 dmesg 工具(或者通过 /proc 本身使用 cat /proc/kmsg 命令)。下面指令 给出了 dmesg 显示的最后几条消息:dmesg | tail -5.

* 一种称为kbuild的新方法被引入,现在外部的可加载内核模块的编译的方法已经同内核编译统一起来。 想了解更多的编 译非内核代码树中的模块请参考帮助文件linux/Documentation/kbuild/modules.txt

* 2.6的内核现在引入一种新的内核模块命名规范:内核模块现在使用.ko的文件后缀(代替 以往的.o后缀),这样内核模块就可以同普通的目标文件区别开。更详细的文档请参考 linux/Documentation/kbuild/makefiles.txt

* 内核模块证书 :

    2.4或更新的内核中,一种识别代码是否在GPL许可下发布的机制被引入, 因此人们可以在使用非公开的源代码产品时得到警告。 利用宏 MODULE_LICENSE(),即当你设置在GPL证书下发布你的代码时, 你可以取消这些警告。这种证书机制在头文件linux/module.h 实现

   类似的,宏MODULE_DESCRIPTION()用来描述模块的用途。

     宏MODULE_AUTHOR()用来声明模块的作者。

                     宏MODULE_SUPPORTED_DEVICE() 声明模块支持的设备。

这些宏都在头文件linux/module.h定义, 并且内核本身并不使用这些宏。它们只是用来提供识别信息,可用工具程序像objdump查看。

* 版本印戳作为一个静态的字符串存在于内核模块中,以 vermagic:。 版本信息是在连接阶段从文件init/vermagic.o中获得的。 查看版本印戳和其它在模块中的一些字符信息,可以使用下面的命令 modinfo module.ko(模块名).

*

模块是在insmod加 载时才连接的目标文件。那些要用到的函数(如:printk)的符号链接是内核自己提供的。 也就是说, 你可以在内核模块中使用的函数只能来自内核本身。如果你对内核提供了哪些函数符号 链接感兴趣,看一看文件/proc/kallsyms 文件/proc/kallsyms保存着内核知道的所有的符号,你可以访问它们, 因为它们是内核代码空间的一部分。

*

库函数和系统调用的区别: 库函数是高层的,完全运行在用户空间, 为程序员提供调用真正的在幕后 完成实际事务的系统调用的更方便的接口。系统调用在内核 态运行并且由内核自己提供。

    一般库函数在用户态执行。 库函数调用一个或几个系统调用,而这些系统调用为库函数完成工作,但是在超级状态。 一旦系统调用完成工作后系统调用就返回同时程序也返回用户态。


* 宏 __init 和 __exit 可以使函数在运行完成后自动回收(限模块中),__initdata用于变量,

举例:

#include //需要包含的头文件

static int ntest __initdata = 3;

static int __init test_init(void) {...}

static void __exit test_exit(void) {...}

module_init(test_init); //申明放在实现函数后

module_exit(test_exit);



参考资料:

[1]有关内核模块的读书笔记
  http://blog.eccn.com/u/107909/archives/2007/1875.htm     

[2] 使用 /proc 文件系统来访问 Linux 内核的内容[important]
   
   http://www.ibm.com/developerworks/cn/linux/l-proc.html
    

[3]linux操作系统内核实习

[4]内核模块编程之_初窥门径
%5Fzeus/blog/item/824023012ffc6f031c9583b7.html

[5]讲解的Makefile语法部分

&

[6]《《Linux内核模块编程指南》jnux内核模块编程指南》(四)      
  

[7]Linux操作系统下 内核模块开发详细解析(简单易懂) 

http://dev.21tx.com/2007/10/23/11093.html    

[8]Linux内核/模块开发常见问题集(FAQ)

[9]5.3  基于Linux操作系统的底层驱动技术(对procfs提供的一些API接口函数有详细讲解)
 

[10]Linux操作系统下 内核模块开发详细解析(一些基础知识)
  
http://dev.21tx.com/2007/10/23/11093.html
阅读(2067) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~