15.检测可用内存

简介

内存作为计算机有限宝贵资源,好的程序需要有良好的内存管理算法。首先需要知道内存的可用区域再给应用程序分配和回收。

目标

检测系统硬件内存,一种比较好的办法是使用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,如下图所示:
在这里插入图片描述

至此我们的内存检测完成!

<think>好的,我现在需要帮助用户解决Oracle数据库在启动或运行过程中遇到的无法检查可用内存的问题。首先,我应该回忆一下相关的Oracle内存管理知识以及常见的错误处理方法。根据用户提供的引用内容,特别是引用[3]中提到的一些GI/RAC数据库安装问题,可能与内存配置或权限有关。 首先,用户的问题是无法检查可用内存,这可能涉及多个方面。我需要分步骤来考虑可能的原因和解决方案。首先,内存不足可能是最常见的原因,所以需要确认系统是否有足够的物理内存和交换空间。Oracle对内存的需求较高,特别是在启动实例时,如果内存不足,可能会导致无法分配所需的内存区域。这时候,用户需要检查系统当前的内存使用情况,比如使用free -m命令查看可用内存和交换空间。如果确实不足,可能需要增加物理内存或调整交换空间的大小。 其次,内存参数设置不当也是一个常见问题。Oracle的SGA(系统全局区)和PGA(程序全局区)如果配置过大,超过系统可用内存,就会导致启动失败。需要检查初始化参数文件(如spfile或pfile)中的memory_target、memory_max_target、sga_target、pga_aggregate_target等参数。适当调低这些参数可能解决问题,尤其是在内存资源紧张的环境中。例如,使用ALTER SYSTEM命令动态调整这些参数,或者在启动时指定pfile来临时修改。 第三,用户权限或配置问题也可能导致内存检查失败。引用[3]提到grid用户需要能够访问某些文件,比如olr.loc。虽然这里的问题不是直接关于安装,但权限问题可能影响数据库的正常运行。需要确认Oracle软件安装用户(通常是oracle用户)是否有足够的权限访问相关内存资源。此外,检查/etc/security/limits.conf文件中的内存限制设置,如memlock、stack等参数是否合理,确保没有过低的限制阻碍Oracle进程申请内存。 第四,系统内核参数设置也可能影响内存分配。例如,在Linux系统中,vm.min_free_kbytes参数设置过低可能导致内存不足,特别是在高负载情况下。需要检查sysctl配置中的相关参数,如kernel.shmall、kernel.shmmax等,确保它们足够支持Oracle的内存需求。根据Oracle的官方文档,这些参数需要根据实际内存大小进行调整。 第五,Oracle的Bug或补丁问题也不能忽视。某些版本的Oracle可能存在已知的内存管理缺陷,需要查看对应的支持文档或MOS(My Oracle Support)是否有相关的补丁或解决方案。例如,引用[3]提到的Note: 1056713.1可能包含相关问题的解决方法,用户应该参考该文档或类似资源,确认是否需要应用特定的补丁或更新。 接下来,用户可能需要具体的操作步骤来解决这些问题。例如,如何检查当前内存使用情况,如何调整Oracle的内存参数,如何修改系统内核参数等。同时,应该提醒用户在进行这些操作前备份相关配置文件,避免误操作导致系统不稳定。此外,建议用户在修改参数后重启数据库实例或服务器,以验证问题是否解决。 最后,生成相关问题部分需要考虑用户可能进一步的问题,比如如何监控Oracle的内存使用,如何优化内存配置以提高性能,或者在集群环境中内存问题的特殊处理方法。这些问题能够帮助用户更全面地理解和解决内存相关的故障。</think>### Oracle数据库无法检查可用内存的解决方案 当Oracle数据库出现无法检查可用内存的错误时,通常由内存不足、参数配置错误或系统限制引起。以下是逐步排查和解决方法: --- #### **1. 检查系统内存资源** - **确认物理内存和交换空间** 使用命令 `free -m` 或 `top` 查看可用内存和交换空间。 - 若物理内存不足,需关闭非必要进程或增加物理内存。 - 若交换空间不足,可通过以下命令扩展(示例扩展至8GB): ```bash sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 sudo mkswap /swapfile sudo swapon /swapfile ``` 并在 `/etc/fstab` 中添加持久化配置。 - **验证Oracle内存需求** Oracle实例启动需要足够的内存支持SGA(系统全局区)和PGA(程序全局区)。参考官方文档确认当前版本的内存需求[^3]。 --- #### **2. 调整Oracle内存参数** - **修改初始化参数** 检查 `spfile` 或 `pfile` 中的以下参数: ```sql memory_target memory_max_target sga_target pga_aggregate_target ``` 若参数值过高,可通过以下方式临时调整: ```sql ALTER SYSTEM SET memory_target=2G SCOPE=spfile; ALTER SYSTEM SET sga_target=1G SCOPE=spfile; ``` 重启实例生效。 - **使用最小配置启动** 若无法正常启动,尝试通过最小参数文件启动: ```sql STARTUP NOMOUNT PFILE='/tmp/init_temp.ora'; ``` 示例 `init_temp.ora` 内容: ``` memory_target=1G sga_target=800M processes=150 ``` --- #### **3. 检查系统限制与权限** - **确认用户资源限制** 检查 `/etc/security/limits.conf` 中Oracle用户的配置: ``` oracle soft memlock unlimited oracle hard memlock unlimited oracle soft stack 10240 ``` 执行 `ulimit -a` 验证设置是否生效。 - **内核参数调整** 修改 `/etc/sysctl.conf`,确保以下参数足够(示例为16GB内存服务器): ``` kernel.shmall = 4194304 # 总共享内存页数 kernel.shmmax = 17179869184 # 最大单块共享内存(16GB) vm.min_free_kbytes = 51200 # 最小保留内存(50MB) ``` 执行 `sysctl -p` 生效。 --- #### **4. 排查Oracle Bug或补丁** - **查询MOS文档** 访问My Oracle Support,搜索类似错误如 *ORA-4031* 或 *Unable to allocate memory*,确认是否与已知Bug相关(例如Note: 1056713.1中提到的权限问题)。 - **应用补丁** 若确认是Bug,根据文档建议升级或打补丁。例如: ```bash opatch apply /path/to/patch ``` --- #### **5. 日志分析与诊断** - **检查告警日志** 查看 `$ORACLE_BASE/diag/rdbms/<DB_NAME>/<INSTANCE>/trace/alert_<INSTANCE>.log`,定位具体错误信息。 - **使用诊断工具** 运行 `ADRCI` 工具生成诊断包: ```bash adrci> show alert -tail 50 adrci> ips create package incident <incident_id> ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值