第2章 Linux内核模块


宏内核和微内核


继续前面第一章的知识,虽然有点啰嗦,既然啰嗦了就继续啰嗦下去吧,也是给第一章的内容增加解释。


我们知道内核如果按种类来划分的话,可以分为宏内核微内核,微内核是一个比较先进的内核,我不知道是不是真的先进,但是 BSD 系统的设计就是想用微内核的思想来完成的,结果导致系统稳定性很差,不能达到正常使用的程度。


什么是微内核呢? 

如果把CPU当成是皇帝的话,皇帝身边的大臣们就可以划分为各种进程和线程了,比如打印机是一个进程,微内核的思想就是其他进程之间不能直接通信,他们只能跟CPU中央处理器进程通信,然后中央进程再做调度处理,这样看起来非常完美,也避免了内核很多高耦合的代码。


什么是宏内核呢?

宏内核的设计就跟微内核有点相反,宏内核的子进程之间是可以互相通信的,各个模块之间是可以进行函数调用的,你可以观察下 Linux 内核模块,导出内核符号表后,可以在其他模块调用。


这样设计从理论角度来看,代码耦合度比较高,出问题的概率会增大,然后实力打脸的是,Linux 非常成功,耦合度高有弊端也有优点,其中优点得到了充分的诠释,再加上内核模块可以动态的加载和卸载,这简直不要太完美哦。


640?wx_fmt=png

640?wx_fmt=png


640?wx_fmt=png

(可以点击放大)

这个图,如果你这样看,可能看不是很清楚,因为图片比较大,你需要点击放大了来看,就可以看到里面的端倪和差别了。


Monolithic kernel 

是我们所说的宏内核了,里面的设备文件,驱动,进程间通信,进程间通信也可以叫做IPC,这些都是柔和在一个一个模式里面,这个模式叫做内核态,宏内核可以动态加载内核模块,当然了,也可以动态卸载内核模块,你看上面图片那个深红色的地方就是内核态。


Micro kernel 

是我们说的微内核了,微内核就是中央集权统治的了,既然是中央集权统治,就应该精简,毕竟贵族的话,都想自己身份特殊点,所以里面就是黄色的往下了很多。


Hybrid kernel

是混合式内核,又是微内核,又是宏内核,windows的设计者就是这么厉害,你们俩都说自己最厉害,又都不退一步,可能你们没有发现,可能我才是最厉害的,所以有人说,Windows 是宏内核,嗯,这样说没什么问题,看看目录[c:\windows\system32\drivers]下面有很多驱动相关的,然后又有人跑出来说,Windows是微内核,嗯,这样说也没有什么不对。

640?wx_fmt=png

640?wx_fmt=png

Linux 环境安装


先说下我在第一章节说的,会在这里给大家一个资源链接,我其中包括ubuntu的下载链接,ubuntu如果运行不了的话,需要进入bios打开虚拟机选项,这个是比较干净的版本,里面的vim ,git什么的都还没有安装,大家打开后需要自己安装,我觉得如果是初学者的话,这样也是要经历的,了解系统的一个过程。


https://github.com/weiqifa0/linuxBook


作为一个Linux的狂热分子,我设置的Linux账号密码就是[ Linux ],大家可要记住啊。


Linux 内核,可以运行在 x86 体系结构上,也可以运行在 ARM 体系结构上。我刚开始学习 Linux 的时候,非常迫切的想要在 ARM 上运行代码,经过这么多年的摸爬滚打,我终于发现那是一个非常玩笑的想法,Linux 内核博大精深,你学的是内核,是思想,我们后面会从是嵌入式Linux开发,安卓开发,openwrt开发,MIFI开发等等,用到的都是Linux内核,会有差别,但是原理性的东西不会差别太大。


所以我想自己给大家写书的时候,还是用 ubuntu 来介绍 Linux 内核。至于在在ARM开发板上运行,给大家说一个东西,叫做交叉编译链,什么是交叉编译链,本来windows是X86体系结构的,正常编译出来的文件,应该只能在X86体系结构上执行,但是有了交叉编译器之后呢,我可以在windows上编译ARM体系结构的固件。


反过来说,你学好了Linux内核,理清了其中的脉络结构,不管你是做安卓开发,还是嵌入式开发,以及其他衍生的Linux应用或者驱动开发,都会得心应手。


我安装好的 ubuntu 开发环境,有需要的可以下载,下载链接查看前面部分提到的 github 链接,本书的所有例程都上传到 github 上做备份,到前面链接上下载 ubuntu 虚拟机压缩包,下载后用 VMware 打开,可能会打开会有提示出错,重启电脑,进入 Bios 把虚拟机功能选项设置为 Enable,再重新进入系统就可以看到一个可以让你学习的 ubuntu 系统了。

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

Linux 内核模块可以做什么?


Linux 内核下面的代码几乎都是用内核模块完成开发的,我们换一个角度来看,内核模块看起来就像是一个应用程序的一段代码,这个点我觉得应该很容易理解,但是它有跟应用程序又不一样,我们知道内核空间和用户空间地址段是不一样的,内核模块编写需要非常谨慎,因为稍微不注意就会可能引起系统的崩溃,我这样说大家应该能理解吧,应用空间运行的应用程序有问题,崩溃了没关系,那只是它自己挂掉了,但是内核模块不一样,如果内核模块有调用它的地方,比如音频的 ALSA 驱动,如果崩溃了,那么整个系统看语音部分就不能工作了。


