ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务
★PART1:中断和异常概述
1. 中断(Interrupt)
中断包括硬件中断和软中断。硬件中断是由外围设备发出的中断信号引发的,以请求处理器提供服务。当I/O接口发出中断请求的时候,会被像8259A和I/O APIC这样的中断寄存器手机,并发送给处理器。硬件中断完全是随机产生的,与处理器的执行并不同步。当中断发生的时候,处理器要先执行完当前的指令(指的是正在执行的指令),然后才能对中断进行处理。
软中断是由int n指令引发的中断处理器,n是中断号(类型码)。
2. 异常(Exception)
异常就是第9章略过的内部中断。内部中断是处理器内部产生的中断,表示在指令执行的时候遇到了错误。当处理器执行一条非法指令(引用一个不合标准的段,任务切换的时候TSS选择子不是有效的,访问了一个没有登记的页等等)。简单来说就是指令不能正常执行的时候,将引发这种类型的中断。
异常分为三种:
- 程序错误异常,指处理器在执行指令的过程中,检测到了程序中的错误,并由此引发的错误。
- 软件引发的异常。这类异常通常是into,int3和bound指令主动发起的,这些指令允许在指令流的当前点上检查实施异常处理跌条件是否满足。比如一个例子,into指令在执行的时候,将检查EFLAGS寄存器的OF标志位,如果满足为“1”的条件,那么就引发异常。
- 第三种是机器检查异常。这种异常是处理器型号相关的,也就是说,每种处理器都不太一样。无论如何,处理器提供了一种对硬件芯片内部和总线处理进行检查的机制,当检测到有错误的时候,将引发此异常。
根据异常情况的性质和严重性,异常又分为以下三种,并分别实施不同的特权保护。
- 故障(Faults)。故障通常是可以纠正的。最典型的就是处理器执行一个内存访问指令的时候,发现那个段或者页不在内存中(P=0),此时,可以在异常处理程序中予以纠正(分配内存,或者执行磁盘的换入换出操作)。返回时,程序可以重新启动并且不失连续性。当故障发生的时候,处理器把及其状态恢复到引发故障的那条指令之前的状态,在进入异常处理时,压入栈中的返回地址(CS和EIP的内容)是指向引起故障的那条指令的,而不像通常那样指向下一条指令。虚拟内存管理就是以异常为基础的。
- 陷阱(Traps)。陷阱中断通常在执行了解惑陷条件的指令立即产生,如果陷阱条件成立的话。陷阱通常用于调试目的,比如单步中断指令int3和溢出检测指令into。陷阱中断允许程序或者任务从中断处理过程返回后继续执行不失连续性。当陷阱异常发生的时候,转入异常处理程序之前,处理器在栈中压入陷阱截获指令的下一条指令地址。
- 终止(Aborts)。终止标志着最严重的错误,诸如硬件错误,系统表(GDT,LDT等)中的数据不一致或者无效。这种错误发生的时候,程序或者任务都不可能重新启动。
对于某些异常,处理器在转入异常处理程序之前,会在当前栈中压入一个称为错误代码的数值,帮助程序进一步诊断异常产生的位置和原因。
现在解释一下一些比较陌生的中断:
- 80486之后,处理器内部集成了浮点运算器x87 FPU,不需要再安装独立的数学协处理器,所以有些的浮点运算有关的异常不会产生(比如向量为9的协处理器段超越故障)。Wait和fwait指令用于主处理器和浮点处理部件(FPU)之间的同步。他们应当放在浮点指令之后,以捕捉任何浮点的异常。
- 从1993年的Pentium处理器开始,引入了用于加速多媒体处理的多媒体拓展技术(Multi-Media eXtension,MMX),该指令使用单指令多数据(Single-Instruction,Multiple-Data,SIMD)执行模式,以便在64位的寄存器内实施并行的整数运算。随着处理器的更新换代,这项技术也多次拓展,第一次被称为SSE(SIMD Extension),第二次是SSE2,第三次是SSE3。和SIMD有关的异常是从Pentium III处理器开始引入的。
- bound(Check Array Index Against Bounds)指令用于检查数组的索引是否在边界之内,其格式为
bound r16,m16(目的操作数是寄存器,包含了数组的索引,源操作数必须指向内存位置,里面包含了成对出现的字,分别十数组的上限和下限,如果数组索引不在上下限之内,则引发异常
bound r32,m32(和上面的基本一样,除了寄存器是32位的,而且内存位置是包含了成对出现的双字)。
- ud2(Undefined Instruction)指令是从Pentium Pro处理器开始引入的,他只有操作码没有操作数,执行该指令时会引发一个无效操作码的异常(用于软件测试),这个异常触发时压入的是指向本身的指令指针。
3. 中断描述符表,中断门和陷阱门,中断和异常处理程序
在保护模式下,处理器不是用的中断向量表来处理中断的,取而代之的是中断描述符表(Interrupt Descriptor Table,IDT),中断描述符表存放的是中断门,陷阱门和任务门。其中中断门和陷阱门是只能放在IDT中。和IVT不一样的是,IDT不要求必须位于内存的最低端。在处理器内部,有一个48位的中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR),保存着中断描述符表在内存中的线性基地址和界限,IDTR只有一个,和GDTR的储存格式是一样的。中断门,陷阱门描述符格式和中段描述符表寄存器的结构如下:
中断门:
陷阱门:
注意,D位是0时,表示的是16位模式下的门,用于兼容早期的16位保护模式;为1时,就是表示32位的门
中断描述符表寄存器,长得和GDTR差不多:
中断描述符表IDT可以位于内存的任何地方,只要IDTR指向了它,整个终端系统就可以正常的工作。为了利用高速缓存使处理器的工作性能最大化,处理器建议IDT的基地址是8字节对齐的。处理器复位的时候,IDTR的基地址部分是0,界限部分是0xFFFF(和GDTR是一样的)。处理器只识别256个中断,所以LDT通常只用2KB。和GDT不一样的是,IDT的第一个槽位可以不是0描述符。
在保护模式下处理器执行中断的时候,先根据相应的中断号乘以8加上IDT的基地址得到相应的中段描述符的位置(如果有页映射也是根据页的映射规则来找到相应的描述符),和通过调用门试试的控制转移一样,处理器也要对中断和异常处理程序进行特权级的保护。但是在中断和异常的特权级检查中有特殊的情况。因为中断和异常的理想两没有RPL,所以处理器在进入中断或者异常处理程序的时候,或者通过人物们发起任务切换的时候,不检查RPL。和普通的门调用一样,CPL要在数值上小于等于目标代码段的DPL才可以执行代码段的切换,但是对于门的DPL的检查中,除了软中断int n和单步中断int3以及into引发的中断和异常外,处理器不对门的DPL进行特权级检查,如果是以上三种中断命令引发的中断,则要求CPL<=门描述符的DPL。(主要是为了防止软中断引发的越权操作)。
如果发生了特权级的转变(比如从局部空间转移到了全局空间)。那么要进行栈切换。压栈顺序如下:
- 根据处理器的特权级别,从当前任务的TSS中取得栈段选择子和栈指针。处理器把旧的栈的选择子和栈指针压入新栈。如果中断处理程序的特权级别和当前特权级别一致。则不用转换栈。
- 处理器把EFLGAS压入栈,然后把CS压栈,然后再压栈EIP。
- 如果有错误代码的异常,处理器还要将错误代码压入新栈,紧挨着EIP之后。
中断门和陷阱门的区别就是对IF位的处理不同。通过中断门进入中断处理程序的收,EFLAGS寄存器的IF位被处理器自动清零。以禁止嵌套的中断,当中断返回的时候,从栈中恢复EFLAGS的原始状态。陷阱中断的优先级比较低,当通过陷阱门进入中断处理程序的时候,EFLAGS寄存器的IF位不变,以允许其他中断的优先处理。EFLAGS寄存器的IF位仅影响硬件中断,对NMI,异常和int n形式的中断不起作用。
和GDT一样,如果要访问的位置超过了IDT的界限,那么就会产生常规保护异常(#GP)。
4. 中断任务
中断任务切换指的是通过在IDT的任务门发起的任务切换。硬件中断发生是客观的,可以用中断来实现抢占式的多任务系统(硬件调度机制,代价很大,需要保存大量的机器状态,现代操作系统都是用的软切换)。
可以想一下,如果在某个任务中发出了双重中断(#DF),是一种终止类型的中断,如果把双重中断的处理程序定义成任务,那么当双重故障发生的时候,可以执行任务切换返回内核,并且抹去出错程序,回收其内存空间,然后执行其他调度,这样会非常的自然。
中断机制使用任务门有以下特点:
- 被中断的程序或者任务的整个环境被保存到TSS中。
- 切换的新任务有自己的栈和虚拟内存空间,防止系统因为出错而崩溃。
中断或者异常发起的任务切换,不再保存CS和EIP的状态,但是任务切换后,如果有错误代码,还是要把错误代码压入新任务要栈中。要注意的是,任务是不可以重入的,在执行中断任务之后和执行其iret之前,必须关中断,以防止因为相同的中断而产生常规保护异常(#GP)。
5. 错误代码
错误代码如上图所示,错误代码的高16位是不用的。
EXT位表示,异常是由外部事件引发的(External Event)。此位是1的时候,表示异常是由NMI,硬件中断引发的。
IDT位用于指示描述符的位置(Descriptor Location)。为1时则表示段选择子的索引部分是存在于中段描述符IDT的;为0时,则表示在GDT或者LDT中。
TI位仅仅在IDT为0的时候才有意义,当此位是0时,则表示段的选择子的索引部分是存在于GDT的,否则在LDT。
当错误代码全都是0的时候,这表示异常的产生并非是由于引用一个段产生的(比如也有可能是页错误,访问了一个没有登记的页),也有可能是因为应用了一个空描述符的时候发生的。需要注意的是,在执行iret指令从中断处理程序返回的时候,处理器并不会自动弹出错误代码,对于那些会压入错误代码的处理过程来说,在执行iret时,必须把错误代码弹出。
特别注意,对于外部异常(通过处理器引脚触发),以及用软中断指令int n引发的异常,处理器不会压入错误代码,即使是有错误代码的异常。分配给外部中断的向量号在31~255之间,处于特殊目的,外部的8259A或者I/O APIC芯片可能给出一个0~19的向量号,比如13(常规异常保护#GP),并希望进行异常处理。在这种情况下处理器不会压入错误代码。如果用软中断有意引发的异常,也不会压入错误代码。
★PART2:加载内核和用户程序
1. 平坦模式
一旦使用了页管理,很多事情都会得到简化了,比如段管理模型,每次操作内存都要注意引用的段有没有错误,太麻烦了,所以我们直接用平坦模式,在平坦模式下,程序的数据段4GB,代码段也是4GB的,从0开始分段,一直到4GB最高端。把程序改成平坦模式然后使用页管理,能大量减少代码量。
具体从代码上就可以看到怎么实现了,其实也很简单稍微改下就好了,现在就是注意几个坑就好了,由于使用了平坦模式,内核无法重定位,所以内核的vstart一定要是0x80040000(注意使用了页管理以后,所有对内存的操作都要页映射,包括内核的段起始地址。
2. 创建中断描述符表
进入内核以后,第一件事情就是先把中断先设置好了,然后才能关中断(注意在所有的中断处理程序没安装完之前,千万不能开中断,否则就是gate_interrupt)。安装也很简单,也就是一堆门而已。
通用的异常处理程序和中断程序的处理都很简单,异常直接停机(注意异常不是每次都会有错误代码),普通中断就直接返回就好了。我们的DEMO演示的是时钟中断,这个中断在第九章就已经讲过了,中断号是0x70,所以现在我们就可以对这个中断进行特殊处理,让他可以进行任务切换,TCB和上一章的TCB是一样的,这里实现的原理就是不断遍历链表,然后找到第一个不忙的任务进行切换,然后把被切换的任务的TCB挂到链表的最后。这个和C写出来的遍历链表的思想是一样的。
注意我们的内核的TCB规定一个任务如果是忙,那么任务状态位(0x04)就是0xffff,如果是空闲那么是0x0000,所以才有取反指令的存在。事实上这样的找任务的方法是很慢的,每一次遍历链表都要花费O(n)的时间复杂度,很慢,在Linux等高级操作系统中,使用红黑树来等数据结构来管理程序,而且用的是软切换(不用TSS硬切换,不用保存大量的机器状态)。
3. 8259A芯片的初始化
这个已经在我转的一篇文章写的很清楚了,我们初始化只要按照上面的来就可以了,比教材讲的详细多了,(http://www.cnblogs.com/Philip-Tell-Truth/articles/5169767.html看这里)。
最后我们来用代码实现一遍:
4. 转换后援缓冲器(Translation Lookaside Buffer,TLB)
开启页功能的时候,处理器页部件要把线性地址转换成物理地址,而访问页目录和页表是相当费时间的,因此,把页表项预先放到处理器中,可以加快转换处理,为此,处理器专门够早了一个特殊的高速缓存器,叫做转换后援缓冲器。如图所示:
在分页模式下,当段部件发出一个线性地址的时候,处理器用线性地址的高20位来查找TLB,如果直接找到匹配项,那么直接用其数据部分的物理地址作为转换用的地址;如果检索不成功,那么就按照页目录-页表-页的顺序来找到相应的页。并把它填写到TLB中。TLB的容量是有限的,如果装满了处理器就会将一些项给清除掉。
TLB中的属性为来自页表项,比如页表项的D位(Dirty);访问权位来自页目录项的对应页表项。比如RW和US位。在分页机制中,对页的访问控制按照最严格的访问权执行。对于某个线性地址,如果其页目录项的位是“0”,而页表项的RW位是1,那么就按照RW是0来存储(TLB的访问权对应页表和页目录项的逻辑与)。
处理器仅仅会缓存那些P位是1的那些页表项,而且,TLB的工作和CR3寄存器的PCD位和PWT是无关的。对于页表项的修改不会同时反映到TLB中,一定要刷新TLB,不然对页表的设置就是无效的。TLB是软件不可直接访问的,只能通过显式刷新CR3,或者任务切换隐式刷新TLB,这样刷新过后TLB的所有条目都会是无效的,但是要注意的是,这样的刷新方法对于那些标记为全局(G=1)的页表无效。
TLB还可以单个刷新,利用invlpg命令(invalidate TLB Entry)。invlpg的格式为invlpg m32,当执行这条指令的时候,处理器会用给出的线性地址搜索TLB,找到那个条目,然后从内存中重新加载其内容到相应的TLB页表数据中。invlpg是特权指令,必须要在CPL为特权0级执行,该指令不影响任何标志位。
我们的内核进行刷新TLB的是在加载程序之前复制页目录的时候做的。但是我自己写的程序加载位置是可变的,其实不刷新也没什么关系。教材那个就一定要刷新。具体看代码。
5. 宏汇编技术(Macro)
所谓的宏汇编技术,其实和C的宏是一样的,就是一个字符串代替一堆东西而已,当然了也可以带参数。
1. 单行宏%define:
顾名思义这种宏只能定义单行的比如:
2. 多行宏%macro:
这种宏的后面都要带%endmacro作为指定宏结束的位置。而且多行宏可以指定参数个数
参数的个数直接定义在宏名称的后面,使用的时候宏内参数由%加对应数字引用参数,上面的例子已经说得很清楚了,如果没有参数,那么参数个数直接设为0。
★PART3:本章的程序
说实话本章的练习题没什么好写的,就把例程写一遍就好了,我自己写的时候自己写了一个很大的坑就是我的宏写错了,导致自己访问内存的时候一直显示页错误(其实是调试了很久才知道是页错误,访问了一个没有登记的页)。而且要注意的是,一些关键的过程,比如put_string,读硬盘和TCB的链接这些过程,一定要关中断,不然会引发系统严重错误。
教材上用的中断只是关闭了从片的中断,我改了一下只留时钟中断,而且是更新结束中断,然后程序可以停机然后给时间中断唤醒,这样感觉会更清晰一点。
1. 主引导程序MBR
- ;========================保护模式主引导扇区代码========================
- core_phy_base: equ 0x00040000 ;内核加载地址
- core_sector_address: equ 0x00000001 ;内核所在扇区
- ;======================================================================
- SECTION mbr align= vstart=0x00007c00 ;注意起始地址已经变成了0x7c00了
- mov ax,cs
- mov ss,ax
- mov sp,0x7c00
- mov eax,[cs:pgdt_base+0x02]
- xor edx,edx
- mov ebx,0x10
- div ebx
- mov ds,eax ;让ds指向gdt位置进行操作
- mov ebx,edx ;别忘了还有可能出现偏移地址
- ;---------------------描述符#0---------------------
- mov dword [ebx+0x00],0x00000000 ;空描述符
- mov dword [ebx+0x04],0x00000000
- ;---------------------描述符#1---------------------
- mov dword [ebx+0x08],0x0000ffff ;4GB代码段,特权级为0
- mov dword [ebx+0x0c],0x00cf9800
- ;---------------------描述符#2---------------------
- mov dword [ebx+0x10],0x0000ffff ;4GB向上拓展数据段和栈段,特权级为0
- mov dword [ebx+0x14],0x00cf9200
- mov word[cs:pgdt_base], ;加载gdt
- lgdt [cs:pgdt_base]
- in al,0x92 ;快速开启A20
- or al,0x02 ;是写入2,不要搞错了,写入1就是重启了
- out 0x92,al
- cli ;关掉中断
- mov eax,cr0
- or eax,0x01 ;设置PE位
- mov cr0,eax
- jmp dword 0x0008:flush ;进入保护模式
- [bits ]
- flush:
- mov eax,0x0010
- mov ds,eax
- mov es,eax
- mov fs,eax
- mov gs,eax
- mov ss,eax ;栈段也是向上拓展的
- mov esp,0x7000
- ;接下来开始读取内核头部
- mov esi,core_sector_address
- mov edi,core_phy_base
- call read_harddisk_0
- mov eax,[core_phy_base] ;读取用户总长度
- xor edx,edx
- mov ebx,
- div ebx
- cmp edx,
- jne @read_last_sector
- dec eax
- @read_last_sector:
- cmp eax,
- je @setup
- mov ecx,eax
- .read_last:
- inc esi
- call read_harddisk_0
- loop .read_last
- @setup: ;下面准备开启页管理
- mov ecx,
- mov ebx,0x00020000
- xor esi,esi
- _flush_PDT: ;清空页表
- mov dword[es:ebx+esi*],0x00000000
- inc esi
- loop _flush_PDT
- ;页目录的最后一个32字节是指向自己的页表(这个页表就是页目录)
- mov dword[ebx+],0x00020003 ;属性:存在于物理内存,只允许内核自己访问
- ;页目录的第一个页表指示最底下1MB内存的4KB页(内核代码,必须虚拟地址和物理地址一致)
- mov edx,0x00021003
- mov dword[ebx+0x000],edx ;低端映射(临时的,创建用户目录的时候就没了)
- mov dword[ebx+0x800],edx ;高端映射
- ;现在0x00020000的页是第0个页表(指示页目录),0x00021000是第一个页表(指示底下1MB的东西)
- mov ebx,0x00021000
- xor eax,eax
- xor esi,esi
- _make_page:
- mov edx,eax
- or edx,0x00000003 ;属性:存在于物理内存,只允许内核自己访问
- mov [ebx+esi*],edx
- add eax,0x1000
- inc esi
- cmp esi,
- jl _make_page
- mov eax,0x00020000
- mov cr3,eax ;把页目录基地址放在cr3,准备开启页功能
- sgdt [pgdt_base]
- add dword[pgdt_base+],0x80000000 ;设定GDT为高地址
- lgdt [pgdt_base]
- mov eax,cr0
- or eax,0x80000000
- mov cr0,eax ;置PG位,开启页功能
- ;将堆栈映射到高端,这是非常容易被忽略的一件事。应当把内核的所有东西
- ;都移到高端,否则,一定会和正在加载的用户任务局部空间里的内容冲突,
- ;而且很难想到问题会出在这里。
- add esp,0x80000000 ;因为已经处于平坦模式了,所以内核栈指针也要映射
- jmp [core_phy_base+0x80000000+] ;都在一个段上了,直接近转移,start在偏移量是4的地方
- ;=============================函数部分=================================
- read_harddisk_0: ;esi存了28位的硬盘号
- push ecx
- mov edx,0x1f2 ;读取一个扇区
- mov al,0x01
- out dx,al
- mov eax,esi ;0~7位,0x1f3端口
- inc edx
- out dx,al
- mov al,ah ;8~15位,0x1f4端口
- inc edx
- out dx,al
- shr eax, ;16-23位,0x1f5端口
- inc edx
- out dx,al
- mov al,ah ;24-28位,LBA模式主硬盘
- inc edx
- and al,0x0f
- or al,0xe0
- out dx,al
- inc edx ;读命令,0x1f7端口
- mov al,0x20
- out dx,al
- .wait:
- in al,dx
- and al,0x88
- cmp al,0x08
- jne .wait
- mov dx,0x1f0
- mov ecx,
- .read:
- in ax,dx
- mov [edi],ax
- add edi,
- loop .read
- pop ecx
- ret
- ;======================================================================
- pgdt_base dw
- dd 0x00008000 ;GDT的物理地址
- ;======================================================================
- times -($-$$) db
- dw 0xaa55
2. 内核程序
- ;============================内核程序=================================
- ;定义内核所要用到的选择子
- All_4GB_Segment equ 0x0018 ;4GB的全内存区域
- Core_Code_Segement equ 0x0008 ;内核代码段
- IDT_Liner_Address equ 0x8001F000 ;IDT线性地址
- ;----------------------------------------------------------------
- User_Program_AddressA equ ;用户程序所在逻辑扇区
- User_Program_AddressB equ ;用户程序所在逻辑扇区
- Switch_Stack_Size equ ;切换栈段的大小
- Global_Page_Directory equ 0x80000000 ;给全局空间的映射地址
- ;----------------------------------------------------------------
- %macro alloc_core_page ;给内核程序安排页
- mov ebx,[core_tcb+0x06]
- add dword[core_tcb+0x06],0x1000 ;注意这里是加法指令,写错了就会页故障(因为已经清空页了,访问一个不存在的页)
- call Core_Code_Segement:alloc_inst_a_page
- %endmacro
- ;----------------------------------------------------------------
- %macro alloc_user_page ;给用户程序安排页
- mov ebx,[esi+0x06]
- add dword[esi+0x06],0x1000
- call Core_Code_Segement:alloc_inst_a_page
- %endmacro
- ;----------------------------------------------------------------
- %macro Read_Data_From_Harddisk
- push esi
- push ds
- push ebx
- push cs
- call Core_Code_Segement:ReadHarddisk
- %endmacro
- ;=========================================================================
- ;============================公用例程区===================================
- ;=========================================================================
- SECTION Code align= vstart=0x80040000 ;注意代码段的开始现在是0x80040000了,映射的线性地址
- Program_Length dd Program_end ;内核总长度
- Code_Entry dd start ;注意偏移地址一定是32位的
- ;----------------------------------------------------------------
- [bits ]
- ;----------------------------------------------------------------
- ReadHarddisk: ;push1:28位磁盘号(esi)
- ;push2:应用程序数据段选择子(ax->ds)
- ;push3: 偏移地址(ebx)
- ;push4: 应用程序代码段选择子(dx)
- cli
- pushad
- mov ebp,esp
- mov esi,[ebp+*]
- movzx eax,word[ebp+*]
- mov ebx,[ebp+*]
- movzx edx,word[ebp+*]
- arpl ax,dx
- mov ds,ax
- mov dx,0x1f2
- mov al,0x01 ;读一个扇区
- out dx,al
- inc edx ;0-7位
- mov eax,esi
- out dx,al
- inc edx ;8-15位
- mov al,ah
- out dx,al
- inc edx ;16-23位
- shr eax,
- out dx,al
- inc edx ;24-28位,主硬盘,LBA模式
- mov al,ah
- and al,0x0f
- or al,0xe0
- out dx,al
- inc edx
- mov al,0x20
- out dx,al
- _wait:
- in al,dx
- and al,0x88
- cmp al,0x08
- jne _wait
- mov dx,0x1f0
- mov ecx,
- _read:
- in ax,dx
- mov [ebx],ax
- add ebx,
- loop _read
- popad
- sti
- retf ;4个数据
- ;----------------------------------------------------------------
- put_string: ;ebx:偏移地址
- cli ;必须关中断
- pushad
- _print:
- mov cl,[ebx]
- cmp cl,
- je _exit
- call put_char
- inc ebx
- jmp _print
- _exit:
- popad
- sti ;记得把中断开了
- retf
- ;--------------------------------------------------------------
- put_char: ;cl就是要显示的字符
- pushad
- mov dx,0x3d4
- mov al,0x0e ;高8位
- out dx,al
- mov dx,0x3d5
- in al,dx
- mov ah,al ;先把高8位存起来
- mov dx,0x3d4
- mov al,0x0f ;低8位
- out dx,al
- mov dx,0x3d5
- in al,dx ;现在ax就是当前光标的位置
- mov bx,ax
- and ebx,0x0000ffff ;准备用32位寻址来显示
- _judge:
- cmp cl,0x0a
- je _set_0x0a
- cmp cl,0x0d
- je _set_0x0d
- _print_visible:
- shl bx,
- mov [0x800b8000+ebx],cl
- mov byte[0x800b8000+ebx+],0x07
- shr bx,
- inc bx ;以下将光标位置推进一个字符
- jmp _roll_screen
- _set_0x0d: ;回车
- mov ax,bx
- mov bl,
- div bl
- mul bl
- mov bx,ax
- jmp _set_cursor
- _set_0x0a: ;换行
- mov bx,ax
- add bx,
- jmp _roll_screen
- _roll_screen:
- cmp bx,
- jl _set_cursor
- cld
- mov edi,0x800b8000 ;一定要记住,现在内存的地址全都是虚拟地址,偏移地址也是一样的
- mov esi,0x800b80a0
- mov ecx,
- rep movsw
- _cls:
- mov ebx,
- mov ecx,
- _print_blank:
- mov word[0x800b8000+ebx],0x0720
- add bx,
- loop _print_blank
- mov ebx, ;别总是忘了光标的位置!
- _set_cursor: ;改变后的光标位置在bx上
- mov dx,0x3d4
- mov al,0x0f ;低8位
- out dx,al
- mov al,bl
- mov dx,0x3d5
- out dx,al
- mov dx,0x3d4
- mov al,0x0e ;高8位
- out dx,al
- mov al,bh
- mov dx,0x3d5
- out dx,al
- popad
- ret
- ;----------------------------------------------------------------
- Make_Seg_Descriptor: ;构造段描述符
- ;输入:
- ;eax:线性基地址
- ;ebx:段界限
- ;ecx:属性
- ;输出:
- ;eax:段描述符低32位
- ;edx:段描述符高32位
- mov edx,eax
- and edx,0xffff0000
- rol edx,
- bswap edx
- or edx,ecx
- shl eax,
- or ax,bx
- and ebx,0x000f0000
- or edx,ebx
- retf
- ;----------------------------------------------------------------
- Make_Gate_Descriptor: ;构造门描述符
- ;输入:
- ;eax:段内偏移地址
- ;bx: 段的选择子
- ;cx: 段的属性
- ;输出:
- ;eax:门描述符低32位
- ;edx:门描述符高32位
- push ebx
- push ecx
- mov edx,eax
- and edx,0xffff0000 ;要高16位
- or dx,cx
- shl ebx,
- and eax,0x0000ffff
- or eax,ebx
- pop ecx
- pop ebx
- retf
- ;----------------------------------------------------------------
- Set_New_GDT: ;装载新的全局描述符
- ;输入:edx:eax描述符
- ;输出:cx选择子
- sgdt [pgdt_base_tmp]
- movzx ebx,word[pgdt_base_tmp]
- inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的
- ;要用到回绕特性
- add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址
- mov [es:ebx],eax
- mov [es:ebx+0x04],edx ;装载新的gdt符
- ;装载描述符要装载到实际位置上
- add word[pgdt_base_tmp], ;给gdt的段界限加上8(字节)
- lgdt [pgdt_base_tmp] ;加载gdt到gdtr的位置和实际表的位置无关
- mov ax,[pgdt_base_tmp] ;得到段界限
- xor dx,dx
- mov bx, ;得到gdt大小
- div bx
- mov cx,ax
- shl cx, ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级)
- retf
- ;----------------------------------------------------------------
- Set_New_LDT_To_TCB: ;装载新的局部描述符
- ;输入:edx:eax描述符
- ; : ebx:TCB线性基地址
- ;输出:cx选择子
- push edi
- push eax
- push ebx
- push edx
- mov edi,[ebx+0x0c] ;LDT的线性基地址
- movzx ecx,word[ebx+0x0a]
- inc cx ;得到实际的LDT的大小(界限还要-1)
- mov [edi+ecx+0x00],eax
- mov [edi+ecx+0x04],edx
- add cx,
- dec cx
- mov [ebx+0x0a],cx
- mov ax,cx
- xor dx,dx
- mov cx,
- div cx
- shl ax,
- mov cx,ax
- or cx,0x0004 ;LDT,第三位TI位一定是1
- pop edx
- pop ebx
- pop eax
- pop edi
- retf
- ;----------------------------------------------------------------
- allocate_4KB_page: ;输入:无
- ;输出eax:页的物理地址
- ;注意这个是近调用
- push ebx
- push ecx
- push edx
- xor eax,eax
- _search_pages:
- bts [page_bit_map],eax
- jnc _found_not_uesd
- inc eax
- cmp eax,page_map_len*
- jl _search_pages
- mov ebx,No_More_Page
- call Core_Code_Segement:put_string
- hlt ;无可用页,直接停机
- _found_not_uesd:
- shl eax, ;eax相当于是选择子,乘以一个4KB得到物理地址
- pop edx
- pop ecx
- pop ebx
- ret
- ;----------------------------------------------------------------
- alloc_inst_a_page: ;分配一个页,并安装在当前活动的层级分页结构中
- ;输入:EBX=页的线性地址
- ;输出:无
- push eax
- push ebx
- push edi
- push esi
- _test_P: ;在页目录中看是否存在这个页表
- mov esi,ebx
- and esi,0xffc00000
- shr esi,
- or esi,0xfffff000 ;指向页目录本身
- test dword[esi],0x00000001
- jnz _get_page_and_create_new_page
- _create_new_page_directory:
- call allocate_4KB_page
- or eax,0x00000007 ;存在于主存,可读可写,允许特权级3程序访问
- mov [esi],eax
- _get_page_and_create_new_page:
- mov esi,ebx
- shr esi, ;页表在页目录的偏移项
- and esi,0x003ff000 ;得到页表的偏移地址
- or esi,0xffc00000 ;指向页目录
- and ebx,0x003ff000
- shr ebx, ;中间10位是页目录-页表-表内偏移量(注意这里的层次理解)
- or esi,ebx ;esi就是页的对应线性地址
- call allocate_4KB_page
- or eax,0x00000007 ;存在于主存,可读可写,允许特权级3程序访问
- mov [esi],eax
- pop esi
- pop edi
- pop ebx
- pop eax
- retf
- ;----------------------------------------------------------------
- Copy_Page: ;把在创建的包含全局和私有部分的页表复制一份给用户程序用
- ;输入:无
- ;输出eax:页的物理地址
- push edi
- push esi
- push ebx
- push ecx
- push edx
- mov edx,[task_pos] ;注意这里不需要加上内核的偏移了,因为程序开始的时候已经有了start
- sub edx,
- add edx,0xfffff000
- invlpg [edx] ;刷新单条TLB
- mov edi,[page_soft_header]
- sub edi,0x1000
- mov esi,0xfffff000 ;指向全局页目录
- call allocate_4KB_page
- mov ebx,eax
- or ebx,0x00000007
- mov [edx],ebx
- mov ecx,
- cld
- repe movsd
- pop edx
- pop ecx
- pop ebx
- pop esi
- pop edi
- retf
- ;----------------------------------------------------------------
- ;-------------------------------------------------------------------------------
- general_interrupt_handler: ;通用的中断处理过程
- push eax
- mov al,0x20 ;中断结束命令EOI
- out 0xa0,al ;向从片发送
- out 0x20,al ;向主片发送
- pop eax
- iretd
- ;-------------------------------------------------------------------------------
- general_exception_handler: ;通用的异常处理过程
- mov ebx,excep_msg
- call Core_Code_Segement:put_string
- hlt
- ;-------------------------------------------------------------------------------
- rtm_0x70_interrupt_handle: ;实时时钟中断处理过程
- pushad
- mov al,0x20 ;直接给8259发EOI终止操作了
- out 0x20,al
- out 0xa0,al
- mov al,0x0c ;允许NMI中断
- out 0x70,al
- in al,0x71 ;读一下RTC的寄存器C,否则只发生一次中断
- mov eax,tcb_chain
- _Search_Not_In_Service:
- mov ebx,[eax]
- cmp ebx,0x00000000
- je _out_interrupt ;说明已经到链表的末尾了,直接就推出中断就好了
- cmp word[ebx+0x04],0xffff ;任务状态为忙
- je _found_current_task
- mov eax,ebx
- jmp _Search_Not_In_Service
- _found_current_task:
- mov ecx,[ebx]
- mov [eax],ecx ;拆除节点,ebx就是节点地址了
- _Last_Pos:
- mov edx,[eax]
- cmp edx,0x00000000
- je _Hang
- mov eax,edx
- jmp _Last_Pos
- _Hang:
- mov [eax],ebx ;挂到末端
- mov dword[ebx],0x00000000 ;节点的末尾标记一下
- mov eax,tcb_chain
- _found_free_task:
- mov eax,[eax]
- cmp eax,0x00000000
- je _out_interrupt
- cmp word[eax+0x04],0x0000
- jne _found_free_task ;找到第一个空闲任务
- ;取反任务状态
- not word[eax+0x04]
- not word[ebx+0x04]
- jmp far[eax+0x14] ;直接任务切换
- _out_interrupt:
- popad
- iretd
- ;-------------------------------------------------------------------------------
- Stop_This_Program:
- hlt
- retf
- ;-------------------------------------------------------------------------------
- ;=========================================================================
- ;===========================内核数据区====================================
- ;=========================================================================
- pgdt_base_tmp: dw
- dd
- pidt_base_tmp: dw
- dd
- salt:
- salt_1: db '@Printf' ;@Printf函数(公用例程)
- times -($-salt_1) db
- dd put_string
- dw Core_Code_Segement
- dw ;参数个数
- salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程)
- times -($-salt_2) db
- dd ReadHarddisk
- dw Core_Code_Segement
- dw ;参数个数
- salt_3: db '@Stop_This_Program' ;@Stop_This_Program函数(公用例程)
- times -($-salt_3) db
- dd Stop_This_Program
- dw Core_Code_Segement
- dw ;参数个数
- salt_length: equ $-salt_3
- salt_items_sum equ ($-salt)/salt_length ;得到项目总数
- salt_tp: dw ;任务门,专门拿来给程序切换到全局空间的
- message_start db ' Working in system core with protection '
- db 'and paging are all enabled.System core is mapped '
- db 'to address 0x80000000.',0x0d,0x0a,
- message_In_Gate db ' Hi!My name is Philip:',0x0d,0x0a,
- core_msg0 db ' System core task running!',0x0d,0x0a,
- No_More_Page db '********No more pages********',
- excep_msg db '********Exception encounted********',
- bin_hex db '0123456789ABCDEF'
- ;put_hex_dword子过程用的查找表
- core_buf times db ;内核用的缓冲区(2048个字节(2MB))
- core_tcb times db ;内核(程序管理器)的TCB
- ;假设只有2MB内存可以用的意思,正确的做法应该先读PCI(E),然后再分配!
- page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff
- db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
- db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
- db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
- db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
- db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
- db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
- db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
- page_map_len equ $-page_bit_map
- core_next_laddr dd 0x80100000 ;内核空间中下一个可分配的线性地址
- task_pos dd 0x00000ffc ;任务程序的页表在全局页目录的偏移
- page_soft_header dd 0xfffff000 ;加载页目录地址
- tcb_chain dd ;任务控制块链头指针
- ;=========================================================================
- ;===========================内核代码区====================================
- ;=========================================================================
- ;---------------------------------------------------------------------
- append_to_tcb: ;写入新的TCB链
- ;输入:ecx新的TCB线性基地址
- cli ;必须关中断,如果过程中间发生了0x70中断那么新内核就会崩溃
- pushad
- mov eax,tcb_chain
- _search_tcb:
- mov ebx,[eax]
- cmp ebx,0x00000000
- je _out_tcb_search
- mov eax,ebx
- jmp _search_tcb
- _out_tcb_search:
- mov [eax],ecx
- mov dword[ecx],0x00000000
- popad
- sti
- ret
- ;---------------------------------------------------------------------
- load_program: ;输入push1:逻辑扇区号
- ; push2: 线性基地址
- pushad
- mov ebp,esp ;别忘了把参数传给ebp
- mov ebx,0xfffff000
- xor esi,esi
- _flush_private: ;必须清空!
- mov dword[ebx+esi*],0x00000000
- inc esi
- cmp esi,
- jl _flush_private
- ;手动刷新页目录缓存
- mov eax,cr3 ;本来这个在16章就应该出现的,不刷新的话页目录的缓存还是旧的表项
- mov cr3,eax
- mov esi,[ebp+*] ;esi必须是逻辑扇区号
- mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了)
- Read_Data_From_Harddisk
- mov eax,[core_buf] ;读取用户程序长度
- mov ebx,eax
- and ebx,0xfffff000 ;清空低12位(强制对齐4096:4KB)
- add ebx,
- test eax,0x00000fff
- cmovnz eax,ebx ;低12位不为0则使用向上取整的结果
- mov ecx,eax
- shr ecx, ;获取占的页数
- mov edi,[ebp+*] ;获取tcb的线性基地址
- mov esi,[ebp+*] ;esi必须是逻辑扇区号
- _loop_read_@1:
- ;注意这个过程和下面的不一样,要使用edi,esi是逻辑号,不要用宏
- mov ebx,[edi+0x06]
- mov dword[edi+0x06],0x1000
- call Core_Code_Segement:alloc_inst_a_page
- push ecx
- mov ecx, ;512*8==4096
- _loop_read_@2:
- Read_Data_From_Harddisk
- inc esi
- add ebx,
- loop _loop_read_@2
- pop ecx
- loop _loop_read_@1
- mov esi,edi ;esi: TCB的线性基地址
- alloc_core_page ;给TSS分配内核页
- mov [esi+0x14],ebx ;填写TSS的线性基地址
- mov word[esi+0x12], ;无I/O映射
- alloc_user_page ;LDT的用户页
- mov [esi+0x0c],ebx ;填写LDT的线地址
- mov edi,[esi+0x14] ;edi就是TSS的线性地址
- ;代码段
- mov eax,0x00000000
- mov ebx,0x000fffff
- mov ecx,0x00c0f800
- call Core_Code_Segement:Make_Seg_Descriptor
- mov ebx,esi
- call Core_Code_Segement:Set_New_LDT_To_TCB
- or cx,0x0003 ;特权级为3
- mov [edi+],cx ;CS域
- ;数据段
- mov eax,0x00000000
- mov ebx,0x000fffff
- mov ecx,0x00c0f200
- call Core_Code_Segement:Make_Seg_Descriptor
- mov ebx,esi
- call Core_Code_Segement:Set_New_LDT_To_TCB
- or cx,0x0003 ;特权级为3
- mov [edi+],cx ;ES,DS,FS,GS域,已经映射到全局空间了
- mov [edi+],cx
- mov [edi+],cx
- mov [edi+],cx
- ;创建一系列栈
- ;创建自身特权级为3的栈
- alloc_user_page
- mov [edi+],cx ;cx是数据段的选择子
- mov edx,[esi+0x06] ;向上拓展的ESP的初始值
- mov [edi+],edx
- ;创建特权级0的栈
- alloc_user_page
- mov eax,0x00000000
- mov ebx,0x000fffff
- mov ecx,0x00c09200 ;4KB粒度的堆栈段描述符,特权级0
- call Core_Code_Segement:Make_Seg_Descriptor
- mov ebx,esi
- call Core_Code_Segement:Set_New_LDT_To_TCB
- or cx,0x0000 ;选择子特权级为0
- mov [edi+],cx ;cx是数据段的选择子
- mov edx,[esi+0x06] ;向上拓展的ESP0的初始值
- mov [edi+],edx
- ;创建特权级1的栈
- alloc_user_page
- mov eax,0x00000000
- mov ebx,0x000fffff
- mov ecx,0x00c0b200 ;4KB粒度的堆栈段描述符,特权级1
- call Core_Code_Segement:Make_Seg_Descriptor
- mov ebx,esi
- call Core_Code_Segement:Set_New_LDT_To_TCB
- or cx,0x0001 ;选择子特权级为1
- mov [edi+],cx ;cx是数据段的选择子
- mov edx,[esi+0x06] ;向上拓展的ESP1的初始值
- mov [edi+],edx
- ;创建特权级2的栈
- alloc_user_page
- mov eax,0x00000000
- mov ebx,0x000fffff
- mov ecx,0x00c0d200 ;4KB粒度的堆栈段描述符,特权级2
- call Core_Code_Segement:Make_Seg_Descriptor
- mov ebx,esi
- call Core_Code_Segement:Set_New_LDT_To_TCB
- or cx,0x0002 ;选择子特权级为2
- mov [edi+],cx ;cx是数据段的选择子
- mov edx,[esi+0x06] ;向上拓展的ESP2的初始值
- mov [edi+],edx
- ;现在开始重定位API符号表
- ;---------------------------------------------------------------------
- cld
- mov ecx,[0x0c]
- mov edi,[0x08]
- _loop_U_SALT:
- push edi
- push ecx
- mov ecx,salt_items_sum
- mov esi,salt
- _loop_C_SALT:
- push edi
- push esi
- push ecx
- mov ecx, ;比较256个字节
- repe cmpsd
- jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的
- mov eax,[esi] ;偏移地址
- mov [es:edi-],eax ;把偏移地址填入用户程序的符号区
- mov ax,[esi+0x04] ;段的选择子
- or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3
- mov [es:edi-],ax ;把段的选择子填入用户程序的段选择区
- _re_match:
- pop ecx
- pop esi
- add esi,salt_length
- pop edi
- loop _loop_C_SALT
- pop ecx
- pop edi
- add edi,
- loop _loop_U_SALT
- ;---------------------------------------------------------------------
- ;----------------------填入临时中转任务门选择子-----------------------
- mov ax,[salt_tp]
- mov [0x14],ax ;填充任务门选择子
- ;---------------------------------------------------------------------
- mov esi,[ebp+*] ;重新获得TCB的线性基地址
- ;在GDT中存入LDT信息
- mov eax,[esi+0x0c]
- movzx ebx,word[esi+0x0a]
- mov ecx,0x00408200 ;LDT描述符,特权级0级
- call Core_Code_Segement:Make_Seg_Descriptor
- call Core_Code_Segement:Set_New_GDT
- mov [esi+0x10],cx ;在TCB放入LDT选择子
- ;构建TSS剩下的信息表
- mov ebx,[esi+0x14]
- mov [ebx+],cx ;TSS中LDT选择子
- mov word[ebx+], ;填充反向链(任务切换的时候处理器会帮着填的,不用操心)
- mov dx,[esi+0x12] ;TSS段界限
- mov [ebx+],dx
- mov word[ebx+], ;T=0
- mov eax,[0x04] ;从任务的4GB地址空间获取入口点
- mov [ebx+],eax ;填写TSS的EIP域
- pushfd
- pop edx
- mov [ebx+],edx ;EFLAGS
- ;在GDT中存入TSS信息
- mov eax,[esi+0x14]
- movzx ebx,word[esi+0x12]
- mov ecx,0x00408900
- call Core_Code_Segement:Make_Seg_Descriptor
- call Core_Code_Segement:Set_New_GDT
- mov [esi+0x18],cx
- ;复制一份页表
- call Core_Code_Segement:Copy_Page
- mov ebx,[esi+0x14]
- mov [ebx+],eax ;填写PDBR(CR3)
- popad
- ret ;相当于是stdcall,过程清栈
- ;---------------------------------------------------------------------
- start:
- ;---------------------------------------------------------------------
- ;安装通用异常处理中断程序
- mov eax,general_exception_handler
- mov bx,Core_Code_Segement
- mov cx,0x8e00 ;中断门
- call Core_Code_Segement:Make_Gate_Descriptor
- mov ebx,IDT_Liner_Address
- xor esi,esi
- IDT_EXCEPTION:
- mov [ebx+esi*],eax ;偏移量是8不是4
- mov [ebx+esi*+],edx
- inc esi
- cmp esi,
- jle IDT_EXCEPTION
- ;---------------------------------------------------------------------
- ;安装通用中断处理中断程序
- mov eax,general_interrupt_handler
- mov bx,Core_Code_Segement
- mov cx,0x8e00 ;中断门
- call Core_Code_Segement:Make_Gate_Descriptor
- mov ebx,IDT_Liner_Address
- IDT_GENERAL:
- mov [ebx+esi*],eax
- mov [ebx+esi*+],edx
- inc esi
- cmp esi,
- jle IDT_GENERAL
- ;---------------------------------------------------------------------
- RTC_SET: ;实时时钟中断的填写
- mov eax,rtm_0x70_interrupt_handle
- mov bx,Core_Code_Segement
- mov cx,0x8e00
- call Core_Code_Segement:Make_Gate_Descriptor
- mov ebx,IDT_Liner_Address
- mov [ebx+0x70*],eax ;填充实时时钟中断的中断门
- mov [ebx+0x70*+],edx
- ;---------------------------------------------------------------------
- mov word[pidt_base_tmp],*-
- mov dword[pidt_base_tmp+],IDT_Liner_Address
- lidt [pidt_base_tmp]
- ;初始化8259A
- mov al,0x11
- out 0x20,al ;ICW1: 级联
- mov al,0x20
- out 0x21,al ;ICW2: 中断向量0x20-0x27
- mov al,0x04
- out 0x21,al ;ICW3: 从片接在主片的引脚2上
- mov al,0x01
- out 0x21,al ;ICW4: 全缓冲,手动EOI模式
- mov al,0x11
- out 0xa0,al ;ICW1:边沿触发/级联方式
- mov al,0x70
- out 0xa1,al ;ICW2: 起始中断向量
- mov al,0x04
- out 0xa1,al ;ICW3: 从片级联到IR2,这里主片一样是巧合
- mov al,0x01
- out 0xa1,al ;ICW4: 非总线缓冲,全嵌套,正常EOI
- ;设置和时钟中断相关的硬件
- mov al,0x0b ;RTC寄存器B
- or al,0x80 ;阻断NMI
- out 0x70,al ;0x70是索引端口
- mov al,0x12
- out 0x71,al ;设置寄存器B,禁止周期性中断,开放更新结束后中断,BCD码,24小时制
- mov al,0xfe
- out 0xa1,al ;只留从片的IR0
- mov ax,0xfb
- out 0x21,al
- mov al,0x0c
- out 0x70,al
- in al,0x71 ;读一下寄存器C
- ;一定要注意,一定要在中断装完之后才能用put_string,因为这个过程里面有sti
- mov ebx,message_start
- call Core_Code_Segement:put_string
- _@load:
- ;----------------------------安装门------------------------------------
- mov edi,salt
- mov ecx,salt_items_sum
- _set_gate:
- push ecx
- mov eax,[edi+]
- mov bx,[edi+] ;选择子
- mov cx,0xec00 ;门是特权级是3的门,那么任何程序都能调用
- or cx,[edi+] ;加上参数个数
- call Core_Code_Segement:Make_Gate_Descriptor
- call Core_Code_Segement:Set_New_GDT
- mov [edi+],cx ;回填选择子
- add edi,salt_length
- pop ecx
- loop _set_gate
- mov ebx,message_In_Gate
- call far [salt_1+] ;调用门显示字符信息(忽略偏移地址(前4字节))
- ;-------------------------初始化任务管理器-----------------------------
- mov word[core_tcb+0x04],0xffff ;状态忙碌
- mov dword[core_tcb+0x06],0x80100000
- mov word[core_tcb+0x0a],0xffff ;LDT初始界限
- mov ecx,core_tcb ;添加到TCB链中
- call append_to_tcb
- alloc_core_page ;为用户管理程序的TSS创造空间
- mov word[ebx+], ;TI=0
- mov word[ebx+], ;任务管理器不需要I/O映射,要大于等于界限
- mov word[ebx+], ;任务允许没有自己的LDT
- mov eax,cr3
- mov dword[ebx+],eax ;设置CR3,注意不是0了!
- mov word[ebx+], ;没有前一个任务
- mov eax,ebx
- mov ebx, ;TSS段界限
- mov ecx,0x00408900
- call Core_Code_Segement:Make_Seg_Descriptor
- call Core_Code_Segement:Set_New_GDT
- mov [core_tcb+0x18],cx
- ltr cx ;启动任务
- sti ;开中断
- ;------------------安装用户管理程序的临时返回任务门--------------------
- mov eax,0x0000 ;TSS不需要偏移地址
- mov bx,[core_tcb+0x18] ;TSS的选择子
- mov cx,0xe500
- call Core_Code_Segement:Make_Gate_Descriptor
- call Core_Code_Segement:Set_New_GDT
- mov [salt_tp],cx ;填入临时中转任务门选择子,注意不需要加260了
- ;----------------------------------------------------------------------
- ;创建用户任务的任务A控制块
- alloc_core_page ;TCB属于内核的东西
- mov word [ebx+0x04], ;任务状态:空闲
- mov dword [ebx+0x06], ;用户任务局部空间的分配从0开始。
- mov word [ebx+0x0a],0xffff ;登记LDT初始的界限到TCB中
- push dword User_Program_AddressA
- push ebx
- call load_program
- mov ecx,ebx
- call append_to_tcb
- ;----------------------------------------------------------------------
- ;创建用户任务的任务B控制块
- alloc_core_page ;TCB属于内核的东西
- mov word [ebx+0x04], ;任务状态:空闲
- mov dword [ebx+0x06], ;用户任务局部空间的分配从0开始。
- mov word [ebx+0x0a],0xffff ;登记LDT初始的界限到TCB中
- push dword User_Program_AddressB
- push ebx
- call load_program
- mov ecx,ebx
- call append_to_tcb
- ;----------------------------------------------------------------------
- _core:
- mov ebx,core_msg0
- call Core_Code_Segement:put_string
- hlt
- jmp _core
- ;----------------------------------------------------------------------
- ;=========================================================================
- SECTION core_trail
- ;----------------------------------------------------------------
- Program_end:
3. 两个用户程序
- ;================================用户程序A=======================================
- program_length dd program_end ;程序总长度#0x00
- entry_point dd start ;程序入口点#0x04
- salt_position dd salt_begin ;SALT表起始偏移量#0x08
- salt_items dd (salt_end-salt_begin)/
- ;SALT条目数#0x0C
- TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
- dw ;任务门的选择子#0x14
- Own_Page dd ;自己页面的物理地址#0x16
- ;-------------------------------------------------------------------------------
- ;符号地址检索表
- salt_begin:
- PrintString db '@Printf'
- times -($-PrintString) db
- TerminateProgram: db '@TerminateProgram'
- times -($-TerminateProgram) db
- ReadDiskData db '@ReadHarddisk'
- times -($-ReadDiskData) db
- Stop_This_Program db '@Stop_This_Program'
- times -($-Stop_This_Program) db
- salt_end:
- message_0 db ' User task A->;;;;;;;;;;;;; I am PhilipA ;;;;;;;;;;;;;;;;;;'
- db 0x0d,0x0a,
- ;-------------------------------------------------------------------------------
- [bits ]
- ;-------------------------------------------------------------------------------
- start:
- mov ebx,message_0
- call far [PrintString]
- call far [Stop_This_Program]
- jmp start
- jmp far [fs:TpBack]
- ;-------------------------------------------------------------------------------
- program_end:
- ;================================用户程序B=======================================
- program_length dd program_end ;程序总长度#0x00
- entry_point dd start ;程序入口点#0x04
- salt_position dd salt_begin ;SALT表起始偏移量#0x08
- salt_items dd (salt_end-salt_begin)/
- ;SALT条目数#0x0C
- TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
- dw ;任务门的选择子#0x14
- Own_Page dd ;自己页面的物理地址#0x16
- ;-------------------------------------------------------------------------------
- ;符号地址检索表
- salt_begin:
- PrintString db '@Printf'
- times -($-PrintString) db
- TerminateProgram: db '@TerminateProgram'
- times -($-TerminateProgram) db
- ReadDiskData db '@ReadHarddisk'
- times -($-ReadDiskData) db
- Stop_This_Program db '@Stop_This_Program'
- times -($-Stop_This_Program) db
- salt_end:
- message_0 db ' User task B->$$$$$$$$$$$$$ I am PhilipB $$$$$$$$$$$$$$$$$$'
- db 0x0d,0x0a,
- ;-------------------------------------------------------------------------------
- [bits ]
- ;-------------------------------------------------------------------------------
- start:
- mov ebx,message_0
- call far [PrintString]
- call far [Stop_This_Program]
- jmp start
- jmp far [fs:TpBack]
- ;-------------------------------------------------------------------------------
- program_end:
ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务的更多相关文章
- ASM:《X86汇编语言-从实模式到保护模式》第15章:任务切换
15章其实应该是和14章相辅相成的(感觉应该是作者觉得14章内容太多了然后切出来了一点).任务切换和14章的某些概念是分不开的. ★PART1:任务门与任务切换的方法 1. 任务管理程序 14章的时候 ...
- 设计模式之第17章-备忘录模式(Java实现)
设计模式之第17章-备忘录模式(Java实现) 好男人就是我,我就是曾小贤.最近陈赫和张子萱事件闹得那是一个沸沸扬扬.想想曾经每年都有爱情公寓陪伴的我现如今过年没有了爱情公寓总是感觉缺少点什么.不知道 ...
- ASM:《X86汇编语言-从实模式到保护模式》第9章:实模式下中断机制和实时时钟
中断是处理器一个非常重要的工作机制.第9章是讲中断在实模式下如何工作,第17章是讲中断在保护模式下如何工作. ★PART1:外部硬件中断 外部硬件中断是通过两个信号线引入处理器内部的,这两条线分别叫N ...
- ASM:《X86汇编语言-从实模式到保护模式》第11章:进入保护模式
★PART1:进入保护模式 1. 全局描述符表(Global Descriptor Table,GDT) 32位保护模式下,如果要使用一个段,必须先登记,登记的信息包括段的起始地址,段的 ...
- ASM:《X86汇编语言-从实模式到保护模式》5-7章:汇编基础
第5-7章感觉是这一本书中比较奇怪的章节,可能是作者考虑到读者人群水平的差异,故意由浅入深地讲如何在屏幕上显示字符和使用mov,jmp指令等等,但是这样讲的东西有点重复,而且看了第六,第七章以后,感觉 ...
- ASM:《X86汇编语言-从实模式到保护模式》第14章:保护模式下的特权保护和任务概述
★PART1:32位保护模式下任务的隔离和特权级保护 这一章是全书的重点之一,这一张必须要理解特权级(包括CPL,RPL和DPL的含义)是什么,调用门的使用,还有LDT和TSS的工作原理(15章着重 ...
- ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构
★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...
- ASM:《X86汇编语言-从实模式到保护模式》第8章:实模式下硬盘的访问,程序重定位和加载
第八章是一个非常重要的章节,讲述的是实模式下对硬件的访问(这一节主要讲的是硬盘),还有用户程序重定位的问题.现在整理出来刚好能和保护模式下的用户程序定位作一个对比. ★PART1:用户程序的重 ...
- ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配
第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式. ★PART1:Intel X86基础分 ...
随机推荐
- 取文件MD5 WINAPI
#include <windows.h> #include <wincrypt.h> #include <stdio.h> BOOL GetFileHash(LPC ...
- Sort using in VS
- C 语言学习 第三次作业总结
本次作业内容: For循环的使用 If判断语句的使用 常用数学运算表达式的使用 数学函数库中几个常见函数的使用及自我实现 将操作代码提交到coding 作业总结: For循环是C语言中一种基本的循环语 ...
- Java构造和解析Json数据
BaseResult wyComany = propertyService.getWyCompanyById(CommunityInfos.getWyCompany());//这里返回的是json字符 ...
- Geolocation API JavaScript访问用户的当前位置信息
Geolocation API在浏览器中的实现是navigator.geolocation对象,常用的有以下方法. 1.第一个方法是getCurrentPosition() 调用这个方法就会触发请求用 ...
- windows 下 新建 点开头的文件和文件夹
新建 .aaa文件夹 cmd:$ mkdir .aaa 新建 .aaa文件夹 echo " >> .aaa
- ppt2013技术整理
1. 显示选择窗格 便于选择该页的所有元素.分组.隐藏与显示等. 位于:开始-编辑-选择-选择窗格 2. 显示动画窗格 便于调节页面中元素的动画状态. 位于:动画-高级动画-动画窗格 3. 绑定动画触 ...
- Samba服务器配置
Samba服务器配置流程: (1)安装samba服务器先用#rpm -ivh samba-列出与samba有关的rpm包然后选择第一个包,用tab键补齐文件名 (2)创建新用户和其密码#useradd ...
- js轮播(qq幻灯片效果)
<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equ ...
- arcgis engine 监听element的添加、更新和删除事件(使用IGraphicsContainerEvents)
IGraphicsContainerEvents Interface 如何监听 element事件? 如,当我们在Mapcontrol上添加.删除.更新了一个Element后,如何捕捉到这个事件? ...