Linux内核学习(八):linux内核配置与模块

简介: Linux内核学习(八):linux内核配置与模块

Linux内核学习(八):linux内核配置与模块

内核的配置是开发的第一步,前面我kconfig那篇讲了内核这些配置怎么生成的。当时也提到了内核配置的一个图形化的界面,这里再来啰嗦几句。

本文内容全部来自《奔跑吧 linux内核》

1、内核配置工具

(1)make config

这是基于文本的一种传统的配置方式。它会为内核支持的每一个特性向用户提问,如果用户输入“y”,则把该特性编译进内核;如果输入“m”,则把该特性变成以模块;如果输入为“n”,则表示不编译该特性。

(2)make oldconfig

make oldconfig和make config很类似,也是基于文本的配置工具,只不过它是在现有的内核配置文件的基础上建立一个新的配置文件,在有新的配置选项时会向用户提问。

(3)make menuconfig

make menuconfig是一种基于文本模式的图形用户界面,用户可以通过移动光标来浏览内核支持的特性。

在那篇文章里写到了上述几种内核内置工具最终会在 Linux 内核源代码的根目录下生成一个隐藏文件,即.config文件,这个文件包含了内核的所有配置信息。下面是一个.config文件的例子。

.config 文件的每个配置选项都以“CONFIG_”字段开始,后面的 y 表示内核会把这个特性静态编译进内核,m表示这个特性会被编译成内核模块。

如果不需要编译到内核中,就要在前面使用“#”注释,并在后面用“is not set”来标识。

.config文件通常有几千行,每一行都通过手工输入显得不现实。

config太多,实际项目中如何生成这个.config文件呢?

(1)基于开发板

使用板级的配置文件一些芯片公司通常会提供基于某款 SoC 芯片的开发板,读者可以基于此开发板来快速开发产品原型。

芯片公司同时会提供板级开发板包,其中包含移植好的Linux内核。以ARM公司的Vexpress板子为例,该板子对应Linux内核的配置文件被放在arch/arm/configs目录中。如图3.3所示,arch/arm/configs目录下包含了众多的ARM板子的配置文件。

ARM Vexpress板子对应的config配置文件是vexpress_defconfig文件,可以通过下面命令来配置内核。

$ export ARCH=arm
$ export CROSS_COMPILE=arm-linux-gnueabi
$ make vexpress_defconfig

(2)使用系统的配置文件

当我们需要编译电脑中的Linux系统内核时,可以使用系统自带的config文件。

以优麒麟18.04系统为例,boot目录下面有一个config-4.15.0-22-generic文件。当我们想编译一个新内核(如 Linux 4.17 内核)时,可以通过如下命令来生成一个新的.config文件。

$ cd linux-4.17
$ cp /boot/config-4.15.0-22-generic ./.config

下面紧接着我们来看看内核模块

2、内核模块

Linux内核采用宏内核架构,即操作系统的大部分功能都在内核中实现,比如进程管理、内存管理、进程调度、设备管理等,并且都在特权模式下(内核空间)运行。

而与之相反的另一种流行的架构是微内核架构,它把操作系统最基本的功能放入内核中,而其他大部分的功能(如设备驱动等)都放到非特权模式下,这种架构有天生优越的动态扩展性。

Linux的这种宏内核可以理解为一个完全静态的内核,那如何实现运行时内核的动态扩展呢?

其实Linux内核在发展过程中很早就引入了内核模块这个机制,内核模块全称Loadable Kernel Module(LKM)。

在内核运行时加载一组目标代码来实现某个特定的功能,这样在实际使用Linux的过程中可以不需要重新编译内核代码来实现动态扩展。

Linux内核通过内核模块来实现动态添加和删除某个功能。

2.1、从一个内核模块开始

上面我们知道了模块的相关知识,现在我们来整一个模块注册进内核看看,体验一下这个流程。

a. 准备内核模块代码

0   #include <linux/init.h>
1   #include <linux/module.h>
2
3   static int __init my_test_init(void)
4   {
5     printk("my first kernel module init\n");
6     return 0;
7   }
8
9   static void __exit my_test_exit(void)
10  {
11    printk("goodbye\n");
12  }
13
14  module_init(my_test_init);
15  module_exit(my_test_exit);
16
17  MODULE_LICENSE("GPL");
18  MODULE_AUTHOR("Ben Shushu");
19  MODULE_DESCRIPTION("my test kernel module");
20  MODULE_ALIAS("mytest");