设备驱动使用内核模块编程,就比如一个触摸屏驱动,在设备模型章节我们好好聊下这个事情,但是我们这里就提一下,一个设备需要依赖一个驱动,没有驱动设备是不可能正常工作了,所以内核模块在系统下的驱动都是用内核模块编写的,Linux 内核下面有非常多的设备,当然就需要非常多的驱动。


文件系统驱动,说到设备文件不得不提一句,Linux 下的所有都可以看做是文件,不同的文件系统还有有一个统一的虚拟文件系统来管理,这些都是需要内核模块来完成的。


系统调用,用户空间的程序需要调用内核服务,就必须要使用系统调用,正常的系统调用比如关机,读写文件,如果你需要新建一个系统调用也是可以的,那就需要你这个大神自己写个内核模块。


我大概就知道这么多,可能还有些没写出来的,也是内核模块编程范围的,比如网络编程等。

640?wx_fmt=png

Hello World 内核模块

学习 C 语言的时候,我们会写一个 Hello World 来证明一下自己,然后我们学习 C++ 的时候,我们也会写一个 Hello World 来证明一下自己,再然后我们会学习 java、python、还有其他很多很多的编程语言,但是最开始,你总是想写一个 Hello World 来证明一下自己。


我也很想知道为什么我们跟 Hello World 那么过不去,来个比喻,这就像我们习惯了喝热水一样,如果仔细去追究,好像喝热水并不能如何如何,为什么我们习惯喝热水,很大的原因是因为我们的妈妈想让我们喝热水,因为我们的妈妈觉得喝热水是一个好习惯,喝热水是可以强身健体的,喝热水包治百病。


再反回来看我们为什么会在刚开始学习一个东西的时候喜欢去写 Hello World ,因为你接触的很多人告诉你这样做,如果可以,你明明是不需要些 Hello World 的,而是写 Hello You 或者 Hello 等其他什么东西的。


说到这里,我还是要写一个 Hello World 来让自己入个门,进入那个深不可测的世界,聆听 Linux 教诲、体会Linux带给我们的快感。


Hello World 内核模块代码

#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);


就是这简单的几行代码,我们来简单的分析一下,前面两行是包含头文件,这个跟我们学习 C 语言没有什么区别,有了头文件才能调用函数,就是这么简单。


第三行,MODULE_LICENSE 这个是声明模块的许可证说明的,比喻一下,我们开车,就必须有一个驾驶证,如果我们没有驾驶证,就会被交警叔叔给抓住,模块代码也一样,如果没有许可证声明,就会被内核给抓住


模块的许可证声明 从 2.4.10 内核版本开始,模块必须通过 MODULE_LICENSE 宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告。从 linux/module.h 文件中可以看到,被内核接受的有意义的许可证有 


"GPL",

"GPL v2",

"GPL and additional rights",

"Dual BSD/GPL",

"Dual MPL/GPL",

"Proprietary"


在同时支持2.4与2.6内核的设备驱动中,模块可按如下方式声明自己的许可证。

MODULE_LICENSE(“GPL”);

然后下面就是声明的两个函数,函数里面调用了 printk 来打印内容,后面再有一个 module_init 和 module_exit ,望文生义,我们可以知道这两行代码是用来说明模块初始化和模块退出时调用的函数。

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png


printk 内核打印和调试工具


内核里面是没有 C 库(C库是我们写C语言的那个库函数)的,大家刚开始学习 C 语言的时候,知道一个函数叫做 printf ,这个也是一个打印函数,但是这个打印函数需要 C 库的帮忙,内核里面需要没有C库但是需要自己的打印函数,这个函数就叫做 printk。


不知道大家想过没有,为什么内核模块里面可以直接调用printk函数,内核模块是属于内核的,printk也是属于内核的,如果内核函数在内核导出了内核符号表,那么这个函数可以被内核里面的其他地方使用,我又要惊叹一下这样的设计了。


KERN_ALERT 这个是一个宏定义,用来控制内核打印的等级,等级很重要,通过控制等级可以动态的控制日志的输出,Linux 经久不衰的原因也是因为有这么多优良的基因,而其中的这个基因肯定是包括 printk


关于内核的打印部分,我觉得后面可以起一个章节单独说明,调试打印还是非常有必要的。


编写Makefile编译HelloWorld模块

有源码还是不够的,就好像我们做饭,准备了很多食材,我们还需要一个锅来料理这些食材,源代码也是一样,它们也是码农的食材,但是也是需要料理的,所以就出现了很多烹饪手法,我们码农也接触到了很多编译器,而在 Linux 下最常用的 就是 GCC 编译器,使用GCC 就离不开 makefile。

我们给上面的 Hello World 模块编写一个 makefile 吧

ifneq ($(KERNELRELEASE),)

obj-m := hello.o

else

PWD  := $(shell pwd)

KVER := $(shell uname -r)

KDIR := /lib/modules/$(KVER)/build

all:

$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:

rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions

endif


很多人写好了Makefile 但是编译却不成功,因为里面还有一些需要注意的地方,特别是初学者,可能要被这几行代码调戏很长时间,其中需要注意的是要用4空格的Tab键,要不然很大概率会出现编译出错。


