《操作系统真象还原》第八章——实现断言函数

2024-06-21 09:04

本文主要是介绍《操作系统真象还原》第八章——实现断言函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

代码文件结构

代码实现

interrupt.h

目的:在上节开中断的实现文件里添加开关中断的函数功能

原因:由于断言函数的目的是当错误发生时打印错误信息,因而此时不应该有其他中断信号来打扰,需要关闭中断

#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H
#include "stdint.h"
typedef void* intr_handler;		//将intr_handler定义为void*同类型
void idt_init(void);/*定义中断状态*/
enum intr_status
{INTR_OFF,//表示中断关闭INTR_ON  //表示中断打开
};
enum intr_status intr_get_status(void);
enum intr_status intr_set_status(enum intr_status);
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);
#endif

interrupt.c

#include "interrupt.h"      //里面定义了intr_handler类型
#include "stdint.h"         //各种uint_t类型
#include "global.h"         //里面定义了选择子
#include "io.h"             
#include "print.h"#define PIC_M_CTRL 0x20	      // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21	      // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0	      // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1	      // 从片的数据端口是0xa1#define IDT_DESC_CNT 0x21	   //支持的中断描述符个数33#define EFLAGS_IF 0x00000200     //表示eflags寄存器中的if位为1//将该宏与当前的if位(中断状态)进行与操作,即可获取当前的中断状态/*
功能:获取eflags寄存器中的值
pushfl将eflags寄存器中的值压入栈顶,再使用popl将栈顶的值弹出到EFLAG_VAR所在内存中
该约束自然用表示内存的字母,但是内联汇编中没有专门表示约束内存的字母,所以只能用g
代表可以是任意寄存器,内存或立即数
*/
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl;pop %0":"=g"(EFLAG_VAR))//定义中断描述符结构体
struct gate_desc {uint16_t    func_offset_low_word;        //函数地址低字uint16_t    selector;                    //选择子字段uint8_t     dcount;                      //此项为双字计数字段,是门描述符中的第4字节。这个字段无用uint8_t     attribute;                   //属性字段uint16_t    func_offset_high_word;       //函数地址高字
};//定义中断门描述符结构体数组,形成中断描述符表idt,该数组中的元素是中断描述符
static struct gate_desc idt[IDT_DESC_CNT];
//静态函数声明,该函数用于构建中断描述符,将上述定义的中断描述符结构体按照字段说明进行填充即可
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);//intr_handler为void*类型,intr_entry_table为中断处理程序数组,其内的元素是中断处理程序入口地址
extern intr_handler intr_entry_table[IDT_DESC_CNT];//存储中断/异常的名字
char* intr_name[IDT_DESC_CNT];    
//定义中断处理程序数组,在kernel.S中定义的intrXXentry只是中断处理程序的入口,最终调用的是ide_table中的处理程序           
intr_handler idt_table[IDT_DESC_CNT];/*初始化可编程中断控制器8259A*/
static void pic_init(void)
{/* 初始化主片 */outb (PIC_M_CTRL, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_M_DATA, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.outb (PIC_M_DATA, 0x04);   // ICW3: IR2接从片. outb (PIC_M_DATA, 0x01);   // ICW4: 8086模式, 正常EOI/* 初始化从片 */outb (PIC_S_CTRL, 0x11);	// ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_S_DATA, 0x28);	// ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.outb (PIC_S_DATA, 0x02);	// ICW3: 设置从片连接到主片的IR2引脚outb (PIC_S_DATA, 0x01);	// ICW4: 8086模式, 正常EOI/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */outb (PIC_M_DATA, 0xfe);outb (PIC_S_DATA, 0xff);put_str("pic_init done\n");
}/*
函数功能:构建中断描述符
函数实现:按照中断描述符结构体定义填充字段
参数:中断门描述符地址,属性,中断处理函数
*/
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) 
{ p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;p_gdesc->selector = SELECTOR_K_CODE;p_gdesc->dcount = 0;p_gdesc->attribute = attr;p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}/*
函数功能:中断描述符表idt
函数实现:循环调用make_idt_desc构建中断描述符,形成中断描述符表idt
参数:中断描述符表中的某个中断描述符地址,属性字段,中断处理函数地址
*/
static void idt_desc_init(void) {int i;for (i = 0; i < IDT_DESC_CNT; i++) {make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); }put_str("idt_desc_init done\n");
}#if 1
/*用c语言定义中断处理函数的具体实现,不再用汇编语言在kernel.S中定义中断处理函数的实现*/
//参数:中断向量号
static void general_intr_handler(uint8_t vec_nr)
{//伪中断向量,无需处理if(vec_nr==0x27 || vec_nr==0x2f){return;}put_str("int vector:0x");put_int(vec_nr);put_char('\n');
}
#endif/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void) {			   // 完成一般中断处理函数注册及异常名称注册int i;for (i = 0; i < IDT_DESC_CNT; i++) {/* idt_table数组中的函数是在进入中断后根据中断向量号调用的,* 见kernel/kernel.S的call [idt_table + %1*4] */idt_table[i] = general_intr_handler;   // 默认为general_intr_handler。// 以后会由register_handler来注册具体处理函数。intr_name[i] = "unknown";				   // 先统一赋值为unknown }intr_name[0] = "#DE Divide Error";intr_name[1] = "#DB Debug Exception";intr_name[2] = "NMI Interrupt";intr_name[3] = "#BP Breakpoint Exception";intr_name[4] = "#OF Overflow Exception";intr_name[5] = "#BR BOUND Range Exceeded Exception";intr_name[6] = "#UD Invalid Opcode Exception";intr_name[7] = "#NM Device Not Available Exception";intr_name[8] = "#DF Double Fault Exception";intr_name[9] = "Coprocessor Segment Overrun";intr_name[10] = "#TS Invalid TSS Exception";intr_name[11] = "#NP Segment Not Present";intr_name[12] = "#SS Stack Fault Exception";intr_name[13] = "#GP General Protection Exception";intr_name[14] = "#PF Page-Fault Exception";// intr_name[15] 第15项是intel保留项,未使用intr_name[16] = "#MF x87 FPU Floating-Point Error";intr_name[17] = "#AC Alignment Check Exception";intr_name[18] = "#MC Machine-Check Exception";intr_name[19] = "#XF SIMD Floating-Point Exception";}/*获取当前中断状态*/
enum intr_status intr_get_status(void)
{uint32_t eflags=0;//获取eflags寄存器中的值,存到eflagsGET_EFLAGS(eflags);return (eflags & EFLAGS_IF)?INTR_ON:INTR_OFF;
}/*开中断并返回中断前的状态*/
enum intr_status intr_enable(void)
{enum intr_status old_status;if(intr_get_status()==INTR_ON){old_status=INTR_ON;return old_status;}else{old_status=INTR_OFF;asm volatile("sti");//开中断,sti命令将if位置为1return old_status;}
}/*关中断,并返回关中断前的状态*/
enum intr_status intr_disable(void)
{enum intr_status old_status;if(intr_get_status()==INTR_ON){old_status=INTR_ON;asm volatile("cli":::"memory");//关中断,cli命令将if位置为0return old_status;}else{old_status=INTR_OFF;return old_status;}
}/*设置中断状态*/
enum intr_status intr_set_status(enum intr_status status)
{return (status & INTR_ON)?intr_enable():intr_disable();
}/*完成有关中断的所有初始化工作*/
void idt_init(void) {put_str("idt_init start\n");idt_desc_init();	                        //完成中段描述符表的构建pic_init();		                           //设定化中断控制器,只接受来自时钟中断的信号exception_init();/* 加载idt */uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));    //定义要加载到IDTR寄存器中的值asm volatile("lidt %0" : : "m" (idt_operand));put_str("idt_init done\n");
}

debug.h

以下是断言函数的实现代码

#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_Hvoid panic_spin(char* filename,int line,const char* func,const char* condition);
#define PANIC(...) panic_spin(__FILE__,__LINE__,__func__,__VA_ARGS__)/*
如果定义了NDEBUG,那么下面定义的ASSERT就是个空。
这样我们可以便捷的让所有ASSERT宏失效。
因为有时候断言太多,程序会运行很慢。
我们如果不想要ASSERT起作用,编译时用gcc-DNDEBUG就行了
*/
#ifdef NDEBUG#define ASSERT(CONDITION) ((void)0)
#else
#define ASSERT(CONDITION) \if(CONDITION){} \else {PANIC(#CONDITION);}//加#后,传入的参数变成字符串
#endif//#ifdef NDEBUG#endif//#define __KERNEL_DEBUG_H

debug,c

#include "debug.h"
#include "print.h"
#include "interrupt.h"/* 打印文件名,行号,函数名,条件并使程序悬停 */
void panic_spin(char* filename,int line,const char* func,const char* condition)
{intr_disable();//发生错误时打印错误信息,不应该被打扰,因此关闭中断put_str("\n\n\n!!!!!error!!!!!\n");put_str("filename:");put_str(filename);put_str("\n");put_str("line:0x");put_int(line);put_str("\n");put_str("function:");put_str((char*)func);put_str("\n");put_str("condition:");put_str((char*)condition);put_str("\n");while(1);
}

print.h

这里在原来实现打印单个字符和打印字符串函数的基础上增加了打印整数的函数

#ifndef __LIB_KERNEL_PRINT_H
#define __LIB_KERNEL_PRINT_H
#include "stdint.h"                 //我们的stdint.h中定义了数据类型,包含进来
void put_char(uint8_t char_asci);   //在stdint.h中uint8_t得到了定义,就是unsigned char
void put_str(char* message);
void put_int(uint32_t num);	        // 以16进制打印
#endif

print.S

TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0;定义显存段选择子section .data
put_int_buffer dq 0                                         ; 定义8字节缓冲区用于数字到字符的转换;--------------------   字符串打印函数   -----------------------
[bits 32]
section .text
global put_strput_str:push ebxpush ecxxor ecx,ecxmov ebx,[esp+12]                ;ebx存放字符串首地址
.goon:mov cl,[ebx]                    ;取出字符串的第一个字符,存放到cx寄存器中cmp cl,0                        ;判断是否到了字符串结尾,字符串结尾标志符'\0'即0jz .str_overpush ecx                        ;压入put_char参数,调用put_char函数call put_charadd esp,4                       ;回收栈空间inc ebx                         ;指向字符串的下一个字符jmp .goon
.str_over:pop ecx                         ;回收栈空间pop ebx                         ;回收栈空间ret;--------------------   单字符打印函数   -----------------------
[bits 32]
section .text
global put_char                     ;通过global关键字将put_char函数定义为全局符号;使其对外部文件可见
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
put_char:pushad                          ;push all double,压入所有双字长的寄存器;入栈顺序为eax->ecx->edx->ebx->esp->ebp->esi->edimov ax,SELECTOR_VIDEOmov gs,ax                       ;为gs寄存器赋予显存段的选择子;以下代码用于获取光标的坐标位置,光标的坐标位置存放在光标坐标寄存器中
;其中索引为0eh的寄存器和索引为0fh的寄存器分别存放光标高8位和低8位
;访问CRT controller寄存器组的寄存器,需要先往端口地址为0x03d4的寄存器写入索引
;从端口地址为0x03d5的数据寄存器中读写数据mov dx,0x03d4                   ;将0x03d4的端口写入dx寄存器中mov al,0x0e                     ;将需要的索引值写入al寄存器中out dx,al                       ;向0x03d4端口写入0x0e索引mov dx,0x03d5                   in al,dx                        ;从0x03d5端口处获取光标高8位mov ah,al                       ;ax寄存器用于存放光标坐标,;因此将光标坐标的高8位数据存放到ah中;同上,以下代码获取光标坐标的低8位mov dx,0x03d4mov al,0x0fout dx,almov dx,0x03d5in al,dx                        ;此时ax中就存放着读取到的光标坐标值mov bx,ax                       ;bx寄存器不仅是光标坐标值,同时也是下一个可打印字符的位置;而我们习惯于bx作为基址寄存器,以后的处理都要基于bx寄存器;因此才将获取到的光标坐标值赋值为bxmov ecx,[esp+36]                ;前边已经压入了8个双字(2个字节)的寄存器,;加上put_char函数的4字节返回地址;所以待打印的字符在栈顶偏移36字节的位置cmp cl,0xd                      ;回车符处理jz .is_carriage_returncmp cl,0xa                      ;换行符处理jz .is_line_feedcmp cl,0x8                      ;退格键处理jz .is_backspace                jmp .put_other                  ;正常字符处理;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;退格键处理;处理思路:;1.将光标位置减一;2.将待删除的字符使用空格字符(ASCII:0x20)代替
.is_backspace:dec bx                          ;bx中存储光标的坐标位置,将光标坐标位置减去一,即模拟退格shl bx,1                        ;由于文本模式下一个字符占用两个字节(第一个字节表示字符的ASCII码,第二个字节表示字符的属性),;故光标位置乘以2就是光标处字符的第一个字节的偏移量mov byte[gs:bx],0x20            ;将空格键存入待删除字符处inc bx                          ;此时bx中存储的是字待删除字符的第一个字节位置,;使用inc指令将bx加1后就是该字符的第二个字节的位置mov byte[gs:bx],0x07            ;将黑底白字(0x07)属性加入到该字符处shr bx,1                        ;bx除以2,恢复光标坐标位置jmp .set_cursor                 ;去设置光标位置, 这样光标位置才能真正在视觉上更新;将cx指向的字符放入到光标处
.put_other:shl bx,1                        ;将光标坐标转换为内存偏移量mov byte[gs:bx],cl              ;将cx指向的字符放入到光标处inc bx                          ;bx指向当前字符的下一个字节处,存放当前字符属性mov byte[gs:bx],0x07            ;存放字符属性shr bx,1                        ;将内存偏移量恢复为光标坐标值inc bx                          ;bx指向下一个待写入字符位置cmp bx,2000                     ;80*25=2000,判断是否字符已经写满屏了jl .set_cursor                  ;更新光标坐标值;换行处理;思路:首先将光标移动到本行行首,之后再将光标移动到下一行行首
.is_line_feed:
.is_carriage_return:xor dx,dx;将光标移动到本行行首mov ax,bxmov si,80div si                          ;除法操作,ax/si,结果ax存储商,dx存储余数sub bx,dx.is_carriage_return_end:add bx,80                   ;将光标移动到下一行行首cmp bx,2000.is_line_feed_end:jl .set_cursor;滚屏处理;思路:屏幕行范围是0~24,滚屏的原理是将屏幕的1~24行搬运到0~23行,再将第24行用空格填充
.roll_screen:cld                             ;清除方向标志位,字符串的内存移动地址从低地址向高地址移动;若方向标志位被设置,则字符串的内存移动地址从高地址向低地址移动mov ecx,960                     ;共移动2000-80=192个字符,每个字符占2个字节,故共需移动1920*2=3840个字节;movsd指令每次移动4个字节,故共需执行该指令3840/4=960次数mov esi,0xb80a0                 ;第一行行首地址,要复制的起始地址mov edi,0xb8000                 ;第0行行首地址,要复制的目的地址rep movsd                       ;rep(repeat)指令,重复执行movsd指令,执行的次数在ecx寄存器中;将最后一行填充为空白mov ebx,3840mov ecx,80.cls:mov word[gs:ebx],0x0720add ebx,2loop .clsmov bx,1920                 ;将光标值重置为1920,最后一行的首字符..set_cursor:;将光标设为bx值;;;;;;; 1 先设置高8位 ;;;;;;;;mov dx, 0x03d4			                                ;索引寄存器mov al, 0x0e				                            ;用于提供光标位置的高8位out dx, almov dx, 0x03d5			                                ;通过读写数据端口0x3d5来获得或设置光标位置 mov al, bhout dx, al;;;;;;; 2 再设置低8位 ;;;;;;;;;mov dx, 0x03d4mov al, 0x0fout dx, almov dx, 0x03d5 mov al, blout dx, al.put_char_done:popadret;--------------------   将小端字节序的数字变成对应的ascii后,倒置   -----------------------
;输入:栈中参数为待打印的数字
;输出:在屏幕上打印16进制数字,并不会打印前缀0x,如打印10进制15时,只会直接打印f,不会是0xf
;------------------------------------------------------------------------------------------
global put_int
put_int:pushadmov ebp, espmov eax, [ebp+4*9]		                                ; call的返回地址占4字节+pushad的8个4字节,现在eax中就是要显示的32位数值mov edx, eax                                             ;ebx中现在是要显示的32位数值mov edi, 7                                               ; 指定在put_int_buffer中初始的偏移量,也就是把栈中第一个字节取出放入buffer最后一个位置,第二个字节放入buff倒数第二个位置mov ecx, 8			                                    ; 32位数字中,16进制数字的位数是8个mov ebx, put_int_buffer                                  ;ebx现在存储的是buffer的起始地址;将32位数字按照16进制的形式从低位到高位逐个处理,共处理8个16进制数字
.16based_4bits:			                                    ; 每4位二进制是16进制数字的1位,遍历每一位16进制数字and edx, 0x0000000F		                                ; 解析16进制数字的每一位。and与操作后,edx只有低4位有效cmp edx, 9			                                    ; 数字0~9和a~f需要分别处理成对应的字符jg .is_A2F add edx, '0'			                                    ; ascii码是8位大小。add求和操作后,edx低8位有效。jmp .store
.is_A2F:sub edx, 10			                                    ; A~F 减去10 所得到的差,再加上字符A的ascii码,便是A~F对应的ascii码add edx, 'A';将每一位数字转换成对应的字符后,按照类似“大端”的顺序存储到缓冲区put_int_buffer;高位字符放在低地址,低位字符要放在高地址,这样和大端字节序类似,只不过咱们这里是字符序.
.store:mov [ebx+edi], dl		                                ; 此时dl中是数字对应的字符的ascii码dec edi                                                  ;edi是表示在buffer中存储的偏移,现在向前移动shr eax, 4                                               ;eax中是完整存储了这个32位数值,现在右移4位,处理下一个4位二进制表示的16进制数字mov edx, eax                                             ;把eax中的值送入edx,让ebx去处理loop .16based_4bits;现在put_int_buffer中已全是字符,打印之前,;把高位连续的字符去掉,比如把字符00000123变成123
.ready_to_print:inc edi			                                        ; 此时edi退减为-1(0xffffffff),加1使其为0
.skip_prefix_0:                                             ;跳过前缀的连续多个0cmp edi,8			                                    ; 若已经比较第9个字符了,表示待打印的字符串为全0 je .full0 ;找出连续的0字符, edi做为非0的最高位字符的偏移
.go_on_skip:   mov cl, [put_int_buffer+edi]inc edicmp cl, '0' je .skip_prefix_0		                                ; 继续判断下一位字符是否为字符0(不是数字0)dec edi			                                        ;edi在上面的inc操作中指向了下一个字符,若当前字符不为'0',要恢复edi指向当前字符		       jmp .put_each_num.full0:mov cl,'0'			                                    ; 输入的数字为全0时,则只打印0
.put_each_num:push ecx			                                        ; 此时cl中为可打印的字符call put_charadd esp, 4inc edi			                                        ; 使edi指向下一个字符mov cl, [put_int_buffer+edi]	                            ; 获取下一个字符到cl寄存器cmp edi,8                                                ;当edi=8时,虽然不会去打印,但是实际上已经越界访问缓冲区了jl .put_each_numpopadret

main.c

为了测试断言函数是否能正确输出错误信息,我们在主函数中添加以下代码

ASSERT(1==2);

很显然,1是不等于2的,因此如果断言函数能够正确执行,它应该报出这个错误

#include "print.h"
#include "init.h"
#include "debug.h"
int main(void)
{put_str("I am kernel\n");init_all();// asm volatile("sti"); // 为演示中断处理,在此临时开中断ASSERT(1==2);while(1);return 0;
}

 

编译运行

makefile

BUILD_DIR=./build
# 程序的入口地址
ENTRY_POINT=0xc0001500
HD60M_PATH=/home/minios/bochs/hd60M.img
AS=nasm
CC=gcc-4.4
LD=ld
LIB= -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/
ASFLAGS= -f elf# -Wall:warning all的意思,产生尽可能多警告信息
# -fno-builtin:不要采用内部函数
# -W:会显示警告,但是只显示编译器认为会出现错误的警告
# -Wstrict-prototypes 要求函数声明必须有参数类型,否则发出警告
# -Wmissing-prototypes 必须要有函数声明,否则发出警告
CFLAGS= -Wall $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -m32
#-Map,生成map文件,就是通过编译器编译之后,生成的程序、数据及IO空间信息的一种映射文件
#里面包含函数大小,入口地址等一些重要信息
LDFLAGS= -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map -m elf_i386OBJS=$(BUILD_DIR)/main.o $(BUILD_DIR)/init.o \$(BUILD_DIR)/interrupt.o $(BUILD_DIR)/kernel.o \$(BUILD_DIR)/print.o $(BUILD_DIR)/debug.o######################编译两个启动文件的代码#####################################
boot:$(BUILD_DIR)/mbr.o $(BUILD_DIR)/loader.o
$(BUILD_DIR)/mbr.o:boot/mbr.S$(AS) boot/mbr.S -o build/mbr.o -I boot/include/
$(BUILD_DIR)/loader.o:boot/loader.S$(AS) boot/loader.S -o build/loader.o -I boot/include/######################编译C内核代码#################################################### $@表示规则中目标文件名的集合这里就是$(BUILD_DIR)/main.o
# $<表示规则中依赖文件的第一个,这里就是kernle/main.c 
$(BUILD_DIR)/main.o:kernel/main.c$(CC) $(CFLAGS) -o $@ $<$(BUILD_DIR)/init.o:kernel/init.c$(CC) $(CFLAGS) -o $@ $<$(BUILD_DIR)/interrupt.o:kernel/interrupt.c$(CC) $(CFLAGS) -o $@ $<$(BUILD_DIR)/debug.o:kernel/debug.c$(CC) $(CFLAGS) -o $@ $<###################编译汇编内核代码#####################################################
$(BUILD_DIR)/kernel.o:kernel/kernel.S$(AS) $(ASFLAGS) -o$@ $<
$(BUILD_DIR)/print.o:lib/kernel/print.S$(AS) $(ASFLAGS) -o$@ $<##################链接所有内核目标文件##################################################
# $^表示规则中所有依赖文件的集合,如果有重复,会自动去重
$(BUILD_DIR)/kernel.bin:$(OBJS)$(LD) $(LDFLAGS) -o $@ $^.PHONY:mk_dir hd clean build all boot	#定义了6个伪目标
mk_dir:
#判断build文件夹是否存在,如果不存在,则创建if [ ! -d $(BUILD_DIR) ];then mkdir $(BUILD_DIR);fi 
hd:dd if=build/mbr.o of=$(HD60M_PATH) count=1 bs=512 conv=notrunc && \dd if=build/loader.o of=$(HD60M_PATH) count=4 bs=512 seek=2 conv=notrunc && \dd if=$(BUILD_DIR)/kernel.bin of=$(HD60M_PATH) bs=512 count=200 seek=9 conv=notrunc#-f, --force忽略不存在的文件,从不给出提示,执行make clean就会删除build下所有文件
clean:@cd $(BUILD_DIR) && rm -f ./* && echo "remove ./build all done"#执行build需要依赖kernel.bin,但是一开始没有,就会递归执行之前写好的语句编译kernel.bin
build:$(BUILD_DIR)/kernel.bin#make all 就是依次执行mk_dir build hd
all:mk_dir boot build hd

在makefile文件所在的位置处执行

make all

屏幕会打印编译信息 

if [ ! -d ./build ];then mkdir ./build;fi
nasm boot/mbr.S -o build/mbr.o -I boot/include/
nasm boot/loader.S -o build/loader.o -I boot/include/
gcc-4.4 -Wall -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -m32 -o build/main.o kernel/main.c
gcc-4.4 -Wall -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -m32 -o build/init.o kernel/init.c
gcc-4.4 -Wall -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -m32 -o build/interrupt.o kernel/interrupt.c
nasm -f elf -obuild/kernel.o kernel/kernel.S
nasm -f elf -obuild/print.o lib/kernel/print.S
gcc-4.4 -Wall -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -m32 -o build/debug.o kernel/debug.c
ld -Ttext 0xc0001500 -e main -Map ./build/kernel.map -m elf_i386 -o build/kernel.bin build/main.o build/init.o build/interrupt.o build/kernel.o build/print.o build/debug.o
dd if=build/mbr.o of=/home/minios/bochs/hd60M.img count=1 bs=512 conv=notrunc && \
dd if=build/loader.o of=/home/minios/bochs/hd60M.img count=4 bs=512 seek=2 conv=notrunc && \
dd if=./build/kernel.bin of=/home/minios/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc
1+0 records in
1+0 records out
512 bytes copied, 0.000430623 s, 1.2 MB/s
2+1 records in
2+1 records out
1290 bytes (1.3 kB, 1.3 KiB) copied, 0.00081601 s, 1.6 MB/s
15+1 records in
15+1 records out
7848 bytes (7.8 kB, 7.7 KiB) copied, 0.00122429 s, 6.4 MB/s

 如果有警告,则进行修改即可

如笔者曾遇到下述警告,原因是函数声明没有参数列表的类型信息

 warning: function declaration isn’t a prototype

假设你有一个函数声明如下面这样:

int myFunction();

这种声明方式可能会导致编译器发出警告,因为它没有指定参数类型。正确的做法是明确指出参数类型,即使它没有参数,也要使用 void

int myFunction(void);

运行

 ./bin/bochs -f boot.disk

这篇关于《操作系统真象还原》第八章——实现断言函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080782

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现