简介
内存作为计算机有限宝贵资源,好的程序需要有良好的内存管理算法。首先需要知道内存的可用区域再给应用程序分配和回收。
目标
检测系统硬件内存,一种比较好的办法是使用15h中断。
1、 将寄存器ax 赋值为 0E820h
2.、将寄存器ebx 初始化为0,该寄存器的内容会被BIOS修改
3、 es:di 指向一块足够大的内存地址,BIOS会把有关内存的信息写到这个地址,内存信息是一种数据结构,称之为地址范围描述符。
4、 ecx 寄存器存储es:di所指向的内存大小,以字节为单位,BIOS最多会填充ecx个字节的数据,通常情况下,无论ecx的数值是多少,BIOS都只填充20字节,有些BIOS直接忽略ecx的值,总是填充20字节。
5.、edx寄存器的值设置为0534D4150h, 这个数值其实对应的是字符组合”SMAP”,其作用我们可以暂时忽略。
检测内存过程中需要不断执行int 15h 中断,中断结果的分析如下:
1: 判断CF位,如果CF位设置为1,则表示出错
2:eax 会被设置为0534D4150h, 也就是字符串’SMAP’
3: es:di 返回地址范围描述符结构指针,跟输入时相同
4:如果ebx的值为0,表明查询结束,如果不为0,则继续调用15h获取有关内
存的信息
内存范围描述符的结构:
struct AddrRangeDesc {
unsigned int baseAddrLow ; //内存基地址的低32位
unsigned int baseAddrHigh; //内存基地址的高32位
unsigned int lengthLow; //内存块长度的低32位
unsigned int lengthHigh; //内存块长度的高32位
unsigned int type; //描述内存块的类型
}
type的取值, type 等于1,表示当前内存块可以被内核使用。
type等于2,表示当前内存块已经被占用,系统内核绝对不能使用,type等于3,保留给未来使用,内核也不能用当前内存块。
6、kernel.s 文件如下:
;全局描述符结构 8字节
; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0
; byte6低四位和 byte1 byte0 表示段偏移上限
; byte7 byte4 byte3 byte2 表示段基址
;定义全局描述符数据结构
;3 表示有3个参数分别用 %1、%2、%3引用参数
;%1:段基址 %2:段偏移上限 %3:段属性
%macro GDescriptor 3
dw %2 & 0xffff
dw %1 & 0xffff
db (%1>>16) & 0xff
dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff)
db (%1>>24) & 0xff
%endmacro
DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的只执行代码段属性值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
;中断描述符表
;Gate selecotor, offset, DCount, Attr
%macro Gate 4
dw (%2 & 0xffff)
dw %1
dw (%3 & 0x1f) | ((%4 << 8) & 0xff00)
dw ((%2>>16) & 0xffff)
%endmacro
DA_386IGate EQU 8Eh ; 中断调用门
org 0x9000
jmp entry
[SECTION .gdt]
;定义全局描述符 段基址 段偏移上限 段属性
LABEL_GDT: GDescriptor 0, 0, 0
LABEL_DESC_CODE: GDescriptor 0, SegCodeLen-1, DA_C+DA_32
LABEL_DESC_VIDEO: GDescriptor 0xb8000, 0xffff, DA_DRW
LABEL_DESC_STACK: GDescriptor 0, STACK_TOP-1, DA_DRWA+DA_32
LABEL_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_DRW
;gdt 表大小
GdtLen equ $-LABEL_GDT
;gdt表偏移上限和基地址
GdtPtr dw GdtLen-1
dd 0
;cpu开机进入实模式时使用的段寄存器 cs,ds,es,ss 和偏移地址组成内存地址,内存地址=段寄存器 * 16 + 偏移地址
;保护模式中段寄存器保存的是gdt 描述表中各个描述符的偏移,也叫选择子
SelectorCode32 EQU LABEL_DESC_CODE-LABEL_GDT
SelectorVideo EQU LABEL_DESC_VIDEO-LABEL_GDT
SelectorStack EQU LABEL_DESC_STACK-LABEL_GDT
SelectorVRAM EQU LABEL_DESC_VRAM-LABEL_GDT
;中断描述符表
LABEL_IDT:
%rep 0x21
Gate SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep
;键盘中断向量(8259A 键盘中断向量0x20,IRQ1 是键盘中断请求,0x20 + IRQ[n] = 0x21
.0x21:
Gate SelectorCode32, KeyboardHandler,0, DA_386IGate
%rep 10
Gate SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep
;从中断控制器8259A 中断向量0x28,IRQ4 是鼠标中断请求,0x28 + IRQ[n] = 0x2c
.0x2c:
Gate SelectorCode32, MouseHandler,0, DA_386IGate
IdtLen equ $ - LABEL_IDT
IdtPtr dw IdtLen - 1
dd 0
[SECTION .s16]
[BITS 16]
entry:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0x100
;检测系统内存
mov ebx, 0
mov di, LABEL_MEM_BUF
LABEL_MEM_CHK_LOOP:
mov eax, 0xE820
mov ecx, 20
mov edx, 0x534D4150
int 0x15
jc LABEL_MEM_CHK_FAIL
add di, 20
inc dword [LABEL_MEM_COUNT]
cmp ebx, 0
jne LABEL_MEM_CHK_LOOP
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [LABEL_MEM_COUNT], 0
LABEL_MEM_CHK_OK:
;设置屏幕色彩模式
mov al,0x13
mov ah,0
int 0x10
;设置LABEL_DESC_CODE描述符段基址
mov eax,0
mov ax,cs
shl eax,4
add eax,SEG_CODE32
mov word [LABEL_DESC_CODE+2],ax
shr eax,16
mov [LABEL_DESC_CODE+4],al
mov [LABEL_DESC_CODE+7],ah
;设置栈空间
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_STACK
mov word [LABEL_DESC_STACK+2],ax
shr eax,16
mov byte [LABEL_DESC_STACK+4],al
mov byte [LABEL_DESC_STACK+7],ah
mov eax,0
mov ax,ds
shl eax,4
add eax,LABEL_GDT
mov dword [GdtPtr+2],eax
;设置GDTR 寄存器
lgdt [GdtPtr]
cli ;关闭可可屏蔽中断,如键盘中断
in al,0x92
or al,0x02
out 0x92,al
mov eax,cr0
or eax,1
mov cr0,eax
call init8259A
;加载中断描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_IDT
mov dword [IdtPtr + 2], eax
lidt [IdtPtr]
sti ;恢复中断
jmp dword SelectorCode32:0
;初始化8259A中断控制器
init8259A:
mov al, 0x11 ;向主8259A发送ICW1
out 0x20, al
call io_delay
out 0xa0, al ;向从8259A发送ICW1
call io_delay
;20h 分解成ICW2 是, ICW2[0,1,2] = 0, 这是强制要求的,
;也就是ICW2的值不能是0x21,0x22之类,只要前三位是0就行
;整个ICW2 = 0x20,这样的话,当主8259A对应的IRQ0管线向CPU发送信号时,
;CPU根据0x20这个值去查找要执行的代码,IRQ1管线向CPU发送信号时,
;CPU根据0x21这个值去查找要执行的代码,依次类推
mov al, 0x20 ;向主8259A发送ICW2
out 0x21, al ;
;
call io_delay
mov al, 0x28 ;向从8259A发送ICW2
out 0xa1, al
call io_delay
;04h 分解成ICW3 相当于ICW[2] = 1,
;这表示从8259A通过主IRQ2管线连接到主8259A控制器
mov al, 0x04 ; 向主8259A发送ICW3
out 0x21, al
call io_delay
mov al, 0x02 ;向从8259A 发送 ICW3
out 0xa1, al
call io_delay
mov al, 0x02
out 0x21, al
call io_delay
out 0xa1, al
call io_delay
;还需要再向两个芯片分别发送一个字节,叫OCW(operation control word),
;一个OCW是一字节数据, 也就是8bit,每一bit设置作用是,当OCW[i] = 1 时,
;屏蔽对应的IRQ(i)管线的信号,例如OCW[0]=1, 那么IRQ0管线的信号将不会被CPU接收,以此类推
;
mov al, 11111001b ;CPU只接收主8259A, IRQ1,IRQ2管线发送的信号,其他管线发送信号一概忽略
out 0x21, al ;IRQ1对应的是键盘产生的中断
call io_delay
mov al, 11101111b ;IRQ4 允许鼠标中断
out 0xa1, al ;鼠标是通过从8259A的IRQ4管线向CPU发送信号
call io_delay
ret
io_delay:
nop
nop
nop
nop
ret
;检测内存数据
LABEL_MEM_BUF: ;存放内存块
times 256 db 0
LABEL_MEM_COUNT: ;内存块数量
dd 0
[SECTION .s32]
[BITS 32]
SEG_CODE32:
mov ax,SelectorStack
mov ss,ax
mov esp,STACK_TOP
mov ax,SelectorVRAM
mov ds,ax
call init_main
fin:
hlt
jmp fin
;8259A中断控制器
LABEL_8259A:
SpuriousHandler equ LABEL_8259A - $$
iretd
;键盘中断程序
LabelKeyboardHandler:
KeyboardHandler equ LabelKeyboardHandler - $$
push es
push ds
pushad
mov eax, esp
push eax
call int_keyboard
pop eax
mov esp, eax
popad
pop ds
pop es
iretd
;鼠标中断程序
LabelMouseHandler:
MouseHandler equ LabelMouseHandler - $$
push es
push ds
pushad
mov eax, esp
push eax
call int_mouse
pop eax
mov esp, eax
popad
pop ds
pop es
iretd
;获取内存块数量
mem_block_count:
mov eax,dword [LABEL_MEM_COUNT]
ret
;导入io操作函数模块
%include "io.s"
;导入C语言编写的功能模块
%include "os.s"
;32位模式代码长度
SegCodeLen EQU $-SEG_CODE32
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 1024 db 0
STACK_TOP EQU $ - LABEL_STACK
开始我们先把di指向内存块LABEL_MEM_BUF,BIOS可把内存地址描述符数据结构填充到这块内存,int 15h执行后,如果调用失败,程序会跳转到分支LABEL_MEM_CHK_FAIL,在这里会把dwMCRNumber的值设置为0,如果这段代码执行成功的话,LABEL_MEM_COUNT的值肯定不是0. 由于内存地址描述符结构的大小是20字节,每次填充后,要把di的值增加20。如果ebx的值变为0的话,表明所有内存块的信息都已经被BIOS填充到LABEL_MEM_BUF所指向的内存中了。
7.新增kernel.h 头文件用于申明kernel.s 汇编编写的相关功能,以给C语言调用:
//申明汇编的功能模块给C语言调用
//获取内存块数量
extern int mem_block_count();
8.修改os.c 文件如下:
// !compile method
// clang -m32 -c os.c -o os.o
// objconv -fnasm os.o -o os.s
//
#include "os.h"
#include "io.h"
#include "kernel.h"
#include "ascii_font.h"
static unsigned char *ascii = ascii_array;
//数据临时内存区
static char tempArr[64];
//键盘和鼠标缓存
static char keybuf[32];
static char mousebuf[128];
static FIFO8 keybufInfo;
static FIFO8 mousebufInfo;
//鼠标移动模型
static MouseDes mouseDes;
static int num = 0;
//操作系统C语言入口函数--可以指定为其他
void init_main() {
io_sti();
initPallet();
drawBackground();
int memCount = mem_block_count();
tempArr[0] = '0';
tempArr[1] = 'x';
char2HexStr(memCount,tempArr);
for(int i=0;i<4;i++){
int x = (num)%32*10;
int y = (num)/32*20;
putChar((char *)0xa0000,x,y,COL8_FFFFFF,ascii+(tempArr[i]-0x20)*16,SCREEN_WIDTH);
num++;
}
fifo8_init(&keybufInfo,32,keybuf);
fifo8_init(&mousebufInfo,128,mousebuf);
mouseDes.x = (320-16)/2;
mouseDes.y = (200-16)/2;
mouseDes.phase = 0;
drawMouseCursor((char *)0xa0000,mouseDes.x,mouseDes.y,COL8_008484);
init_mouse();
for(; ;){
if(keybufInfo.len>0){
io_cli();
tempArr[0] = '0';
tempArr[1] = 'x';
for(int t=0;t<keybufInfo.len;t++){
char data = fifo8_get(&keybufInfo);
char2HexStr(data,tempArr);
for(int i=0;i<4;i++){
int x = (num)%32*10;
int y = (num)/32*20;
putChar((char *)0xa0000,x,y,COL8_FFFFFF,ascii+(tempArr[i]-0x20)*16,SCREEN_WIDTH);
num++;
}
}
io_sti();
}
else if(mousebufInfo.len>0){
io_cli();
for(int t=0;t<mousebufInfo.len;t++){
mouseCursorMoved(&mouseDes,COL8_008484);
}
io_sti();
}
else{
io_hlt();
}
}
}
void initPallet(){
//定义调色板
static 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:暗灰*/
};
unsigned char *rgb = (unsigned char *)table_rgb;
int flag = io_readFlag();
io_cli();
io_out8(0x03c8, 0);
for(int i=0;i<16;i++){
io_out8(0x03c9,rgb[0] / 4);
io_out8(0x03c9,rgb[1] / 4);
io_out8(0x03c9,rgb[2] / 4);
rgb += 3;
}
io_writeFlag(flag);
}
void fillRect(int x,int y,int width,int height,char colIndex){
char *vram = (char *)0xa0000;
for(int i=y;i<=y+height;i++){
for(int j=x;j<=x+width;j++){
vram[i*SCREEN_WIDTH+j] = colIndex;
}
}
}
void drawBackground(){
fillRect(0,0,SCREEN_WIDTH-1,SCREEN_HEIGHT-29, COL8_008484);
fillRect(0,SCREEN_HEIGHT-28,SCREEN_WIDTH-1,28, COL8_848484);
fillRect(0,SCREEN_HEIGHT-27,SCREEN_WIDTH,1, COL8_848484);
fillRect(0,SCREEN_HEIGHT-26,SCREEN_WIDTH,25, COL8_C6C6C6);
fillRect(3,SCREEN_HEIGHT-24,56,1, COL8_FFFFFF);
fillRect(2,SCREEN_HEIGHT-24,1,20, COL8_FFFFFF);
fillRect(3,SCREEN_HEIGHT-4,56,1, COL8_848484);
fillRect(59,SCREEN_HEIGHT-23,1,19, COL8_848484);
fillRect(2,SCREEN_HEIGHT-3,57,0, COL8_000000);
fillRect(60,SCREEN_HEIGHT-24,0,19, COL8_000000);
fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-24,43,1, COL8_848484);
fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-23,0,19, COL8_848484);
fillRect(SCREEN_WIDTH-47,SCREEN_HEIGHT-3,43,0, COL8_FFFFFF);
fillRect(SCREEN_WIDTH-3,SCREEN_HEIGHT-24,0,21, COL8_FFFFFF);
}
void putChar(char *addr,int x,int y,char col,unsigned char *pch,int screenWidth){
for(int i=0;i<16;i++){
char ch = pch[i];
int off = (y+i)*screenWidth;
//显示的字形最左边的是低地址,右侧的是高地址。例如:0x80,则高地址部分显示在内存的低地址,
//最低位的应该偏移7
if((ch & 0x01) != 0){
addr[off+x+7] = col;
}
if((ch & 0x02) != 0){
addr[off+x+6] = col;
}
if((ch & 0x04) != 0){
addr[off+x+5] = col;
}
if((ch & 0x08) != 0){
addr[off+x+4] = col;
}
if((ch & 0x10) != 0){
addr[off+x+3] = col;
}
if((ch & 0x20) != 0){
addr[off+x+2] = col;
}
if((ch & 0x40) != 0){
addr[off+x+1] = col;
}
if((ch & 0x80) != 0){
addr[off+x+0] = col;
}
}
}
void drawMouseCursor(char *vram,int x,int y,char bc){
//16*16 Mouse
//鼠标指针点阵
static char cursor[16][16] = {
"*...............",
"**..............",
"*O*.............",
"*OO*............",
"*OOO*...........",
"*OOOO*..........",
"*OOOOO*.........",
"*OOOOOO*........",
"*OOOOOOO*.......",
"*OOOO*****......",
"*OO*O*..........",
"*O*.*O*.........",
"**..*O*.........",
"*....*O*........",
".....*O*........",
"......*........."
};
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
int off = (i+y)*SCREEN_WIDTH+x+j;
if (cursor[i][j] == '*') {
vram[off] = COL8_000000;
}
if (cursor[i][j] == 'O') {
vram[off] = COL8_FFFFFF;
}
if (cursor[i][j] == '.') {
vram[off] = bc;
}
}
}
}
void char2HexStr(unsigned char val,char *arr) {
unsigned char tmp = val >> 4;
if(tmp>=10){
arr[2] = 'a'+tmp-10;
}
else{
arr[2] = '0'+tmp;
}
tmp = val & 0x0f;
if(tmp>=10){
arr[3] = 'a'+tmp-10;
}
else{
arr[3] = '0'+tmp;
}
}
/*
*8259A 键盘中断调用
*
*/
void int_keyboard(char *index){
//0x20是8259A控制端口
//0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,
//CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号
io_out8(0x20,0x21);
//读取8259A 0x60端口键盘扫描码
char data = io_in8(0x60);
fifo8_put(&keybufInfo,data);
}
#define PORT_KEYDAT 0x60
#define PORT_KEYSTA 0x64
#define PORT_KEYCMD 0x64
#define KEYCMD_WRITE_MODE 0x60
#define KBC_MODE 0x47
//鼠标电路对应的一个端口是 0x64, 通过读取这个端口的数据来检测鼠标电路的状态,
//内核会从这个端口读入一个字节的数据,如果该字节的第二个比特位为0,那表明鼠标电路可以
//接受来自内核的命令,因此,在给鼠标电路发送数据前,内核需要反复从0x64端口读取数据,
//并检测读到数据的第二个比特位,直到该比特位为0时,才发送控制信息
void waitKBCReady(){
for( ; ;){
if((io_in8(PORT_KEYSTA) & 0x02)==0){
break;
}
}
}
#define KEYCMD_SENDTO_MOUSE 0xd4
#define MOUSECMD_ENABLE 0xf4
//初始化键盘控制电路,鼠标控制电路是连接在键盘控制电路上,通过键盘电路实现初始化
void init_mouse(){
waitKBCReady();
//0x60让键盘电路进入数据接受状态
io_out8(PORT_KEYCMD,KEYCMD_WRITE_MODE);
waitKBCReady();
//数据0x47要求键盘电路启动鼠标模式,这样鼠标硬件所产生的数据信息,通过键盘电路端口0x60就可读到
io_out8(PORT_KEYDAT,KBC_MODE);
waitKBCReady();
io_out8(PORT_KEYCMD,KEYCMD_SENDTO_MOUSE);
waitKBCReady();
//0xf4数据激活鼠标电路,激活后将会给CPU发送中断信号
io_out8(PORT_KEYDAT,MOUSECMD_ENABLE);
}
/*
*8259A 鼠标中断调用
*
*/
void int_mouse(char *index){
//当中断处理后,要想再次接收中断信号,就必须向中断控制器发送一个字节的数据
io_out8(0x20,0x20);
io_out8(0xa0,0x20);
//读取鼠标数据
char data = io_in8(0x60);
fifo8_put(&mousebufInfo,data);
}
void fifo8_init(FIFO8 *fifo, int size,char *buf){
fifo->buf = buf;
fifo->r = 0;
fifo->w = 0;
fifo->size = size;
fifo->len = 0;
fifo->flag = 0;
}
int fifo8_put(FIFO8 *fifo,char data){
if (fifo->len == fifo->size) {
return -1;
}
fifo->buf[fifo->w] = data;
fifo->w++;
if (fifo->w == fifo->size) {
fifo->w = 0;
}
fifo->len++;
return 0;
}
int fifo8_get(FIFO8 *fifo) {
if (fifo->len == 0) {
return -1;
}
int data = fifo->buf[fifo->r];
fifo->r++;
if (fifo->r == fifo->size) {
fifo->r = 0;
}
fifo->len--;
return data;
}
int mouse_decode(MouseDes *mdec,unsigned char dat){
int flag = -1;
if (mdec->phase == 0) {
if (dat == 0xfa) {
mdec->phase = 1;
}
flag = 0;
}
else if (mdec->phase == 1) {
if ((dat & 0xc8) == 0x08) {
mdec->buf[0] = dat;
mdec->phase = 2;
}
flag = 0;
}
else if (mdec->phase == 2) {
mdec->buf[1] = dat;
mdec->phase = 3;
flag = 0;
}
else if (mdec->phase == 3) {
mdec->buf[2] = dat;
mdec->phase = 1;
mdec->btn = mdec->buf[0] & 0x07;
mdec->offX = mdec->buf[1];
mdec->offY = mdec->buf[2];
if ((mdec->buf[0] & 0x10) != 0) {
mdec->offX |= 0xffffff00;
}
if ((mdec->buf[0] & 0x20) != 0) {
mdec->offY |= 0xffffff00;
}
//鼠标y坐标偏移和平面y坐标相反
mdec->offY = -mdec->offY;
flag = 1;
}
return flag;
}
//鼠标移动处理
void mouseCursorMoved(MouseDes *mdec,char bc){
unsigned char val = fifo8_get(&mousebufInfo);
//表示处理到第3步,需要绘制鼠标光标
if(mouse_decode(mdec,val) == 1) {
fillRect(mdec->x,mdec->y,16,16,bc);
mdec->x += mdec->offX;
mdec->y += mdec->offY;
if(mdec->x < 0){
mdec->x = 0;
}
if(mdec->x > SCREEN_WIDTH-16/2){
mdec->x = SCREEN_WIDTH-16/2;
}
if(mdec->y < 0 ){
mdec->y = 0;
}
if(mdec->y > SCREEN_HEIGHT - 16){
mdec->y = SCREEN_HEIGHT - 16;
}
drawMouseCursor((char *)0xa0000,mdec->x,mdec->y,bc);
}
}
9.加载并允许floppy.img 文件后效果如下:
说明内存被分成8个AddrRangeDesc 结构体描述信息,笔者使用的虚拟机内存配置为64M,如下图所示:
至此我们的内存检测完成!