Linux内核模块的概念和基本的编程方法_linux /sys/module/xx/section bug_table-程序员宅基地

技术标签: Linux内核  linux内核  模块  

Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。
4.1~4.2节讲解了Linux内核模块的概念和结构,4.3~4.8节对Linux内核模块的各个组成部分进行了展现,4.1~4.2与4.3~4.8节是整体与部分的关系。
4.9节说明了独立存在的Linux内核模块的Makefile文件编写方法和模块的编译方法。
4.1 Linux内核模块简介Linux内核的整体结构已经非常庞大,而其包含的组件也非常多。我们怎样把需要的部分都包含在内核中呢?
一种方法是把所有需要的功能都编译到Linux内核。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。 
有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?
答案是肯定的,Linux提供了这样的一种机制,这种机制被称为模块(Module)。模块具有这样的特点:
·  模块本身不被编译入内核映像,这控制了内核的大小。
·  模块一旦被加载,它就和内核中的其它部分完全一样。
为了建立读者对模块的初步感性认识,我们先来看一个最简单的内核模块“Hello World”,如代码清单4.1。
代码清单4.1 一个最简单的Linux内核模块
1  #include <linux/init.h>
2  #include <linux/module.h>
3  MODULE_LICENSE("Dual BSD/GPL");
4  static int hello_init(void)
5  {
6    printk(KERN_INFO " Hello World enter\n");
7    return 0;
8  }
9  static void hello_exit(void)
10 {
11   printk(KERN_INFO " Hello World exit\n ");
12 }
13 module_init(hello_init);
14 module_exit(hello_exit);
15 
16 MODULE_AUTHOR("Song Baohua");
17 MODULE_DESCRIPTION("A simple Hello World Module");
18 MODULE_ALIAS("a simplest module");
这个最简单的内核模块只包含内核模块加载函数、卸载函数和对Dual BSD/GPL许可权限的声明以及一些描述信息。编译它会产生hello.ko目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello World enter”,卸载时输出“Hello World exit”。
内核模块中用于输出的函数是内核空间的printk()而非用户空间的printf(),printk()的用法和printf()基本相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段,在Linux驱动的调试章节中将详细讲解这个函数。
在Linux中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系,例如:
[root@localhost driver_study]# lsmod
Module                  Size   Used by
hello                    1568    0 
ohci1394                32716   0 
ide_scsi                 16708   0 
ide_cd                  39392   0 
cdrom                  36960   1 ide_cd
lsmod命令实际上读取并分析“/proc/modules”文件,与上述lsmod命令结果对应的“/proc/modules”文件如下:
[root@localhost driver_study]# cat /proc/modules 
hello 1568 0 - Live 0xc8859000
ohci1394 32716 0 - Live 0xc88c8000
ieee1394 94420 1 ohci1394, Live 0xc8840000
ide_scsi 16708 0 - Live 0xc883a000
ide_cd 39392 0 - Live 0xc882f000
cdrom 36960 1 ide_cd, Live 0xc8876000
内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行tree –a得到如下目录树: 
[root@localhost hello]# tree -a
.
|-- refcnt
`-- sections
    |-- .bss
    |-- .data
    |-- .gnu.linkonce.this_module
    |-- .rodata
    |-- .rodata.str1.1
    |-- .strtab
    |-- .symtab
    |-- .text
    `-- __versions
       modprobe命令比insmod命令要强大,它在加载某模块时,会同时加载该模块所依赖的其它模块。使用modprobe命令加载的模块若以“modprobe -r filename”的方式卸载将同时卸载其依赖的模块。
       使用modinfo <模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持的参数以及vermagic:
[root@localhost driver_study]# modinfo hello.ko
filename:       hello.ko
license:        Dual BSD/GPL
author:         Song Baohua
description:    A simple Hello World Module
alias:          a simplest module
vermagic:       2.6.15.5 686 gcc-3.2
depends:   
4.2 Linux内核模块程序结构一个Linux内核模块主要由如下几个部分组成:
       ·  模块加载函数(一般需要)
       当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
