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

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

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

java中查看函数运行时间和cpu运行时间

android开发调查性能问题中有一个现象,函数的运行时间远低于cpu执行时间,因为函数运行期间线程可能包含等待操作。native层可以查看实际的cpu执行时间和函数执行时间。在java中如何实现? 借助AI得到了答案 import java.lang.management.ManagementFactory;import java.lang.management.Threa

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页:

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议