网站首页 / 育儿 / 正文

操作系统的主要功能(操作系统的主要功能是)

时间:2022-05-07 11:10:04 浏览:48次 作者:经济日报 【我要投诉/侵权/举报 删除信息】

第一天直接写了一个“操作系统”的二进制文件,详细在:https://www.toutiao.com/a7003704649312141836

由于写二进制文件很不方便,也太繁琐,所以我们使用了编译器,编译器可以将汇编语言编译成二进制的机器码,以后就直接写汇编,然后用编译器把我们写的汇编生成二进制文件就行了。这就大大提高了人的编程效率。

所以,第二天我们用汇编语言来重写了“操作系统”。

第二天我们用汇编语言写的”操作系统“,详细在:https://www.toutiao.com/a7005537960355512839

但是,用汇编语言来写操作系统,效率还是太慢。为了提升写操作系统的效率,我们在第03天,改汇编为C语言来写操作系统。本节程序代码地址为:https://gitee.com/-/ide/project/mingminglaoshi/MakeComputerOperateSystemin30Days/edit/master/-/projects/03_day/harib00j/bootpack.c

由于C语言出现在计算机已经发展积累了一段时间一后,所以,要使用C来写操作系统,需要与汇编语言配合做一些铺垫工作。具体做哪些铺垫工作呢?外围硬件的初始化,以及CPU芯片内部寄存器的初始化。

要了解得更详细,需要了解一下前置知识:

操作系统的主要功能

操作系统前,计算机做的事情

主要看上面的第3条:

BIOS 里面有一段写死的代码,会帮我们把启动区的第一扇区的 512 字节的内容,原封不动复制到内存里 0x7c00 这个位置,并跳转到此处,这个是不用我们管的。

更详细可以参考:https://blog.csdn.net/sinat_34560749/article/details/104060723

也就是说,BIOS里面已经固化了一段代码,它会把磁盘上的512字节的内容复制到内存的0x7c00的这个位置。注意到,它只复制512字节。

那么准备用C来写操作系统的话,512字节不够用了,因为他只有512Bytes,太小了。所以,我们会用这块512b的内存做一些准备性的工作,操作系统的真正的功能,大部分的功能都不会在这512字节里。

所以,这512KB的空间我们就称为“启动区”了。电脑去执行操作系统的代码前,先运行这512KB区域内的程序,在这512KB内的程序中,我们做一些寄存器的设置等工作,在程序的最后,跳转到真正操作系统的代码处去执行。

启动区与操作系统

综上,也就是说,首先我们要完成启动区的程序,其次才是去完成操作系统的程序。

那么启动区应该写一些什么内容呢?启动区程序能不能用C语言?

启动区程序的功能:

从软盘/磁盘/U盘/硬盘等复制操作系统的代码到内存位置0x8200处,然后就跳转到0x8200处,开始执行

启动区的程序就是复制功能、一些设置功能,所以似乎用汇编和C来写都可以的,但是最终我们还是选择了汇编来写。因为C语言设置寄存器状态还是没有汇编方便。

操作系统代码的功能:

首先:要设定一些硬件的状态,显示屏的状态等,键盘的状态,cpu周围的寄存器的状态等

其次:实现去组织调用鼠标,键盘,显示屏等硬件供用户随时使用

因为要设定硬件的状态,显示屏状态等,需要调用BIOS,而BIOS是工作在CPU16位模式的,C语言是工作在CPU32位模式的,这就决定了操作系统也不能全是C写的,操作系统有一些基础的功能还是需要用工作在16位模式的汇编来写的。

也就是说,启动区之后的操作系统,要使用汇编与C两种语言来写。

那么工作在操作系统之前的启动区程序,负责从存储介质上讲操作系统代码复制到内存上,用汇编语言写,代码量也不大,如果要设置硬件也容易,所以,启动区的程序用汇编来写。

启动区代码讲解

电脑开机后,会在BIOS里将磁盘上的512字节的程序复制到内存的0x7c00处,然后跳到0x7c00处执行。

这512字节程序的作用,就是将真正操作系统的代码复制到内存的0x8200处,然后跳转到0x8200开始执行。

