第1关:第一次页故障
本关任务:系统的第一次页故障发生时:
1.当时正在执行几号进程? 正在访问的线性地址是多少?
2.该线性地址当时对应的页表项是什么?被映射到的物理地址是什么?
3.该页故障处理完后,该线性地址对应的页表项是什么?被映射到的物理地址是什么?
4.引发这次页故障的指令地址是什么?
5. main 函数中的 fork 系统调用的陷入指令的地址是多少?
由于需要在bochsdbg模式下确定进程号,所以现在gdb模式跟踪到schedule函数,然后查看当前所有进程的进程控制块地址,并确定其进程号
可见 0 号进程和 1 号进程的进程控制块地址分别是 0x1bec0 和 0xfff000 。现在查看 current 的地址:
如何跟踪到第一次页故障发生
页故障处理函数是 page_fault ,首先查询这个函数的地址,然后在 bochsdbg 中在该地址设断点,并跟踪到该断点的第一次出现即可。所以在gdb模式查看page_cault函数地址
在bochsdbg模式跟踪到page_fault函数,并查看当前进程号
可以看到是1号进程
如何知道是对哪个线性地址的访问引发了页故障
在页故障发生时,CR2 寄存器中记录了此地址,可以用 creg 命令查看。
可以看到正在访问的线性地址是多少0x402574c
观察该线性地址对应的页表项和映射到的物理地址
页表项是0x25065,所以映射到的物理地址是0x2574c
通过u命令进行反汇编,找到iret指令并跟踪到其地址
此时页故障处理完成,再次查看上面线性地址对应的页表项和映射到的物理地址
通过一步s命令就可以查看引发这次页故障的指令地址。这里需要注意的是:Page fault是fault类异常,其断点和恢复点是同一条指令!
对于main 函数中的 fork 系统调用的陷入指令的地址,我们只需要在gdb模式跟踪到fork函数然后进行反汇编就可以看到
这里为了确保准确,先跟踪到fork函数的上一行,然后通过n命令步进,然后再进行反汇编
最终答案:
系统的第一次页故障发生时:
1.当时正在执行几号进程?(1)正在访问的线性地址是多少?(0x402574c)
2.该线性地址当时对应的页表项是什么?(0x25065)被映射到的物理地址是什么?(0x2574c)
3.该页故障处理完后,该线性地址对应的页表项是什么?(0xffd007)被映射到的物理地址是什么?(0xffd74c)
4.引发这次页故障的指令地址是什么?(0x690a)
5. main 函数中的 fork 系统调用的陷入指令的地址是多少?(0x6908)
第2关:父子进程间的共享内存通信实现
本关任务:改写版本 1.3 内核,使得 0 号进程顺序循环输出小写字母 a、b、c . . . ,每输出一个字母就执行 pause 系统调用; 1 号进程的行为类似,只不过将小写变为大写;同时要求两个进程输出的字符是关联递增的,即如果 0 号进程上一次输出了字符a,那么这一次如果是 1 号进程运行,那它应该输出字符B,反之亦然,运行画面如下图所示。(要求还是使用 int 0x81 输出字符,且不能修改 int 0x81 的实现方式)
最终答案:
- @@ -59,6 +59,7 @@
- extern void task0(void); /* wyj, 2 lines */
- extern void task1(void);
- extern void output_char(char c);
- +int next_char = 0; /* the next char to be outputed, in range [0, 25) */
- /*
- * This is set up by the setup-routine at boot-time
- @@ -108,10 +109,6 @@
- static long main_memory_start = 0;
- struct drive_info { char dummy[32]; } drive_info;
- -
- -
- -static int mynext = 0; /* the next char to be outputed, in range [0, 25) */
- -
- void main(void) /* This really IS void, no error here. */
- { /* The startup routine assumes (well, ...) this */
- /*
- @@ -148,13 +145,13 @@
- sti();
- move_to_user_mode();
- + next_char = 0;
- if (!fork()) { /* we count on this going ok */
- (void)mysignal(SIGALRM, SIG_IGN);
- for(;;) {
- - if (mynext < 7) {
- - output_char('A' + mynext);
- - mynext++;
- - }
- + output_char('A'+next_char++); /* wyj */
- + if (next_char >= 26)
- + next_char = 0;
- alarm(1);
- pause();
- }
- @@ -168,10 +165,9 @@
- * task can run, and if not we return here.
- */
- for(;;) {
- - if (mynext < 7) { /* wyj */
- - output_char('a' + mynext);
- - mynext++;
- - }
- + output_char('a'+next_char++); /* wyj */
- + if (next_char >= 26)
- + next_char = 0;
- pause();
- }
- }
修改linux/mm/memory.c
- @@ -130,7 +130,7 @@
- invalidate();
- return 0;
- }
- -
- +extern int next_char;
- /*
- * Well, here is one of the most complicated functions in mm. It
- * copies a range of linerar addresses by copying only the pages.
- @@ -175,7 +175,9 @@
- this_page = *from_page_table;
- if (!(1 & this_page))
- continue;
- - this_page &= ~2;
- + if ((this_page & 0xfffff000) !=
- + (((unsigned int)&next_char) & 0xfffff000))
- + this_page &= ~2;
- *to_page_table = this_page;
- if (this_page > LOW_MEM) {
- *from_page_table = this_page;