·  模块卸载函数(一般需要)
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。
·  模块许可证声明(必须)
许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。
在Linux 2.6内核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。
大多数情况下,内核模块应遵循GPL兼容许可权。Linux 2.6内核模块最常见的是以MODULE_LICENSE( "Dual BSD/GPL" )语句声明模块采用BSD/GPL双LICENSE。
·  模块参数(可选)
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。
·  模块导出符号(可选)
内核模块可以导出符号(symbol,对应于函数或变量),这样其它模块可以使用本模块中的变量或函数。
·  模块作者等信息声明(可选)
4.3模块加载函数Linux内核模块加载函数宜被以__init标识声明,典型的模块加载函数的形式如代码清单4.2所示。
代码清单4.2 内核模块加载函数
1    static int __init initialization_function(void)
2    {     
3    /* 初始化代码 */
4    }
5    module_init(initialization_function);
模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回0。而在初始化失败时,应该返回错误编码。在Linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。总是返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。
在Linux 2.6内核中,可以使用request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用
request_module(module_name); 

request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));
这种灵活的方式加载其它内核模块。
在Linux中,所有标识为__init的函数在连接的时候都放在.init.text这个区段内,此外,所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后,释放init区段(包括.init.text,.initcall.init等)。
4.4模块卸载函数       Linux内核模块加载函数宜被以__exit标识声明,典型的模块卸载函数的形式如代码清单4.3所示。
代码清单4.3 内核模块卸载函数
1     static void __exit cleanup_function(void)
2     {
3     /* 释放代码 */
4     }
5     module_exit(cleanup_function);
模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)”的形式来指定。
通常来说,模块卸载函数要完成与模块加载函数相反的功能,例如:
·  若模块加载函数注册了XXX,则模块卸载函数应该注销XXX。
·  若模块加载函数动态申请了内存,则模块卸载函数应释放该内存
·  若模块加载函数申请了硬件资源(中断、DMA通道、I/O端口和I/O内存等)的占用,则模块卸载函数应释放这些硬件资源。
·  若模块加载函数开启了硬件,则卸载函数中一般要关闭之。
和__init一样,__exit也可以使对应函数在运行完成后自动回收内存。实际上,__init和__exit都是宏,其定义分别为:
#define __init        __attribute__ ((__section__ (".init.text")))

#ifdef MODULE
#define __exit        __attribute__ ((__section__(".exit.text")))
#else
#define __exit        __attribute_used__ __attribute__ ((__section__(".exit.text")))
#endif
数据也可以被定义为__initdata和__exitdata,这两个宏分别为:
#define __initdata   __attribute__ ((__section__ (".init.data")))

