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列显示哪些内核模块在使用这些符号。

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

Linux内核学习(八):linux内核配置与模块相关推荐

  1. Linux内核学习之Linux内核“源码目录”

    一.Linux内核版本前期知识介绍 : 我们先去Linux官网看看,它的网站是:kernel.org: 我们可以看到最新稳定版本已经来到了5.6.7了,不过在这之前我们稍微了解一下版本号这一串数字的含 ...

  2. [奔跑吧 Linux内核][学习记录]编译内核-实验1-2-[环境以及参考]

    1.下载的版本 VMware  workstation player v16.2.4 Ubuntu v22.04.1 Linux kernel v5.19 2.参考的文章 环境安装 [干货]win10 ...

  3. Linux netfilter 学习笔记 之十五 netfilter模块添加一个match

    通过这段时间的学习,基本上熟悉了netfilter模块,为了进一步加深对netfilter的认识以及理解iptables与netfilter的联系,准备添加一个match模块. 在看到网关产品会有一个 ...

  4. Linux内核学习笔记——Linux中的用户组和权限管理(UID是什么?)

    目录 一.背景 进程权限 最小权限原则 二.linux系统安全模型 用户 用户组 用户和组的关系 安全上下文 进程的用户ID 函数setreuid和setregid 函数seteuid和setegid ...

  5. Linux系统学习 八、SSH服务—SSH远程管理服务

    1.SSH简介 ssh(安全外壳协议)是Secure Shell的缩写,是建立在应用层和传输层基础上的安全协议.传输的时候是经过加密的,防止信息泄露,比telnet(明文传递)要安全很多. ftp安装 ...

  6. Linux基础学习八:mysql主从复制原理以及详细搭建步骤

    MySQL的主从复制 MySQL的主从复制,指的是可以创建多台和主数据库完全一样的数据库环境(从数据库),对主数据库的写操作(增.删.改)会自动同步到子数据库中. 作用: 从数据库作作为冷备机,进行日 ...

  7. linux 的学习笔记-linux 指令大全

    1. find 基本语法参数如下: find [PATH] [option] [action]# 与时间有关的参数: -mtime n : n为数字,意思为在n天之前的"一天内"被 ...

  8. 嵌入式linux培训教程,嵌入式Linux开发学习之Linux文件系统学习

    导读 操作系统的基本功能之一就是文件管理,而文件的管理是由文件系统来完成的.Linux 支持多种文件系统,本文我们就来讲解 Linux 下的文件系统.文件系统类型.文件系统结构和文件系统相关Shell ...

  9. linux的学习路径,Linux学习路线图『从入门到精通』

    学习任何知识,目标明确很重要.如果你要学习Linux,下面分享一张Linux学习路线图,帮你明确学习目标,告诉你Linux应该怎样学. 一.学习Linux要达到什么程度? 1. 掌握至少50个以上的常 ...

  10. linux系统学习之Linux打印文件和发送邮件

    2019独角兽企业重金招聘Python工程师标准>>> 文件打印 如果你希望打印文本文件,最好预先处理一下,包括调整边距.设置行高.设置标题等,这样打印出来的文件更加美观,易于阅读. ...

最新文章

  1. 精确人工智能——核物理与粒子物理领域的新生力量
  2. 中国科协发布20个重大科学问题和工程技术难题
  3. 福禄克网络与NBASE-T联盟联合发布电缆布线基础设施白皮书
  4. hihocoder 1260
  5. 学习手记(2018/7/14~2018/7/18)——快乐纪中
  6. android studio发布项目到github
  7. 因为我们一直强调选品的重要性
  8. linux信号常用函数
  9. Oracle 10g 安装教程
  10. 百科知识 scm文件如何打开
  11. 请简述一下RS485通讯连接方式及其应用?
  12. ppapi获取html,在HTML中给PPAPI插件配置参数
  13. python模拟微信登录公众号_python通过手机抓取微信公众号
  14. JDK Required: 'tools.jar' seems to be not in IDEA classpath解决办法
  15. 如何将写好的网页放到服务器上,写好的网页怎么上传云服务器
  16. python重启路由器_Python实现路由器的重启和查看实时流量
  17. golang $or $in $ge $gte 查询使用
  18. bode函数_频响函数及其bode图nyquist图
  19. 4号线地铁站点列表_北京地铁4号线线路图 4号线地铁站点列表
  20. -nan(ind) 重载运算符以及结构体排序

热门文章

  1. 一鸣心所向:可以直接套用的成交标准法则
  2. energy plus matlab,Energyplus教程系列1—Energyplus到底能干啥.ppt
  3. 黑客6种方法入侵你的计算机系统
  4. 一步一步教你如何在手机上看电子书
  5. 《电脑书籍镜像下载3CD PDG格式》PDG电子书[ISO]
  6. python文件操作方法seek_Python文件操作及seek偏移详解
  7. API LayoutInflater
  8. 整理:国内主流云计算方案比较
  9. 关于MyEclipse 10 破解程序打开的原因
  10. 机器学习入门好文章--超级推荐