向内核添加自己的模块
http://www.cnblogs.com/timkyle/archive/2012/04/13/2444975.html
说明:
我开发板的内核启动参数环境变量bootargs内容为root=/dev/nfs nfsroot=192.168.1.10:/nfsroot ip=192.168.1.20 console=ttySAC0,115200。
表示文件系统为远程网络文件系统,通过nfs连接,远程网络文件系统具体位置为192.168.1.10主机上的/nfsroot/目录,板子本身的IP为192.168.1.20。
而我主机的/nfsroot是链接,实际上就是/nfsroot -> /timkyle-dev/my/nfsroot。
内核源代码目录为/timkyle-dev/my/arm/newnfs/home/linux-2.6.28_smdk6410。
一:在非内核源代码目录树创建文件夹,做成模块,然后加载入内核。(做这个例子,必须保证内核源代码已经编译过!)
第一步:在文件系统/nfsroot创建目录/nfsroot/kern/my20120408/01/。
创建文件/nfsroot/kern/my20120408/01/test.c,内容如下:
说明:
printk()函数在内核源码树的./include/linux/kernel.h中声明。
module_init()在内核源码树的./include/linux/init.h中声明。
module_exit()在内核源码树的./include/linux/init.h中声明。
modules_init()的参数是一个函数指针,指向的函数必须满足格式int fun(void),此函数在模块加载入内核的时候自动调用;
modules_exit()的参数是一个函数指针,指向的函数必须满足格式void fun(void),此函数在模块从内核卸载的时候自动调用。
创建文件/nfsroot/kern/my20120408/01/Makefile,内容如下:
说明:
obj-m = test.o 表示从test.c或test.S编译出test.o,然后把test.o做成模块test.ko。
KERN = /timkyle-dev/my/arm/newnfs/home/linux-2.6.28_smdk6410 是用变量KERN保存内核源码树的目录
make -C $(KERN) M=`pwd` modules 表示用内核源码树中的make规则编译当前目录的文件为模块
make -C $(KERN) M=`pwd` modules clean 表示用内核源码树中的make规则清理当前目录的模块相关文件
特别注意因为当前目录不在内核源码树里,所以只能是obj-m编译成模块形式,绝对不能是obj-y直接编译进内核模式。
然后在目录/nfsroot/kern/my20120408/01/,执行指令# make,编译当前代码为模块,过程如下:
查看目录/nfsroot/kern/my20120408/01/,生成如下文件,其中test.ko就是编译好的模块文件:
查看模块test.ko的信息,如下:
然后在开发板上操作,过程如下:
说明:
lsmod指令用于查看当前运行的内核加载有什么模块。
insmod指令用于将模块加载入当前运行的内核。
rmmod指令用于将模块从当前运行的内核卸载。
从输出信息可以看出,模块test.ko成功加入当前运行的内核,不过提示说污染了内核,因为我们的内核没有遵循GPL等开源协议。
但移除模块时,说没有找到/lib/modules
为了达到完美且正确的效果,重新编译内核,并拷贝zImage到tftp目录,然后把模块安装到文件系统。如下:
1 [root@localhost linux-2.6.28_smdk6410]# make 2 [root@localhost linux-2.6.28_smdk6410]# \cp -f ./arch/arm/boot/zImage /tftpboot/ 3 [root@localhost linux-2.6.28_smdk6410]# make modules_install INSTALL_MOD_PATH=/nfsroot/
则文件系统中就有对应模块相关的文件了:
1 [root@uplooking /]# ls lib/modules/2.6.28.6/
修改文件/nfsroot/kern/my20120408/01/test.c,如下:
说明:
文件./include/linux/module.h包含MODULE_LICENSE()、MODULE_AUTHOR()、MODULE_DESCRIPTION()、MODULE_VERSION()等宏定义。
在文件夹/nfsroot/kern/my20120408/01/重新执行make,编译test.ko模块。过程如下:
在主机端查看模块test.ko的信息,如下:
在开发板上,执行如下:
说明:
用modinfo指令查看模块的信息,如代码描述的一致。
用insmod指令加载test.ko模块时,自动执行module_init()注册的函数,打印出timkyle。
用rmmod指令卸载test模块时,自动执行module_exit()注册的函数,打印出bye!。
注意卸载模块时,提示模块test没找到,是此内核的rmmod工具指令本身的问题。
可以用另一种方法加载模块,就是指令modprobe。要使用此指令模块文件test.ko必须已经位于文件系统中的目录/lib/modules/2.6.28.6/里。过程如下:
该文档来自http://hi.baidu.com/sddghaladeng/blog/item/fba787eefe797de5cf1b3ee5.html
linux设备驱动有两种加载方式insmod和modprobe,下面谈谈它们用法上的区别
1、insmod一次只能加载特定的一个设备驱动,且需要驱动的具体地址。写法为:
insmod drv.ko
2. modprobe则可以一次将有依赖关系的驱动全部加载到内核。不加驱动的具体地址,但需要在安装文件系统时是按照make modues_install的方式安装驱动模块的。驱动被安装在/lib/modules/$(uname -r)/...下。写法为:
modprob drv
------
modprobe 和insmod一样都是用来加载内核module的
不过modprobe比较智能,它可以根据module的依赖性来自动为你加载;
而insmod就做不到这点。
可见顺利完成任务,而且卸载模块时,也不会提示错误!
二:多函数封装成模块,并且把通用的函数实现导出符号。
第一步:创建文件夹/nfsroot/kern/my20120408/02/。
创建文件/nfsroot/kern/my20120408/02/test2.c,内容如下:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 5 MODULE_LICENSE("GPL"); 6 MODULE_AUTHOR("timkyle"); 7 MODULE_DESCRIPTION("Just for test module!"); 8 MODULE_VERSION("0.01"); 9 10 int x = 5; 11 int y = 7; 12 int num; 13 14 int add(int a, int b) 15 { 16 return a + b; 17 } 18 EXPORT_SYMBOL(add); 19 20 static int __init load(void) 21 { 22 printk("module load!\n"); 23 printk("welcome:\n"); 24 num = add(x, y); 25 printk("%d + %d = %d\n", x, y, num); 26 27 return 0; 28 } 29 30 static void __exit unload(void) 31 { 32 printk("module unload!\n"); 33 printk("Bye!\n"); 34 } 35 36 module_init(load); 37 module_exit(unload);
说明:
EXPORT_SYMBOL()用于导出符号,导出后可供其他模块使用。
__init, __initdata等属性标志, 是要把这种属性的代码放入目标文件的.init.text节, 数据放入.init.data节。
这一过程是通过编译内核时为相关目标平台提供了xxx.lds(vmlinux.lds)链接脚本, 来指导ld完成的。
对编译成module的代码和数据来说, 当模块加载时, __init属性的函数就被执行;
对静态编入内核的代码和数据来说, 当内核引导时, do_basic_setup()函数调用do_initcalls()函数, 后者负责所有.init节函数的执行。
在初始化完成后,用这些关键字标识的函数或数据所占的内存会被释放掉。
__exit修饰词标记函数只在模块卸载时使用。
如果模块被直接编进内核则该函数就不会被调用。如果内核编译时没有包含该模块,则此标记的函数将被简单地丢弃。
创建文件/nfsroot/kern/my20120408/02/Makefile,内容如下:
1 obj-m = test2.o 2 3 KERN = /timkyle-dev/my/arm/newnfs/home/linux-2.6.28_smdk6410 4 5 all: 6 make -C $(KERN) M=`pwd` modules 7 clean: 8 make -C $(KERN) M=`pwd` modules clean
首先在主机端编译模块,过程如下:
然后在开发板端载入模块和卸载模块,过程如下:
说明:
文件系统中的文件/proc/kallsyms动态包含符号表。可在文件系统上用指令# cat /proc/kallsyms查看。
像[test2]这样[]括起来的,表示是模块。注意其中一行,其中T表示,通过EXPORT_SYMBOL()导出的符号。
另外在主机上通过编译出来的内核镜像./vmlinux(带符号表)也能查看内核的符号表,指令为# arm-none-linux-gnueabi-nm ./vmlinux。
还有一种方法是在主机上直接读取内核编译后出现的文件./System.map,指令为# cat ./System.map。
但是vmlinux和System.map都是静态的,即已经直接编译进内核的。/proc/kallsyms才是动态的,能反应加入模块的符号表信息。
三:多文件封装成多模块。
第一步:创建文件夹/nfsroot/kern/my20120408/03/。
创建文件/nfsroot/kern/my20120408/03/test.c,内容如下:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 5 #include "add.h" 6 7 MODULE_LICENSE("GPL"); 8 MODULE_AUTHOR("timkyle"); 9 MODULE_DESCRIPTION("Just for module testing"); 10 MODULE_VERSION("0.0.1"); 11 12 int x = 8; 13 int y = 9; 14 int sum; 15 16 static int __init load(void) 17 { 18 printk("module load!\n"); 19 sum = add(x, y); 20 printk("%d + %d = %d\n", x, y, sum); 21 22 return 0; 23 } 24 25 static void __exit unload(void) 26 { 27 printk("module unload!\n"); 28 printk("Bye!\n"); 29 } 30 31 module_init(load); 32 module_exit(unload);
创建文件/nfsroot/kern/my20120408/03/add.h,内容如下:
1 #ifndef _ADD_H_ 2 #define _ADD_H_ 3 4 int add(int, int); 5 6 #endif // _ADD_H_
创建文件/nfsroot/kern/my20120408/03/add.c,内容如下:
1 #include <linux/module.h> 2 3 #include "add.h" 4 5 MODULE_LICENSE("GPL"); 6 7 int add(int a, int b) 8 { 9 return a + b; 10 } 11 EXPORT_SYMBOL(add);
说明:EXPORT_SYMBOL()是导出符号,供其他模块使用。也可以使用EXPORT_SYMBOL_GPL()来导出符号。
创建文件/nfsroot/kern/my20120408/03/Makefile,内容如下:
1 obj-m = add.o test.o 2 3 KERN = /timkyle-dev/my/arm/newnfs/home/linux-2.6.28_smdk6410 4 5 all: 6 make -C $(KERN) M=`pwd` modules 7 8 clean: 9 make -C $(KERN) M=`pwd` modules clean
说明:obj-m = add.o test.o表示分别编译出模块add.ko和test.ko,注意因为test.c依赖add.c,所以必须先载入模块add.ko。
首先,在主机端编译模块,过程如下:
然后在开发板端载入和卸载模块,过程如下:
说明:显示
表示add模块被其他1个模块使用,这个模块是test。
四:多文件封装成单模块
创建文件夹/nfsroot/kern/my20120408/04/。
创建文件/nfsroot/kern/my20120408/04/test.c,内容如下:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 5 #include "add.h" 6 7 MODULE_LICENSE("GPL"); 8 MODULE_AUTHOR("timkyle"); 9 MODULE_DESCRIPTION("Just for module testing"); 10 MODULE_VERSION("0.0.1"); 11 12 int x = 3; 13 int y = 4; 14 int sum; 15 16 static int __init load(void) 17 { 18 printk("module load!\n"); 19 sum = add(x, y); 20 printk("%d + %d = %d\n", x, y, sum); 21 22 return 0; 23 } 24 25 26 static void __exit unload(void) 27 { 28 printk("module unload!\n"); 29 printk("Bye!\n"); 30 } 31 32 module_init(load); 33 module_exit(unload);
创建文件/nfsroot/kern/my20120408/04/add.h,内容如下:
1 #ifndef _ADD_H_ 2 #define _ADD_H_ 3 4 int add(int, int); 5 6 #endif // _ADD_H_
创建文件/nfsroot/kern/my20120408/04/add.c,内容如下:
1 #include <linux/module.h> 2 3 #include "add.h" 4 5 MODULE_LICENSE("GPL"); 6 7 int add(int a, int b) 8 { 9 return a + b; 10 } 11 EXPORT_SYMBOL(add);
创建文件/nfsroot/kern/my20120408/04/Makefile,内容如下:
1 obj-m = mydrv.o 2 3 mydrv-objs = add.o test.o 4 5 KERN = /timkyle-dev/my/arm/newnfs/home/linux-2.6.28_smdk6410 6 7 all: 8 make -C $(KERN) M=`pwd` modules 9 10 clean: 11 make -C $(KERN) M=`pwd` modules clean
说明:obj-m = mydrv.o表示编译成模块mydrv.ko。mydrv-objs = add.o test.o表示模块mydrv.ko有add.o和test.o组成。
首先在主机端编译模块,过程如下:
然后在开发板端载入和卸载模块,过程如下:
五:给模块传参数
在用户态下编程可以通过main()的来传递命令行参数,而编写一个内核模块则通过module_param()。
参数用 module_param 宏定义来声明, 它定义在 ./include/linux/moduleparam.h。
module_param(name,type,perm);
module_param 使用了 3 个参数: 变量名, 它的类型, 以及一个权限掩码用来做一个辅助的 sysfs 入口.。
这个宏定义应当放在任何函数之外, 典型地是出现在源文件的前面。
创建文件夹/nfsroot/kern/my20120408/05/。
创建文件/nfsroot/kern/my20120408/05/test.c,内容如下:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/moduleparam.h> 4 #include <linux/kernel.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("timkyle"); 8 MODULE_DESCRIPTION("Just for module testing"); 9 MODULE_VERSION("0.0.1"); 10 11 static int x = 5, y = 8; 12 13 module_param(x, int, 0640); 14 module_param(y, int, 0644); 15 16 static int __init load(void) 17 { 18 printk("module load!\n"); 19 printk("%s %d: %d + %d = %d\n", __func__, __LINE__, x, y, x + y); 20 printk("Welcome!\n"); 21 22 return 0; 23 } 24 25 static void __exit unload(void) 26 { 27 printk("module unload!\n"); 28 printk("%s %d: %d + %d = %d\n", __func__, __LINE__, x, y, x + y); 29 printk("Bye!\n"); 30 } 31 32 module_init(load); 33 module_exit(unload);
创建文件/nfsroot/kern/my20120408/05/Makefile,内容如下:
1 obj-m = test.o 2 3 KERN = /timkyle-dev/my/arm/newnfs/home/linux-2.6.28_smdk6410 4 5 all: 6 make -C $(KERN) M=`pwd` modules 7 8 clean: 9 make -C $(KERN) M=`pwd` modules clean
首先,在主机端编译出模块,过程如下:
然后在开发板端载入和卸载模块,过程如下:
说明:当模块成功载入后,会动态的在/sys/module/目录下生产模块对应的文件夹,
里面的parameters文件夹里放着对应每个用module_param()声明的变量。
只要权限允许,可以通过cat读取变量的当前值,或通过echo改变变量的当前值,即时生效。
特别说明:
如果要把这些模块做成是和主机的模块,只需更改Makefile中的KERN变量,路径改为主机内核源码路径。举例如下:
把上面第五个程序的Makefile修改如下:
首先,编译成模块,过程如下:
然后,载入和卸载模块,过程如下:
说明:http://smilejay.com/2011/12/linux_loglevel/
只有当printk打印信息时的loglevel小于console loglevel的值(即:优先级高于console loglevel),这些信息才会被打印到console上。
改变console loglevel的方法有如下几种:
1.启动时Kernel boot option:loglevel=level
2.运行时Runtime: dmesg -n level
(注意:demsg -n level 改变的是console上的loglevel,dmesg命令仍然会打印出所有级别的系统信息。)
3.运行时Runtime: echo $level > /proc/sys/kernel/printk
4.运行时Runtime:写程序使用syslog系统调用(可以man syslog)
================简述综述==================
01: 内核模块的基本框架
"test.c"
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL"); // 模块遵循GPL协议发布 (防止内核报警告)
int aaa(void) { return 0; } // 入口函数原型
void bbb(void) { } // 出口函数原型
module_init(aaa); // 模块进入内核时执行
module_exit(bbb); // 模块退出内核时执行
"Makefile"
obj-m = test.o
all:
make -C 内核源码目录 M=`pwd` modules
clean:
make -C 内核源码目录 M=`pwd` modules clean
02: 模块相关宏
module_init module_exit
__init __exit
03: 多文件编译
obj-m = mydrv.o
mydrv-objs = aaa.o bbb.o ccc.o
04: 导出符号
EXPORT_SYMBOL
EXPORT_SYMBOL_GPL
05: 模块参数传递
module_param
=============================================
配置内核 make menuconfig 会根据Kconfig文件生成选项菜单。
保存退出后 根据用户的选择 生成 .config 文件
在 编译内核 make 时。会根据 .config 文件自动生成 autoconf.h 头文件
每一个内核源代码都会包含该头文件
可以通过里头的宏定义 进行条件编译
=======================
Kconfig
语法规则 Documentation/kbuild/kconfig-language.txt
主菜单:arch/arm/Kconfig
==============================================
将我们的代码加入内核
语法规则 Documentation/kbuild/kconfig-language.txt
主菜单:arch/arm/Kconfig
==============================================
将我们的代码加入内核
新建目录 包含以下三部分:
源代码 : xxx.c
Makefile : obj-$(CONFIG_XXX) = xxx.o
Kconfig : config XXX
其中Makefile和Kconfig要分别被上一层的Makefile和Kconfig所包含
==============================================
安装模块
insmod ./xxx.ko
modprobe xxx
卸载模块
rmmod xxx
查看模块信息
modinfo ./xxx.ko
查询系统中已加载的模块
lsmod