#define __exitdata  __attribute__ ((__section__(".exit.data")))
4.5模块参数我们可以用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了1个整型参数和1个字符指针参数:
static char *book_name = "深入浅出Linux设备驱动";
static int num = 4000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);
在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或modprobe)模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的缺省值。
参数类型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或invbool(布尔的反),在模块被编译时会将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。
模块被加载后,在/sys/module/目录下将出现以此模块名命名的目录。当“参数读/写权限”为0时,表示此参数不存在sysfs文件系统下对应的文件节点,如果此模块存在“参数读/写权限”不为0的命令行参数,在此模块的目录下还将出现parameters目录,包含一系列以参数名命名的文件节点,这些文件的权限值就是传入module_param()的“参数读/写权限”,而文件的内容为参数的值。
除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组类型,数组长,参数读/写权限)”。从2.6.0至2.6.10 版本,须将数组长变量名赋给“数组长”,从2.6.10 版本开始,须将数组长变量的指针赋给“数组长”,当不需要保存实际输入的数组元素个数时,可以设置“数组长”为NULL。
运行insmod或modprobe命令时,应使用逗号分隔输入的数组元素。
现在我们定义一个包含2个参数的模块(如代码清单4.4),并观察模块加载时被传递参数和不传递参数时的输出。 
代码清单4.4 带参数的内核模块
1  #include <linux/init.h>    
2  #include <linux/module.h> 
3  MODULE_LICENSE("Dual BSD/GPL");                                
4  
5  static char *book_name = "dissecting Linux Device Driver";     
6  static int num = 4000;    
7        
8  static int book_init(void)           
9  {                                
10    printk(KERN_INFO " book name:%s\n",book_name);                        
11    printk(KERN_INFO " book num:%d\n",num);                               
12    return 0;                                
13 }                                
14 static void book_exit(void)                                
15 {                                
16   printk(KERN_INFO " Book module exit\n ");                            
17 }                                
18 module_init(book_init);                                
19 module_exit(book_exit);                                
20 module_param(num, int, S_IRUGO);                                
21 module_param(book_name, charp, S_IRUGO);
22                                 
23 MODULE_AUTHOR("Song Baohua, [email protected]");
24 MODULE_DESCRIPTION("A simple Module for testing module params");
25 MODULE_VERSION("V1.0");
对上述模块运行“insmod book.ko”命令加载,相应输出都为模块内的默认值,通过察看“/var/log/messages”日志文件可以看到内核的输出:
[root@localhost driver_study]# tail -n 2 /var/log/messages
Jul  2 01:03:10 localhost kernel:  <6> book name:dissecting Linux Device Driver
Jul  2 01:03:10 localhost kernel:  book num:4000
当用户运行“insmod book.ko book_name=’GoodBook’ num=5000”命令时,输出的是用户传递的参数:
[root@localhost driver_study]# tail -n 2 /var/log/messages
Jul  2 01:06:21 localhost kernel:  <6> book name:GoodBook
Jul  2 01:06:21 localhost kernel:  book num:5000
4.6导出符号Linux 2.6的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。
模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);
       导出的符号将可以被其它模块使用,使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块。代码清单4.5给出了一个导出整数加、减运算函数符号的内核模块的例子(这些导出符号毫无实际意义,仅仅只是为了演示)。
代码清单4.5 内核模块中的符号导出
1  #include <linux/init.h>                                
2  #include <linux/module.h>                                
3  MODULE_LICENSE("Dual BSD/GPL");                                
4                                  
5  int add_integar(int a,int b)                                
6  {                                
7  return a+b;                             
8  } 
9                                 
10 int sub_integar(int a,int b)                                
11 {                                
12   return a-b;                             
13 }                            
14 
15 EXPORT_SYMBOL(add_integar);
16 EXPORT_SYMBOL(sub_integar);
       从“/proc/kallsyms”文件中找出add_integar、sub_integar相关信息:
[root@localhost driver_study]# cat /proc/kallsyms | grep integar
c886f050 r __kcrctab_add_integar        [export]
c886f058 r __kstrtab_add_integar        [export]
c886f070 r __ksymtab_add_integar        [export]
c886f054 r __kcrctab_sub_integar        [export]
c886f064 r __kstrtab_sub_integar        [export]
c886f078 r __ksymtab_sub_integar        [export]
c886f000 T add_integar  [export]
c886f00b T sub_integar  [export]
13db98c9 a __crc_sub_integar    [export]
e1626dee a __crc_add_integar    [export]
4.7模块声明与描述在Linux内核模块中,我们可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明模块的作者、描述、版本、设备表和别名,例如:
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,如代码清单4.6。
代码清单4.6 驱动所支持的设备列表
1 /* 对应此驱动的设备表 */ 
2 static struct usb_device_id skel_table [] = { 
3 { USB_DEVICE(USB_SKEL_VENDOR_ID, 
4    USB_SKEL_PRODUCT_ID) }, 
5   { } /* 表结束 */ 
6 }; 

