2016.8月做的笔记,偶然翻到以防后面丢失。最后的操作系统代码 上传GitHubhttps://github.com/Dandelionshine/30OS
- console:控制台 通过键盘输入命令的一种方式。基本上只用文字进行计算机操作。
- 开发操作系统:制作一张“含有操作系统的,能够自动启动的磁盘”
- 映像文件:软盘的备份数据。做出备份数据,将备份数据写入磁盘
世界上第一个操作系统:对着CPU的命令代码表,将0,1排列,将数据写入磁盘。之后利用它开发后面的操作系统
汇编:
计算机读写软盘按照一个个扇区进行读取,一张软盘空间为1440KB(512*2880),共2880个扇区,每个扇区512个字节.软盘第一个扇区称为启动区(boot sector)
计算机从最初一个扇区开始读软盘,然后去检查这个扇区最后2个字节是否为55AA,若为,则计算机认为这个扇区的开头是启动程序,并开始执行这个程序
IPL(启动程序加载器 initial program loader):大部分将加载操作系统本身的程序放在启动区里
启动(boot)
机器语言中寄存器的编号顺序:AX,CX,DX,BX
软盘的磁面不能摸
与光盘不同,软盘磁盘是两面记录数据
EBX->4G内存,CPU能处理的最大内存量
Makefile可以使用简单的变量
- 系统装载时自动装载启动区
- 进入32位模式并导入C语言 2M内存,段寄存器,读写磁盘
- 制作真正的IPL(启动程序装载器),
- 调用磁盘BIOS后,返回值存在进位标志里,若无错,则CF=0,AH=0,若有错,则CF=1,AH=错误号码
- 试错
- 软盘不可靠,有时不能读数据,尝试再读,但一直重试磁盘会真的坏掉,决定重试5次
- 读到18扇区
- 循环读取2-18扇区,没有采用直接读取17个扇区,方便以后循环读取其他磁道以及反面的磁道
- 读入10个柱面
- 着手开发操作系统
- 将文件1.sys保存到磁盘映像1.img里,即:
- 使用make install指令,将磁盘映像文件写入磁盘
- 在Windows里打开那个磁盘,把1.sys保存到磁盘上
- 使用工具将磁盘备份为磁盘映像
- 一般向一个空软盘保存文件时
- 文件名会写在0x002600以后的地方
- 文件的内容会写在0x004200以后的地方,即内存里0x8000+0x4200=0xc200, 故将操作系统本身的内容写在1.sys文件中,再把它保存到磁盘映像里,在从启动区执行1.sys就行了
- 一般向一个空软盘保存文件时
- 将文件1.sys保存到磁盘映像1.img里,即:
- 从启动区执行操作系统
- 启动区内容最后跳到0xc200(操作系统的内容)
- 确认操作系统的执行情况->颜色模式
- 32位模式前期准备->设置画面模式并保存在内存里,从BIOS得到键盘状态
- 32位模式指的是CPU的模式,16位模式使用32位寄存器(EAX)比较麻烦。
- 模式不同,机器语言的命令代码不同。16位模式的机器语言在32位模式下不能运行,反之亦然
- CPU的自我保护功能(识别出可疑的机器语言并进行屏蔽以免破坏系统)在16位下不能用,但32位能用
- 32位模式不能调用BIOS(16位机器语言写的),故调用BIOS放在开头做
- 开始导入C语言-》切换到32位模式
- C语言的不便之处,生成的目标文件是一种特殊的机器语言文件,必须与其他文 件链接后才能成为真正可以执行的机器语言文件
- 编译器内部:C语言->机器语言
- 使用ccl.exe(C编译器,gcc以gas汇编语言为基础,输出gas用的源程序),1.c->1.gas
- gas2nask.exe,1.gas->1.nas
- nask.exe,1.nas->1.obj,目标文件必须与其他文件进行链接
- obj2bim.exe,1.obj->1.bim,将必要的目标文件全部链接,生成二进制映像文件binary image
- bim2hrb.exe,1.bim->1.hrb,针对不同操作系统进行必要的加工
- 实现HLT
- C语言与画面显示的练习
- 用C语言实现内存写入
与C语言联合使用,能自由使用的寄存器只有EAX,ECX,EDX3个,其他寄存器只能使用其值,而不能改变其值
CPU(英特尔系列)家谱:
8086->80186->286(16位)->386(32位)->486->Pentium->PentiumPro->PentiumII->PentiumIII->Pentium4->… - 条纹实现
- 挑战指针
- 色号设定
- 绘制矩形
- 用C语言实现内存写入
- 结构体、文字显示与GDT/IDT初始化
- 接收启动信息
- 显示字符->1个字符是16个字节
- 显示变量值->自制操作系统中不能随便使用printf函数(按指定格式输出),但可以使用sprintf(将输出内容作为字符串写在内存中),只对内存进行操作,应用于所有操作系统
- 显示鼠标指针->16*16字节
- GDT与IDT的初始化
- 采用分段,32位模式下,段寄存器仍为16位,由于设计上的原因,段寄存器的低3位不能使用,段号0-8181
- CPU用8个字节表示段的信息
- GDT(global descriptor table):全局段号记录表,记录段号与段的对应关系,存放在内存中;
- 内存的起始地址和有效设定个数放在CPU内的GDTR的特殊寄存器中,GDTR低16位表示段上限,高32位表示GDT的开始地址
- IDT(interrupt descriptor table):中断记录表,记录了0-255的中断号码与调用函数的对应关系。
- 中断:当CPU遇到外部状况变化,或者是内部偶然发生某些错误时,会临时切换过去处理这些突发事件。
- 由于中断,CPU不用一直查询键盘、鼠标、网卡等设备的状态
- 分割编译与中断处理
- 分割源文件
- 整理Makefile,Makefile一般规则,普通生成规则优于一般规则,文件可使用单独的规则进行编译
- 整理头文件
- 双引号:头文件与源文件在同一个文件夹下;尖括号:头文件位于编译器所提供的文件夹里
- CPU处于系统模式还是应用模式,取决于执行中的应用程序是位于访问权为0x9a的段还是0xfa。
- 初始化PIC(programmable interrupt controller):可编程中断控制器:将8个中断信号集合成一个中断信号的装置。
- PIC监视输入管脚的8个中断信号,只要有1个,则将唯一的输出信号变成ON,并通知给CPU;
- 主PIC负责0-7号中断,从PIC负责8-15号中断。从PIC仅能通过连接主PIC的第2号IRQ通知CPU。
- 以INT0x20-0x2f接收中断信号IRQ0-15;
- PIC寄存器8位,IMR(interrupt mask register):中断屏蔽寄存器,若某位为1,PIC就忽视该信号。
ICW(initial control word):初始化控制数据,4个,1-4,ICW2决定IRQ以哪一号中断通知CPU。
- 中断处理程序的制作
- 鼠标:IRQ12,键盘:IRQ1
- FIFO与鼠标控制
- 获取按键键码
- 加快中断处理 键盘缓冲区存储一个字节 右Ctrl两个字节
- 制作FIFO缓冲区 32个字节
- 改善FIFO缓冲区 循环数组,不需数据移送
- 整理FIFO缓冲区 鼠标移动3个字节数据
- 鼠标:先有效鼠标控制电路,在有效鼠标
- 从鼠标接受数据
- 鼠标控制与32位模式切换
- 鼠标3个字节数据,第一个字节:点击,移动;第二个字节:左右移动;第三个字节:上下移动
- 内存管理
- 只有在内存检查时才将缓存设为OFF
- 内存管理:内存分配,内存释放;
- 内存管理:表或者列表,表采用将内存分为4KB的大小,用表记录每块是否空闲;本系统采用了列表方式,列表记录可用内存的地址及大小。
- MEMMAN,当可用空间很零散时,暂时先割舍掉小块内存,当MEMMAN有空闲时,再对使用中的内存进行检查,将割舍掉的捡回来
- 叠加处理
- 内存以0x1000=4KB进行分配,释放;与运算只能用于二进制数向下舍入处理,在以2^n以外的数进行向下,上舍入处理时必须使用除法指令
- 叠加处理:鼠标,窗口叠加,将图层按照高度升序排列,并将其地址写入sheets;从下往上描画图层;
- 提高叠加处理速度:图层移动,对整个画面进行刷新->只描画移动前后的部分
- 制作窗口
- 鼠标显示:鼠标移到画面之外隐藏
- 显示窗口
- 高速计数器-闪烁
- 消除闪烁->仅对refresh对象及以上的图层进行刷新
- 消除闪烁->刷新窗口时避免鼠标所在的地方对VRAM进行写入处理
- 定时器:每隔一段时间发送一个中断信号给CPU
- 管理定时器->设定PIT(可编程的间隔型定时器),PIT连接着IRQ的0号
- 中断产生的频率=单位时间时钟周期数(即主频)/设定的数值,设值为11932(0x2e9c)-》中断频率为100HZ,即1s发生100次中断
- 超时功能-》计量处理所花费的时间:处理前时间存放,处理结束后时间存放,计算时间差
- 定时器2
- 简化字符串显示
- 所有定时器使用一个缓冲区,每个定时器写入的数据不同
- 重新调整FIFO缓冲区-》键盘、鼠标、定时器仅使用一个缓冲区
- 写入FIFO的数值 中断类型
- 0-1 光标闪烁用定时器
- 3 3秒定时器
- 加快中断处理
- 取代移位处理:1.改变下一次的读取地址 2.用next链表实现
- 使用“哨兵”简化程序-》在最后加上一个永远也到不了的定时器
- 高分辨率及键盘输入
- VBE:使用高分辨率显卡;不使用VBE:AH=0;AL=画面模式号码; 使用VBE:AX=0x4f02;BX=画面模式号码+0x4000;
VBE的画面模式号码:
0x101–6404808bit彩色
0x103–8006008bit彩色
0x105–10247688bit彩色
0x107–128010248bit彩色** - 键盘输入–根据键盘缓冲区得到的键码在图层上显示相应的字符
- 窗口移动–将窗口图层移到鼠标附近
- VBE:使用高分辨率显卡;不使用VBE:AH=0;AL=画面模式号码; 使用VBE:AX=0x4f02;BX=画面模式号码+0x4000;
- 多任务:多个应用程序同时运行,操作系统尽可能快地切换任务,切换任务就是执行JMP指令;
- 任务切换:A任务->B任务,设置任务的TSS,在GDT中设定,初始化寄存器;
任务切换:CPU把寄存器的值全部写入内存;然后CPU将下一个程序有关的寄存器值从内存中读取出来
任务切换所需的时间=内存写入和读取操作的时间
任务状态段(task status segment)TSS,使用32位版。26个int成员,104字节;
EIP:CPU记录下一条执行的指令在内存中的地址;
JMP若跳转到TSS,则进行任务切换;CPU执行带有段地址的指令时,去GDT里确认是否执行任务切换;
TR寄存器:CPU记住当前正在运行哪一个任务,TR=GDT的编号*8; - .任务切换:返回到A任务
- 简单的多任务-》计数,刷新次数多,计数慢,背景图层的地址放在内存中共享
- 提高运行速度-》每隔0.01秒刷新一次;背景图层的地址放在该任务的栈中最后,作为传递的变量;
- 多任务切换函数设置定时器,2个任务自动进行切换,在定时器中断里若控制任务切换的定时器发生中断,则进行任务切换;
- 任务切换:A任务->B任务,设置任务的TSS,在GDT中设定,初始化寄存器;
- 多任务2
- 任务管理自动化,任务管理的结构体
- 让任务休眠–将任务从tasks中删除,则任务处于休眠;但FIFO有数据过来,将任务唤醒-运行一次task_run,进入等待状态
在fifo中加入要唤醒的任务 - 增加窗口数量-》任务A,B0-3,任务切换间隔固定为0.02s
- 设定任务优先级-》时间片轮转调度算法:为每个任务在0.01s-0.1s的范围内设定不同的任务切换间隔,数值越大,计数速度越快
- 设定任务优先级-》高优先级的任务同时运行-混乱-》
- 把任务分到Level0、Level1、Level2这三层中的一个,当Level0有活动的任务(非休眠状态)时,只在Level0的任务间切换。
- 当Level0没有任务或均处于休眠状态时,在Level1的任务间切换。Level2同理。
- 命令行窗口
- 闲置任务–所有LEVEL都没有任务,在最下层LEVEL中加一个闲置任务(只执行HLT)
- 创建命令行窗口–光标
- 切换输入窗口–按下“TAB”键,将输入窗口切换到命令行窗口
- 改变窗口标题栏颜色—key_to为0,键盘输入发送到任务A,为1则发送到命令行任务
- 实现字符输入—key_to为0,键盘输入发送到任务A,为1则发送到命令行任务的缓冲区
- 符号的输入—实现!,%;key_shift变量,当左shift按下时置1,右shift按下时置2,两个都不按时置0,两个都按下时置3;
- 大写字母与小写字母–同时判断shift和CapsLock的状态
- 从BIOS获取到了键盘状态,保存在binfo->leds,第4位:ScrollLock状态,第5位:NumLock状态,第6位:CapsLock的状态;
- 对各种锁定键的支持—向键盘控制器发送数据(数据放在新的缓冲区中)
- dir命令
- 控制光标闪烁–只有可以接受键盘输入的窗口有光标闪烁,
- 控制光标闪烁–控制命令行窗口中光标闪烁,按下TAB,在命令行任务的FIFO中放入2,光标开始闪烁;放入3,光标停止闪烁;
- 对回车键的支持–cursor_y记录光标的纵坐标;空格消除光标,cursor_y+=16;
- 对窗口滚动的支持–将所有的像素向上移动一行,并将最下面一行涂黑
- mem命令:显示内存使用情况,用cmdline依次记录键盘输入的内容
- cls命令:清屏;将图层所有像素涂黑
- dir命令:显示磁盘内文件名,文件的日期和大小;
- 用BIOS读取磁盘;在ipl10.nas已读取10个柱面的内容存放在内存中;
- 文件信息(32字节):
struct FILEINFO {
unsigned char name[8], ext[3], type;/文件名(8),扩展名(3),文件的属性信息(1)/
char reserve[10];/保留/
unsigned short time, date, clustno;/时间,日期,簇号(文件的内容从磁盘上的哪个扇区开始存放)/
unsigned int size;/文件大小/
};
文件名的第一个字节为0xe5,表示这个文件已经被删除;文件名第一个字节为0x00,代表这一段不包含任何文件信息;
文件的属性信息:0x01—只读文件;0x02–隐藏文件;0x04–系统文件;0x08–非文件信息(比如磁盘名称);0x10–目录;
多种属性值相加;一般为0x20或0x00
- 应用程序
- type命令:type 文件名:显示文件的内容;
磁盘映像中的地址=clustno * 512+ 0x003e00; - type命令改良–处理换行和制表符的字符编码
0x09–制表符:显示空格直到x被4整除;当前位置到下一个制表位之间没有填充空格;
0x0a–换行符:换行
0x0d–回车符:忽略;让文字显示的位置回到行首
实现的type可以正确显示文件开头的512字节的内容。若大于512字节,中间可能突然显示其他文件的内容。 - 对FAT的支持
大于512字节的文件可能并不是存入连续的扇区(磁盘碎片)。Windows中的磁盘整理工具,用来将扇区内容重新排列以减少磁盘碎片的程序。
文件分配表(FAT):磁盘中记录文件的下一段存放在哪个扇区;FAT位于从0柱面、0磁头、2扇区-10扇区;在磁盘映像中0x00200-0x0013ff;
FAT中以3字节为一组,扇区号对应的数字即是clustno,里面存储的数字即是该文件下一段存放的扇区。一般来说,FF8-FFF,代表文件数据到此结束。
FAT中一个地方损坏,之后的部分就会全部混乱。故磁盘中存放了2份FAT(第2份是备份FAT,内容与第1份完全相同) - 应用程序
为每个应用程序创建一个内存段,并用file_loadfile将文件内容读到内存中,然后goto到该段中的程序(farjmp)。
GDT:1-2:dsctbl.c;3-1002:mtask.c;1003:hlt.hrb;
- type命令:type 文件名:显示文件的内容;
- API(系统调用:由应用程序对操作系统功能的调用)
2. 显示单个字符的API:应用程序调用cons_putchar;
3. 显示单个字符的API2:应用程序对API要执行far-CALL;far-RET;
4. 结束应用程序:应用程序结束后返回操作系统(RETF),操作系统通过far-CALL调用应用程序;
5. 不随操作系统版本而改变的API:–通过中断来调用API- IDT(中断号码与函数的对应)仅32种中断,IDT中0x30-0xff均空闲,将asm_cons_putchar注册在0x40;
- 故INT 0X40就能调用asm_cons_putchar;但中断无法通过RETF返回,需用IRETD.
- 为应用程序自由命名
- 当心寄存器 加上PUSHAD;POPAD;
- 用API显示字符串–1.显示一串字符遇到字符0结束;2.指定好字符串的长度再显示
- 借鉴BIOS的调用方式,在寄存器中存入功能号(EDX),只用一个INT选择调用不同的函数;
- 功能号1–显示单个字符(AL=字符编码)
- 功能号2–显示字符串0( EBX=字符串地址)
- 功能号3–显示字符串1(EBX=字符串地址,ECX=字符串长度)
- 保护操作系统
- 字符串显示API—应用程序的起始地址通过内存传递
- 用C语言编写应用程序
- 凡是通过bim2hrb生成的hrb文件,其第4-7字节一定为Hari;
- 保护操作系统
- 保护操作系统2–为应用程序提供专用的内存空间
- 创建应用程序专用的数据段,并在应用程序运行期间,将DS,SS指向该段地址
- 操作系统用代码段–28
- 操作系统用数据段–18
- 应用程序用代码段–10038
- 应用程序用数据段–10048
- 38-10028为TSS使用的段
- CPU可以自动进行复杂段切换;
- 对异常的支持–强制结束程序–在中断号0x0d中注册
- 当应用程序试图破坏操作系统,或者试图违背操作系统的设置时,自动产生0x0d中断(一般性异常)
- 保护操作系统3–应用程序用汇编直接将操作系统的段地址存入DS
- 保护操作系统4–应用程序无法使用操作系统的段地址
- 在段定义时,访问权限+0x60,将段设置为应用程序用。当CS中为应用程序用段地址时,则DS无法存入操作系统用的段地址
但必须在TSS中注册操作系统用的段地址和ESP; - 不可以从操作系统CALL/JMP应用程序,但可以从应用程序CALL操作系统
- 在段定义时,访问权限+0x60,将段设置为应用程序用。当CS中为应用程序用段地址时,则DS无法存入操作系统用的段地址
- 用C语言编写应用程序
- 保护操作系统5–随意更改定时器·
当以应用程序模式运行时,执行IN指令和OUT指令都会产生一般保护异常。通过修改CPU设置,可允许应用程序使用IN指令和OUT指令。
当以应用程序模式运行时,执行CLI/STI/HLT这些指令都会产生异常。调用任务休眠API来实现省电。
CPU规定除了设置好的地址之外,禁止应用程序CALL其他的地址。 - 帮助发现bug-知道引发异常的指令的地址
CPU的异常处理功能,保护操作系统,帮助应用程序发现bug
栈异常中断号0x0c;
产生异常时寄存器的值:
esp[0-7]为asm_inthandler中PUSHAD的结果:
esp[0]:EDI;esp[1]:ESI;esp[2]:EBP;esp[3]:ESP;esp[4]:EBX;esp[5]:EDX;esp[6]:ECX;esp[7]:EAX;
esp[8-9]为asm_inthandler中PUSH的结果:esp[8]:DS;esp[9]:ES;
esp[10-15]为一场产生时CPU自动PUSH的结果:
esp[10]:错误编号(基本上是0);esp[11]:EIP;esp[12]:CS;esp[13]:EFLAGS;esp[14]:ESP(应用程序用ESP);esp[15]:SS(应用程序用SS); - 强制结束应用程序–强制结束键-shift+F1
当应用程序运行时(task_cons->tss.ss0不为0),按下强制结束键结束程序; - 用C语言显示字符串—由bim2hrb生成的hrb文件运行;
数据段的大小根据.hrb文件中指定的值进行分配
将.hrb文件中的数据部分先复制到应用程序的数据段后再启动程序; - 应用程序显示窗口
API:EDX=5;EBX=窗口缓冲区;ESI=窗口在x轴上的大小;EDI=窗口在y轴上的大小;EAX=透明色;ECX=窗口名称;
返回值EAX=用于操作窗口的句柄(用于刷新窗口等操作); - 在窗口中描绘字符和方块
显示字符的API:EDX=6;EBX=窗口句柄;ESI=显示位置的x坐标;EDI=显示位置的y坐标;EAX=色号;ECX=字符串长度;EBP=字符串;
描绘方块的API:EDX=7;EBX=窗口句柄;EAX=x0;ECX=y0;ESI=x1;EDI=y1;EBP=色号;
- 保护操作系统5–随意更改定时器·
- 图形处理相关
- 编写malloc-API
- 本系统采用为应用程序事先多分配一点数据段内存空间,malloc时从多余的空间里拿出一部分。
- 画点–API
- 刷新窗口
- 在所有的窗口绘图命令中设置一个不自动刷新的选项(窗口句柄(图层地址,一定为偶数)设为奇数(在原来的数值上加1)),再编写一个仅用来刷新的API;
- 画直线
- API–指定直线两端的坐标,自动计算出dx,dy,len并画出直线;
- 目前不支持浮点数,将坐标预先扩大1000倍,速度:除以1024,
- 关闭窗口–将窗口图层高度设为-1,并标为未使用;
- 键盘输入API–当按下回车键时再结束运行应用程序;等待键盘输入过程中-休眠;
- 键盘输入–实现上下左右移动
- 强制结束并关闭窗口–按下shift+F1强制结束应用程序
- 当应用程序结束时,查询所有的图层,若图层的task为将要结束的应用程序任务,则关闭该图层。
- 编写malloc-API
- 窗口操作
- 窗口切换–按下F11(0x57),将最下面的窗口放在最上面
- 窗口切换2–用鼠标点击来切换窗口
- 从上到下判断鼠标的位置落在哪个图层的范围内,并确保该位置不是透明色区域。
- 移动窗口
- 当鼠标左键点击窗口的位置位于窗口的标题栏区域,则移动;放开鼠标左键时,退出移动;
- 用鼠标关闭窗口
- 若点击了x按钮,结束程序;
- 将输入切换到应用程序窗口
- 按下tab键时将键盘输入切换到当前输入窗口下面一层的窗口中,若当前窗口为最下层,则切换到最上层窗口。
- 若应用程序窗口处于输入模式被关闭,让系统自动切换到最上层的窗口;
- 用鼠标切换输入窗口–按下左键
- 定时器API–应用程序使用定时器;每1秒定时器发送128(不是键盘编码),计时
- 取消定时器–在应用程序结束的同时取消定时器 ,定时器标记区分是否需要在应用程序结束时自动取消
- 增加命令行窗口
- 蜂鸣器发声–PIT控制
PIT时钟与CPU无关,频率恒定为1.19318MHz;
2.为窗口移动提速 - 提高窗口移动速度–改进refreshamap;
接收到鼠标移动数据后不立即进行绘图操作,等FIFO为空时再进行绘图操作; - 启动时只打开一个命令行窗口–随意启动新命令行窗口
按下shift+F2打开一个新的命令行窗口; - 关闭命令行窗口
在命令行中输入exit命令关闭当前窗口–释放窗口占用的内存,释放窗口的图层和任务结构
exit命令:取消光标定时器,FAT用的内存释放,调用close_console(从命令行任务想task_a发送一个数据,请task_a帮忙关闭命令行任务) - 关闭命令行窗口–鼠标关闭命令行窗口
当鼠标点击窗口上的X按钮,向命令行窗口任务发送4;命令行任务接受到4后开始执行exit命令;
在应用程序运行的时候,点击命令行窗口的X按钮不会关闭命令行窗口; - start命令–打开一个新的命令行窗口并运行指定的应用程序
- ncst命令–不打开新命令行窗口的start命令;
用ncst命令启动的应用程序会忽略字符串显示的API的调用;
不显示命令行窗口,无法输入其他命令,在命令执行完毕时立即结束命令行窗口任务;
29.
2.文件压缩–将压缩好的文件解压缩
- 蜂鸣器发声–PIT控制