第五天 - 结构体,文字显示,GDT,IDT的初始化
结构体, 文字显示, GDT/IDT初始化
一、结构体
1. 接受启动信息
- 避免写死相关的地址常量,改用指针获取启动所需的信息
- binfo -- bootinfo
- scrn --screen
void HariMain(void)
{
char *vram;
int xsize, ysize;
short *binfo_scrnx, *binfo_scrny;
int *binfo_vram;
init_palette();
binfo_scrnx = (short *) 0x0ff4;
binfo_scrny = (short *) 0x0ff6;
binfo_vram = (int *) 0x0ff8;
xsize = *binfo_scrnx;
ysize = *binfo_scrny;
vram = (char *) *binfo_vram;
// 画面背景处理函数
init_screen(vram, xsize, ysize);
for (;;)
{
io_hlt();
}
}
2. 使用结构体
- 为了方便管理变量的声明,避免代码冗余,将其放入到结构体中
struct BOOTINFO
{
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};
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(vram, xsize, ysize);
for (;;)
{
io_hlt();
}
}
- binfo是一个结构体变量的地址(这里结构体大小为12字节,则binfo就是这块地址的起始地址),访问结构体的成员属性需要间址访问
3. 结构体指针的间址访问方式 -> 箭头记号
- 类似于 a[i] 是 * (a + i) 的省略表现方式,binfo->scrnx 是 (* binfo).scrnx 的省略表现方式
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
init_palette();
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
for (;;)
{
io_hlt();
}
}
二、显示字符
4. 显示字符
-
字符采用 8 * 16的长方形像素点阵来表示
-
用于描画文字形状的数据成为字体(font)数据,就是C语言用十六进制的方式去将二进制的01像素点信息记录下来,一个十六进制数据记录8个位
-
static char font_A[16] = { 0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24, 0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00 };
-
-
putfont8 函数作用
-
然后调用for循环,画8个像素点的程序循环16遍
-
文字形状的八个像素点的数据存于一个十六进制数中,然后for循环内部对其的每一位逐个位进行按位与,判断该点是否需要显示,需要显示的,向该像素点存储位置写入颜色号码
-
void putfont8(char *vram, int xsize, int x, int y, char c, char *font) { int i; char *p,d; /* date */ 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; } } } void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; static char font_A[16] = { 0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24, 0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00 }; init_palette(); init_screen(binfo->vram, binfo->scrnx, binfo->scrny); putfont8(binfo->vram, binfo->scrnx, 10, 10, COL8_FFFFFF, font_A); for (;;) { io_hlt(); } }
-
0x80 的二进制形式 是 1000 0000,与d按位与的结果为0则表示d最左边一位的值为0, 否则为1
- 这里的操作是,通过 and操作把每一位为1的像素点都设置为 char c所表示的颜色号码
-
5. 增加字体
-
暂时沿用OSASK字体,后续再补充其他字体
-
字体的制作方式
- 使用makefont.exe这个编译器,将字体的文本文件读入并输出一个二进制文件
- 然后用bin2obj.exe给二进制文件加上链接所需借口信息,然后转为目标文件
-
C语言中使用当前的字体 hankaku
-
extern char hankahu[4096]; //extern 是声明全局变量
-
-
OSASK按照ASCII编码,含有256个字符,A的字符编码是0x41,在 "hankaku + 0x41*16"开始的16个字节里,然后调用putfont8函数,在循环 8 * 16 的内存中,通过按位与运算判断该字符对应的每个位的值为0还是1,为1则在指定显存中写入内容
-
在屏幕显示 字符 “ABC 123”
-
oid HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; extern char hankaku[4096]; init_palette(); init_screen(binfo->vram, binfo->scrnx, binfo->scrny); 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); for (;;) { io_hlt(); } }
-
6. 显示字符串
- C语言中,字符串是按照顺序排列在内存中,末尾加上0x00结尾的字符编码
- putfonts8_asc 用于新的字体显示
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p,d; /* date */
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; }
}
}
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, 8, 8, COL8_FFFFFF, "ABC 123");
putfonts8_asc(binfo->vram, binfo->scrnx, 31, 31, COL8_000000, "Haribote OS.");
putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Haribote OS.");
for (;;)
{
io_hlt();
}
}
7. 显示变量值
-
采用 sprintf函数,只将输出内容作为字符串写在内存中,只对内存进行操作,不依赖于任何操作系统的功能
-
sprintf函数的是使用方式
- sprintf (地址, 格式, 值,值,值,......)
-
部分替换符的格式说明,它们用于指定数值以什么方式变换成字符串
-
代码
-
sprintf(s, "scrnx = %d", binfo->scrnx); putfonts8_asc(binfo->vram, binfo->scrnx, 16, 64, COL8_FFFFFF, s);
-
将binfo->scrnx 的值写入 s的内存地址中存储
-
8.显示鼠标指针
-
指定鼠标的大小为 16*16,申请256字节的内存作为缓冲区,然后往里面写入鼠标指针的数据(像素点对应的颜色号码)
-
oid init_mouse_cursor8(char *mouse, char bc) /* 准备鼠标指标 (16 x 16) */ { 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 - back-color变量, 背景色,要显示背景色,还得将buf的数据复制到显卡内存vram中
-
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; }
-
vram ( 0xa0000), vxsize(320) 是VRAM的信息
-
pxsize, pysize 是要显示的图形的大小,在这里是鼠标的大小
-
px0,py0指定图形在画面上的位置
-
buf是图形的存放地址, bxsize是图形的每一行含有的像素数
-
-
三、GDT与IDT的初始化
9. GDT与IDT的初始化
-
操作系统可以同时运行多个程序,如何避免不同的程序所使用内存地址不会重叠和冲突呢,这就用到了分段机制去解决这个问题
-
分段 (segment)
- 将物理内存(如4GB)划分为多个block,每个block是一个段,每一块的起始地址都可以看看作0来处理
-
段寄存器
-
默认的段寄存器是 DS
-
示例
-
MOV AL,[DS:EBX]
-
EBX 中的值就是DS所表示端的起始地址
-
-
段的属性
- 段的属性信息
- 段的大小
- 段的起始地址
- 段的管理属性
- CPU用8个字节(64位)来表示分段的信息,但用于指定段寄存器的只有16位
- 段寄存器只有16位,低三位不可用(CPU设计原因),能够使用的段号只有13位,即 0 ~ 8191, 而定义这么多段的数据需要 64KB大小的空间,存储在内存中一个叫做GDT的地方
GDT - 全局段号记录表
- global(segment) descriptor table
- 整齐地排列存储着分段的信息
- 而GDT在内存中的起始地址和有效设定个数存放在CPU中的GDTR的寄存器中
IDT - 中断记录表
- interrupt descriptor table
- 中断功能
- 当CPU遇到外部变化(如鼠标键盘等外设请求),或者内部偶尔发生某些错误时,会收到中断信号,然后临时切换去处理突发事件
- 原因
- CPU在执行指令的时候,还需要相应键盘,鼠标,网卡等外设的请求,但外设的速度是非常慢的,而CPU执行指令的速度则是非常快,而且外设的数量很多,如果每次查询一次外设都要等待,则则比较浪费CPU性能
- 中断机制的功能
- 外设有了变化,需要请求CPU时,发出中断信号,CPU接收到中断后,暂时停止正在处理的任务并存储当前环境,转而根据中断信号执行中断程序,中断程序执行完后,再调用事先设定好的函数,返回处理中的任务继续执行。
- IDT中记录了0~255的中断号与调用函数的对应关系,接收到什么中断号就执行对应的中断函数
- 示例:
- 当我们要使用键盘时,则需要向CPU发送一个中断号,然后CPU根据IDT的表去找到相应的中断函数并执行
struct SEGMENT_DESCRIPTOR //存放GDT的8个字节的内容
{
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;
/* GDT的初始化 */
for (i = 0; i < 8192; i++)
{
set_segmdesc(gdt + i, 0, 0, 0);
}
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
load_gdtr(0xffff, 0x00270000);
/* IDT的初始化 */
for (i = 0; i < 256; i++)
{
set_gatedesc(idt + i, 0, 0, 0);
}
load_idtr(0x7ff, 0x0026f800); //借助汇编给GDTR寄存器赋值
return;
}
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;
}
-
首先对gdt表进行初始化
- gdt类型是一个指向SEGMENT_DESCRIPTOR结构体的指针 ,当执行gdt + i 时,地址会加上 i * 8
-
对指定的1,2段进行设定
-
```
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
-
0xffffffff 表示 4GB 的内存,段的属性为 0x4092
-
0x00280000 是bootpack.hrb文件准备,使用 段 2 就可以执行该文件,该文件是 ORG 0 为前提翻译成的机器文件
-
-
>>
是一个右移位运算符, 舍弃右边溢出位,左边不足位补 0
_load_gdtr给GDTR寄存器赋值
-
_load_gdtr: ; void load_gdtr(int limit, int addr); MOV AX,[ESP+4] ; limit MOV [ESP+6],AX LGDT [ESP+6] RET
-
将指定段的上限和地址值赋给GDTR这个48位寄存器
- 赋值方法特殊,需要指定一个内存地址,从指定的地址读取6个字节的数据,然后通过LGDT指令赋值给GDTR寄存器
- 寄存器的低16位存储段上限(GDT的大小-1),高32位代表GDT的开始地址
-
编译代码的作用
-
set_segmdesc函数 - GDT初始化
-
按照CPU的规格,将段的信息归结为8个字节并写入内存中
-
struct SEGMENT_DESCRIPTOR //存放GDT的8个字节的内容 { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; }; void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar) { if (limit > 0xfffff) { ar |= 0x8000; /* G_bit = 1 即单位大小为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; }
-
段地址 - 4个字节 32位表示
- base 表示段的基址, 为了兼容80286,分了三段 ,low 2个字节,mid一个字节,high一个字节
-
段上限,表示段有几个字节
- 只有20位,当段的属性Gbit为0时,单位则为KB; Gbit为1,limit的单位是页(4KB),通过这个操作,可以在有限位数中增加段地址的上限(从1MB 增加到 4GB)
-
段的访问权属性 - 12位,通常用ar表示
- 段属性的高四位又称之为拓展访问权,在80386才出现放在了 limit_high的高4位中,这四位是 GD00
- G 代表 Gbit,单位的大小
- D代表段的模式,1表示32位模式, 0表示16位模式
- ar的低八位
- 在32位模式下,CPU分 系统模式 和应用模式两种
- 应用模式下,CPU对LGDT指令不执行,防止应用修改GDT,避免漏洞
- CPU的模式取决于执行的应用程序所在的段
- 系统模式
- 访问权为0x9a的段
- 应用模式
- 访问权为 0xfa的段
- 系统模式
- 段属性的高四位又称之为拓展访问权,在80386才出现放在了 limit_high的高4位中,这四位是 GD00
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现