【嵌入式环境下linux内核及驱动学习笔记-(2-linux内核模块)】
目录
- 1、内核模块介绍
- 2、内核模块的结构
-
- 2.1 hello world例程
- 2.2 结构说明
-
- 2.2.1 包含库
- 2.2.2 __init的作用 :
- 2.2.3 内核是裸机程序,不可以调用C库中printf函数来打印程序信息, Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel), printk不支持浮点数打印。
- 2.2.4 __exit的作用:
- 2.2.5 MODULE_LICENSE(字符串常量);
- 2.2.6 module_init 宏
- 2.2.7 module_exit宏
- 2.2.8 内核模块信息宏
- 2.2.9 模块参数
- 2.2.10 导出符号
- 3、常用操作命令
-
- 3.1 lsmod
- 3.2 insmod
- 3.3 rmmod
- 3.4 dmesg
- 3.5 modinfo
1、内核模块介绍
Linux提供了一种 需要时可以被动态加载和移除的代码的机制,这种机制称为模块(Module),内核模块具有以下两个特点:
- 模块本身不被编译入内核映像,从而使内核映像比较精简。
- 模块被加载后,其与其它内核进程没有区别。
内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。
记住,内核模块的运行是在内核空间里的。
2、内核模块的结构
2.1 hello world例程
#include <linux/init.h>
#include <linux/module.h>/*/static int __init my_init(void){printk("Hello Driver initalized!\\n")return 0;
}static void __exit my_exit(void){printk("Hello Driver exit!\\n")
}module_init(my_init);
module_exit(my_exit);/*/MODULE_LICENSE("GPL");
MODULE_AUTHOR("Micher Lee");
MODULE_DESCRIPTION("");
MODULE_ALIAS("");
这是一个典型的内核模块,用注释符分隔成了三个部分。
- 第一部分为包含的头文件;
- 第二部分为模块的加载与制裁函数;
- 第三部分为许可权声明等描述信息。
而实际,只要记住模块三要素:入口函数 、出口函数、 MODULE__LICENSE
2.2 结构说明
2.2.1 包含库
#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE
2.2.2 __init的作用 :
int __init my_init(void){};
- __init 是一个宏,展开后为:attribute ((section (“.init.text”))) 实际是gcc的一个特殊链接标记
- 指示链接器将该函数放置在 .init.text区段
- 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
2.2.3 内核是裸机程序,不可以调用C库中printf函数来打印程序信息, Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel), printk不支持浮点数打印。
2.2.4 __exit的作用:
void __exit my_exit(void){};
1.__exit是一个宏,展开后为:attribute ((section (“.exit.text”))) 实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
2.2.5 MODULE_LICENSE(字符串常量);
MODULE_LICENSE(“GPL”);
字符串常量内容为源码的许可证协议 可以是"GPL" “GPL v2” “GPL and additional rights” “Dual BSD/GPL” “Dual MIT/GPL” "Dual MPL/GPL"等, "GPL"最常用
其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
2.2.6 module_init 宏
module_init(my_init);
- 用法:module_init(模块入口函数名)
- 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
- 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
2.2.7 module_exit宏
module_exit(myhello_exit);
1.用法:module_exit(模块出口函数名)
2.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
3.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
2.2.8 内核模块信息宏
MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明
MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明
MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名
这些宏用来描述一些当前模块的信息,可选宏
这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:modinfo 模块文件名
2.2.9 模块参数
- 说明
模块也可以被传入参数。只是和函数传参的方式不同。其形式如下:
module_param( name , type , perm);
或
module_param_array(数组name, type , 数组长度 , perm);
其中:
name为全局变量名
type为全局变量的模参专用类型
perm为对应文件 /sys/module/对应模块名/parameters/变量名的操作权限。 一般赋值0664即可。
关于perm这里多解释一下:模块被加载后,在/sys/module目录下会出现以此模块名命名的目录。当perm不为0时,在此目录下还将出现parameters目录,基中包含一系列以参数名命名的文件节点。这些文件的权限值就是perm的值。
- 例子
/*模块名:book 编译后文件名为 book.ko */
#include <linux/init.h>
#include <linux/module.h>static char *book_name = "hello";
module_param(book_name , charp , 0664);static int book_um = 400;
module_param(book_num , int , 0664);static init __init my_init(void){printk("book name:%s\\n", book_name);printk("book num :%d\\n", book_num);return 0
}static void __exit my_exit(void){}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
- 加载
上面这段代码编译后的文件名为book.ko
如果用 “ insmod book.ko ”来加载,则没有参数传入, 变量book_name及book_num的值是默认初始值。
如果用 “ insmod book.ko book_name=“world” book_num=500 ” 语句来加载,则参数被传入。
2.2.10 导出符号
模块是内核的一部分,与内核是一个整体,因此就可以与内核其它程序去共享全局的符号。
Linux的“/proc/kallsyms”文件,对应着整个内核所有的符号表,它记录了符号以及符号所在在内存地址。
对于一个模块,可以通过如下的宏,将自身的符号导出到内核符号表中,可供其它内核进程使用。
- 导出语法
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名); - 使用导出符号
extern 符号名 - 编译及加载
- 案例
下面有两种驱动, 一个export.c用于导出符号,另一个extern.c用于接收使用导出的符号。下面是源码
对两个驱动进行加载后的输出结果:
3、常用操作命令
3.1 lsmod
3.2 insmod
3.3 rmmod
3.4 dmesg
3.5 modinfo
本篇结束