这个简单的内核模块只有两个函数:

  • 一个是 my_test_init()函数,输出一句话“my first kernel module init”;
  • 另一个是my_test_exit()函数,输出“goodbye”。

麻雀虽小,五脏俱全,这是一个可以运行的内核模块。

第0行和第1行包含了两个Linux内核的头文件,其中<linux/init.h>头文件对应的是内核源代码的 include/linux/init.h 文件

在这个头文件中包含了第 14 行和第 15 行中的module_init()和 module_exit()函数的声明。

<linux/module.h>头文件对应的是内核源代码的include/linux/module.h文件,包含了第17~20行的MODULE_AUTHOR()这些宏的声明。

**第 14 行的 module_init()告诉内核这是该模块的入口。**内核在各个模块初始化时有一个优先级顺序。对于驱动模块来说,它的优先级不是特别高,而且内核把所有模块的初始化函数都存放在一个特别的段中来管理。

第15行的module_exit()宏告诉内核这个模块的退出函数是my_test_exit()。

第3~7行是该内核模块初始化函数,我们在这个例子中仅仅用printk输出函数往终端中输出一句话。printk是类似C语言库中的printf()输出函数,但是它增加了输出级别的支持。这个函数在内核模块被加载时运行.

(可以使用insmod命令来加载一个内核模块。)

第9~12行是该内核模块的退出函数,在这个例子中我们也仅仅用printk输出一句话,标记卸载了该模块

(可以使用rmmod命令卸载一个内核模块)

(insmod和rmmod两个的意思就是咱们可以不只是说说prink,可是直接干。)

第 17~20 行,MODULE_LICENSE()表示这个模块代码接受的软件许可协议。Linux 内核是一个使用GPL V2的开源项目,这要求所有使用和修改了Linux内核源代码的个人或者公司都有义务把修改后的源代码公开,也就是一个强制的开源协议,因此一般我们编写的驱动代码中都需要显式地申明和遵循这个协议。

MODULE_AUTHOR()用来描述该模块的作者信息,可以包括作者的姓名和邮箱等。

MODULE_DESCRIPTION()用来简单描述该模块的用途或者功能介绍。

**MODULE_ALIAS()为用户空间提供一个合适的别名。**下面我们来看如何编译这个内核模块。在优麒麟Linux上编译该内核模块,下面是编写内核模块的Makefile文件。

现在创建好了咱们的模块,下一步就是来编写内核模块的makefile文件

b. 内核模块的makefile文件

