第4天
今天一开始也学最后学的用汇编写一个能被C语言调用的函数,函数名叫write_mem8,功能是在指定内容中写入特定的数据。这个函数有两个参数,第一个参数表示目标地址,第二个参数表示写入的数据。
这个函数最重要的功能就是往指定的内存地址中写入数据,那么用C语言可不可能也指定一个内存地址呢?其实是可以的,使用C语言中指针的概念就可以了。C语言中所谓的指针其是就是平时所谓的内存地址。比如想要往0xa0000内存地址中写入数据,可以这样:
int i;
char* p;
i = 0xa0000;
p = (char*)i;
*p = i;
这样就把变量i 中的内容写到内存0xa0000中了,而这个地址就是前面写的操作系统中的显存地址,往这个地方写入数据就相当于在屏幕上画上内容。
在进入32位之前,用BIOS中断把显卡设置成调色板模式。要写设置显卡中的调色板,只能使用汇编语言中的in和out指令。首先将屏蔽中断;将想要设定的调色板号码写入0x03c8,按RGB序顺写入0x03c9,如果还要继续写入,只需要继续写入0x03c9;如果要读出调色板状态,将调色板的号码写入0x03c7,再从0x03c9读取3次,调出的顺序是RGB,如果要读取下一个,只要继续从0x03c9中调取下一个调色板;结束之后,取消中断屏蔽。
中断屏蔽指令:
CLI(clear interrupt flag)
取消中断屏蔽指令:
STI(set interrupt flag)
根据C语言的规定,汇编中放入eax中的数据就是C语言的返回值。
第五天
在32位CPU的模式下操作系统中显示字符和在屏幕上画图的原理是一样的,字符只是一种特殊的图形。本书里把一个字符定义为8*16个像素的位图,那么一个字符就占16字节。一般为了画字符方便,而且操作系统为了支持不同字体功能,会另外单独定义一个字体文件,这个操作系统会在加载内存的时候把字体数据读入内存中。如果定义ASCII编码的256个字符,每个字符16字节,那么一共4096个字节,还可以接受。
sprintf(地址, 格式,值,值,值,....)
关于格式的详细说明:
- %d,单纯的十进制数
- %5d,5位十进制数
- %05d,在前面补0,强制达到时5位
- %x,单纯十六进制数字母用小写
- %X,单纯十六进制数字母用大写
在屏幕上显示鼠标,也就是在屏幕上画一个图像。本书把鼠标定义为16*16个像素的位图,每个像素是8个字节,总共256个字节。鼠标图像有三种颜色,第一是鼠标的边缘,我们定义为黑色,位图中用*表标;第二个是鼠标本身的颜色,我们定义为黑色,位图中用0表示;第三个是背景色,就是不显示颜色,用背景色补充,用.表示。
在32位CPU模式下,内存需要分段,为了表示一个段,需要三个信息:1、段的大小;2、段的起始地址;3、段的管理属性(禁止写入,禁止执行,系统专用等)。CPU用8个字节表示这些信息,但是段寄存器只有16位。表示的方法跟调色板很相似,先有一个段选择符,存放在段寄存器里,然后在内存中预先设定这8个字节的信息。16位段寄存器的低3位不能使用,能用的只有13位,所以一共能表示8192个段。每个段需要8个字节表示,那么一共需要8192*8=64KB表示,这64KB的数据就是GDT:global descriptor table,全局段号记录表。GDT存入在内存的某个地方,然后将内存的起始地址和有效设定个数存放在CPU的GDTR(global descriptor table rigister)中。
IDT就是中断记录表(interrupt descriptor table),当CPU遇到中断后,暂时停止正在处理的任务,转而执行中断程序。IDT记录了0~255的中断号码与调用函数的对应关系,设定方法与GDT很相似。而且要先设定GDT再设定IDT。设定IDT的目的是想让我们的操作系统对鼠标的移动作出反应。
第六天
用C语言写的操作系统源文件bootpack.c已经很长了,现在考虑要将源文件根据不同的功能拆分开来。分成三个部分:1、关于屏幕上画图的部分graph.c;2、关于GDT\IDT设置的部分dsctbl.c;3、其他处理bootpack.c
由于源文件多了相应的Makefile里的编绎规则也就多了。Makefile有一个技巧可以将类似的生成规则
%.gas : %.c Makefile
$(CC1) -o $*.gas $*.c
%.nas : %.gas Makefile
$(GAS2NASK $*gas $*.nas
make.exe会先寻找普通的生成规则,如果没有找到,就尝试用一般规则。
C语言中的include有<>和“”的区别,前者表示头文件在编绎器的文件夹中,后者表示头文件在源文件所在的文件夹中。
_load_gdtr ; void load_gdtr(int limit, int addr);
mov ax, [esp + 4]; limit
mov [esp + 6], ax
lgdt [esp + 6]
ret
这个函数将指定的段上限和地址赋值给GDTR这个48位寄存器,也就是6个字节。低16位存放段上限,它等于GDT的有效字节数-1,剩余的4个字节代表GDT的开始地址。上面程序把传入的参数limit低2字节移到高2字节,然后执行lgdt指令。
GDT中每一个段信息用8个字节表示,写的操作系统中用一个定义了一个结构体表示。
struct SEGMENT_DESCRIPTOR
{
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
段的地址也叫做段基础,用32位表示,在结构体中用4个字段表示,分别是base_low(2个字节), base_mid(1个字节), base_high(1个字节)。正好一共4个字节。
段的大小也叫做段上限,结构体中用3个字节表示,分别是limit_low(2个字节),limit_high(1个字节)。虽然段上限我们结构体里使用了3个字节,但是实际是有效的只有20位,另外4位表示段属性。limit_high的高4位表示段属性。段大小用20位表示,那么一个段最大的只有1MB。但是可以通过段属性中的一个位设置成1就可以把段上限的单位从字节转换成页,一页表示4KB。那么20位段上限就可以不示4GB。
结构体中还有一个字段access_right,表示段属性,也叫做段的访问权属性。段属性一共用12个字节表示,高4位保存在limit_high字段的高4位中,这4位又被叫做扩展访问权,这4位由GD00构成,G表示段上限字段的单位是字节还是页(0表示字节,1表示页)。D字段表示段模式,1是指32位,0是指16位。access_right简单说明如下:
- 00000000(0x00): 未使用的记录 段
- 10010010(0x92):系统专用,可读写,不可执行
- 10011010(0x9a):系统专用,可执行,可读不可写
- 11110010(0xf2):应用程序用,可读写,不可执行
- 11111010(0xfa):应用程序用,可执行,可读不可写
在使用中断之前一定要先设置PIC,programmable interrupt controller。电脑的中断信用有15个,两个PIC,与CPU直接相连的称为主PIC,与主PIC相连的PIC称为从PIC。从PIC通过2号IRQ与主PIC相连。
PIC的所有寄存器都是8位的,IMR是interrupt mask register的缩写,中断屏蔽寄存器。8位分别对应8路IRQ信号,如果某一位设置为1则忽略该路信号。
ICW是initial control word的缩写,为初始化控制数据,一共有4个,编号分别为1-4。ICW1、ICW3,ICW4的设定值为固定,操作系统唯一能设置的是ICW2。ICW2表示IRQ以几号中断通知CPU。我们写的操作系统里把015号中断设置为0x200x2f号中断。
汇编指令pushad相当于
push eax
push ecx
push edx
push ebx
push esp
push ebp
push esi
push edi
相对应的也有popad
想让CPU处理中断,先写中断的处理程序,然后再把中断处理程序的入口地址注册IDT中,等到中断信号发生的时候,系统会自动调用中断处理程序。
网友评论