操作系统第四篇

第四章 C语言

  前面章节用 x86 汇编语言写了引导记录 mbr.bin,并让 BIOS 引导到内存 0x07c00 处执行成功。然后用 x86 汇编语言写了 myos 的极简版 start.bin,并让 mbr.bin 加载到内存 0x10000处执行成功。接下来只要在 start.bin 的基础上增加操作系统的基本功能就可以了。start.bin是用 x86 汇编语言写的,全部的 myos 用汇编语言编写也不是不可以,但这对大家来说是一个挑战。编写操作系统的高级语言当然首选 C 语言了。本章主要讲解关于 C 语言编程的基本问题以及 x86 汇编与 C 语言的混合编程问题。

4.1 gcc简介

  Richard Stallman 是自由软件的拥趸。上世纪八十年代,免费可自由流通的软件在软件工业商业化的强大压力下土崩瓦解,麻省理工学院 AI 实验室的 Stallman 为了自由软件辞去了 MIT 的工作,于 1985 年发表了著名的 GNU 宣言:开发一套完全自由免费,兼容于 Unix的操作系统 GNU(GNU is Not Unix)。之后他又建立了自由软件基金会(Free Software Foundation,FSF),并于 1989 年起草了 GNU 通用公共协议证书(General Public License,GPL)。
  虽然 GNU 项目中的操作系统内核进展缓慢且后来被 Linux 内核取代,但 GNU 还是开发了很多非常有效的工具,其中最著名的就是编辑器 Emacs 和编译器 gcc。
  最初的 gcc 是一款可移植、可优化、支持 ANSI C 的开源 C 编译器,经过几十年的发展,gcc 已经由最初的 GNU C Compiler 变成了 GNU Compiler Collection。
  gcc 是可以在多种硬件平台上编译出可执行程序的超级编译器,gcc 支持的体系结构有四十多种,常见的有 X86、Arm、PowerPC、MIPS、RISC-V 等,其执行效率与一般的编译器相比要高 20%~30%。
gcc 除支持 C 语言外,还支持多种其他语言,包括 C++、Ada、Java、FORTRAN、Pascal等,当然,本章只讲 C 语言程序的编译。
  gcc 还能按需求生成各种类型的输出文件,包括中间文件,汇编文件,目标文件和可执行文件等。
  gcc 基本的命令格式是:
  gcc [options] [filenames]
  其中,options 是 gcc 可识别的选项参数,filenames 给出相关的文件名称。gcc 功能丰富,选项参数多达 100 多个,这里只列出最常用的一些选项参数:
  1、无参数
  gcc 的编译过程需要经历四个相互关联的步骤:预处理(Preprocessing)、编译(Compilation)、
汇编(Assembly)和连接(Linking),无参数的 gcc 相当于把这四个步骤全都做了,即 gcc 直接
把 C 语言源程序预处理、编译、汇编并链接成可执行文件,产生的可执行文件名默认为 a.out
(LINUX 下)或 a.exe(Windows 下)。比如:
  gcc writeA.c
表示把writeA.c直接编译并链接成可执行文件,可执行文件名在Windows的cmd下为a.exe。
  gcc 可以把多个 C 语言源程序文件编译链接成一个可执行文件,此时要求这些源程序文件中必须有且只有一个 main 函数,这是因为 gcc 在链接时默认 main 函数为程序开始执行的入口函数。比如 m.c 中有 main 函数并调用了 add 函数,而 add 函数被定义在 n.c 中,则用以下命令可以把 m.c 和 n.c 编译链接成一个可执行文件:
  gcc m.c n.c
输出的可执行文件名为默认的 a.exe。
  2、-E 参数
  -E 参数要求 gcc 对 C 语言源程序只进行预处理。在预处理阶段,gcc 调用 cpp.exe,把各 C 语言源程序中头文件的内容展开在该头文件的位置,并预处理一些宏定义。该阶段会生成一个文本的扩展名为.i 的中间文件。比如:
  gcc -E writeA.c
会在屏幕上输出预处理后的内容,而:
  gcc -E writeA.c -o writeA.i
则将预处理结果保存在中间文件 writeA.i 中。预处理结果也可以保存在任何文件中,但 gcc只认.i 的中间文件。比如:
  gcc -E writeA.c -o abc
