Overview

复习:操作系统

  • 应用视角 (设计): 一组对象 (进程/文件/…) + API
  • 硬件视角 (实现): 一个 C 程序

本次课回答的问题

  • Q: 到底什么是 “程序”?

本次课主要内容

  • 程序的状态机模型 (和编译器)
  • 操作系统上的 {最小/一般/图形} 程序

    数字电路与状态机

    数字电路与状态机

    数字逻辑电路

  • 状态 = 寄存器保存的值 (flip-flop)

  • 初始状态 = RESET (implementation dependent)
  • 迁移 = 组合逻辑电路计算寄存器下一周期的值

前置课程: 计算机系统基础 操作系统与数字电路的等价性

  • 计数器
  • 计数器跳转: 状态的迁移为一个时钟周期
  • 通过逻辑公式, 算出下一个计数器

例子:
X′=¬X∧Y
Y′=¬X∧¬Y

数字逻辑电路:模拟器

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. // 定义2个寄存器:X, Y
  4. #define REGS_FOREACH(_) _(X) _(Y)
  5. // 组合\运算逻辑
  6. #define RUN_LOGIC X1 = !X && Y; \
  7. Y1 = !X && !Y;
  8. // 定义一个寄存器
  9. #define DEFINE(X) static int X, X##1;
  10. // 更新一个寄存器
  11. #define UPDATE(X) X = X##1;
  12. // 打印一个寄存器
  13. #define PRINT(X) printf(#X " = %d; ", X);
  14. int main() {
  15. REGS_FOREACH(DEFINE);
  16. while (1) { // clock
  17. RUN_LOGIC;
  18. REGS_FOREACH(PRINT);
  19. REGS_FOREACH(UPDATE);
  20. putchar('\n');
  21. sleep(1);
  22. }
  23. }

#include <unistd.h>找不到头文件, cygwin有头文件, 但就是找不到

gcc -E a.c: 宏展开

  1. int main() {
  2. static int X, X1; static int Y, Y1;;
  3. while (1) {
  4. X1 = !X && Y; Y1 = !X && !Y;;
  5. printf("X" " = %d; ", X); printf("Y" " = %d; ", Y);;
  6. X = X1; Y = Y1;;
  7. putchar('\n');
  8. sleep(1);
  9. }
  10. }

更完整的实现:数码管显示

输出数码管的配置信号

  • logisim.c
  • 会编程,你就拥有全世界!
    • seven-seg.py
    • 同样的方式可以模拟任何数字系统
      • 当然,也包括计算机系统 ```c

        include

        include

define REGSFOREACH() (X) (Y)

define OUTSFOREACH() (A) (B) (C) (D) (E) (F) _(G)

define RUN_LOGIC X1 = !X && Y; \

  1. Y1 = !X && !Y; \
  2. A = (!X && !Y) || (X && !Y); \
  3. B = 1; \
  4. C = (!X && !Y) || (!X && Y); \
  5. D = (!X && !Y) || (X && !Y); \
  6. E = (!X && !Y) || (X && !Y); \
  7. F = (!X && !Y); \
  8. G = (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() { REGS_FOREACH(DEFINE); OUTS_FOREACH(DEFINE); while (1) { // clock RUN_LOGIC; OUTS_FOREACH(PRINT); REGS_FOREACH(UPDATE); putchar(‘\n’); fflush(stdout); sleep(1); } }

  1. 输出7段数码管
  2. ```shell
  3. A = 1; B = 1; C = 1; D = 1; E = 1; F = 1; G = 0;
  4. A = 0; B = 1; C = 1; D = 0; E = 0; F = 0; G = 0;
  5. A = 1; B = 1; C = 0; D = 1; E = 1; F = 0; G = 1;
  6. A = 1; B = 1; C = 1; D = 1; E = 1; F = 1; G = 0;
  7. A = 0; B = 1; C = 1; D = 0; E = 0; F = 0; G = 0;
  8. A = 1; B = 1; C = 0; D = 1; E = 1; F = 0; G = 1;
  9. A = 1; B = 1; C = 1; D = 1; E = 1; F = 1; G = 0;
  10. A = 0; B = 1; C = 1; D = 0; E = 0; F = 0; G = 0;
  11. A = 1; B = 1; C = 0; D = 1; E = 1; F = 0; G = 1;
  12. ...