我们编译内核需要编译工具Makefile,还需要一个很重要的东西,就是内核库文件,我现在是自己安装了一个ubuntu系统,这个系统在安装的时候已经有了一个内核编译好的库文件,位置就是 KDIR 指定的位置,里面的东西大概如下。


HelloWorld 模块编译的时候,需要从这个库文件里面去找到它需要依赖的函数,头文件等等。

linux@ubuntu:/lib/modules/3.13.0-32-generic/build$ ls

arch    Documentation  fs       ipc      kernel    mm              samples   sound   usr

block   drivers        include  Kbuild   lib       Module.symvers  scripts   tools   virt

crypto  firmware       init     Kconfig  Makefile  net             security  ubuntu

linux@ubuntu:/lib/modules/3.13.0-32-generic/build$


假设像这样的错误

linux@ubuntu:~/linux$ make

Makefile:11: *** missing separator.  Stop.


我们编译出来的内核模块,是包含一定的信息的,我们可以使用命令来查看这些信息,当然了,你也可以用这个方法去查看别人编写的内核模块的信息,很多初学者写代码都是哐哐哐,编译通过,开心一下,运行成功了,开心一下,完全不去考虑其中的细节,查看内核模块信息就是其中的一个细节,使用命令和运行结果如下。


linux@ubuntu:~/linuxBook/HelloWorld$ modinfo hello.ko

filename:       hello.ko

license:        Dual BSD/GPL

srcversion:     31FE72DA6A560C890FF9B3F

depends:        

vermagic:       3.13.0-117-generic SMP mod_unload modversions

linux@ubuntu:~/linuxBook/HelloWorld$


内核模块的加载和卸载


内核模块编译好了之后,在当前的路径下会生成一些产物,如下图

linux@ubuntu:~/linux$ ls

hello.c  hello.ko  hello.mod.c  hello.mod.o  hello.o  Makefile  modules.order  Module.symvers

linux@ubuntu:~/linux$


这时候,我们就需要装载这个内核模块到内核里,Linux 内核里面有一个链表来维护在运行中的内核模块,我们装载内核模块,就是往这个链表插入我们的内核模块,像下面的图片一样,如果你想得极端一点,就有点像插队。

640?wx_fmt=png

装载 HelloWorld 内核模块

linux@ubuntu:~/linux$ sudo insmod hello.ko

[sudo] password for linux:

linux@ubuntu:~/linux$

输入 sudo insmod hello.ko 后,需要输入 su 超级用户的密码,这个时候,我们理论上就会执行module_init ,会有一个打印出来,我们去哪里看这个打印了这样输入命令 dmesg |tail -5 查看后面 5 行日志。

linux@ubuntu:~/linux$ dmesg |tail -5

[ 1154.971812] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready

[41991.859527] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None

[41991.869932] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready

[41991.870367] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready

[42035.102864] Hello, world

linux@ubuntu:~/linux$

同样我们去卸载内核模块的时候,执行指令sudo rmmod hello.ko

linux@ubuntu:~/linux$ dmesg |tail -5

[41991.859527] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None

[41991.869932] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready

[41991.870367] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready

[42035.102864] Hello, world

[42284.945557] Goodbye, cruel world

linux@ubuntu:~/linux$

insmod 和rmmod 的流程图如下面的图片,我这个图片引用的是其他书籍里面的,有可能跟最新的内核有出入,但是大致不会相差太多,可以作为参考。

640?wx_fmt=png

(内核模块加载的流程图)


Module_init


Linux 内核里面非常多的代码都是通过模块的方式加载的,但是每个模块的加载顺序都是不一样的,比如设备驱动,因为设备驱动是在系统启动完成后再进行加载的,属于偏后面一些,但是肯定有一些核心组件需要提前加载的(比如时钟,I2C core等),因为需要不同的加载顺序,就有了不同级别的xx_init级别。


\include\linux\init.h里面定义了不同的级别函数,数字越低,代表的级别越高,越早加载。


/*

* A "pure" initcall has no dependencies on anything else, and purely

* initializes variables that couldn't be statically initialized.

*

* This only exists for built-in code, not for modules.

* Keep main.c:initcall_level_names[] in sync.

*/

#define pure_initcall(fn) __define_initcall(fn, 0)

#define core_initcall(fn) __define_initcall(fn, 1)

#define core_initcall_sync(fn) __define_initcall(fn, 1s)

#define postcore_initcall(fn) __define_initcall(fn, 2)

#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)

#define arch_initcall(fn) __define_initcall(fn, 3)

#define arch_initcall_sync(fn) __define_initcall(fn, 3s)

#define subsys_initcall(fn) __define_initcall(fn, 4)

#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)

#define fs_initcall(fn) __define_initcall(fn, 5)

#define fs_initcall_sync(fn) __define_initcall(fn, 5s)

#define rootfs_initcall(fn) __define_initcall(fn, rootfs)

#define device_initcall(fn) __define_initcall(fn, 6)

#define device_initcall_sync(fn) __define_initcall(fn, 6s)

#define late_initcall(fn) __define_initcall(fn, 7)

#define late_initcall_sync(fn) __define_initcall(fn, 7s)

#define __initcall(fn) device_initcall(fn)

