15 | Linux初始化(下):从_start到第一个进程
LMOS
该思维导图由 AI 生成,仅供参考
你好,我是 LMOS。
今天我们继续来研究 Linux 的初始化流程,为你讲解如何解压内核,然后讲解 Linux 内核第一个 C 函数。最后,我们会用 Linux 的第一个用户进程的建立来收尾。
如果用你上手去玩一款新游戏做类比的话,那么上节课只是新手教程,而这节课就是更深入的实战了。后面你会看到很多熟悉的“面孔”,像是我们前面讲过的 CPU 工作模式、MMU 页表等等基础知识,这节课都会得到运用。
解压后内核初始化
下面,我们先从 setup.bin 文件的入口 _start 开始,了解启动信息结构,接着由 16 位 main 函数切换 CPU 到保护模式,然后跳入 vmlinux.bin 文件中的 startup_32 函数重新加载段描述符。
如果是 64 位的系统,就要进入 startup_64 函数,切换到 CPU 到长模式,最后调用 extract_kernel 函数解压 Linux 内核,并进入内核的 startup_64 函数,由此 Linux 内核开始运行。
为何要从 _start 开始
通过上节课对 vmlinuz 文件结构的研究,我们已经搞清楚了其中的 vmlinux.bin 是如何产生的,它是由 linux/arch/x86/boot/compressed 目录下的一些目标文件,以及 piggy.S 包含的一个 vmlinux.bin.gz 的压缩文件一起生成的。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了Linux初始化的流程,从内核解压到第一个用户进程的建立。文章首先介绍了从setup.bin文件的入口_start开始,解释了启动信息结构,以及在64位系统中切换到长模式的过程。接着详细解释了setup_header结构的重要性,以及16位的main函数在初始化过程中的作用。随后,文章介绍了go_to_protected_mode函数和protected_mode_jump函数的功能,以及在startup_32函数中重新加载段描述符、设置内核栈、检测CPU支持长模式等操作。最后,文章提到了进入长模式并跳转到startup_64函数的过程。整篇文章通过深入的技术细节,帮助读者全面了解了Linux初始化的流程,适合对Linux内核感兴趣的读者深入学习。文章还介绍了startup_64函数、extract_kernel函数以及Linux内核的startup_64和x86_64_start_kernel函数,展示了内核初始化的关键步骤和技术特点。文章内容丰富,涵盖了Linux内核初始化的各个重要环节,对于想深入了解Linux内核启动流程的读者具有很高的参考价值。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《操作系统实战 45 讲》,新⼈⾸单¥68
《操作系统实战 45 讲》,新⼈⾸单¥68
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(16)
- 最新
- 精选
- neohope大体上整理了一下,有问题欢迎帮忙指正【上】: Grub在/boot目录下找到的linux内核,是bzImage格式 1、bzImage格式生成: 1.1、head_64.S+其他源文件->编译-> vmlinux【A】 1.2、objcopy工具拷贝【 拷贝时,删除了文件中“.comment”段,符号表和重定位表】->vmlinux.bin【A】 1.3、gzib压缩->vmlinux.bin.gz 1.4、piggy打包,附加解压信息->piggy.o->其他.o文件一起链接->vmlinux【B】 1.5、objcopy工具拷贝【 拷贝时,删除了文件中“.comment”段,符号表和重定位表】->vmlinux【B】 1.6、head.S +main.c+其他->setup.bin 1.7、setup.bin+vmlinux.bin【B】->bzImage合并->bzImage 2、GRUB加载bzImage文件 2.1、会将bzImage的setup.bin加载到内存地址0x90000 处 2.2、把vmlinuz中的vmlinux.bin部分,加载到1MB 开始的内存地址 3、GRUB会继续执行setup.bin代码,入口在header.S【arch/x86/boot/header.S】 GRUB会填充linux内核的一个setup_header结构,将内核启动需要的信息,写入到内核中对应位置,而且GRUB自身也维护了一个相似的结构。 Header.S文件中从start_of_setup开始,其实就是这个setup_header的结构。 此外, bootparam.h有这个结构的C语言定义,会从Header.S中把数据拷贝到结构体中,方便后续使用。 4、GRUB然后会跳转到 0x90200开始执行【恰好跳过了最开始512 字节的 bootsector】,正好是head.S的_start这个位置; 5、在head.S最后,调用main函数继续执行 6、main函数【 arch/x86/boot/main.c】【16 位实模式】 6.1、拷贝header.S中setup_header结构,到boot_params【arch\x86\include\uapi\asm\bootparam.h】 6.2、调用BIOS中断,进行初始化设置,包括console、堆、CPU模式、内存、键盘、APM、显卡模式等 6.3、调用go_to_protected_mode进入保护模式 7、 go_to_protected_mode函数【 arch/x86/boot/pm.c】 7.1、安装实模式切换钩子 7.2、启用1M以上内存 7.3、设置中断描述符表IDT 7.4、设置全局描述符表GDT 7.4、protected_mode_jump,跳转到boot_params.hdr.code32_start【保护模式下,长跳转,地址为 0x100000】 8、恰好是vmlinux.bin在内存中的位置,通过这一跳转,正式进入vmlinux.bin 9、startup_32【 arch/x86/boot/compressed/head64.S】 全局描述符GDT 加载段描述符 设置栈 检查CPU是否支持长模式 开启PAE 建立MMU【4级,4G】 开启长模式 段描述符和startup_64地址入栈 开启分页和保护模式 弹出段描述符和startup_64地址到CS:RIP中,进入长模式 10、 startup_64【 arch/x86/boot/compressed/head64.S】 初始化寄存器 初始化栈 调准给MMU级别 压缩内核移动到Buffer最后 调用.Lrelocated 11、.Lrelocated 申请内存 被解压数据开始地址 被解压数据长度 解压数据开始地址 解压后数据长度 调用 extract_kernel解压内核 12、extract_kernel解压内核【 arch/x86/boot/compressed/misc.c】 保存boot_params 解压内核 解析ELF,处理重定向, 把 vmlinux 中的指令段、数据段、BSS 段,根据 elf 中信息和要求放入特定的内存空间 返回了解压后内核地址,保存到%rax 13、返回到.Lrelocated继续执行 跳转到%rax【解压后内核地址】,继续执行 解压后的内核文件,入口函数为【arch/x86/kernel/head_64.S】
作者回复: 学习非常认真啊
2021-06-12333 - neohope大体上整理了一下,有问题欢迎帮忙指正【下】: 14、SYM_CODE_START_NOALIGN(startup_64)【arch/x86/kernel/head_64.S】 SMP 系统加电之后,总线仲裁机制会选出多个 CPU 中的一个 CPU,称为 BSP,也叫第一个 CPU。它负责让 BSP CPU 先启动,其它 CPU 则等待 BSP CPU 的唤醒。 第一个启动的 CPU,会跳转 secondary_startup_64 函数中 1 标号处,对于其它被唤醒的 CPU 则会直接执行 secondary_startup_64 函数。 15、secondary_startup_64 函数【arch/x86/kernel/head_64.S】 各类初始化工作,gdt、描述符等 跳转到initial_code,也就是x86_64_start_kernel 16、 x86_64_start_kernel【 arch/x86/kernel/head64.c】 各类初始化工作,清理bss段,清理页目录,复制引导信息等 调用x86_64_start_reservations 17、x86_64_start_reservations【 arch/x86/kernel/head64.c】 调用start_kernel(); 18、start_kernel【 init/main.c】 各类初始化:ARCH、日志、陷阱门、内存、调度器、工作队列、RCU锁、Trace事件、IRQ中断、定时器、软中断、ACPI、fork、缓存、安全、pagecache、信号量、cpuset、cgroup等等 调用 arch_call_rest_init,调用到rest_init 19、rest_init【 init/main.c】 kernel_thread,调用_do_fork,创建了kernel_init进程,pid=1 . 是系统中所有其它用户进程的祖先 kernel_thread,调用_do_fork,创建了 kernel_thread进程,pid=2, 负责所有内核线程的调度和管理 【最后当前的进程, 会变成idle进程,pid=0】 20、kernel_init 根据内核启动参数,调用run_init_process,创建对应进程 调用try_to_run_init_process函数,尝试以 /sbin/init、/etc/init、/bin/init、/bin/sh 这些文件为可执行文件建立init进程,只要其中之一成功就可以 调用链如下: try_to_run_init_process run_init_process kernel_execve bprm_execve exec_binprm search_binary_handler-》依次尝试按各种可执行文件格式进行加载,而ELF的处理函数为 load_elf_binary load_elf_binary start_thread start_thread_common,会将寄存器地址,设置为ELF启动地址 当从系统调用返回用户态时,init进程【1号进程】,就从ELF执行了 到此为止,系统的启动过程结束。
作者回复: 是的 大写6666
2021-06-12217 - pedro_start在setup.bin的开头, x86_64_start_kernel在vmlinux.bin的开头,然后start_kernel初始化,然后rest_init初始化第一个用户进程和第一个内核进程,开始操作系统罪恶的一生。
作者回复: 哈哈
2021-06-116 - Geek_59a6f9老师,你这里说的grub是grub legacy还是grub2啊?grub2应该首先会进入保护模式,那grub2还会跳转到inux/arch/x86/boot/head.S 里的main函数 再去执行一次切换保护模式吗?这个时候应该早就是保护模式了吧
作者回复: 是的 ,但是Linux并不用GRUB初始化的保护模式
2021-06-2422 - 青玉白露课程已经快进入正题了,下一步就是内存了吧
编辑回复: 是的,下个模块是内存管理,更加有挑战性,敬请期待!
2021-07-061 - blentle收获盛大,终于看到了稍微能消化的一篇了
作者回复: 你好,你平常接触Linux吧
2021-06-111 - springXu这个问题是考对Linux熟悉程度了。哈
作者回复: 这很难吗
2021-06-111 - Qfeng内核启动最后创建了两个进程:kernel_init和kernel_thread,前面是第一个用户进程,后续用户进程都是从它fork而来,后面是内核进程,用来管理后续内核线程调度。这两个进程令我印象深刻。
作者回复: 666666
2022-06-19 - 艾恩凝这节终于结束了,计划俩月完成,感觉进度有点慢了,到现在快20天了,应该去年来参与这门课的
编辑回复: 相逢即有缘,现在跟进也不迟,有什么收获或疑问欢迎多多分享哦(ง •̀_•́)ง
2022-04-10 - ifelse果然厉害了
作者回复: 什么厉害了
2022-02-13
收起评论