这里虽然把 writeA.c 的预处理结果保存在了文件 abc 中,但反过来 gcc 不会认为 abc 是预处理后的中间文件,只有加上扩展名.i,gcc 才认。
   3、-S 参数
   -S 参数要求 gcc 对输入文件进行编译。在编译阶段,gcc 调用 ccl.exe,把输入文件编译成文本的扩展名为.s 的汇编语言文件。比如:
  gcc -S writeA.c -o writeA.s
由于输入文件 writeA.c是C 语言源程序,因此gcc 需要先进行预处理,然后再进行编译,把结果保存在汇编文件 writeA.s 中。也可以:
  gcc -S writeA.i -o writeA.s
由于输入文件 writeA.i 已经是预处理过的中间文件,因此 gcc 略过预处理,只进行编译操作,然后把结果保存在汇编文件 writeA.s 中。上例中的 abc 虽然也是预处理后的中间文件,但是:
  gcc -S abc -o writeA.s
就不行,因为扩展名不是.i,gcc 不会把 abc 当作预处理结果去编译。
   4、-c 参数
  -c 参数要求 gcc 对输入文件进行汇编。在汇编阶段,gcc 调用 as.exe,把输入文件汇编成二进制的扩展名为.o 的目标文件。比如:
gcc -c writeA.s -o writeA.o
表示 gcc 把已经预处理并且编译过的汇编文件 writeA.s 汇编成目标文件 writeA.o。也可以:
  gcc -c writeA.i -o writeA.o
表示 gcc 把只经过预处理的中间文件先编译,然后再汇编成目标文件 writeA.o。也可以:
   gcc -c writeA.c -o writeA.o
表示 gcc 把 C 语言源程序先预处理,然后再编译,最后汇编成目标文件 writeA.o。
   这里还需要解释一下,虽然 gcc 汇编的目标文件格式应该是 ELF 格式,但 MinGw 是Windows 下的 gcc 平台,因此在 MinGw 下 gcc 汇编的目标文件格式是 PE 而不是 ELF。
   5、-o 参数
  -o 参数用于指出输出文件名。
  6、-g 参数
  如果要用 gcc 工具 gdb 对源代码进行调试,就必须在编译时加上这个参数。
  7、-O 参数
  -O 参数对程序进行优化编译、链接。采用这个选项,整个源代码会在编译、链接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是编译、链接的速度就相应地要慢一些,而且对执行文件的调试会产生一定的影响,造成一些执行效果与对应源文件代码不一致等情况。
  8、-O2 参数
  -O2 参数是比-O 更好的优化编译、链接,但整个编译链接过程会更慢。
  9、-I 参数
  -I 参数用来指定头文件所在的子目录,是在预处理过程中使用的参数。使用尖括号(<>)括住的头文件,预处理程序 cpp 将在系统默认的头文件目录中搜索相应的文件;而使用双引号(“”)括住的头文件,预处理程序 cpp 首先在源程序所在的当前目录中搜索,如果没有找到,就到 I 参数指定的目录中去搜索。
  10、-L 参数
  -L 参数用来指定库文件所在的子目录,是在链接过程中使用的参数。链接时,链接程序 ld 在系统默认子目录中寻找所需要的库文件,如果有库文件不在默认子目录中,-L 参数指定要查找的子目录。
  11、-l 参数
  -l 参数用来指定链接时需要的函数库名,该函数库位于系统默认的子目录或由-L 选项指定的子目录下。gcc 规定所有的函数库名必须以“lib”字符串开头,因此在用-l 选项指定链接的函数库文件名时可以省去 lib 这 3 个字母。比如,-lm 表示链接名为 libm.a 的静态数学函数库。
  12、-v 参数
  -v 参数输出 gcc 工作的详细过程。
  13、-Werror 参数
  -Werror 参数要求 gcc 将所有的警告当成错误进行处理,gcc 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。