#define __exitcall(fn) \

static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn) \

static initcall_t __initcall_##fn \

__used __section(.con_initcall.init) = fn

......

/**

* module_init() - driver initialization entry point

* @x: function to be run at kernel boot time or module insertion

*

* module_init() will either be called during do_initcalls() (if

* builtin) or at module insertion time (if a module).  There can only

* be one per module.

*/

#define module_init(x) __initcall(x);

/**

* module_exit() - driver exit entry point

* @x: function to be run when driver is removed

*

* module_exit() will wrap the driver clean-up code

* with cleanup_module() when used with rmmod when

* the driver is a module.  If the driver is statically

* compiled into the kernel, module_exit() has no effect.

* There can only be one per module.

*/

#define module_exit(x) __exitcall(x);


可以看到 module_init 实际上是__define_initcall(fn, 6) 属于设备级别的初始化,我们之前一直在说Linux 内核支持动态模块加载,Linux内核的很多子系统都是通过模块化来设计的,既然有动态加载,那就必须有静态加载,如果需要内核静态加载的话,就必须把这些内核模块编译进内核里面,我们学习内核编译的时候,肯定还记得一个 [*]和[M],只要简单的配置一下,就决定了你这个模块是作为一个单独的模块还是作为内核的一个部分。


这个加载顺序我认为是非常有用的,我之前做项目的时候,我们同事有个需求,需要A驱动必须要在B驱动之前加载,而且尽可能早的加载,这时候,就用到了不同的优先级别的xx_init了。



Linux 内核模块的依赖安装(动态加载模块)


我好像在之前说过,Linux 内核是宏内核,内核模块之间是可以互相进行函数调用的,这样的耦合性是比较高的,所以呢,Linux 内核肯定是存在依赖关系的,就好像,我像喝水,然后我首先要找到一个杯子,然后还要找到饮水机,等等等等。


按照上面的命令,我们安装一个内核模块使用 insmod 命令,如果这个模块依赖另一个模块呢?

那就再需要用 insmod 安装另一个模块,这样看起来比较麻烦,所以就有了这个命令 modprobe,modprobe可以动态的安装内核模块,如果发现有依赖关系,就把需要依赖的内核模块也安装了。


提醒一下,初学者做这个的时候,肯定会遇到一些意想不到的问题,所以我也针对这些问题做了些总结,希望看完文章的同学不会踩坑。


Makefile 文件

$(info $(PWD))

$(info $(VER))

ifneq ($(KERNELRELEASE),)

obj-m := hello.o

else

PWD  := $(shell pwd)

KVER := $(shell uname -r)

KDIR := /lib/modules/$(KVER)/build

all:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

install:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install

clean:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

endif

源文件跟之前的HelloWorld一样,就不贴出来了,这里主要是多了一个 make install 编译,用来安装内核模块。


运行modprobe 之前,要先运行depmod -a来更新依赖文件

linux@ubuntu:/lib/modules$ uname -r

3.13.0-117-generic

linux@ubuntu:/lib/modules$ cd /lib/modules/3.13.0-117-generic/


linux@ubuntu:/lib/modules/3.13.0-117-generic$ sudo depmod -a


linux@ubuntu:/lib/modules/3.13.0-117-generic$ cat modules.dep |tail -2

kernel/lib/percpu_test.ko:

extra/hello.ko:

linux@ubuntu:/lib/modules/3.13.0-117-generic$


然后再执行 modprobe -v hello ,后面跟的是 hello 而不是 hello.ko ,如果你输入 hello.ko 的话,会一直提示找不到该模块。

linux@ubuntu:/lib/modules/3.13.0-117-generic$ modprobe -v hello

linux@ubuntu:/lib/modules/3.13.0-117-generic$ dmesg|tail -2

[244664.594921] Goodbye, cruel world

[245400.931435] Hello, world

linux@ubuntu:/lib/modules/3.13.0-117-generic$ sudo modprobe -r hello

linux@ubuntu:/lib/modules/3.13.0-117-generic$ dmesg|tail -2

[245400.931435] Hello, world

[246484.772567] Goodbye, cruel world

linux@ubuntu:/lib/modules/3.13.0-117-generic$


内核空间和用户空间


上面这两个名字有点拗口,也许可以这样说,内核地址和用户地址,内核态和用户态实际上就是通过地址来区分的。


640?wx_fmt=png

我觉得作为初学者,把这个图片看明白,然后再把旁边的英文自己看明白,然后把他们吃透,多看几次,多理解几次,多问几次,这样之后再去看其他的内容。


如果把Linux 看作是一个一个家庭,一个家庭首先就需要有一个房子,这就是硬件设备,然后呢父母就是这个家庭的内核,孩子们就是这个家庭用户空间进程,父母需要为这个家庭支付房租,水电,买吃的,喝的,还有给孩子们煮饭,洗衣服等等。那孩子们做什么事情呢?孩子们就负责上学,负责玩,他们要吃了,就找父母去拿,想睡觉了,就去睡觉,总之,他们不是这个家庭的基础,如果内核空间没有了,这个家庭肯定就瓦解了,但是用户没有了,内核还是会存在,这就是我们所说的丁克家庭。