0   BASEINCLUDE ?= /lib/modules/`uname -r`/build
1
2   mytest-objs := my_test.o
3   obj-m  :=  mytest.o
4
5   all :
6   $(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
7
8   clean:
9   $(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) clean;
10  rm -f *.ko;

第0行的****BASEINCLUDE指向正在运行Linux的内核编译目录,对于编译优麒麟Linux中运行的内核模块,我们需要指定到当前系统对应的内核中。一般来说,Linux系统的内核模块都会安装到/lib/modules这个目录下,通过“uname -r”命令可以找到对应的内核版本。

首先通过“uname -r”来查看当前系统的内核,比如我的系统里面装了4.15.0-20-generic的内核版本,这个内核版本的头文件放在/usr/src/linux-headers-4.15.0-20-generic目录中。

第2行表示该内核模块需要哪些目标文件,格式是:<模块名>-objs := <目标文件>.o

第3行表示要生成的模块。注意,模块名字不能和目标文件名相同。格式是: obj-m :=<模块名>.o

第5~6行表示要编译执行的动作。

第8~10行表示执行make clean需要的动作。

然后在终端中输入make命令来执行编译。$ make编译完成之后会生成mytest.ko文件。

我们可以通过file命令检查编译的模块是否正确,可以看到变成x86-64架构的ELF文件,说明已经编译成功了。

另外,也可以通过modinfo命令进一步做检查。

c.验证

接下来就可以在优麒麟Linux机器上验证我们的内核模块了

$sudo insmod mytest.ko  (安装模块)

你会发现没有输出,别着急,因为例子中的输出函数 printk()的默认输出等级,可以使用dmesg命令查看内核的打印信息。

$dmesg…
[258.575353] my first kernel module init

另外,你可以通过lsmod命令查看当前mytest模块是否已经被加载到系统中,它会显示模块之间的依赖关系。

加载模块之后,系统会在/sys/modules目录下新建一个目录,比如对于mytest模块会建一个名为mytest的目录。

figo@figo-OptiPlex-9020:/sys/module/mytest$ tree -a

如果需要卸载模块,可以通过rmmod命令来实现。

d. linux模块小结

模块加载函数:加载模块时,该函数会被自动执行,通常做一些初始化工作。

模块卸载函数:卸载模块时,该函数也会被自动执行,做一些清理工作。

模块许可声明:内核模块必须声明许可证,否则内核会发出被污染的警告。

模块参数:根据需求来添加,为可选项。

模块作者和描述声明:一般都需要完善这些信息。

模块导出符号:根据需求来添加,为可选项。

2. 模块参数传递

内核模块作为一个可扩展的动态模块,为Linux内核提供了灵活性。但是有时我们需要根据不同的应用场景给内核模块传递不同的参数,Linux内核提供一个宏来实现模块的参数传递。

#define module_param(name, type, perm)        \
module_param_named(name, name, type, perm)
#define MODULE_PARM_DESC(_parm, desc) \
__MODULE_INFO(parm, _parm, #_parm ":" desc)

module_param()宏由3个参数组成,name表示参数名,type表示参数类型,perm表示参数的读写等权限。

MODULE_PARM_DESC()宏为这个参数的简单说明,参数类型可以是byte、short、ushort、int、uint、long、ulong、char和bool等类型。

perm指定在sysfs中相应文件的访问权限,如设置为0表示不会出现在sysfs文件系统中;如设置成S_IRUGO(0444)可以被所有人读取,但是不能修改;如设置成S_IRUGO|S_IWUSR(0644),说明可以让root权限的用户修改这个参数。

(这里你可能有点懵,没事我们接着看看下面的栗子会好点)

这个例子定义了一个模块参数debug,类型是int,初始化值为1,权限访问为0644。

也就是说root权限用户可以修改这个值,这个参数的用途是打开调试信息。(参数说明的意思)

其实这是一个比较常用的内核调试方法,可以通过模块参数使用调试功能。通过debug=1 ,打开了调试功能。

下面这个例子定义了两个内核参数,一个是debug,另一个是静态全局变量mytest。

当通过“insmod mymodule.ko mytest=200”命令来加载模块时,可以看到终端里输出为:

(这里也可以安装)

还可以通过调试参数来关闭和打开调试信息。

在/sys/module/mymodule/parameters目录下面可以看到新增的两个参数。

具体更多地想法和疑惑,去实际操作一下会有深的体会哦

3、符号共享

我们在为一个设备编写驱动程序时,会把驱动按照功能分成好几个内核模块,这些内核模块之间有一些接口函数需要相互调用,这怎么实现呢?(锁一个驱动是很多模块的组合)

Linux内核为我们提供两个宏来解决这个问题。

EXPORT_SYMBOL( )
EXPORT_SYMBOL_GPL( )

EXPORT_SYMBOL()把函数或者符号对全部内核代码公开,也就是将一个函数以符号的方式导出给内核中的其他模块使用

EXPORT_SYMBOL_GPL()只能包含GPL许可的模块,内核核心的大部分模块导出来的符号都是使用GPL()这种形式的。如果要使用EXPORT_SYMBOL_GPL()导出函数,那么需要显式地通过模块申明为“GPL”,如MODULE_LICENSE(“GPL”)。

内核导出的符号表可以通过/proc/kallsyms来查看。

这些信息包含的内容:

  • 第1列显示的是该符号在内核地址空间的地址;
  • 第2列是符号属性,比如T表示该符号在text段中;
  • 第3列表示符号的字符串,也就是EXPORT_SYMBOL()导出来的符号;
  • 第4列显示哪些内核模块在使用这些符号。

相信到了这一步其实懵懵懂懂的应该具有一些能力去自己实现一些模块插到内核中了,想想自己竟然能改变内核了,有没有很兴奋。

目录
相关文章
|
12天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
12天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
13天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
13天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
15天前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
32 3
|
1月前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
67 4
|
19天前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
27 2
|
19天前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
43 1
|
1月前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
63 9
|
1月前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。