实现一个简陋操作系统的相关笔记日志


2015年 01月 22日 星期四 16:48:52 CST (vi的`:r!`命令真心好用,可将外部命令的执行结果插入文字中来)
今天乘着将近两个月的寒假将于渊的《一个操作系统的实现》这本书看一遍,在刚过去的半年里刚开始时看了前两章,但由于看到保护模式那一块时感到困难也没了心情,因此搁置了。现在操作系统原理和计算机组成已粗略学过了,是时候照着这本书实践一下了。
$表示当前行被汇编后的地址
$$表示一个section的开始被汇编后的地址。
BIOS会将引导扇区中的内容加载到内存,起始地址为0000:7c00,然后跳到此处执行。引导扇区以0xAA55结束。
bochs的强大之处或许在于其可以调试系统,但是发现安装起来有些麻烦,按照书上的教程会有一些依赖库等问题。qemu也很好用,界面简洁,qemu -fda a.img
BIOS Interrupt (INT) 0x13 Function 0 - Reset Floppy Disk
INT 0x13/AH=0x0 - DISK : RESET DISK SYSTEM
AH = 0x0
DL = Drive to Reset
Returns:
AH = Status Code
CF (Carry Flag) is clear if success, it is set if failure

BIOS Interrupt (INT) 0x13 Function 0x02 - Reading Sectors
INT 0x13/AH=0x02 - DISK : READ SECTOR(S) INTO MEMORY
AH = 0x02
AL = Number of sectors to read
CH = Low eight bits of cylinder number
CL = Sector Number (Bits 0-5). Bits 6-7 are for hard disks only
DH = Head Number
DL = Drive Number (Bit 7 set for hard disks)
ES:BX = Buffer to read sectors to
Returns:
AH = Status Code
AL = Number of sectors read
CF = set if failure, cleared is successfull

GdtPtr dw GdtLen-1 ;GDT limit
dd 0 ;GDT base addr
上边的实际为一句指令,定义了一个变量GdtPtr,并直接(direct)给其赋值,word是2Bytes,double是两倍的,即4bytes,故定义了这个结构体,低16位为界限(ds寄存器),高32位为基地址
lgdt [GdtPtr]将该机构体变量的值加载到寄存器gdtr中,gdtr与GdtPtr结构相同。
保护模式下中断的处理机制不同,需要关中断,指令:cli
为进入保护模式还需将cr0寄存器的第0位(PE位)置为1
%macro宏定义多行,如:
%macro Descriptor 3 ;指明接受3个参数
push %1 ;%1为第一个参数
push %2
...
%endmacro
读写端口in/out需要时间,所以可以看到代码中加了空操作nop以便有微小的延迟
软盘FAT12:FAT项(entry)位于FAT表,FAT表有两个,FAT2可看做FAT1的备份,FAT表占9x2=18个扇区,每个FAT项为12位,其存储的值是文件的下一个簇号(若将一簇设置为一个扇区,则簇号就是扇区号了),若值大于等于0xFF8,则表示当前簇已经是本文件的最后一个,若值为0xFF7,表示坏簇。FAT表后面是根目录区,存放的是文件的属性信息,再后面是数据区,存放着真正的文件的正文内容。
在进行堆栈操作时改变的是ss:sp的值(push,call,int,retf),sp指向的总是称为栈顶,不应该随便乱改,除非你使用sub esp 2等来开辟空间保存数据,通常在访问栈中数据时将sp的值传给bp,通过bp访问栈中任意数据。(bp的段也是ss)
si的默认段为ds,di的段为es
GDT/LDT描述符的基地址编写时不知道(除了显存首地址知道为0xB8000,记得后边多加了一个零,即20位),要在运行时填充:cs×16+LABEL_SEG_CODE32即为真实的物理地址
lodsb:Load a byte from source %esi to destination %al
lodsb将栈的某byte内容送入al, ds:si->al
cli:clear interrupt flags
cld:clear flag:DF(display)
由于保护模式使用段描述符来保存段信息而不是像实模式一样直接使用段地址,在段描述符中就可以添加一些属性来限制对段的访问权限。这样,通过在访问段时检查权限和属性,就能做到对程序段的更完善保护和更好的内存管理。
x86 使用全局描述符表(GDT)和局部描述符表(LDT)来实现不同需求下对程序段的控制,操作系统使用唯一的一个 GDT 来维护一些和系统密切相关的段描述符信息,为不同的任务使用不同的 LDT 来实现对多任务内存管理的支持,简化了任务切换引起的内存切换的难度
在将控制权从一个代码段转移到另一个代码段之前,目标代码段的段选择子必须被加载到 CS 中。处理器在这个过程中会查看目标代码段的段描述符以及对其界限、类型和特权级进行检查。如果没有错误发生,CS 寄存器会被加载,程序控制权被转移到新的代码段,从 EIP 指示的位置开始运行。

2015年 02月 13日 星期五 10:51:58 CST
读到第七章,tty
开机的默认显示模式是80x25文本模式(25行,每行80个字符),显存大小为32KB,占用范围0xB8000~0xBFFFF.每2字节代表一个字符,低字节为字符的ASCII码,高字节表示字符的属性,从高位到低位为:高亮/R/G/B(背景)/高亮/R/G/B(前景)/8位ASCII
前景最高位为1的话,字符颜色会比0的亮一些,背景最高位为1的话,字符将闪烁。
一个屏幕映射到显存中所占的空间大小就很容易计算:80*25*2=4000字节(4KB),故可以放32/4=8个屏幕的数据,如果我们让一个tty占10kbyte,则可以实现三个,每个都可以滚屏