为何叫它启动区?因为它把真正的操作系统代码“复制”到内存里了。

那么 BIOS的代码也是复制了512B的内容到了内存里,为什么BIOS不叫启动区?

我想如果这512字节能放下一个操作系统的话,BIOS里的代码叫启动区合适。

目前来看,操作系统的代码远超过512字节。

所以,”启动区“这个名字就归这512字节了。

我们要把启动区的这512字节的程序放在启动磁盘/U盘/光盘的前512字节处,等待BIOS程序去调用。我们这里就以软盘为例,代码如下ipl10.nas:

1 ; haribote-ipl 2 ; TAB=4 3 4 CYLS EQU 10 ; 设置软磁盘的结束柱面号,操作系统放在软磁盘的0-10柱面 5 6 ORG 0x7c00 ; 将本代码放在内存的0x7c00处 7 8 ; 以下是标准FAT12格式软盘的描述 9 10 JMP entry 11 DB 0x90 12 DB "HARIBOTE" ; 设置扇区的名称 13 DW 512 ; 设置一个扇区的大小 一个扇区有多少betys,对于软磁盘来说,这个是必须是512 14 DB 1 ; 集群大小 15 DW 1 ; FAT从第1扇区开始 16 DB 2 ; FAT的个数 17 DW 224 ; 根目录最多有224个条目 18 DW 2880 ; 驱动器有2880个扇区 19 DB 0xf0 ; 媒体类型 20 DW 9 ; FAT每个区域含9个扇区 21 DW 18 ; 一个柱面一共多少个扇区 22 DW 2 ; 磁头数目 23 DD 0 ; 磁盘分区数目0 24 DD 2880 ; 驱动器含有2880个扇区 25 DB 0,0,0x29 ; 26 DD 0xffffffff ; 当前FAT格式的序列号 27 DB "HARIBOTEOS " ; 磁盘名称 28 DB "FAT12 " ; 文件格式名称 29 RESB 18 ; 对当前的18个字节不做操作 30 31 32 entry: 33 MOV AX,0 ;用AX对寄存器SS初始化 34 MOV SS,AX 35 MOV SP,0x7c00 36 MOV DS,AX 37 38 ; 读磁盘 39 40 MOV AX,0x0820 41 MOV ES,AX 42 MOV CH,0 ;柱面0 43 MOV DH,0 ;磁头0 44 MOV CL,2 ;扇区2 45 readloop: 46 MOV SI,0 ;记录失败次数 47 retry: 48 MOV AH,0x02 ; AH=0x02 :读磁盘 49 MOV AL,1 ; 1个扇区 50 MOV BX,0 51 MOV DL,0x00 ; A驱动器 52 INT 0x13 ; 调用磁盘BIOS 53 JNC next ; 读取成功,跳转到next,去读区一下个扇区 54 ADD SI,1 ; 读区失败,记录失败次数:SI+1 55 CMP SI,5 ; 如果失败次数SI>5,则显示读取失败,跳转到error 56 JAE error ; SI >= 5 57 MOV AH,0x00 ; 重置驱动器 58 MOV DL,0x00 ; A 驱动器 59 INT 0x13 ; 调用AH=0x00时对应的中断程序:重置驱动器 60 JMP retry 61 next: 62 MOV AX,ES ;获取当前内存地址 63 ADD AX,0x0020 ;在内存地址上+0x0020 64 MOV ES,AX ; ADD ES,0x020 65 ADD CL,1 ; CL+1,扇区+1 66 CMP CL,18 ; 比较CL 与18,查看有没有到达18扇区 67 JBE readloop ; CL <= 18 还没读取到18扇区,则继续去读区数据 68 MOV CL,1 ; 如果已经到了18扇区,扇区设置为1 69 ADD DH,1 ; 磁头+1 70 CMP DH,2 ; 对比磁头与2 71 JB readloop ; DH < 2,磁头小与2,继续复制 72 MOV DH,0 ; 磁头>2,将磁头设置位0 73 ADD CH,1 ; 柱面+1 74 CMP CH,CYLS ; 对比柱面与 CYLS=10 75 JB readloop ; CH < CYLS ,柱面<10,继续复制 76 77 ;以上代码完成了将操作系统代码复制到内存的0x8200处的作用 78 79 MOV [0x0ff0],CH ; 将CH的值存储到内存的[0x0ff0]位置处,CH是柱面号,柱面0-柱面10是启动区的程序 80 JMP 0xc200 ; 启动区的程序运行结束,跳转到内存的0xc200处执行 81 82 error: 83 MOV SI,msg ; 如果运行失败,就显示msg,msg的内容是"load error" 84 putloop: 85 MOV AL,[SI] ; 将msg中的字符放入AL 86 ADD SI,1 ; SI+1,等待区 msg中的下一个字符 87 CMP AL,0 ; 查看是否取到了msg最后一个字符 88 JE fin ; 如果取到了最后一个字符,跳转到fin 89 MOV AH,0x0e ; 定义中断程序号 90 MOV BX,15 ; 设置显示字符的颜色 91 INT 0x10 ; BIOS中断程序调用,显示AL中的字符 92 JMP putloop 93 fin: 94 HLT ; 让CPU在一个时钟周期内什么也不做 95 JMP fin ; 96 msg: 97 DB 0x0a, 0x0a ; 两个换行符 98 DB "load error" ; 等待被显示的字符串 99 DB 0x0a ; 换行符100 DB 0 ; msg的最后一个字符0,显示程序遇到这个字符,就跳转到fin101 102 RESB 0x7dfe-$ ; 以0x00来填充当前程序所处的内存,一直到内存地址的0x7dfe处103 104 DB 0x55, 0xaa ; 最后再写0x55,0xaa到程序的最后。用这两个字节结束表示当前程序是启动区程序。

