操作系统笔记
8086基础阶段总结
通用寄存器
内存访问&字节序
段地址&偏移地址
虚拟硬盘の写入&虚拟机(Bochs)
虚拟硬盘
- 写入数据工具:WinHex
- 一切从主引导扇区开始
Bochs虚拟机
可调试操作系统の虚拟机
显卡&显存
屏幕显示文本
一字节字符编码信息 + 一字节显示属性信息
MOV
mov 目的操作数,源操作数
汇编程序の标号
代表距离最近的汇编指令の汇编地址
JMP -> 段间绝对跳转指令
jmp 段地址:偏移地址
虚拟机添加硬盘运行写屏程序
主引导扇区布局
0x07C00 - 0x07E00
使用标号计算偏移地址
直接 对标号做加减操作 进行跳转
寄存器 绝对 间接 近跳转 -> jmp bx
近跳转:段内跳转
间接 :跳转地址由寄存器间接给出
绝对 :给出的地址是实际地址
使用相对偏移量の短跳转&近跳转(可省参数)
- 短跳转:jmp (short) 标号 -编译-> EB 8位の相对偏移量
- 近跳转:jmp (near) 标号 -编译-> E9 16位の相对偏移量
屏幕显示数字
- 无符号数除法 指令
- XOR -> 异或指令
- ADD -> 加法指令
- 段超越前缀 -> 跨段访问内存时,使用另一个段地址寄存器作前缀用:连接
循环、批量传送 和 条件转移
NASM 的 $ 和 $$ 指令
$ -> 当前代码的汇编地址
$$ -> 代码首部的起始汇编地址
跳过 非指令の数据区
- 指令:db、dw等定义数据区
- 在起始处用jmp跳至指令区
串传送
使用前准备工作:
DS:SI 原始数据串地址:偏移地址
ES:DI 目标位置段地址:偏移地址
指令(REP指令依cx计数):
- (REP) MOVSB -> (重复) 按字节传送(一次)
- (REP) MOVSW -> (重复)按字传送(一次)
- CLD -> 方向标志清零,使之指示正方向(低地址->高地址)
- STD -> 置方向标志为1,指示反方向
标志寄存器FLAGS中:
- 方向标志(DF):指示串传送流方向
- 零 标志(ZF):指示最近一次计算所得是(1)否(0)为零
LOOP指令
用法:loop 标号(地址)
循环次数:cx寄存器中所存数值
机器码:E2 8位相对偏移量
执行过程:
- 将cx内容减一
- 若cx内容 不为零,转移执行,否则顺序往后执行
基址寻址& INC指令
INC -> 自增(可用于 基址偏移地址寻址 使基地址循环自增)
inc r/m(m需指定占用字节大小)
8086中,只能使用BX、SI、DI、BP寄存器来提供偏移地址(如mov [bx], dl)
寄存器BX在设计之初的作用之一就是用来提供数据访问的基地址,所以又叫基址寄存器(Base Address Register)*
在设计8086处理器时,每个寄存器都有自己的特殊用途,比如AX是累加器(Accumulator),与它有关的指令还会做指令长度上的优化(较短);CX是计数器(Counter);DX是数据寄存器(Data),除了作为通用寄存器外,还专门用于和外设之间进行数据传送;SI是源索引寄存器(Source Index);DI是**目标索引寄存器(Destination Index)**,用于数据传送操作,我们已经在movsb和movsw指令用法中领略过了
DEC指令
DEC -> 自减
dec (r/m)
基址变址寻扯&条件转移指令
8086中基址变址允许的组合:
bx + si
bx + di
bp + si
bp + di
条件转移指令:
- JNS -> 符号标志(SF)为0时跳转
标志寄存器FLAGS中:
- 符号标志(SF):计算结果最高位为0则为0,反之为1
计算机中的负数
减法指令&求补指令(SUB&NEG)
SUB -> 减法指令
sub r/m, r/m/imm(立即数)
注意:两操作数宽度需一致,且不能同为内存地址(m)
NEG -> 求负or求补
neg r/m -> 执行 0-操作数 操作 得到补码(负数),执行后替换操作数中的内容
对于 有(无)符号数 の区分
可以说,大多数指令既适用于无符号整数,也适用于有符号整数。指令执行的结果不管是用无符号整数来解释,还是用有符号整数来解释,都是正确的
但是,也有一些指令不能同时应付无符号数和有符号数,需要根据你的实际情况选择它们的无符号版本和有符号版本。比如,无符号数乘法指令mul和有符号数乘法指令imul,以及无符号数除法指令div和有符号数除法指令idiv
简单总结,有些指令皆适用,有些指令要区分使用
有符号数の除法指令IDIV
idiv r/m
商和余数的符号性
如果 被除数 和 除数 的符号不同,商为负数
余数的符号 始终 和被除数相同
有符号数の符号扩展指令
栈&逻辑指令
栈
cs -> 指定代码段识别区
ds/es -> 指定数据段识别区
ss:sp -> 指定栈段识别区
栈の使用:(必须保持栈平衡,有压必有出)
PUSH -> 入栈
push r/m
push dx
push word [0x2002]
push 执行过程:
- sp = sp - 操作数大小(字节);
- 利用ss:sp生成物理地址;
- 将操作数写入上述地址处
POP -> 出栈
pop r/m
pop ax
pop word [0x08]
pop 执行过程:
- 利用ss:sp生成物理地址;
- 从上述地址处取得数据,存入由操作数提供的目标位置处;
- sp = sp + 2;
注意:
- 必须保持栈平衡,有压必有出
- 充分估计需要的栈空间,开辟安全的空间
逻辑指令
OR -> 逻辑或
or r/m, r/m/imm(左右操作数长度需一致)
AND -> 逻辑与
and r/m, r/m/imm(长度需一致)
Intel8086处理器の寻址方式
- 寄存器/立即数寻址/直接寻址
- 基址寻址
- 变址寻址
- 基址变址寻址
硬盘&显卡の访问与控制
align -> 设置段对齐长度
section s align=16
vstart -> 设置段内相对起始汇编地址
section s align=16 vstart=0x100
加载器 & 用户程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18;=================================================
section header vstart=0
; 程序总长度
program_lenth dd program_end
; 用户程序入口点
code_entry dw start
dd section.code.start
; 段重定位表项个数
realloc_tbl_len dw (segtbl_end-segtbl_begin)/4
; 段重定位表
segtbl_begin:
code_segment dd section.code.start
data_segment dd section.data.start
stack_segment dd section.stack.start
segtbl_end:
;=================================================
section code align=16 vstart=0
start:section.段名字.start -> 计算段汇编地址
加载器的工作流程 & 常数的声明方法
加载器工作流程
- 读取用户程序的起始扇区
- 把整个用户程序都读入内存
- 计算段的物理地址和逻辑段地址(段的重定位)
- 转移到用户程序执行(将处理器的控制权交给用户程序)
声明常量:
1
app_lba_start equ(als) 100 ; 相当于#define,常数的声明不占用汇编地址
外围设备及其接口
中断&动态时钟显示
中断
外部硬件中断
后经中断屏蔽寄存器
标志寄存器可选是否忽略中断
1 -> 接受&响应中断,0 -> 屏蔽信号
实模式下的中断向量表(由 BIOS 在开机时创建供 CPU 使用)
中断号 x 4 = 物理地址(段地址 + 偏移地址)
遇到中断时操作
- 保护断点现场(将标志寄存器、指令指针寄存器等压栈)
- 执行中断处理程序(取得地址、执行)
- 遇到 iret(中断返回)指令,返回断点,将前状态出栈,继续执行
时钟(RTC 电路 + CMOS RAM)
RTC:单独供电,CMOS RAM 中一小部分存储时钟信息,其它空间存储硬件配置等信息
RTC 电路可以发出的中断信号
周期性中断信号(Periodic Interrupt: PI)
PIE 置 1 时需将 0 ~ 3 位置数选择速率(0 ~ 3 全部置 0 则 PIE 自动置 0)
更新周期结束中断(Update-ended Interrupt: UI)
进行时钟更新的周期
更新周期的开关
更新周期的过程监视
时钟更新过程中会暂时与总线断开以防误访问
闹钟中断(Alarm Interrupt: AI)
中断类型的判别
对 寄存器 C(只读寄存器) 的读操作会使所有位清零,低 4 位为保留位(始终为 0)