8 MODULE_DEVICE_TABLE (usb, skel_table);
此时,并不需要读者理解MODULE_DEVICE_TABLE的作用,后续相关章节会有详细介绍。
4.8模块的使用计数2.4内核中,模块自身通过MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT宏来管理自己被使用的计数。
Linux 2.6内核提供了模块计数管理接口try_module_get(&module)和module_put (&module),从而取代2.4中的模块使用计数管理宏。模块的使用计数一般不必由模块自身管理,而且模块计数管理还考虑了SMP与PREEMPT机制的影响。
int try_module_get(struct module *module);
该函数用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中。
void module_put(struct module *module);
该函数用于减少模块使用计数。
try_module_get ()与module_put()的引入与使用与2.6内核下的设备模型密切相关。Linux 2.6内核为不同类型的设备定义了struct module *owner域,用来指向管理此设备的模块。当开始使用某个设备时,内核使用try_module_get(dev->owner)去增加管理此设备的owner模块的使用计数;当不再使用此设备时,内核使用module_put(dev->owner)减少对管理此设备的owner模块的使用计数。这样,当设备在使用时,管理此设备的模块将不能被卸载。只有当设备不再被使用时,模块才允许被卸载。
在Linux 2.6内核下,对于设备驱动工程师而言,很少需要亲自调用try_module_get()与module_put(),因为此时开发人员所写的驱动通常为支持某具体设备的owner模块,对此设备owner模块的计数管理由内核里更底层的代码如总线驱动或是此类设备共用的核心模块来实现,从而简化了设备驱动开发。
4.9模块的编译       我们可以为代码清单4.1的模板编写一个简单的Makefile:
obj-m := hello.o
并使用如下命令编译Hello World模块:
make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules
       如果当前处于模块所在的目录,则以下命令与上述命令同等:
         make –C /usr/src/linux-2.6.15.5 M=$(pwd) modules
       其中-C后指定的是Linux内核源代码的目录,而M=后指定的是hello.c和Makefile所在的目录,编译结果如下:
[root@localhost driver_study]# make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules
make: Entering directory `/usr/src/linux-2.6.15.5'
  CC

  /driver_study/hello.o

/driver_study/hello.c:18:35: warning: no newline at end of file
  Building modules, stage 2.
  MODPOST
  CC      /driver_study/hello.mod.o
  LD

  /driver_study/hello.ko

make: Leaving directory `/usr/src/linux-2.6.15.5'
从中可以看出,编译过程中,经历了这样的步骤:先进入Linux内核所在的目录,并编译出hello.o文件,运行MODPOST会生成临时的hello.mod.c文件,而后根据此文件编译出hello.mod.o,之后连接hello.o和hello.mod.o文件得到模块目标文件hello.ko,最后离开Linux内核所在的目录。
       中间生成的hello.mod.c文件的源代码如代码清单4.7所示。
代码清单4.7 模块编译时生成的.mod.c文件
1    #include <linux/module.h>
2    #include <linux/vermagic.h>
3    #include <linux/compiler.h>
4    
5    MODULE_INFO(vermagic, VERMAGIC_STRING);
6    
7    struct module __this_module
8    __attribute__((section(".gnu.linkonce.this_module"))) = {
9     .name = KBUILD_MODNAME,
10    .init = init_module,
11    #ifdef CONFIG_MODULE_UNLOAD
12    .exit = cleanup_module,
13    #endif
14    };
15    
16    static const char __module_depends[]
17    __attribute_used__
18    __attribute__((section(".modinfo"))) =
19    "depends=";
hello.mod.o产生了ELF(Linux所采用的可执行/可连接的文件格式)的2个节,即modinfo和.gun.linkonce.this_module。
如果一个模块包括多个.c文件(如file1.c、file2.c),则应该以如下方式编写Makefile:
obj-m := modulename.o
modulename-objs := file1.o file2.o
4.10使用模块绕开GPL       对于企业自己编写的驱动等内核代码,如果不编译为模块则无法绕开GPL,编译为模块后企业在产品中使用模块,则公司对外不再需要提供对应的源代码,为了使公司产品所使用的Linux操作系统支持模块,需要完成如下工作:
· 在内核编译时应该选上“可以加载模块”,嵌入式产品一般不需要动态卸载模块,所以“可以卸载模块”不用选,当然选了也没关系,如图4.1。