我举个例子,家庭里的小孩有一天想吃饭,好了,他把碗拿到桌子边上,敲着碗说,爸爸妈妈,爸爸妈妈,快给我吃饭,然后呢,CPU就需要跳转到内核态,让父母去给小孩准备食物,准备好了,小孩就可以吃饭了,这时候CPU又回到了用户态执行。


有一天,我很开心的在上网,然后又一张图片印入了我的眼帘,你应该猜到了,就是下面这张图片,这张图片非常完美的解释了用户空间和内核空间,作者给我们举得例子就是系统调用,系统调用了write的函数方法,在调用的过程中,CPU跳转到内核空间执行,函数执行结束后,又会跳转到用户空间执行。


640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png640?wx_fmt=png

构建自己的 Linux 内核


我们想学习 Linux 内核,一个最重要的条件是会构建自己的 Linux 内核,要不然就是只是一个空谈家,我的书籍也是希望给大家一个这样动手的条件,理论加实践相结合来渗透进入 Linux 内核,我在前面已经给大家提供了一个安装好的 ubuntu 系统,我们就用这个 ubuntu 开刀,扒出我们自己的 Linux 内核。


获取内核源码

sudo apt-cache search linux-source

sudo apt-get install linux-source-3.2.0

第一句是查询 Linux 内核源码,就是说我们现在运行的这个 ubuntu的系统内核源码是哪个版本的,我们如果要开发自己的内核,必须要有完整的内核源码,我说的 ubuntu 指的是我上面给出链接的那个。

第二句是安装 Linux 内核源码,安装完后在 /usr/src 下面有有源码压缩包。

ubuntu 下的 Linux 内核源码位置

/usr/src

我们安装了 ubuntu 下面就有了 linux 内核源码,但是这个源码需要我们自己解压出来,对应的应该是

linux@ubuntu:/usr/src$ ls

linux-headers-3.13.0-117          linux-headers-3.13.0-32-generic

linux-headers-3.13.0-117-generic  linux-source-3.2.0

linux-headers-3.13.0-32           linux-source-3.2.0.tar.bz2

linux@ubuntu:/usr/src$


所以就是这个压缩包了

linux-source-3.2.0.tar.bz2


使用 make menuconfig 配置自己的内核

我们有了自己的源码,还需要一个 .config 来配置内核,世界上的硬件设备千千万万,我们就只有一个内核源码,他们最大的区别就是配置文件了,所以我们就需要一个配置文件,这一步也很简单。

使用 uname -a 查看内核版本号

root@ubuntu:/usr/src# uname -a

Linux ubuntu 3.13.0-117-generic #164~precise1-Ubuntu SMP Mon Apr 10 16:16:25 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

root@ubuntu:/usr/src#


把内核版本号下面的 .config 复制到内核源码下面

root@ubuntu:/usr/src# cp linux-headers-3.13.0-117-generic/.config linux-source-3.2.0/linux-source-3.2.0/


使用 make menuconfig 配置内核

这一步不要做任何修改,直接 exit 就可以了,可以执行看看配置的界面爽一把是可以的。


编译内核

执行 make bzImage -jN 编译内核,我截取了编译过程中的一些输出日志,这里的 N 可以指定并行编译的线程,但不限定是core的个数。

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$sudo make bzImage -j6

[sudo] password for linux:

 CHK     include/linux/version.h

make[1]: Nothing to be done for `relocs'.

 CHK     include/generated/utsrelease.h

 CALL    scripts/checksyscalls.sh

 CHK     include/generated/compile.h

 VDSOSYM arch/x86/vdso/vdso-syms.lds

 VDSOSYM arch/x86/vdso/vdso32-int80-syms.lds

 VDSOSYM arch/x86/vdso/vdso32-syscall-syms.lds

 VDSOSYM arch/x86/vdso/vdso32-sysenter-syms.lds

 VDSOSYM arch/x86/vdso/vdso32-syms.lds

 LD      arch/x86/vdso/built-in.o

 LD      arch/x86/built-in.o

 LD      vmlinux.o

 MODPOST vmlinux.o

 GEN     .version

 CHK     include/generated/compile.h

 UPD     include/generated/compile.h

 CC      init/version.o

 LD      init/built-in.o

 LD      .tmp_vmlinux1

 KSYM    .tmp_kallsyms1.S

 AS      .tmp_kallsyms1.o

 LD      .tmp_vmlinux2

 KSYM    .tmp_kallsyms2.S

 AS      .tmp_kallsyms2.o

 LD      vmlinux

 SYSMAP  System.map

 SYSMAP  .tmp_System.map

 VOFFSET arch/x86/boot/voffset.h

 CC      arch/x86/boot/version.o

 OBJCOPY arch/x86/boot/compressed/vmlinux.bin

 GZIP    arch/x86/boot/compressed/vmlinux.bin.gz

 MKPIGGY arch/x86/boot/compressed/piggy.S

 AS      arch/x86/boot/compressed/piggy.o

 LD      arch/x86/boot/compressed/vmlinux

 ZOFFSET arch/x86/boot/zoffset.h

 OBJCOPY arch/x86/boot/vmlinux.bin

 AS      arch/x86/boot/header.o

 LD      arch/x86/boot/setup.elf

 OBJCOPY arch/x86/boot/setup.bin

 BUILD   arch/x86/boot/bzImage

Setup is 17052 bytes (padded to 17408 bytes).

System is 4422 kB

CRC e36fccb0

Kernel: arch/x86/boot/bzImage is ready  (#2)

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$


编译 Linux 内核模块

我们执行 make modules - j4 编译内核模块,我们前面说了,Linux 内核下面都是按照模块化来编程的,我们需要把这些模块编译出来,然后再安装到现在运行的 ubuntu 系统上,就完成了Linux 内核的安装。


执行的时候有可能出现一些什么问题,找到 error 相关的 Log ,百度找一下答案就可以了,如下是我执行的结果

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$ sudo make modules -j6

[sudo] password for linux:

 CHK     include/linux/version.h

make[1]: Nothing to be done for `relocs'.

 CHK     include/generated/utsrelease.h

 CALL    scripts/checksyscalls.sh

 Building modules, stage 2.

 MODPOST 2801 modules

