先加载BIOS,然后跳转到MBR进行引导接下来的程序Loader,从实模式进入保护模式,建立全局描述符表,启用内存分页机制,之后加载内核。逐步实现了线程调度, 内存管理, 进程管理。线程调度的本质是CPU定时将就绪队列中的任务切换, 这就需要实现中断定时器。内存管理采⽤位图管理, 为每个任务维护⼀个虚拟内存池。锁采⽤信号量实现
- 操作系统: Arch Linux
- 类型: 64位
- 编译器版本: GCC 12.2.1
- nasm 版本: 2.15.05
- bochs 版本: 2.7 安装方法
- 安装gcc , bochs , dd , nasm , bximage
- 在主目录运行 make all 命令
- 在终端运行 bochs -f bochsrc命令
-
BIOS 基本输入输出, 检查各种设备的情况, 并且建立中断向量表 填写中断例程, 某些非常实用的中断是由BIOS提供的, BIOS将0柱面0磁头1扇区 的数据转移到了0x7c00位置, 并将CS:IP指向了该块位置, 默认认为0盘0道1扇区的就是启动盘, 且校验方式为看最后两个字节的魔数 是否为0x55和0xaa, 若是的话则校验成功.
-
MBR接到了接力棒, 作为主引导程序, 完成从磁盘读入Loader加载器的使命.
-
初始化GDT, 从实模式进入保护模式(4GB), 打开A20, 加载GDT, cr0寄存器第0位置1, 然后刷新流水线. 这里我们用的是平坦模型 ,用LGDT指令把GDT表的位置放了进去, 并且初始化了三个段描述符: 显示段描述符, 代码段, 数据段. 从此开始了从16位实模式到了32位模式, 开启内存分页机制, 加载内核.
-
编写打印函数
-
实现中断
-
内存管理系统
-
线程
-
进程 = 资源 + 线程
-
我们通过创建了PCB, 把我们线程所需要用到的相关内容全部放在了里面. PCB下端(我们申请的一页内存) 存放着的是我们的结构体task_struct 目前里面含有的东西不多 线程状态
特权级
边界魔数``还有我们的线程内核栈的地址. 我们的 中断栈所在位置 位于一页内存的最顶点位置 我们上下文切换时 如果外面有中断 我们就把我们所有的上下文环境放在那里 我们的内核栈 所在位置 是位于中断栈的下方 那里我们放着的是我们的4个寄存器 和 我们的 某些需要的函数地址 -
切换线程
- 先创建线程
- 打开中断 每个时钟中断调用中断函数 减去当前时间片
- 时间片为0 简称到期了 到期之后 调用schedule调度器 切换线程
- schedule 把在最前面的准备队列的任务的pcb获取 把当前的放到最后
- 之后转到switch_to 保存寄存器 上下文环境 切换esp 即切换线程
-
输入输出系统
- 同步机制--锁--采用信号量实现
- 实现线程的阻塞与唤醒
- 实现环形输入缓冲区
-
进程创建
- 先在内核中分配一页内存 为线程thread分配一页pcb
- 初始化线程后 为我们的进程的虚拟内存位图分配内存
- 创造线程 此线程为了调用start_process初始化函数 (备注 start_process中把进程的环境给初始化好 即可调用intr_exit iretd跑路 当然这是后话)
- 紧接着把进程的页表空间分配好 并且初始化好
- 把含有start_process的线程放到就绪列表中 就等着被调用了
- 线程schedule调用后 发现pgdir非空 换页表重新装载 并调用start_process
- start_process做了一大堆事情 并把分配了一页内存给栈空间 之后就jm intr_exit切换去了
- 各种pop之后 cs ip ds各种寄存器已经被切换了 进入用户进程
- 切换进程时 只需要把TSS中的esp0切换即可 即内核线程栈
-
实现系统调用
- 例如进程调用getpid()后
- 调用getpid()的函数 == 调用_syscall0宏函数 函数号放入了eax
- _syscall0中 int 0x80 引发中断 eax此时已经是函数号了
- int 0x80的中断处理函数就是syscall_handler syscall_handler的核心在call [syscall_table+eax*4] 我们的syscall_table是提前准备好的 就是我们各种系统调用的函数指针表 根据我们输入的函数号即可定位到对应的处理函数
- 之后再进入intr_exit 还原上下文 中断退出 完整的系统调用结束 程序照常进行
- 《操作系统真相还原》
- 各方博客