图4.1 内核中支持模块的编译选项

如果有项目被选择“M”,则编译时除了make bzImage或zImage以外,也要make modules。
· 将我们编译的内核模块.ko文件应该放置在目标文件系统的相关目录中。
· 产品的文件系统中应该包含了支持新内核的insmod、lsmod、rmmod等工具,由于嵌入式产品中一般不需要建立模块间依赖关系,所以modprobe可以不要,一般也不需要卸载模块,所以rmmod也可以不要。 
· 在使用中用户可使用insmod命令手动加载模块,如insmod xxx.ko。
       · 但是一般而言,产品在启动过程中应该加载模块,在嵌入式产品Linux的启动过程中,加载企业自己的模块的最简单的方法是修改启动过程的rc脚本,增加insmod /.../xxx.ko这样的命令。
如某设备正在使用的Linux系统中的rc脚本是这样的: 
mount /proc
mount /var
mount /dev/pts
mkdir /var/log
mkdir /var/run
mkdir /var/ftp
mkdir -p /var/spool/cron
mkdir /var/config
...
insmod /usr/lib/company_driver.ko 2> /dev/null
/usr/bin/userprocess
/var/config/rc
总结       本章主要讲解了Linux内核模块的概念和基本的编程方法。内核模块由加载/卸载函数、功能函数以及一系列声明组成,它可以被传入参数,也可以导出符号供其它模块使用。
由于Linux设备驱动以内核模块的形式而存在,因此,掌握这一章的内容是编写任何类型设备驱动的必须。在具体的设备驱动开发中,将驱动编译为模块也有很强的工程意义,因为如果将正在开发中的驱动直接编译入内核,而开发过程中会不断修改驱动的代码,则需要不断的编译内核并重启Linux,但是如果编译为模块,则只需要rmmod并insmod即可,开发效率为大为提高


在soundcore_open打开/dev/dsp节点函数中会调用到下面的:
    request_module("sound-slot-%i", unit>>4);
函数,这表示,让linux系统的用户空间调用/sbin/modprobe函数加载名为sound-slot-0.ko模块
#define request_module(mod...) __request_module(true, mod)
#define request_module_nowait(mod...) __request_module(false, mod)

luther@gliethttp:~$ cat /proc/sys/kernel/modprobe 
/sbin/modprobe
我们也可以向/proc/sys/kernel/modprobe添加新的modprobe应用程序路径,这里的/sbin/modprobe是内核默认路径.

/**
 * __request_module - try to load a kernel module       // 尝试加载一个ko模块[luther.gliethttp]
 * @wait: wait (or not) for the operation to complete
 * @fmt: printf style format string for the name of the module
 * @...: arguments as specified in the format string
 *
 * Load a module using the user mode module loader. The function returns
 * zero on success or a negative errno code on failure. Note that a
 * successful module load does not mean the module did not then unload
 * and exit on an error of its own. Callers must check that the service
 * they requested is now available not blindly invoke it.
 *
 * If module auto-loading support is disabled then this function
 * becomes a no-operation.
 */