WARNING: modpost: Found 4 section mismatch(es).

To see full details build your kernel with:

'make CONFIG_DEBUG_SECTION_MISMATCH=y'

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$


安装内核

先安装内核模块,使用这个骚命令make modules_install

$sudo make modules_install

INSTALL /lib/firmware/keyspan_pda/keyspan_pda.fw

 INSTALL /lib/firmware/keyspan_pda/xircom_pgs.fw

 INSTALL /lib/firmware/cpia2/stv0672_vp4.bin

 INSTALL /lib/firmware/yam/1200.bin

 INSTALL /lib/firmware/yam/9600.bin

 DEPMOD  3.2.79

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$


然后安装内核,使用这个命令make install

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$ sudo make install

sh /usr/src/linux-source-3.2.0/linux-source-3.2.0/arch/x86/boot/install.sh 3.2.79 arch/x86/boot/bzImage \

System.map "/boot"

run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 3.2.79 /boot/vmlinuz-3.2.79

run-parts: executing /etc/kernel/postinst.d/initramfs-tools 3.2.79 /boot/vmlinuz-3.2.79

update-initramfs: Generating /boot/initrd.img-3.2.79

run-parts: executing /etc/kernel/postinst.d/pm-utils 3.2.79 /boot/vmlinuz-3.2.79

run-parts: executing /etc/kernel/postinst.d/update-notifier 3.2.79 /boot/vmlinuz-3.2.79

run-parts: executing /etc/kernel/postinst.d/zz-update-grub 3.2.79 /boot/vmlinuz-3.2.79

Generating grub.cfg ...

Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported.

Found linux image: /boot/vmlinuz-3.13.0-117-generic

Found initrd image: /boot/initrd.img-3.13.0-117-generic

Found linux image: /boot/vmlinuz-3.13.0-32-generic

Found initrd image: /boot/initrd.img-3.13.0-32-generic

Found linux image: /boot/vmlinuz-3.2.79

Found initrd image: /boot/initrd.img-3.2.79

Found memtest86+ image: /boot/memtest86+.bin

done

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$

查看新安装的内核

内核安装完成之后,我们怎么判断新安装的内核是自己编译出来的呢?这个操作非常关键,很多码农写了代码烧录后,发现执行的情况不如自己的预期,也没有去查看有没有烧录成功,就不断的修改代码排查,这个是非常低级的错误。

生成initrd.img文件,使用命令update-initramfs。

#cd /lib/modules/3.2.79

root@ubuntu:/lib/modules# update-initramfs -c -k 3.2.79

update-initramfs: Generating /boot/initrd.img-3.2.79

root@ubuntu:/lib/modules#


使用 update-grub2 更新新的内核,这个命令会去查找当前存在的新内核,然后用新的内核去安装引导。

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$ sudo update-grub2

Generating grub.cfg ...

Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported.

Found linux image: /boot/vmlinuz-3.13.0-117-generic

Found initrd image: /boot/initrd.img-3.13.0-117-generic

Found linux image: /boot/vmlinuz-3.13.0-32-generic

Found initrd image: /boot/initrd.img-3.13.0-32-generic

Found linux image: /boot/vmlinuz-3.2.79

Found initrd image: /boot/initrd.img-3.2.79

Found memtest86+ image: /boot/memtest86+.bin

done

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$


查看重启前运行的内核版本

linux@ubuntu:/usr/src/linux-source-3.2.0/linux-source-3.2.0$ uname -a

Linux ubuntu 3.13.0-117-generic #164~precise1-Ubuntu SMP Mon Apr 10 16:16:25 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

重启系统 shutdown -r now

这时候重启,你应该是看不到我们的内核版本的,因为我们用的是 ubuntu ,还有一些诡异的地方需要设置,其中就需要设置的是 /etc/grub.d/00_header

vim /etc/grub.d/00_header 修改style="countdown"为style="menu",这样我们在开机的时候就可以选择我们系统里面的内核了,要不然就一直是一个默认的内核。


通过上面的骚操作之后,我们再重启,重启的过程中会提示我们选择内核版本,我们记得别选原来的内核版本了,选一个我们原来不认识的内核版本,然后自信的按下回车键,等待一下。


这时候,我们再来查看一下内核版本

root@ubuntu:~# uname -a

Linux ubuntu 3.2.79 #4 SMP Wed Jun 12 19:33:11 PDT 2019 x86_64 x86_64 x86_64 GNU/Linux

root@ubuntu:~#

640?wx_fmt=png

