2023年春夏季开源操作系统训练营 IceCliffs
课后习题答案
编程题
- * 实现一个linux应用程序A,显示当前目录下的文件名。(用C或Rust编程)
- *** 实现一个linux应用程序B,能打印出调用栈链信息。(用C或Rust编程)
- **实现一个基于rcore/ucore tutorial的应用程序C,用sleep系统调用睡眠5秒(in rcore/ucore tutorial v3: Branch ch1)
第一题解
比较简单,直接写代码秒了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <stdio.h> #include <dirent.h>
int main() { DIR *directory; struct dirent *entry; directory = opendir("."); if (directory == NULL) { perror("无法打开目录"); return 1; } while ((entry = readdir(directory)) != NULL) { printf("%s\n", entry->d_name); } closedir(directory); return 0; }
|
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| (base) / ⌚ 11:19:19 $ ./1 opt libx32 sys boot bin dev .. proc var . sbin wget-log.3 snap lib32 1 usr swapfile patch tmp lib64 lost+found wget-log.2 home root etc lib wget-log cdrom mnt wget-log.6 .Recycle_bin wget-log.1 wget-log.5 www wget-log.4 srv media run flag
|
第二题解
第三题解
问答题
答:比如说我们开了一个应用程序A,那么最可能占用的可能是内存,其次是CPU,最后是硬盘。因为Linux有swap机制,妈的内存不够了会写swap到硬盘里,这就占用了硬盘的空间
- * 请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围。
答:这里我写了一个 hello_world.c
源码如下
1 2 3 4 5 6
| #include<stdio.h>
int main() { printf("Hello, world!\n"); return 0; }
|
然后用 readelf
来查看信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| $ readelf -h hello_world ELF 头: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 类别: ELF64 数据: 2 补码,小端序 (little endian) Version: 1 (current) OS/ABI: UNIX - System V ABI 版本: 0 类型: DYN (Position-Independent Executable file) 系统架构: Advanced Micro Devices X86-64 版本: 0x1 入口点地址: 0x1060 程序头起点: 64 (bytes into file) Start of section headers: 13984 (bytes into file) 标志: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 13 Size of section headers: 64 (bytes) Number of section headers: 31 Section header string table index: 30 节头: [号] 名称 类型 地址 偏移量 大小 全体大小 旗标 链接 信息 对齐 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000318 00000318 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.gnu.pr[...] NOTE 0000000000000338 00000338 0000000000000030 0000000000000000 A 0 0 8 [ 3] .note.gnu.bu[...] NOTE 0000000000000368 00000368 0000000000000024 0000000000000000 A 0 0 4 [ 4] .note.ABI-tag NOTE 000000000000038c 0000038c 0000000000000020 0000000000000000 A 0 0 4 [ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b0 0000000000000024 0000000000000000 A 6 0 8 [ 6] .dynsym DYNSYM 00000000000003d8 000003d8 00000000000000a8 0000000000000018 A 7 1 8 [ 7] .dynstr STRTAB 0000000000000480 00000480 000000000000008d 0000000000000000 A 0 0 1 [ 8] .gnu.version VERSYM 000000000000050e 0000050e 000000000000000e 0000000000000002 A 6 0 2 [ 9] .gnu.version_r VERNEED 0000000000000520 00000520 0000000000000030 0000000000000000 A 7 1 8 [10] .rela.dyn RELA 0000000000000550 00000550 00000000000000c0 0000000000000018 A 6 0 8 [11] .rela.plt RELA 0000000000000610 00000610 0000000000000018 0000000000000018 AI 6 24 8 [12] .init PROGBITS 0000000000001000 00001000 000000000000001b 0000000000000000 AX 0 0 4 [13] .plt PROGBITS 0000000000001020 00001020 0000000000000020 0000000000000010 AX 0 0 16 [14] .plt.got PROGBITS 0000000000001040 00001040 0000000000000010 0000000000000010 AX 0 0 16 [15] .plt.sec PROGBITS 0000000000001050 00001050 0000000000000010 0000000000000010 AX 0 0 16 [16] .text PROGBITS 0000000000001060 00001060 0000000000000107 0000000000000000 AX 0 0 16 [17] .fini PROGBITS 0000000000001168 00001168 000000000000000d 0000000000000000 AX 0 0 4 [18] .rodata PROGBITS 0000000000002000 00002000 0000000000000012 0000000000000000 A 0 0 4 [19] .eh_frame_hdr PROGBITS 0000000000002014 00002014 0000000000000034 0000000000000000 A 0 0 4 [20] .eh_frame PROGBITS 0000000000002048 00002048 00000000000000ac 0000000000000000 A 0 0 8 [21] .init_array INIT_ARRAY 0000000000003db8 00002db8 0000000000000008 0000000000000008 WA 0 0 8 [22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0 0000000000000008 0000000000000008 WA 0 0 8 [23] .dynamic DYNAMIC 0000000000003dc8 00002dc8 00000000000001f0 0000000000000010 WA 7 0 8 [24] .got PROGBITS 0000000000003fb8 00002fb8 0000000000000048 0000000000000008 WA 0 0 8 [25] .data PROGBITS 0000000000004000 00003000 0000000000000010 0000000000000000 WA 0 0 8 [26] .bss NOBITS 0000000000004010 00003010 0000000000000008 0000000000000000 WA 0 0 1 [27] .comment PROGBITS 0000000000000000 00003010 000000000000002b 0000000000000001 MS 0 0 1 [28] .symtab SYMTAB 0000000000000000 00003040 0000000000000360 0000000000000018 29 18 8 [29] .strtab STRTAB 0000000000000000 000033a0 00000000000001e1 0000000000000000 0 0 1 [30] .shstrtab STRTAB 0000000000000000 00003581 000000000000011a 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), l (large), p (processor specific)
There are no section groups in this file.
|
查看 hello_world
堆栈空间的分布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| /home/ryu1nser ⌚ 13:36:10 $ ps -aux | grep hello_world root 11403 15.0 0.0 2776 1152 pts/2 R+ 13:35 0:03 ./hello_world root 11753 0.0 0.0 12320 2432 pts/4 S+ 13:36 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox hello_world (base) /home/ryu1nser ⌚ 13:36:13 $ cat /proc/11403/maps 562c3e498000-562c3e499000 r--p 00000000 08:03 4344198 /root/project/my-rust-tutorial/practising/2/hello_world 562c3e499000-562c3e49a000 r-xp 00001000 08:03 4344198 /root/project/my-rust-tutorial/practising/2/hello_world 562c3e49a000-562c3e49b000 r--p 00002000 08:03 4344198 /root/project/my-rust-tutorial/practising/2/hello_world 562c3e49b000-562c3e49c000 r--p 00002000 08:03 4344198 /root/project/my-rust-tutorial/practising/2/hello_world 562c3e49c000-562c3e49d000 rw-p 00003000 08:03 4344198 /root/project/my-rust-tutorial/practising/2/hello_world 562c3ef4e000-562c3ef6f000 rw-p 00000000 00:00 0 [heap] 7f0b3ac00000-7f0b3ac28000 r--p 00000000 08:03 2496231 /usr/lib/x86_64-linux-gnu/libc.so.6 7f0b3ac28000-7f0b3adbd000 r-xp 00028000 08:03 2496231 /usr/lib/x86_64-linux-gnu/libc.so.6 7f0b3adbd000-7f0b3ae15000 r--p 001bd000 08:03 2496231 /usr/lib/x86_64-linux-gnu/libc.so.6 7f0b3ae15000-7f0b3ae16000 ---p 00215000 08:03 2496231 /usr/lib/x86_64-linux-gnu/libc.so.6 7f0b3ae16000-7f0b3ae1a000 r--p 00215000 08:03 2496231 /usr/lib/x86_64-linux-gnu/libc.so.6 7f0b3ae1a000-7f0b3ae1c000 rw-p 00219000 08:03 2496231 /usr/lib/x86_64-linux-gnu/libc.so.6 7f0b3ae1c000-7f0b3ae29000 rw-p 00000000 00:00 0 7f0b3af68000-7f0b3af6b000 rw-p 00000000 00:00 0 7f0b3af7f000-7f0b3af81000 rw-p 00000000 00:00 0 7f0b3af81000-7f0b3af83000 r--p 00000000 08:03 2493978 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7f0b3af83000-7f0b3afad000 r-xp 00002000 08:03 2493978 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7f0b3afad000-7f0b3afb8000 r--p 0002c000 08:03 2493978 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7f0b3afb9000-7f0b3afbb000 r--p 00037000 08:03 2493978 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7f0b3afbb000-7f0b3afbd000 rw-p 00039000 08:03 2493978 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7ffcdba8c000-7ffcdbaad000 rw-p 00000000 00:00 0 [stack] 7ffcdbb82000-7ffcdbb86000 r--p 00000000 00:00 0 [vvar] 7ffcdbb86000-7ffcdbb88000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] (base)
|
答:两者相同的点都是运行在计算机硬件上,计组课中,硬件系统和软件系统共同构成了一个完整的计算机系统,也就是说这两者前者偏向于服务用户,即为了实现特定的功能而实现,后者则偏向于管理硬件资源,例如内存管理、文件系统、设备驱动、网络连接等等
- ** 请基于QEMU模拟RISC—V的执行过程和QEMU源代码,说明RISC-V硬件加电后的几条指令在哪里?完成了哪些功能?
答:
答:SBI全称为Supervisor Binary Interface,即监管者二进制接口。它的主要功能是为上层的操作系统提供一个标准的二进制接口。通过实现SBI,操作系统内核可以与底层硬件之间进行通信和交互,而无需直接处理具体的架构细节,实现了对底层硬件的抽象。这种抽象使得操作系统内核能够更加灵活地适应不同的硬件平台,而不必对每个平台进行特定的修改。
SBI定义了一组标准的操作,包括特权指令、硬件访问、异常处理和系统调用等。通过这些操作,操作系统内核可以执行特权操作、访问硬件资源、处理异常事件,并提供系统调用接口给用户程序使用。
因此,SBI的实现确实使得操作系统可以专注于实现SBI接口的相关功能,从而更加独立于具体的硬件架构,提高了可移植性和可扩展性。
详见官方文档:https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/riscv-sbi.adoc
- ** 为了让应用程序能在计算机上执行,操作系统与编译器之间需要达成哪些协议?
答:先过一遍程序编译过程,以 hello_world.c
为例,预处理器→编译器→汇编器→链接器→二进制可执行文件,那么操作系统与编译器之间
- ** 请简要说明从QEMU模拟的RISC-V计算机加电开始运行到执行应用程序的第一条指令这个阶段的执行过程。
答:
- ** 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?
答:我们在编译的时候,编译器已经自动为我们开辟了指定的空间,所以我们不需要再去编写着一些空间了
- *** 现代的很多编译器生成的代码,默认情况下不再严格保存/恢复栈帧指针。在这个情况下,我们只要编译器提供足够的信息,也可以完成对调用栈的恢复。
我们可以手动阅读汇编代码和栈上的数据,体验一下这个过程。例如,对如下两个互相递归调用的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void flip(unsigned n) { if ((n & 1) == 0) { flip(n >> 1); } else if ((n & 1) == 1) { flap(n >> 1); } }
void flap(unsigned n) { if ((n & 1) == 0) { flip(n >> 1); } else if ((n & 1) == 1) { flap(n >> 1); } }
|
在某种编译环境下,编译器产生的代码不包括保存和恢复栈帧指针 fp
的代码。以下是 GDB 输出的本次运行的时候,这两个函数所在的地址和对应地址指令的反汇编,为了方便阅读节选了重要的控制流和栈操作(省略部分不含栈操作):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| (gdb) disassemble flap Dump of assembler code for function flap: 0x0000000000010730 <+0>: addi sp,sp,-16 // 唯一入口 0x0000000000010732 <+2>: sd ra,8(sp) ... 0x0000000000010742 <+18>: ld ra,8(sp) 0x0000000000010744 <+20>: addi sp,sp,16 0x0000000000010746 <+22>: ret // 唯一出口 ... 0x0000000000010750 <+32>: j 0x10742 <flap+18>
(gdb) disassemble flip Dump of assembler code for function flip: 0x0000000000010752 <+0>: addi sp,sp,-16 // 唯一入口 0x0000000000010754 <+2>: sd ra,8(sp) ... 0x0000000000010764 <+18>: ld ra,8(sp) 0x0000000000010766 <+20>: addi sp,sp,16 0x0000000000010768 <+22>: ret // 唯一出口 ... 0x0000000000010772 <+32>: j 0x10764 <flip+18> End of assembler dump.
|
启动这个程序,在运行的时候的某个状态将其打断。此时的 pc
, sp
, ra
寄存器的值如下所示。此外,下面还给出了栈顶的部分内容。(为阅读方便,栈上的一些未初始化的垃圾数据用 ???
代替。)
1 2 3 4 5 6 7 8 9 10 11 12 13
| (gdb) p $pc $1 = (void (*)()) 0x10752 <flip>
(gdb) p $sp $2 = (void *) 0x40007f1310
(gdb) p $ra $3 = (void (*)()) 0x10742 <flap+18>
(gdb) x/6a $sp 0x40007f1310: ??? 0x10750 <flap+32> 0x40007f1320: ??? 0x10772 <flip+32> 0x40007f1330: ??? 0x10764 <flip+18>
|
根据给出这些信息,调试器可以如何复原出最顶层的几个调用栈信息?假设调试器可以理解编译器生成的汇编代码 1 。
答:
实验练习
彩色化LOG
完成ch1
Reference