MIT6828 学习笔记 001 (lec 1,2)

MIT6828 学习笔记 001 (lec 1,2)

RayAlto OP

1. 入门

看看 MIT 的计分标准: 70% 实验、 20% 提问实验内容、 10% 作业&讨论,太散漫辣!学生就应该每天坐在教室/自习室里学习来应付期末考试,缺课=作业都不是自己写的(数据库何蕾老师教我的),然后写论文时先不说内容,首先一定要引用大量文献(肥工毕业设计的老师教我的),这一点肥工做得就很好,赢。

示例代码都用 exit(0) 而不是 return 0; 不知道有什么原因,难道为了强调系统调用?

答:因为 Xv6 并不“标准”,一般 Linux 程序的起点不是 main 而是像这样的:

1
2
3
4
5
void start(void) {
/* ... */
int r = main(argc, argv, envp);
exit(r);
}

起初 Xv6 下程序的起点就是 main ,然后 return 0; 时程序想 pop 发现没得 pop 就 segmentation fault 了。但在 commit 76ad8e974fefb4588b0a318f6502fde6bd730097 之后就不会 segfault 了,方法也差不多( git diff 可以看到):

1
2
3
4
5
6
// user/ulib.c 添加了这个函数
void _main() {
extern int main();
main();
exit(0);
}
1
2
# Makefile ld -e 指定了 _main 而不是 main
$(LD) $(LDFLAGS) -N -e _main -Ttext 0 -o $@ $^

1.1. 软件调用了系统调用(如 open )时发生了什么

  1. 硬件保存一些用户的寄存器
  2. 硬件提升权限级别
  3. 硬件跳转到内核里一个已知的入口点
  4. 执行内核里的代码
  5. 内核调用这个系统调用的实现
    1. sys_open() 在文件系统中寻找这个文件名
    2. 可能会 wait 磁盘
    3. 更新内核数据结构(文件块缓存, fd 表)
  6. 恢复用户的寄存器
  7. 降低权限级别
  8. 在用户代码系统调用处返回

大体的描述,后面会深入

1.2. Unix Shell

  • 虽然现在 Unix 有 GUI 、 Web 服务器之类的来进行工作,但 Unix 最初专注于通过 shell 实现分时系统。
  • shell 在接收指令后会 fork, exec, wait 然后等待下一个指令,而在一个指令后面加上 & 表示 shell 在 fork, exec 后不用 wait
  • 后面的 lab “copy-on-write” 会解决 fork 复制, exec 又覆盖看起来可能产生的浪费
  • 程序只需要知道 fd 0 (stdin), fd 1 (stdout) ,不用关心具体从哪里输入,往哪里输出, shell 会管理这些

1.3. 有趣的问题

不得不说这个思维真的让我挺震惊的,下面找几个我觉得精彩的,然而肥工老师的学生不需要这些花里胡哨,只要背作业答案过期末、引用各种国内牛人的文献写论文,肥又赢!

  • 为什么有时这些 IO 是抽象的、而有时又不是
  • 为什么内核规范了文件系统,而不允许程序自行定义磁盘的使用方式
  • 为什么文件是字节流,而不是磁盘块或格式化的记录

2. Xv6 的 C 语言

2.1. Xv6 C 程序的内存分布

  • text: 代码、只读数据
  • data: 全局变量
  • stack: 函数的局部变量
  • heap: 用 sbrk, malloc/ free 动态分配的内存

局部变量没有默认值, clang-tidy 也会提醒

2.2. 编译

Makefile 定义编译过程、 gcc 把代码编译成 .old.o 文件链接成为一个可执行文件。

ulibc.o 是 Xv6 的最小 C 库。

可执行文件的格式分为几个部分,包括:文本(代码)、初始化了的数据、符号表、调试信息等等。

2.3. 反编译

1
objdump -S a.out

目前没弄明白怎么在 Xv6 下编译代码,所以上面的是我的 Linux 上的反编译方法

答:在我的物理机器 (X86-64 Linux) 上交叉编译出 RISC-V 的程序 (RISC-V Linux) ,放进 Xv6 里运行

2.4. 查看符号表

1
nm -n a.out

这也是我的 Linux 上查看符号的方法, -n 表示按数字顺序排序

2.5. C

  • static 使一个变量只能在所在文件里可视,但在这个文件里它是全局变量