第三章 Linux内核及内核编程
3.1 Linux内核的发展与演变
3.2 Linux 2.6后的内核特点
- 支持内核任务抢占(中断、软中断、自旋锁等原子上下文进程无法抢占执行)
- 总线、设备、驱动实现对设备进行控制。通过一种总线类型,将设备和驱动联系起来,总线中的match()函数用来匹配设备和驱动,完成后会执行驱动程序中的probe()函数。
3.3 Linux内核的组成
- 内核源码目录
arch:对不同平台的支持
block:块设备驱动I/O调度
crypto:加密、散列算法、压缩和CRC算法
documentation:解释和注释
drivers:设备驱动程序,例如char、block、net、mtd、i2c
fs:所支持的文件系统,例如EXT、FAT、NTFS、JFFS2等
include:和系统相关的头文件在include/linux下
init:内核初始化代码
ipc:进程间通信代码
kernel:内核核心部分,包括进程调度、定时器等,和平台相关的一部分代码放在arch/*/kernel下
lib:库文件代码
mm:内存管理代码,和平台相关的一部分代码放在arch/*/mm下
net:网络相关代码,常见的网络协议的实现
scripts:配置内核的脚本
security:SElinux模块,安全增强
sound:ALSA、OSS框架
usr:用于打包和压缩的cpio等 - 内核要求drivers和arch软件架构分离,驱动中不包含板级信息。
- 内核的通用部分(kernel、fs、ipc、net等)与硬件剥离(arch、drivers)。
- Linux内核主要由进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)5个子系统组成。
- 在Linux内核中,使用task_struct结构体来描述进程,该结构体中包含描述该进程内存资源、文件系统资源、文件资源、tty资源、信号处理等的指针。
- 在内核编程中,如果需要几个并发执行的任务,可以启动内核线程,这些线程没有用户空间。启动内核线程的函数为:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags); - 32位处理器的Linux的每个进程享有4GB的内存空间,0~3GB属于用户空间,3~4GB属于内核空间
- Linux支持进程间的多种通信机制,包含信号量、共享内存、消息队列、管道、UNIX域套接字等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。
- CPU内部往往实现了不同操作模式(级别),不同模式有不同功能,高层程序往往不能访问低级功能,而必须以某种方式切换到低级模式。在Linux系统中,内核可进行任何操作,而应用程序则被禁止对硬件的直接访问和对内存的未授权访问。
3.4 Linux内核的编译及加载
- 配置内核命令:
- make config
- make menuconfig(不依赖QT和GTK+)
- make xconfig
- make gconfig
- arch/arm/configs/xxx_defconfig文件包含了许多电路板的默认配置。运行make ARCH=arm xxx_defconfig就可以为xxx开发板配置内核。
编译内核和模块的命令:- make ARCH=arm zImage
- make ARCH=arm modules
- 在源代码的根目录下会得到未压缩的内核映像vmlinux和内核符号表文件System.map,在arch/arm/boot/目录下会得到压缩的内核映像zImage,在内核各对应目录内得到选中的内核模块。
- 使用make config、make menuconfig等命令后,会生成一个.config配置文件,记录哪些部分被编译入内核、哪些部分被编译为内核模块。
- 运行make menuconfig等时,配置工具首先分析与体系结构对应的/arch/xxx/Kconfig文件(xxx即为传入的ARCH参数),Kconfig又可能再次通过source引入下一层的Kconfig
- 在Linux内核中增加程序需要完成以下3项工作:
- 将编写的源代码复制到Linux内核源代码的相应目录中。
- 在目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项。
- 在目录的Makefile文件中增加对新源代码的编译条目。
- Kconfig语法和Makefile:
- 在内核源代码的drivers目录内的相应子目录中增加新设备驱动的源代码或者在arch/arm/mach-xxx下新增加板级支持的代码,同时增加或修改Kconfig配置脚本和Makefile脚本
- Makefile语法:
- 目标定义:eg:obj -y += a.o 将a.c或者a.s文件编译为a.o
- obj -y 无条件编译
- obj -m 编译为模块
- obj -n 不编译
- 常见做法是根据.config文件中的变量进行判断:eg:obj -$(CONFIG_A) += a.o ;eg :obj -$(CONFIG_ABC) += abc.o
- 多文件模块的定义
- 如果一个模块由多个文件组成,应采用模块名加 -y 或 -objs 后缀
obj-$(CONfiG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o fsync.o ialloc.o inode.o ioctl.o namei.o super.o symlink.o
ext2-$(CONfiG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONfiG_EXT2_FS_POSIX_ACL) += acl.o
ext2-$(CONfiG_EXT2_FS_SECURITY) += xattr_security.o
ext2-$(CONfiG_EXT2_FS_XIP) += xip.o
- 如果一个模块由多个文件组成,应采用模块名加 -y 或 -objs 后缀
- 目录层次的迭代:
- obj-$(CONfiG_EXT2_FS) += ext2/ 当CONFIG_EXT2_FS的值为y或m时,kbuild将会把ext2目录列入向下迭代的目标中。
- 目标定义:eg:obj -y += a.o 将a.c或者a.s文件编译为a.o
- Kconfig语法:
- 配置选项:
- config关键字:定义新的配置选项,之后的几行代码定义了该配置选项的属性。配置选项的属性包括类型、数据范围、输入提示、依赖关系、选择关系及帮助信息、默认值等
- 每个配置选项都必须指定类型,类型包括bool、tristate、string、hex和int
- 输入提示的一般格式为:prompt <prompt> [if <expr>] 其中if用来提示依赖
- 默认值的格式:default <expr> [if <expr>]
- 依赖关系depends on <expr>,如果定义了多重依赖关系,它们之间用“&&”间隔。
- 选择关系:select <symbol> [if <expr>]
- 数据范围的格式为:range <symbol> <symbol> [if <expr>]
- 表达式:
config SHDMA_R8A73A4
def_bool y
depends on ARCH_R8A73A4 && SH_DMAE != n
表示当ARCH_R8A73A4被选中且SH_DMAE没有被选中的时候,才可能出现这个SHDMA_R8A73A4 - 帮助信息:help
- 菜单结构:
- menu和endmenu:
menu "Network device support"
depends on NET
config NETDEVICES
…
endmenu - 通过分析依赖关系生成菜单结构。如果菜单项在一定程度上依赖于前面的选项,它就能成为该选项的子菜单。如果父选项为“n”,子选项不可见;如果父选项可见,子选项才可见。
- menu和endmenu:
- 配置选项:
- LINUX内核引导:
- ARM LINUX内核的引导过程:
上电时bootrom运行。对于CPU0而言,bootrom会去引导bootloader,而其他CPU则判断自己是不是CPU0,进入WFI的状态等待CPU0来唤醒它。CPU0引导bootloader,bootloader引导Linux内核,在内核启动阶段,CPU0会发中断唤醒CPU1
- ARM LINUX内核的引导过程:
3.5 Linux编程风格
- 下划线命名
- Linux的代码缩进使用“TAB”。
- Linux中代码括号“{”和“}”的使用原则如下:
- 对于结构体、if/for/while/switch语句,“{”不另起一行
- 如果if、for循环后只有1行,不要加“{”和“}”
- if和else混用的情况下,else语句不另起一行
- 对于函数,“{”另起一行
- 在switch/case语句方面,Linux建议switch和case对齐