利用《Grub的概述》引导器,我们终于可以加载内核了。如何获取,编译,修改,发布,是程序员的必修课程。
可以在官网下载,这里使用linux-4.15,其包含的内容:
最权威的参考资料:/Documentation/Kbuild/makefiles.txt
顶层Makefile决定哪些目录中的文件将编译进内核。
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
core-y := usr/
...
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
顶层Makefile将13个子目录分成5个部分:init-y、drivers-y、net-y、libs-y、core-y顶层通过下列语句包含和体系架构有关的Makefile。其中/arch比较特殊, .config文件中,用户配置$(ARCH)参数,来决定其Makefile的。
关于.config文件的生成
内核配置工具读取各个Kconfig文件,生成配置界面共开放人员配置内核,最后生成配置文件.config。
关于Kconfig的最权威资料在/Documentations/Kbuild/kconfig-language.txt
观察.config文件会发现变量的值主要有两种y、m,各级的Makefile将会根据这些变量的值来决定编译哪些文件,同时是编译进内核,还是作为内核模块存在。
当.o文件由单个文件编译而成时,用下面的语句:
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
当.o文件由多种文件编译而成时,用下面的语句:
obj-$(CONFIG_ISDN) +=isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_commen.o
以linux
中的驱动为例,其一般都放在 linux-4.15/drivers/ 目录下,因此在这个目录中创建一个hello
文件夹。
$ cd drivers
$ mkdir hello
首先是源码,在hello
文件夹中创建源文件hello.c
#include
#include
// 当驱动被加载的时候,执行此函数
static int __init hello_init(void)
{
printk(KERN_ALERT "welcome, hello\n");
return 0;
}
// 当驱动被卸载的时候,执行此函数
static void __exit hello_exit(void)
{
printk(KERN_ALERT "bye, hello\n");
}
// 版权声明
MODULE_LICENSE("GPL");
// 以下两个函数属于 Linux 的驱动框架,只要把驱动两个函数地址注册进去即可。
module_init(hello_init);
module_exit(hello_exit);
有两个小地方注意一下:
在内核中,打印函数是 printk,而不是 printf;
打印信息的级别有好几个,从 DEBUG 到 EMERG,这里使用的是 KERN_ALERT,方便查看打印信息。
Linux内核整体结构已经很庞大,包含了很多的组件,而对于我们工程师而言,有两种方法将需要的功能包含进内核当中。
创建 Kconfig 文件这个文件是用来对内核进行配置的,当执行 make menuconfig 指令的时候,这个文件就被解析。
config HELLO
tristate "hello driver"
help
just a simplest driver.
default y
它登记在 linux-4.15/drivers/Kconfig 文件的末尾
source "drivers/hello/Kconfig"
endmenu // 加在这一句的上面
执行下面的命令,重新编译。
make distclean
make ARCH=x86_64 defconfig
make ARCH=x86_64 menuconfig
可以看到 hello driver 前面显示的是型号 *,这表示:该驱动将会编译进内核。
然后创建Makefile
文件
obj-$(CONFIG_HELLO) += hello.o
##CONFIG_HELLO 可以看做一个变量,在编译的时候,这个变量的值可能是:y, n 或者 m。
##在刚才的 Kconfig 参数配置中,CONFIG_HELLO 被设置为 y,于是这句话就被翻译成: obj-y += hello,表示把 hello 驱动编译进内核。
再把hello目录,加入到driver的Makefile中
obj-$(CONFIG_HELLO) += hello/
万事俱备,只欠编译!依次执行如下指令:
make
编译为驱动模块,也有两种 操作方式:
在执行 make ARCH=x86_64 menuconfig 指令的时候,把 hello 配置成 M;
然后在 linux-4.15 中执行编译模块指令:make -j4 modules。
编译成功之后,就可以得到文件: linux-4.15/drivers/hello/hello.ko。
这样的编译指令,是把所有的模块都编译了一次(在输出信息中,可以看到编译了很多模块)。
只编译 hello 这一个驱动模块
另外一种编译驱动模块的方式是:进入hello
目录,只编译这一个驱动模块。
这种编译方法,就需要修改hello
目录下的Makefile
文件了,内容如下:
可以把 hello 目录下的所有文件删除,只保留源文件 hello.c,然后新建 Makefile 文件。
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif
然后,在hello
文件夹中执行make
指令,即可得到驱动模块 hello.ko 。
验证
## 加载驱动
sudo insmod ./hello.ko
## 可以看到welcome, hello
dmesg
## 卸载驱动
sudo rmmod hello
## 可以看到bye,hello
dmesg
《Linux内核源码组织结构》
《Linux驱动实践:带你一步一步编译内核驱动程序 》