int __request_module(bool wait, const char *fmt, ...)
{
    va_list args;
    char module_name[MODULE_NAME_LEN];
    unsigned int max_modprobes;
    int ret;
// char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";
    char *argv[] = { modprobe_path, "-q", "--", module_name, NULL };
    static char *envp[] = { "HOME=/",
                "TERM=linux",
                "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
                NULL }; // 环境变量.
    static atomic_t kmod_concurrent = ATOMIC_INIT(0);
#define MAX_KMOD_CONCURRENT 50    /* Completely arbitrary value - KAO */
    static int kmod_loop_msg;

    va_start(args, fmt);
    ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);   // 生成module名,这里就是sound-slot-0
    va_end(args);
    if (ret >= MODULE_NAME_LEN)
        return -ENAMETOOLONG;

    /* If modprobe needs a service that is in a module, we get a recursive  // 对递归循环的一个硬性限制
     * loop.  Limit the number of running kmod threads to max_threads/2 or  // 最多同时运行max_modprobes个modprobe用户程序
     * MAX_KMOD_CONCURRENT, whichever is the smaller.  A cleaner method     // 如果超过,那么__request_module返回-ENOMEM
     * would be to run the parents of this process, counting how many times // [luther.gliethttp]
     * kmod was invoked.  That would mean accessing the internals of the
     * process tables to get the command line, proc_pid_cmdline is static
     * and it is not worth changing the proc code just to handle this case. 
     * KAO.
     *
     * "trace the ppid" is simple, but will fail if someone's
     * parent exits.  I think this is as good as it gets. --RR
     */
    max_modprobes = min(max_threads/2, MAX_KMOD_CONCURRENT);    // 最多同时运行max_modprobes个modprobe用户程序
    atomic_inc(&kmod_concurrent);
    if (atomic_read(&kmod_concurrent) > max_modprobes) {
        /* We may be blaming an innocent here, but unlikely */
        if (kmod_loop_msg++ < 5)
            printk(KERN_ERR
                   "request_module: runaway loop modprobe %s\n",
                   module_name);
        atomic_dec(&kmod_concurrent);                           // 消除前面atomic_inc产生的引用
        return -ENOMEM;                                         // 返回错误[luther.gliethttp]
    }

    ret = call_usermodehelper(modprobe_path, argv, envp,        // 执行用户空间的应用程序/sbin/modprobe
            wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
    atomic_dec(&kmod_concurrent);                               // 消除atomic_inc产生的引用
    return ret;
}
EXPORT_SYMBOL(__request_module);


1. MODULE_DEVICE_TABLE (usb, skel_table);
该宏生成一个名为__mod_pci_device_table的局部变量,该变量指向第二个参数。内核构建时,depmod程序会在所有模块中搜索符号__mod_pci_device_table,把数据(设备列表)从模块中抽出,添加到映射文件/lib/modules/KERNEL_VERSION/modules.pcimap中,当depmod结束之后,所有的PCI设备连同他们的模块名字都被该文件列出。当内核告知热插拔系统一个新的PCI设备被发现时,热插拔系统使用modules.pcimap文件来找寻恰当的驱动程序 

MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是USB设备,那自然是usb(如果是PCI设备,那将是pci,这两个子系统用同一个宏来注册所支持的设备)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。例:假如代码定义了USB_SKEL_VENDOR_ID是 0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的 vendor ID和product ID,如果他们的值是0xfff0时,那么子系统就会调用这个模块作为设备的驱动。

2. 其他相关宏的定义      

这些宏定义在<linux/module.h>下

1)MODULE_AUTHOR(name) 定义驱动的编程者,name为string

2)MODULE_LICENSE(license) 定义驱动的license,一般为GPL,或相关公司的license

3)MODULE_DESCRIPTION(desc) 对驱动程序的描述,string

4)MODULE_SUPPORTED_DEVICE(name) 驱动程序所支持的设备,string

5)MODULE_PARM(var,type)

