第六天 - 分割编译与中断处理
第六天 - 分割编译与中断处理
一、文件整理
1. 源文件分割
-
按照内容对源文件进行分割为多个小文件,修改和编译都会提高make的速度
-
graphic.c - 描画等图形处理
/* u关于描画的处理 */
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 boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void init_screen8(char *vram, int x, int y);
void putfont8(char *vram, int xsize, int x, int y, char c, char *font);
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s);
void init_mouse_cursor8(char *mouse, char bc);
void putblock8_8(char *vram, int vxsize, int pxsize,
int pysize, int px0, int py0, char *buf, int bxsize);
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
void init_palette(void)
{
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, 0x00, 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;
/* C语言中的static char语句只能用于数据, 相当于汇编中的DB指令 */
}
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;
}
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;
}
void init_screen8(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; /* 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; }
}
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 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;
}
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;
}
dsctbl.c — GAT,IDT相关
/* 关于 GDT、IDT 等 descriptor table 的处理 */
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);
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
void load_gdtr(int limit, int addr);
void load_idtr(int limit, int addr);
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);
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;
}
bootpack.c - 其他处理
/* bootpack 其他处理 */
#include <stdio.h>
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 boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void init_screen8(char *vram, int x, int y);
void putfont8(char *vram, int xsize, int x, int y, char c, char *font);
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s);
void init_mouse_cursor8(char *mouse, char bc);
void putblock8_8(char *vram, int vxsize, int pxsize,
int pysize, int px0, int py0, char *buf, int bxsize);
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};
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);
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
void load_gdtr(int limit, int addr);
void load_idtr(int limit, int addr);
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
char s[40], mcursor[256];
int mx, my;
init_gdtidt();
init_palette();
init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
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);
sprintf(s, "(%d, %d)", mx, my);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
for (;;) {
io_hlt();
}
}
对应的Makefile流程
2. Makefile优化
-
普通的生成规则
-
bootpack.gas : bootpack.c Makefile $(CC1) -o bootpack.gas bootpack.c graphic.gas : graphic.c Makefile $(CC1) -o graphic.gas graphic.c dsctbl.gas : dsctbl.c Makefile $(CC1) -o dsctbl.gas dsctbl.c
-
-
一般规则
-
整合雷同的普通生成规则
-
%.gas : %.c Makefile $(CC1) -o $*.gas $*.c
-
-
make.exe 会优先寻找普通的生成规则,找不到再尝试一般规则
3. 头文件
-
将各个源文件相同的函数声明等内容,整合到一个头文件中,减少代码冗余
-
使用头文件只需要
-
#include "头文件名.h"
-
-
使用头文件的规则
使用 双引号引用头文件,表示头文件和源文件在同一个文件夹,而尖括号 '< >' 引用头文件表示该头文件位于编译器所提供的文件中
-
当编译器看到该行引用,就会把该行替换为所指定文件的内容,然后编译
bootpack.h
/* asmhead.nas */
struct BOOTINFO { /* 0xff0-0x0fff */
char cyls; /* 启动区读硬盘读到何处为止 */
char leds; /* 启动时键盘LED的状态 */
char vmode; /* 显卡模式为多少位彩色 */
char reserve;
short scrnx, scrny; /* 画面分辨率 */
char *vram;
};
#define ADR_BOOTINFO 0x00000ff0
/* naskfunc.nas */
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 load_gdtr(int limit, int addr);
void load_idtr(int limit, int addr);
/* graphic.c */
void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void init_screen8(char *vram, int x, int y);
void putfont8(char *vram, int xsize, int x, int y, char c, char *font);
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s);
void init_mouse_cursor8(char *mouse, char bc);
void putblock8_8(char *vram, int vxsize, int pxsize,
int pysize, int px0, int py0, char *buf, int bxsize);
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
/* dsctbl.c */
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);
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar);
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
#define ADR_IDT 0x0026f800
#define LIMIT_IDT 0x000007ff
#define ADR_GDT 0x00270000
#define LIMIT_GDT 0x0000ffff
#define ADR_BOTPAK 0x00280000
#define LIMIT_BOTPAK 0x0007ffff
#define AR_DATA32_RW 0x4092
#define AR_CODE32_ER 0x409a
4.放到第五天
5. 初始化PIC - 可编程中断控制器
-
programmable interrupt controller
-
为了解决CPU单独只能处理一个中断的问题,提出了PIC,PIC是8个中断信号集成一个中断信号的装置,只要有一个中断信号进来,就会将输出引脚改为ON并通知CPU处理中断
-
可以两个PIC相连,可以处理15个中断信号,称之主PIC和从PIC
init.c - PIC 初始化程序
/* 与中断有关 */
#include "bootpack.h"
void init_pic(void)
/* PIC的初始化 */
{
io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */
io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */
io_out8(PIC0_ICW1, 0x11 ); /* 边沿触发模式 (edge trigger mode) */
io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7 由 INT20-27 接收 */
io_out8(PIC0_ICW3, 1 << 2); /* PIC1 由 IRQ2 连接 */
io_out8(PIC0_ICW4, 0x01 ); /* 无缓冲区模式 */
io_out8(PIC1_ICW1, 0x11 ); /* 边沿触发模式 (edge trigger mode) */
io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15 由 INT28-2f 接收 */
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;
}
- PIC 对于CPU来说是外设,需要通过OUT命令进行操作,PIC0 位主PIC, PCI1为从PIC
PIC中的寄存器用端口区分(bootpack.h),都为8位寄存器
IMR
- interrupt mask register, 中断屏蔽寄存器
- 8位分别对应8路IRQ信号,若其中一位为1,则PIC屏蔽其信号
- 作用
- 正在对终端设定进行修改时,为防止混乱,暂停接受中断信号
ICW
- initial control word,初始化控制数据
- ICW1,4(主板的配线方式,中断信号的电气特性等相关)
- ICW3与 主从连接的设定有关
- ICW2可以独立设定,用于设定中断号,决定IRQ以哪一号中断通知CPU
6. 中断处理程序的制作
中断处理程序
- 鼠标是IRQ12,键盘是IRQ1,他们的中断处理程序如下:
void inthandler21(int *esp)
/* 来自PS/2键盘的中断 */
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard");
for (;;) {
io_hlt();
}
}
- 中断处理程序知识显示一条信息,然后保持待机状态
执行IRETD指令
-
中断处理程序完成后,不能return返回,而是必须执行IRETD指令(借助汇编语言修改)
-
该函数执行流程
- 将寄存器的值保存到栈中
- 然后将DS和ES调整到与SS相等
- 最后调用_inthandler21函数
- 函数返回后,将所有的寄存器再返回原来的值
- 最后执行IRETD
_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
先进先出的缓冲区(队列),简称FIFO
后进先出的缓冲区(栈),简称FILO
-
-
PUSH EAX 这个压栈操作相当于
-
ADD ESP, -4 MOV [SS:ESP], EAX
-
-
POP EAX
-
MOV [SS:ESP], EAX ADD ESP, 4
-
-
PUSHAD 指令
-
POPAD 相对于PUSHAD进行弹栈,恢复寄存器的原来的值,然后再执行IRETD进行返回
将中断处理函数注册到IDT中
-
/* IDT的设定 */ set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
-
asm_inthandler21注册在idt的 0x21号,2*8表示asm_inthandler21属于段2,即
-
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
-
AR_CODE32_ER将IDT的属性设为0x008e,即表示用于中断处理的有效设定
-
-
设定完成后,当发生了相应的0x21号中断后,就会执行相应的中断处理函数
bootpack.c - HariMain 补充分析
/* bootpack 其他处理 */
#include "bootpack.h"
#include <stdio.h>
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
char s[40], mcursor[256];
int mx, my;
init_gdtidt();
init_pic();
io_sti();
init_palette();
init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
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);
sprintf(s, "(%d, %d)", mx, my);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
io_out8(PIC0_IMR, 0xf9);
io_out8(PIC1_IMR, 0xef);
for (;;) {
io_hlt();
}
}
-
io_sti() 执行STI执行,即CLI的逆指令,执行了STI后,IF(interrupt flag)中断标志位为1,CPU就可以接受外部设备的中断
-
HariMain最后修改了PIC的IMR,以便接收来自键盘和鼠标的中断
总结
- 命令行 make run
【推荐】国内首个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,普通电脑可用
· 按钮权限的设计及实现