今天开始开启了人生新阶段,从学生身份转变为社会人儿了。

也从 3 年的 CVer 即将变成 Coder 了。

3 年的 CV 生涯可以说是毫无建树,希望在未来的两年能够有所收获,成为一名工程师。

码农界流传着一种说法,想要获得长足的发展,基础一定要牢固。所以我决定花半年左右的时间学习《操作系统》这门课程,至于为什么是操作系统,因为 jyywiki.cn 的教程制作精良,不学有些可惜了。

本系列的博客主要为 jyywiki.cn 的示例代码作笔记,以便于加深印象。

绪论

Demo: 数学概念的探索与发现

# Life is short; you need Python.
import z3
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt

x = sp.symbols('x')

def plot(f, points, draw_label=True, draw_points=True):
    """Plot a sympy symbolic polynomial f."""

    xmin = min([x_ for x_, _ in points], default=-1) - 0.1
    xmax = max([x_ for x_, _ in points], default=1) + 0.1

    xs = np.arange(xmin, xmax, (xmax - xmin) / 100)
    ys = [f.subs(x, x_) for x_ in xs]

    plt.grid(True)
    plt.plot(xs, ys)
    if draw_points:
        plt.scatter(
            [x_ for x_, y_ in points],
            [y_ for x_, y_ in points],
        )
    if draw_label:
        for x_, y_ in points:
            plt.text(x_, y_, f'$({x_},{y_})$', va='bottom', ha='center')
        plt.title(f'$y = {sp.latex(f)}$')

这是一段 Python 的代码,用于画一个函数的图像,其中 z3 库可以用于解方程(就先这么认为吧), numpy 很熟悉了,现代人工智能的基石,sympy 与 numpy 不同,它是用于符号计算(而不是数值计算)的。

def interpolate(n, xs, ys):
    """Return a polynomial that passes through all given points."""
    n = max(n, len(xs), len(ys))
    if len(xs) == 0: xs = [sp.symbols(f'x{i}') for i in range(n)]
    if len(ys) == 0: ys = [sp.symbols(f'y{i}') for i in range(n)]
    vs = [sp.symbols(f'a{i}') for i in range(n)]
    power = list(range(n))
	
    # 1
    cons = [
        sum(
            v * (x_ ** k) for v, k in zip(vs, power)
        ) - y
            for x_, y in zip(xs, ys)
    ]
    # end 1
    # 2
    sol = list(sp.linsolve(cons, vs))[0]
    # end 2
    f = (sum(
        v * (x ** k) for v, k in zip(sol, power)
    ))
    return f

第二个代码比第一个更加晦涩一些,主要涉及库的使用方法。这个函数的目的是给定一些点,找到一条多项式曲线能够穿过这些点。# 1 的部分构建了一系列关于系数的线性方程组,这可以说是大学线性代数的知识,也可以说是初中的解方程组知识,# 2 的部分是求解方程组获得系数,最后 f 返回符号方程。

其实我看的也很晕。

于是调试了一下看看输出的都是什么东西。

xs = [-1, 0, 1, 2, 3]
ys = [-1, 2, 1, 4, 5]
f = interpolate(0, xs, ys)

print(cons)
# [a0 - a1 + a2 - a3 + a4 + 1, a0 - 2, a0 + a1 + a2 + a3 + a4 - 1, a0 + 2*a1 + 4*a2 + 8*a3 + 16*a4 - 4, a0 + 3*a1 + 9*a2 + 27*a3 + 81*a4 - 5]
print(sol)
# (2, -3/2, -17/12, 5/2, -7/12)
print(f)
# -7*x**4/12 + 5*x**3/2 - 17*x**2/12 - 3*x/2 + 2

Demo: 模拟数字系统

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

typedef bool wire; // Wires
typedef struct {
  bool value;
  wire *in, *out;
} reg; // Flip-flops

// Circuit constructs
#define CLOCK       for (; ; END_CYCLE)
#define NAND(X, Y)  (!((X) && (Y)))
#define NOT(X)      (NAND(X, 1))
#define AND(X, Y)   (NOT(NAND(X, Y)))
#define OR(X, Y)    (NAND(NOT(X), NOT(Y)))

// Circuit emulation helpers
#define END_CYCLE ({ end_cycle(); putchar('\n'); fflush(stdout); sleep(1); })
#define PRINT(X) printf(#X " = %d; ", X)

// Wire and register specification
wire X, Y, X1, Y1, A, B, C, D, E, F, G;
reg b1 = {.in = &X1, .out = &X};
reg b0 = {.in = &Y1, .out = &Y};

// Dump wire values at the end of each cycle
void end_cycle() {
  PRINT(A); PRINT(B); PRINT(C); PRINT(D);
  PRINT(E); PRINT(F); PRINT(G);
}

int main() {
  CLOCK {
    // 1. Wire network specification (logic gates)
    X1 = AND(NOT(X), Y);
    Y1 = NOT(OR(X, Y));
    A = D = E = NOT(Y);
    B = 1;
    C = NOT(X);
    F = Y1;
    G = X;

    // 2. Lock data in flip-flops and propagate output to wires
    b0.value = *b0.in;
    b1.value = *b1.in;
    *b0.out = b0.value;
    *b1.out = b1.value;
  }
}

这是一个模拟数码管的程序,嗯… 记得单片机课程上学过数码管啥的,但是忘没了啊。这段代码实际上就是 VHDL 的写法。

但是这里有一个我已经遗忘了的 C 语言语法,结构体初始化的时候使用.符号来指定初始化成员。