640?wx_fmt=png


总结


内核模块是基础,是我们开发内核的基础,但是又因为基础的东西难度不是非常大,所以写的也比较轻松,不过学习的时候,不应该怠慢,该敲代码的时候,就敲代码,该看书的时候,就看看书,有理解不了的,就提下问题。扎实的基础就应该在学习的时候边动手边思考,加深自己的印象。


原创文章写起来非常不容易,辛苦是在所难免的,因为从后面开始,没一个章节的篇幅就增加很多,所以我会拆开来写,比如一个章节我发几次。


我的文章后面默认打开赞赏,原因是我的原创名字和赞赏账号一样,也是因为懒的原因,这样我只需要点击一下就可以填好原创的作者,打开赞赏不是想大家给我多少钱,也是想得到读者们的肯定,如果是学生还不如留着钱去追下妹子,大学美好时光来之不易,该浪荡的时候就不要浪费了,但是觉得不错,有帮助的,可以给个转发支持一下,感激不尽。


下一章节会讲内核调试的一些方法,包括GDB,/proc文件系统调试,还有加dump_stack等手段来调试等等


640?wx_fmt=png

640?wx_fmt=png

推荐阅读

第1章  Linux内核概述

============================

640?wx_fmt=jpeg


推荐一下我自己的知识星球


1、首先非常感谢大家付费进入我的知识星球,这是对我的肯定,我也将竭尽全力帮助大家一起成长,如果有任何问题,请点击[提问]咨询我。如果有问题我解答不了的,请放心,我也会竭尽所能帮助你,周立功是我的微信好友,我也有认识一些天使投资人,创业者,在各大企业从事工作的很多专业性的朋友。 


2、其次,我想说,人在成长过程中,肯定是需要一个指引者的,需要一个让你相信他的人,很多时候,我们也不知道前方对错,我们很想得到一个背后的力量,我觉得这个非常重要,互相鼓励,共同成长。 


3、再次,我觉得为了鼓励大家交流,如果主动回答问题,被采纳的话,我会给予 5 元赞赏奖励,赞赏的钱会到达你的知识星球钱包里面。邀请朋友进来的,我会给予 20 元奖励,鼓励形成一个互助的社群。


640?wx_fmt=png


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468523.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

的注册表怎么才能删干净_油烟净化器怎么清洗才能清理干净呢?

油烟机的净化器的主要功能是过滤厨房里的油烟&#xff0c;因为它总是处理油烟&#xff0c;所以清洗净化器很麻烦&#xff0c;那么如何清洗呢&#xff1f;经常清洗油烟净化器是很有必要的&#xff0c;但清洗起来很麻烦&#xff0c;清洗起来也不容易。今天&#xff0c;我想告诉大…

ado 字符串变量

这次变量主要针对 Mfc 的 Cstring 类型的变量&#xff08;前面VC 链接Access 数据库 插入变量到表&#xff09; 思路; 1 把cstring 类型 转为 string 2 string 转 char 数组 3 sprintf 写入数组 string 转 char 数组函数[cpp]view plaincopyprint?char* zhuanhuan(std::strin…

周立功先生和他的AWorks团队招聘

我之前写的一篇文章&#xff0c;介绍了周立功先生&#xff0c;我记得那篇文章的阅读量非常多&#xff0c;也让我迎来一段小高潮&#xff0c;随着时间的推移&#xff0c;慢慢的增加了我对周立功先生的了解&#xff0c;我们很多人&#xff0c;像我吧&#xff0c;工作的时候&#…

mongodb python 大于_Python中使用MongoDB详解

作者&#xff1a;Zarten知乎专栏&#xff1a;Python爬虫深入详解知乎ID&#xff1a; Zarten简介&#xff1a; 互联网一线工作者&#xff0c;尊重原创并欢迎评论留言指出不足之处&#xff0c;也希望多些关注和点赞是给作者最好的鼓励 &#xff01;介绍MongoDB是一种面向文档型的…

这不是商业互吹,是学习的宝藏

学习如逆水行舟&#xff0c;不进则退&#xff1b;只有坚持不断的学习,才能保持进步。今天给大家精心挑选的这几个优质的公众号&#xff0c;在行业深耕已久&#xff0c;相信大家一定会有所收获&#xff0c;感兴趣的可以关注一下。互联网架构师 号主985计算机硕士毕业&#xff…

关于这些那些

关于篮球先说下&#xff0c;我刚才已经写完文章了&#xff0c;但是因为没有保存&#xff0c;浏览器想着周末早点回去休息就闪退了&#xff0c;把写好的文章给闪退没有了&#xff0c;这个真是拿起自己的坑砸死了自己&#xff0c;那种赶脚只有自己能够明白&#xff0c;真的是太难…

mysqldump 定时备份数据(全量)

MYSQL 数据库备份有很多种(cp、tar、lvm2、mysqldump、xtarbackup)等等&#xff0c;具体使用哪一个还要看你的数据规模。下面给出一个表 #摘自《学会用各种姿态备份Mysql数据库》 备份方法备份速度恢复速度便捷性功能一般用于cp快快一般、灵活性低很弱少量数据备份mysqldump慢慢…

第3章 Linux内核调试手段之内核打印