以上程序将被BIOS程序自动复制到0x7c00处,然后运行。

程序复制了软磁盘的内容到0x8200处,若成功跳转到0xc200处,若不成功,跳转到error处。

不成功的话,显示错误提示。

成功的话,应该跳转到操作系统代码处,即内存的0x8200处呀?这里为甚么跳转到了0xc200处?

这是因为在取执行真正的操作系统代码之前,需要做一些硬件设置,cpu工作模式的改变等,而这些代码就存放在0xc200处,

我们看下0xc200处的代码asmhead.asm:

; haribote-os boot asm; TAB=4BOTPAK EQU 0x00280000 ; 将操作系统的代码存放到0x00280000处,等待执行DSKCAC EQU 0x00100000 ; 将软盘中的启动区代码,操作系统代码等保存到0x00100000处DSKCAC0 EQU 0x00008000 ; 0x7c00+512,BIOS将软盘的启动区代码复制到了0x7c00处,我们要把从0x8000处的内容,软盘中除了启动区的代码,从内存0x8000开始的代码复制到0x001002ff处CYLS EQU 0x0ff0 ; 复制的柱面数存放的地址0x0ff0LEDS EQU 0x0ff1 VMODE EQU 0x0ff2 ; 显示屏显示模式放在0x0ff2处SCRNX EQU 0x0ff4 ; 宽度放在内存0x0ff4处SCRNY EQU 0x0ff6 ; 高度VRAM EQU 0x0ff8 ; 像素数据缓冲区 ORG 0xc200 ; 将此代码放到内存的0xc200处,等待启动区程序跳转到此处; 显示屏显示模式的设置 MOV AL,0x13 ; 设定显卡工作在VGA模式,分辨率为320x200x8bit位彩色 MOV AH,0x00 INT 0x10 ;调用BIOS中的中断程序,对显卡进行设定 MOV BYTE [VMODE],8 ; 将画面模式放入内存地址VMODE处 MOV WORD [SCRNX],320; 将宽度放到内存地址SCRNX处 MOV WORD [SCRNY],200; 将高度放到内存地址SCRNX处 MOV DWORD [VRAM],0x000a0000; ;将0x000a0000放到内存地址 VRAM处,表明内存的0x000a0000处,是放像素质的地方,0x000a0000通过查BIOS手册可以得到; 键盘灯的设定 MOV AH,0x02 INT 0x16 ; BIOS中关于键盘灯的中断程序 MOV [LEDS],AL; 根据AT兼容机的规格,如果要初始化PIC,必须在关闭中断前执行,否则会进入终端程序无法返回,造成“挂起”现象 MOV AL,0xff OUT 0x21,AL NOP ; 连续OUT命令中间加上空指令,提高OUT指令成功率 OUT 0xa1,AL ; 执行OUT指令,初始化PIC CLI ; 关闭中断; 为了能让CPU访问1M以上的内存,设定第20根地址线可用,即A20可用 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的地址,GDT,全局内存段记录表,这里用了一个表将内存管理起来,使用这个表可以对操作系统使用的内存特殊保护 MOV EAX,CR0 ; 获取CR0寄存器的置, AND EAX,0x7fffffff ; 将CR0的 31位设置为0,禁止颁 OR EAX,0x00000001 ; 将CRO寄存器的第0位设置为1,切换到保护模式 MOV CR0,EAX JMP pipelineflushpipelineflush: 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 ; 调用复制程序; 启动区程序,放在软盘上的程序也要复制一下,复制到DSKCAC处; 首先复制一个扇区 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] ; 到0x0ff0位置获取读区成功的柱面数 IMUL ECX,512*18*2/4 ; 用柱面数计算双字数 SUB ECX,512/4 ; 减去IPL程序占用的双字数 CALL memcpy; 启动bootpack.c程序; 查看C代码编译好的二进制文件,发现程序的地址在内存的:2*8:0x0000001b处,所以最终跳转到了2*8:0x0000001b处; 至于跳转前的复制不知道啥意思,作者也没提 MOV EBX,BOTPAK ; BOTPAK 0x00280000 MOV ECX,[EBX+16] ; 需要复制的双字数:0x11a8 ADD ECX,3 ; ECX += 3; SHR ECX,2 ; ECX /= 4; JZ skip ; 不需要复制 MOV ESI,[EBX+20] ; 复制的数据源0x10c8,内存这个地址是什么呢?需要复制到0x00310000处? ADD ESI,EBX MOV EDI,[EBX+12] ; 复制的目的地 0x00310000处 CALL memcpyskip: MOV ESP,[EBX+12] ; 将0x00310000设为栈地址 JMP DWORD 2*8:0x0000001bwaitkbdout: IN AL,0x64 AND AL,0x02 JNZ waitkbdout ; 如果AL与0x02 做AND操作后,不为零,就重新读区端口0x64的数据 RETmemcpy: MOV EAX,[ESI] ;将ESI地址处的内容MOV到EDI地址处的内容 ADD ESI,4 MOV [EDI],EAX ADD EDI,4 SUB ECX,1 ;移动一次,ECX -1 JNZ memcpy ; 如果ECX 不等于 0 ,则复制下一个字节 RET ALIGNB 16 ; 一直添加DB0,一直到地址能被16整除GDT0: RESB 8 ; 保留8个字节,32位 DW 0xffff,0x0000,0x9200,0x00cf ; 用32位的数据,设置可以读写的内存段 DW 0xffff,0x0000,0x9a28,0x0047 ; 用32为的数据,设置可以存放操作系统代码的内存段(bootpack.c文件就保存在这里) DW 0GDTR0: DW 8*3-1 DD GDT0 ;将内存段的记录表放到GDTR0处,等待被放到LGDT寄存器里 ALIGNB 16bootpack:

