LAB 3 进程管理

回顾 828 的 Lab3,进程管理部分。

此部分增强 JOS 内核的功能,实现对进程的支持。

进程管理包括创建新进程、初始化、分配页目录、分配以及映射物理内存、加载程序 ELF 二进制映像到用户地址空间 以及 在用户模式运行进程。

除此之外,由于进程常常要调用操作系统提供的接口,所以还实现了对中断和异常的处理,操作系统在内核态和用户态之前进行切换。

1、接上篇,内存初始化完毕之后,进行进程初始化

实现创建和运行进程所需的函数:

  • env_init(): 初始化 envs 数组中的所有 Env 结构,并将它们添加到 env_free_list 中。调用 env_init_percpu,它将分区硬件配置为权限级别为 0 (kernal)和权限级别为 3 (user)的单独段。
  • env_setup_vm(): 为新环境分配一个页目录,并初始化新环境的地址空间的内核部分。
  • region_alloc() :为环境分配和映射物理内存。
  • load_icode(): 您将需要解析 ELF 二进制映像,就像引导加载程序已经做的那样,并将其内容加载到新环境的用户地址空间中。
  • env_create():使用 env_alloc 分配环境,并调用 load_icode 将 ELF 二进制文件加载到其中。
  • env_run(): 启动一个以用户模式运行的给定环境。

以上功能实现对用户进程的支持,但当用户进程使用 int $0x30 指令进行系统调用时,会出现错误(int 是将字符显示到控制台的系统调用)。

这是因为还没有实现 CPU 对系统调用的中断处理。

2、异常处理

异常和中断都是"受保护的控制传输",导致处理器从用户切换到内核模式 (CPL = 0),而不会给用户模式代码任何干扰内核或其他环境功能的机会。

在 x86 上,两个机制协同工作以提供此保护:

  1. The Interrupt Descriptor Table

    CPU 使用向量作为处理器中断描述符表 (IDT) 的索引,内核在内核专用内存中设置该表,与 GDT 很像。从此表中的合适条目中,处理器加载:

    • 要加载到指令指针 (EIP) 寄存器中的值,指向用于处理该类型的异常的内核代码。
    • 要加载到代码段 (CS) 寄存器中的值,其中包括在位 0-1 中运行异常处理程序的权限级别。(在 JOS 中,所有异常都在内核模式下处理,权限级别 0)
  2. The Task State Segment

    简而言之,JOS 使用 TSS 来保存处理器从用户模式切换到内核模式时的处理器状态。比如:EIP、CS 寄存器的值。

    以便异常处理程序结束后,从中断的位置恢复处理器的状态。

JOS 将中断向量 48 (0x30) 作为系统调用中断向量。

3、设置 IDT

void
i386_init(void)
{
    ...
    env_init();
    trap_init(); // 初始化 IDT
    ...
}

// 设置 IDT,并把 TSS 和 IDT 加载到 CPU 特定位置。
kern/trap.c/trap_init()
    --> SETGATE(idt[T_FPERR], 0, GD_KT, V_FPERR, 0)
        ...
    --> trap_init_percpu() // Initialize and load the per-CPU TSS and IDT
        --> Setup a TSS
        --> Initialize the TSS slot of the gdt.
        --> ltr(GD_TSS0)   // Load the TSS selector
        --> lidt(&idt_pd)  // Load the IDT

4、创建一个进程:

// hello, world
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
    cprintf("hello, world\n");
    cprintf("i am environment %08x\n", thisenv->env_id);
}

编译时,hello.c 被编译为 ELF 二进制可执行映像嵌入到内存中,以便内核运行。

// 内核态下
--> ENV_CREATE(user_hello, ENV_TYPE_USER) // 内核态下,创建进程,然后切换到用户态执行进程
    --> ENV_PASTE3(_binary_obj_, user_hello, _start)[] // 生成二进制程序的名,以便使用
    --> env_create(_binary_obj_user_hello_start, ENV_TYPE_USER) // 包含两个函数
        --> env_alloc(&e, 0) // 分配并且初始化一个进程,设置各种属性
            --> env_setup_vm(e) // 为进程分配并设置虚拟页表
        --> load_icode(e, binary) // 加载进程的二进制程序段