提供在运行时通过控制台将参数传递给模块(insmod)。如果我们想用这个宏来传递命令行参数,那么在我们的模块中定义一个全局变量.insmod模块时,便可以用参数的形式,将具体的实参传递给模块中的那个全局变量.
MODULE_PARM(name,type)
有两个参数,一个是这个全局变量的名称,另一个是这个全局变量的类型.
而他的类型有一下几种:
b:
比特型
h:
短整型
i:
整型
l:
长整型
s:
字符串型
在传递字符串型的参数时,这个全局变量需要在模块中用Char *来声明!insmod会自动为其分配内存空间。
例如:
int a = 3;
char *st;
MODULE_PARM(a,”i”);
MODULE_PARM(st,”s”);
insmod是我们加这样的参数:
insmode a.o “a = 3″, “st = hello world”


这里最重要的是,MODULE_PARM()也支持我们最常用的数组类型。用短线‘-’把两个数字分开,分别表示数组参数中的最小位数和最大位数。例如:
int array[8];
MODULE_PARM(array,”1-8i”);
在命令行我们使用加这样的参数:
insmod a.o “array = 38745,123,4000″
 

在那些模块编程时,我们往往给这些全局变量以默认值,如果我们才insmod时没有传入参数时,模块会使用这些默认值,而如果我们传入参数时,这些默认值便被覆盖掉。

6)MODULE_PARM_DESC(var,desc) 对变量的描述

7)GPL_HEADER()

8)THIS_MODULE 指向全局变量 __this_module (struct module)的指针。

9)系统对每个模块维护一个usage counter,以便决定何时可以安全的卸载模块。

下面的宏用来对该usage counter操作,usage counter可以通过/proc/modules文件查看

MOD_INC_USE_COUNT 

MOD_DEC_USE_COUNT

MOD_IN_USE

MODULE_DEVICE_TABLE

10)EXPORT_SYMTAB 预处理宏,当在程序中用EXPORT_SYMBOL等宏时需要定义该宏。例如,可以在Makefile中定义:-DEXPORT_SYMTAB

__EXPORT_SYMBOL(sym,str)

EXPORT_SYMBOL(var)

11)EXPORT_SYMBOL_NOVERS(var) 导出一个符合到内核符号表,导出后,该符合可以供其他模块使用。这个宏有助于编写驱动程序时清楚的划分出层次。可以通过/proc/ksyms文件或ksyms命令查看内核符号表。EXPORT_SYMBOL_NOVERS(var),导出是不带版本信息。在使用该宏时,需定义 EXPORT_SYMBOL_GPL(var)

12)EXPORT_NO_SYMBOLS 显示指出,该模块不向内核符合表导出符号

13)SET_MODULE_OWNER

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/bugouyonggan/article/details/9097201

智能推荐

VS2017生成一个简单的DLL文件 和 LIB文件——C语言-程序员宅基地

文章浏览阅读2k次。下面我们将用两种不同的姿势来用VS2017生成dll文件(动态库文件)和lib文件(静态库文件),这里以C语言为例,用最简单的例子,来让读者了解如何生成dll文件(动态库文件)生成动态库文件姿势一:第一步:新建一个项目第二步:选择Windows桌面向导(这里先不要去管上面的“动态链接库(DLL)”)第三步:选择动态链接库,并空项目打勾√..._vs 生成dll时.lib文件中是哪个文件中的函数

java.lang包的简单介绍_java.lang是什么意思-程序员宅基地

文章浏览阅读7.3k次,点赞2次,收藏19次。java.lang包是Java语言的核心类库(lang是language的缩写),包括了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等。每个Java程序运行时,系统都会自动地引入java.lang包,所以这个包的加载是缺省的。 ..._java.lang是什么意思

VirtualBox + CentOS 使用 NAT + Host-Only 方式联外网_nat和host only 一起用-程序员宅基地

