gem-universe / blog

0 stars 0 forks source link

[操作系统]2. 应用视角的操作系统 #2

Open supergem3000 opened 7 months ago

supergem3000 commented 7 months ago

操作系统:设计与实现 (2023 春季学期) (jyywiki.cn)

构建最小的Hello World

// minimal.c
int main() {
}

生成目标文件gcc -c minimal.c 查看二进制文件 objdump -d minimal.o


minimal.o:     文件格式 elf64-x86-64

Disassembly of section .text:

0000000000000000

: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: b8 00 00 00 00 mov $0x0,%eax 9: 5d pop %rbp a: c3 ret

手动进行链接 `ld minimal.o -e main`,得到最小的a.out,运行时发生了Segmentation Fault。

┌────────────────────────────────────────────────────────┐ │ 0x401000

push %rbp │ │ 0x401001 <main+1> mov %rsp,%rbp │ │ 0x401004 <main+4> mov $0x0,%eax │ │ 0x401009 <main+9> pop %rbp │ │ > 0x40100a <main+10> ret │ │ 0x40100b add %al,(%rax) │ │ 0x40100d add %al,(%rax) │ │ 0x40100f add %al,(%rax) │ └────────────────────────────────────────────────────────┘ native process 863 In: main L?? PC: 0x40100a (gdb) si 0x0000000000401001 in main () 0x0000000000401004 in main () 0x0000000000401009 in main () 0x000000000040100a in main () (gdb) p $rsp $1 = (void *) 0x7fffffffdd20 (gdb) x $rsp 0x7fffffffdd20: 0x00000001 (gdb)

使用gdb调试查看,执行到ret指令时,把0x00000001地址给到pc,非法地址。
那么如何将程序正确的停下来?这时需要一条特殊的指令:syscall。

执行syscall,操作系统接管程序
- 程序把控制权完全交给操作系统
- 操作系统可以改变程序状态甚至终止程序
因此写一个最小的Hello World如下:

include <sys/syscall.h>

.globl _start _start: movq $SYS_write, %rax // write( movq $1, %rdi // fd=1, movq $st, %rsi // buf=st, movq $(ed - st), %rdx // count=ed-st syscall // );

movq $SYS_exit, %rax // exit( movq $1, %rdi // status=1 syscall // );

st: .ascii "\033[01;31mHello, OS World\033[0m\n" ed:

# 理解高级语言程序
汉诺塔程序
```c
void hanoi(int n, char from, char to, char via) {
  if (n == 1) {
    printf("%c -> %c\n", from, to);
  } else {
    hanoi(n - 1, from, via, to);
    hanoi(1,     from, to,  via);
    hanoi(n - 1, via,  to,  from);
  }
}

怎么写一个非递归的汉诺塔?

typedef struct {
  int pc, n;
  char from, to, via;
} Frame;

#define call(...) ({ *(++top) = (Frame) { .pc = 0, __VA_ARGS__ }; })
#define ret()     ({ top--; })
#define goto(loc) ({ f->pc = (loc) - 1; })

void hanoi(int n, char from, char to, char via) {
  Frame stk[64], *top = stk - 1;
  call(n, from, to, via);
  for (Frame *f; (f = top) >= stk; f->pc++) {
    n = f->n; from = f->from; to = f->to; via = f->via;
    switch (f->pc) {
      case 0: if (n == 1) { printf("%c -> %c\n", from, to); goto(4); } break;
      case 1: call(n - 1, from, via, to);   break;
      case 2: call(    1, from, to,  via);  break;
      case 3: call(n - 1, via,  to,  from); break;
      case 4: ret();                        break;
      default: assert(0);
    }
  }
}

编译器做的其实也是把high level代码翻译成指令的行为。

理解编译器

有两种状态机:

  • 高级语言代码 .c,状态:栈、全局变量;状态迁移;语句执行
  • 汇编指令序列 .s,状态:(内存M,寄存器R);状态迁移;指令执行
  • 编译器是二者之间的桥梁:.s=compile(.c) 编译正确性:.c执行中所有外部观测者可见的行为,必须在.s中保持一致。在此前提下,任何翻译都是合法的。

    操作系统上的软件

    与日常使用的文件,没有本质区别,操作系统提供API打开、读取、改写。 可用xxd查看可执行文件。

打开程序的执行:Trace(追踪)。 重要的工具:strace System call trace

execve("./a.out", ["./a.out"], 0x7ffc30b64060 /* 30 vars */) = 0
write(1, "\33[01;31mHello, OS World\33[0m\n", 28Hello, OS World) = 28
exit(1)                                 = ?
+++ exited with 1 +++

所有的软件其实都是计算、执行系统调用……