--> env_run(&envs[0])  // 运行进程,如果有正在运行的进程,还需要进行上下文的切换
    --> lcr3(PADDR(e->env_pgdir)) // 加载进程页表基址对应的物理地址,以便进行地址转换和内存访问
    --> env_pop_tf(&e->env_tf) // (内联汇编)  把&env_tf为起始地址的一段空间'当成栈',逐步popa、pop、iret到相应寄存器。且iret结束后进入'user mode'
        --> iret // 返回用户模式
    
// env_pop_tf 是进入用户模式之前的最后一个函数,iret 指令之后进入用户模式。
// 执行 lib/entry.S 中 start 标签处的 cmpl 指令。
// 用户态下  
--> 此时 eip = env_tf.tf_eip = 0x80020, 即 lib/extry.S/_start, 之后 call libmain。
--> start executing some environment's code
--> libmain(int argc, char **argv)
    --> thisenv = &envs[ENVX(sys_getenvid())] //set thisenv to point env structure
        --> lib/sys_getenvid() // 系统调用
        --> lib/syscall(SYS_getenvid, 0, 0, 0, 0, 0, 0) // 陷入内核态
            // 向寄存器中推送值
        --> TRAPHANDLER_NOEC(V_SYSCALL, T_SYSCALL) // jmp _alltraps and call trap
        --> trap(tf)
            --> trap_dispatch(tf) // Dispatch based on what type of trap occurred
                // 系统调用
                --> kern/syscall()     // since tf->tf_trapno == T_SYSCALL
                --> sys_getenvid()     // since syscallno == SYS_getenvid and
                                       // return curenv->env_id to tf->tf_regs.reg_eax
            --> env_run(curenv)
                --> lcr3(PADDR(e->env_pgdir))
                --> env_pop_tf(&e->env_tf) // (内联汇编) 恢复寄存器的值
                    --> iret // 返回用户模式, thisenv 
    --> umain(argc, argv) // 执行程序代码
        --> user/hello.c/umain()
        --> cprintf("hello, world\n")   // kern/printf.c/cprintf()
            --> vcprintf()              // kern/printf.c/cprintf()
                --> vprintfmt((void*)putch, &cnt, fmt, ap) //lib/printfmt.c
                    --> punch()         // kern/printf.c/punch()
                        --> cputchar(c) // kern/console.c
                            --> cons_putc(c) // kern/console.c  
                            // output a character to the console
                                --> serial_putc(c); 
                                --> lpt_putc(c);
                                --> cga_putc(c);
                    --> 输出
        --> cprintf("i am environment %08x\n", thisenv->env_id); // 同上  
    --> exit()  // lib/exit.c
        --> lib/sys_env_destroy(0)
            --> lib/syscall(SYS_env_destroy, 1, envid, 0, 0, 0, 0) // 系统调用
                --> "i" (T_SYSCALL) // 推送值到寄存器
                    --> TRAPHANDLER_NOEC(V_SYSCALL, T_SYSCALL) // 陷入到内核态
                 // kern/..
                --> trap(tf) 
                    --> trap_dispatch(tf) // Dispatch based on what type of trap occurred
                        // since tf->tf_trapno == T_SYSCALL
                        --> syscall() // 内核态
                        // since syscallno == SYS_env_destroy
                        --> sys_env_destroy(a1)
                            --> env_destroy(e)
                                --> env_free(e)
                                --> monitor(NULL)
--> while (1) monitor(NULL)
  1. lib/syscall 在用户态下调用系统调用,kern/syscall 则是在内核态下。
  2. CR2 控制寄存器存储导致错误的 linear/virtual address
  3. A user program 开始运行在 lib/entry.S 的顶部。
  4. 进入用户态后,执行的第一条指令从CS:EIP去找,找到是lib/entry.S/_start,_start里调用了libmain()。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容