这是 rCore Tutorial Book 第一章「应用程序与基本执行环境」的练习。
本文首发于 blog.skywt.cn,建议查看原文以获得更好的阅读体验。
课后练习
编程题
Program 1
1. 实现一个 linux 应用程序 A,显示当前目录下的文件名。
#include <dirent.h>
#include <stdio.h>
int main() {
DIR *dir = opendir(".");
struct dirent *entry;
while ((entry = readdir(dir))) {
printf("%s\n", entry->d_name);
}
return 0;
}
相关库的用法:
- dirent.h 这个库中,定义了
DIR
数据类型,表示一个目录流。同时还定义了一个叫做dirent
的数据类型,是一个结构体,其中一个成员是char d_name[]
,即目录的名称。 - opendir 函数打开一个目录流,返回的正是
DIR
对象。 - readdir 函数以一个
DIR
对象为参数,返回一个dirent
类型结构体,它表示传入的DIR
指针指向的条目;同时,会将指针指向下一个条目。
Program 2
2. 实现一个 Linux 应用程序 B,能打印出调用栈链信息。
(前置知识:C 语言中内联汇编语句 asm 的用法)
这里在 x86_64 环境下运行。我们直接内联汇编命令,将栈底指针寄存器 rbp 取出来,并不断根据寄存器的内容追溯上一个栈帧的 rbp。(注意我们是 64 位环境,所以最好使用 64 位的 rbp 而不是 ebp)
需要注意的是,根据实践,第一个栈帧的 rbp 指向的是 0x1 而不是 0x0。while 循环何时停止需要根据此判断。
编写输出栈信息的函数如下:
void print_call_stack_info() {
unsigned long long stack_ptr;
asm ("mov %%rbp, %0" : "=r" (stack_ptr));
printf("== BEGIN CALL STACK INFO\n");
while (stack_ptr != 0x1) {
printf(" == %p\n", (void*)stack_ptr);
stack_ptr = *((unsigned long long*)stack_ptr);
}
printf("== END CALL STACK INFO\n");
}
递归调用的函数能让我们最直观地体会到栈的变化。编写好如上函数之后,我们来编写一个递归计算阶乘的函数,并让程序计算 5 的阶乘:
int fact(int x){
printf("Calling fact(%d)\n", x);
print_call_stack_info();
if (x == 0) return 1;
return fact(x-1) * x;
}
int main() {
print_call_stack_info();
printf("5! = %d\n", fact(5));
return 0;
}
编译运行,程序会在每次调用 fact
时输出栈信息。可以看到在 fact(0)
中调用 print_call_stack_info()
有 8 行输出(也就是有 8 个栈帧),在 fact(5)
中则是 3 行输出(3 个栈帧)。这表示从 fact(5)
递归调用到 fact(0)
,创建了 5 个栈帧。每个栈帧的大小是 0x20
。