reg b1 = {.in = &X1, .out = &X};

为了验证这段程序是正确的,写一个 Python 脚本来解析一下上个程序的输出。

这里涉及到了 Unix 哲学,“管道”。

./a.out | python3 seven-seg.py  # The UNIX Philosophy

seven-seg.py 的写法如下:

import fileinput
 
DISPLAY = '''
     AAAAAAAAA
    FF       BB
    FF       BB
    FF       BB
    FF       BB
     GGGGGGGG
   EE       CC
   EE       CC
   EE       CC
   EE       CC
    DDDDDDDDD
''' 

# STFW: ANSI Escape Code
CLEAR = '\033[2J\033[1;1f'
BLOCK = {
    0: '\033[37m░\033[0m',
    1: '\033[31m█\033[0m',
}

for line in fileinput.input():
    # Load "A=0; B=1; ..." to current context
    exec(line)

    # Render the seven-segment display
    pic = DISPLAY
    for seg in set(DISPLAY):
        if seg.isalpha():
            val = globals()[seg]  # 0 or 1
            pic = pic.replace(seg, BLOCK[val])

    # Clear screen and display
    print(CLEAR + pic)

这里有一个很有趣的想法,是我没有想到的。当看到DISPLAY 的时候我大概知道是要进行替换,我的想法自然是从 a.out 输出的字符串中进行解析,获得 A - G 的值。

而这段代码却直接把 a.out 的输出直接作为 Python 代码执行了。exec(line),然后再通过 globals() 字典获得 A - G 的值。

demo2

Demo: 模拟 RISC-V 指令执行

这个Demo就更难了,作了一个模拟指令执行的程序来求解鸡兔同笼问题。

//@file_name: uncore.c
static inline bool inst_fetch(inst_t *in) {
  union {
    inst_t i;
    u32 u;
  } u;
  int r = scanf("%x", &u.u);
  *in = u.i;
  return r > 0;
}

static inline void ebreak(CPUState *cpu) {
  switch (cpu->x[10]) {
    case 1: { putchar(cpu->x[11]); break; }
    case 2: { printf("%d", cpu->x[11]); break; }
    case 3: { cpu->on = false; break; }
    default: assert(0);
  }
}
//@file_name: rvemu.c
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef uint32_t u32;

typedef struct { u32 op:7, rd:5, f3:3, rs1:5, rs2:5, f7:7; } inst_t;
typedef struct {
  u32 on, x[32];
} CPUState;

// Uncore:
//   inst_fetch - read an instruction from stdin
//   ebreak - hyper call: putchar/putd/exit
#include "uncore.c"

static inline u32 sext(u32 val, u32 n) {
  // Sign extend n-bit integer val to 32-bit
  u32 mask = ~((1 << n) - 1);
  u32 set = (val >> (n - 1)) & 1;
  u32 ret = set ? (val | mask) : val;
  return ret;
}

int main(int argc, char *argv[]) {
  CPUState cpu = {.on = 1, .x = { 0 }}; // The RESET state
  for (int i = 0; argv[i + 1] && i < 8; i++) {
    cpu.x[10 + i] = atoi(argv[i + 1]); // Set a0-a7 to arguments
  }

  inst_t in;
  while (cpu.on && inst_fetch(&in)) {
    // For each fetched instruction, execute it following the RV32I spec
    u32 op = in.op, f3 = in.f3, f7 = in.f7;
    u32 imm = sext((f7 << 5) | in.rs2, 12), shamt = in.rs2;
    u32 rd = in.rd, rs1_u = cpu.x[in.rs1], rs2_u = cpu.x[in.rs2], res = 0;

    #define __ else if // Bad syntactic sugar!
    if (op == 0b0110011 && f3 == 0b000 && f7 == 0b0000000) res = rs1_u + rs2_u;
    __ (op == 0b0110011 && f3 == 0b000 && f7 == 0b0100000) res = rs1_u - rs2_u;
    __ (op == 0b0010011 && f3 == 0b000)                    res = rs1_u + imm;
    __ (op == 0b0010011 && f3 == 0b001 && f7 == 0b0000000) res = rs1_u << shamt;
    __ (op == 0b0010011 && f3 == 0b101 && f7 == 0b0000000) res = rs1_u >> shamt;
    __ (op == 0b1110011 && f3 == 0b000 && rd == 0 && imm == 1) ebreak(&cpu);
    else assert(0);
    if (rd) cpu.x[rd] = res;
  }
}
@file_name: ji_tu.txt
00050713
00151793
40f587b3
0017d793
00200513
40f705b3
00100073
00100513
02000593
00100073
00200513
00078593
00100073
00100513
00a00593
00100073
00300513
00100073
0000006f
a.out: rvemu.c uncore.c
# RTFM: Automatic variables
	gcc -ggdb -Wall $<

run: a.out
	@echo 2 Head, 4 Feet:
	@cat ji-tu.txt | ./a.out 2 4
	@echo 2 Head, 6 Feet:
	@cat ji-tu.txt | ./a.out 2 6
	@echo 2 Head, 8 Feet:
	@cat ji-tu.txt | ./a.out 2 8
	@echo 35 Head, 94 Feet:
	@cat ji-tu.txt | ./a.out 35 94

clean:
	rm -f a.out

这个 Demo 实在看不懂,不过也有所收获,就是这段代码也有一个我遗忘的 C 语法,用 : 来指定结构体的默认值。

typedef struct { u32 op:7, rd:5, f3:3, rs1:5, rs2:5, f7:7; } inst_t;

看来我的 C 基础不行啊。