跳转至

PA1 复盘

PA1.1

  • nemu 是什么?
  • nemu 是一个模拟器,是一个在 Linux 运行上的应用程序,他模拟了红白机运行所需要的全部硬件设施。红白机运行在 nemu 上,就如同运行在真实机器上一样。

PA1.2

计算机就是一个状态机,我们可以把计算机化成两部分。

  • 一部分是由所有时序逻辑电路构成
  • 剩下是由组合逻辑构成

这样,当每个时钟周期到来的时候,计算机根据当前时序逻辑部件的状态,在组合逻辑电路的作用下,计算机并转移到下一时钟周期的新状态。

这里的在组合逻辑电路的作用下,并不是很理解。通过后面的章节再来学习吧

程序是什么?

给定一个程序, 把它放到计算机的内存中, 就相当于在状态数量为N的状态转移图中指定了一个初始状态, 程序运行的过程就是从这个初始状态开始, 每执行完一条指令, 就会进行一次确定的状态转移. 也就是说, 程序也可以看成一个状态机! 这个状态机是上文提到的大状态机(状态数量为N)的子集.

PA1.3

配置系统

  • 配置系统程序位于 nemu/tools/kconfig
  • 配置描述文件位于 nemu/Kconfig

运行 make menuconfig 时,nemu/tools/kconfig/mconf 会解析 nemu/Kconfig 中的描述文件,并以 TUI 的形式展示,退出菜单时,mconf 会将用户选择保存到 .config 文件中

之后 conf 程序会结合 Kconfig.config 文件来生成

  1. 可供 c 语言引用的宏定义文件
  2. 可供 Makefile 引用的 宏定义文件

Makefile

如何了解一个项目

  • 静态分析,看源码
  • 动态分析,看动态过程

make 有一些选项可以打印出项目构建的一些命令。配合正则表达式过滤,可以很好的了解一个项目的 运行过程

正则表达式

在命令行里查找文件名或者文件内容时,直接使用 rg 和 fd 可以直接模糊查找,避免使用正则表达式

有时候需要进行精确的正则替换,这个时候就需要使用正则。

常用正则表达式

  • ? 0 或者 一个 atom
  • . 任一一个 atom
  • * 重复前面的 atom 0个或者多个
  • + 重复前面的 atom 1个或者多个
  • .*? 非贪婪匹配
  • .\{-} vim 下的非贪婪匹配
  • ^ 行首
  • $ 行尾
  • \b \b 一般的 word boundary
  • \< \> vim 下 word boundary

这里不得不提到 vim 中的 :s 命令,和命令行里的 sed,基本一致

:%s/ / # 全文(%表示全文)查找替换
:%s/ /g # 全文贪婪替换
# 如果要替换的字符串中包含 /, 也可以使用 + 作为分隔符
:%s+ +g

monitor

  • 宏是如何展开的
  • 为什么 init_monitor 全是函数
    • 可以使 init_monitor 的实现更加精简,有着不言自明的效果,函数名本身就可以作为注释的效果

究竟要执行多久?

cmd_c()函数中, 调用cpu_exec()的时候传入了参数-1, 你知道这是什么意思吗?

答: cpu_exec 接受的参数是执行资料的条数,它的类型是 unsigned int. 将 -1 赋值给一个 unsigned int, 实际上就是将执行指令的条数置为最大正数, 让 nemu 一直执行下去。

理解指针的自增

int* i; i++ <=> ((int)i + sizeof (int)), 读作让指针指向下一个存储单元。

谁来指示程序的结束

通过使用 strace ./test 可以发现,在执行 test 可执行文件时,会有一条 execv(./test, argc, argv) 的系统调用。execv 系统调用会调用 __libc_main 这个函数,int result = __libc_main(argc, argv), 之后 exit(result)

优雅的退出

原因是需要在 cmd_q 中设置 nemu 的 state 为 quit

getopt_long 处理 --, getopt 处理 -

PA1.4

要怎么测试呢

  • 单步执行

使用 strtok + strtol 解析参数,然后传给 cpu_exec 就好

  • 打印寄存器

在 reg.c 遍历一遍寄存器就好,gpr(i) 获取寄存器值

  • 扫描内存

读取两个参数就好,注意使用 vaddr_read 读取

PA1.5 && PA1.6

为什么 printf 输出要换行

因为换行可以起到刷新缓冲器的作用,这样可以保证程序在死循环之前,把内容输出

check_parent.. 思路

  • 首先检查首尾字符
  • 通过栈这个数据结构来检查括号时候合法
  • 在循环的过程中,还需要保证栈不为空(直到最后一个字符)

最后栈为空就是合法的

如何找主运算符

还是用栈检测括号,栈为空说明在括号外,此时应判断当前元素是否为算术运算符,并且保留优先级最低的。

如何实现负数

向前看一个元素,如果前面一个元素是 运算符类型或者减号是第一个字符,那么此时 - 应该解析为 负号

递归求值

如何进行测试

popen 会返回一个 fp pointer,我们可以通过fp来获取 c 语言的打印结果

mannual: Conversely, reading from the stream reads the command's standard output, and the com‐ mand's standard input is the same as that of the process that called popen().

如何保证表达式进行无符号运算? 给数字后添加 u。 如何随机插入空格? 在数字前/后面随机生成空格就好了。 如何生成长表达式, 同时不会使buf溢出? 生成表达式的时候,传入 depth 参数,控制 gen_expr 的递归深度 如何过滤求值过程中有除0行为的表达式? 在编译时启用 -Wall -Werror 让编译器自动过滤

static 含义

表示这个变量作用域是当前文件,而非全局。 用在此处表示,不希望在外部引用这两个变量

在同一时刻触发两个以上的监视点也是有可能的, 你可以自由决定如何处理这些特殊情况, 我们对此不作硬性规定.

这里我直接 break 了,只处理第一个

如何实现监视点

如何测试监视点

  • 首先写一个 test_bench 来静态测试观测点 (测试链表结构时候有效)
  • 之后是测试 cpu 是不是真的可以检测到 观测点。
  • 因为 t0 寄存器的值是会变的,观测它就好了。

bug recording

  • clangd: pp file not found.

如果需要在多处路径查找 header file, 那么 compile_flags.txt 需要写成

-I
xxx/
-I
xxx/
-I 后只能指定一个路径

后面发现可以通过 bear 这个工具自动生成 compile_commands.json, 完全不需要手动写 compile_flags.txt

  • nvim clangd 插件不能够识别宏定义是否有效,并且以特殊格式显示
  • semanticTokens.enable: True

扫描地址的时候触发 segment fault - 需要使用 vaddr_read 来模拟内存的读写。使用 *p 的方式读取的宿主机的内存单元内容,并没有权限读取。

索引自增运算应该放在一个逻辑块的最后,不然在一个逻辑块里会 会出现两个不同的下标,这通常不是预期行为。

swicth case 里的语句记得 break;

颜色主题调整

评论区~