BruceFan's Blog

Stay hungry, stay foolish

0%

Linux设备驱动(一)内核模块

实验环境

VirtualBox + Ubuntu12.04 32bit + linux-3.10.103内核
这次编译的是32位的内核,因此64-bit kernel一项不能选中,而且一般将内核源码放置在/usr/src目录下。
编译命令make执行完之后,会产生核心bzImage和可加载模块modules
接着是安装过程:

1
2
3
4
5
$ sudo make modules_install # 安装模块
$ sudo make install # 安装核心
$ mkinitramfs -o /boot/initrd.img-3.10.103 # 创建initrd文件
$ cd /boot/grub
$ sudo update-grub # 更新grub来显示自己安装的内核

经过以上的步骤,内核基本上已经编译成功并且已经安装上了,重启即可使用自己编译的内核了。

Hello World模块

先写一个最简单的内核模块的”Hello World”模块:
代码清单 hello.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
printk(KERN_ALERT "Hello, world!\n");
return 0;
}

static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

所有模块代码中都包含module.hinit.h两个头文件,module.h包含可装载模块需要的大量符号和函数定义。包含init.h的目的是指定初始化和清除函数。大部分模块还包括moduleparam.h头文件,这样就可以在装载的时候向模块传递参数,下一节中会介绍。
接着需要用make来编译这个源文件,Makefile如下:
代码清单 Makefile

1
2
3
4
5
6
7
8
9
obj-m := hello.o
KERNELBUILD := /lib/modules/$(shell uname -r)/build
# specify flags for the module compilation
EXTRA_CFLAGS = -g -O0

modules:
make -C $(KERNELBUILD) M=$(CURDIR) modules
clean:
make -C $(KERNELBUILD) M=$(CURDIR) clean

Makefile文件与源代码hello.c位于同一目录,开启其中的EXTRA_CFLAGS = -g -O0,可以包含调试信息。CURDIR是当前目录。
编译并加载和卸载模块:

1
2
3
4
5
6
7
8
9
10
11
$ make
$ sudo insmod hello.ko # 加载模块需要root权限
$ lsmod # 查看已经加载的模块
Module Size Used by
hello 590 0
...
$ sudo rmmod hello # 卸载模块
$ dmesg # 查看内核信息
...
[4618.166833] Hello, world
[4686.249465] Goodbye, cruel world

带参数的模块

代码清单 hellop.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");

static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
static int hello_init(void)
{
int i;
for (i = 0; i < howmany; i++)
printk(KERN_ALERT "(%d) Hello, %s\n", i, whom);
return 0;
}

static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

参数必须使用module_param宏来声明,这个宏在moduleparam.h中定义。module_param需要三个参数:变量的名称、类型以及用于sysfs入口项的访问许可掩码,这个宏必须放在任何函数之外,通常在源文件头部。
Makefile和hello模块的基本相同:
代码清单 Makefile

1
2
3
4
5
6
7
8
9
obj-m := hellop.o
KERNELBUILD := /lib/modules/$(shell uname -r)/build
# specify flags for the module compilation
EXTRA_CFLAGS = -g -O0

modules:
make -C $(KERNELBUILD) M=$(CURDIR) modules
clean:
make -C $(KERNELBUILD) M=$(CURDIR) clean

编译加载和卸载模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo insmod hellop.ko howmany=10 whom="Fan"
$ sudo rmmod hellop
$ dmesg
[23365.767137] (0) Hello, Fan
[23365.767137] (1) Hello, Fan
[23365.767137] (2) Hello, Fan
[23365.767137] (3) Hello, Fan
[23365.767137] (4) Hello, Fan
[23365.767137] (5) Hello, Fan
[23365.767137] (6) Hello, Fan
[23365.767137] (7) Hello, Fan
[23365.767137] (8) Hello, Fan
[23365.767137] (9) Hello, Fan
[23388.198252] Goodbye, cruel world

reference
《Linux设备驱动程序》