2015年 02月 14日 星期六 00:25:02 CST
尝试开启分页机制,导致无限重启,实验发现SetupPaging这个函数不能放在call它的代码之前,否则导致操作系统无限重启,发现这里的函数的定义都是在调用之后,从此可以看出汇编器对代码的检查程度有限,逻辑错误不会在变异时发现而是在运行时.

2015年 02月 15日 星期日 15:47:41 CST
标志寄存器(16位FLAGS,32位EFLAGS)用来保存一条指令执行结束后,CPU所处的状态和运算的特征,其中的很多位被称为标志位ZF,OF,SF,CF等等
spurious:假的,伪造的

2015年 02月 16日 星期一 12:42:53 CST
昨晚一晚上在检查代码,由于bochs中运行系统总是重启。糟糕的是自己安装的bochs没有调试功能,bochs的源码也没有,上网不便。而且自己把bochs的所有输出都转到了/dev/null.今天才想起bochs的输出包含寄存器信息,异常信息等,查看发现还是很详细的,有段选择子,段界限等,jmp跳转时检查cs有没有超出范围,LDT的索引有没有超出LDT.limit.关键信息是在hadware reset之前:exception():3rd exception with no resolution,遂上网查询,OSDev Wiki中写道:
The CPU didn't manage to invoke an exception handler and would normally triple fault.This is probably due to a bad IDT register content,or a bad IDT descriptor.Sometimes(but less likely),it can also be due to a severebug in your exception handler code.Check your exception works with "illegal" ASM instructions.
于是仔细查看idt相关的代码,从它的定义到赋值再到加载,同时测试异常及硬件中断的处理程序,到了最后才发现自己在加载ldt时用了lidt指令,将idt给毁了。现在才发现等宽字体中l,i也难以区分。
虽然话费了很多时间,但收获很丰富。
2015年 02月 18日 星期三 16:21:58 CST
发现键盘处理代码部分CAPS_LOCK键有问题,静态变量caps_lock的值竟然在不断的变化,通过改变这个变量为非静态的或改变这个变量的相对位置,这个值就默认为0了,大小写输入就正常了。遂怀疑是发生了栈溢出,将该变量对应的内存地址的内容改变了。于是想到给用户进程分配栈空间的代码才发现少分配了一个进程的。导致给最后一个进程分配的sp寄存器值为一个负数转成u32变成的很大的整数,但是溢出到哪里去了却不知。
2015年 02月 19日 星期四 16:59:46 CST
大年初一头一天,雪花纷纷。
明白了键盘输入缓冲区中一开始就有一个0xFA的缘故,因为键盘初始化LED灯时往8048编码器的0x60端口发送LED的设置命令0xED,键盘会先回复一个ACK(0xFA),等待你往0x60端口写入LED参数,低三位控制三个灯。键盘收到后再回复一个ACK,便开始设置。

2015年 02月 21日 星期六 18:25:17 CST
在将get_ticks系统调用改成微内核形式的消息同步机制后,sleep等函数就崩溃了。表现在循环调用get_ticks时调用上几十遍时就发生#GP保护异常,而原来的直接返回ticks的形式的系统调用正常,说明系统调用的汇编入口部分对中断重入的处理不当,猜测是由于新的开中断期间的代码执行时间较长导致重入过多。遂直接将开中断的代码去掉,也就是避免了重入。感觉自定义的软中断的重入处理也没有必要,不像时钟中断。以后有时间再思考解决。

2015年 02月 24日 星期二 17:32:37 CST
hlt指令让CPU停止执行指令,进入待机状态,但是在发生外部事件如敲击键盘后恢复执行。
只有bx,bp,si,di可以指定内存地址,mov al,[si]

2015年 02月 28日 星期六 11:48:49 CST
《一个操作系统的实现》的上半部分比较认真的看了,而且照着过程自己实践了一遍,对于保护模式算是明白了很多。下半部分占全书的三分之一,但是代码量很大,自己走马观花式的看了一遍,并未看懂那些细枝末节。有心情再看。看了点《30天自制操作系统》的讲解思路倒是比于渊的简单,但感觉没有后者的详细和系统。
BIOS是用16位实模式写的,所以保护模式下不能进行该调用。
0x10号调用可以设置VGA显卡的显示模式,如al=0x13时为320x200x8位彩色(256色,VRAM地址范围0xA0000~0xAFFFF,64KB)
32位模式下应积极使用32位寄存器eax等,若使用16位的ax,不只机器语言的字节数会增加,而且执行速度也会变慢。C语言内嵌汇编时eax,ecx,edx能自由使用而其他寄存器只能使用其值而不能改变其值。因为这些寄存器在C语言编译后生成的机器语言中,用于记忆非常重要的值。

2015年 03月 02日 星期一 12:06:34 CST
对于EFLAGS的访问:pushfd(push flags double-word),popfd将EFLAGS压栈和弹出。如
_io_load_eflags: ;int io_load_eflags(void);
pushfd
pop eax
ret ;C语言归约eax中的值被看作是函数的返回值。
_io_store_eflags: ;void io_store_eflags(int eflags);
mov eax,[esp+4]
push eax
popfd
ret
在汇编代码中调用C语言的函数要做以下处理:
mov ax,ss
mov ds,ax
mov es,ax
call c_func
因为C编译器认为ds,es,ss指向同一个段
如果汇编指令中sti的下一条是hlt,则CPU暂时不受理这两条指令之间的中断,而是等到hlt指令之后才受理

原文地址:https://www.cnblogs.com/makefile/p/4321880.html