开始前面说的话在我写代码的生涯里&#xff0c;我看到过很多大神炫耀自己的调试手段&#xff0c;也看到很多大神写过非常厉害的代码&#xff0c;我认为&#xff0c;相比于写代码&#xff0c;调试更加重要&#xff0c;而那些能在写代码的时候就加入了自己的调试信息的&#xff0…

电源管理 解析_智能电源控制箱

智能电源控制箱?智能电源控制箱又被称之为&#xff1a;智能监控箱、智能设备箱、智能运维箱&#xff0c;智能电源控制箱的作用主要就是为视频监控打造良好的运行环境&#xff0c;保障视频监控系统稳定的运行。说到视频监控&#xff0c;大家都知道视频监控的故障率是比较高的&a…

centos7 开机后进去了命令行_Linux系统管理:开机启动流程(二)

CentOS71.BIOS(开机自检)2.MBR ( Master Boot Record 主引导记录)3.GRUB2 Bootloader&#xff08;引导菜单&#xff09;4.Kernel&#xff08;内核引导&#xff09;5.Systemd &#xff08;不再使用init&#xff0c;改成了systemd&#xff09;6.Runlevel-Target &#xff08;运行…

一点小思考

我记得12年的时候&#xff0c;我就申请了微信公众号&#xff0c;那时候我的号主是TCL&#xff0c;是公司的同事用我的微信号申请公司的主体号&#xff0c;那时候我也有一点想法自己做个公众号写点文章&#xff0c;但是一直没有下决心&#xff0c;后来离职了&#xff0c;原来用我…

第3章 Linux内核调试手段之二

gdb 和 addr2line 调试内核模块内核模块插入内核链表的时候&#xff0c;会调用 init 里面的程序&#xff0c;我们上面给的那个例程的程序因为是经过多年风吹雨打的&#xff0c;但是如果你是一个萌新的码农&#xff0c;你能保证自己写的内核模块没有问题吗&#xff1f;所以就需要…

儿童手表怎么删除联系人_华为儿童手表4X体验:与你一起守护孩子的成长,带娃不再辛苦...

带娃是一件很辛苦的事情&#xff0c;从身体到精神的辛苦&#xff0c;相信各位家长都懂。对于照看正在成长期的孩子&#xff0c;更是让很多家长亲身感受"成长的烦恼"。孩子活泼好动的天性让很多家长都不放心&#xff0c;同时大部分家长又没有能力随时在身边守护&#…

Jmeter分布式测试过程中遇到的问题及摘抄前辈问题汇总

遇到的常见问题&#xff1a; 1、在Controller端上控制某台机器Run&#xff0c;提示"Bad call to remote host"。 解决方法&#xff1a;检查被控制机器上的jmeter-server有没有启动&#xff0c;或者JMeter.properties中remote_hosts的配置错误。2、Agent机器启动Jmete…

介绍一个我创业的朋友

大家好&#xff0c;今天给大家介绍一位跟我一样正在创业路上的朋友&#xff0c;不知道大家对我之前的文章还有没有印象&#xff0c;最近我在做一件特别有意思的事情&#xff0c;这件有意思的事情一直催促着我起床上班&#xff0c;每天都充满能量和动力&#xff0c;又累又充实的…

微信小程序装修解决方案ppt_装修公司微信小程序都有哪些功能?

传统装修行业存在收费不透明、消费者装修服务过程体验差等问题&#xff0c;传统装修已无法满足消费者的实际需求&#xff0c;面临无客量、无签约的困境。然而&#xff0c;随着移动互联网的发展&#xff0c;许多装饰企业利用微信小程序来帮助其疏导和推广。一个装修公司小程序能…

要用什么态度去面对生活?

最近&#xff0c;张扣扣的新闻铺天盖地&#xff0c;因为我非常喜欢逛知乎&#xff0c;刚好张扣扣的新闻这几天上了知乎热搜&#xff0c;所以我就关注上了&#xff0c;说实话&#xff0c;有点痛心&#xff0c;外人看起来很爽&#xff0c;忍辱负重好多年&#xff0c;终于把自己的…

第3章 Linux内核调试手段之三

之前的内核调试&#xff0c;我觉得应该再加上下面的东西&#xff0c;只有好好把下面的几个问题研究透了&#xff0c;你可能才是一个真正的内核高手&#xff0c;或者说&#xff0c;你还不是一个高手&#xff0c;就是一个内核的普通工程师&#xff0c;这个是我和Z总聊天说的&…

中希尔排序例题代码_【数据结构与算法】这或许是东半球分析十大排序算法最好的一篇文章...

码农有道 历史文章目录(请戳我)关于码农有道(请戳我)前言本文全长 14237 字&#xff0c;配有 70 张图片和动画&#xff0c;和你一起一步步看懂排序算法的运行过程。预计阅读时间 47 分钟&#xff0c;强烈建议先收藏然后通过电脑端进行阅读。No.1 冒泡排序冒泡排序无疑是最为出名…

星期四随笔

周二的晚上&#xff0c;发了一篇文章&#xff0c;题目是《要用什么态度去面对生活》&#xff0c;发文是凌晨1点&#xff0c;早上7点起来&#xff0c;用手机刷了一下&#xff0c;很多回复&#xff0c;其中几个回复有点偏激&#xff0c;感觉被刺痛了&#xff0c;毕竟是睡着的&…