内核模块学习

linux内核的框架很大,组件很多,如果把所有东西全编译进内核,内核会很大,如果我们要进行修改时,还要重新编译内核,耗时费力.linux的模块机制解决了这样的问题,模块本身不编译进内核,控制了内核的大小,模块一旦被加载,和内核中其他部分一样.

一.简单的例子

说的多,不如贴代码,一个简单的例子程序如下:

/*bike.c*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>


static char * bike_name = "mobike";
module_param(bike_name,charp,S_IRUGO);

static int bike_num = 3000;
module_param(bike_num,int,S_IRUGO);

static int __init bike_init(void)
{
    printk(KERN_INFO "hello bike module\n");
    printk(KERN_INFO "bike-name:%s\n",bike_name);
    printk(KERN_INFO "bike_num:%d\n",bike_num);
    return 0;
}
module_init(bike_init);

static void __exit bike_exit(void)
{
    printk(KERN_INFO "bike module exit\n");
}
module_exit(bike_exit);

MODULE_AUTHOR("Trice");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple program for testing kernel module");
MODULE_VERSION("V1.0");

一个内核模块主要由:
1.模块加载函数
2.模块卸载函数
3.模块声明
4.模块参数(可选)
5.模块作者(可选)


模块加载函数

static int __init bike_init(void)
{
    printk(KERN_INFO "%s\n",bike_name);
    printk(KERN_INFO "%d\n",bike_num);
    return 0;
}
module_init(bike_init);

linux中,标识为__init的函数如果直接编译进内核,连接时会放在.init.text段,初始化时,内核会调用这些__init函数,完成后释放.类似数据可以标识为__initdata.
所以bike_init以__init标识,加载时用module_init指定.


模块卸载函数

static void __exit bike_exit(void)
{
    printk(KERN_INFO "bike module exit\n");
}
module_exit(bike_exit);

卸载函数用__exit标识,用module_exit指定


模块声明

MODULE_AUTHOR("Trice");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple program for testing kernel module");
MODULE_VERSION("V1.0");

声明一些信息,其中MODULE_LICENSE("GPL v2");声明模块的许可权限.


模块参数

static char * bike_name = "mobike";
module_param(bike_name,charp,S_IRUGO);

static int bike_num = 3000;
module_param(bike_num,int,S_IRUGO);

使用module_param(参数名,类型,参数读写权限),charp表示字符指针,传递时采用insmod 模块名 参数名=参数值,S_IRUGO,表示读,U->user G->group O->other.


二.模块的编译

上面的bike.c可以用下面的Makefile进行编译

KVERS = $(shell uname -r)

#kernel module
obj-m += bike.o


build:kernel_modules

kernel_modules:
    make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules

clean:
    make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

obj-m += bike.o这种在内核编译中很常见,意思把bike.c编成模块,进入相应目录使用里面Makefile进行编译,M=$(CURDIR)指定目标.
如果有多个文件可以使用:

obj-m := xx.o
xx-objs := file1.o file2.o

三.测试结果

huang@ubuntu:~/test$ ls
bike.c  Makefile
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ make
make -C /lib/modules/3.16.0-30-generic/build M=/home/huang/test modules
make[1]: Entering directory `/usr/src/linux-headers-3.16.0-30-generic'
  CC [M]  /home/huang/test/bike.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/huang/test/bike.mod.o
  LD [M]  /home/huang/test/bike.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.16.0-30-generic'
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ ls
bike.c   bike.mod.c  bike.o    modules.order
bike.ko  bike.mod.o  Makefile  Module.symvers
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ sudo insmod bike.ko
huang@ubuntu:~/test$ lsmod | grep bike
bike                   12665  0 
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ dmesg | tail -3
[19695.556447] hello bike module
[19695.556453] bike-name:mobike
[19695.556456] bike_num:3000
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ sudo rmmod bike
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ lsmod | grep bike
huang@ubuntu:~/test$ 
huang@ubuntu:~/test$ dmesg | tail -1
[19804.797345] bike module exit
huang@ubuntu:~/test$ sudo insmod bike.ko bike_name=ofo bike_num=666
huang@ubuntu:~/test$ dmesg | tail -3
[20692.083907] hello bike module
[20692.083913] bike-name:ofo
[20692.083915] bike_num:666
huang@ubuntu:~/test$ sudo rmmod bike;dmesg | tail -1
[20760.653996] bike module exit

insmod rmmod lsmod分别用于装载 卸载 查看模块
dmesg用于显示打印信息
tail -n显示最后n行

四.总结

内核模块只是把.c文件编译成.ko文件,其实就是一种目标文件的形式,然后在装载时,由内核实现模块的链接运行,同时,在内核加载和卸载的过程中,会通过函数回调用户定义的模块入口函数和模块出口函数,实现相应的功能。

猜你喜欢

转载自blog.csdn.net/weixin_34392843/article/details/87232384