精华内容
下载资源
问答
  • 内核模块

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

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

    一、内核模块的编写:

    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<linux/module.h>:它定义了模块的 API、类型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的内核模块都必须包含这个头文件。

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

           #include<linux/init.h>头文件:module_init、module_exit等宏定义。

           #include<linux/proc_fs.h>: 在对/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<proc_fs.h>(这些函数是在此文件中声明的),其次利用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_proc 和 write_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)时,我们需要使用 start 和 off 参数;

                     当所有数据全部写入之后,就需要设置 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);

    展开全文
  • Linux内核模块在形式上以.ko文件存在,首先需要知道的是内核模块的编译也分为动态编译和静态编译,动态编译模块生成.ko文件。动态模块的加载命令如下:insmod ****.ko,比较常见的是Linxu设备驱动以内核模块的形式...

    Linux内核模块在形式上以.ko文件存在,首先需要知道的是内核模块的编译也分为动态编译和静态编译,动态编译模块生成.ko文件。动态模块的加载命令如下:insmod  ****.ko,比较常见的是Linxu设备驱动以内核模块的形式存在。

    关于内核模块管理的常见命令是:lsmod、insmod、rmmod、modinfo、modprobe、depmod

    一、基本介绍

    内核模块的管理命令安装在kmod包中

    [root@DC-F4-01-E6-FA-94 ~]# rpm -ql kmod|grep sbin
    /usr/sbin/depmod
    /usr/sbin/insmod
    /usr/sbin/lsmod
    /usr/sbin/modinfo
    /usr/sbin/modprobe
    /usr/sbin/rmmod
    /usr/sbin/weak-modules
    

    CentOS中所有与内核模块相关的文件都存放在"/lib/modules/$(uname -r)/“下面

    [root@DC-F4-01-E6-FA-94 ~]# cd /lib/modules/$(uname -r)/
    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# ls
    build              modules.block        modules.devname      modules.softdep      vdso
    extra              modules.builtin      modules.drm          modules.symbols      weak-updates
    kernel             modules.builtin.bin  modules.modesetting  modules.symbols.bin
    modules.alias      modules.dep          modules.networking   source
    modules.alias.bin  modules.dep.bin      modules.order        updates
    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# 

    二、命令的详解

    1、lsmod :显示已经加载到内核中的模块的状态信息

    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# lsmod|head -4
    Module                  Size  Used by
    nr_drv                 65825  0 
    vtsspp                389226  0 
    sep4_1                777016  0

    2、modinfo:查看模块的基本信息

    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# modinfo /home/gnb_image/libs/cpa_sub6/rec/drv/nr_drv.ko 
    filename:       /home/gnb_image/libs/cpa_sub6/rec/drv/nr_drv.ko
    license:        Dual BSD/GPL
    version:        2017.0.45
    author:         Leon Woestenberg <leon@sidebranch.com>,Sonal Santan <sonal.santan@xilinx.com>,Richard Tobin <richard.tobin@xilinx.com>
    license:        GPL v2
    rhelversion:    7.4
    srcversion:     7FC7569E26685EBDCB1A2E4
    alias:          pci:v00001172d0000EAAAsv*sd*bc*sc*i*
    alias:          pci:v00001172d0000EBBBsv*sd*bc*sc*i*
    alias:          pci:v00001172d00000FECsv*sd*bc*sc*i*
    alias:          pci:v00001172d0000E003sv*sd*bc*sc*i*
    alias:          pci:v00001172d0000E002sv*sd*bc*sc*i*
    alias:          pci:v00001172d0000E001sv*sd*bc*sc*i*
    alias:          pci:v000010EEd0000903Fsv*sd*bc*sc*i*
    depends:        
    vermagic:       3.10.0-693.2.2.rt56.623.el7.x86_64 SMP preempt mod_unload modversions 
    parm:           major:Device major number, default is 0 (dynamic value) (uint)
    parm:           poll_mode:Set 1 for hw polling, default is 0 (interrupts) (uint)
    parm:           enable_credit_mp:Set 1 to enable creidt feature, default is 0 (no credit control) (uint)

    3、insmod 将指定模块加载到内核中,modprobe和其功能一致

    4、rmmod 将已加载的模块从内核中移植,modprobe -r 命令与其功能一致

    5、modprobe  加载或者卸载内核模块

    modprobe dev_nr.ko 加载

    modprobe -r dev_nr.ko 卸载模块

    6、depmod  :查找/lib/moduels/(uname -r)中的所有模块并建立modules.dep.bin 文件,该文件记录了模块位置及其依赖关系

    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# ls | grep dep
    modules.dep
    modules.dep.bin
    modules.softdep
    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# rm -rf modules.dep.bin 
    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# depmod
    [root@DC-F4-01-E6-FA-94 3.10.0-693.2.2.rt56.623.el7.x86_64]# ls | grep dep
    modules.dep
    modules.dep.bin
    modules.softdep

     

     

     

    展开全文
  • 下面我们通过编写一个简单的内核模块直接获取当前系统时间。 1.1模块源码编写 在Linux内核源码中,定义了一个struct timeval结构体,结构体中有两个成员变量tv_sec,tv_usec,分别保存当前系统时间的秒和毫秒,...

    1. 内核简单模块的编写

    通过命令date可以获取当前系统时间,如下面示例。

    下面我们通过编写一个简单的内核模块直接获取当前系统时间。

    1.1模块源码编写

    在Linux内核源码中,定义了一个struct timeval结构体,结构体中有两个成员变量tv_sec,tv_usec,分别保存当前系统时间的秒和毫秒,time_t和suseconds_t类型变量在x86架构中,均为long型,变量类型定义在文件include/linux/time.h中。

    00018: struct timeval {

    00019time_t tv_sec/ * seconds */

    00020suseconds_t tv_usec; / * microseconds */

    00021: };

    00022:

     

    模块源码如下:

    00001:

    00002: #include <linux/ module.h>

    00003: #include <linux/ time.h>

    00004:

    00005: static char modname[] = "time";

    00006:

    00007: extern struct timespec xtime;

    00008:

    00009: int init_module( void )

    00010: {

    00011struct timeval tv;

    00012printk( "Installing %s module.", modname );

    00013do_gettimeofday(&tv);

    00014printk("\njiffies:%lu, tv.tv_sec:%lu, tv.tv_nsec:%lu ", jiffies, tv.tv_sec, tv.tv_usec);

    00015:

    00016return 0;

    00017: }

    00018:

    00019:

    00020: void cleanup_module( void )

    00021: {

    00022printk( "\nRemoving %s module.", modname );

    00023: }

    00024:

    00025: MODULE_LICENSE("GPL");

    00026:

     

    1.2Makefile

    创建一个Makefile,执行make,即可编译生成内核模块,生成后缀名为.ko的文件。

    Makefile内容:

    注意:在default:后面的$(MAKE) … … 和rm –r … …两行前面必须是Tab键,不能为空格或其他字符,否则执行make时,会报告“Makefile:10: *** missing separator. Stop.”错误。

    1.3模块加载

    执行make,编译生成模块.ko文件后,就可以通过insmod命令来加载模块。

    通过lsmod命令可以查看驱动是否成功加载到内核中。

    通过insmod命令加载刚编译成功的time.ko模块后,似乎系统没有反应,也没看到打印信息。而事实上,内核模块的打印信息一般不会打印在终端上。驱动的打印都在内核日志中,我们可以使用dmesg命令查看内核日志信息。

    内核模块time.ko获取到的当前系统时间为1289489871秒,与执行date ‘+%s’命令获取到的值一致。

    2内核模块版本与符号表

    在编写和使用内核模块过程中,会发现在某个内核版本上编译的模块只能在当前内核版本中使用。若模块版本号与当前内核版本号不匹配导致就会无法加载,提示“insmod: error inserting 'time.ko': -1 Invalid module format”,内核会打印类似信息“time: version magic '2.6.32.12-0.7-default SMP mod_unload modversions ' should be '2.6.18-92.el5 SMP mod_unload gcc-4.1'”。

    2.1内核模块版本号

    查看内核模块版本信息的命令为modinfo,如查看刚才我们编写的time.ko。

    模块的版本号在“vermagic“一项,当前系统中使用的模块版本号都是相同的。

    模块版本号是哪里决定的?我们是否可以更改?我们是否可以在当前系统中,编译其他内核版本的模块?

    2.1.1模块版本号的确定

    在make编译模块时,通过-C参数制定内核源码头文件位置。前面我们编译time模块内核源码头文件位置为/lib/modules/2.6.32.12-0.7-default/build。

    make -C /lib/modules/2.6.32.12-0.7-default/build SUBDIRS=/root/programming modules

    我们来分析模块版本号的确定

    vermagic: 2.6.32.12-0.7-default SMP mod_unload modversions

    在RHEL5系统中,模块版本号vermagic由include/linux/vermagic.h和include/linux/utsrelease.h两个文件的内容来决定,即vermagic就为VERMAGIC_STRING

    文件include/linux/utsrelease.h的内容如下:

    文件include/linux/vermagic.h的内容如下:

    而在SLES11.1内核2.6.32.12-0.7-default的模块版本号VERMAGIC_STRING由scripts/mod/modpost可执行文件确定。

    2.1.2模块版本号的修改

    前面分析了模块版本号由VERMAGIC_STRING确定产生,若我们需要修改模块版本号或希望在当前内核版本中编译其他内核的模块(注意gcc大版本和CPU架构i686/x86_64保持一致),只需要修改控制模块的VERMAGIC_STRING值即可。

    如我们将示例中的time模块在RHEL5.2内核中编译RHEL5.3内核模块,然后在RHEL5.3系统上可以加载。

    2.1.3编译非当前内核版本模块

    在模块版本号的修改介绍的方法中,仅适合内核版本(OS发行版本)差别不大的情况下,可以方便修改某块版本。本小节介绍如何编译非当前内核版本模块。

    步骤:

    1. 将待编译特定内核源码开发包拷贝到当前系统中某个目录下

    如我们打算在SLES11.1 x86_64系统中编译RHEL5.5 x86_64内核模块,应先将RHEL5.5 x86_64内核开发包拷贝到SLES11.1系统中。

    1. 修改Makefile,将KDIR指向待编译内核开发包目录

    在Makefile中,设置KDIR变量为指定内核源码目录位置。

    3、执行make编译模块

    执行后,生成内核模块。可以使用modinfo命令来查看新生成模块的版本号。如

    #modinfo /root/programming/time.ko

     

    编译非当前内核版本模块后,加载再次提示“Invalid module format”时,通过dmesg命令查看加载失败原因。

    如上面的提示,我们直接include/linux/vermagic.h文件即可,将gcc版本信息值改为固定值gcc-4.1即可。

    2.2内核符号表及使用

    加载模块时,insmod使用公共内核符号表解析模块中未定义的符号。公共符号表中包含了所有的全局内核项(即函数和变量)的地址,内核符号表的内容全部在文件/proc/kallsyms中,可以通过cat等命令查看。内核和模块将函数、变量导出后,就成为内核符号表的一部分。

    在我们编写的内核模块中,可以使用内核或其他模块定义的函数和变量,如本章示例的获取时间模块中,就调用了内核函数do_gettimeofday()。

     

    内核中有两个宏用来导出函数和变量:

    EXPORT_SYMBOL(symbolname)

    将函数或变量导出到所有模块

    EXPORT_SYMBOL_GPL(symbolname)

    将函数或变量仅导出到GPL模块

    我们也可以在自己的模块中导出部分函数或变量,这样其他模块就可以访问这部分函数、变量。C语言用户态程序编程中,我们常会使用在其他C文件或lib库中定位的函数和变量,内核符号表和这有相似之处。

    系统中所有内核和模块导出的变量和函数,就成了内核符号表,在/proc/kallsyms文件中。

    内核符号表中,第一列为函数或变量的在内核中的地址,第二列为符号的类型,第三列为符号名,第四列为符号所属的模块。若第四列为空,则表示该符号属于内核代码。

    内核符号属性

    符号属性

    含义

    b

    符号在未初始化数据区(BSS)

    c

    普通符号,是未初始化区域

    d

    符号在初始化数据区

    g

    符号针对小object,在初始化数据区

    i

    非直接引用其他符号的符号

    n

    调试符号

    r

    符号在只读数据区

    s

    符号针对小object,在未初始化数据区

    t

    符号在代码段

    u

    符号未定义

     

    若符号在内核中是全局性的,则属性为大写字母,如T、U等。其他符号属性含义,请参考命令nm的帮助信息。

    00273: / * Only label it "global" if it is exported. */

    00274: static void upcase_if_global(struct kallsym_iter *iter)

    00275: {

    00276if (is_exported(iter- >name, iter- >owner))

    00277iter- >type += 'A' - 'a';

    00278: }

    00279:

     

    若打算使用内核中的符号,在模块中增加函数或变量说明即可。如:

    00091: extern struct timespec xtime;

     

    3模块版本控制

    Linux内核版本在不变升级,内核提供的API或符号可能也随之变化。这对内核模块开发来说,是一个比较麻烦的问题,通常要适应不同的内核版本,或者只针对具体某些内核版本开发。

    内核为了确保模块的函数接口与内核借口一致,采用了模块版本控制。版本控制最简单的办法就是为了内核和模块都设置一个常量,该常量会随着接口变化而不断增加。加载模块时,内核会检查模块提供的常量是否和内核版本常量相等,若不相等则拒绝加载。

    采用常量的办法进行版本控制,方法简单,但不够灵活。如内核部分接口变化后,版本常量就会增加。但若某模块使用的这些接口并没有变化,也会导致驱动无法加载。基于这个原因,最恰当的方法是将单个内核API的变化考虑进去。实际的模块和内核实现无关,模块和内核关系密切的是API接口。

    3.1checksum方法

    CRC checksum原理是使用函数的参数来计算校验码,若校验码不相等,加载模块失败。

    我们来看一下内核执行模块加载的函数load_module()(文件kernel/module.c)。1767行会调用check_modstruct_version()函数来检查struct_module符号的CRC校验码。若校验码不相等,则提示“disagrees about version of symbol struct_module”。如

    hwinc_kernel_driver: disagrees about version of symbol struct_module

    Found checksum B6AF205C vs module F3D5F8AF

     

    01600: static struct module *load_module(void __user *umod,

    01601unsigned long len,

    01602const char __user *uargs)

    01603: {

    01604Elf_Ehdr *hdr;

    01605Elf_Shdr *sechdrs;

    … …

    01766/ * Check module struct version now, before we try to use module. */

    01767if (! check_modstruct_version(sechdrs, versindex, mod)) {

    01768err = - ENOEXEC;

    01769goto free_hdr;

    01770}

    01771:

    01772modmagic = get_modinfo(sechdrs, infoindex, "vermagic");

    01773/ * This is allowed: modprobe - - force will invalidate it. */

    01774if (! modmagic) {

    01775add_taint_module(mod,TAINT_FORCED_MODULE);

    01776printk(KERN_WARNING "%s: no version magic, tainting

    kernel.\n",

    01777mod- >name);

    01778else if (! same_magic(modmagic, vermagic )) {

    01779printk(KERN_ERR "%s: version magic '%s' should be '%s'\n",

    01780mod- >name, modmagic, vermagic );

    01781err = - ENOEXEC;

    01782goto free_hdr;

    01783}

     

    在编译内核模块时,会生成*.mod.c文件,该文件中包含了模块中各个符号的校验码。校验码的生成,由scripts/genksyms/genksyms计算生成。

    注意:scripts/genksyms/genksyms文件是在内核源码目录或内核开发包目录中,如/usr/src/linux-2.6.32.12-0.7-obj/x86_64/default/scripts/genksyms/genksyms

    3.2vermagic

    查看内核版本模块信息时,会看到vermagic一项。模块在装载时,load_module()函数会比较(如前面代码的1772行)当前运行内核的vermagic和当前要加载的模块的vermagic比较,如果不同,则禁止加载模块。

    Vermagic的的确定请参考章节2.1.1。

    3.3内核模块版本控制使能与关闭

    我们常遇到内核提示“disagrees about version of symbol struct_module”,而导致模块无法加载的情况。

    3.3.1内核中关闭/使能

    若选择关闭内核的模块版本控制功能,则会避免出现这种情况。模块版本控制选项在内核源码配置文件.config中,注释掉CONFIG_MODVERSIONS就取消了模块版本控制。

    CONFIG_MODVERSIONS=y

    重新编译内核,重启即可。

    3.3.2模块中关闭/使能版本控制

    如下面的实例。虽然模块的vermagic和内核一致,但struct_module的版本号不一致。我们可以不修改当前内核,重新编译模块即可解决问题。

    若去掉模块版本控制后,加载驱动导致系统死机。建议解决办法:使用待运行内核的.config配置文件覆盖模块编译指向的内核开发包(源码).config文件。

    .config配置文件的获取:(1)可以拷贝/proc/config.gz,然后解压缩,拷贝为.config;(2)若/proc/config.gz不存在,可以使用/boot/目录下对应的内核配置文件;(3)或向内核提供者获取.config配置文件。

    此时我们可以修改内核开发包中的模块版本控制选项,修改文件.config(在内核源码或开发包根目录下),注释掉或删除CONFIG_MODVERSIONS选项,重新编译模块即可去除模块的版本控制。

    CONFIG_MODULES=y

    CONFIG_OBSOLETE_MODPARM=y

    #CONFIG_MODVERSIONS=y

    CONFIG_MODULE_SIG=y

    4内核模块参数

    在用户执行系统命令或其他程序时,可以使用参数。内核模块也可以使用参数。

    参数必须使用宏module_param()声明,该宏定义在include/linux/moduleparam.h文件中。module_param()需要三个参数:参数名称、类型、sysfs文件系统入口项的访问权限掩码。

    模块参数的定义必须放在任何函数之外。如本章获取系统时间的模块示例,我们增加province和population两个参数(参数仅作示范,和系统时间无任何关系)。

    00001: #include <linux/ module.h>

    00002: #include <linux/ time.h>

    00003: #include <linux/ moduleparam.h>

    00004:

    00005: static char modname[] = "time";

    00006:

    00007: static char *province = "Guangdong";

    00008: module_param(provincecharp0);

    00009: static int population = 10000;

    00010: module_param(populationint0);

    00011:

    00012: int init_module( void )

    00013: {

    00014struct timeval tv;

    00015printk( "Installing %s module.", modname );

    00016do_gettimeofday(&tv);

    00017printk("\njiffies:%lu, tv.tv_sec:%lu, tv.tv_nsec:%lu ",

    00018jiffies, tv.tv_sec, tv.tv_usec);

    00019:

    00020printk("\nProvince:%s, Population:%d \n", province , population );

    00021:

    00022return 0;

    00023: }

    00024:

    00025:

    00026: void cleanup_module( void )

    00027: {

    00028printk( "\nRemoving %s module.", modname );

    00029: }

    00030:

    00031: MODULE_LICENSE("GPL");

    00032:

     

    加载模块time后,内核打印信息:

    内核模块支持的参数类型如下:

    bool

    invbool

    charp:字符串指针。内核会为用户提供的字符串自动分配内存。

    int

    long

    short

    uint:unsigned int

    ulong:unsigned long

    ushort:unsigned short

    5模块入口/出口函数及其他

    每个内核模块都要有初始化(入口)函数和清除(出口)函数,清除函数负责在模块被移除前注销接口并向系统返回所有资源。

    在本章time模块的示例中,并没有像用户态C程序一样有main()入口函数。time模块入口函数为init_module(),而出口函数为cleanup_module()。

    在复杂的模块中,我们可以指定模块的入口/出口函数名称。通过module_init()和module_exit()函数分别指定。如LSISAS1068E驱动mptsas中的入口/出口函数:

    04828: module_init(mptsas_init);

    04829: module_exit(mptsas_exit);

    在模块中,我们还可以添加作者信息、模块描述、模块版本等信息。

    MODULE_AUTHOR():模块作者信息

    MODULE_DESCRIPTION():模块描述

    MODULE_LICENSE():模块协议

    MODULE_VERSION():模块版本

    如:

    00070: #define my_NAME "Fusion MPT SCSI Host driver"

    00071: #define my_VERSION MPT_LINUX_VERSION_COMMON

    00072: #define MYNAM "mptscsih"

    00073:

    00074: MODULE_AUTHOR(MODULEAUTHOR);

    00075: MODULE_DESCRIPTION(my_NAME);

    00076: MODULE_LICENSE("GPL");

    00077: MODULE_VERSION(my_VERSION);

     

    6内核模块与用户程序区别

    6.1用户空间与内核空间

    内核空间具有最高权限,可以访问所有CPU寄存器和其他所有资源。

    • 内核空间可以访问所有的CPU指令和所有的内存空间、I/O空间。

    • 用户空间只能访问有限的资源,若需要特殊权限,可以通过系统调用获取相应的资源。

    • 用户空间允许页面中断,而内核空间则不允许。

    • 用户空间是0-3G的地址范围,内核空间是3G-4G的地址范围。

    • 内核空间和用户空间是针对线性地址空间的。

    • 所有内核进(线)程共用一个地址空间,而用户进程都有各自的地址空间。

    ​​​​​​​                                                               Linux 32位系统用户空间与内核空间

    6.2内核模块与应用程序的对比

    • 内核模块具有独立的地址空间

    模块运行在内核空间中。应用程序运行在用户空间中。系统软件受到保护,不允许用户程序访问。内核空间和用户空间有各自独立的内存地址空间。

    • 内核模块具有更高的执行特权

    运行在内核空间中的代码要比运行在用户空间中的代码具有更大的特权。

    • 内核模块不按顺序执行

    用户程序通常按顺序执行并且从头到尾地执行单独的任务。内核模块并不按顺序执行,它注册自己是为了服务将来的请求。

    • 内核模块可以被中断

    在同一时刻,可能有许多进程同时向驱动程序发出请求。中断程序可以在驱动程序正在响应系统调用时,向驱动程序发出请求。在对称多处理器(SMP)系统中,驱动程序可能在多个 CPU 上并发地执行。

    • 内核模块必须是可抢占的

    • 内核模块能够共享数据

    一个应用程序的不同线程常常不会共享数据。与之相对应的是,组成驱动程序的数据结构和例程被所有使用驱动程序的线程所共享。驱动程序必须能够处理由多个请求导致的竞争问题。

    • 错误处理

    应用程序的错误导致Segmentation Fault,而内核模块的错误影响整个系统,甚至使内核

    7常见问题处理

    1、头文件引用

    在用户程序和内核模块时,可能都会使用头文件 #include <linux/time.h>,但两者文件所在的位置是不同的。

    用户态用户程序使用的time.h,在gcc库文件中,一般位置是/usr/include/linux/time.h或/usr/include/sys/time.h。

    内核模块使用的time.h,在内核源码头文件中,一般位置是<内核版本>/include/linux/time.h,如/usr/src/kernels/2.6.18-128.el5-x86_64/include/linux/time.h。

     

    2、提示内核build目录不存在

    在编写好内核模块后,执行make时,有的系统会提示类似“make: *** /lib/modules/2.6.18-128.el5xen/build: No such file or directory. Stop.”错误信息。原因在于内核源码开发包没有安装。

    解决办法:安装当前内核版本的源码开发包。

    3、模块加载提示Invalid Module Format”

    解决步骤:

    1. 执行dmesg命令,查看模块提示Invalid Module Format的详细原因

    2. 根据提示信息,结合本章提到内核模块版本号与修改一些,修复相应的错误。

    4、模块加载提示“Symbol not found”

    解决步骤:

    1. 执行dmesg命令,查看模块哪些符号在当前系统中不存在。

    2. 执行modinfo命令,查看当前模块依赖关系,并检查依赖的模块是否已加载到系统中。

    4、模块加载提示“disagrees about version of symbol struct_module

    请参考“模块版本控制”一节。

    5、是否有办法将模块加载到非当前内核版本中,而不重新编译模块?

    在内核版本相近和CPU架构相同的情况下,如2.6.18-92.e15 i686和2.6.18-194.e15 i686内核,可以直接二进制编辑模块,修改模块的版本信息,这样就可以加载到非当前内核版本中了。

     

    展开全文
  • 编译内核模块及交叉编译内核模块

    千次阅读 2020-01-19 12:02:36
    编译内核模块,可以用两种方法. 1. 常规方法,通过make menuconfig 去设置对应的项,将功能编进内核或编成module. 2. 利用内核环境直接从模块源代码编出module 第一种方法菜单操作,不易出错. 第二种方法更简单,实质....

    理解了它就简单了,所以关键是理解.

    编译内核模块,可以用两种方法.
    1. 常规方法,通过make menuconfig 去设置对应的项,将功能编进内核或编成module.
    2. 利用内核环境直接从模块源代码编出module
    第一种方法菜单操作,不易出错.
    第二种方法更简单,实质.可以理解模块的生成过程

    下面以usb转串行口驱动的生成为例来说明.
    将pl2303.c pl2303.h 源码copy到一个目录下,编写如下Makefile.
    如果是本地编译更简单,如果是交叉编译则需要进交叉内核路径和传人交叉参数即可.
    具体看注释就不解释了.

    $(warning KERNELRELEASE = $(KERNELRELEASE))
    ifneq ($(KERNELRELEASE),)
        obj-m := pl2303.o
    else
        PWD=$(shell pwd)
        KVER=$(shell uname -r)

    #编译x86_64模块时, 指定内核代码所在路径
    #KDIR=/lib/modules/$(KVER)/build
    #编译Arm 时,指定内核代码所在路径, 这个路径下,有编译内核的Makefile存在
    KDIR=/home/hjj/M6G2C/04.源码示例/3.软件源码/linux-src


    all:
        #编译x86_64, 简单的进入内核代码路径即可
        #make -C $(KDIR) M=$(PWD)
        #交叉编译arm, 我的环境需要指明ARCH, CROSS_COMPILE 变量
        make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
    clean:
        rm *.o *.ko modules.* Module.symvers *.mod.c
    endif


    其它的模块生成也可以如此操作.
    可见模块的编译是两次读取Makefile, 是用内核环境而不是普通环境编译的.

     

    展开全文
  • Linux内核模块详解

    万次阅读 多人点赞 2019-08-26 09:22:36
    内核模块 实验目的 内核模块是Linux操作系统中一个比较独特的机制。通过这一章学习,希望能够理解Linux提出内核模块这个机制的意义;理解并掌握Linux实现内核模块机制的基本技术路线;运用Linux提供的工具和命令,...
  • linux如何卸载内核模块 本文摘自Manning出版的《 Linux in Action》第15章。 Linux使用内核模块管理硬件外围设备。 这是这样的。 一个运行中的Linux内核是您不想烦恼的事情之一。 毕竟,内核是驱动计算机执行...
  • 简述linux系统下内核模块的编写和编译以及模块的加载与卸载操作
  • 4.2 Linux内核模块程序结构一个Linux内核模块主要由如下几个部分组成。(1)模块加载函数当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。(2)模块卸载...
  • Linux内核模块

    2019-04-17 22:09:00
    1. Linux内核模块 Linux本身是宏内核(单内核),但是又提供了模块这样一种机制,构成了Linux独有的模块特性。 Linux内核模块的特点: (1)模块本身不被编译入内核镜像,从而控制了内核的大小。 (2)模块一旦...
  • Linux内核模块编译

    千次阅读 2018-07-25 19:12:52
    Linux内核模块是一种可被动态加载和卸载的可执行程序。通过内核模块可以扩展内核功能,内核模块通常用于设备驱动、文件系统等。如果没有内核模块,需要向内核添加功能就需要自发代码、重新编译内核、安装新内核等...
  • 使用VMware fusion 安装虚拟机系统,一直显示“打不开 /dev/vmmon: 断裂管道 请确保已载入内核模块 ’vmmon’” 网上的命令试了一大堆,都不行,原来关键点在这,如下图所示: 打开 设置 -&amp;gt; 安全性与隐私...
  • 内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的...
  • 一、内核模块的概念 1、什么是模块?  内核模块是一些可以让操作系统内核在需要时载入和执行的代码,同时在不需要的时候可以卸载。这是一个好的功能,扩展了操作系统的内核功能,却不需要重新启动系统,是一种动态...
  • Linux内核模块编程

    千次阅读 2018-10-24 16:47:13
    Linux内核模块编程 (作者:Baron_wu 禁止转载) 首先,创建一个内核模块并插入Linux内核中。这是实验第一部分 首先查看当前内核模块使用情概况:lsmod Module:模块名 Size:模块大小 Used by:这些模块在哪被使用 ...
  • 一:Linux内核模块组成部分 1.linux内核模块加载 2.linux内核模块卸载 3.linux内核许可证声明(GPL,GPLv2) 4.Linux内核模块参数 5.Linux内核模块导出符号 6.linux作者声明等信息 1.Linux内核模块加载 (1)加载...
  • Linux内核模块编程与内核模块LICENSE——《Linux设备驱动开发详解(第3版)》预读
  • 内核模块编译

    2016-11-12 15:31:55
    一.Linux内核介绍  1.Linux 内核很庞大,相应的包含的组件也非常多。Linux文件就是常说的zImage和bzImage,在内核启动期间会被解压到内存。 2.新的使用组件的思路:动态加载和卸载。...4.关于内核模块的操
  • fuse内核模块挂载

    千次阅读 2020-03-08 11:25:23
    Linux用于支持用户空间文件系统的内核模块名叫FUSE,用户空间文件系统(Filesystem in Userspace,简称FUSE)是一个面向类Unix计算机操作系统的软件接口,它使无特权的用户能够无需编辑内核代码而创建自己的文件系统...
  • Linux内核模块开发

    2019-06-03 21:38:51
    1.LINUX内核模块基础 1.1 什么是内核模块? Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用这些组件呢?方法1:把所有的组件都编译进内核文件,即:zImage或bzImage,但这样会导致一个问题:占用...
  • Ubuntu上开发编译内核模块以及调试内核模块,并查看printk打印的消息   前言:在开发中,为了方便,其实我们是可以先在ubuntu运行和测试内核模块,测试好了再用交叉编译到ARM设备上运行。下面就介绍一下方法和...
  • Linux内核分析(二)----内核模块简介|简单内核模块实现 http://www.cnblogs.com/wrjvszq/p/4260996.html Linux内核分析(二) 昨天我们开始了内核的分析,网上有很多人是用用源码直接分析,这样造成的...
  • 内核模块---module_param_array
  • 大纲: 1.交叉编译环境搭建 2.内核模块的编写 3.将开发板连接上PC串口测试,加载模块 4.内核模块参数 5.总结
  • 树莓派4编译内核与内核模块

    千次阅读 2020-02-15 21:52:42
    最初只是想试一下交叉编译树莓派4的内核模块,结果 pi@raspberrypi:~/Proj$ sudo insmod hello.ko insmod: ERROR: could not insert module hello.ko: Invalid module format 该问题是由于内核版本不一致造成的。...
  • 内核与内核模块的位置: 内核:/boot/vmlinuz 或 /boot/vmlinux-version 内核解压缩所需: RAM Disk: /boot/inittramfs 内核模块: /lib/modules/version/kernel 或 /lib/modules/$(uname -r)/kernel 内核源码: /usr...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,417
精华内容 12,566
关键字:

内核模块