一、前言
本文为研究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. 构建根文件系统
- 这里选择ubuntu的根文件系统
http://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04-base-amd64.tar.gz
- 创建镜像并mount
1 | 创建10G根镜像文件 |
3. 起系统
-s
: 相当于-gdb tcp::1234
,在1234启用gdb调试-append "root=/dev/sda rw console=ttyS0"
: root使用sda,要rw否则会只读;console设置输出到当前控制台- 使用
spice://127.0.0.1:5900
可以看到画面
1 | qemu-system-x86_64 -enable-kvm -m 4G -smp 1 -kernel /path/to/kernel/source/arch/x86_64/boot/bzImage -hda rootfs.img -drive format=raw -append "root=/dev/sda rw console=ttyS0" -nographic -s -spice port=5900,disable-ticketing=on |
- 配置网络,因为没有设置网卡,所以使用的是qemu自己模拟的用户态网络,配置网络dhcp获取即可
1 | 查看连接 |
- 共享磁盘在
/dev/sdb
,自己看着挂载就好了
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] |