首先我们要明确linux下内核编程与平时我们在linux下写的C程序之间的区别,在这里我们把在linux下写一般C程序的过程称之为用户层编程,与之相对的就是我们要学习的内核编程。

首先介绍linux内核,内核指的是一个提供硬件抽象层、磁盘、文件系统控制及多任务等功能的系统软件。我们可以把内核理解为操作系统的核心部分(注意:内核不等于操作系统)。而linux内核就是linux操作系统的内核。

内核由负责不同功能的内核模块组成,内核模块可以被单独编译,但是不能单独运行,它必须要链接到内核作为内核的一部分在内核空间中运行。内核之所以采用这种结构方式是因为这体现了模块化的思想,在保证内核不会太大的同时,又可以做到模块一旦被加载就和内核中的其他部分一样使用。

用户层编程和内核模块编程的区别:

用户层编程与内核模块编程区别

下面编写一个内核模块中的hello world程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//filename: hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
//这里的三个头文件是编写内核模块程序所必须的
MODULE_LICENSE("Dual BSD/GPL");
/*可选
MODULE_AUTHOR("yinwoods");
MODULE_DESCRIPTION("This is a simple example!\n");
MODULE_ALIAS("A simplest example");
*/
static int hello_init(void) {
printk(KERN_ALERT "hello, I am yinwoods");
//printk是内核态信息打印函数,功能上与printf类似。不同的是printk具有消息打印机别,这里的KERN_ALERT即为一个消息级别。
return 0;
}
static void hello_exit(void) {
printk(KERN_ALERT "good bye, kernel\n");
}
module_init(hello_init);
module_exit(hello_exit);

module_init()函数和module_exit()函数

module_init(hello_init)是指模块程序从hello_init开始执行,函数参数就是注册函数的函数名。

同理,module_exit(hello_exit)是指模块程序从hello_exit离开,函数参数就是卸载函数的函数名。

这里可以类比C++类中的构造函数与析构函数;也就是说我们一般在module_init()中动态申请内存、中断等资源;而在模块卸载函数module_exit()中回收这些资源。

程序写好了,那么怎么编译运行呢?

内核编写程序的编译运行

在linux下对内核程序进行编译运行与普通程序是不同的,不是通过g++的命令实现,而是需要我们编写makefile脚本并使用make命令来实现多文件的编译。

makefile是一种脚本,这种脚本主要是用于多文件的编译。它定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译等等。

make是一个解释makefile中指令的命令工具,可以维护具有相互依赖性的源文件,当某些文件发生改变时,它能自动识别出,并只对改动后的文件进行编译。

简单介绍一下如何编写makefile:

makefile的规则大体上就是以下格式:

1
2
target:dependency-file
command

target是一个目标文件,可以是Object File(linux下的.o文件),也可以是最终的执行文件。
而dependency-file是生成相应target所需要依赖的文件或者其它的target。
command就是最终由make执行的命令。

也就是说我们告诉了make程序需要生成的文件target和它所依赖的dependency-file文件还有执行命令command,那么make程序只需要按照这种方式解析makefile即可。

内核程序的编译运行

1、编写好makefile文件

2、使用make进行编译

3、编译后使用sudo insmod *.ko 加载模块

4、使用dmesg查看运行结果

5、使用rmmod tiger卸载模块

6、使用make clean删除中间生成的文件

在这里贴出示例程序对应的makefile文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
KERNEL_DIR = /usr/src/linux-headers-$(shell uname -r)/
#KERNAL_DIR表示内核源代码的位置。在这里是链接到包含着正在使用内核对应源代码的目录树位置。
PWD := $(shell pwd)
#PWD指示了当前工作目录并且是我们自己内核模块的源代码位置。
obj-m := hello.o
#需要编译连接多个文件时,只需要在添加。再添加相应的语句即可,如下:
#obj-m += device1.o
#obj-m += device2.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
#这里是指在包含源代码位置的地方进行make,然后编译$(PWD)目录下的modules。这里允许我们使用所有定义在内核源代码树下的所有规则来编译我们的内核模块。
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
$(RM) Module.markers modules.order

按照上述的编译运行步骤,make, insmod hello.ko, dmesg即可看到

1
2
hello, I am yinwoods
good bye, kernel

最后通过rmmod hello 卸载该模块。

因为导师布置的毕设题目是platform虚拟总线下的进程调度,所以这段时间要好好补一补linux内核设备驱动方面知识了。

下一步是实现platform下简单的线程通信。加油!