加入后端处理

  1. import fileinput
  2. TEMPLATE = '''
  3. \033[2J\033[1;1f
  4. AAAAAAAAA
  5. FF BB
  6. FF BB
  7. FF BB
  8. FF BB
  9. GGGGGGGGGG
  10. EE CC
  11. EE CC
  12. EE CC
  13. EE CC
  14. DDDDDDDDD
  15. '''
  16. BLOCK = {
  17. 0: '\033[37m░\033[0m', # STFW: ANSI Escape Code
  18. 1: '\033[31m█\033[0m',
  19. }
  20. VARS = 'ABCDEFG'
  21. for v in VARS:
  22. globals()[v] = 0
  23. stdin = fileinput.input()
  24. while True:
  25. exec(stdin.readline())
  26. pic = TEMPLATE
  27. for v in VARS:
  28. pic = pic.replace(v, BLOCK[globals()[v]]) # 'A' -> BLOCK[A], ...
  29. print(pic)
  1. # python3 seven-seg.py
  2. A = 1
  3. █████████
  4. ░░ ░░
  5. ░░ ░░
  6. ░░ ░░
  7. ░░ ░░
  8. ░░░░░░░░░░
  9. ░░ ░░
  10. ░░ ░░
  11. ░░ ░░
  12. ░░ ░░
  13. ░░░░░░░░░

image.png
./a.out | python3 seven-seg.py
动画.gif
1.2 操作系统上的程序 - 图3


你还体验了 UNIX 哲学

  • Make each program do one thing well
  • Expect the output of every program to become the input to another

什么是程序 (源代码视角)

什么是程序?

Hmm….


你需要《程序设计语言的形式语义

  • by 梁红瑾 🎩
  • λ-calculus, operational semantics, Hoare logic, separation logic
  • 入围 “你在南京大学上过最牛的课是什么?” 知乎高票答案
    • 当然,我也厚颜无耻地入围了


不,你不需要

程序就是状态机 (你在 gdb 里看到的)

  • 试试程序吧 hanoi-r.c
    1. void hanoi(int n, char from, char to, char via) {
    2. if (n == 1) printf("%c -> %c\n", from, to);
    3. else {
    4. hanoi(n - 1, from, via, to);
    5. hanoi(1, from, to, via);
    6. hanoi(n - 1, via, to, from);
    7. }
    8. return;
    9. }

    理解这个递归并不容易

  1. #include <stdio.h>
  2. #include "hanoi-r.c"
  3. int main()
  4. {
  5. hanoi(3, 'A', 'B', 'C');
  6. return 0;
  7. }

GDB运行 >所指向的亮条就是PC

  1. > gcc -g main.c -o main
  2. > gdb -tui main
  3. (gdb) start

image.png
(gdb) s
image.png
info frame: 查看栈帧信息
image.png

递归

  1. (gdb) s
  2. (gdb) s
  3. (gdb) info frame

image.png
C 程序的状态机模型 (语义,semantics)

  • 状态 = 堆 + 栈
  • 初始状态 = main 的第一条语句
  • 迁移 = 执行一条简单语句

(这还只是 “粗浅” 的理解)

  • Talk is cheap. Show me the code. (Linus Torvalds): 任何真正的理解都应该落到可以执行的代码

    写一个C语言的解释器

C 程序的语义

C语言的状态到底是什么 函数调用是什么? 函数返回是什么? 能说明白吗?

C 程序的状态机模型 (语义,semantics)

  • 状态 = stack frame 的列表 (每个 frame 有 PC) + 全局变量
  • 初始状态 = main(argc, argv), 全局变量初始化
  • 迁移 = 执行 top stack frame PC 的语句; PC++
    • 函数调用 = push frame (frame.PC = 入口)
    • 函数返回 = pop frame

      懵逼中…

应用:将任何递归程序就地转为非递归

  • 汉诺塔难不倒你 hanoi-nr.c
  • A → B, B → A 的也难不倒你
    • 还是一样的 call(),但放入不同的 Frame ```c // 栈帧 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++) { switch (f->pc) { case 0: if (f->n == 1) { printf(“%c -> %c\n”, f->from, f->to); goto(4); } break; case 1: call(f->n - 1, f->from, f->via, f->to); break; case 2: call( 1, f->from, f->to, f->via); break; case 3: call(f->n - 1, f->via, f->to, f->from); break; case 4: ret(); break; default: assert(0); } } }

  1. ```c
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include "hanoi-nr.c"
  5. int main()
  6. {
  7. hanoi(3, 'A', 'B', 'C');
  8. return 0;
  9. }

与递归版本对比

GDB调试, 查看状态转换