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

智能推荐

已知num为无符号十进制整数,请写一非递归算法,该算法输出num对应的r进制的各位数字。要求算法中用到的栈采用线性链表存储结构(1<r<10)。-程序员宅基地

文章浏览阅读74次。思路:num%r得到末位r进制数,num/r得到num去掉末位r进制数后的数字。得到的末位r进制数采用头插法插入链表中,更新num的值,循环计算,直到num为0,最后输出链表。//重置,s指针与头指针指向同一处。//更新num的值,至num为0退出循环。//末位r进制数存入s数据域中。//头插法插入链表中(无头结点)//定义头指针为空,s指针。= NULL) //s不为空,输出链表,栈先入后出。

开始报名!CW32开发者扶持计划正式进行,将助力中国的大学教育及人才培养_cw32开发者扶持计划申请-程序员宅基地

文章浏览阅读176次。武汉芯源半导体积极参与推动中国的大学教育改革以及注重电子行业的人才培养,建立以企业为主体、市场为导向、产学研深度融合的技术创新体系。2023年3月,武汉芯源半导体开发者扶持计划正式开始进行,以打造更为丰富的CW32生态社区。_cw32开发者扶持计划申请

希捷硬盘开机不识别,进入系统后自动扫描硬件以识别显示_st2000dm001不认盘-程序员宅基地

文章浏览阅读5.7k次。2014年底买的一块2TB希捷机械硬盘ST2000DM001-1ER164,用了两年更换了主板、CPU等,后来出现开机不识别的情况,具体表现为:关机后开机,找不到硬盘,就进入BIOS了,只要在BIOS状态下待机半分钟左右再重启,硬盘就会出现。进入系统后,重启(这个过程中主板对硬盘始终处于供电状态),也不会出现不识别硬盘的现象。就好像是硬盘或主板上某个电容坏了一样,刚开始给硬盘通电的N秒钟内电容未能..._st2000dm001不认盘

ADO.NET包含主要对象以及其作用-程序员宅基地

文章浏览阅读1.5k次。ADO.NET的数据源不单单是DB,也可以是XML、ExcelADO.NET连接数据源有两种交互模式:连接模式和断开模式两个对应的组件:数据提供程序(数据提供者)&DataSetSqlConnectionStringBuilder——连接字符串Connection对象用于开启程序和数据库之间的连接public SqlConnection c..._列举ado.net在操作数据库时,常用的对象及作用

Android 自定义对话框不能铺满全屏_android dialog宽度不铺满-程序员宅基地

文章浏览阅读113次。【代码】Android 自定义对话框不能铺满全屏。_android dialog宽度不铺满

Redis的主从集群与哨兵模式_redis的主从和哨兵集群-程序员宅基地

文章浏览阅读331次。Redis的主从集群与哨兵模式Redis的主从模式全量同步增量同步Redis主从同步策略流程redis主从部署环境哨兵模式原理哨兵模式概述哨兵模式的作用哨兵模式项目部署Redis的主从模式1、Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。2、为了分担读压力,Redis支持主从复制,保证主数据库的数据内容和从数据库的内容完全一致。3、Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。全量同步Redis全量复制一般发_redis的主从和哨兵集群

随便推点

mysql utf-8的作用_为什么不建议在MySQL中使用UTF-8-程序员宅基地

文章浏览阅读116次。作者:brightwang原文:https://www.jianshu.com/p/ab9aa8d4df7d最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误:Incorrect string value: ‘😃 我用的是UTF-8编码的客户端,服务器也是UTF-8编码的,数据库也是,就连要保存的这个字符串“????..._mysql utf8的作用

MATLAB中对多张图片进行对比画图操作(包括RGB直方图、高斯+USM锐化后的图、HSV空间分量图及均衡化后的图)_matlab图像比较-程序员宅基地

文章浏览阅读278次。毕业这么久了,最近闲来准备把毕设过程中的代码整理公开一下,所有代码其实都是网上找的,但都是经过调试能跑通的,希望对需要的人有用。PS:里边很多注释不讲什么意思了,能看懂的自然能看懂。_matlab图像比较

16.libgdx根据配置文件生成布局(未完)-程序员宅基地

文章浏览阅读73次。思路:  screen分为普通和复杂两种,普通的功能大部分是页面跳转以及简单的crud数据,复杂的单独弄出来  跳转普通的screen,直接根据配置文件调整设置<layouts> <loyout screenId="0" bg="bg_start" name="start" defaultWinId="" bgm="" remark=""> ..._libgdx ui 布局

playwright-python 处理Text input、Checkboxs 和 radio buttons(三)_playwright checkbox-程序员宅基地

文章浏览阅读3k次,点赞2次,收藏13次。playwright-python 处理Text input和Checkboxs 和 radio buttonsText input输入框输入元素,直接用fill方法即可,支持 ,,[contenteditable] 和<label>这些标签,如下代码:page.fill('#name', 'Peter');# 日期输入page.fill('#date', '2020-02-02')# 时间输入page.fill('#time', '13-15')# 本地日期时间输入p_playwright checkbox

windows10使用Cygwin64安装PHP Swoole扩展_win10 php 安装swoole-程序员宅基地

文章浏览阅读596次,点赞5次,收藏6次。这是我看到最最详细的安装说明文章了,必须要给赞!学习了,也配置了,成功的一批!真不知道还有什么可补充的了,在此做个推广,喜欢的小伙伴,走起!_win10 php 安装swoole

angular2里引入flexible.js(rem的布局)_angular 使用rem-程序员宅基地

文章浏览阅读1k次。今天想实现页面的自适应,本来用的是栅格,但效果不理想,就想起了rem布局。以前使用rem布局,都是在原生html里,还没在框架里使用过,百度没百度出来,就自己琢磨,不知道方法规范不规范,反正成功了,操作如下:1、下载flexible.js2、引入到angular项目里3、根据自己的需要修改细节3.1、在flexible.js里修改每份的像素,3.2、引入cssrem插件,在设置里设..._angular 使用rem

推荐文章

热门文章

相关标签