ucore lab1
上来先看看Intel 80386 Programmer's Reference Manual, 1987 (HTML)吧。 http://www.logix.cz/michal/doc/i386/
makefile
https://chyyuu.gitbooks.io/ucore_os_docs/content/lab0/lab0_ref_ucore-resource.html
跟我一起写makefile > https://seisman.github.io/how-to-write-makefile/functions.html > makefile这鬼东西真tm功能强,强得我什么都看不懂。原来在那个眼花缭乱的makefile里面全是各种各样的函数。。。。 但是实际上不需要全部读懂,稍微看看就好,这个不是重点,而且好像这个脚本比较通用。
https://blog.csdn.net/u013484370/article/details/50638353
1
2
3
4
5
6
7
8
9
10
11
12 # add files to packet: (#files, cc[, flags, packet, dir])
#此模板,就是真正在makefile中用来编译所有的目表文件,并生成makefile规则的模板。
define do_add_files_to_packet
#__temp_packet__用来记录所有的临时目标文件。
__temp_packet__ := $(call packetname,$(4))
ifeq ($$(origin $$(__temp_packet__)),undefined)
$$(__temp_packet__) :=
endif
__temp_objs__ := $(call toobj,$(1),$(5))
$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5))))
$$(__temp_packet__) += $$(__temp_objs__)
endef
其他资料: > How to make an Operating System > https://samypesse.gitbook.io/how-to-create-an-operating-system/chapter-3 > 别人的lab1实验报告: > https://www.jianshu.com/p/2f95d38afa1d > https://www.cnblogs.com/maruixin/p/3175894.html >
硬盘读取
《读取磁盘:LBA方式》 https://www.cnblogs.com/mlzrq/p/10223060.html
这个写的比那个gitbook实验指导书讲得好一点 > LBA简介 > 磁盘读取发展 > > IO操作读取硬盘的三种方式: > > chs方式 :小于8G (8064MB) > > LBA28方式:小于137GB > > LBA48方式:小于144,000,000 GB > > LBA方式访问使用了data寄存器,LBA寄存器(总共3个),device寄存器,command寄存器来完成的。 > > LBA28和LBA48方式: > LBA28方式使用28位来描述一个扇区地址,最大支持128GB的硬磁盘容量。 > > LBA28的寄存器 > >
寄存器 | 端口 | 作用 |
---|---|---|
data寄存器 | 0x1F0 已经读取或写入的数据,大小为两个字节(16位数据) | 每次读取1个word,反复循环,直到读完所有数据 |
features寄存器 | 0x1F1 | 读取时的错误信息,写入时的额外参数 |
sector count寄存器 | 0x1F2 | 指定读取或写入的扇区数 |
LBA low寄存器 | 0x1F3 | lba地址的低8位 |
LBA mid寄存器 | 0x1F4 | lba地址的中8位 |
LBA high寄存器 | 0x1F5 | lba地址的高8位 |
device寄存器 | 0x1F6 | lba地址的前4位(占用device寄存器的低4位) 主盘值为0(占用device寄存器的第5位) 第6位值为1 LBA模式为1,CHS模式为0(占用device寄存器的第7位) 第8位值为1 |
command寄存器 | 0x1F7 | 读取,写入的命令,返回磁盘状态。 1 读取扇区:0x20 写入扇区:0x30 磁盘识别:0xEC |
IDE通道1,读写0x1f0-0x1f7号端口 IDE通道2,读写0x170-0x17f号端口
【补充】保护模式下,有两个段表:GDT(Global Descriptor Table)和LDT(Local Descriptor Table),每一张段表可以包含8192 (2^13)个描述符[1],因而最多可以同时存在2 * 2^13 = 2^14 个段。虽然保护模式下可以有这么多段,逻辑地址空间看起来很大,但实际上段并不能扩展物理地址空间,很大程度上各个段的地址空间是相互重叠的。目前所谓的64TB(2^(14+32) =2^46 )逻辑地址空间是一个理论值,没有实际意义。在32位保护模式下,真正的物理空间仍然只有2^32字节那么大。注:在ucore lab中只用到了GDT,没有用LDT。
没想到说32位系统最多有4gb内存不是假的啊。。。换页还能换着用超出4gb的内存吗?
另外为什么是2^13次方? 因为段寄存器占用了三个比特位用来干别的。参见 https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_1_protection_mode.html 这实验指导书这次摘录得特别好啊!
段描述符
问: 如果某个段描述符要求的权限级别是3,那么我通过加载段寄存器,index选择这个描述符,但是权限级是0,那这样不就让当前权限变成0了? 答:对应的段因为权限是三,本来就没有对权限做限制。所以访问起来并没有什么区别。 但是如果再切换的话就会造成影响,使得能够切换到ring0的数据段。 难道是真正切换时,权限级不由自己加载的段寄存器的值确定,而是由选择出来的段描述符的权限级确定?
特权级和特权级转移 https://www.jianshu.com/p/377f473dd0a9
这段描述符的结构很重要。 * 段基地址:规定线性地址空间中段的起始地址。在80386保护模式下,段基地址长32位。因为基地址长度与寻址地址的长度相同,所以任何一个段都可以从32位线性地址空间中的任何一个字节开始,而不象实方式下规定的边界必须被16整除。 * 段界限:规定段的大小。在80386保护模式下,段界限用20位表示,而且段界限可以是以字节为单位或以4K字节为单位。 * 段属性:确定段的各种性质。: * 段属性中的粒度位(Granularity),用符号G标记。G=0表示段界限以字节位位单位,20位的界限可表示的范围是1字节至1M字节,增量为1字节;G=1表示段界限以4K字节为单位,于是20位的界限可表示的范围是4K字节至4G字节,增量为4K字节。 * 类型(TYPE):用于区别不同类型的描述符。可表示所描述的段是代码段还是数据段,所描述的段是否可读/写/执行,段的扩展方向等。 * 描述符特权级(Descriptor Privilege Level)(DPL):用来实现保护机制。 * 段存在位(Segment-Present bit):如果这一位为0,则此描述符为非法的,不能被用来实现地址转换。如果一个非法描述符被加载进一个段寄存器,处理器会立即产生异常。图5-4显示了当存在位为0时,描述符的格式。操作系统可以任意的使用被标识为可用(AVAILABLE)的位。 * 已访问位(Accessed bit):当处理器访问该段(当一个指向该段描述符的选择子被加载进一个段寄存器)时,将自动设置访问位。操作系统可清除该位。
http://www.logix.cz/michal/doc/i386/chp06-03.htm#06-03-01 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65Figure 6-1. Protection Fields of Segment Descriptors
DATA SEGMENT DESCRIPTOR
31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+---------+-----------------+
|#################|#|#|#|A| LIMIT |#| | TYPE |#################|
|###BASE 31..24###|G|B|0|V| 19..16 |P| DPL | |###BASE 23..16###| 4
|#################|#|#|#|L| |#| |1|0|E|W|A|#################|
|-----------------+-+-+-+-+---------+-+-----+-+-+-+-+-+-----------------|
|###################################| |
|########SEGMENT BASE 15..0#########| SEGMENT LIMIT 15..0 | 0
|###################################| |
+-----------------+-----------------+-----------------+-----------------+
EXECUTABLE SEGMENT DESCRIPTOR
31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+---------+-----------------+
|#################|#|#|#|A| LIMIT |#| | TYPE |#################|
|###BASE 31..24###|G|D|0|V| 19..16 |P| DPL | |###BASE 23..16###| 4
|#################|#|#|#|L| |#| |1|0|C|R|A|#################|
|-----------------+-+-+-+-+---------+-+-----+-+-+-+-+-+-----------------|
|###################################| |
|########SEGMENT BASE 15..0#########| SEGMENT LIMIT 15..0 | 0
|###################################| |
+-----------------+-----------------+-----------------+-----------------+
SYSTEM SEGMENT DESCRIPTOR
31 23 15 7 0
+-----------------+-+-+-+-+---------+-+-----+-+-------+-----------------+
|#################|#|#|#|A| LIMIT |#| | | |#################|
|###BASE 31..24###|G|X|0|V| 19..16 |P| DPL |0| TYPE |###BASE 23..16###| 4
|#################|#|#|#|L| |#| | | |#################|
|-----------------+-+-+-+-+---------+-+-----+-+-------+-----------------|
|###################################| |
|########SEGMENT BASE 15..0#########| SEGMENT LIMIT 15..0 | 0
|###################################| |
+-----------------+-----------------+-----------------+-----------------+
A - ACCESSED E - EXPAND-DOWN
AVL - AVAILABLE FOR PROGRAMMERS USE G - GRANULARITY
B - BIG P - SEGMENT PRESENT
C - CONFORMING R - READABLE
D - DEFAULT W - WRITABLE
DPL - DESCRIPTOR PRIVILEGE LEVEL
Table 6-1. System and Gate Descriptor Types
Code Type of Segment or Gate
0 -reserved
1 Available 286 TSS
2 LDT
3 Busy 286 TSS
4 Call Gate
5 Task Gate
6 286 Interrupt Gate
7 286 Trap Gate
8 -reserved
9 Available 386 TSS
A -reserved
B Busy 386 TSS
C 386 Call Gate
D -reserved
E 386 Interrupt Gate
F 386 Trap Gate
IDT的结构
1 | Figure 9-1. IDT Register and Table |
这里的idt limit是idt的长度减1
1 | Figure 9-3. 80306 IDT Gate Descriptors |
关于 Segment Present
Segment-Present bit: If this bit is zero, the descriptor is not valid for use in address transformation; the processor will signal an exception when a selector for the descriptor is loaded into a segment register. Figure 5-4 shows the format of a descriptor when the present-bit is zero. The operating system is free to use the locations marked AVAILABLE. Operating systems that implement segment-based virtual memory clear the present bit in either of these cases:
When the linear space spanned by the segment is not mapped by the paging mechanism. When the segment is not present in memory.
TSS (Task Status Segment)
没想到在看似平常的GDT下面,居然出现了一个全新的没见过的概念。。。 https://www.cnblogs.com/yasmi/articles/5198138.html
首先GDT里有TSS Descriptor,用来保存当前指令地址。 另外,在IDT里有Task Gate Discriptor,相当于是TSS的一个指针,不过在权限上可以单独设置 Task Gate Discriptor在LDT里居然也有! http://www.logix.cz/michal/doc/i386/chp07-04.htm
- 加载task register ltr 指令使用提供的 selector 在 GDT / LDT 里索引查找到 TSS descriptor 后,加载到 TR 寄存器里。初始的 TSS descriptor 必须设为 available 状态,否则不能加载到 TR。processor 加载 TSS descriptor 后,将 TSS descriptor 置为 busy 状态。
- 任务切换 当前进程要切换另一个进程时,可以使用 2 种 selector 进行:使用 TSS selector 以及 Task gate selector(任务门符)。 当前进程的执行环境被保存在当前进程的 TSS segment 中。 发生了 TSS selector 切换。新的 TSS selector 被加载到 TR.selector,而新的 TSS descriptor 也被加载到 TR 寄存的隐藏部分。 processor 从当前的 TSS segment 取出新进程的执行环境。经过相关的 selector & descriptor 的常规检查以及权限检查。通过之后才真正加载。 将新进程的 TSS descriptor 置为 busy 状态。使得新进程不能重入。
https://www.xuebuyuan.com/737019.html 我们可以看到,只有用CALL指令+调用门方式跳转,且目标代码段是非一致代码段时,才会引起CPL的变化,即引起代码执行特权级的跃迁,这是目前得知的改变执行特权级的唯一办法
mark 关于push esp pop esp https://blog.csdn.net/particleHorizon/article/details/78722683
中断门调用过程
最早设置好idt表,通过汇编指令lidt。里面保存了表的大小和起始地址,地址指向的表的每一项都有地址,段寄存器,特权级这三个属性。特权级限制了访问,而地址加上段寄存器的组合就指向了中断服务程序。 于是当发生中断的时候,cpu就会找到指定的地址调用。 调用前,会依次把一些重要的寄存器压栈。 (特权级切换时还有的ss, esp)eflags, cs, eip, error code。 然后就像call调用一样,跳转到指定的地方继续运行。 这里,一般的操作系统会继续保存上下文,以方便恢复一切结束中断。被打断的有当前的各种寄存器,还有栈。结束的时候就恢复自己保存的栈和寄存器再iret就可以了。 当不切换栈的时候是直接保存在当前栈上,反正之后会回来清理,中断的时候cpu忙,旧的栈也没有人来用。 切换栈的时候,系统是会从tss段取出esp吗?保存的栈帧是在系统栈还是在用户栈??
https://www.cnblogs.com/chaozhu/p/6283495.html 1. 在发生中断、异常时前,程序运行在用户态,ESP指向的是Interrupted Procedure's Stack,即用户栈。 1. 运行下一条指令前,检测到中断(x86不会在指令执行没有指向完期间响应中断)。从TSS中取出esp0字段(esp0代表的是内核栈指针,特权级0)赋给ESP,所以此时ESP指向了Handler's Stack,即内核栈。 1. cpu控制单元将用户堆栈指针(TSS中的ss,sp字段,这代表的是用户栈指针)压入栈,ESP已经指向内核栈,所以入栈指的的是入内核栈。 1. cpu控制单元依次压入EFLAGS、CS、EIP、Error Code(如果有的话)。此时内核栈指针ESP位置见图4中的ESP After Transfer to Handler。
https://www.xuebuyuan.com/915513.html 也是讲堆栈切换的
为什么当前的tss保存的是内核的东西?虽然这和TSS的用法不符,但是应该没错吧。 为什么垃圾intel 80386 程序员手册没有在中断一节说清楚?是我瞎吗
那么当一个os初始化完成之后,它第一个用户态程序是怎么切换权限级的?直接加载一个更低的权限? 当系统解析ELF文件的时候,就要安排好各种数据,分段(有页吗?)设置好权限(读写执行)和权限级。然后跳转过去吗?这样不就直接转换
8295A和8253 timer
详解8259A https://blog.csdn.net/longintchar/article/details/79439466 https://baike.baidu.com/item/8253%E8%8A%AF%E7%89%87/3699917