文章浏览阅读1.2k次。virtualbox版本5.2.10 r122406 centos版本6.5_x64最近在配置本地虚拟机环境,之前纠结于NAT模式可以连外网,Host-Only模式只能连宿主机,配置环境的时候来回切比较烦。本来打算走网络共享路线来实现Host-Only模式下连接外网的,但是没有成功。问度娘半天终于实现NAT+Host-Only的方式联网1、设置全局网络管理网络CIDR设置为192.1..._nat和host only 一起用

Java实现解压zip压缩包(支持多层级)_java解压多层zip-程序员宅基地

文章浏览阅读994次,点赞30次,收藏16次。最常见的压缩文件格式之一,可以存储一个或多个文件,并可在不同的操作系统中进行解压缩。_java解压多层zip

AI绘画商业变现-程序员宅基地

文章浏览阅读1k次,点赞25次,收藏20次。这一时期出现了很多运用 Diffusion 技术的模型,比如 DALL-E 系列,Midjourney 等等,它们都有非常不错的效果,例如使用 Midjourney 创作的画作《太空歌剧院》就在美国科罗拉多州博览会的艺术比赛中获得了第一名,引发了媒体的竞相转载,以至于在指数工具里飙出了一个很高的 “异常值”,但因为它们要么是彻底闭源的,要么是只开放一些 api,所以 “异常值” 飙的快降得也快,最终没有特别出圈,停留在了新闻和少量技术尝鲜者中。用说的可能不清楚,我弄一组图片在下面,大家看了就明白了。

随便推点

Qt更改字体为思源黑体_qt 思源黑体-程序员宅基地

文章浏览阅读5.8k次,点赞4次,收藏20次。由于微软雅黑字体版权限制,现更改Qt应用程序默认字体为思源黑体-Mdeium黑度。直接在ui环境中更改字体需要运行机安装过思源黑体,故而只能在qrc中添加字体文件SourceHanSansCN-Medium.otf,然后在main函数中动态加载字体文件: int loadedFontID = QFontDatabase::addApplicationFont(":font/SourceHanSansCN-Medium.ttf"); QStringList loadedFontFa..._qt 思源黑体

python-docx高亮单词_python查找单词高亮显示-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏8次。最近在读经济学人,阅读的时候遇到不认识的单词不想停下来查词典,我寻思如果这些单词的中文解释自动标注在旁边就好了。之前我已经做了个小工具(link),阅读的时候运行程序不断读取剪切板里的英文单词,并生成对应的中文解释,但是我无法在手机上使用程序。为了解决这个问题,我需要利用计算机将文章里我可能不认识的单词自动翻译并标注,这样省去了手机上阅读时的查询过程,从而实现流畅阅读。代码思路1.以雅思词库(7600)作为我认识的词汇,创建excel文件word_list(将来会将更多单词写入文件,并利用excel排_python查找单词高亮显示

喝汽水问题(C语言)_祝大家题题都ac-程序员宅基地

文章浏览阅读363次,点赞9次,收藏10次。刷题_祝大家题题都ac

VS2017代码自动对齐快捷键_vs c++如何一键对其-程序员宅基地

文章浏览阅读6.2k次。VS2017代码自动对齐快捷键 C#:Ctrl + K + D C++:Ctrl+K+F(松开K后再按F) _vs c++如何一键对其

【socket】网卡内部缓冲区、socket缓冲区、滑动窗口-程序员宅基地

文章浏览阅读1k次。网卡内部缓冲区、socket缓冲区、滑动窗口三者的关系_socket缓冲区

自动化运维(十)Ansible 之进程管理模块-程序员宅基地

文章浏览阅读1.1k次,点赞25次,收藏6次。Ansible的进程管理模块提供了一种强大而灵活的方式来管理和操作各种进程管理器和服务。无论你使用的是Supervisor、Systemd、传统的init脚本还是Runit,这些模块都可以帮助你轻松地管理服务的生命周期。通过合理地使用这些模块,你可以实现服务的自动化管理,提高系统的可靠性和稳定性,下面我们一起来学习这些进程管理模块。

推荐文章

热门文章

相关标签