Linux内核模块(Module)初解

#include <linux/init.h>                     // __init   __exit
#include <linux/module.h>                   // module_init  module_exit
 
static int __init hello_init(void)
{
   printk(KERN_ALERT "helloworld!\n");
   return 0;
}
 
static __exit void hello_exit(void)
{
   printk(KERN_ALERT "helloworld exit!\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Musk <qq:739112417>");
MODULE_DESCRIPTION("A Simple Hello World");
MODULE_ALIAS("A simplest module");
View Code

    一. 分析module_init宏定义

          1.1.   module_init宏被定义在kernel/include/linux/init.h文件里 

#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn);
#define device_initcall(fn) __define_initcall(fn, 6);
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
View Code

          1.2.    module_init(hello_init)深入分析宏定义:

#define module_init(hello_init) __initcall(hello_init);
#define __initcall(hello_init) device_initcall(hello_init);
#define device_initcall(hello_init) __define_initcall(hello_init, 6);
#define __define_initcall(hello_init, 6) \
static initcall_t __initcall_hello_init6 __used \
__attribute__((__section__(".initcall6.init"))) = hello_init
View Code

         1.3.   其中static initcall_t 这里 initcall_t的定义是 : typedef int           (*initcall_t)(void)   这里决定了hello_init函数类型。

            上面大概的意思就是定义一个指向函数的指针,以hello_init为例,就是定义了__initcall_hello_init6,把hello_init函数的指针赋给它

             并把__initcall_hello_init6放到在vmlinux.lds文件里边指定的.initcall6.init区间里边

         1.4.   这里再介绍一下像int __init hello_init(void) { }这种用包含在module_init里边的函数定义,函数名字前面加上__init到底什么意                  思。 

              __init的定义是 #define __init __section(.init.text) __cold notrace

               在init.h中对这些宏的说明如下:

/* These macros are used to mark some functions or 
 * initialized data (doesn't apply to uninitialized data)
 * as `initialization' functions. The kernel can take this
 * as hint that the function is used only during the initialization
 * phase and free up used memory resources after
 *
 * Usage:
 * For functions:
 * 
 * You should add __init immediately before the function name, like:
 *
 * static void __init initme(int x, int y)
 * {
 *    extern int z; z = x * y;
 * }
 *
 * If the function has a prototype somewhere, you can also add
 * __init between closing brace of the prototype and semicolon:
 *
 * extern int initialize_foobar_device(int, int, int) __init;
 *
 * For initialized data:
 * You should insert __initdata between the variable name and equal
 * sign followed by value, e.g.:
 *
 * static int init_variable __initdata = 0;
 * static const char linux_logo[] __initconst = { 0x32, 0x36, ... };
 *
 * Don't forget to initialize data not at file scope, i.e. within a function,
 * as gcc otherwise puts the data into the bss section and not into the init
 * section.
 * 
 * Also note, that this data cannot be "const".
 */
/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init __section(.init.text) __cold notrace //__cold 和notrace具体干嘛的?
#define __initdata __section(.init.data)
#define __initconst __constsection(.init.rodata)
#define __exitdata __section(.exit.data)
#define __exit_call __used __section(.exitcall.exit)
View Code

        就是说如果某个函数或者变量只在初始化的时候用到,后面可以被清理掉也        没有关系的话,就最好定义成上面的一种,以便释放更多空间。 

        这里具体怎么释放加了上面定义的的函数和变量? 应该是放到了特定区域里        边一起释放,具体后面再说。 

    还有这种__init定义方式需要注意,不要用在函数里边的

二. 模块操作命令

    2.1. 常用模块操作命令

        2.1.1. lsmod(list module,将模块列表显示),功能是打印出当前内核中已经        安装的模块列表

        2.1.2. insmod(install module,安装模块),功能是向当前内核中去安装        一个模块,用法是insmod xxx.ko

        2.1.3. modinfo(module information,模块信息),功能是打印出一个内        核模块的自带信息。

    用法是modinfo xxx.ko,注意要加.ko,也就是说是一个静态的文件形式

        2.1.4. rmmod(remove module,卸载模块),功能是从当前内核中卸载        一个已经安装了的模块,

    用法是rmmod xxx.ko  rmmod xxx都可以

    2.2. 模块的安装

        2.2.1. insmod与module_init宏。模块源代码中用module_init宏声明了一        个函数(在我们这个例子里是hello_init函数),

    作用就是指定hello_init这个函数和insmod命令绑定起来,也就是说当我们            insmod xxx.ko时,insmod命令内部实际执行的操作就是帮我们调用hello_init        函数。

        2.2.2. 模块安装时insmod内部除了帮我们调用module_init宏所声明的函数        外,实际还做了一些别的事(譬如lsmod能看到多了一个模块也是insmod帮我        们在内部做了记录,

     也就是将我们的模块加入到内核的一个数据结构中去),但是我们就不用管了

     2.3. 模块的卸载

        2.3.1. module_exit和rmmod的对应关系当我们执行rmmod命令的时候,        就会执行模块的module_exit宏声明的函数,

    同样也会将我们这个模块信息从我们内核的模块管理的数据结构中将其删除

    2.4. 模块的版本信息       

        2.4.1.  使用modinfo查看模块的版本信息

        2.4.2. 内核zImage中也有一个确定的版本信息

        2.4.3. insmod时模块的vermagic必须和内核的相同,否则不能安装,报错        信息为:insmod: ERROR: could not insert module module_test.ko: Invalid     module format        

        2.4.4. 模块的版本信息是为了保证模块和内核的兼容性,是一种安全措施

        2.4.5. 如何保证模块的vermagic和内核的vermagic一致?编译模块的内核源        码树就是我们编译正在运行的这个内核的那个内核源码树即可。说白了就是模        块和内核要同出一门

    2.5. 模块中常用宏

        2.5.1. MODULE_LICENSE("GPL"),模块的许可证。一般声明为GPL许证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在            的函数提升找不到)。

        2.5.2. MODULE_AUTHOR(Musk <qq:739112417> "),用来添加模块的作者信息

        2.5.3. MODULE_DESCRIPTION("xxx"),用来添加模块的描述信息

        2.5.4. MODULE_ALIAS("xxxx"),用来添加模块的别名

    2.6. printk函数详解

        2.6.1.printk在内核源码中用来打印信息的函数,用法和printf非常相似

        2.6.2. printk和printf最大的差别:printf是C库函数,是在应用层编程中使用        的,不能在linux内核源代码中使用;printk是linux内核源代码中自己封装出来的        一个打印函数,是内核源码中的一个普通函数,只能在内核源码范围内使用,不        能在应用编程中使用

        2.6.3. printk相比printf来说还多了个:打印级别的设置。printk的打印级别        是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信        息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。所以才有了打印级别这个概念

        2.6.4. 命令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来.可以通过这个命令查看 :  cat /proc/sys/kernel/printk。

        2.6.4. ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。

    2.7. 关于驱动模块中的头文件

        驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的所有include目录下的头文件。

    2.8. 用开发板调试模块

        2.8.1. 设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage:  set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'

        2.8.2. 设置bootargs使开发板从nfs去挂载rootfs(内核配置时需要使其支持挂载NFS文件系统,这个在uboot中已经说过)setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off  init=/linuxrc console=ttySAC2,115200

        2.8.3. 修改Makefile中的KERN_DIR使其指向自己建立的内核源码树

        2.8.4. 将自己编译好的驱动.ko文件放入nfs共享目录下去

        2.8.5 开发板启动后使用insmod、rmmod、lsmod等去进行模块实验

        

索引文献:https://www.cnblogs.com/deng-tao/p/6165573.html

索引文献:https://blog.csdn.net/hongzg1982/article/details/54836465

posted @ 2019-01-04 13:13  三七鸽  阅读(5408)  评论(0编辑  收藏  举报