diff --git a/docs/lab1/libs/x86_h.md b/docs/lab1/libs/x86_h.md new file mode 100644 index 0000000..01877c4 --- /dev/null +++ b/docs/lab1/libs/x86_h.md @@ -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置0,ESI 或者 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:DI,ES: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. + + + diff --git a/docs/lab1/控制寄存器.md b/docs/lab1/控制寄存器.md new file mode 100644 index 0000000..9b32b39 --- /dev/null +++ b/docs/lab1/控制寄存器.md @@ -0,0 +1,18 @@ +### 控制寄存器 + +--- + +- **CR0** — 包含系统控制标志,控制处理器的操作模式和状态 +- **CR1** — 保留 +- **CR2** — 包含 page-fault 线性地址 (造成 page-fault 的线性地址) +- **CR3** — 包含页目录基址以及两个标志位 +- **CR4** — 包含一组使能几个CPU扩展的标志位 + +#### CR0 控制寄存器 + +**PG**:当置位时,允许页表映射,否则不允许页表映射,此时所有线性地址被看做物理地址。 + +#### 参考手册 + +[1]. intel 手册卷三 2.5 Control Registers + diff --git a/docs/lab1/练习二.md b/docs/lab1/练习二.md new file mode 100644 index 0000000..2714e16 --- /dev/null +++ b/docs/lab1/练习二.md @@ -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段 +② 初始化控制台 +③ 打印欢迎信息 +④ 打印内核信息 +...(后面的参考注释) +``` +