操作系统实验1

选择了唯一的Challenge。

Exercise 2

BIOS在初始化%ss%esp寄存器,并通过一系列in/out指令设置输入输出。

Exercise 3

Question 1

执行ljmp $PROT_MODE_CSEG, $protcseg后进入32位代码。

这是通过seta20.1seta20.2打开A20,并切换到保护模式实现的。

Question 2

bootloader的最后一条指令是call *0x10018,对应着bootmain()函数的((void (*)(void))(ELFHDR->e_entry))()语句。

内核的第一条指令是movw $0x1234,0x472

Question 3

内核的第一条指令位于0x10000c

Question 4

bootmain函数中,读取elf文件头。然后,根据elf文件头的信息,去读取各个程序段头。最终,通过程序段头的memsz信息,获知每个程序段的大小,并以此在readseg()函数算出扇区数。

Exercise 5

第一条执行错误的指令是lgdt gdtdesc

第一条“break”的指令是ljmp $PROT_MODE_CSEG,$protcseg,也即ljmp $0x8,$0x7c36

因为这一行指令不是位置无关代码,而protecseg链接到了错误的位置。

Exercise 6

bootloader的bootmain()函数的for循环的第一次运行,会读取内核.text段的前29058字节到0x100000。这8个字即是内核.text段的前8个字。

Exercise 7

指令jmp *%eax会发生异常。

Exercise 8

lib/printfmt.c文件中有一vprintfmt()函数,其中switchcase 'o'修改如下:

c
case 'o':
    num = getuint(&ap, lflag);
    base = 8;
    goto number;

Question 1

kern/printf.ckern/console.c之间的接口是cputchar()函数。具体来说,是这样的:

  1. kern/console.c中的cputchar()函数实质上是cons_putc()的套壳。后者在控制台中输出一个字符。
  2. kern/printf.c中有putch()函数,实质上是cputchar()的套壳。
  3. putch()的函数指针作为vprintfmt()的第一个参数。后者又被vcprintf()`调用。
  4. vcprintf()又被cprintf()调用。

Question 2

在已输出的字符数大于CRT_SIZE时,将屏幕滚动一行。

Question 3

  1. 参数fmt指向格式化字符串"x %d, y %x, z %d\n"ap指向变参结构体。
  2. text
    cprintf()
        vcprintf()
            cons_putc('x')
            cons_putc(' ')
            va_arg(), ap 由指向 x 变为指向 y
            cons_putc('1')
            cons_putc(',')
            cons_putc(' ')
            cons_putc('y')
            cons_putc(' ')
            va_arg(), ap 由指向 y 变为指向 z
            cons_putc('3')
            cons_putc(',')
            cons_putc(' ')
            cons_putc('z')
            cons_putc(' ')
            va_arg(), ap 由指向 z 变为指向我们不知道的东西
            cons_putc('4')
            cons_putc('\n')

Question 4

输出He110 World。具体过程如下:

  1. 输出H

  2. 输出57616的十六进制e110

  3. 输出Wo

  4. i在小端序机器上,地址从低到高为4个字节:0x72 0x6c 0x64 0x00,(在ASCII码)恰好可以视为长度为3的空终止字符串rld。又考虑到格式化字符串中的转换说明%s,这一步输出rld

    如果是大端序,那么i应当设为0x726c640057616不必改变。

Question 5

将会输出我们不知道的值。具体来说,如果参数3的地址为byte *ptr,则会输出ptr + 4指向的,类型为int的值。

解释:在当前ABI中,函数参数全部使用栈传参,且从右向左压栈。因此,在输出x=后,va_arg会获取值3,并将变参列表指针ap向上移动4字节。在输出y=后,va_arg会获取ptr + 4指向的值。

Question 6

cprintf(..., const char *fmt),其中可变参数需要倒着输入。例如,为了输出xy的值,需要:

c
cprintf(y, x, "x=%d, y=%d");

Exercise 9

kern/entry.Srelocated函数中,有一行指令:

movl   $(bootstacktop),%esp

在此初始化内核栈。

.data段中,静态地保留了KSTKSIZE大小的空间作为内核栈。

栈指针初始化时,指向上述空间的最高处。

Exercise 10

分别是参数x - 1,返回地址、%ebp%esi。共计4个32位字。

Exercise 11 and 12

对于mon_backtrace编写如下:

c
int mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
    struct Eipdebuginfo debug_info;
    cprintf("Stack backtrace:\n");

    uint32_t *ebp = (uint32_t *)read_ebp();
    while ((uint32_t)ebp != 0)
    {
        uint32_t eip = *(ebp + 1), arg_1 = *(ebp + 2), arg_2 = *(ebp + 3),
                    arg_3 = *(ebp + 4), arg_4 = *(ebp + 5), arg_5 = *(ebp + 6);
        cprintf("  ebp %08x  eip %08x  args %08x %08x %08x %08x %08x\n", ebp,
                eip, arg_1, arg_2, arg_3, arg_4, arg_5);

        debuginfo_eip(eip, &debug_info);

        cprintf("         %s:%d: %.*s+%d\n", debug_info.eip_file,
                debug_info.eip_line, debug_info.eip_fn_namelen,
                debug_info.eip_fn_name, eip - debug_info.eip_fn_addr);
        ebp = (uint32_t *)(*ebp);
    }
    return 0;
}

第6行用于获取当前的%ebp

while循环的终止条件是%ebp == 0,因为relocated%ebp为0。

第9 ~ 12行是获取和打印%eip以及5个参数的过程。

第14 ~ 18行是获取和打印%eip以及5个参数的过程。

第19行用于获取上一层函数的%ebp,因为这一层函数的第一个语句即是push %ebp

再在debuginfo_eip补充如下的代码,用于获取对应的行号:

c
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);

if (lline <= rline)
    info->eip_line = lline;
else
    info->eip_line = -1;

Challenge

为了在VGA字符模式向屏幕打印字符,我们必须将它写入硬件提供的VGA字符缓冲区。VGA字符缓冲区中的每一个字符单元为2字节,有如下的格式:

值含义
0 ~ 7字符ASCII码
8 ~ 11前景色
12 ~ 14背景色
15闪烁

可以在kern/console.c中的cga_putc()函数看到如下语句:

c
if (!(c & ~0xFF))
    c |= 0x700;

此即硬编码的黑底白字。为了更改输出颜色,我添加了一个color命令,类似Windows的COLOR命令,支持以下三种输入:

  1. color恢复默认的黑底白字。
  2. color <fg color>改变前景色。<fg color>的值可以为0 ~ f。
  3. color <fg color> <bg color>改变前景色和背景色。<fg color>的值可以为0 ~ f,<bg color>的值可以为0 ~ 7。

color命令会检查参数是否合法,并保证前景色与背景色不同。

color命令的详细实现见代码。这里只说思路:

  1. 加入全局变量fg_color, bg_color,并将硬编码的黑底白字修改为:
c
if (!(c & ~0xFF))
    c |= (fg_color << 8) | (bg_color << 12);
  1. 使用color命令修改全局变量fg_color, bg_color

效果如图:

color命令

Todo List
Valaxy v0.28.4 驱动|主题-Yunv0.28.4