4.2 C语言基础

  我们在这里就不长篇大论的讲C语言的基础知识了,就讲讲C语言的头文件和库文件吧,希望能帮你梳理 C 程序结构,为以后编写或阅读 C 语言程序打下坚实基础。
  我们知道,一个 C 语言程序就是多个函数的组合,最主要的函数就是 main 函数。main 函数是程序执行的入口函数,一个不管多么复杂的 C 程序,一切都从 main 函数开始。
  在 main 函数中可以调用其他函数,在其他函数中又可以再调用其他的其他函数。
  除 main 函数之外,任何一个 C 语言函数在使用时都需要有声明、定义和调用三个阶段。
  函数的声明主要用于让编译器检查被调用函数参数类型的一致性。(1)如果在一个 C语言源程序文件中,在 main 函数之前定义一个函数,则该函数不需要声明,相当于声明与定义一起做了;(2)如果在 main 函数之后定义一个函数(这样程序结构更清晰一些),则在main 函数之前要声明该函数;(3)更多的时候,一个函数被定义在另外的 C 语言源程序文件中,则必须在调用该函数的 C 语言源程序文件中声明该函数;(4)由于全部的 C 语言库函数被封装在不同的库文件中,那么在调用库函数的 C 语言源程序文件中当然需要声明库函数了。
  头文件就是用来声明函数的。
  函数的定义就是描述这个函数的功能。在一个 C 语言源程序文件中,函数的定义可以放在 main 函数之前,也可以放在 main 函数之后,但无论在前还是在后,这种写法使得这个函数只能在这个文件中被调用。如果希望一个函数能自由的在多个不同的 C 语言源程序文件中被调用,就需要把该函数单独定义在一个 C 语言源程序文件中,然后在编译链接时包含该文件就可以了。也可以把多个定义单个函数的 C 语言源程序文件编译成多个目标文件,然后归档在一个库文件中,最后在编译链接时只需要包含一个库文件就可以了。
  库文件就是用来定义函数的。
  著名的 Hello World 是你认识的第一个 C 语言源程序,里面 main 函数只调用了 printf 这一个函数。printf 函数要声明吗?肯定要声明,具体的声明在 stdio.h 头文件中,因此 Hello World 程序必须包含 stdio.h 头文件。printf 函数要定义吗?肯定要定义,具体的定义在 printf.o
中,而 printf.o 归档在库文件 libc.a 中。在 LINUX 操作系统中,gcc 默认的头文件路径是/usr/include,默认的库文件路径是/usr/lib,默认的 C 语言函数库是 libc.a(静态库)或 libc.so(动态库)。本教材在 Windows 操作系统中 gcc 采用的是 MinGw 平台,根据第一章的安装路径,如果是安装在D盘,默认的头文件路径是 D:\MinGw\include,默认的库文件路径是 D:\MinGw\lib。不是默认的头文件和库文件路径要在 gcc 时通过“-I”参数和“-L”参数指出,不是默认的库文件要用“-l”参数指出。
  有了以上基础,你在阅读任何 C 语言源程序时,就能找到所有函数的声明和定义,从而读懂和理解该程序。反过来,你也可以组织自己的头文件和库文件,使自己的 C 语言程序读起来更清晰,调用起来更方便,看起来更加高大上。

4.3 GNU Binutils 简介

  为了不增加更多负担,我们这里就不详细介绍了,学习几个命令就行了。

  1. nasm mbr.asm -o mbr.bin: 使用NASM汇编器将mbr.asm文件汇编为mbr.bin的命令
  2. nasm -f elf start.asm -o start.o: 使用NASM汇编器将start.asm文件汇编为start.o的命令
  3. gcc -c myos.c -o myos.o: 使用GCC编译器将myos.c文件编译为myos.o的命令
  4. ld -s --entry=start -Ttext=0x0 start.o myos.o -o myos.exe: 使用链接器将start.o和myos.o链接为myos.exe的命令。–entry=start指定了程序的入口点为start,-Ttext=0x0指定了程序的加载地址为0x0。
  5. objcopy -O binary myos.exe myos.com:使用objcopy工具将myos.exe转换为myos.com的命令
  6. writea mbr.bin A.img 1:将mbr.bin写入A.img的第一个扇区的命令

4.4 8086 汇编与 C 语言混合编程

  前面已经讲过,开发 myos 需要汇编语言与 C 语言混合编程。汇编语言主要用于编写底层的启动代码、中断处理程序以及输入输出程序,而 C 语言则用于编写 myos 的内核代码及应用程序。
  在正式编写 myos 之前,本节先引入 C 语言编程。基本思路是由第三章的 start.asm 跳转到 C 语言程序写的函数上继续执行。回顾一下,系统上电先执行 BIOS,BIOS 引导 MBR 后执行 MBR,MBR 加载 start.bin 后执行 start.bin。在 start.bin 中,屏幕上显示了“my os is running! ”后无事可做,只好死循环。现在用 C 语言写一个函数,然后从 start.bin 中跳转到该函数处继续执行。

