一、前言
本文为研究linux kernel源码所记录的一些笔记
- 源码下载路径: https://mirrors.edge.kernel.org/pub/linux/kernel/
- 源码git路径: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
由于篇幅问题,后续更新放在 linux内核源码分析记录
二、linux启动过程
1. 从引导加载程序内核
1.1. cpu上电
- 主板通电后,会启动cpu
- cpu启动复位后,开始在实模式下工作
- 所有x86兼容处理器均支持实模式
- 启动后开始从地址
0xfffffff0
执行第一条指令,这个地址被放置了BIOS的入口 - 但是实模式下只有16位寄存器,不能索引到上面的地址。其实这个地址被映射到了rom中
- ROM中存放各个硬件厂商定制的BIOS或者UEFI的启动代码,用于找到硬盘并引导系统启动
1.2. bios启动
UEFI和BIOS的区别
BIOS启动流程:
- 系统开机
- 上电自检(Power On Self Test 或 POST)。POST过后初始化用于启动的硬件(磁盘、键盘控制器等)
- BIOS会运行BIOS磁盘启动顺序中第一个磁盘的首440bytes(MBR启动代码区域)内的代码。
- 启动引导代码从BIOS获得控制权,然后引导启动下一阶段的代码(如果有的话)(一般是系统的启动引导代码)。
- 再次被启动的代码(二阶段代码)(即启动引导)会查阅支持和配置文件。根据配置文件中的信息,启动引导程序会将内核和initramfs文件载入系统的RAM中,然后开始启动内核。
UEFI启动流程:
- 系统开机
- 上电自检(Power On Self Test 或 POST)。UEFI 固件被加载,并由它初始化启动要用的硬件。
- 固件读取其引导管理器以确定从何处(比如,从哪个硬盘及分区)加载哪个 UEFI 应用。
- 固件按照引导管理器中的启动项目,加载UEFI 应用。
- 已启动的 UEFI 应用还可以启动其他应用(对应于 UEFI shell 或 rEFInd 之类的引导管理器的情况)或者启动内核及initramfs(对应于GRUB之类引导器的情况),这取决于 UEFI 应用的配置。
MBT和GPT
MBR
- bios在初始化和检查硬件之后,需要找一个可引导设备
- 初始可引导设备列表存在bios配置中,根据顺序一个一个找
- 对于硬盘,引导扇区在第一个扇区(512字节)的头446字节,并且引导扇区最后必须是
0x55
和0xaa
,这两个字节可以称为魔术字节,如果bios看到这两个字节,认为这个设备是可引导设备,这个也是MBR硬盘的第一个扇区的构成 - 在实模式下,内存的组成如下,所以我们写bootloader程序需要加载到
0x7c00
,如果需要操作显示,需要写入到0xa0000 ~ 0xbffff
1 | 0x00000000 - 0x000003FF - Real Mode Interrupt Vector Table |
- linux kernel启动函数(main函数)并不是常规的main,是
init/main.c
里面的start_kernel()
函数
三、数据结构
1. 公共机制
1.1. container_of
根据数据结构节点找value
2. rbtree
3. rcu 读拷贝更新
四、系统调用
1. 网络相关
1.1. epoll
1.2. bind 绑定地址到socket
1) 接口定义
1 | // net/socket.c |
1.3. unix套接字
源码主要看net/unix/af_unix.c
- unix套接字仅支持
SOCK_STREAM
、SOCK_RAW
、SOCK_DGRAM
、SOCK_SEQPACKET
这几种type,源码看unix_create()
- 使用unix套接字,protocol参数必须为
0
,原因查看unix_create()
的第一个判断
2. 文件相关
2.1. 监听文件变化 inotify
五、底层的几个机制
1. 惊群现象和处理
- 在linux的2.6.x已经解决,底层仅会唤起一个进程进行处理
六、编译内核
1. 编译过程
1.1. 配置
1.2. 编译
1.3. 安装
1) 安装
2) 安装模块
1 | INSTALL_MOD_PATH指定安装的根目录位置,会自动安装到此目录下的lib/modules/<arch>下 |
2. 选项解释
2.1. 基础知识
1) 模块编译选项
- 内核编译选项中,前面是
<*>
代表编译进内核 <M>
代表编译成模块
2) 部分配置解释
1 | # 内核开启kasan选项,有内存异常检测到会写到dmesg中 |
七、调试内核
1. 编译内核
- 下载内核源码
https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.19.1.tar.gz
- 解压后进入目录
1 | 创建默认配置 |
- 修改下述配置,开启debug,关闭地址随机化
1 | Kernel hacking ---> |
- 编译
1 | make -j 20 |
1.1. 想要某个函数不优化
- 给单个函数添加
__attribute__((optimize("O0")))
- 如,不优化
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
就写成下面这样
1 | static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb) __attribute__((optimize("O0"))); |
2. 构建initramfs和根文件系统
- 这里选择ubuntu的根文件系统
http://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04-base-amd64.tar.gz
- 创建镜像并mount
1 | ######### raw格式直接搞 ########## |
- 解压根文件系统到fs
1 | 解压根文件系统到目录下 |
- 构建initramfs,下载busybox:https://www.busybox.net/downloads/
- 编译
1 | => tar -xjvf busybox-1.36.1.tar.bz2 && cd busybox-1.36.1 |
- 编译之后在
busybox-1.36.1/_install
下面 - 找个目录写一个init启动脚本
1 | !/bin/busybox sh |
- 然后写一个脚本制作initramfs
1 | !/bin/bash |
- 执行制定busybox目录后,就生成了
initramfs.cpio.gz
过程遇到的坑
1) busybox编译过程make menuconfig
报错Unable to find the ncurses libraries or the required header files.
但实际安装了
- 确保安装了的情况下,参考: https://aur.archlinux.org/cgit/aur.git/tree/esp8266-rtos-sdk-aur-ncurses-fix.patch?h=esp8266-rtos-sdk
- 按照下面的diff修改一下即可
1 | diff --git a/tools/kconfig/lxdialog/check-lxdialog.sh b/tools/kconfig/lxdialog/check-lxdialog.sh |
2) busybox编译过程make
报错networking/tc.c:236:27: error: ‘TCA_CBQ_MAX’ undeclared (first use in this function); did you mean ‘TCA_CBS_MAX’?
- 原因是内核在6.9之后不支持CBQ了,需要打上一个patch: https://git.openembedded.org/openembedded-core/plain/meta/recipes-core/busybox/busybox/busybox-1.36.1-no-cbq.patch
- 手动按照patch的改动改一下即可
3. 起系统
-s
: 相当于-gdb tcp::1234
,在1234启用gdb调试-hda rootfs.img
: 将rootfs.img作为硬盘加到设备里面-initrd initramfs.cpio.gz
: 将制作的initramfs作为ram启动-append "console=ttyS0,38400" -serial file:output.txt
: 将启动过程输出到文件
1 | qemu-system-x86_64 -enable-kvm -m 4G -smp 1 -kernel /path/to/kernel/source/arch/x86_64/boot/bzImage -hda rootfs.img -initrd initramfs.cpio.gz -nographic -s -append "console=ttyS0,38400" -serial file:output.txt |
- 配置网络,因为没有设置网卡,所以使用的是qemu自己模拟的用户态网络,配置网络dhcp获取即可
1 | 查看连接 |
- 共享磁盘在
/dev/sdb
,自己看着挂载就好了
踩坑记
1) initramfs中无法识别/dev/sda
- 应该是内核里面将驱动外置了,默认是驱动编译到bzImage里面的,部分可能搞到外面了,需要手动拷贝然后挂载
- 修改制作initramfs的脚本
1 | !/bin/bash |
- 修改init脚本
1 | !/bin/busybox sh |
4. gdb调试
4.1. 命令行调试
- 到linux内核编译的目录下
1 | => gdb vmlinux |
4.2. vscode调试
- vscode打开源码目录
- 配置
launch.json
1 | { |
八、crash调试vmcore排查宕机问题
1. crash命令使用
1.1. 查看系统信息
系统信息
1 | sys |
dmesg
1 | log | tail -n 10 |
内存情况
1 | kmem -i |
runq 查看各个cpu正在运行的线程和时间
1 | runq -m |
中断状态
1 | irq -s |
1.2. bt 查看堆栈
-a
: 展示每个cpu的正在运行的进程的堆栈-c cpu
: 展示特定cpu的堆栈,示例 “3”、”1,8,9”、”1-23”或”1,8,9-14”-l
: 显示对应的代码所在文件和行pid
: 直接跟pid可以查看对应进程的栈
1.3. struct 查看结构体
-o
: 展示结构体偏移
1 | 对照地址查看结构体对应的值 |
1) 查看特定地址的结构体的某些成员变量的值
1 | struct rq.lock,nr_running,cpu_load ffff8f6d6faa1ec0 |
2) 几个常见的全局变量
struct rq 每个cpu一个
1 | struct rq.lock runqueues:0,1 |
1.4. set 设置进程上下文
-p
: 设置context到panic的task
1 | set -p |
1.5. task 查看进程的task_struct和thread_info信息
1 | task [pid] |