This commit is contained in:
kiukotsu 2018-12-03 21:40:13 +08:00
parent a23ed1ce83
commit ed68c5c1fa
3 changed files with 172 additions and 0 deletions

91
docs/lab1/libs/x86_h.md Normal file
View File

@ -0,0 +1,91 @@
##### do_div(n, base)
- 在[内联汇编官网](https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints)搜索 x86然后就能看到关于`"A"` 约束的描述:表示 eax 和 ebx 寄存器对,用来返回双字结果。如果是单字的话,那么就会被随机分配到 eax 或者 ebx 中。
- `div val` 指令将 `ax, ax:ax, edx:eax, rdx:rax` 中存储的数除以`val`,并将结果存储在 `ax, ax:ax, edx:eax, rdx:rax` 中。商存在 rax 中,余数存在 rdx 中。`val` 的字节数对应使用何种寄存器。例如64字节则使用 rdx 和 rax。
- 代码的整体结构如下:
```assembly
#define do_div(n, base) ({ \
unsigned long __upper, __low, __high, __mod, __base; \
__base = (base); \
asm("" : "=a" (__low), "=d" (__high) : "A" (n)); \
__upper = __high; \
if (__high != 0) { \
__upper = __high % __base; \
__high = __high / __base; \
} \
asm("divl %2" : "=a" (__low), "=d" (__mod) \
: "rm" (__base), "0" (__low), "1" (__upper)); \
asm("" : "=A" (n) : "a" (__low), "d" (__high)); \
__mod; \
}) \
```
$$
\left\{
\begin{array}{}
n = x:y; \\
\_high = x; \\
\_low = y; \\
\_base = b; \\
\_upper = u;
\end{array}
\right.\\
u = x \% b; \\
x = x / b; \\
u:y / b ==> 商给 \_low, 余数给 \_mod
x:\_low 组成了商,\_mod形成了余数。
$$
上面一整段的操作逻辑就是,`n / base ` 商存在`n` 中,余数存在 `mod` 中,并返回 `mod`
##### inb(unint16_t port)
从16位端口 port 读取一个字节存放到 data 中。
##### insl(unit32_t port, void *addr, int cnt)
- **cld**DF置0ESI 或者 EDI 自增
- The REP (repeat)
- REPE (repeat while equal)
- REPNE (repeat while not equal):重复执行一个字符串操作指令
- REPZ (repeat while zero)
- REPNZ (repeat while not zero) ZF = 0表示运算结果非0则一直执行直到 ZF = 1
- **ins**从指定的I/O端口读取到 ES:DIES:EDI 或者RDI及寄存器指定的内存地址。端口号由DX寄存器制定。
- `"=D"` 表示 `edi` 约束。
- `"cc"` 表示汇编代码会修改标志寄存器
- `"memory"` 表示汇编代码对输入和输出操作数中列出的项以外的项执行内存读取或写入操作例如访问其中一个输入参数指向的内存。为确保内存包含正确的值GCC可能需要在执行asm之前将特定的寄存器值刷新到内存中。此外编译器不会假定在asm执行前从内存读取的数据会在asm执行后仍然保持不变;它会根据需要重新加载它们。使用“memory”clobber有效地形成了编译器的读/写内存屏障。将寄存器的值刷到内存会对性能产生影响。“memory”clobber使得GCC认为任何内存都可以由asm块任意读取或写入因此会阻止编译器重新排序加载或存储在其中。
> 不管是编译器重排指令还是CPU乱序处理都要遵守一个最基本的原则”不能违反指令之间的依赖“即有依赖的指令之间是不能够重排或乱序的。
简单来说”memory“就是告诉编译器这段汇编代码段我修改了内存所以这段汇编代码前面的指令不能乱序到后面执行后面的指令不能乱序到前面执行就相当于一个内存屏障一样。主要是防止`-fschedule-insns`优化时做代码重排。
从32为端口port读取cnt个字节到 addr 指向的内存区域。
##### sti
设置中断标志位IF允许处理器响应可屏蔽硬件中断
##### cli
清除中断标志位IF使得处理器忽略可屏蔽外部中断。
> 这两个指令只能在内核模式下执行不可以在用户模式下执行而且在内核模式下执行时应该尽可能快的恢复中断因为CLI会禁用硬件中断若长时间禁止中断会影响其他动作的执行如移动鼠标等等系统就会变得不稳定。在标志寄存器中中断标志清零的情况下可以以“int ××”的形式调用软中断。
##### ltr
Load Task Register.

View File

@ -0,0 +1,18 @@
### 控制寄存器
---
- **CR0** — 包含系统控制标志,控制处理器的操作模式和状态
- **CR1** — 保留
- **CR2** — 包含 page-fault 线性地址 (造成 page-fault 的线性地址)
- **CR3** — 包含页目录基址以及两个标志位
- **CR4** — 包含一组使能几个CPU扩展的标志位
#### CR0 控制寄存器
**PG**:当置位时,允许页表映射,否则不允许页表映射,此时所有线性地址被看做物理地址。
#### 参考手册
[1]. intel 手册卷三 2.5 Control Registers

63
docs/lab1/练习二.md Normal file
View File

@ -0,0 +1,63 @@
### 练习二
> ```
> next 单步到程序源代码的下一行,不进入函数。
> nexti 单步一条机器指令,不进入函数。
> step 单步到下一个不同的源代码行(包括进入函数)。
> stepi 单步一条机器指令。
> ```
**问题一**从CPU加电后执行的第一条指令开始单步跟踪BIOS的执行。
**回答**:在 lab1 目录下运行如下代码:
```
make debug
i r
x /2i 0xffff0 CS:IP = 0xffff0
x /10i 0xfe05b (0xfe05b是BIOS跳转的地址)
```
BIOS里面的代码没啥好看的都是写上电自检等硬件相关的例程不需要过多关心。
**问题二**在初始化位置0x7c00设置实地址断点,测试断点正常。
**回答**直接将tools/gdbinit中的内容修改如下
```shell
file bin/kernel
target remote :1234
set architecture i8086
b *0x7c00
continue
x /2i $pc
```
然后运行 `make debug` 调试即可。
**问题三**从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
**回答**:直接运行 `make debug-nox` ,然后运行 `x /32i $pc`,之后就能看到`call bootmain` 对应的汇编代码是`0x7c4a: call 0x7ccf` 。然后直接 `b *0x7c4a``c` 。之后就跳转到 `bootmain` 的执行代码。
```
把磁盘的前4K字节读入0x10000位置处磁盘对应ucore.img具体参考make debug-nox执行的Makefile命令。这前4K个字节包含了ELF头部。然后执行ELF头部所指示的入口点通过 readelf -eW kernel 可以查看ucore.img 的入口点)。之后程序就跳转到地址 0x100000 处执行。
```
> 注意ucore.img加载的位置是0x10000 程序跳转执行的位置是0x100000。
然后 `b *0x100000``c` 就可以看到程序跳转到内核初始化的代码部分了至此内核执行前的准备工作已经全部完成。禁用中断设置数据段寄存器使能A20引脚加载GDT进入保护模式跳转到bootmain读取ELF头头部跳转到内核初始化代码处
**问题四**自己找一个bootloader或内核中的代码位置设置断点并进行测试。
**回答**:内初初始化的操作逻辑
```shell
在tools/kernel.ld文件中定义了内核的各个段的分布情况
① 数据段之后的所有内容全部用0填充即.bss段
② 初始化控制台
③ 打印欢迎信息
④ 打印内核信息
...(后面的参考注释)
```