什么是微内核
微内核主要用于解决宏内核操作系统在扩展性、可靠性等方面遇到的问题。区别于宏内核的左右系统功能都实现在内核中,微内核只在内核中保留必要的系统功能,比如基础的IPC、进程调度机制等,而其他诸如文件系统、网络IO等功能都在用户空间实现。 现有的微内核:QNX、SEL家族(更新到seL4)
什么是实模式与保护模式?
实模式指的就是直接使用物理地址对内存进行访问的方式。早期CPU一般只有16位的运行环境,将整个物理内存进行分段,代码和数据保存在各个段中。普通程序在运行时,可能会修改与系统相关的内存段中的值,这是十分危险的。后来,CPU发展到32位之后,寻址空间达到了4GB,为了对内存进行保护以及访问更多的地址空间,引入了保护模式。 保护模式提供段页式的内存管理机制,以及多任务的执行。保护模式的段基址是一个指向全局描述符表的指针,这个全局描述符保存着每一段相关的信息。 但是本着向下兼容的思想,大多数CPU都保留了实模式,并且在CPU上电或复位时启用,加载操作系统的时候必须将工作模式切换为保护模式。
jos如何实现实模式到保护模式的切换?
jos模式的切换是发生在操作系统引导与加载的时候,所以下面直接介绍一下jos引导与加载的过程。 主要分为两个阶段:
- 引导程序的加载
- 内核的加载
引导程序的加载
PC上电启动,运行为实模式,硬件将直接存储在”磁盘“的BIOS加载到内存中。进入实模式运行后,下一条指令CS:IP就是BIOS所在的物理地址。(BIOS主要是完成加电自检和硬件初始化的工作,之后将处理器控制权转交)将处理器的设备初始化完成之后,从存储器中加载内核引导器(boot loader)。boot loader的主要作用是切换处理器的工作模式,将内核从磁盘加载到内存。 bootloader的第一条指令就是cli,关中断(内核准备好之后再打开)。在实模式下,处理器有8个16位通用寄存器,但是处理器需要发送20位的地址给存储器(实模式的地址空间是1MB),所以需要使用cs,ss,es,ds提供额外位。
jos中模式的切换在boot.s中实现: 要从实模式切换到保护模式,首先需要开启更多的地址线。 这就首先涉及到A20 Gate的开启,通过键盘控制器的输出端口第二位的电平来判定是否启用第21位地址线,当其电平为高时,就启用第21位的地址线,让处理器能够寻址32位。主要分为两部分:
- 一部分是向键盘输入缓冲器(0x64端口)写一个控制命令(0xd1), 0xd1表示向P2输出端口写数据,其具体数据就是0x60端口输入
- 具体写向P2输出端口的数据,向键盘输入缓冲器(0x60端口)写入0xdf, 其二进制表示0b11011111, 其中第2位则为A20选通位,这时A20 Gate开启
至此,可以使用32位地址线了,但是实模式到保护模式的实际切换是由CR0寄存器的最低位(保护模式使能位)值1决定的。
- 在GDTR(段描述符表寄存器)中存入GDT(段描述符表)的首地址
- 将CR0寄存器的PE位置1,开启处理器的保护模式
使用ljmp指令跳转进入保护模式,在保护模式中需要重新对一些32位的段寄存器进行初始化,对堆栈指针ESP进行初始化,然后进入bootmain函数完成对内核的加载。
内核的加载(ELF文件的加载)
内核的加载就是从磁盘中读取ELF可执行文件的过程,所以需要首先了解ELF文件格式。
什么是ELF文件?
ELF文件是一种文件格式,一般可以是可执行文件、目标文件、共享库和二进制文件。Linux中主要的可执行文件格式就是ELF文件。操作系统中通过加载可执行文件来实现对程序和数据的访问。
ELF文件结构 jos中ELF文件头的数据结构和程序头表的数据结构:
struct Elf {
uint32_t e_magic; // must equal ELF_MAGIC
uint8_t e_elf[12];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint32_t e_entry;
uint32_t e_phoff; // 程序节头表数据的信息,所有的段
uint32_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum; // 段的数量
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Proghdr {
uint32_t p_type;
uint32_t p_offset;
uint32_t p_va;
uint32_t p_pa;
uint32_t p_filesz;
uint32_t p_memsz;
uint32_t p_flags;
uint32_t p_align;
};
过程:
- 从磁盘的1号扇区中读取读取8个扇区大小的ELF格式kernel到物理地址0x10000,检查其是否是有效的ELF文件;
- 需要去读取程序头表中指明 的所有段,第一个segment对象的地址存放在ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff)中,则 结束的地址为:eph = ph + ELFHDR->e_phnum;然后就可以遍历这个proghdr数组了。至此,内核完全加载完毕。
for (; ph < eph; ph++)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
|