导入系统
回顾一下我们昨天写好的代码(建议复制后更新一下,确保我们的出发点一致):
; TAB=4 CLYS EQU 10 ORG 0x7c00 ; 指明程序装载地址 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code JMP entry DB 0x90 DB "HELLOIPL" ; 启动扇区名称(8字节) DW 512 ; 每个扇区(sector)大小(必须512字节) DB 1 ; 簇(cluster)大小(必须为1个扇区) DW 1 ; FAT起始位置(一般为第一个扇区) DB 2 ; FAT个数(必须为2) DW 224 ; 根目录大小(一般为224项) DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) DB 0xf0 ; 磁盘类型(必须为0xf0) DW 9 ; FAT的长度(必??9扇区) DW 18 ; 一个磁道(track)有几个扇区(必须为18) DW 2 ; 磁头数(必??2) DD 0 ; 不使用分区,必须是0 DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明(固定) DD 0xffffffff ; (可能是)卷标号码 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) RESB 18 ; 先空出18字节 ; 程序主体 entry: MOV AX,0 ; 初始化寄存器 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV AX, 0x0820 MOV ES, AX MOV CH, 0 ; 0号柱面 MOV DH, 0 ; 0号磁头(正面) MOV CL, 2 ; 2号扇区 readloop: MOV SI, 0 ; 记录失败次数的寄存器(本来应该用CX,但是CL和CH已经被占用了) retry: MOV AH, 0x02 MOV AL, 1 MOV BX, 0 MOV DL, 0x00 INT 0x13 JNC next ADD SI, 1 ; jnc没有执行,则说明有错误发生了! CMP SI, 5 JAE error ; 如果si中的错误次数大于等于5,则跳转到error代码段 MOV AH, 0x00 ; 重置 MOV DL, 0x00 ; 重置 INT 0x13 JMP retry ; 循环 next: MOV AX,ES ; 把内存地址后移0x200(512/16十六进制转换) ADD AX,0x0020 MOV ES,AX ; ADD ES,0x020因为没有ADD ES,只能通过AX进行 ADD CL, 1 CMP CL, 18 JBE readloop MOV CL, 1 ADD DH, 1 CMP DH, 2 JB readloop MOV DH, 0 ADD CH, 1 CMP CH, 10 JBE readloop JMP ok fin: HLT ; 让CPU停止,等待指令 JMP fin ; 无限循环 ok: MOV SI, ok_msg JMP putloop error: MOV SI, err_msg JMP putloop putloop: MOV AL, [SI] ADD SI, 1 CMP AL, 0 JE fin MOV AH, 0x0e MOV BX, 15 INT 0x10 JMP putloop ok_msg: DB "Reading 10 cylinders finished! Congratulations!", 0xd, 0xa, 0 err_msg: DB "An error happened! Please check your code.", 0xd, 0xa, 0
这一程序会读出软盘上10个柱面、每个柱面18个扇区的信息。在正式开始今天的内容前,我们先来解决一个疑问。
为什么一开始用于指定扇区编号的CL寄存器被设置成了2?
首先我们先回忆,每个柱面一共有18个扇区,都从1开始编号。每个扇区大小均为512个字节,其二进制表示为0x200,恰好是每次ES的增量的16倍(0x020乘以十进制的16等于0x200)。那么为什么一开始不从第一个扇区开始读呢?因为第一个扇区是留给启动区的,从第0号柱面的第2个开始才是真正的数据。至于为什么ES每次只增加0x20而不是0x200,这是因为我们访问内存时满足的格式是[ES:BX],即真正访问的地址是ES × 16 + BX。注意到基址寄存器BX作为16位寄存器,访问的空间极为有限,而当时我们又不能升级BX为EBX(32位寄存器),因此只能人为添加一个寄存器作为辅助,从而大大提升了可访问的内存空间。
完成了引导区的编写,我们需要写一个被引导的操作系统。
; 操作系统本体 MOV SI, msg putloop: MOV AL, [SI] ADD SI, 1 CMP AL, 0 JE fin MOV AH, 0x0e MOV BX, 15 INT 0x10 JMP putloop fin: HLT JMP fin msg: DB "Operating system is loaded.", 0
将该文件保存为os.nas
,随后在命令行中执行以下指令:
..\tolset\z_tools\nask.exe ipl.nas ipl.bin ..\tolset\z_tools\nask.exe os.nas os.sys ..\tolset\z_tools\edimg.exe imgin:..\tolset\z_tools\fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 copy from:os.sys to:@: imgout:os.img
注意,我们此时直接编译得到的不再是.img
文件,而是ipl.bin
和os.sys
文件,只有在得到二者后再合并后,才能得到最终的镜像文件os.img
。下面用Visual Studio Code打开os.img,选择十六进制编辑器。
我们可以看到一些系统的信息,正是我们之前在FAT32软盘引导代码中填写的部分。下拉到0x2600位置,可以发现这里赫然储存着我们的文件名。
继续下拉到0x4200位置,可以发现这里储存着我们的文件内容。
我们现在的思路是执行ipl.nas,通过ipl.nas将软盘中的数据读取到内存中,之后跳转到内存的相应位置开始执行操作系统文件。
但先别急着执行os.img
,因为我们的ipl.nas目前还没有跳转到内存指定位置!我们按以下方式修改ipl.nas中的fin代码段:
fin: HLT ; 让CPU停止,等待指令 MOV [0x0ff0], CH ; 保存读取柱面的数量 JNC 0xc200 ; 若无异常则跳转到系统起始位置 JMP fin ; 无限循环
等等,为什么是0xc200而不是0x4200?!这是因为我们本来是从0x8000开始储存读取到的软盘数据的,如果系统文件原先在软盘的0x4200,那么此时在内存中对应为0x8000+0x4200=0xc200。重新编译后,得到新的os.img
。注意,我们此时的编译方法相较于以前更复杂了。
..\tolset\z_tools\nask.exe ipl.nas ipl.bin ..\tolset\z_tools\nask.exe os.nas os.sys ..\tolset\z_tools\edimg.exe imgin:..\tolset\z_tools\fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 copy from:os.sys to:@: imgout:os.img
运行新的镜像文件,我们会得到奇怪的输出。这是怎么回事?!查阅原书我们发现在操作系统文件os.nas
的开头缺少了一条重要的语句:ORG 0xc200
。如果我们没有指定程序起始地点的话,就会默认从0开始装载程序,此时代码和信息对应的内存就会以0为起点翻译成机器语言。具体举例来说,此时msg可能被编译器翻译成了在0x100的位置,但是实际上它被读取到了0c300的位置,因此操作系统无法读取正确的程序。
修改后的os.nas
文件如下:
; 操作系统本体 ORG 0xc200 MOV SI, msg putloop: MOV AL, [SI] ADD SI, 1 CMP AL, 0 JE fin MOV AH, 0x0e MOV BX, 15 INT 0x10 JMP putloop fin: HLT JMP fin msg: DB "Operating system is loaded.", 0xd, 0xa 0
编译、合并、运行!大功告成!
接下来我们要准备从16位模式进入32位模式,此时可使用的内存大大增加,但是汇编语言翻译的结果也发生了变化,而且也不能再使用BIOS。为此,我们对os.nas
稍作修改并更名为asmhead.nas
。
; 操作系统本体 CYLS EQU 10 ; 读取范围 LEDS EQU 0x0ff1 ; 键盘灯信息 VMODE EQU 0x0ff2 ; 关于颜色数目的信息、颜色的位数 SCRNX EQU 0x0ff4 ; 分辨率的X SCRNY EQU 0x0ff6 ; 分辨率的Y VRAM EQU 0x0ff8 ; 图像缓冲期的开始地址 ORG 0xc200 MOV AL, 0x13 ; VGA 显卡, 320×200×8位颜色 MOV AH, 0x00 INT 0x10 MOV BYTE [VMODE], 8 ; 记录画面模式 MOV WORD [SCRNX], 320 MOV WORD [SCRNY], 200 MOV DWORD [VRAM], 0x000a0000 MOV SI, msg putloop: MOV AL, [SI] ADD SI, 1 CMP AL, 0 JE fin MOV AH, 0x0e MOV BX, 15 INT 0x10 JMP putloop fin: HLT JMP fin msg: DB "VRAM loaded successfully.", 0xd, 0xa, 0
其中VRAM是视频内存(video RAM)的简称,在INT 0x10
模式下,VRAM被分配在0xa0000
到0xaffff
之间64KB大小的空间。我们人为规定此时电脑屏幕支持的分辨率为320×200×8
。
按下面命令编译合并。
..\tolset\z_tools\nask.exe ipl.nas ipl.bin ..\tolset\z_tools\nask.exe asmhead.nas asmhead.sys ..\tolset\z_tools\edimg.exe imgin:..\tolset\z_tools\fdimg0at.tek wbinimg src:ipl.bin len:512 from:0 to:0 copy from:asmhead.sys to:@: imgout:os.img
运行后发现输出的字符样式发生了改变!我们刷新了显存,并重新输出了信息!
引入C语言
新建文件bootpack.c
,之所以取名为此是因为这只是C语言编写的引导性文件,还没涉及到系统本身。和先前一样,我们还是可以用引导文件完成一些看似是“操作系统”的任务。
void HariMain(void) { fin: goto fin; }
注意⚠️:一定要在最后留一个空行,否则编译不会通过!这是GCC编译器的硬性要求。这段代码和我们先前编写的汇编操作系统完全一样,只是在不停地循环!但最大的区别在于,我们无法直接使用HLT!此外,还需要注意,我们定义主函数就是HariMain,这个名字不能修改!
32位模式,启动!
下面的内容和原书有些出入,因为原书作者将增加的汇编代码留到了第八天才讲,相当于买了个关子,而多出来的100多行代码是我们进入32位模式的关键,接下来我们跳转到原书第八天的最后一节,彻底攻克32位模式汇编代码。
点击查看完整代码
; haribote-os boot asm ; TAB=4 BOTPAK EQU 0x00280000 ; 加载bootpack DSKCAC EQU 0x00100000 ; 磁盘缓存的位置 DSKCAC0 EQU 0x00008000 ; 磁盘缓存的位置(实模式) ; BOOT_INFO相关 CYLS EQU 0x0ff0 ; 引导扇区设置 LEDS EQU 0x0ff1 VMODE EQU 0x0ff2 ; 关于颜色的信息 SCRNX EQU 0x0ff4 ; 分辨率X SCRNY EQU 0x0ff6 ; 分辨率Y VRAM EQU 0x0ff8 ; 图像缓冲区的起始地址 ORG 0xc200 ; 程序要被装载的内存地址 ; 设置显示模式 MOV AL,0x13 ; VGA显卡,320x200x8位 MOV AH,0x00 INT 0x10 MOV BYTE [VMODE],8 ; 显示模式 MOV WORD [SCRNX],320 MOV WORD [SCRNY],200 MOV DWORD [VRAM],0x000a0000 ; 通过BIOS获取指示灯状态 MOV AH,0x02 INT 0x16 ; keyboard BIOS MOV [LEDS],AL ; 防止PIC接受所有中断 MOV AL,0xff OUT 0x21,AL NOP ; 部分机型不支持连续执行OUT指令 OUT 0xa1,AL CLI ; 进一步中断CPU ; 让CPU支持1M以上内存、设置A20GATE CALL waitkbdout MOV AL,0xd1 OUT 0x64,AL CALL waitkbdout MOV AL,0xdf ; enable A20 OUT 0x60,AL CALL waitkbdout ; 保护模式转换 [INSTRSET "i486p"] ; 说明使用486指令 LGDT [GDTR0] ; 设置临时GDT MOV EAX,CR0 AND EAX,0x7fffffff ; 使用bit31(禁用分页) OR EAX,0x00000001 ; bit0到1转换(保护模式过渡) MOV CR0,EAX JMP pipelineflush pipelineflush: MOV AX,1*8 ; 写32bit的段 MOV DS,AX MOV ES,AX MOV FS,AX MOV GS,AX MOV SS,AX ; bootpack传递 MOV ESI,bootpack ; 源 MOV EDI,BOTPAK ; 目标 MOV ECX,512*1024/4 CALL memcpy ; 传输磁盘数据 ; 从引导区开始 MOV ESI,0x7c00 ; 源 MOV EDI,DSKCAC ; 目标 MOV ECX,512/4 CALL memcpy ; 剩余的全部 MOV ESI,DSKCAC0+512 ; 源 MOV EDI,DSKCAC+512 ; 目标 MOV ECX,0 MOV CL,BYTE [CYLS] IMUL ECX,512*18*2/4 ; 除以4得到字节数 SUB ECX,512/4 ; IPL偏移量 CALL memcpy ; 由于还需要asmhead才能完成 ; 其余的bootpack任务 ; 启动bootpack MOV EBX,BOTPAK MOV ECX,[EBX+16] ADD ECX,3 ; ECX += 3; SHR ECX,2 ; ECX /= 4; JZ skip ; 传输完成 MOV ESI,[EBX+20] ; 源 ADD ESI,EBX MOV EDI,[EBX+12] ; 目标 CALL memcpy skip: MOV ESP,[EBX+12] ; 堆栈的初始化 JMP DWORD 2*8:0x0000001b waitkbdout: IN AL,0x64 AND AL,0x02 IN AL, 0x60 ; 空读,清空缓冲区内原有数据 JNZ waitkbdout ; AND结果不为0跳转到waitkbdout RET memcpy: MOV EAX,[ESI] ADD ESI,4 MOV [EDI],EAX ADD EDI,4 SUB ECX,1 JNZ memcpy ; 运算结果不为0跳转到memcpy RET ; memcpy地址前缀大小 ALIGNB 16 GDT0: RESB 8 ; 初始值 DW 0xffff,0x0000,0x9200,0x00cf ; 写32bit位段寄存器 DW 0xffff,0x0000,0x9a28,0x0047 ; 可执行的文件的32bit寄存器(bootpack用) DW 0 GDTR0: DW 8*3-1 DD GDT0 ALIGNB 16 bootpack:
BOTPAK EQU 0x00280000 ; 加载bootpack DSKCAC EQU 0x00100000 ; 磁盘缓存的位置 DSKCAC0 EQU 0x00008000 ; 磁盘缓存的位置(实模式)
理解这三个数需要用到原书作者设计的系统分布图(当然了,你也可以设计自己的)。
; 防止PIC接受所有中断 MOV AL,0xff OUT 0x21,AL NOP ; 部分机型不支持连续执行OUT指令 OUT 0xa1,AL CLI ; 进一步中断CPU
这一步是阻断所有中断,先是来自主PIC(可编程中断控制器)的中断,其次是来自副PIC的中断,最后终止CPU级别的中断。为什么要阻断这些中断?因为我们正在进行模式转换,不能让CPU在模式转换过程中停下来去做任何事。因此,这部分是死代码,不容更改。
; 让CPU支持1M以上内存、设置A20GATE CALL waitkbdout MOV AL,0xd1 OUT 0x64,AL CALL waitkbdout MOV AL,0xdf ; enable A20 OUT 0x60,AL CALL waitkbdout ;---分隔线--- waitkbdout: IN AL,0x64 AND AL,0x02 IN AL, 0x60 ; 空读,清空缓冲区内原有数据 JNZ waitkbdout ; AND结果不为0跳转到waitkbdout RET
这段代码的目的是初始化键盘并启动A20GATE信号线。为什么要启动这条信号线呢?你可以把它当作《魁拔》里的脉门或者《火影》里的八门遁甲,只有启动了这条线,CPU才可以访问1MB以上的内存,分别是32位模式的代价和优点。
但这段代码还有用到了一个waitkbdout
函数。它的功能是等待键盘输入完成,IN AL, 0x64
的意思是将0x64号设备(即键盘)的数据读入到AL寄存器当中,恰好是一个字节!
; 保护模式转换 [INSTRSET "i486p"] ; 说明使用486指令 LGDT [GDTR0] ; 设置临时GDT MOV EAX,CR0 AND EAX,0x7fffffff ; 使用bit31(禁用分页) OR EAX,0x00000001 ; bit0到1转换(保护模式过渡) MOV CR0,EAX JMP pipelineflush pipelineflush: MOV AX,1*8 ; 写32bit的段 MOV DS,AX MOV ES,AX MOV FS,AX MOV GS,AX MOV SS,AX
这段代码开始我们正式切换到32位保护模式。首先,程序读取了一个全局描述符表(global descriptor table, GDT)。这个表是32位保护模式的关键。进入32位模式,相当于我们利用八门遁甲彻底打开了查克拉的限制,我们可以操纵极为庞大的内存空间。但是为了兼容先前16位模式,我们在32位模式中沿用了“段”的思想。我们称先前的模式为实模式(real mode),眼下的模式为保护模式(protected mode)。实模式操纵的是实际地址,可以理解为小农村里用人名代替住所地址。而保护模式为了防止内存交叉,需要根据全局描述符表(GDT)进行寻址,比较像小区里单元楼-楼层-房间号的寻址方法。眼下,我们只是读取了一个随意准备的GDT,日后还会详细设计。
接下来我们把CR0,即0号控制寄存器(control register 0)的数据读取到EAX,再将最高位设置为0,最低位设置为1,最后把处理后的值代入回CR0中。如此一来,就完成了模式转换,进入到了不用分页的保护模式。该模式下,普通的应用程序不能修改既定的GDT(大胆住户竟敢肆意修改单元号、楼层和房间号?),操作系统专用的段就得到了CPU充分的保护。
通过代入CR0而切换保护模式时,一定要立即执行JMP指令。这是因为在模式改变后,机器语言的解释就会发生变化。形象的理解方式是16位模式一次处理长度为16个字节的指令,而32位一次处理32个字节,这当然会发生歧义!
; bootpack传输 MOV ESI,bootpack ; 源 MOV EDI,BOTPAK ; 目标 MOV ECX,512*1024/4 CALL memcpy ; 从引导区开始 MOV ESI,0x7c00 ; 源 MOV EDI,DSKCAC ; 目标 MOV ECX,512/4 CALL memcpy ; 接下来是剩余数据 MOV ESI,DSKCAC0+512 ; 源 MOV EDI,DSKCAC+512 ; 目标 MOV ECX,0 MOV CL,BYTE [CYLS] IMUL ECX,512*18*2/4 ; 除以4得到字节数 SUB ECX,512/4 ; IPL偏移量 CALL memcpy
这三段程序的大意就是在搬运数据,其大小是以双字为单位的,所以必须用字节数除以4来指定。
中间一段的大意是将0x7c00处的数据转移到DSKCAC的位置(注意DSKCAC是保护模式下的地址!),数据总量为512字节,即启动扇区的大小。也就意味着将启动扇区复制到了1MB以后的区域。而下面一段的大意是将软盘剩余的数据跟着储存到启动扇区后的区域。
第一段的大意则是将我们未来要用的C语言引导代码装载到BOTPAK
地址,大小为512KB,这比我们写的代码体积要大很多,富裕出很多空间。
; 启动bootpack MOV EBX,BOTPAK MOV ECX,[EBX+16] ADD ECX,3 ; ECX += 3; SHR ECX,2 ; ECX /= 4; JZ skip ; 传输完成 MOV ESI,[EBX+20] ; 源 ADD ESI,EBX MOV EDI,[EBX+12] ; 目标 CALL memcpy skip: MOV ESP,[EBX+12] ; 堆栈的初始化 JMP DWORD 2*8:0x0000001b
这段程序会将C语言编写的引导程序从0x10c8字节开始的0x11a8字节复制到0x00310000号地址。这一操作的意义会在最后为我们自制的操作系统编写专用应用程序的时候揭晓!
memcpy: MOV EAX,[ESI] ADD ESI,4 MOV [EDI],EAX ADD EDI,4 SUB ECX,1 JNZ memcpy ; 运算结果不为0跳转到memcpy RET
最终我们来到内存复制程序,它先将[ESI]内的数据复制到EAX,再将ESI进四个字节(一个双字),也就是移动指针,之后将EAX复制到[EDI],再将EDI也进四个字节(一个双字),最后将ECX减1,证明完成了一次复制。如果ECX仍不为0,则继续复制。最后RET
语句可以返回(return)调用该代码块的地方。
由于接下来文件数量增多,我们先确认你的文件目录大概是如下形状:
也就是你的操作系统文件所在文件夹要和z_tools并列。最后,在操作系统文件夹添加以下两个文件:make.bat
和Makefile
,它们两个会帮助我们高效地组织多个文件。
..\z_tools\make.exe %1 %2 %3 %4 %5 %6 %7 %8 %9
TOOLPATH = ../z_tools/ INCPATH = ../z_tools/haribote/ MAKE = $(TOOLPATH)make.exe -r NASK = $(TOOLPATH)nask.exe CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet GAS2NASK = $(TOOLPATH)gas2nask.exe -a OBJ2BIM = $(TOOLPATH)obj2bim.exe BIM2HRB = $(TOOLPATH)bim2hrb.exe RULEFILE = $(TOOLPATH)haribote/haribote.rul EDIMG = $(TOOLPATH)edimg.exe IMGTOL = $(TOOLPATH)imgtol.com COPY = copy DEL = del # 默认动作 default : $(MAKE) img # 镜像文件生成 ipl.bin : ipl.nas Makefile $(NASK) ipl.nas ipl.bin ipl.lst asmhead.bin : asmhead.nas Makefile $(NASK) asmhead.nas asmhead.bin asmhead.lst bootpack.gas : bootpack.c Makefile $(CC1) -o bootpack.gas bootpack.c bootpack.nas : bootpack.gas Makefile $(GAS2NASK) bootpack.gas bootpack.nas bootpack.obj : bootpack.nas Makefile $(NASK) bootpack.nas bootpack.obj bootpack.lst bootpack.bim : bootpack.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \ bootpack.obj # 3MB+64KB=3136KB bootpack.hrb : bootpack.bim Makefile $(BIM2HRB) bootpack.bim bootpack.hrb 0 os.sys : asmhead.bin bootpack.hrb Makefile copy /B asmhead.bin+bootpack.hrb os.sys os.img : ipl.bin os.sys Makefile $(EDIMG) imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl.bin len:512 from:0 to:0 \ copy from:os.sys to:@: \ imgout:os.img # 其他指令 img : $(MAKE) os.img run : $(MAKE) img $(COPY) os.img ..\z_tools\qemu\fdimage0.bin $(MAKE) -C ../z_tools/qemu install : $(MAKE) img $(IMGTOL) w a: os.img clean : -$(DEL) *.bin -$(DEL) *.lst -$(DEL) *.gas -$(DEL) *.obj -$(DEL) bootpack.nas -$(DEL) bootpack.map -$(DEL) bootpack.bim -$(DEL) bootpack.hrb -$(DEL) *.sys src_only : $(MAKE) clean -$(DEL) os.img
此时你的操作系统文件夹里应该是这样的结构:
在系统文件夹打开命令行,输入make img
,在VMware中重新指定镜像文件,运行后应当出现一片黑屏,这意味着我们成功启动了32位保护模式,并且导入C语言!
然而,让原书作者难受的是,我们无法使用汇编语言中的HLT来休眠CPU。为此,我们新建一个汇编代码文件naskfunc.nas
来存放C语言中可能会用到的汇编指令或函数。
[FORMAT "WCOFF"] ; 制作目标文件的格式 [BITS 32] ; 制作32位模式对应的机械语言 [FILE "naskfunc.nas"] ; 源文件名 GLOBAL _io_hlt ; 程序中包含的函数名 [SECTION .text] ; 以下是函数本体 _io_hlt: HLT RET
相应地,我们要更新我们的Makefile文件.
TOOLPATH = ../z_tools/ INCPATH = ../z_tools/haribote/ MAKE = $(TOOLPATH)make.exe -r NASK = $(TOOLPATH)nask.exe CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet GAS2NASK = $(TOOLPATH)gas2nask.exe -a OBJ2BIM = $(TOOLPATH)obj2bim.exe BIM2HRB = $(TOOLPATH)bim2hrb.exe RULEFILE = $(TOOLPATH)haribote/haribote.rul EDIMG = $(TOOLPATH)edimg.exe IMGTOL = $(TOOLPATH)imgtol.com COPY = copy DEL = del # 默认动作 default : $(MAKE) img # 镜像文件生成 ipl.bin : ipl.nas Makefile $(NASK) ipl.nas ipl.bin ipl.lst asmhead.bin : asmhead.nas Makefile $(NASK) asmhead.nas asmhead.bin asmhead.lst bootpack.gas : bootpack.c Makefile $(CC1) -o bootpack.gas bootpack.c bootpack.nas : bootpack.gas Makefile $(GAS2NASK) bootpack.gas bootpack.nas bootpack.obj : bootpack.nas Makefile $(NASK) bootpack.nas bootpack.obj bootpack.lst # 这里加入了naskfunc.nas的编译 naskfunc.obj : naskfunc.nas Makefile $(NASK) naskfunc.nas naskfunc.obj naskfunc.lst # 对应地,也要把naskfunc.obj包含进去 bootpack.bim : bootpack.obj naskfunc.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \ bootpack.obj naskfunc.obj # 3MB+64KB=3136KB bootpack.hrb : bootpack.bim Makefile $(BIM2HRB) bootpack.bim bootpack.hrb 0 os.sys : asmhead.bin bootpack.hrb Makefile copy /B asmhead.bin+bootpack.hrb os.sys os.img : ipl.bin os.sys Makefile $(EDIMG) imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl.bin len:512 from:0 to:0 \ copy from:os.sys to:@: \ imgout:os.img # 其他指令 img : $(MAKE) os.img run : $(MAKE) img $(COPY) os.img ..\z_tools\qemu\fdimage0.bin $(MAKE) -C ../z_tools/qemu install : $(MAKE) img $(IMGTOL) w a: os.img clean : -$(DEL) *.bin -$(DEL) *.lst -$(DEL) *.gas -$(DEL) *.obj -$(DEL) bootpack.nas -$(DEL) bootpack.map -$(DEL) bootpack.bim -$(DEL) bootpack.hrb -$(DEL) *.sys src_only : $(MAKE) clean -$(DEL) os.img
此时,我们就可以在C语言文件中使用汇编语言写的函数了。
void io_hlt(void); void HariMain(void) { fin: io_hlt(); goto fin; }
命令行运行make img
,随后启动虚拟机。再次运行成功!今天已经学了很多了,我们明天会开始进入C语言和画面显示的部分。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY