初学目标
1. 通过内核代码的学习,更好的了解上层应用程序的工作原理。
2. 通过驱动开发,为后续从事内核开发做准备。
3. 可以看懂上游厂商提供的驱动代码,掌握内核开发使用的调试方法。
4. 了解驱动的工作原理,可以编写简单的驱动代码。
什么是操作系统内核(Kernel)
作用:管理硬件,对上层应用提供API接口
Linux内核官网:https://www.kernel.org/
内核版本的选择:尽量选择Longterm版本,只修改BUG,支持新硬件,不新增功能,接口稳定,问题少。
发行版使用的Linux内核 = kernel.org内核 + 厂商自己的补丁
Linux内核启动流程:
iROM(BL0)--->SPL(BL1)---->BL2(加载u-boot.bin到内存ddr)----->Linux(运行内核uImage)---->rootfs(根文件系统/网络文件系统)---->init(根文件系统下的一号进程)----->getty(初始化终端)---->login(输入口令)---->bash(shell)
bootloader (u-boot)完整的启动过程:(iROM(BL0) ---BL1--- BL2---- u-boot.bin)
Pc(或linux系统)启动流程:
BIOS(pc启动加载的第一个软件)--->GRUB(选择操作系统)---->Linux(/boot/vmlinuz-xxx),vmlinux是未压缩的内核,vmlinuz是vmlinux的压缩文件(系统移植第四天笔记)
内核命令行参数:
setenv bootargs console=ttySAC2,115200 ...
tftpboot 41000000 uImage
tftpboot 42000000 exynos4412-fs4412.dtb
bootm 41000000 - 42000000
命令行参数放在设备树传给Linux内核
Linux内核怎么知道设备树的地址?
ARM引导协议(boot protocol):Documentation/arm/Booting 设备树地址通过R2寄存器传给内核
驱动程序两种形式:
1. 与内核编译到一起(和静态库类似)
2. 编译为内核模块
内核模块(LKM):将内核的一部分功能或实现提取出来,放在单独的ko文件中,和动态库类似
优点:
1. 减少内核的内存占用,不需要的功能不加载
2. 减少内核文件(uImage)大小,缩短启动时间
3. 内核修改需要重启系统才能生效,更新内核模块不需要重启系统就可以生效
内核模块通常放在/lib/modules以内核版本号命名的目录下
kernel目录中是操作系统自带的内核模块
extra第三方驱动
查看模块信息:modinfo 模块名/文件名
加载内核模块:insmod 文件名
查看已加载的内核模块:lsmod
卸载内核模块:rmmod 模块名
自动处理模块间依赖关系:modprobe 模块名/别名
内核源码目录:
arch:CPU相关代码
block:块设备驱动
crypto:加密算法
Documentation:文档
drivers:硬件驱动
firmware:硬件固件
fs:文件系统驱动
include:头文件
init:挂载根文件系统,创建init进程
ipc:进程间通信
kernel:内核基本功能
lib:通用算法(校验,压缩)
mm:内存管理
net:网络协议栈
samples:示例代码
scripts:编译和调试脚本
security:安全模型
sound:声卡驱动
tools:编译和调试工具
usr:生成内存根文件系统的工具
virt:虚拟化驱动
Kbuild:编译工程文件(Makefile)
Kconfig:配置参数
Linux内核最新代码
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/
https://elixir.bootlin.com/linux/latest/source
Linux下查看内核代码(或者使用source Insight代码浏览器查看):
在内核代码根目录下执行:make tags(首先得安装相应的软件包tags)
生成tags文件,打开vim,执行:set tags tags命令加载tags文件
跳转到函数:ts 函数名
跳转到光标所在函数:g ctrl+]
向后跳转:ctrl+t
内核使用基于Makefile的Kbuild编译框架
Kconfig生成配置界面使用配置文件
mconf程序读取Kconfig配置文件显示内核配置界面,生成.config配置文件
Kbuild和Makefile根据.config文件进行编译
目标是obj-y就将目标文件链接到内核
目标是obj-m就编译生成ko文件
编译内核模块
make modules_prepare----->编译外置模块所需的文件
make modules------->编译生成.ko文件(模块)
make modules_install INSTALL_MOD_PATH=rootfs------>把编译之后的模块安装到rootfs(根文件系统)
外部模块
代码不在内核中的模块
Makefile一般会被调用两次,第一次是make命令调用,第二次是内核编译框架调用
在内核源代码目录下执行(执行一次):
make exynos_defconfig----->拷贝模板,建立配置文件
make modules_prepare
在模块代码路径下执行:
make modules
make modules_install INSTALL_MOD_PATH=rootfs
新版本内核推荐将内核模块的编译规则放到Kbuild文件中描述,使用Makefile调用内核编译框架的主makefile
编译模块使用的内核版本需要与运行使用的内核版本一致
make uImage----->编译生成内核
make dtbs------>编译生成设备树
配置内核
1. 增加nfs支持
2. 增加IP自动配置支持CONFIG_IP_PNP
3. 增加nfsroot支持CONFIG_ROOT_NFS
4. 网卡驱动DM9000
5. 设备树中恢复firmware节点
内核代码特点:
1. 无法使用C语言标准库
2. 不能使用浮点数
3. 内核栈大小有限(4-8KB),不能定义过大的局部变量,不能使用递归算法
4. 被动调用(进程和中断调用)
5. 应用程序代码在用户态执行,内核代码在内核态执行,应用程序执行系统调用时会切换到内核态
ps列出的进程名称用[]括起来的是没有对应的可执行文件的程序,绝大部分都是内核线程。
内核线程的代码是主动运行的。
上下文:进程、中断
CPU运行状态:用户态(ARMv7的USER模式,x86的ring3)、内核态(ARMv7的SVC模式,x86的ring0)
进程上下文
一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行
中断上下文
由于触发信号,导致CPU中断当前进程,转而去执行另外的程序。那么当前进程的所有资源要保存,比如堆栈和指针。保存过后转而去执行中断处理程序,快读执行完毕返回,返回后恢复上一个进程的资源,继续执行。这就是中断的上下文
区分运行状态的优点:
1. 进程间隔离 2. 屏蔽底层硬件操作,简化上层应用程序