JOS源文件安排:
inc/ 这个文件夹是一些include文件,即各种类型的头文件。
env.h Public definitions for user-mode environments
trap.h Public definitions for trap handling
syscall.h Public definitions for system calls from user environments to the kernel
lib.h Public definitions for the user-mode support library
kern/ env.h Kernel-private definitions for user-mode environments
env.c Kernel code implementing user-mode environments
trap.h Kernel-private trap handling definitions
trap.c Trap handling code
trapentry.S Assembly-language trap handler entry-points
syscall.h Kernel-private definitions for system call handling
syscall.c System call implementation code
lib/ Makefrag Makefile fragment to build user-mode library, obj/lib/libjos.a
entry.S Assembly-language entry-point for user environments
libmain.c User-mode library setup code called from entry.S
syscall.c User-mode system call stub functions
console.c User-mode implementations of putchar and getchar, providing console I/O
exit.c User-mode implementation of exit
panic.c User-mode implementation of panic
user/ * Various test programs to check kernel lab 3 code
8086与80386寄存器对比:
8086寄存器 | 80386寄存器 | |
---|---|---|
通用寄存器 | ax(accumulator), bx(base), cx(count), dx(data), bp(base pointer), sp(stack pointer), di(des index), si(src index) |
eax, ebx, ecx, edx, ebp, esp, edi, esi |
段寄存器 | cs(code seg), ds(data seg), ss(stack seg), es(extra seg) | cs, ds, ss, es, fs, gs |
段描述符寄存器 | 无 | 对程序员不可见 |
状态和控制寄存器 | flags, ip | eflags, eip, cr0, cr1, cr2, cr3 |
系统地址寄存器 | 无 | gdtr, idtr, tr, ldtr |
调试寄存器 | 无 | dr0-dr7 |
测试寄存器 | 无 | tr0-tr7 |
“访存”指令中给定地址值(16位),CPU将其送往地址线(20位)。在8086时代,在把地址送往地址线之前,CPU会把它与某个段寄存器中的值相加:实际物理地址 = (段寄存器 << 4) + 偏移地址——从而实现了从16位内存地址到20位实际地址的映射。
到80286时代,引入了一个全新理念:“保护模式”,它的引入给寻址方式、中断管理都带来了变化。从此,我们把之前的段式管理方法称为“实模式”。
1)实模式下段的管理
实模式下一共有8种寻址方式:
立即数寻址 | |
寄存器寻址 | |
直接寻址 | |
寄存器间接寻址 | |
基址寻址 | |
变址寻址 | |
基址加变址 | |
带位移的基址加变址寻址 |
本质上实模式下的寻址方式都是段基址左移4位加上偏移。
2)保护模式下段的管理
在保护模式下,利用一个段选择子(本质上是在一组段描述符数组中的下标或者说索引)到全局描述符表(一组段描述符构成的数组)中找到所需的段描述符(本质上是个结构体,它有三个成员变量:段物理首地址、段界限、段属性),而这个段描述符中就存放着真正的段的物理首地址,然后再加上偏移量得到最后的物理地址。
实际物理地址 = selector + offset,其中selector是16位的用于在GDT中索引,offset是32位的。
当发生内存寻址与定位时,处理器通过GDTR寄存器找到GDT,并通过selector找到对应的描述符,今儿得到该段的起始地址,最后加上偏移得到实际地址。
kern/init.c:
入口:
void i386_init(void){
cons_init(); //命令台初始化
kern/console.c:
{
cga_init();
kbd_init();
serial_init();
}
mem_init(); //内存管理初始化
kern/pmap.c:
{
i386_detect_memory();
kern_pgdir = boot_alloc();
pages = boot_alloc();
envs = boot_alloc();
page_init();
boot_map_region( .pages. );
boot_map_region( .envs. );
boot_map_region( .bootstack. );
boot_map_region( .0. );
lcr3(PADDR(kern_pgdir));
}
env_init(); //进程管理初始化
kern/env.c:
{
... //建立envs数组
env_init_percpu(); //载入GDT和段描述符
}
trap_init(); //进程管理初始化:中断处理
kern/trap.c:
{
SETGATE(); //建立中断描述符表
trap_init_percpu();
}
//创建进程,等同于env_create(_binary_obj_user_hello_start, ENV_TYPE_USER);
//这里的符号名‘_binary_obj_user_hello_start’是链接器在链接内核镜像时自动生成的
ENV_CREATE(user_hello, ENV_TYPE_USER);
kern/env.c:
{
env_alloc();
{
env_setup_vm(e); //为进程分配并建立页目录
... //生成env_id
... //设置基本状态变量
... //设置段寄存器初值
}
load_icode();
{
lcr3(PADDR(e->env_pgdir));
region_alloc(); //分配一定字节物理内存并映射到进程地址空间的某虚地址
{
page_alloc();
page_insert();
}
memmove();
memset();
lcr3(PADDR(kern_pgdir));
region_alloc();
}
}
env_run(&envs[0]); //运行进程,此函数不会返回,将不断循环直到销毁进程并退出
{
... //设置(需要时切换)当前curenv
lcr3(PADDR(curenv->env_pgdir));
env_pop_tf(); //这是进入用户模式前的最后一个函数
}
while(1) //陷入内核监控器
monitor(NULL);
}
env_pop_tf()函数的作用就是按Trapframe的结构将其从栈中恢复到各寄存器中去,最后执行iret指令,从而从内核退出并开始执行用户进程代码。
执行完 iret 后进入用户模式,跳往lib/entry.S中定义的 _start: 处,执行完一些准备工作后执行 call libmain。
libmain()定义在lib/libmain.c中,在其中调用用户主程序:umain(),执行完用户程序后exit()。
umain()定义在user/hello.c中,在其中可能执行一些如cprintf()之类的库函数,这些函数本质上最终都会执行系统调用。
上述调用过程发生在用户态下,调用过程如下:
kern/env.c:
void env_pop_tf()
{
... //从栈中弹出Trapframe,恢复寄存器的值
iret //退出内核态,进入用户程序
}
--------------------内核态用户态分界线-------------------
lib/entry.S:
.text
.global _start
_start:
...
args_exist:
call libmain //调用库函数libmain
...
lib/libmain.c:
void libmain()
{
...
umain(); //调用用户主函数umain
exit();
}
user/hello.c:
void umain()
{
cprintf("hello world\n");
}