可以看到,这里设定了显示屏显卡,设定了显示屏的像素地址,设定了键盘灯,切换到了486的32位模式,保护模式,设定了2个断记录表来管理内存,不再用段寄存器来访问内存了,

设定完成后,本程序将磁盘文件复制到了0x00100000处,为甚么复制到这里,没有特别的用处,也可以空着。

本程序将操作系统代码复制到了第二个内存段0x00280000处。

然后就跳转到了2*8:0x0000001b,第二个段的0x0000001b处,这里就是我们写的C程序编译而成的二进制文件。

我们写的c程序就是bootpack.c,它就在0x0000001b处,它也及时操作系统的代码,如下:

操作系统代码

bootpack.c

// 声明函数,这个函数来自汇编,因为我们c程序中要使用,所以这里需要先声明一下void io_hlt(void);void HariMain(void){fin: io_hlt(); /* cpu空一个时钟周期 */ goto fin;}

这个代码非常简单,就是cpu闲置,程序一直等待。

以后就可以在这个HariMain函数里写控制显示屏显示的程序了,用C写,而不是用汇编写。

用C写效率就很高了。

这个C代码里有个函数需要说一下,就是这个io_hlt函数,

这个函数定义在汇编代码asmfunc.nas里,如下:

; naskfunc; TAB=4[FORMAT "WCOFF"] ; 指定目标文件格式 [BITS 32] ; 编译成32模式对应的机器码; 目标文件其他信息指定[FILE "naskfunc.nas"] ; 源文件名 GLOBAL _io_hlt ; 声明_io_hlt为GLOBAL; 具体函数的实现[SECTION .text] ; 将一下代码放在程序区_io_hlt: ; 定义 void io_hlt(void); HLT RET