4.4.1 混合编程的几个问题

   一、16 位代码
  默认情况下,nasm 会将汇编语言程序汇编成 32 位代码,由于 myos 运行在实地址模式下,必须是 16 位代码,所以要求 nasm 把汇编语言程序汇编成 16 位代码,这通过在汇编语言程序中增加“BITS 16”关键字来实现。同样的,GCC 默认情况下会把 C 语言程序编译成32 位代码,而 myos 中要求 C 语言程序也要编译成 16 位可执行代码,这通过在 C 语言程序中增加“code16gcc”关键字来实现。具体格式参见代码实例。
  二、符号引用
  如果要在 C 语言程序中引用汇编语言程序中的符号(变量和函数),首先肯定要在汇编语言程序中定义该符号,这只需要在某行语句前写上该符号就行,加不加冒号都可以;然后在汇编语言程序的开始部分用 global 关键字将其声明为全局符号;最后在 C 语言程序中用extern 关键字将其声明为外部符号,就可以在 C 语言程序中引用了。注意,在汇编语言程序中定义和声明时符号前都要加下划线,在 C 语言程序中声明和引用时符号前都不加下划线。
  如果在汇编语言中引用 C 语言程序中的符号(变量和函数),首先肯定要在 C 语言程序中定义该变量或函数,并用 global 关键字将其声明为全局符号;然后在汇编语言程序中用extern 关键字将其声明为外部符号,就可以在汇编语言程序中引用了。注意,在 C 语言程序中定义和声明时符号前都要加单下划线,在汇编语言程序中声明和引用时符号前都要加双下划线。
  文字可能表达不太清楚,参考代码实例会看得比较清楚。
  三、参数传递
  调用者与被调用者之间传递参数时,主要通过堆栈完成,这需要搞清楚参数的入栈顺序及其宽度。在 16 位代码中,问题相对简单,后面具体遇到时再讨论。调用者与被调用者之间的返回地址,虽然也要通过堆栈完成,但情况稍微复杂一些。主要原因是 nasm 把汇编语言程序汇编成 16 位代码时,所有的指令都是 16 位模式,但 gcc 把C 语言源程序编译成 16 位代码时,指令中会大量采用 32 位数据模式,也就是说很多指令字节的前面都会多一个值为 0x66 的字节,包括 call 和 ret 指令。为了不致引起混乱,导致参数传递的错位,需要改变调用方式或手工添加 0x66 字节。具体包括以下 4 种情况:
  1、如果调用函数用汇编语言编写,同时被调函数用 C 语言编写时,通过修改调用函数中的 call 指令为 push 指令与 jmp 指令的组合来实现。即将返回地址压入堆栈,然后直接跳转到被调函数名。
  2、如果调用函数用汇编语言编写,同时被调函数也用汇编语言编写时,在主调函数中用 call 指令,被调函数中用 ret 指令即可。
  3、如果调用函数用 C 语言编写,同时被调函数用汇编语言编写时,在主调函数中直接调用汇编程序中不加下划线的符号名即可,同时被调函数中的 ret 指令前要增加一条伪指令“DB 0x66”,即手工添加 0x66 字节。
  4、如果调用函数用 C 语言编写,同时被调函数也用 C 语言编写时,按正常方式调用与返回即可。

4.4.2 混合编程的一个实例

  接下来我们写一个汇编语言与 C 语言混合编程的例子。这个例子虽然非常简单,但它包含了混合编程的基本要素。同时这个例子也是 myos 的最基本框架,后面我们会看到,越来越丰富的 myos 只需要在该框架上添砖加瓦就可以了。
  一、start.asm
  在 myos4 子目录下,首先修改 start.asm。
  之前在汇编 mbr.asm 和 start.asm 时,都没有“-f”选项,即没有指定目标文件的格式,nasm 默认把它们汇编成 16 位的纯二进制的无格式可执行文件,而这也恰恰是我们需要的。现在 start.asm 要和 C 语言程序链接在一起,start.asm 就必须有格式,因为只有有格式的目标文件,ld 命令才能识别里面的各种信息并分门别类的链接在一起。而一旦有格式,nasm就会默认的汇编成 32 位代码,如果需要 16 位,如前所述就要用“BITS 16”关键字告诉 nasm。
  你可以想象,当用 ld 命令把 start.asm 和一个 C 语言程序链接成一个可执行文件时,这个可执行文件应该有一个程序入口,即程序从什么地方开始执行,是从 start.asm 开始还是从 C 语言程序开始,这需要你亲自指定。因为我们的程序结构是在 start.asm 里面调用 C 语言程序中的函数,所以当然是从 start.asm 开始执行。因此,需要把 start.asm 程序中第一条指令的地址符号用 global 关键字声明为全局的,并在 ld 时用“–entry”选项指定。这个程序入口的地址符号自己定义,没有特殊要求,一般是 start、entry 和 begin 等有意义的符号。
  第三章中 start.asm 汇编程序的最后是两条实现死循环的汇编语句,现在把它改为跳转到__mymain 处执行,注意这里是双下划线,mymain 是定义在 C 语言程序中的函数名,并且在 start.asm 的开始处要用 extern 声明为外部符号。在 start.asm 文件的后面,增加一段程序。第一条汇编语句用一个符号“_puts”标识,最后两条语句是 DB 0x66 和 RET,则这段程序表示一个名为_puts 的子程序。这段程序的功能是打印一个参数字符串,字符串的地址从堆栈中取出。puts 函数要在 C 语言程序中调用,因此需要在 start.asm 中用 global 关键字将其声明为全局的。
  修改后的 start.asm 如下所示:

	[BITS 16]
	GLOBAL start
	EXTERN __mymain
	GLOBAL _puts
start: 
	JMP entry
	entry: MOV AX,CS
	MOV DS,AX
	MOV ES,AX
	MOV AX,0
	MOV SS,AX
	MOV SP,0x7e00
	LEA SI,msg
	ploop: LODSB
	CMP AL,0
	JE fin
	MOV AH,0x0e
	MOV BX,0x0f
	INT 0x10
	JMP ploop
fin: 
	JMP _ __mymain
	msg: DB "myos is running!-start.asm",0x0a,0x0a,0x0a,0
_puts: ;在屏幕光标位置打印参数字符串
	MOV BP,SP
	MOV SI,[BP+0x04]
loop2: 
	LODSB
	CMP AL,0
	JZ loop3
	MOV AH,0x0e
	MOV BL,0x07
	INT 0x10
	JMP loop2
loop3: 
	DB 0x66
	RET

  程序在初始化各段寄存器和堆栈之后,打印了一个字符串,然后跳转到 mymain 处继续执行,程序最后定义了一个函数 puts。想必通过前面的学习,你能看明白,这里不再解释。
  二、myos.c
  在 myos4 子目录下,编写 C 语言程序 myos.c。
  myos.c 要被 gcc 编译成 16 位代码,因此要用关键字“asm(“.code16gcc\n”);”来要求。
  myos.c 要调用定义在汇编语言程序 start.asm 中的函数 puts,因此要用 extern 关键字将其声明为外部的。注意调用 puts 函数时不加下划线。
  myos.c 里只有一个函数,函数名为_mymain,该函数名可以写成任意你喜欢的名字。由于要从汇编语言程序 start.asm 中跳转到该函数,所以函数名前要加一个下划线,表示它是一个全局符号。
  _mymain 函数里面只调用了 puts 函数后就又继续死循环。这里显示字符串的函数是在start.asm 中自定义的 puts,而不是 C 语言的库函数 printf。这是因为 printf 是已经封装好的库函数,它要运行在 32 位环境,因此在 16 位的 myos 中无法使用。
  注意字符串的结束符。前面在汇编语言中已经以打印单字符的方式打印过两个字符串了,判断字符串结束的标记是 0,因此在定义字符串时要注意加上结束符 0(即字节的值是 0)。这里是 C 语言中定义的字符串,你可以通过十六进制编辑器查看 myos.o,gcc 会自动的在字
符串尾加上结束符 0。
  myos.c 虽然简单,但框架重要,其内容如下所示。

__asm__(".code16gcc\n");
extern int puts(char *str);
char * str = "Hello world from C Language! -myos.c"; 

int _mymain()
{
    
    
	puts(str);
	for(;;);
}

三、汇编、编译和链接
  首先汇编 start.asm。nasm 可以输出多种格式,考虑到 MinGw 平台的 gcc 只能输出 pe格式,本想把 start.asm 汇编成 pe 格式,但实际操作时显示 pe 格式不支持 16 位代码,所以只能把 start.asm 汇编成 elf 格式,好在 ld 命令功能强大,可以链接多种不同格式的目标文件。
  汇编 start.asm 的命令如下图所示。尽管我们已经沉的够底层了,但还在受制于人,除非自己开发出编译器。
请添加图片描述

  注意这里的 objdump 命令只是顺便看一下 start.o 文件的格式,实际操作中并不是一个必须的动作。
  然后编译 myos.c。myos.c 只能用“-c”选项编译成目标文件。具体命令如下图所示。
请添加图片描述

  最后,把它们链接成一个可执行文件。具体命令如下图所示

请添加图片描述

  其中“-s”选项要求忽略输出文件中所有的符号信息,这可以有效减小可执行文件的大小,“–entry=start”选项及参数指出程序的入口点是 start.asm 中的 start 符号处,而“-Ttext=0x0”选项及参数则告诉链接器 text 节的绝对地址为 0,“-o myos.exe”选项及参数指出输出的可执行文件名为 myos.exe。
  四、转储和加载
  链接后的可执行文件名是 myos.exe,格式是 pei-i386,长度约 4KB。pei-i386 格式中的各种信息专为操作系统的加载器准备,myos 不想这么复杂,所以用 objcopy 工具将 myos.exe拷贝成纯二进制格式的可执行文件 myos.com,这样加载和执行都方便。具体命令为:
   objcopy -O binary myos.exe myos.com
其中-O 选项及对应的参数要求把输出文件拷贝成纯二进制格式;myos.exe 是输入文件,可以用-I 选项指出输入文件的格式,但也可以不用该选项而让 objcopy 自己判断输入文件的格式;myos.com 为输出文件。
  第三章假定 start.bin 是 myos 操作系统,因为只有 57 字节,所以 mbr.asm 在加载的时候只读了 1 个扇区。现在 myos.com 是 myos 操作系统,注意到其长度大约 17KB,因此需要修改 mbr.asm,把读入的扇区个数改为 40。
  实际上目前 myos.com还是一个简单得不能再简单的框架,实际代码和数据远没有 17KB这么多,那为什么 objcopy 后会这么大呢?主要原因是 ld 时对各段逻辑地址的相对定位,如下图所示。
请添加图片描述
  如果是 myos.exe 被加载,则要由操作系统重定位各段的实际地址,现在 myos.com 是纯二进制文件,加载是不需要重定位,因此在 objcopy 时就要把各段地址实际填充到位,因此文件就大了。你可以用十六进制编辑器查看 myos.exe 和 myos.com,以后不断丰富 myos 时,代码和数据会占据哪些填充为 0 的位置,文件的实际大小不会变化太大。
  用工具 writeA.exe 把 myos.com 优雅的写入 A.img 中从第 2 个扇区开始的位置。
  最后的运行结果如下图所示。

请添加图片描述

  现在,myos.com 已经初步具有了操作系统的基本框架,接下来只需要你继续增加功能就可以了。
  五、启用批处理功能
  虽然 myos 还很简单,只有为数不多的几个文件,但在调试过程中已经用到了很多命令,调试过程稍嫌复杂。后面随着 myos 功能的不断完善,会有更多的文件,调试过程会更趋复杂。
  好在 cmd 有批处理功能,利用批处理文件可以大幅度的减少命令的输入,从而简化调试过程。
  批处理的意思就是可以把一批命令放在一起处理。在早期的 DOS 系统和后来的Windows 系统中,可以把多个命令放在一个扩展名为 bat 的文本文件中,然后在命令行直接输入这个文件名,就会自动的逐行执行文件中的所有命令。
  当然,在 UNIX 和类 Linux 系统中也有这样的功能,并且如果你了解 Linux,就会知道Linux 中的 shell 脚本文件功能更加强大。事实上,UNIX 比 DOS 要早很多。打开记事本,依次输入本节给出的调试命令,然后存盘命名为 a.bat(文件名任意,只要扩展名为 bat 就行)。文件内容如下所示:

nasm mbr.asm -o mbr.bin
nasm -f elf start.asm -o start.o
gcc -c myos.c -o myos.o 
ld -s --entry=start -Ttext=0x0 start.o myos.o -o myos.exe 
objcopy -O binary myos.exe myos.com
writea mbr.bin A.img 1
writea myos.com A.img 2
pause

  然后在 cmd 命令行上输入 a 或 a.bat,然后回车,就会自动执行文件中的所有命令。

猜你喜欢

转载自blog.csdn.net/weixin_59357453/article/details/131709171
今日推荐