DB define byte
RESB reserve byte
DW define word
DD define double-word
word -> 16 bytes 字节 -> 16*8 = 128 bits
double- word -> 32 bytes -> 256 bits0x1fe-$:这一行现在的字节数如果是100, 则输出 0x1fe-100 个字节的 0x00
FAT12:windows软盘格式 File Allocation Table 文件配置表
boot sector 启动区:软盘的第一个扇区,会检查这个扇区的最后两个字节是否为0x55 AA(固定),否则会启动错误
扇区:计算机读取软盘以512字节也就是一个扇区为一个单位进行读写
IPL:initial program loader 启动程序加载器 把加载操作系统本身的程序放在启动区里
输入文本 cmd.exe,保存文件,文件名修改为!cons_nt.bat, windows批处理文件, 在当前目录下打开cmd命令行
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
DB 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
DB 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
RESB 16
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd, 0x0a, 0x0a, 0x68, 0x65
DB 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
DB 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 368
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
; hello-os
; TAB=4
; 标准FAT12格式软盘专用的代码
DB 0xeb, 0x4e, 0x90
DB "HELLOIPL" ; 启动区名称(8字节)
DW 512 ; 扇区(sector)大小 必须512字节
DB 1 ; 簇(cluster)大小 必须为一个扇区
DW 1 ; FAT的起始位置 一般为第一个扇区
DB 2 ; FAT的个数 一般为2
DW 224 ; 根目录的大小 一般224
DW 2880 ; 该磁盘的大小 必须是2880扇区
DB 0xf0 ; 磁盘的种类 必须是0xf0
DW 9 ; FAT的长度 必须是9扇区
DW 18 ; 1个磁道(track)的扇区数目 必须是18
DW 2 ; 磁头数 必须是2
DD 0 ; 不使用分区 必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 固定
DD 0xffffffff ; 卷标号码(可能)
DB "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称(8字节)
RESB 18 ; 先空出18字节
; 程序主体
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd
; 信息显示部分
DB 0x0a, 0x0a ; 0x0a表示换行
DB "hello, world"
DB 0x0a ; 换行
DB 0
RESB 0x1fe-$ ; 填写0x00直到0x001fe
DB 0x55, 0xaa
; 启动区以外部分的输出
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
BIOS: basic input output system 它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序
ORG origin 指明程序装载地址 0x7c00~0x7dff 内存中用于启动区内容的装载地址
JMP jump -> goto 跳转到
xxx: 标签的声明
MOV 赋值 MOV AX, 0 -> AX = 0
MOV SS, AX -> SS = AX
类似与copyAX accumulator 累加寄存器
CX counter 计数register
DX data 数据r
BX base 基址r
SP stack pointer 栈指针r
BP base pointer 基址指针r
SI source index 源变址r
DI destination index 目的变址r
以上都为16位寄存器 可以存储16位的二进制数AH——累加寄存器高位(accumulator high)AL——累加寄存器低位(accumulator low)
CH——计数寄存器高位(counter high)CL——计数寄存器低位(counter low)
DH——数据寄存器高位(data high)DL——数据寄存器低位(data low)
BH——基址寄存器高位(base high)BL——基址寄存器低位(base low)
8个8位寄存器 16位寄存器的高8位低8位EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
32位寄存器, ExtendAX … 低16位为16位寄存器MOV SI, msg 把根据ORG指令计算出的msg的内存地址传给SI
MOV AL, [SI] [ ]表示内存地址
MOV指令源数据与目的数据位数必须一致 所以可以省略BYTEMOV WORD [678], 123
123会被解释成16位的二进制数 678号存储低8位 679号存储高8位指定"数据大小"为BYTE就是地址所指定的字节
WORD 则相邻的字节也会成为这个指令的操作对象
DWORD 地址相邻的两个字节(共4个字节)成为这个指令的操作对象
相邻 -> 地址增加方向寄存器指定内存地址只有BX BP SI DI
CMP compare 比较是否相等
JE jump if equal 根据比较的结果决定是否跳转 相等则跳转 不相等继续执行
CMP AL, 0
JE fin
if (AL == 0) { goto fin; }
INT interrupt 中断
HLT halt 停止 让CPU进入待机状态
; hello-os
; TAB=4
ORG 0x7c00 ; 指明程序装载地址
; FAT12格式
JMP entry
DB 0x90
DB "HELLOIPL" ;
DW 512 ;
--- 省略
; 程序核心
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; add 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让cpu停止等待指令
JMP fin ; 无限循环
msg:
DB 0x0a, 0x0a ; 换行
DB "hello, world"
DB 0x0a ;
DB 0
RESB 0x7dfe-$ ;
DB 0x55, 0xaa
;
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
# 直接输入make时默认是make img
default :
../z_tools/make.exe img
# 编译
# ipl.nas和Makefile存在才会继续
ipl.bin : ipl.nas Makefile
../z_tools/nask.exe ipl.nas ipl.bin ipl.lst
# 生成镜像文件
# \换行继续的符号
helloos.img : ipl.bin Makefile
../z_tools/edimg.exe imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img
# 编译
asm :
../z_tools/make.exe -r ipl.bin
# 生成镜像文件
img :
../z_tools/make.exe -r helloos.img
# img -> helloos.img -> ipl.bin 直接执行会向上找一个一个执行
# 最后在qemu虚拟机中执行
run :
../z_tools/make.exe img
copy helloos.img ..\z_tools\qemu\fdimage0.bin
../z_tools/make.exe -C ../z_tools/qemu
install :
../z_tools/make.exe img
../z_tools/imgtol.com w a: helloos.img
clean :
-del ipl.bin
-del ipl.lst
# 只保留源文件
src_only :
../z_tools/make.exe clean
-del helloos.img
JC jump if carry 如果进位标志(carry flag)是1就跳转
INT 0x13 调用磁盘BIOS
AH=0x02;(读盘)AH=0x03;(写盘)AH=0x04;(校验)AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号 &0xff;
CL=扇区号(0-5位)|(柱面号&0x300)>>2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)
返回值:FLAGS.CF0:没有错误
AH0FLAGS.CF==1:有错误,错误号码存入AH内(与重置(reset)功能一样)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PX7CGcZw-1595223014838)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200703105012620.png)]
80(cylinder) * 18(sector) * 2(head) * 512(bytes) = 1474560 = 1440KB
事实上,不管我们要指定内存的什么地址,都必须同时指定段寄存器,这是规定。一般如果省略的话就会把 DS: 作为默认的段寄存器
MOV CX, [1234] -> MOV CX, [DS:1234] 可以理解为DS*16+1234的内存地址, 先用DS指定大致地址, 再用1234指定具体地址, DS必须预先指定为0JNC jump if not carry 返回值为0时跳转
JAE jump if above or equal 大于等于则跳转
JBE jump if below or equal 小于等于则跳转
ES指定读入地址, CL+1, ES+0x20 即可读下一个扇区
JB jump if below 小于则跳转
EQU equal
CYLS EQU 10 -> #define CYLS 10 -> CYLS=10一般向一个空软盘保存文件时
(1) 文件名会写在0x002600以后的地方
(2) 文件的内容会写在0x004200以后的地方设定AH=0x00后,调用显卡BIOS的函数,这样就可以切换显示模式了
设置显卡模式(video mode)
❏ AH=0x00;
❏ AL=模式:(省略了一些不重要的画面模式)
0x03:16色字符模式, 80× 25
0x12:VGA图形模式, 640× 480× 4位彩色模式,独特的4面存储模式
0x13:VGA图形模式, 320× 200× 8位彩色模式,调色板模式
0x6a:扩展VGA图形模式,800× 600× 4位彩色模式,独特的4面存储模式(有的显卡不支持这个模式)
❏ 返回值:无VRAM video ram
❏ 首先,使用cc1.exe从bootpack.c生成bootpack.gas
❏ 第二步,使用gas2nask.exe从bootpack.gas生成bootpack.nas
❏ 第三步,使用nask.exe从bootpack.nas生成bootpack.obj
❏ 第四步,使用obi2bim.exe从bootpack.obj生成bootpack.bim
❏ 最后,使用bim2hrb.exe从bootpack.bim生成bootpack.hrb
❏ 这样就做成了机器语言,再使用copy指令将asmhead.bin与bootpack.hrb单纯结合到起来,就成了haribote.sys
gcc(gas汇编) -> cc1编译器 -> gas2nask -> nask2obj -> object目标文件 特殊机器语言文件必须与其他文件链接link才能使用 -> obj2bim -> binary image -> bim2hrb -> hrb链接后根据不同的操作系统的要求进行加工 -> asmhead.bim + bootpack.bim -> haribote.sysGLOBAL 声明需要连接的函数名
RET return
; 新 增 读C0-H0-S2
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0 cylinder
MOV DH,0 ; 磁头0 head
MOV CL,2 ; 扇区2 sector
MOV AH,0x02 ; AH=0×02:读盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0×00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JC error ; 返回值为1时跳转
; 读磁盘 C0-H0-S2 失败重新读直到5次
MOV AX,0X0820
MOV ES, AX
MOV CH, 0 ; cylinder
MOV DH, 0 ; head
MOV CL, 2 ; sector
MOV SI, 0 ; 记录读取磁盘失败次数
retry:
MOV AH, 0X02
MOV AL, 1
MOV BX, 0
MOV DL, 0X00
INT 0X13
JNC fin
ADD SI, 1
CMP SI, 5
JAE error
MOV AH, 0x00 ; 三步系统复位 reset
MOV DL, 0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
; read disk C0-H0-S2~C0-H0-S18 读17个扇区
CYLS EQU 10
mov ax, 0x0820
mov es, ax
mov ch, 0 ; cylinder
mov dh, 0 ; head
mov cl, 2 ; sector
readloop:
mov si, 0
retry:
mov ah, 0x02
mov al, 1
mov bx, 0
mov dl, 0x00
int ox13
jnc next
add si, 1
cmp si, 5
jae error
mov ah, 0x00
mov dl, 0x00
int 0x13
jmp retry
next:
mov ax, es
add ax, 0x0020
mov es, ax
add cl, 1
cmp cl, 18
jbe readloop ; <=18 jump
; 继续读到C9-H1-S18
next:
mov ax, es
add ax, 0x0020
mov es, ax
add cl, 1
cmp cl, 18
jbe readloop
mov cl, 1
add dh, 1 ; head
cmp dh, 2
jb readloop ; <2 jump
mov dh, 0
add ch, 1
cmp ch, CYLS
jb readloop ; <10 jump
; haribote-os
; TAB=4
; BOOT_INFO
CYLS EQU 0X0FF0 ; 设定启动区
LEDS EQU 0XOFF1
VMODE EQU 0X0FF2 ; 关于颜色数目的信息
SCRNX EQU 0X0FF4 ; 分辨率的x screen x
SCRNY EQU 0X0FF8 ; screen y
ORG 0xc200 ; 装载内存地址
MOV AL,0x13 ; 显卡模式 video mode
MOV AH,0x00
INT 0x10
MOV BYTE[VMODE], 8 ; 记录画面模式
MOV WORD[SCRNX], 320
MOV WORD[SCRNY], 200
MOV DWORD[VRAM], 0X000A0000
;BIOS取得键盘上指示灯的状态
MOV AH, 0x02
INT 0x16 ; keyborad BIOS
MOV [LEDS], AL
fin:
HLT
JMP fin
; naskfunc
; TAB=4
; 实现HLT功能
; 与bootpack.obj链接
[FORMAT "WCOFF"] ; 制作目标文件
[BITS 32] ; 制作32位机器语言模式
[FILE "naskfunc.nas"]
GLOBAL _io_hlt
[SECTION .text]
_io_hlt: ; void io_hlt(void)
HLT
RET
与C语言联合自由使用的寄存器只有EAX ECX EDX
[INSTRSET “i486p”] 486用 -> 32位CPU
OR 有1就为1
AND 都是1为1
XOR 相同为0不同为1CLI clear interrupt flag 将中断标志置0
STI set interrupt flag 将中断标志置1[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iU7iBFqp-1595223014840)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200704012224978.png)]
PUSHFD push flags double-word 将标志位的值按双字长压入栈
POPFD pop flags double-word 弹出栈pushfd pop eax -> mov eax, eflags
; 向VRAM写入数据
_write_mem8: ;void write_mem8(int addr, int data)
mov ECX, [ESP+4] ;[ESP+4]中存放的是地址
mov AL, [ESP+8] ;[ESP+8]中存放的是数据
mov [ECX], AL
RET
// link
void io_hlt(void);
void write_mem8(int addr, int data);
void HariMain(void) {
int i;
for(i = 0xa0000; i <= 0xaffff; i++){
write_mem8(i, 15);
}
for(;;){
io_hlt();
}
}
// 用指针重写
write_mem8(i, i & 0x0f);
int i;
char *p;
for (i = 0xa0000; i <= 0xaffff; i++) {
// mov byte[i], (i & 0x0f)
// 第一种写法
p = i;
*p = i & 0x0f;
}
p = (char *) 0xa0000;
for (i = 0; i <= 0xffff; i++) {
// 第二种
*(p + i) = i & 0x0f;
// 第三种
p[i] = i & 0x0f;
}
// *(p+i) -> BYTE[p+i] -> C语言中p[i]表示 -> 当然也可以用i[p]表示
// 想不明白 *p -> p[0]
char *p; // 用于BYTE类地址
short *p; // 用于WORD类地址
int *p; // 用于DWORD类地址
// 因为*p是用来记录地址的变量, 所以都是4字节
p = (char *) i;
// 指针 -> 内存地址 -> 整数 ≠> 内存地址 -> cast -> 表示内存地址的整数 -> 地址值
// 只存在p, *p相当于汇编中的 BYTE[p]
// 显卡模式 -> 调色盘 -> 设定颜色
void io_hlt(void);
void io_cli(void);
void io_out8(int port, int data);
int io_load_eflags(void);
void io_store_eflags(int eflags);
void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void HariMain(void)
{
int i;
char *p;
init_palette();
p = (char *) 0xa0000;
for (i = 0; i <= 0xffff; i++) {
p[i] = i & 0x0f;
}
for (;;) {
io_hlt();
}
}
void init_palette(void)
{
// 避免一个一个赋值 -> static char -> 相当于汇编中的DB
// unsigned char -> 0~255
// signed char -> -128~127
static unsigned char table_rgb[16 * 3] = {
0x00, 0x00, 0x00, /* 0: 黑 */
0xff, 0x00, 0x00, /* 1: 亮红 */
0x00, 0xff, 0x00, /* 2: 亮绿 */
0xff, 0xff, 0x00, /* 3: 亮黄 */
0x00, 0x00, 0xff, /* 4: 亮蓝 */
0xff, 0x00, 0xff, /* 5: 亮紫 */
0x00, 0xff, 0xff, /* 6: 浅亮蓝 */
0xff, 0xff, 0xff, /* 7: 白 */
0xc6, 0xc6, 0xc6, /* 8: 亮灰 */
0x84, 0x00, 0x00, /* 9: 暗红 */
0x00, 0x84, 0x00, /* 10: 暗绿 */
0x84, 0x84, 0x00, /* 11: 暗黄 */
0x00, 0x00, 0x84, /* 12: 暗青 */
0x84, 0x00, 0x84, /* 13: 暗紫 */
0x00, 0x84, 0x84, /* 14: 浅暗蓝 */
0x84, 0x84, 0x84 /* 15: 暗灰 */
};
set_palette(0, 15, table_rgb);
return;
}
/* ❏ 调色板的访问步骤
❏ 首先在一连串的访问中屏蔽中断(比如CLI)
❏ 将想要设定的调色板号码写入0x03c8,紧接着,按R, G, B的顺序写入0x03c9。
如果还想继续设定下一个调色板,则省略调色板号码,再按照RGB的顺序写入0x03c9就行了
❏ 如果想要读出当前调色板的状态,首先要将调色板的号码写入0x03c7,再从0x03c9读取3次。读出的顺序就是R, G, B。如果要继续读出下一个调色板,同样也是省略调色板号码的设定,按RGB的顺序读出
❏ 如果最初执行了CLI,那么最后要执行STI */
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); // 记录中断许可装置的值
io_cli(); // 将中断许可标志置为0, 禁止中断
io_out8(0x03c8, start);
for (i = start; i <= end; i++) {
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); // 复原中断许可标志
return;
}
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ;
[INSTRSET "i486p"] ;
[BITS 32] ;
[FILE "naskfunc.nas"] ;
GLOBAL _io_hlt, _io_cli, _io_sti, _io_stihlt
GLOBAL _io_in8, _io_in16, _io_in32
GLOBAL _io_out8, _io_out16, _io_out32
GLOBAL _io_load_eflags, _io_store_eflags
[SECTION .text]
_io_hlt: ; void io_hlt(void);
HLT
RET
_io_cli: ; void io_cli(void);
CLI
RET
_io_sti: ; void io_sti(void);
STI
RET
_io_stihlt: ; void io_stihlt(void);
STI
HLT
RET
_io_in8: ; int io_in8(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AL,DX
RET
_io_in16: ; int io_in16(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AX,DX
RET
_io_in32: ; int io_in32(int port);
MOV EDX,[ESP+4] ; port
IN EAX,DX
RET
_io_out8: ; void io_out8(int port, int data);
MOV EDX,[ESP+4] ; port
MOV AL,[ESP+8] ; data
OUT DX,AL
RET
_io_out16: ; void io_out16(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,AX
RET
_io_out32: ; void io_out32(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,EAX
RET
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ;
POP EAX
RET
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ;
RET
// 画出初始界面 省略了部分函数和定义
void HariMain(void)
{
char *vram;
int xsize, ysize;
init_palette();
vram = (char *) 0xa0000;
xsize = 320;
ysize = 200;
boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28);
boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1);
boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24);
boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5);
boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3);
boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3);
}
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c;
}
return;
}
struct -> 箭头 -> 字符的显示 -> 使用makefont.exe将txt文件读入生成更多字符 -> extern char hankaku[4096] -> A 0x41 ‘A’ -> hankaku+ ‘A’ * 16 -> os中显示变量的值 -> sprintf() -> 画出鼠标指针
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAnIkkWf-1595223014841)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200707200415206.png)]
// 使用结构体 显示字符A
struct BOOTINFO
{
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};
// struct 用法
// 可以视为存储多个变量的变量 可以初始化 通过.来调用其中的变量
// 传参时可以一起传递
void HariMain(void)
{
char *vram;
int xsize, ysize;
struct BOOTINFO *binfo;
init_palette();
binfo = (struct BOOTINFO *)0x0ff0;
xsize = (*binfo).scrnx;
ysize = (*binfo).scrny;
vram = (*binfo).vram;
// 可以用箭头替换 定义都可以省略
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
init_screen(vram, xsize, ysize);
// 写字符
static char font_A[16] = {
0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
};
putfont8(binfo->vram, binfo->scrnx, 10, 10, COL8_FFFFFF, font_A);
// 使用makefont工具显示ABC 123
extern char hankaku[4096];
putfont8(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, hankaku + 'A' * 16);
putfont8(binfo->vram, binfo->scrnx, 16, 8, COL8_FFFFFF, hankaku + 'B' * 16);
putfont8(binfo->vram, binfo->scrnx, 24, 8, COL8_FFFFFF, hankaku + 'C' * 16);
putfont8(binfo->vram, binfo->scrnx, 40, 8, COL8_FFFFFF, hankaku + '1' * 16);
putfont8(binfo->vram, binfo->scrnx, 48, 8, COL8_FFFFFF, hankaku + '2' * 16);
putfont8(binfo->vram, binfo->scrnx, 56, 8, COL8_FFFFFF, hankaku + '3' * 16);
}
void init_screen(char *vram, int x, int y)
{
boxfill8(vram, x, COL8_008484, 0, 0, x - 1, y - 29);
boxfill8(vram, x, COL8_C6C6C6, 0, y - 28, x - 1, y - 28);
boxfill8(vram, x, COL8_FFFFFF, 0, y - 27, x - 1, y - 27);
boxfill8(vram, x, COL8_C6C6C6, 0, y - 26, x - 1, y - 1);
boxfill8(vram, x, COL8_FFFFFF, 3, y - 24, 59, y - 24);
boxfill8(vram, x, COL8_FFFFFF, 2, y - 24, 2, y - 4);
boxfill8(vram, x, COL8_848484, 3, y - 4, 59, y - 4);
boxfill8(vram, x, COL8_848484, 59, y - 23, 59, y - 5);
boxfill8(vram, x, COL8_000000, 2, y - 3, 59, y - 3);
boxfill8(vram, x, COL8_000000, 60, y - 24, 60, y - 3);
boxfill8(vram, x, COL8_848484, x - 47, y - 24, x - 4, y - 24);
boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y - 4);
boxfill8(vram, x, COL8_FFFFFF, x - 47, y - 3, x - 4, y - 3);
boxfill8(vram, x, COL8_FFFFFF, x - 3, y - 24, x - 3, y - 3);
return;
}
// 画字符
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d;
for (i = 0; i < 16; i++)
{
p = vram + (y + i) * xsize + x;
d = font[i];
if ((d & 0x80) != 0)
p[0] = c;
if ((d & 0x40) != 0)
p[1] = c;
if ((d & 0x20) != 0)
p[2] = c;
if ((d & 0x10) != 0)
p[3] = c;
if ((d & 0x08) != 0)
p[4] = c;
if ((d & 0x04) != 0)
p[5] = c;
if ((d & 0x02) != 0)
p[6] = c;
if ((d & 0x01) != 0)
p[7] = c;
}
return;
}
// 写一个函数封装显示字符串
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
extern char hankaku[4096];
for (; *s != 0x00; s++) {
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
x += 8;
}
return;
}
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
init_palette();
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Hello Mancuojie");
putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Hello Mancuojie.");
for (;;) {
io_hlt();
}
}
// 在os中显示变量的值
#include
// s -> 字符串的存放地址
sprint(s, "scrnx = %d", binfo->scrnx);
putfonts8_asc(binfo->varm, binfo->scrnx, 16, 64, COL8_FFFFFF, s);
// 画鼠标指针 16*16
void HariMain(void) {
// 省略
char s[40], mcursor[256];
int mx, my;
mx = (binfo->scrnx - 16) / 2;
my = (binfo->scrny - 28 - 16) / 2;
init_mouse_cursor8(mcursor, COL8_008484);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
}
void init_mouse_cursor8(char *mouse, char bc)
{
static char cursor[16][16] = {
"**************..",
"*OOOOOOOOOOO*...",
"*OOOOOOOOOO*....",
"*OOOOOOOOO*.....",
"*OOOOOOOO*......",
"*OOOOOOO*.......",
"*OOOOOOO*.......",
"*OOOOOOOO*......",
"*OOOO**OOO*.....",
"*OOO*..*OOO*....",
"*OO*....*OOO*...",
"*O*......*OOO*..",
"**........*OOO*.",
"*..........*OOO*",
"............*OO*",
".............***"
};
int x, y;
for (y = 0; y < 16; y++) {
for (x = 0; x < 16; x++) {
if (cursor[y][x] == '*') {
mouse[y * 16 + x] = COL8_000000;
}
if (cursor[y][x] == 'O') {
mouse[y * 16 + x] = COL8_FFFFFF;
}
if (cursor[y][x] == '.') {
mouse[y * 16 + x] = bc;
}
}
}
return;
}
// 找到bc -> background color
// vram -> 0xa0000 vxsize -> 320
// pxsize pysize -> 16 * 16
// px0 py0 -> 指定图形在画面上的显示位置
// buf -> 指定图形的存放位置 bxsize -> 每一行含有的像素数
void putblock8_8(char *vram, int vxsize, int pxsize, int pysize, int px0, int py0, char *buf, int bxsize)
{
int x, y;
for (y = 0; y < pysize; y++) {
for (x = 0; x < pxsize; x++) {
vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x];
}
}
return;
}
多任务 -> 内存地址使用冲突 -> 分段 -> 将内存分成很多块, 每一块的起始地址都看作0, 任何程序都能先写一句 ORG 0 -> 分割出来的块就称作段 segment -> 分段信息 段的大小 段的起始地址 段的管理属性(禁止写入, 系统专用等) -> 段寄存器16位 -> 低3位不能使用(CPU设计) -> 13位 -> 0~8191 -> 8192 * 8 = 65536 byte (64KB) -> 写入内存中 称作全局段号记录表GDT global segment descriptor table -> 内存的起始地址和有效设定个数放入CPU的GDTRegister中
段的地址(基址 base) -> 32位 -> low(2 byte) mid(1) high(1)
段上限 -> 一个段有多少字节 -> 最大4GB 32位数值4字节 + base4字节 = 8个字节 -> 占满了结构体没地方保存段的管理属性 -> 只能用20位 -> 也就是1MB -> 标志位Gbit -> 为1时limit单位不解释成字节, 解释成页page -> 1page = 4KB -> Gbit granularity -> 写到limit_low和limit_high里 -> limit_high的高4位写短属性
段属性 -> 段的访问权限 -> access_right ar -> 12位 -> 由于高4位在limit_high中 -> 当作xxxx0000xxxxxxxx16位来处理 -> 高4位被称为扩展访问权 -> GD00 -> Gbit -> D为段的模式 1指32位模式, 0指16位模式 -> 通常D=1, 16位无法调用BIOS -> ar的低八位如图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpRf0Vm1-1595223014844)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200708200630684.png)]
IDT interrupt descriptor table -> 中断记录表 -> 中断机制 处理事务时暂时停止做好继续处理的准备, 转而执行中断程序 -> IDT记录了0~255的中断号码与调用函数的对应关系 -> 使用鼠标
分割源文件 -> bootpack.c graphic.c dsctbl.c -> 定义一个头文件 bootpack.h -> #include “bootpack.h” -> " "表示头文件与源文件位于同一个文件夹里,而< >则表示该头文件位于编译器所提供的文件夹里
鼠标移动 -> 使用中断 -> 正确初始化GDT和IDT -> 初始化PIC -> PIC programmable interrupt controller 可编程中断控制器 -> 8个中断信号(IRQ interrupt request)集合成一个 -> 一个中断信号进来就发送给CPU -> 设计了15个 -> 2个PIC -> 直接与CPU相连master PIC -> 与主PIC相连 slave PIC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2F1Co9k-1595223014845)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200708201153052.png)]
PIC的寄存器 -> IMR -> interrupt mask register 中断屏蔽寄存器 -> 8位对应8路IRQ(中断信号) -> 某一位值为1则该位对应的IRQ被屏蔽, PIC就会忽视该路信号
ICW -> initial control word 初始化控制数据 -> 4个 -> 编号1~4 -> 共有4个字节的数据ICW2 -> 决定IRQ以哪一号中断通知CPU -> CPU与PIC只有一根管脚相连 -> CPU受理中断 -> PIC发送两个字节的数据 -> 0xcd 0x?? -> 0xcd 即调用BIOS的INT指令 -> INT 0x??
INT 0x00~0x1f 不能用于IRQ -> 应用程序想要对操作系统干坏事的时候,CPU内部会自动产生INT 0x00~0x1f -> 0x20 ~ 0x2f 用于IRQ 0~15
大量数据需要中介慢慢处理 -> 缓冲区 buffer -> FIFO先进先出 first in first out 或者叫做LILO后进后出 last in last out -> 还有 FILO先进后出 或者叫做LIFO后进先出 -> 栈 FILO stack -> PUSH 将数据压入栈顶 -> POP 将数据从栈顶取出
CALL 调用指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3TIKbPRS-1595223014847)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200709172153620.png)]
键盘调用中断后 -> 需要输出 0x60+IRQ号码 给OCW2 -> io_out8(PIC0_OCW2, 0x61) -> 通知PIC已经知道发生了IRQ1中断 -> 否则PIC会继续监视IRQ1中断是否发生 -> 每次都需要对重启中断监视
// 初始化GDT全局段号记录表与IDT中断记录表
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
struct GATE_DESCRIPTOR {
short offset_low, selector;
char dw_count, access_right;
short offset_high;
};
void init_gdtidt(void)
{
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800;
int i;
// 8192个段
for (i = 0; i < 8192; i++) {
set_segmdesc(gdt + i, 0, 0, 0);
}
// 对1,2段设定
// 1段大小为4GB, 地址为0, 代表CPU能管理的全部内存
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
// 2段大小为512KB, 地址为0x280000, 可以执行bootpack.hrb -> 是以ORG 0为前提翻译成的机器语言。
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
// 链接汇编给GDTR赋值
load_gdtr(0xffff, 0x00270000);
for (i = 0; i < 256; i++) {
// *idt 指向8字节的结构体 +1地址增加了8
set_gatedesc(idt + i, 0, 0, 0);
}
// 与GDTR相似
load_idtr(0x7ff, 0x0026f800);
return;
}
// limit上限 base基址 ar访问权限
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
if (limit > 0xfffff) {
ar |= 0x8000; /* G_bit = 1 */
limit /= 0x1000;
}
sd->limit_low = limit & 0xffff;
sd->base_low = base & 0xffff;
sd->base_mid = (base >> 16) & 0xff;
sd->access_right = ar & 0xff;
sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
sd->base_high = (base >> 24) & 0xff;
return;
}
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
gd->offset_low = offset & 0xffff;
gd->selector = selector;
gd->dw_count = (ar >> 8) & 0xff;
gd->access_right = ar & 0xff;
gd->offset_high = (offset >> 16) & 0xffff;
return;
}
; gdtr48位寄存器, 不能用mov来赋值, 只能从指定的内存地址中读取48位(6个字节)然后赋值 -> lgdt指令
_load_gdtr: ;void load_gdtr(int limit, int addr)
; [ff ff 00 00 00 00 27 00]
mov ax, [esp+4]
mov [esp+6], ax
; [ff ff ff ff 00 00 27 00]
lgdt [esp+6]
; [ff ff 00 00 27 00]
ret
// int.c 中断 PIC的初始化
void init_pic(void)
{
io_out8(PIC0_IMR, 0xff ); // 禁止主PIC所有中断
io_out8(PIC1_IMR, 0xff ); // 禁止从PIC所有中断
// PIC0 masterPIC
io_out8(PIC0_ICW1, 0x11 ); // 边沿触发模式
io_out8(PIC0_ICW2, 0x20 ); // IRQ 0~7由INT 20~27接收
io_out8(PIC0_ICW3, 1 << 2); // PIC1由IRQ2连接 00000100
io_out8(PIC0_ICW4, 0x01 ); // 无缓冲区模式
// PIC1 slavePIC
io_out8(PIC1_ICW1, 0x11 ); // 边沿触发模式
io_out8(PIC1_ICW2, 0x28 ); // IRQ 8~15由INT 28~2f接收28292a2b2c2d2e2f
io_out8(PIC1_ICW3, 2 ); // PIC1由IRQ2连接
io_out8(PIC1_ICW4, 0x01 ); // 无缓冲区模式
io_out8(PIC0_IMR, 0xfb ); // 11111011 PIC1外全部禁止
io_out8(PIC1_IMR, 0xff ); // 11111111 禁止所有中断
return;
}
// 键盘IRQ1 鼠标IRQ12
// INT 0x21 INT 0x2c
// 键盘编号位为0x0060
#define PORT_KEYDAT 0x0060
void inthandler21(int *esp)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
unsigned char data, s[4];
io_out8(PIC0_OCW2, 0x61); /* 通知PIC IRQ-01已受理完毕 */
data = io_in8(PORT_KEYDAT);
// 按下按键在屏幕输出按键的编码
sprintf(s, "%02X", data);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
return;
}
; 中断处理完成后不能执行return即RET指令
; 必须执行IRETD指令 只能用汇编
EXTERN _inthandler21, _inthandler27, _inthandler2c
_asm_inthandler21:
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler21
POP EAX
POPAD
POP DS
POP ES
IRETD
; 另外两个类似 只是调用call的函数不一样
// 注册在idt的0x21号
// 2*8 2为段号 *8也就是<<3 因为低三位必须是0
// AR_INTGATE32 == 0x008e 表示这是用于中断处理的有效设定
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
// 2段 涵盖了整个bootpack.hrb
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
优化中断 -> 将画字符串的程序分离出来 -> 中断程序只接收按键的编码 -> 缓冲区读写 -> 在主程序中查看 -> 如果有就在屏幕输出
FIFO缓冲区基本结构 -> 一边读一边写 -> 缓冲区初始化 -> 传输数据 -> 获取数据 -> 判断非空闲区 -> 主函数调用
取得鼠标数据 -> 鼠标控制电路 -> 键盘控制电路(包含鼠标)初始化 -> 缓冲区读写 -> 主程序查看中断发送在缓冲区的数据 (与键盘中断类似)
// 头文件中定义
struct KEYBUF {
unsigned char data[32];
int next;
};
// int.c的修改 键盘输入缓冲区
#define PORT_KEYDAT 0x0060
struct KEYBUF keybuf;
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61);
data = io_in8(PORT_KEYDAT);
// 一边写入一边读取 当写满32字节数组时从0重新开始
if (keybuf.len < 32) {
keybuf.data[keybuf.next_w] = data;
keybuf.len++;
keybuf.next_w++;
if (keybuf.next_w == 32) {
keybuf.next_w = 0;
}
}
return;
}
// HariMain.c的修改
for (;;) {
// 每次循环开始都先将IF置为0 clear
io_cli();
// 键盘缓冲区为空时将IF置为1并待机 set
if (keybuf.flag == 0) {
io_stihlt();
} else {
// 键盘缓冲区有数据时, 一次读一个
i = keybuf.data[keybuf.next_r];
keybuf.len--;
keybuf.next_r++;
if (keybuf.next_r == 32) {
keybuf.next_r = 0;
}
// IF置为1
io_sti();
sprintf(s, "%02X", i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
}
}
// 将函数分离出来 FIFO缓冲区 fifo.c
struct FIFO8() {
unsigned char *buf;
int p, q, size, free, flags;
};
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
{
fifo->size = size;
fifo->buf = buf;
fifo->free = size; // 缓冲区大小
fifo->flags = 0;
fifo->p = 0; // 下一个数据写入位置
fifo->q = 0; // 下一个数据读出位置
return;
}
// 向FIFO传送数据并保存
#define FLAGS_OVERRUN 0x0001
int fifo8_put(struct FIFO8 *fifo, unsigned char data)
{
// 溢出返回-1 flags置为1
if (fifo->free == 0) {
fifo->flags |= FLAGS_OVERRUN;
return -1;
}
fifo->buf[fifo->p] = data;
fifo->p++;
if (fifo->p == fifo->size) {
fifo->p = 0;
}
// 写入一个数据, 空闲区-1
fifo->free--;
return 0;
}
// 从FIFO取得一个数据
int fifo8_get(struct FIFO8 *fifo)
{
int data;
if (fifo->free == fifo->size) {
// 如果缓冲区为空, 则返回-1
return -1;
}
data = fifo->buf[fifo->q];
fifo->q++;
if (fifo->q == fifo->size) {
fifo->q = 0;
}
// 读取一个数据, 空闲区+1
fifo->free++;
return data;
}
int fifo8_status(struct FIFO8 *fifo)
{
// 返回非空闲区的数量
return fifo->size - fifo->free;
}
// int.c的修改
#define PORT_KEYDAT 0x0060
struct FIFO8 keyfifo;
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61);
data = io_in8(PORT_KEYDAT);
fifo8_put(&keyfifo, data);
return;
}
// HariMain.c的修改
char keybuf[32];
fifo8_init(&keyfifo, 32, keybuf);
for (;;) {
io_cli();
// 判断有无数据
if (fifo8_status(&keyfifo) == 0) {
io_stihlt();
} else {
i = fifo8_get(&keyfifo);
io_sti();
sprintf(s, "%02X", i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
}
}
// 键盘控制电路 -> 激活鼠标 bootpack.c
#define PORT_KEYDAT 0x0060
#define PORT_KEYSTA 0x0064
#define PORT_KEYCMD 0x0064
#define KEYSTA_SEND_NOTREADY 0x02
#define KEYCMD_WRITE_MODE 0x60
#define KBC_MODE 0x47
// KBC keyboard controller 键盘控制电路
void wait_KBC_sendready(void)
{
/* 等待键盘控制电路准备完毕 */
for (;;) {
//0x0060 0x0064
if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
break;
}
}
return;
}
void init_keyboard(void)
{
/* 初始化键盘控制电路 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, KBC_MODE);
return;
}
#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4
void enable_mouse(void)
{
/* 激活鼠标 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
return; /* 顺利的话会返送回ACK(0xfa) */
}
// int.c 鼠标中断
struct FIFO8 mousefifo;
void inthandler2c(int *esp)
{
unsigned char data;
io_out8(PIC1_OCW2, 0x64); /* 告诉PIC1来自IRQ-12的中断受理完成 */
io_out8(PIC0_OCW2, 0x62); /* 告诉主PIC即PIC0 IRQ-02受理完成 */
data = io_in8(PORT_KEYDAT);
fifo8_put(&mousefifo, data);
return;
}
// bootpack.c中增加读取鼠标数据的程序
// 鼠标移动点击都要发送三个字节的数据 所以设为128
fifo8_init(&mousefifo, 128, mousebuf);
for (;;) {
io_cli();
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
io_stihlt(); // 如果缓冲区都没有数据就HLT;
} else {
if (fifo8_status(&keyfifo) != 0) {
i = fifo8_get(&keyfifo);
io_sti();
sprintf(s, "%02X", i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);
io_sti();
sprintf(s, "%02X", i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
}
}
}
取得鼠标数据 -> 鼠标移动 -> 鼠标每有一个动作发送三个字节 -> 屏幕画出鼠标移动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QzZ0WB3c-1595223014848)(C:\Users\13373\AppData\Roaming\Typora\typora-user-images\image-20200711013010306.png)]
最初的1MB还有BIOS, VRAM等并不是都空着
// bootpack.c
unsigned char mouse_debuf[3], mouse_phase;
init_keyboard();
enable_mouse();
mouse_phase = 0; // 进入到等待鼠标的0xfa状态 激活鼠标顺利会返回0xfa
for (;;) {
io_cli();
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
io_stihlt();
} else {
if (fifo8_status(&keyfifo) != 0) {
i = fifo8_get(&keyfifo);
io_sti();
sprintf(s, "%02X", i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);
io_sti();
if (mouse_phase == 0) {
/* 等待鼠标的0xfa状态 */
if (i == 0xfa) {
mouse_phase = 1;
}
} else if (mouse_phase == 1) {
/* 等待鼠标的第一字节 */
mouse_dbuf[0] = i;
mouse_phase = 2;
} else if (mouse_phase == 2) {
/* 等待鼠标的第二字节 */
mouse_dbuf[1] = i;
mouse_phase = 3;
} else if (mouse_phase == 3) {
/* 等待鼠标的第三字节 */
mouse_dbuf[2] = i;
mouse_phase = 1;
/* 三个字节集齐显示 */
sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
}
}
}
}
// bootpack.c的修改 解耦
// 函数的定义 按顺序调用
extern struct FIFO8 keyfifo, mousefifo;
void init_keyboard(void);
void enable_mouse(struct MOUSE_DEC *mdec);
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);
// x,y存储移动信息 btn存储鼠标按键状态
struct MOUSE_DEC {
unsigned char buf[3], phase;
int x, y, btn;
};
#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4
void enable_mouse(struct MOUSE_DEC *mdec)
{
/* 激活鼠标 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
/* 顺利会返回0xfa */
mdec->phase = 0; /* 等待0xfa */
return;
}
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{
if (mdec->phase == 0) {
if (dat == 0xfa) {
mdec->phase = 1;
}
return 0;
}
if (mdec->phase == 1) {
if ((dat & 0xc8) == 0x08) {
/* 第一字节设置中必须为
对移动有反应的部分在0~3范围内
对点击有反应的部分在8~F范围内 */
mdec->buf[0] = dat;
mdec->phase = 2;
}
return 0;
}
if (mdec->phase == 2) {
mdec->buf[1] = dat;
mdec->phase = 3;
return 0;
}
if (mdec->phase == 3) {
mdec->buf[2] = dat;
mdec->phase = 1;
// 只有低三位与鼠标点击有关 & 00000111 取出来放到btn中
mdec->btn = mdec->buf[0] & 0x07;
mdec->x = mdec->buf[1];
mdec->y = mdec->buf[2];
if ((mdec->buf[0] & 0x10) != 0) {
mdec->x |= 0xffffff00;
}
if ((mdec->buf[0] & 0x20) != 0) {
mdec->y |= 0xffffff00;
}
mdec->y = - mdec->y; /* 鼠标的y方向与画面符号相反 */
return 1;
}
return -1; /* 应该不会到这里 */
}
// for(;;)里的修改 解读
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);
io_sti();
if (mouse_decode(&mdec, i) != 0) {
/* 显示三个字节 */
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
// 状态与三位中哪一位有关 就会把字符串中的哪一个字母替换成大写
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
/* 鼠标指针的移动 */
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15); // 隐藏鼠标
mx += mdec.x;
my += mdec.y;
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 16) {
mx = binfo->scrnx - 16;
}
if (my > binfo->scrny - 16) {
my = binfo->scrny - 16;
}
sprintf(s, "(%3d, %3d)", mx, my);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 隐藏坐标 */
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 显示坐标 */
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); /* 画出鼠标 */
}
}
; asmhead.nas的解释
; PIC关闭一切中断
; 根据AT兼容机的规格如果要初始化PIC必须要在CLI之前进行前几步操作
; OUT命令连续执行有些机器会无法正常运行
MOV AL,0xff
OUT 0x21,AL
NOP ; NOP no operation 是让CPU休息一个时钟长
OUT 0xa1,AL
CLI ; 禁止CPU级别的中断
; 等同于这段C程序
; io_out(PICO_IMR, 0xff); /* 禁止主PIC的全部中断 */
; io_out(PIC1_IMR, 0xff); /* 禁止从PIC的全部中断 */
; io_cli(); /*禁止CPU级别的中断*/
; 为了让CPU能访问1MB以上的内存, 设定A20GATE
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
waitkbdout:
IN AL,0x64
AND AL,0x02
JNZ waitkbdout ; JNZ jump if not zero
RET
; waitkbdout与wait_KBC_sendready()的处理基本相同
; #define KEYCMD_WRITE_OUTPORT 0xd1
; #define KBC_OUTPOR_A20G_ENABLE 0xdf
; / * A20GATE的设定 */
; wait_KBC_sendready();
; io_out8(PORT_KEYCMD, KEYCMD_WRITE_OUTPORT);
; wait_KBC_sendready() ;
; io_out8(PORT_KEYDAT, KBC_OUTPORT_A20G_ENABLE);
; wait_KBC_sendready(); /*这句话是为了等待完成执行指令*/
; 指令键盘控制电路的附属端口输出0xdf 也就是让A20GATE信号线变成ON的状态 使内存的1MB以上的部分变成可用态
[INSTRSET "i486p"] ; 使用486命令
LGDT [GDTR0] ; 设定临时GDT
MOV EAX,CR0
AND EAX,0x7fffffff ; 设bit31为0(为了禁止分页)
OR EAX,0x00000001 ; 设bit0为1(为了切换到保护模式)
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 可读写的段32bit
MOV DS,AX
MOV ES,AX
MOV FS,AX
MOV GS,AX
MOV SS,AX
; CR0 (control register 0) 只有操作系统能用的32位寄存器 最高位置0最低位置1 进入保护模式
; 本来的说法应该是"protected virtual address mode"翻译过来就是"受保护的虚拟内存地址模式”
; 与此相对,从前的16位模式称为"real mode"它是"real address mode"的省略形式,翻译过来就是实际地址模式
; 这些术语中的"virtual" ,"real" 的区别在于计算内存地址时,是使用段寄存器的值直接指定地址值的一部分呢,还是通过GDT使用股寄存器的值指定并非实际存在的地址号码
; CPU使用管道pipeline也就是前一条指令还在执行时 就执行下面的指令 所以模式切换后需要重新解释一遍 擦除管道
; 除了CS所有的寄存器都从0x0000成了0x0008 相当于'gdt+1'的段
; 进入保护模式后段寄存器也不再是乘以16后再加算了
; bootpack的转送
MOV ESI,bootpack ; 转送元
MOV EDI,BOTPAK ; 转送目的地
MOV ECX,512*1024/4
CALL memcpy
; 磁盘数据最终转送到本来的位置
; 启动扇区开始
MOV ESI,0x7c00
MOV EDI,DSKCAC
MOV ECX,512/4
CALL memcpy
; 所有剩下的
MOV ESI,DSKCAC0+512
MOV EDI,DSKCAC+512
MOV ECX,0
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4 ; 从柱面数变为字节数/4
SUB ECX,512/4 ; 减去IPL
CALL memcpy
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy
RET
; C程序表示为memcpy(转送源地址, 转送目的地址, 转送数据的大小)
; 即一个复制内存的函数, 转送数据以double-word为单位, 所以用字节数/4来指定
; 1. 从bootpack的地址开始的512KB内容复制到0x00280000那里
; bootpack是一个空标签 链接bootpack.hrb最前面的部分
; 2. 0x7c00复制521字节到0x00100000 将启动扇区复制到1MB以后的内存去
; 3. 将始于0x00008200的磁盘内容复制到0x00100200那里
; IMUL integer multiply 乘法运算 SUB subtract
; bootpack的启动
MOV EBX,BOTPAK
MOV ECX,[EBX+16]
ADD ECX,3 ; ECX += 3;
SHR ECX,2 ; ECX /= 4; SHR shift right 右移两位也就是/4
JZ skip ; JZ jump if zero
MOV ESI,[EBX+20] ; 转送源
ADD ESI,EBX
MOV EDI,[EBX+12] ; 转送目的地
CALL memcpy
skip:
MOV ESP,[EBX+12] ; 栈初始值
JMP DWORD 2*8:0x0000001b
; 对bootpack.hrb的头部内容进行解析,将执行所必需的数据传送过去
; 它会将bootpack.hrb第0x10c8字节开始的0x11a8字节复制到0x00310000号地址去
; 最后将0x310000代入到ESP里,然后用一个特别的JMP指令,将2*8代入到CS里,同时移动到0x1b号地址。
; 这里的0x1b号地址是指第2个段的0x1b号地址。第2个段的基地址是0x280000,所以实际上是从0x28001b开始执行的。这也就是bootpack.hrb的0x1b号地址
; ALIGNB 一直添加DB 0直到地址能被16整除的时候 因为不是8的整数倍 mov指令会慢一些
ALIGNB 16
GDT0:
RESB 8 ; null sector GDT0是一种特定的GDT不能在那定义段
DW 0xffff,0x0000,0x9200,0x00cf ; 可以读写的段(segment)32bit
DW 0xffff,0x0000,0x9a28,0x0047 ; 可以执行的段 32bit(bootpack用)
DW 0
GDTR0:
DW 8*3-1
DD GDT0
ALIGNB 16
bootpack:
CPU寄存器内部MOV要比寄存器MOV到内存快的多 -> 486以上高速缓存储存器 Cache memory -> 每次访问内存,都要将所访问的地址和内容存入到高速缓存里 -> 先更新缓存再写入内存 -> 检查内存时先关闭缓存 -> memtest ->
#define EFLAGS_AC_BIT 0x00040000
#define CR0_CACHE_DISABLE 0x60000000
unsigned int memtest(unsigned int start, unsigned int end)
{
char flg486 = 0;
unsigned int eflg, cr0, i;
/* 确认CPU是386还是486以上的
如果是486以上,EFLAGS寄存器的第18位应该是所谓的AC标志位
如果CPU是386,那么就没有这个标志位,第18位一直是0 */
eflg = io_load_eflags();
eflg |= EFLAGS_AC_BIT; /* AC-bit = 1 */
io_store_eflags(eflg);
eflg = io_load_eflags();
if ((eflg & EFLAGS_AC_BIT) != 0) { /* 如果是386即使设定AC=1 AC的值还会自动回到0 */
flg486 = 1;
}
eflg &= ~EFLAGS_AC_BIT; /* AC-bit = 0 */
io_store_eflags(eflg);
if (flg486 != 0) {
cr0 = load_cr0();
cr0 |= CR0_CACHE_DISABLE; /* 禁止缓存 */
store_cr0(cr0);
}
i = memtest_sub(start, end);
if (flg486 != 0) {
cr0 = load_cr0();
cr0 &= ~CR0_CACHE_DISABLE; /* 允许缓存 */
store_cr0(cr0);
}
return i;
}
unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
for (i = start; i <= end; i += 0x1000) {
p = (unsigned int *) (i + 0xffc);
old = *p; /* いじる前の値を覚えておく */
*p = pat0; /* ためしに書いてみる */
*p ^= 0xffffffff; /* そしてそれを反転してみる */
if (*p != pat1) { /* 反転結果になったか? */
not_memory:
*p = old;
break;
}
*p ^= 0xffffffff; /* もう一度反転してみる */
if (*p != pat0) { /* 元に戻ったか? */
goto not_memory;
}
*p = old; /* いじった値を元に戻す */
}
return i;
}