第2讲 应用视角的操作系统
主要内容
- 什么是软件(程序)
- 操作系统上的{最小/一般/图像}程序
- 什么是编译器?编译器把一段代码翻译成什么样的指令序列才算正确?
最小的程序
HelloWorld
gcc a.c --verbose
可以查看所有编译选项
gcc a.c -static -Wl,--verbose
查看所有链接选项
走一遍编译链接流程
用gdb调试汇编代码
gdb layout asm
syscall 系统调用
改变进程状态,创建进程或销毁自己
当年在写汇编的时候,发现只去做加减,做运算是远远不够的,你不可能无限循环下去,你写出的绝妙的汇编矩阵乘法,如果没有输入输出,有什么用? 当时就有一个想法,能不能不用操作系统,直接在cpu上运行asm代码?当然可以,但有什么用?没有输入输出,你只能看到一个黑盒子里面不知道发生了什么。 所以必须要有操作系统,就比如用INT 21H 中断退出程序。
gcc -c
只编译不链接
最小的c代码是可以运行的
|
|
链接警告,定义成_start避免警告
哈哈居然可以不以main函数为开头,见到c语言代码必须有main函数是不是可以判错了
|
|
改成这个 ld os_litter.o -e main 指定入口
可以链接,运行失败
|
|
加一个while(1)就可以运行了
|
|
这是一个超级小的a.out
观察此状态机的运行的工具:gdb
starti可以帮助我们从第一条指令开始执行程序
|
|
|
|
没有while
的void _start(){}
错位的地方在retq
原因 返回的地址是非法的0x1?
解决异常退出
有什么办法让程序停下来?
- 没有停机指令 解决方法:增加系统调用42
系统调用 在syscall发生的时候,把程序完全交给操作系统
|
|
这就没有问题了,正常返回了
|
|
syscall的实现在libc里,不方便直接链接
|
|
这里能看到callq
jyy为我们准备了最小的汇编代码,我哭死
|
|
接下来见证奇迹
|
|
甚至也可以用gdb看执行过程
|
|
操作系统中的程序
你理解程序吗? 如果你能写一个c语言的解释器,那你就完全理解了高级语言
尝试写一个非递归的hanio吧,用基础结构模拟函数调用和递归
来看看chatgpt写的例子
|
|
jyy写的非递归的hanio
|
|
理解是函数调用,返回。
简单的c语言的状态机模型(语义)
c语言程序是状态机
状态栈帧的列表+全局变量
初始状态=main(argc,argv)
迁移=执行top stack frame PC++
函数调用时是什么?
栈帧oush stack frame
创建新栈帧
函数返回pop stack frame
理解编译器
什么是正确的编译
两种状态机的翻译
外部观察需要完全一致
除去不可优化的部分都给它优化了 inline assembly也可以参与优化
未来的编译器
根据语义优化,把冒泡优化成快排
-
semantic-based compilation
-
AI-based rewriting
扩展阅读:
An executable formal semantics of C with applications (POPL'12)
ComCert C verified compiler and a paper(POPL'06,Most Influential Paper Award)
Copy-and-patch compilation (OOPSLA'21 Distinguished Paper)
PL的领域有一种倾向:用数学化的语言定义和理解一切
背后的直觉依然是System/software
和minimal.S没有本质区别:程序=计算->syscall
操作系统收编(管理)了所有的硬件/软件资源
- 只能用操作系统允许的方式访问操作系统的对象
|
|
|
|
常见的应用程序
Core Utilities
系统/工具程序
bash,binutils,apt,ip,ssh,vim,tmux,python
Hello World输出的第一条指令是什么,第一条指令在libc里
main()之前发生了什么
ld-linux-x86-64.so 加载了libc
之后libc完成了自己的初始化
- RTFM: libc startup on Hurd
- main() 的开始/结束并不是整个程序的开始/结束
|
|
在main()执行前/后执行指令
为什么是ld-linux-x86-64.so而不是其他的.so
elf里写的是ld-linux-x86-64.so
strace能看到所有的系统调用
|
|
本质上所有的程序和 Hello World 类似
程序 = 状态机 = 计算 → syscall → 计算 →
- 被操作系统加载
通过另一个进程执行 execve 设置为初始状态
- 状态机执行
- 进程管理:fork, execve, exit, …
- 文件/设备管理:open, close, read, write, …
- 存储管理:mmap, brk, …
- 直到 _exit (exit_group) 退出
(初学者对这一点会感到有一点惊讶)
说好的浏览器、游戏、杀毒软件、病毒呢?都是这些 API 吗?
这些 API 就是操作系统的全部
编译器 (gcc),代表其他工具程序
- 主要的系统调用:execve, read, write
- strace -f gcc a.c (gcc 会启动其他进程)
- 可以管道给编辑器 vim -
- 编辑器里还可以 %!grep (细节/技巧)
strace -f gcc a.c |& vim -
图形界面程序 (xedit),代表其他图形界面程序 (例如 vscode)
- 主要的系统调用:poll, recvmsg, writev
- strace xedit
- 图形界面程序和 X-Window 服务器按照 X11 协议通信
- 虚拟机中的 xedit 将 X11 命令通过 ssh (X11 forwarding) 转发到 Host
jyy用一个数码管的例子,来把状态机的概念更清晰地表示了
管道yyds!
一些小tips
分屏 Windows Terminal可以用alt shift =/- 来左右/上下分屏,jyy用的是tmux
terminal可以在explorer地址栏直接输入wt打开,记得配置要选父进程
扩展阅读:
介绍一个define的用法
|
|
使用gcc -E
展开
|
|
#define t(func) func(x) func(y)
,遇到t(abs)展开以后变成 abs(x) abs(y)
##
是连接操作符
什么意思呢
|
|
表示x连接y
|
|