NJUOS-2-操作系统上的程序

本文最后更新于:1 年前

JYY第二课:操作系统上的程序

JYY是我的男神!!!

状态机与数字电路

image-20221208112807148

状态机的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>

#define REGS_FOREACH(_) _(X) _(Y)
#define RUN_LOGIC X1 = !X && Y; Y1 = !X && !Y;
#define DEFINE(X) static int X, X##1;
#define UPDATE(X) X = X##1;
#define PRINT(X) printf(#X " = %d; ", X);

int main(){
// 定义好所有的寄存器(X, Y)
REGS_FOREACH(DEFINE);
while(1) { // clock
// 运行组合逻辑 -> 打印出来 -> 更新回去
RUN_LOGIC;
REGS_FOREACH(PRINT);
REGS_FOREACH(UPDATE);
putchar('\n'); sleep(1);
}
}

REGS_FOREACH这个是一个小的语法特性,查看小技巧昂!!!

image-20221208114700366

上面就类似于一个晶体管 -> 如果用管道连到对应的Python程序,甚至可以图形化界面类似于晶体管输出,就很方便昂!

image-20221208114816053

什么是程序(程序视角)

  • 数字系统就是状态机,程序运行在数字系统中 -> 程序也是状态机。
  • C语言也是状态机:
    • 状态 = 栈 + 堆
    • 初始状态 = main的第一条语句
    • 迁移 = 执行一条简单的语句
      • 任何C程序都可以改写成“非复合语句”的C代码
      • 有这种工具

任何真正的理解都应该落实到可以执行的代码

  • 函数调用/返回无非就是stack frame,栈的改变嘛

image-20221208115603038

对于每一种语句,都写出状态机是如何变动的行为 -> 拨开递归,自己去迭代创建stack frame,弹出stack frame等

image-20221208115655601

image-20221208120220663

程序就是一个状态机,每一步都是状态,我们完全可以根据程序的语句,操纵程序的行为 -> 从一个状态转变为另外一个状态。

可执行文件视角

image-20221208120551131

本质也是状态机啊,更加底层昂!!!随机数怎么办呢? -> 状态机有多个分叉:

image-20221208121002035

程序理解:状态转移!死循环就是状态转移嘛…又回到当初了呢!

image-20221208121304036

  • 操作系统上的程序:
    • 绝大多数的指令只能计算 -> 状态机状态转移
    • 特殊的指令syscall -> 将当前的所有状态(M, R)无条件交给操作系统 -> 操作系统可以对于当前状态做任何事,接替程序执行,想杀了就杀了,返回一个全新的(M’, R’),操作系统帮助我们更改状态机,system call就是程序和操作系统交互的接口。
    • 程序 = 计算 + syscall(操作系统调用)

image-20221208121654536

构造一个最小的hello world程序

1
2
3
4
5
// 这个直接gcc非常大昂!!!还有很多动态链接库... -> 最小 -> 直接调用操作系统!!!
int main()
{
printf("hello world\n");
}
1
2
3
4
5
6
7
8
9
10
// gcc -c main.cpp && objdump -d main.o
0000000000000000 <_main>:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 48 8d 3d 0b 00 00 00 leaq 11(%rip), %rdi ## 0x16 <_main+0x16>
b: b0 00 movb $0, %al
d: e8 00 00 00 00 callq 0x12 <_main+0x12>
12: 31 c0 xorl %eax, %eax
14: 5d popq %rbp
15: c3 retq
  • 直接编译可以看到,其实转换成的代码不长orz -> 使用ld命令去进行链接:

gcc -c main.c + ld

1
2
3
4
5
alex ~/my_code/c++/CLionProject  $ gcc -c main.cpp && ld main.o    
Undefined symbols for architecture x86_64:
"_printf", referenced from:
_main in main.o
ld: symbol(s) not found for architecture x86_64

gdb找不到_printf… -> 因为没有环境变量之类的东西。gdb调试之后,发现可以被操作系统处理,但是出错了…,程序while(1)都可以正常执行。

image-20221208155113849

报错,错误退出。GDB去调查,到底哪里的数据 or 堆栈寄了 -> 发现某个寄存器,访问不到哇orz。异常退出 -> 正常退出。syscall:

image-20221208123048830