代码naskfunc.nas中,定义了一个函数io_hlt,供C程序去调用。

运行:

将以上ipl10.nas, asmhead.nas,bootpack.c,naskfunc.nas四个文件编译,生成操作系统映像haribote.img,编译规则如下:

ipl10.bin : ipl10.nas Makefile $(NASK) ipl10.nas ipl10.bin ipl10.lstasmhead.bin : asmhead.nas Makefile $(NASK) asmhead.nas asmhead.bin asmhead.lstbootpack.gas : bootpack.c Makefile $(CC1) -o bootpack.gas bootpack.cbootpack.nas : bootpack.gas Makefile $(GAS2NASK) bootpack.gas bootpack.nasbootpack.obj : bootpack.nas Makefile $(NASK) bootpack.nas bootpack.obj bootpack.lstnaskfunc.obj : naskfunc.nas Makefile $(NASK) naskfunc.nas naskfunc.obj naskfunc.lstbootpack.bim : bootpack.obj naskfunc.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \ bootpack.obj naskfunc.obj# 3MB+64KB=3136KBbootpack.hrb : bootpack.bim Makefile $(BIM2HRB) bootpack.bim bootpack.hrb 0haribote.sys : asmhead.bin bootpack.hrb Makefile copy /B asmhead.bin+bootpack.hrb haribote.sysharibote.img : ipl10.bin haribote.sys Makefile $(EDIMG) imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl10.bin len:512 from:0 to:0 \ copy from:haribote.sys to:@: \ imgout:haribote.img

先将ipl10.nas编译成ipl10.bin待用

将asmhead.nas编译成asmhead.bin待用

将bootpack.c编译成bootpack.obj待用

将naskfunc.nas编译成naskfunc.obj待用

将naskfunc.obj与bootpack.obj链接在一起,生成bootpack.bim 然后编译生成bootpack.hrb

bootpack.hrb与asmhead.bin文件连接成haribote.sys

将ipl10.bin与haribote.sys连接在一起生成操作系统映像文件haribote.img

然后用模拟器加载映像文件haribote.img即可看到运行结果:显示屏显示一片黑。

这次主要完成了一些铺垫工作,成功用C实现了显示屏显示黑色,然后等待的状态。下一节开始,就开始控制显示屏显示我们期待的内容。

总结:

为了用C来写操作系统,我们不仅仅需要利用启动区的512字节的程序,把磁盘文件复制到内存中,还需要在把cpu的工作模式切换到32位保护模式,需要管理更大的超过1M的内存,需要用内存段记录表的形式来管理内存。

只有当以上工作完成之后,我们才能够让cpu跳转到我们写的c程序里。

另外c程序里,需要调用汇编代码,这里提供了c调用汇编代码的方式;写一个naskfunc文件,内部写上_io_hlt的具体实现。

这里还有很多细节,比如内存段记录表是什么?内存段记录表寄存器LGDR的作用是什么?这些等下次来介绍。

版权声明:
本文内容由互联网用户自发贡献,该文观点仅代表作者本人,因此内容不代表本站观点、本站不对文章中的任何观点负责,内容版权归原作者所有、内容只用于提供信息阅读,无任何商业用途。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站(文章、内容、图片、音频、视频)有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至353049283@qq.com举报,一经查实,本站将立刻删除、维护您的正当权益。