搓OS-day2
手搓OS-day2
今天看的东西全都围绕这两个C语言代码展开
1 | // say.c |
1 | // main.c |
操作系统上的C程序
编译过程的一些参数含义
1 | gcc -c -O2 -o main.o main.c |
1 | file say.o main.o |
输出结果:
1 | say.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped |
表明两个文件都是64位的ELF格式可重定位文件
1 | objdump -d main.o |
汇编代码:
1 | 0000000000000000 <main>: |
发现地址是从0开始的(死去的组成原理开始袭击我),可以看出这是一个CISC指令集,采用小端存储,系统地址按字节编址,call
指令对应机器码为:0xe8H
,可以看见偏移量现在是0,因为并不知道从哪儿调用
lea
是获取调用say参数的指令
链接
1 | gcc main.o say.o |
这里不能直接用ld
进行链接,这其实可以理解的,因为我们并没有对putchar
做任何定义。而真正的链接流程与下面的加载流程的2中的细节其实是完全对应的(这也不难理解,链接好后加载到内存是很合理的吧)
同理这里也可用使用objdump
来查看文件内容
1 | objdump -d a.out |
加载
流程:
- Shell接受命令后使用
fork()
创建一个新进程 - 在子进程中使用
execve()
加载a.out
,在这里做出必要的内存映射:
① 执行动态链接库
②
跳转到a.out
的_start
运行,初始化C语言运行环境
③ 执行main
- 如需要输入输出会使用特殊指令进行系统调用
补漏:
gdb指令
1 | (gdb) starti #运行到第一条指令便停止 |
1 | (gdb) bt f # backtrace full 打印堆栈信息 |
1 | (gdb)info inferiors # 打印线程/进程信息 |
Linux指令
1 | !cat /proc/{PID}/maps #打印进程内存信息 |
具体流程我也没看懂,后边看懂了再补
Bare-Metal上的C程序
Bare-Metal就是一块纯铁(bushi),就是类似一个没有
操作系统的情况下(我先这么理解),续:通常用来描述在没有操作系统或软件层的支持的情况下运行的计算机系统(chatGPT如是说)。
唉,还是要写Makefile
1 | NAME := hello |
1 | make -nB ARCH=x86_64-qemu |
编译
在之前C语言的部分上研究过,编译就是对.c文件翻译为可重定位的(relocatabel)的二进制目标文件(.o)。没有操作系统意味着所有的系统调用都是不成立的,所以没啥太大区别()。需要加一个参数。
链接
链接命令(看着吓人):
1 | ld -melf_x86_64 -N -Ttext-segment=0x00100000 -o build/hello-x86_64-qemu.o \ |
-melf_x86_64
:指定链接为x86_64 ELF
格式
-N
:没看懂()
-Ttext-segment=0x00100000
:设置加载地址
后边给出了要链接的文件,分别是main.o
,say.o
和必要的库函数(AbstractMachine和klib)
总结一下,这个链接后的文件并不能直接在操作系统上正常运行(不能不能跑,而是会报错),原因在于会非法访问,需要在bare-mental上运行,要创建一个镜像文件:
1 | ( cat abstract-machine/am/src/x86/qemu/boot/mbr \ |
镜像由一个512字节的MBR(主引导记录),1024字节的空文件和hello-x86_64-qemu.o组成
这里提一下qemu的含义:QUEM(Quick Emulator)是一款开源的模拟器和虚拟机监视器,支持模拟多种体系结构(包括 x86、ARM、MIPS 等)以及在不同操作系统上运行。
启动qemu的方式:
1 | qemu-system-x86_64 -hda your_disk_image.img |
加载
1 | qemu-system-x86_64 -S -s -serial none -nographic hello-x86_64-qemu |
这也是在启动镜像文件,其中加了不少参数,具体含义:
-S
:在模拟器初始化完成(CPU Reset)后暂停
-s
:启动gdb调试服务
-serial none
:忽略串口输入输出
-nographic
:不启动图形化界面
CPU Reset
运行完成后采用info registers
查看寄存器状态。这里关心两个状态:
CR0=60000010
:先解释CR0是个什么玩意(毕竟我也不知道),CR0是x86架构中的控制寄存器,是控制处理器行为的一个寄存器,关键位有:关键位 位置 作用 PE(Protection Enable) 最低位 PE=1时为保护模式 PE=0时为实模式 MP(Monitor Coprocessor) 通常不用 控制是否启用监视协处理器监视 EM(Emulation) 控制x87浮点指令 TS(Task Switched) 任务切换位 ET(Extension Type) 用于标识处理器支持的浮点单元类型 NE(Number Error) 浮点异常 %cs = 0xf000
,%ip = 0xfff0
,相当于PC指针位于0xfff0
这里还是得解释:
CS
寄存器标识的是代码段寄存器,根据内存分段式存储的原理,这里存储的代码的起始地址,那么我们猜一猜也能猜出这个IP
(instruction point)实际上代表的就是偏移量,因此能够算出实际的PC=CS+IP
(此处需要做一个逻辑扩展,当然算数扩展也无所谓),死去的组成原理和操作系统疯狂攻击
Firmware:加载Master Boot Record
下面会相对亲切一点,这就是操作系统启动的过程
- CPU从一个特定主存地址(BIOS中断向量,这个东西就是我们上面刚才算出的PC)开始,取指令,执行ROM中的引导程序,这里会进行硬件自检
- 读入主引导记录(Master Boot Record),执行磁盘引导程序,扫描分区表
- 从主分区(活动分区)读入分区引导记录,执行程序
- 从根目录下找到操作系统初始化程序并执行
Boot Loader:解析并加载 ELF 文件
这块属实看太不懂,总结一手就是我们通过某种程序将我们要运行的ELF文件加载到了MBR,并通过启动操作系统的类似的方法,成功将程序放入了内存
总结
这下是确实深入了解操作系统了,我们通过阅读这个在裸机上的如何实现一个C语言程序的运行,比较透彻的理解了C语言的编译链接,以及具体在启动过程中的一些细节,最重要的是,理解操作系统实际上建立在一个AbstractMachine 上的一个程序,在链接程序时表现的尤其明显。(这里有大量细节推荐去看老师的课程主页,链接贴在下面了,点查看原文可看)
参考链接
为 Bare-Metal 编程:编译、链接与加载 (jyywiki.cn)
感谢chatGPT的友情回答(麻木.jpg)