zoukankan      html  css  js  c++  java
  • ucoreos_lab1

    前言

    最近觉得自己之前蛮多基础课学的并不咋滴,便想再补补。前段时间突然看到清华的操作系统实验,于是乎就打算试试,一边学一边做实验,然后通过博客来记录记录。

    实验内容

    lab1 中包含一个 bootloader 和一个 OS。这个 bootloader 可以切换到 X86 保护模式,能够读磁盘并加载 ELF 执行文件格式,并显示字符。而这 lab1 中的 OS 只是一个可以处理时钟中断和显示字符的幼儿园级别 OS。

    练习

    练习1:理解通过make生成执行文件的过程。

    1.操作系统镜像文件ucore.img是如何一步一步生成的?

    打开命令行,执行以下命令

    cd /home/moocos/ucore_lab/labcodes_answer/lab1_result
    make V=
    

    结果如下

    # 构建 Kernel 文件
    + cc kern/init/init.c
    gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
    + cc kern/libs/readline.c
    gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
    + cc kern/libs/stdio.c
    gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
    + cc kern/debug/kdebug.c
    gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
    + cc kern/debug/kmonitor.c
    gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
    + cc kern/debug/panic.c
    gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
    + cc kern/driver/clock.c
    gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
    + cc kern/driver/console.c
    gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o
    + cc kern/driver/intr.c
    gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
    + cc kern/driver/picirq.c
    gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
    + cc kern/trap/trap.c
    gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
    + cc kern/trap/trapentry.S
    gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
    + cc kern/trap/vectors.S
    gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
    + cc kern/mm/pmm.c
    gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
    + cc libs/printfmt.c
    gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/printfmt.c -o obj/libs/printfmt.o
    + cc libs/string.c
    gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/string.c -o obj/libs/string.o
    + ld bin/kernel
    ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel  obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o  obj/libs/printfmt.o obj/libs/string.o
    # 构建 BootLoader
    + cc boot/bootasm.S
    gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
    + cc boot/bootmain.c
    gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
    + cc tools/sign.c
    # 使用 gcc 将 sign.c 编译成可执行文件
    gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
    gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign
    # 使用 ld 命令链接 bootasm.o、bootmain.o 至 bootblock.out
    + ld bin/bootblock
    ld -m    elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
    'obj/bootblock.out' size: 488 bytes
    build 512 bytes boot sector: 'bin/bootblock' success!
    # 构建 ucore.img
    # 使用 dd 工具创建 ucore.img 空文件夹
    dd if=/dev/zero of=bin/ucore.img count=10000
    10000+0 records in
    10000+0 records out
    5120000 bytes (5.1 MB) copied, 0.113584 s, 45.1 MB/s
    dd if=bin/bootblock of=bin/ucore.img conv=notrunc
    1+0 records in
    1+0 records out
    512 bytes (512 B) copied, 0.0018245 s, 281 kB/s
    # 使用dd工具将文件bin/bootblock写入bin/ucore.img, 参数conv=notrunc表示不截断输出文件
    dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
    146+1 records in
    146+1 records out
    74923 bytes (75 kB) copied, 0.00668055 s, 11.2 MB/s
    

    由以上输出大致为

    • 编译了若干内核文件,构建出内核 kernel
    • 生成 bootblock 引导程序
    • 生成 ucore.img 虚拟磁盘

    image-20200526192036913

    2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

    在课程中有提示可通过 tools 目录下的 sign.c 文件了解,因此,查看 sign.c 文件

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/stat.h>
    
    int main(int argc, char *argv[]) {
        struct stat st;
        if (argc != 3) {
            fprintf(stderr, "Usage: <input filename> <output filename>
    ");
            return -1;
        }
        if (stat(argv[1], &st) != 0) {
            fprintf(stderr, "Error opening file '%s': %s
    ", argv[1], strerror(errno));
            return -1;
        }
        printf("'%s' size: %lld bytes
    ", argv[1], (long long)st.st_size);
        if (st.st_size > 510) {
            fprintf(stderr, "%lld >> 510!!
    ", (long long)st.st_size);
            return -1;
        }
        char buf[512];
        memset(buf, 0, sizeof(buf));
        FILE *ifp = fopen(argv[1], "rb");
        int size = fread(buf, 1, st.st_size, ifp);
        if (size != st.st_size) {
            fprintf(stderr, "read '%s' error, size is %d.
    ", argv[1], size);
            return -1;
        }
        fclose(ifp);
        buf[510] = 0x55;
        buf[511] = 0xAA;
        FILE *ofp = fopen(argv[2], "wb+");
        size = fwrite(buf, 1, 512, ofp);
        if (size != 512) {
            fprintf(stderr, "write '%s' error, size is %d.
    ", argv[2], size);
            return -1;
        }
        fclose(ofp);
        printf("build 512 bytes boot sector: '%s' success!
    ", argv[2]);
        return 0;
    }
    
    

    由以上代码大致可知

    1. 大小为 512 字节,空余部分用 0 填充
    2. 文件内容不超过 510 bytes
    3. 最后两位为 0x55 0xAA

    练习2:使用qemu执行并调试lab1中的软件

    1. 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。
    2. 在初始化位置 0x7c00 设置实地址断点,测试断点正常。
    3. 从 0x7c00 开始跟踪代码运行,将单步跟踪反汇编得到的代码与 bootasm.S 和 bootblock.asm 进行比较。
    4. 自己找一个 bootloader 或内核中的代码位置,设置断点并进行测试。

    修改 tools/gdbinit 如下

    set architecture i8086
    target remote :1234
    

    在 /home/moocos/ucore_lab/labcodes_answer/lab1_result 目录下执行命令 make debug

    image-20200526200018980

    分别输入 i r cs 、 i r eip 以及 x /2i 0xffff0 可以看到如下结果

    image-20200527124207631

    可知:

    1. CS 中地址为 0xF000, PC 中地址为 0xFFF0
    2. 由课程内容可知,第一条指令执行位置在 0xffff0 处,并且为长跳转指令
    3. BIOS 实例存储在 cs:ip 为 0xf000:0xe05b 处

    接着,修改 gdbinit 如下

    file obj/bootblock.o
    set architecture i8086
    target remote :1234
    b *0x7c00
    continue
    

    继续 make debug

    image-20200527125030749

    可发现 0x7C00 为主引导程序的入口地址,代码与 bootasm.S 一致。

    练习3:分析bootloader进入保护模式的过程。

    BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。

    提示:需要阅读小节“保护模式和分段机制”和lab1/boot/bootasm.S源码,了解如何从实模式切换到保护模式,需要了解:

    • 为何开启A20,以及如何开启A20
    • 如何初始化GDT表
    • 如何使能和进入保护模式

    1.为何开启A20,以及如何开启A20

    先来说说 CPU 是如何寻址获取数据,i8086 处理器数据总线是 16 bit ,地址总线是 20 bit,寄存器 16 bit,因此,CPU 只能访问 1MB 以内的空间。CPU 想获取数据,需对 segment 左移 4 位,再加上 offest,最终形成一个 20bit 的地址

    address = segment << 4 | offset
    

    因此,理论上 20bit 地址可以访问 1MB 的内存空间。但在实模式下,这 20bit 的地址可以访问 (0x00000 ~ (0xffff0 + 0xffff = 0x10ffef)) 的存空间,大于 1MB。虽然大于 1MB,但对于 8086 来说,是没有任何影响,但是对于现在有更宽的地址总线的 CPU 来说,就会出现问题。在实模式下,这些 CPU 就能访问超过 1MB 的空间。为了解决这个问题,CPU 中添加了一个可控制 A20 地址线的模块,通过这个模块我们在实模式下将第 20 bit 的地址线限制为 0,这样 CPU 就无法访问超过 1MB 的空间了。

    具体代码在 bootasm.S 文件中,然而没怎么看懂 orz

        # Enable A20:
        #  For backwards compatibility with the earliest PCs, physical
        #  address line 20 is tied low, so that addresses higher than
        #  1MB wrap around to zero by default. This code undoes this.
    seta20.1:
        inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
        testb $0x2, %al
        jnz seta20.1
    
        movb $0xd1, %al                                 # 0xd1 -> port 0x64
        outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port
    
    seta20.2:
        inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
        testb $0x2, %al
        jnz seta20.2
    
        movb $0xdf, %al                                 # 0xdf -> port 0x60
        outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
    

    2.如何初始化GDT表

    GDT(Global Descriptor Table, 全局描述表)。同实模式一样,在保护模式下,对内存的访问仍采用短地址加偏移地址的方式。其内存的管理方式有两种,段模式和页模式。在保护模式下,对于一个段的描述包括:Base Address,Limit(段的最大长度),Access(权限),这三个数据加在一起被放在一个 64 bit 的数据结构中,被称为段描述符。而由于寄存器为 16 bit,很明显,我们无法直接通过 16 bit 长度的寄存器来直接使用 64 bit 的段描述符。而对此的解决方案便是将这些段描述符放入一个全局数组中,将段寄存器中的值作为下标索引(段寄存器中的高 13 bit 的内容作为索引)来间接引用。而这个全局数组便是 GDT。

    image-20200527170905054

    image-20200527170931967

    GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以 Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过 LGDT 指令将 GDT 的入口地址装入此积存器,从此以后,CPU 就根据此积存器中的内容作为 GDT 的入口来访问GDT了。

    代码中关于 GDT 表的定义以及初始化

    asm.h 文件
    /* Normal segment */
    #define SEG_NULLASM                                             
        .word 0, 0;                                                 
        .byte 0, 0, 0, 0
    
    #define SEG_ASM(type,base,lim)                                  
        .word (((lim) >> 12) & 0xffff), ((base) & 0xffff);          
        .byte (((base) >> 16) & 0xff), (0x90 | (type)),             
            (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
    
    bootasm.S 文件
    
    lgdt gdtdesc
    
    gdt:
        SEG_NULLASM                                     # null seg
        SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
        SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel
    
    gdtdesc:
        .word 0x17                                      # sizeof(gdt) - 1
        .long gdt                                       # address gdt
    

    3.如何使能和进入保护模式

    开启 A20,初始化 GDT 后,将控制寄存器 CR0 的 PE 置为 1 即可

    movl %cr0, %eax
    orl 0x1, %eax
    movl %eax, %cr0
    

    由此,可总结出 bootloader 从实模式进入保护模式的过程

    1. bootloader 开始运行在实模式,物理地址为 0x7C00
    2. bootloader 关闭所有中断,方向标志位复位,ds,es,ss段寄存器清零
    3. 打开 A20 使之能够使用高位地址线
    4. 使用 lgdt 指令把 GDT 描述符表的大小和起始地址存入 GDT 寄存器,修改寄存器CR0的最低位(orl $CR0PEON, %eax)完成从实模式到保护模式的转换,通过 ljmp 指令跳转到32位指令模式
    5. 进入保护模式后,设置ds,es,fs,gs,ss段寄存器,堆栈指针,便可以进入 c 程序 bootmain

    练习4:分析bootloader加载ELF格式的OS的过程

    通过阅读bootmain.c,了解bootloader如何加载ELF文件。通过分析源代码和通过qemu来运行并调试bootloader&OS,

    • bootloader如何读取硬盘扇区的?
    • bootloader是如何加载ELF格式的OS?
    1. 定义指针 ELFHDR,指向 0x10000

    2. 读取 8 个扇区大小的 ELF 头到 0x10000 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);

    3. 检验文件是否符合格式

      // is this a valid ELF?
      if (ELFHDR->e_magic != ELF_MAGIC) {
          goto bad;
      }
      
    4. 循环调用readseg函数加载每一个程序段

    5. 跳转到操作系统入口

    练习5:实现函数调用堆栈跟踪函数

    我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。

    void
    print_stackframe(void) {
         /* LAB1 YOUR CODE : STEP 1 */
         /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t);
          * (2) call read_eip() to get the value of eip. the type is (uint32_t);
          * (3) from 0 .. STACKFRAME_DEPTH
          *    (3.1) printf value of ebp, eip
          *    (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4]
          *    (3.3) cprintf("
    ");
          *    (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.
          *    (3.5) popup a calling stackframe
          *           NOTICE: the calling funciton's return addr eip  = ss:[ebp+4]
          *                   the calling funciton's ebp = ss:[ebp]
          */
    	uint32_t ebp = read_ebp(), eip = read_eip();
    	int i, j;
    	for(i = 0; ebp != 0 && i < STACKFRAME_DEPTH; i ++){
    		cprintf("ebp: 0x%08x eip:0x%08x args:", ebp, eip);
    		uint32_t *args = (uint32_t*)ebp + 2;
    		for(j = 0; j < 4; j ++){
    			cprintf("0x%08x  ", args[j]);
    		}
    		cprintf("
    ");
    		print_debuginfo(eip-1);
    		eip = ((uint32_t*)eip)[1];
    		ebp = ((uint32_t*)ebp)[0];
    	}
    }
    

    在 lab1 目录下,命令行执行 make qemu

    image-20200527162304666

    练习6:完善中断初始化和处理

    请完成编码工作和回答如下问题:

    1. 中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
    2. 请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
    3. 请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。

    1.中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

    • 一个表项占 8 个字节
    • image-20200527171123033

    2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init

    void idt_init(void) {
    	extern uintptr_t __vectors[];
    	int i;
    	for(i = 0; i < 256; i ++){
    		SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
    	}
    	SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);
    	lidt(&idt_pd);
    }
    
    

    3.请编程完善trap.c中的中断处理函数trap

    if (++ticks == TICK_NUM) {
      print_ticks();
      ticks = 0;
    }
    

    扩展练习 Challenge 1

    扩展proj4,增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务。

    在中断表中,可以看到 T_SWITCH_TOU 和 T_SWITCH_TOK,一个是切换到用户态,另一个是切换回内核态。切换时只需要修改段寄存器即可。

    static void
    switch_to_user(struct trapframe *tf){
    	if((tf->tf_cs & 3) == 3) return;
    	tf->tf_ds = tf->tf_es = tf->tf_fs = tf->tf_gs = tf->tf_ss = USER_DS;
    	tf->tf_cs = USER_CS;
    	tf->tf_eflags |= FL_IOPL_3;
    }
    
    static void
    switch_to_kernel(struct trapframe *tf) {
      if ((tf->tf_cs & 3) == 0) return;
      tf->tf_ds = tf->tf_es = tf->tf_fs = tf->tf_gs = tf->tf_ss = KERNEL_DS;
      tf->tf_cs = KERNEL_CS;
      tf->tf_eflags &= ~FL_IOPL_3;
    }
    ...
    ...
    case T_SWITCH_TOU:
        	switch_to_user(tf);
        	break;
    case T_SWITCH_TOK:
            switch_to_kernel(tf);
            break;
    

    这样的话,只要触发 T_SWITCH_TOU 和 T_SWITCH_TOK 编号的中断, CPU 指令流就会通过 ISR 执行到这里,并进行内核态和用户态的切换。

    接下来只需要在 kern/init/init.c 中将 lab1_switch_test() 解注释,然后实现两个函数即可。

    static void
    lab1_switch_to_user(void) {
        //LAB1 CHALLENGE 1 : TODO
    	asm volatile (
    			"sub $0x8, %%esp 
    "
    			"int %0 
    "
    			"movl %%ebp, %%esp"
    			:
    			: "i"(T_SWITCH_TOU)
    	    );
    }
    
    static void
    lab1_switch_to_kernel(void) {
        //LAB1 CHALLENGE 1 :  TODO
    	asm volatile (
    		    "int %0 
    "
    		    "movl %%ebp, %%esp 
    "
    		    :
    		    : "i"(T_SWITCH_TOK)
    		);
    }
    

    扩展练习 Challenge 2

    用键盘实现用户模式内核模式切换。具体目标是:“键盘输入3时切换到用户模式,键盘输入0时切换到内核模式”。 基本思路是借鉴软中断(syscall功能)的代码,并且把trap.c中软中断处理的设置语句拿过来。

    击键会触发中断,对其的处理在 IRQ_KBD case 处,直接判断是不是 '0' 或 '3' 即可。

    c = cons_getc();
    		switch (c) {
    		 case '0':
    			switch_to_kernel(tf);
    			print_trapframe(tf);
    			break;
    		 case '3':
    			switch_to_user(tf);
    			print_trapframe(tf);
    			break;
    		}
    		cprintf("kbd [%03d] %c
    ", c, c);
    		break;
    

    小节

    感觉 lab1 跟计组和汇编挂钩蛮多,做了快一周,之前这两门课就学的不认真,很多东西都忘的差不多了,做起实验来真的是要命,各种看不懂 orz。感觉自己这两年摸鱼摸得有点多。。。。

    参考

    1. https://www.cnblogs.com/lsgxeva/p/8948153.html

    2. https://bravey.github.io/2019-09-08-%E5%AE%9E%E6%A8%A1%E5%BC%8F%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F.html#%E5%AE%9E%E6%A8%A1%E5%BC%8F%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F

    3. https://zhuanlan.zhihu.com/p/25867829

    4. https://wiki.osdev.org/IDT

    5. https://xr1s.me/2018/05/15/ucore-lab1-report/

    6. https://www.cnblogs.com/ECJTUACM-873284962/p/11178427.html

  • 相关阅读:
    PHP排序问题
    正则表达式——获取指定IP的物理地址
    两款网站页面翻译插件
    正则表达式应用之提炼歌词
    PHP 读取网页文件
    PHP正则表达式——匹配多行
    正则表达式——获取指定IP的物理地址(二)
    用百度音乐Widget从此不在为找MP3外链而烦恼
    I Saw Thee Weep
    POJ 3281 Dining
  • 原文地址:https://www.cnblogs.com/csu-lmw/p/12975336.html
Copyright © 2011-2022 走看看