第二章 Linux 的内核结构及构建
- ---->这一章是自己总结的
1、内核结构(主要是下面这几个部分)
- 系统调用接口<–>System call interface
- 进程管理<------>Process management
- 内存管理<------>Memory management
- 虚拟文件系统<–>VFS
- 网络设备<------>Net
- 进程间通信<---->IPC
- 设备驱动<------>Drivers
- -------->具体对应的目录见书上59页
2、Makefiles and build system of kernel–kbuild
2.1 makefile包含五大部分
-
The top Makefile
:内核源码根目录下的Makefile,所有内核代码编译的入口。它负责两类东西的编译:vmlinux
:内核映像,Image->zImage -> boot.img,img是内核的常住居民。随系统启动而运行,直到系统关机。modules
:内核模块 xxx.ko,这种形式,也是内核源码的一种二进制形式,从本质上来说和内核映像是一样。但是ko是以普通文件的形式存在的,不随系统启动而运行,在系统需要你的时候 insmod xxx.ko,不需要你的时候就rmmod xxx.ko.如: camera.ko,ko形式多见于外设驱动它递归进入到内核源码树子目录去构建这些目标,应该至少包含一个arch的makefile和一个arch/$(ARCH)/Makefile(跟具体硬件平台相关)
-
.config
:内核源码根目录下,内核编译的配置文件,该文件直接决定哪些文件编译、怎么编译,内核配置文件包含像下面这些选项:- CONFIG_EXPERIMENTAL=y
- CONFIG_BROKEN_ON_SMP=y
- CONFIG_INIT_ENV_ARG_LIMIT=32
- CONFIG_CROSS_COMPILE=“arm-linux-”
-
arch/$(ARCH)/Makefile
:体系结构相关的Makefile,决定底层CPU相关的源码如何编译。为top Makefile提供具体硬件体系结构相关的信息。 -
scripts/Makefile.*
:包含了一些通用的Makefile模式,函数,脚本等等。供其他Makefile包含 -
kbuild Makefiles
_:每个子目录有一个kubild makefile,决定哪些文件编译到vmlinux(built-in),哪些编成在ko,kbuild(内核编译系统)内置了几个变量-
obj-y
: 这个变量指定的.o文件列表,最终编译到vmlinux中去
obj-y := a.o b.o c.o
a-y = 1.o 2.o- 1.o <- 1.c,2.o < 2.c
->a.o - b.o <- b.c,c.o <- c.c
=> vmlinux -> … -> boot.img
- 1.o <- 1.c,2.o < 2.c
-
obj-m
:obj-m这个变量指定的.o文件列表,kbuild最终会为每个.o生成一个同名的ko文件- obj-m := a.o b.o,a.o -> a.ko,b.o -> b.ko
-
语法:
目标名-y/目标名-objs
:生成"目标名"所依赖的.o文件列表-
obj-m := a.o b.o
- a.c(a.s) ->a.o -> a.ko
- b.c(b.s) ->b.o -> b.ko
-
obj-m := sb.o nb.o
-
sb-y := a.o b.o c.o
- a.o <- a.c(a.s)
- b.o <- b.c(b.s)
- c.o <- c.c(c.s)
- =>sb.o -> sb.ko
- nb.c(nb.s) -> nb.o -> nb.ko
-
obj-$(CONFIG_XXX) += sb.o
-
kbuild只认 obj-y obj-m
-
sb.o编还是不编,得看变量CONFIG_XXX的值
-
if CONFIG_XXX = y sb.o编,而且编进vmlinux
-
if CONFIG_XXX = m sb.o编成sb.ko
-
else other , 不编
obj-m += camera.o camera-objs += a.o b.o camera-$(CONFIG_MEIYANG) += mei.o
-
-
obj-$(CONFIG_XXX) += xxx/
- 表示你如果选择了CONFIG_XXX请到子目录xxx下面去读Makefile
-
-
2.2 内核编译的两大步骤
- 1、配置
make menuconfig
:=> 生成一个人性化的配置菜单树(供你选择)- sudo apt-get install libncurses5-dev
- 目的是生成一个 .config (配置文件,决定哪些文件编译,哪些不编译)
- CONFIG_FOO = y
- CONFIG_MM = m
- CONFIG_XX = n
- 2、编译 make(sudo ./mk -k)
- a. 先读.config里面的内容,把相应的变量值替换到Makefile中去
- obj-$(CONFIG_FOO) += foo.o
- obj-$(CONFIG_MM) += mm.o
- obj-$(CONFIG_XX) += xx.o
- ----->
- obj-y += foo.o
- obj-m += mm.o
- obj-n += xx.o
- b. 编译
- foo.o -> foo.o -> vmlinux
- mm.c -> mm.o -> mm.ko
- a. 先读.config里面的内容,把相应的变量值替换到Makefile中去
- 3、一个例子
- 先准备好下面的文件:my_char_drivers/hello.c,Kconfig,Makefile
- (1) 把my_char_drivers整个目录拷贝到 内核源码目录的 drivers/char
- (2) 修改 drivers/char/Kconfig 增加一行,让其包含my_char_drivers/Kconfig
- vim dirvers/char/Kconfig 增加一行
- source “drivers/char/my_char_drivers/Kconfig”
- (3) 修改 drivers/char/Makefile 增加一行,让其包含my_char_drivers/Makefile
- vim drivers/char/Makefile 增加-行
- obj-$(CONFIG_zhoulong_drivers) += my_char_drivers/
2.3 .config的内容
- Kconfig
- Kconfig是作为.config的配置文件
- 内容是用kconfig特有的语言编写
- 配置信息的数据库是一个配置选项的集合
- Kconfig 语法
- config
- config <symbol>
- <config options>
-
关键字config开始一个新的配置入口,紧接着的一行是上一个选项的属性,举例:
config FOO tristate prompt "a test" depends on BAR default m help "a example for config entry"
-
- menuconfig
- menuconfig <symbol>
- <config options>
- menu/endmenu
- menu “some prompt info”
- <menu options>
- <menu block>
- endmenu
- if/endif
- if <expr>
- endif
- source
- source “xxx/xxx/Kconfig”
- choice/endchoice
- choice [symbol]
- <choice options>
- <choice block>
- endchoice
- 一个选择只能是一个布尔值或三态,布尔选项只允许单个配置选项被选中,但是三态选择还允许将任意数量的配置项设置为“m”。
- 这个可以用在如果一个硬件有多个驱动程序存在并且只有一个驱动程序可以编译进内核,但是所有的驱动程序可以编译成模块
- comment
- comment “some prompt info”
- <comment options>
- 这定义了一个注释,它在配置过程中显示给用户,并且也与输出到输出文件。唯一可能的选项是依赖项。
- config
3、An example
4、Process of making zImage(uImage)
第三章 Linux内核及内核编程
3.2 linux 2.6以后内核的特点(相对于linux2.4)
1.新的调度器
- 内核 2.6 的早期采用了 O(1) 算法,之后转移到 CFS(Completely Fair Scheduler,完全公平调度)算法。在 Linux
3.14 中,也增加了一个新的调度类:SCHED_DEADLINE,它实现了 EDF(Earliest Deadline First,最早截止期限优先)调度算法。
2.内核抢占
- Linux 2.6 以后的内核版本还是存在一些不可抢占的区间,如中断上下文、软中断上下文和自旋锁锁住的区间
可以看到,在 Linux 2.4 的内核中,在 IRQ1 的中断服务程序唤醒 RT(实时)任务后,必须要等待前面一个 Normal(普通)任务的系统调用完成,返回用户空间的时候,RT 任务才能切入;而在 Linux 2.6 内核中,Normal 任务的关键部分(如自旋锁)结束的时候,RT 任务就从内核切入了。
3.改进的线程模型
- Linux 2.6 以后版本中的线程采用 NPTL(Native POSIX Thread Library,本地 POSIX 线程库)模型,操作速度得以极大提高,内核本身也增加了 FUTEX(Fast Userspace Mutex,快速用户态互斥体),从而减小多线程的通信开销。
4.虚拟内存的变化
- 从虚拟内存的角度来看,新内核融合了 r-map(反向映射)技术,显著改善虚拟内存在一定大小负载下的性能。反向映射即可以通过页结构体快速寻找到页面的映射。
5.文件系统
- Linux 2.6 版内核增加了对日志文件系统功能的支持,还包括对扩展属性及 POSIX 标准访问控制的支持。ext2/ext3/ext4 作为大多数 Linux 系统默认安装的文件系统,在 Linux 2.6 版内核中增加了对扩展属性的支持,可以给指定的文件在文件系统中嵌入元数据。
- 在文件系统方面,当前的研究热点是基于 B 树的 Btrfs,Btrfs 称为是下一代 Linux 文件系统,它在扩展性、数据一致性、多设备管理和针对 SSD 的优化等方面都优于 ext4
6.音频
- 高级 Linux 音频体系结构(Advanced Linux Sound Architecture,ALSA),ALSA 支持 USB 音频和 MIDI 设备,并支持全双工重放等功能
7.总线、设备和驱动模型
- 总线是三者联系起来的基础,通过一种总线类型,将设备和驱动联系起来。
总线类型中的 match() 函数用来匹配设备和驱动,当匹配操作完成之后就会执行驱动程序中的probe() 函数。
8.电源管理
9.联网和 IPSec
- 改进了对 IPv6 的支持
10. 用户界面层
11. Linux 3.0 后 ARM 架构的变更
- ARM Linux 的代码在时钟、DMA、pinmux、计时器刻度等诸多方面都进行了优化和调整,也删除了arch/arm/mach-xxx/include/mach 头文件目录,以至于 Linux 3.7 以后的内核可以支持多平台,即用同一份内核镜像运行于多家 SoC 公司的多个芯片,实现“一个Linux 可适用于所有的 ARM 系统”。
3.3 Linux 内核的组成
3.3.1 Linux 内核源代码的目录结构
目录 | 功能 |
---|---|
arch | 包含和硬件体系结构相关的代码,每种平台占一个相应的目录,如 i386、arm、arm64、powerpc、mips 等。存放的是各个平台以及各个平台的芯片对 Linux 内核进程调度、内存管理、中断等的支持,以及每个具体的 SoC 和电路板的板级支持代码。 |
block | 块设备驱动程序 I/O 调度 |
crypto | 常用加密和散列算法(如 AES、SHA 等),还有一些压缩和 CRC 校验算法 |
documentation | 内核各部分的通用解释和注释 |
drivers | 设备驱动程序,每个不同的驱动占用一个子目录,如 char、block、net、mtd、i2c 等。 |
fs | 所支持的各种文件系统,如 EXT、FAT、NTFS、JFFS2 等 |
include | 头文件,与系统相关的头文件放置在 include/linux 子目录下 |
init | 内核最核心的部分,包括进程调度、定时器等,而和平台相关的一部分代码放在 arch/*/kernel 目录下 |
lib | 库文件代码 |
mm | 内存管理代码,和平台相关的一部分代码放在 arch/*/mm 目录下 |
net | 网络相关代码,实现各种常见的网络协议 |
scripts | 用于配置内核的脚本文件 |
security | 主要是一个 SELinux 的模块 |
sound | ALSA、OSS 音频设备的驱动核心代码和常用设备驱动 |
usr | 实现用于打包和压缩的 cpio 等 |
include | 内核 API 级别头文件 |
3.3.2 Linux 内核的组成部分
- 进程调度
- 在 Linux 内核中,使用 task_struct 结构体来描述进程,该结构体中包含描述该进程内存资源、文件系统资源、文件资源、tty 资源、信号处理等的指针。Linux 的线程采用轻量级进程模型来实现,在用户空间通过 pthread_create() API 创建线程的时候,本质上内核只是创建了一个新的 task_struct,并将新task_struct 的所有资源指针都指向创建它的那个 task_struct
的资源指针。
- 在 Linux 内核中,使用 task_struct 结构体来描述进程,该结构体中包含描述该进程内存资源、文件系统资源、文件资源、tty 资源、信号处理等的指针。Linux 的线程采用轻量级进程模型来实现,在用户空间通过 pthread_create() API 创建线程的时候,本质上内核只是创建了一个新的 task_struct,并将新task_struct 的所有资源指针都指向创建它的那个 task_struct
- 内存管理
- 一般而言,32 位处理器的 Linux 的每个进程享有 4GB 的内存空间,0 ~ 3GB 属于用户空间,3 ~ 4GB 属于内核空间,内核空间对常规内存、I/O 设备内存以及高端内存有不同的处理方式。
上次CVTE笔试考了下面这个图(画出来)
- 一般而言,32 位处理器的 Linux 的每个进程享有 4GB 的内存空间,0 ~ 3GB 属于用户空间,3 ~ 4GB 属于内核空间,内核空间对常规内存、I/O 设备内存以及高端内存有不同的处理方式。
-
- 虚拟文件系统
- Linux 虚拟文件系统隐藏了各种硬件的具体细节,为所有设备提供了统一的接口。而且,它独立于各个具体的文件系统,是对各种文件系统的一个抽象。它为上层的应用程序提供了统一的 vfs_read()、vfs_write() 等接口,并调用具体底层文件系统或者设备驱动中实现的 file_operations 结构体的成员函数。
- 4.网络接口(略)
- 5.进程间通信(略)
3.3.3 Linux 内核空间与用户空间
- ARM 处理器分为 7 种工作模式:
3.4 Linux 内核的编译及加载
3.4.1 Linux 内核的编译
- Linux 驱动开发者需要牢固地掌握 Linux 内核的编译方法以为嵌入式系统构建可运行的Linux 操作系统映像。
运行 make menuconfig 等时,配置工具首先分析与体系结构对应的 /arch/xxx/Kconfig 文件(xxx 即为传入的 ARCH 参数),/arch/xxx/Kconfig 文件中除本身包含一些与体系结构相关的配置项和配置菜单以外,还通过 source 语句引入了一系列 Kconfig 文件,而这些 Kconfig又可能再次通过 source 引入下一层的 Kconfig,配置工具依据 Kconfig 包含的菜单和条目即可描绘出一个如图 3.9 所示的分层结构。
3.4.2 Kconfig 和 Makefile
- 在 Linux 内核中增加程序需要完成以下 3 项工作
- 将编写的源代码复制到 Linux 内核源代码的相应目录中
- 在目录的 Kconfig 文件中增加关于新源代码对应项目的编译配置选项
- 在目录的 Makefile 文件中增加对新源代码的编译条目
3.4.3 Linux 内核的引导(ARM Linux 为例)
- 。一般的 SoC 内嵌入了 bootrom,上电时 bootrom 运行。对于 CPU0 而言,bootrom 会去引导 bootloader,而其他 CPU 则判断自己是不是 CPU0,进入 WFI 的状态等待 CPU0 来唤醒它。CPU0 引导 bootloader,bootloader 引导Linux 内核,在内核启动阶段,CPU0 会发中断唤醒 CPU1,之后 CPU0 和 CPU1 都投入运行。CPU0 导致用户空间的 init 程序被调用,init 程序再派生其他进程,派生出来的进程再派生其他进程。CPU0 和 CPU1 共担这些负载,进行负载均衡。
3.5 Linux 下的 C 编程特点
3.5.1 Linux 编码风格
-
在内核源代码下存在一个文件 Documentation/CodingStyle,进行了比较详细的描述
-
在 Windows 程序中,习惯以如下方式命名宏、变量和函数
#def ine PI 3.1415926 /* 用大写字母代表宏 */ int minValue, maxValue; /* 变量:第一个单词全小写,其后单词的第一个字母大写 */ void SendData(void); /* 函数:所有单词第一个字母都大写 */
-
在 Linux 中它会被命名为:
#def ine PI 3.1415926 int min_value, max_value; void send_data(void);
3.6 工具链
- 见书本page100-page101
第4章 Linux内核模块
…
4.9 模块的编译
- 例:
KVERS = $(shell uname -r)
#Kernel module
obj-m += hello.o
#Specify flags for the module compilation
#EXTRA_CFLAGS = -g -O0
build:kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean