日期:2022-09-27
版本号:V1.0
作者:snow
一、基础概念
1.1、任务寄存器
TR寄存器指向当前任务的TSS,通过ltr访问
LDTR寄存器指向当前任务的LDT,通过lldt访问
二、内存规划
2.1、备份根设备与硬盘参数
由于主存存储空间后续会被重新规划,所以需要提前备份好重要参数。(原系统参数通过setup程序存放在了0x90000~0x901FC)
- 将根设备号备份至ROOT_DEV(位于内核数据区)
- 将硬盘参数备份至drive_info(位于内核数据区)
2.2、内存规划(按功能)
2.2.1 分配缓冲区
内存容量 | m>16MB | 16MB≥m>12MB | 12MB≥m>6MB | 6MB≥m |
块0-缓冲区 | 6MB | 4MB | 2MB | 1MB |
块1-主存区 | 12MB | m-4 | m-2 | m-1 |
注:块0-缓冲区的前1MB空间为内核代码与内核数据。后续对于非内核区内容将会采用分页管理机制。而分页管理所需要的参数则据此存储在内核数据区中。(小彩蛋,基于分页管理基础上的用户程序无法访问没有通过分页管理的内容,同时这里指的是纯粹的页式管理,需要区别下段页式管理的页式机制)。缓冲区主要用以与外设-块设备进行数据交互的缓冲。
2.2.2 虚拟盘初始化
若在编译阶段虚拟盘(典型代表ramdisk)使用标志宏定义有效,则会默认在主存区的低地址范围分配一个2MB的空间,然后将对应的处理函数挂接到块设备第7项中,随后对虚拟盘该范围内进行清0。后续对7个块设备的访问即对应和虚拟盘的数据交互。
2.2.3 内存管理结构初始化
将缓冲区(不含内核)的使用次数标记为100,除缓冲区以外的主存区内容设置为0并将次数置为0,后续仅将使用次数为0的页认为是未使用页面。
三、中断&外设初始化
3.1、初始化中断描述符
将中断函数与中断号绑定,即初始化中断号对应IDT中各项内容,主要是将每个中断的中断函数地址、中断优先级等内容写入到中断描述符中。
set_trap_gate(0,÷_error);
set_trap_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_intr_gate(3,&int3);
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_task_gate(8,31);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
set_trap_gate(18,&machine_check);
set_trap_gate(19,&simd_coprocessor_error);
set_system_gate(128,&system_call);
3.2、初始化块设备请求项结构
系统会定义32个(或更多)块设备请求项结构,将每个设备请求项指向一个空设备,并将其断开请求项链表(下一个请求项为空)
3.3、初始化人机交互外设
3.3.1 初始化串口
(1)将串口中断服务程序与中断描述表连接
(2)初始话串口工作参数
(3)允许串口外设发出相关中断
3.3.2 初始化显示器
(1)根据系统数据配置显示器工作参数、显存位置以及相关寄存器位置
(2)设置滚屏相关参数、顶底端行号等工作参数
3.3.3 初始化键盘
(1)将键盘中断服务程序与中断描述表连接
(2)允许键盘发出相关中断
3.3.4 确定系统时间
根据CMOS时钟芯片(或者RTC)获取系统启动时间,并据此记录系统时间
3.4、启动前准备
3.4.1 初始化进程槽
3.4.1.1 进程0描述符表挂接
将进程0的状态描述符表添加到gdt+FIRST_TSS_ENTRY处,也就是第4个描述符
将进程0的局部数据描述符表添加到gdt+FIRST_LDT_ENTRY处,也就是第5个描述符
3.4.1.2 其他描述符表清0
定义了64个任务对应描述符空间,每个任务描述符即对应一个任务状态描述符和一个局部数据描述符,故还需要清理余下63个任务对应的描述符空间。
3.4.1.3 分配进程0的栈
在第二篇章的2.1.2.1节中定义了一个页的栈空间,内核(引导程序)启动完成后,这份空间有进程0继续使用,所以直接将进程槽首地址指向这个栈空间。
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
为方便理解,这里可以解释为2步
- 分配了NR_TASKS个进程描述符指针,每个指针均指向自己的栈空间
- 将进程0的进程描述符指针指向内核栈空间。
3.4.1.4 清空进程槽其他进程的栈
task[i]=NULL //i!=0
3.4.1.4 加载当前任务描述符
ltr(0) //让TR寄存器指向第0个TSS
lldt(0) //让LDTR寄存器指向第0个LDT,对应3.4.1.1节
3.4.2 初始化时钟中断
根据系统所需要中断周期配置定时器的中断周期,并允许定时器发出相关中断。
3.4.3 初始化系统调用程序
将系统调用程序绑定到中断描述表,系统调用程序可以理解为由软件触发的中断。
3.4.4 初始化缓冲区
由于缓冲区采用了空闲表与哈希表两种管理方式,故需要两种管理结构均需要初始化。
(1)空闲表建立
将所有的缓冲块连接为一个双向链表,并清空缓冲块中的数据区。
(2)哈希表的建立
让整个哈希表的所有指针均指向空,而不是任何一个缓冲块。
3.4.5 初始化硬盘
(1)将硬盘请求与块设备请求项结构挂接
(2)将硬盘中断函数与中断描述表挂接
(3)允许硬盘发送中断
3.4.6 初始化软盘
(1)将软盘请求与块设备请求项结构挂接
(2)将软盘中断函数与中断描述表挂接
(3)允许软盘发送中断
3.4.7 开中断
sti()
开启中断后标志进程0可以启动,并且系统能够与交互外设、内存、时钟、块设备(软/硬盘)等子系统进行交互,具备了基本的运行条件,可以为系统提供基本的运算功能,但还不能为用户提供计算功能。
3.4.8 状态切换
由于所有的进程任务都只能由已有进程并在用户态创建,而当前系统还处于内核态,故需要通过模仿中断的方式进行状态的切换。
- 将用户态下的ss、esp、eflags、cs、eip所需要的数据压栈
- 执行iret
- 将压入的数据弹出给ss、esp、eflags、cs、eip。
特权等级的切换标志进程0正式启动。