#define PORT_KEYDAT 0x0060
void inthandler21(int *esp)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
unsigned char data, s[4];
/* void io_out8(int port,int data);将数据0x61写入端口PIC0_OCW2
通知PIC"IRQ-01已经受理完毕" */
io_out8(PIC0_OCW2, 0x61);
/* int io_in8(int port);从端口处读取8位数据,即什么操作导致了中断 */
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;
}
创建一个结构体,该结构体中存有数据和缓冲区是否为空的标识。
for (;;) {
io_cli();//屏蔽中断
if (keybuf.flag == 0) {
io_stihlt();//开放中断,并待机
} else {
i = keybuf.data;//读出缓冲区数据
keybuf.flag = 0;//表示缓冲区可写入
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);//打印字符串s
}
}
在7.2节中,每次只能存储一个字节,并不方便。所以将KEYBUF结构体中的data改成数组类型。
struct KEYBUF {
unsigned char data[32];//存储数据的缓冲区
int next;//即将写入数据的位置下标
};
void inthandler21(int *esp)
{
unsigned char data;
io_out8(PIC0_OCW2, 0x61); /* 通知PIC IRQ-01已经受理完毕 */
data = io_in8(PORT_KEYDAT);//读取按下键盘中的某一按键
/// 当keybuf.next=32时,不再向缓冲区写入数据,也不是使keybuf.next增加
if (keybuf.next < 32) {
keybuf.data[keybuf.next] = data;
keybuf.next++;
}
return;
}
for (;;) {
io_cli();
if (keybuf.next == 0) {
io_stihlt();
} else {
i = keybuf.data[0];
//keybuf.next是即将写入数据的位置下标,所以已经成功写入了keybuf.next-1个数据
keybuf.next--;
for (j = 0; j < keybuf.next; j++) {
keybuf.data[j] = keybuf.data[j + 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);
}
}
该节不再指定缓冲区的长度,并改写KEYBUF结构体为FIFO8结构体。
// p下一个数据写入地址 q下一个数据读出地址 size已经写入的数据个数
// free缓冲区里没有数据的字节数 flags该缓冲区是否可用
struct FIFO8 {
unsigned char *buf;
int p, q, size, free, flags;
};
/* fifo.c */
#define FLAGS_OVERRUN 0x0001
// 对FIFO8结构体中的元素进行初始化
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf){
fifo->buf=buf;
fifo->p=0; /* 下一个写入地址 */
fifo->q=0; /* 下一个读出地址 */
fifo->size=size;/* 缓冲区大小 */
fifo->free=size;/* 可用缓冲区大小 */
fifo->flags=0;
return;
}
// 向fifo中写入data,即更新缓冲区数据、p++和free--
int fifo8_put(struct FIFO8 *fifo, unsigned char data){
if(fifo->free==0){/* 没有可用的缓冲区,设置flags并返回-1 */
fifo->flags|=FLAGS_OVERRUN;
return -1;
}
fifo->free--;
fifo->buf[fifo->p]=data;
fifo->p++;
if(fifo->p == fifo->size)fifo->p=0;
return 0;
}
// 从fifo读出一个数据,即更新缓冲区数据、q++和free++
int fifo8_get(struct FIFO8 *fifo){
int data;
if(fifo->free==fifo->size){/* 缓冲区内没有数据,直接返回-1 */
return -1;
}
data = fifo->buf[fifo->q];
fifo->q++;
fifo->free++;
if(fifo->q==fifo->size)fifo->q=0;
return data;
}
/* 报告缓冲区内已存数据的个数 */
int fifo8_status(struct FIFO8 *fifo){
return fifo->size - fifo->free;
}
#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/* 利用鼠标模式的模式号码是0x47 */
//等待键盘控制电路准备完毕
void wait_KBC_sendready(void){
/*如果键盘控制电路可以接受CPU指令了,CPU从设备号码0x0064处所读
取的数据的倒数第二位(从低位开始数的第二位)应该是0*/
for(;;){
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(struct MOUSE_DEC *mdec){
/* 激活鼠标,如果往键盘控制电路发送指令0xd4,下一个数据就会自动发送给鼠标*/
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);//确认可否往命令端口发送指令信息
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);//向数据端口发送模式设定指令
mdec->phase=0;/* 进入等待鼠标的0xfa的状态 enable_mouse()函数设置初始值MOUSECMD_ENABLE=0xfa */
return; /* 顺利的话,键盘控制其会返送回ACK(0xfa)*/
}
unsigned char mouse_dbuf[3], mouse_phase;
enable_mouse();
mouse_phase = 0; /* 进入到等待鼠标的0xfa的状态 */
for (;;) {
io_cli();
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {// keyfifo和mousefifo都没有数据
io_stihlt();
} else {// keyfifo、mousefifo中至少有一个有数据
if (fifo8_status(&keyfifo) != 0) {//keyfifo有数据,即键盘有输入数据
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) {
/*//mousefifo有数据,即鼠标有输入数据。把最初读到的0xfa舍弃掉,之后每次从鼠标那里送过来的数据都应该是3个字节一组的,所以要连续读取。*/
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;
/* 鼠标的3个字节都齐了,显示出来 */
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);
}
}
}
}
将8.1节中的鼠标读取3字节的功能封装成函数int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat) 。
struct MOUSE_DEC {
//buf为鼠标的三元素 phase为鼠标的状态,分为0/1/2/3,0为等待状态,其他数字为等待写入第几个字节
unsigned char buf[3], phase;
};
//dat为从鼠标缓冲区中读出的数据
//当读入三个数据时返回1,其他情形返回0
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{
if (mdec->phase == 0) {
/* 等待鼠标的0xfa的阶段 */
if (dat == 0xfa) {
mdec->phase = 1;
}
return 0;
}
if (mdec->phase == 1) {
/* 等待鼠标第一字节的阶段 */
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;
return 1;
}
return -1; /* 应该不可能到这里来 */
}
struct MOUSE_DEC{
unsigned char buf[3], phase;
int x, y, btn;/* x,y存储鼠标的坐标,btn为鼠标的按键状态信息 */
};
mdec->btn = mdec->buf[0] & 0x07;//鼠标键的状态,放在buf[0]的低3位
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方向与画面符号相反 */
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);
io_sti();
if (mouse_decode(&mdec, i) != 0) {
/* 数据的3个字节都齐了,显示出来 */
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); /* 描画鼠标 */
}
}
#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){/* 如果是386,即使设定AC=1,AC的值还会自动回到0 */
flg486=1;
}
eflg&= ~EFLAGS_AC_BIT;/* 将AC标志位重置为0 */
io_store_eflags(eflg);
if(flg486){
cr0=load_cr0();
cr0|=CR0_CACHE_DISABLE;/*禁止缓存*/
store_cr0(cr0);
}
// 调查从start地址到end地址的范围内,能够使用的内存的末尾地址
i=memtest_sub(start, end);
if(flg486){
cr0=load_cr0();
cr0&= ~CR0_CACHE_DISABLE;
store_cr0(cr0);
}
return i;
}
unsigned int memtest_sub(unsigned int start, unsigned int end)的功能是从start地址到end地址的范围内,能够使用的内存的末尾地址,也就是确定在这个范围内能够使用的内存个数。确定的方法就是通过对地址内数据的两次反转,判断是否能恢复到初始值,如果可以则检查下一个,否则就返回当前地址。
将函数memtest_sub改写成汇编语言,以加快处理速度。
;调查从start地址到end地址的范围内,能够使用的内存的末尾地址
_memtest_sub: ; unsigned int memtest_sub(unsigned int start, unsigned int end)
PUSH EDI ; (由于还要使用EBX, ESI, EDI)
PUSH ESI
PUSH EBX
MOV ESI,0xaa55aa55 ; pat0 = 0xaa55aa55;
MOV EDI,0x55aa55aa ; pat1 = 0x55aa55aa;
MOV EAX,[ESP+12+4] ; i = start;
mts_loop:
MOV EBX,EAX
ADD EBX,0xffc ; p = i + 0xffc;
MOV EDX,[EBX] ; old = *p;
MOV [EBX],ESI ; *p = pat0;
XOR DWORD [EBX],0xffffffff ; *p ^= 0xffffffff;
CMP EDI,[EBX] ; if (*p != pat1) goto fin;
JNE mts_fin
XOR DWORD [EBX],0xffffffff ; *p ^= 0xffffffff;
CMP ESI,[EBX] ; if (*p != pat0) goto fin;
JNE mts_fin
MOV [EBX],EDX ; *p = old;
ADD EAX,0x1000 ; i += 0x1000;
CMP EAX,[ESP+12+8] ; if (i <= end) goto mts_loop;
JBE mts_loop
POP EBX
POP ESI
POP EDI
RET
mts_fin:
MOV [EBX],EDX ; *p = old;
POP EBX
POP ESI
POP EDI
RET
数据交换等操作对内存管理的要求较高,且使用并释放一个字节的内存比较费时费力,所以结合《深入理解Linux内核》采取页管理模式。
struct FREEINFO { /* 可用状况 */
unsigned int addr, size;// addr页的首地址 size页大小
};
struct MEMMAN { /* 内存管理 */
int frees;//可用的数量
struct FREEINFO free[1000];
};
struct MEMMAN memman;
memman.frees = 1; /* 可用状况list中只有1件 */
memman.free[0].addr = 0x00400000; /* 从0x00400000号地址开始,有124MB可用 */
memman.free[0].size = 0x07c00000;/* 0x07c00000=7*16+12MB=124MB */
假设每页有4k内存,如果函数需要3226字节的内存,则从memman.free[0]中选一段连续的、未被使用的3226字节分配给函数( memman.free[0].addr+=3226; memman.free[0].size-=3226; ),函数用完内存后再释放给该页( memman.free[0].addr-=3226; memman.free[0].size+=3226; )。
首先就需要对结构体中的元素进行初始化,构造void memman_init(struct MEMMAN *man)函数。申请内存时需要考虑在哪个内存数组中申请多大的内存,确定函数的输入项为struct MEMMAN *man和size,接着就需要遍历man中所有的页,检查页中剩余内存能否满足size,如果可以满足则返回分配到的内存首地址,否则检查下一页内存。如果没有页满足要求,则返回0。
/* 在man分配内存:1.检索能放下size空间大小的内存,2.更新该块内存的信息,
3.检查该内存是否还有剩余内存,没有则更新数组信息 */
unsigned int memman_alloc(struct MEMMAN *man, unsigned int size){
unsigned int i, a;
for(i=0;ifrees;i++){
if(man->free[i].size>size){/* 找到了足够大的内存 */
a=man->free[i].addr;
man->free[i].addr+=size;
man->free[i].size-=size;
if(man->free[i].size==0){/* 如果free[i]变成了0,表明该区间没有可用内存,就减掉一条可用信息 */
man->frees--;
for(;ifrees;i++){/* 代入结构体,后续空间前移 */
man->free[i] = man->free[i + 1];
}
}
return a;
}
}
return 0;/* 没有可用空间 */
}
内存释放就是确定从什么地址开始的多大内存释放到哪页内存中,由此得出函数的输入项为memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)。思路为释放内存空间,如下有4种情况:
1.可以与前面相邻的内存空间进行合并(需要考虑是否前面存在内存空间);
2.可以与后面相邻空间进行合并(man->free数组需要移动);
3.不能与前后相邻内存空间进行合并(需要扩展man->free数组存放待释放的内存);
4.待释放的内存无法被释放。
int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
int i, j;
/* 为便于归纳内存,将free[]按照addr的顺序排列 */
/* 所以,先决定应该放在哪里 */
for (i = 0; i < man->frees; i++) {
/* 表明addr在man->free[i].addr之前,即确定了释放空间位置 */
if (man->free[i].addr > addr) {
break;
}
}
/* free[i - 1].addr < addr < free[i].addr */
if (i > 0) {
/* 前面有可用内存,即不是释放到整个内存空间头部 */
if (man->free[i - 1].addr + man->free[i - 1].size == addr) {
/* 可以与前面的可用内存归纳到一起 */
man->free[i - 1].size += size;
if (i < man->frees) {
/* 紧后也有可用内存 */
if (addr + size == man->free[i].addr) {
/* 也可以与后面的可用内存归纳到一起 */
man->free[i - 1].size += man->free[i].size;
/* man->free[i]删除 */
/* free[i]变成0后归纳到前面去 */
man->frees--;
/* 由于下标i的内存与i-1内存进行了合并,所以后续空间前移 */
for (; i < man->frees; i++) {
man->free[i] = man->free[i + 1];
}
}
}
return 0; /* 成功完成 */
}
}
/* 不能与前面的可用空间归纳到一起 */
if (i < man->frees) {
/* 后面还有,正好与下标为i的内存连接 */
if (addr + size == man->free[i].addr) {
/* 可以与后面的内容归纳到一起 */
man->free[i].addr = addr;
man->free[i].size += size;
return 0; /* 成功完成 */
}
}
/* 既不能与前面归纳到一起,也不能与后面归纳到一起 */
if (man->frees < MEMMAN_FREES) {
/* free[i]之后的,向后移动,腾出一个可用空间 */
for (j = man->frees; j > i; j--) {
man->free[j] = man->free[j - 1];
}
man->frees++;
if (man->maxfrees < man->frees) {
man->maxfrees = man->frees; /* 更新最大值 */
}
man->free[i].addr = addr;
man->free[i].size = size;
return 0; /* 成功完成 */
}
/* 不能往后移动 */
man->losts++;
man->lostsize += size;
return -1; /* 失败 */
}