向内核添加自己的模块

时间:2022-08-15 15:46:17

向内核添加自己的模块

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 入口.。

这个宏定义应当放在任何函数之外, 典型地是出现在源文件的前面。

更多说明,请参看http://baike.baidu.com/view/4212836.htm

创建文件夹/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