1
2
3
4
5
int main()
{
// 可以追踪代码执行,看看操作系统是如何执行程序退出的(自己退出不了昂!!!)
syscall(SYS_exit, 42);
}

image-20221208123155298

image-20221208164058745

追踪发现:这里执行syscall的时候,无非是给各种寄存器赋值(M, R)。然后调用syscall,让系统进行处理。

image-20221208164915639

如何在程序的两个视角之间切换

汇编 和 C的视角切换

C视角(Source) -> 汇编视角(Assembly code)

C = compile(S)

  • 什么是编译器:

image-20221208165846070

  • 程序的状态机中,可能包含,可以优化的部分,和不能优化的部分。
  • 正确的编译:S和C的可观测行为严格一致!!!C对应的状态机 -> 汇编对应的状态机,C代码中不可优化的部分全部被正确翻译到汇编上!
  • 因此就有优化空间,怎样去理解这些semantics,并进行编译的优化,正确,精准且快速的编译!!!

  • 编译器优化例子:

image-20221208170440633

image-20221208170527319

即使上面这种情况,编译器依然十分聪明的进行了优化。。。

  • 不可优化?

image-20221208170702441

image-20221208170743024

程序合成:编译器先去理解我的代码的意思,再去等价生成小而精简的汇编代码(or other codes)

image-20221208171256289

  • 程序本质就是:计算 + syscall
  • 应用眼睛里看到的操作系统,就是system call,也就是API。因此集中管理了所有的软硬件的资源。 -> 操作系统就是一个系统调用。

image-20221208171603887

等价于在问,操作系统给这个进程/程序的初始状态是什么?

image-20221208171659681

image-20221208171750335

  • strace工具:

image-20221208172043992

操作系统不神秘,strace可以看任意的状态昂!!!

image-20221208172146723

image-20221208172530691

小技巧

  1. REGS_FOREACH,参见上面,可以定义一个_函数,对于某些变量做操作!!!

  2. Gcc -E xxx.c -> 把上面的宏展开,变成简单的简洁的代码:

image-20221208114419403

  1. C语言中的#include的语意本质就是复制粘贴,把我们引入的内容直接粘贴到对应的文件中。
  2. gcc -c verbose可以看到诊断结果,gcc -static可以静态编译,不需要动态链接库 -> 程序就会大一些。gcc -c -> compile only,这个时候就会小很多昂!!!
  3. compile only出来的结果,可以通过ld去尝试直接链接这段代码 -> 可能一些库里支持的操作(包含操作系统调用),由于纯编译,导致ld去链接的时候,用不了!!!
  4. GDB很好用!!!汇编代码和C代码,都可以进行打断点处理!!!!! -> 可以去看看文档昂!!!
  • 这里的一点小测试:
1
2
3
4
5
6
#include <stdio.h>

int main(){
// 定义好所有的寄存器(X, Y)
printf("hello world\n");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// gcc -c 可以编译某个文件,objdump可以将main.o反汇编
alex ~/my_code/c++/CLionProject $ gcc -c main.cpp && objdump -d main.o

main.o: file format mach-o 64-bit x86-64

Disassembly of section __TEXT,__text:

0000000000000000 <_main>:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 48 8d 3d 0b 00 00 00 leaq 11(%rip), %rdi ## 0x16 <_main+0x16>
b: b0 00 movb $0, %al
d: e8 00 00 00 00 callq 0x12 <_main+0x12>
12: 31 c0 xorl %eax, %eax
14: 5d popq %rbp
15: c3 retq

  1. man sys call -> 可以看到各种sys call调用的环境,变量,以及使用昂!对照着手册,才能正常使用昂!
  2. strace工具可以看到操作系统上,某个程序执行的所有的调用。

References

  1. vedio link: https://www.bilibili.com/video/BV12L4y1379V/?spm_id_from=333.999.0.0&vd_source=ff957cd8fbaeb55d52afc75fbcc87dfd

  2. objdump link: https://ivanzz1001.github.io/records/post/linux/2018/04/09/linux-objdump

  3. GCC & GDB(非常多的宝藏!!!): https://sourceware.org/


NJUOS-2-操作系统上的程序
https://alexanderliu-creator.github.io/2022/12/08/njuos-2-cao-zuo-xi-tong-shang-de-cheng-xu/
作者
Alexander Liu
发布于
2022年12月8日
许可协议