1.Linux内核模块编程特点
1)不能使用C库和C标准头文件
2)必须使用GNU规范
3)没有内存保护机制
4)不能处理浮点运算
5)注意同步和并发的问题
6)注意可移植性
2.函数架构
int xxx(void)
{
return 0;//成功
return 负值;//失败
}
void yyy(void)
{
}
/* 使用module_init()函数告诉内核模块的加载函数
* 使用module_exit()函数告诉内核模块的卸载函数
*/
module_init(xxx);
module_exit(yyy);
3.编译模块可以使用内核中编译模块的方法
3.1 模块的操作命令
insmod:加载模块,内核会执行模块加载函数;
rmmod:卸载模块,内核会执行模块卸载函数;
lsmod:查看当前已加载的模块;
modinfo:查看模块信息;
modprobe:加载模块,内核会执行模块加载函数;
modprobe和insmod的区别
modprobe需要模块信息文件的支持modules.dep,modprobe还会检查模块的依赖,自动加载依赖的模块,insmod则没有这些性质。
modinfo也需要modules.dep的支持。
3.2 模块许可证(GPL)
MODULE_LICENSE("GPL v2");
如果不加,内核提示警告信息,内核有些函数将无法使用。
3.3 内核的输出级别和printk的输出级别
printk函数时具有打印级别,级别0-7,数字越小,级别越高。内核也有默认的输出级别。
当printk的输出级别小于内核的输出级别时,printk打印的内容就可以被打印出来。否则printk的打印信息将被忽略。
查看内核的输出级别
cat /proc/sys/kernel/printk
7 4 1 7
内核输出级别为7,printk的默认级别为4
如何修改内核的默认输出级别
方法1:
echo 5 > /proc/sys/kernel/printk //方法1无法修改在内核启动时的输出信息
方法2:
通过uboot的环境变量bootargs传递打印级别的参数
set bootargs root=/dev/nfs init=/linuxrc nfsroot=192.168.1.141:/home/student/workdir/rootfs ip=192.168.1.20:192.168.1.141:192.168.1.1:255.255.255.0::eth0:on console=ttySAC0,115200 debug //级别为10
set bootargs root=/dev/nfs init=/linuxrc nfsroot=192.168.1.141:/home/student/workdir/rootfs ip=192.168.1.20:192.168.1.141:192.168.1.1:255.255.255.0::eth0:on console=ttySAC0,115200 quiet //级别为4
set bootargs root=/dev/nfs init=/linuxrc nfsroot=192.168.1.141:/home/student/workdir/rootfs ip=192.168.1.20:192.168.1.141:192.168.1.1:255.255.255.0::eth0:on console=ttySAC0,115200 loglevel=6 //直接指定级别
4.内核模块参数
4.1 作用
在加载模块和加载模块后,能够给模块传递相应的参数信息
4.2 使用
module_param();
module_param_array();
//在其它文件调用时候,extern 申明下就可以了,但是加载insmod的时候,必须先加载所依赖的模块,再加载自己
/sys/module/模块名/paramters/ 目录下有对应的文件。
如果模块参数的权限为0,在该目录下就不会有对应的文件;
如果模块参数的权限非0,在该目录下有对应的同名文件,同时该文件的权限和定义模块参数是提供的权限相同。
我们可以通过修改这些文件的内容来实现对模块参数的修改。
5.内核模块依赖
一个模块使用了另一个模块的变量或者函数,第一个模块就依赖于第二个模块
模块的导出符号
如果一个模块中的变量或者函数希望被别的模块使用,需要将对应的变量或者函数导出
EXPORT_SYMBOL();
EXPORT_SYMBOL_GPL();
前一个导出的内容所有模块都能使用,后一个导出的内容只有遵循GPL协议的模块才能使用。所以模块编程是一定要添加:MODULE_LICENSE("GPL v2");
6.linux系统调用的原理和实现
6.1 作用
为用户提供统一的硬件抽象层,操作一个文件,无需关心这个文件是存在于哪个硬件上(硬盘 SD卡 U盘 nand flash),只需要调用对应的系统调用即可(open read write...)。
6.2 原理
1)应用程序调用open
2)进程会调用C库中对应的open函数的实现
3)C库的open的实现会将open对应的系统调用号保存到寄存器中
4)C库的open的实现会调用软中断swi(svc)触发一个软中断异常
5)进程就会跳转到内核实现定义的一个位置(异常向量表)
6)该位置对应的异常向量入口是(vector_swi)
7)这个函数会根据系统调用号,在系统预先定义的一个系统调用表中找到open函数对应的内核实现sys_open
8)执行该函数
9)执行完毕,原路返回
6.3 添加一个系统调用
1)添加系统调用的内核实现
在arch/arm/kernel/sys_arm.c中添加新系统调用的内核实现sys_add;
2)添加新的系统调用号
在arch/arm/include/asm/unistd.h中添加一个新的系统调用号__NR_add;
3)更新系统调用表
在arch/arm/kernel/calls.S中的系统调用表中添加新的一项:CALL(sys_add);
重新编译内核,新内核就有新的系统调用,可以在用户空间使用syscall函数完成对新的系统调用的调用。