事件与等待机制
shell的本质
taskA wait (“TaskB”)12345678910111213141516typedef struct{ RegValue rv; //寄存器的值,记录任务执行状态 Descriptor ldt[3]; //局部段描述符表,LDT描述局部于每个程序的段,包括代码段、数据段、显存段 ushort ldtSelector; //LDT选择子 ushort tssSelector; //TSS,用于查询内核栈 void (*tmain)(); //任务起始地址 uint id; //任务id ushort current; //任务已经执行的时间 ushort total; //执行队列中 任务总共允许执行的时间 char name[1 ...
文件系统设计
虚拟硬盘bximage hd.img -q hd -mode=flat -size=80创建一个虚拟硬盘(-hd),-q表示不交互,-mode=flat连续硬盘,-size=80大小为80M。
重新配置告诉bochs 虚拟硬盘是以hd.img来展现的,CHS柱面柱头的信息
BIOS 运行后会遍历系统中的硬件, 并记录相关信息。其中, 硬盘数量被BIOS主动记录于 0x475 地址处。因此我们可以通过读取0x475地址的数据拿到硬盘数量信息
1234byte* pb = (byte*)0x475;PrintString( "Number of Hard Disk: " );PrintIntDec(*pb);PrintChar('\n');
IDEIDE 是什么?
早期,硬盘控制器和硬盘本身是分开的。软件控制硬盘控制器来读取硬盘的数据。后来,硬盘控制器和硬盘本身合二为一(Integrated Drive Electronics)。再后来, 有了别名 ATA, 然后是ATA / IDE。最初的硬盘是用并口传输数据, 所以 IDE 常指代并 ...
键盘驱动设计与shell任务
外部设备键盘 键盘的本质
键盘是一种计算机外部设备
键盘与计算机的通信(数据交互) 需要借助中断完成
键盘中断服务程序
使能主 8259A 引脚 IRQ1,让此引脚的值为0(这样才能接收键盘中断)
编写中断服务程序, 并注册到中断向量表 (由于先前让主8259A设置成20号中断开始,因此键盘中断是0x21号中断)
键盘工作原理
注意一定要将8042缓冲区的键位信息的数据给读取完才可以存储下一个摁键信息。
因此键盘中断服务程序一定要同段端口0x60去获取8042缓冲区的内容。而缓冲区存储的是键盘扫描码。扫描码指的是硬件电路对键位的编码makecode(摁下键时的扫描码)+0x80=breakcode(释放键时的扫描码)
注意C语言通过ax存储函数返回值
12345678910111213141516171819;; byte ReadPort(ushort port); ReadPort: push ebp mov ebp, esp xor eax, eax mov dx, [ebp + 8] ; 参数port赋值给dx寄存 ...
动态内存分配
动态内存管理通常在一块较大且连续的内存空间上根据需要分配内存和回收内存
计算机早期内存资源稀缺,多任务并发执行不可能将多个任务都加载到内存当中,可以通过内存复用增加任务的并发性,某一时间仅有一个任务在内存当中
因此动态内存管理的本质是时间换空间,通过动态分配和回收 “扩大” 物理内存
动态内存管理的关键
从发出内存申请到获得内存的时间越短越好为了管理内存(记录内存信息等)而占用的内存越少越好最大可分配内存占空闲内存总和的比例越大越好,即尽量避免外部碎片
动态内存管理的分类
定长内存管理:将内存分为大小相同的单元, 每次申请一单元的内存(实时性高,效率高)
变长内存管理:每次申请需要的内存(大小不固定, 以字节为单位)
定长内存管理的设计与实现将内存分为两部分:管理单元(4bytes)&分配单元(32bytes),每次分配仅仅分配32bytes
管理单元应该与分配单元一一对应,n = lengthof(管理单元+分配单元)/36
申请内存:
从链表头(全局变量gFMemList的node成员)获取一个管理单元并且计算下标,根据下标计算分配单元的内存起始地址
归还内存: ...
多进程调度
前言先前我们实现了多个任务并行执行。然而没有考虑任务结束后应该怎么办。因此存在两个问题:
iret跳转到进程入口函数执行并非函数调用(没有将返回地址压入栈的操作),函数无法返回,也就无法销毁此任务
当前任务主动结束后应该如何处理此任务,如何具体地销毁当前任务并且执行下一个任务?
销毁任务KillTask增加可销毁任务的返回点当前方案直接通过iret(中断处理降低特权级)跳转到进程入口函数执行,并非函数调用,因此无法正确返回! ! !
因此构造一个函数即可,让这个任务结束后能到达一个我们可以销毁已结束任务的地方
改进思路:
*Task结构体中新增void(tmain)()成员(保存任务入口函数地址)
12345678910111213typedef struct{ RegValue rv; Descriptor ldt[3]; ushort ldtSelector; ushort tssSelector; void (*tmain)(); uint id; ushort current; ...
多进程并行
前言时钟中断前会保存当前任务(gTask)信息,时钟中断后会恢复当前任务(gTask)信息。那么可以借助时钟中断函数,时钟中断函数设置当前任务为另一个任务(修改gTask指针指向另一个任务),这样就可以实现任务切换了
两个任务并行执行思路
启动时钟中断
启动 Task A 并打开中断开关
在时钟中断服务程序中使得 gCTaskAddr 指向 Task B
Task B 执行(中断开关已打开)
在时钟中断服务程序中使得 gCTaskAddr 指向 Task A
再再论TSS-共享TSS目前的设计中 TSS, 使用 TSS 的唯一意义仅是转入高特权级时获取栈的位置。 (潜台词: 仅仅在中断发生时, 将 ss0 和esp0 指定的内存当作初始内核栈。
而注意先前如何表示一个进程的,所有任务可以共享同一个TSS结构体,但是LDT还是每个进程私有的不可共享
12345678910111213141516171819202122232425262728293031323334volatile Task* gCTaskAddr = NULL; //注意设置volatile避免编译器优化 ...
内核的中断处理
借助中断实现任务切换
可使用时钟中断打断任务(每个任务执行固定时间片)
中断发生后立即转而执行中断服务程序 (ISR)
在中断服务程序中完成任务上下文保存(寄存器)及任务切换
解决方案的实现基础
建立并加载中断描述符表 (IDT)
编写时钟中断服务程序 (ISR)
初始化 8259A 并启动时钟中断(这种外部设备中断一般都要借助8259A)
初始工作-IDT及相应操作函数loader.asm中,选择子是Code32Selector,默认处理函数DefaultHandler,之后需要用到中断的时候将offset修改为对应的中断处理函数即可sfunc字段存储放上与中断相关的函数gfunc存储C语言调用的函数,这些函数地址需要放到交换区,给kernel调用
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 ...
进程实现
任务定义(进程定义)
任务状态指的是任务执行时各个寄存器的状态
进程表示如何在计算机表示一个进程Task?使用C语言表示大致如下:
123456789101112131415typedef struct{ RegValue rv; //寄存器的值,记录任务执行状态 Descriptor ldt[3]; //局部段描述符表,LDT描述局部于每个程序的段,包括代码段、数据段、显存段 ushort ldtSelector; //LDT选择子 ushort tssSelector; //TSS,用于查询内核栈 void (*tmain)(); //任务起始地址 uint id; //任务id ushort current; //任务已经执行的时间 ushort total; //执行队 ...
bootloader到内核雏形
bootloader重构boot程序(主引导程序512字节以内)必须加载loader文件到内存,并且跳转到loader执行
loader作为加载器主要功能:通过BIOS获取硬件信息,之后加载内核,进入内核的保护模式
bootloader合起来就是加载真正的操作系统内核Kernel,操作系统内核使用汇编与C语言实现
既然如此为什么需要一个中转loader,不直接boot跳转到kernel呢?因为boot小于512字节,无法完成过多功能(比如全局段描述表建立和初始化等等)
boot.asm和loader.asm都需要加载某个其他文件的功能,因此可以将此功能独立出来放到文件blfunc.asm中
common.asm 定义常量和宏
blfunc.asm 实模式下的文件加载功能定义
boot.asm加载loader并且跳转到引导扇区
loader.asm 必要的硬件初始化,加载kernel,进入保护模式并且跳转到kernel
最后文件架构如下所示:
loader文件被加载到0x9000处,因此si开始指向0x9000处,一个扇区512字节,每次加512字节si迟早得溢出变为0,这也就导 ...
C与汇编混合编程
前置C语言函数入栈
使用汇编语言编写 Linux 可执行程序定 义 _start 标签作为程序执行的起点
通过 int 0x80 使用内核服务 (执行系统调用)
12345678910111213141516global _start[section .data] vstr db "I'm fengyun!", 0x0A[section .text]_start: mov edx, 13 mov ecx, vstr mov ebx, 1 mov eax, 4 int 0x80 mov ebx, 0 mov eax, 1 int 0x80
1234fengyun@ubuntu:~/share/os$ nasm -f elf entry.asm -o entry.ofengyun@ubuntu:~/share/os$ ld -m elf_i386 -s entry.o -o app.outfengyun@ubuntu:~/share/os$ ./app.out I'm fe ...