「30天制作操作系统系列」1~4天从汇编到C语言

本文介绍了制作操作系统的初期阶段,从开机启动过程、BIOS功能,到编写启动引导程序(bootloader)的重要性,强调了汇编语言在这一阶段的必要性。通过汇编源码解析,展示了如何编写最简单的启动程序,以及如何用Makefile编译和生成软盘镜像。最后,简要概述了从实模式转换到32位保护模式的过程,为后续调用C语言代码奠定了基础。
摘要由CSDN通过智能技术生成

目录

image

背景

30制作操作系统?这个听起来像天方夜谭,但是真有一本书就叫做《30天自制操作系统》,当然,自制操作系统的目的不是要去对标Windows、Linux、MacOS等流行的现代化操作系统,而是了解操作系统制作与运行的整个流程与细节,了解汇编与C语言。

下图是该书作者最终制作的操作系统的模样,至少我看到有图像查看器、调色板、文本阅读器与console终端,麻雀虽小五脏俱全,这张图就是我坚持的动力(笑)。

image

本系列文章的目的不是教你怎么一天天去实现上述操作系统,而是笔者在阅读并实践了《30天自制操作系统》一书后的一些笔记和整理,本文后面会提供该书以及所有光盘附带源码,感兴趣的读者可以自己动手实践,如果遇到不了解的内容,也可以结合本笔记查看。

操作系统,英文(Operating System,下文简称OS),是一个特殊的软件,往上运行着应用软件(比如我们常用的QQ,微信),往下和硬件打交道(比如CPU、内存、鼠标等),它就像是硬件和应用软件之间的桥梁与纽带。

仓库中包含该书以及所有源码文件:
https://github.com/scriptwang/30DaysMakeOS

电脑启动过程

怎么才能让电脑通电后再启动我们自制的OS?首先一定要先明白电脑是怎么启动的:

  1. 按下开机键通电
  2. BIOS启动并自检(检查硬件设备是否就绪),新版的BIOS也叫UEFI,不要纠结,本质是一样的
  3. BIOS读取硬盘/软盘/光驱的第一个扇区(512字节)到内存0x7c00~0x7dff处(0x7dff-0x7c00+1 刚好等于512)
  4. 执行0x7c00~0x7dff代码启动相应的OS

这512字节的程序就叫做bootloader,在书中叫做IPL(initial program loader),叫啥不重要,总之开机就会执行这512字节的指令,这512字节的指令再去将OS kernel加载到内存,然后启动真正的OS!

如下图所示

image

需要注意的是BIOS的一些设置信息和系统时钟信息存储在CMOS中,CMOS全称Complementary Metal Oxide Semiconductor(互补金属氧化物半导体),它需要通电,台式机主板上的纽扣电池就是给CMOS供电的,如果没电了,BIOS设置信息就会丢失然后恢复到默认值(假设设置了BIOS密码拔掉纽扣电池密码就没有啦),系统时钟也会停止

为啥有这样的设计?想一下为什么每次电脑开机时钟总是正确的?那么一定有一个地方一直在运行着,然后开机时告诉OS当前的系统时钟,要一直运行就得一直通电。

BIOS

参考:
https://zh.wikipedia.org/wiki/BIOS

BIOS(英文:Basic Input/Output System),即基本输入输出系统,BIOS是16位汇编语言程序,只能运行在16位实模式,可访问的内存只有1MB,而UEFI是32位或64位高级语言程序(C语言程序),突破实模式限制,可以达到要求的最大寻址。实模式的内容后面会详细讲到

当电脑的电源开启,BIOS就会从主板上的ROM芯片运行,运行加电自检(POST),测试和初始化CPU、RAM、直接存储器访问控制器、芯片组、键盘、软盘、硬盘等设备。当所有的Option ROM被加载后,BIOS就试图从引导设备(如硬盘、软盘、光盘)加载引导程序,由引导程序加载操作系统。BIOS也可从网卡等设备引导。

下图是BIOS自检过程中检查出的硬盘错误,这个界面有没有很熟悉的感觉。

image

启动引导程序编写

启动引导程序即上文所说的512字节的bootloader,在书中叫做IPL,启动引导程序后文用IPL代替

汇编的必要性

这里讲的是用汇编来写IPL程序的必要性,编程语言那么的多,为什么要选汇编?要讲明白这个问题首先明白几个概念。

机器语言

参考:
https://zh.wikipedia.org/wiki/%E6%9C%BA%E5%99%A8%E8%AF%AD%E8%A8%80

机器语言(machine language)是一种指令集的体系。这种指令集称为机器码(machine code),是电脑的CPU可直接解读的资料。

所谓机器语言说白了就是一堆0101…的数字,无论任何语言最终都要翻译成机器码才能被CPU执行,下面来个示例感受一下性感的机器语言

  • 1011 代表赋值操作(MOV)
  • 0000 代表寄存器AL
  • 01100001代表数字97(十六进制表示为0x61)

所以要给寄存器AL赋值十进制数97用机器码写成

10110000 01100001

怎么样,性感不?任何程序最后在CPU看来都是0和1的排列组合,来看看上古时期怎么编程,就下图这玩意叫做打孔带:带孔为1,无孔为0,上古时期的程序就是一条条纸带,看得见,摸得着,怎么样,纸带是否更性感?

image

汇编语言

定义

汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。

在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。

在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。

特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。

定义很复杂,其实明白了机器码那么汇编其实很简单,汇编本质上就是机器码的助记符!比如上文提到的赋值操作1011 给它取个名儿就叫做MOV,MOV就是汇编语言,1000 代表寄存器AX,那么就给1000取个名儿叫AX,97的二进制为01100001,这个不好记,我们用十六进制表示成0x61,所以上文中性感的机器码

10110000 01100001

用汇编来表示就是

MOV AL,0x61

有没有感觉清爽了很多?至少看到汇编能猜到大概意思,看到机器码那就一脸懵逼。

但是汇编方便了人们记忆和理解,但是不利于CPU执行啊,所以产生了将汇编助记码翻译成机器码的东西,这个东西就叫做编译器。

所以IPL程序为什么要选汇编?

CPU只认识机器码,无论什么语言最后都变成机器码才能喂给CPU,编译器就是专门干这个事情的,看看主流的编程语言是怎么被CPU执行的

  • 汇编 → 机器码
  • C/C++ → 汇编 → 机器码
  • Java → 字节码 → JVM虚拟机(编译/解释) → 汇编 → 机器码

可以看到汇编离CPU最近(汇编本质上就是机器码的助记符而已),意味着更少的依赖以及可以操作更加底层的东西(比如直接操作CPU的寄存器)

假设用Java来编写IPL,那么它要依赖JVM虚拟机,JVM虚拟机装在哪?现在我们要制作OS,而JVM需要OS,这不就循环依赖了吗?那肯定不行!

假设用C/C++,C/C++要编译成机器码,中间也是要经过汇编的,而且很多C/C++做不到的事情还得需要汇编来做,那么为啥不直接用汇编呢?

所以经过上述分析,IPL程序用汇编编写是具有必要性的!

CPU构成

既然讲到汇编,那就不得不提CPU的构成,如下图

image

CPU构成包括计算单元(ALU)、寄存器(Register)、缓存(Cache)、程序计数器(Program Counter),寄存器和缓存都是存东西的,不过寄存器更快,大概比喻如下:

寄存器就好像你房间里床边的小抽屉,比较小,但伸手就能打开,速度贼快;

缓存(特指CPU的缓存)就像你房间里的衣柜,比较大,但是要起床才能打开,速度十分快;

物理内存(就是RAM,内库条,俗称条子)就像是你房间对面的仓库,容量很大,但是你要起床,开门,再去库房,拿完东西回来再关门,速度也挺快;

硬盘就像天安门广场,很宽很大很广,可以放下很多东西,但是你得起床,出门做公交或者地铁才能到,拿完东西再回家,那速度就比你家库房(内存)拿东西慢多了。

寄存器

那我们就来讲讲你房间床边的小抽屉,因为汇编里面全是对寄存器的操作,如果不清楚寄存器,会很懵

寄存器就是右边红框内的那一坨,这里展示的是32位的寄存器,寄存器都有自己的名字,所谓寄存器,简单来讲就是临时存下数据的,就像炒菜需要几个碗分别放葱姜蒜,炒的时候一股脑锅里倒就完事,那几个临时放放葱姜蒜的碗就是寄存器。

image

E为Extended的缩写,表示扩展;H为High的缩写,表示高位;L为Low的缩写,表示低位,例:AH表示AX寄存器的高8位

  • AX、BX、CX、DX四个16位寄存器用于通用目的,比如计数、存储数据等,其实就是有4个4字节的存储空间随便取个名字叫A、B、C、D而已,然后把前8位,前8~16位,前16位分别取个个名字而已AH、BH、CH、DH表示高8位AL、BL、CL、DL表示低8位EAX、EBX、ECX、EDX,把原来的16位扩展成32位
  • SI、DI、BP、SP四个寄存器都是16位的,各有各的用途,前面加个E表示32位的SI:Source Index,源变址寄存器,可以用来存放数据、地址等DI:Destination Index,目的变址寄存器,可以用来存放数据、地址等BP:Base Pointer,基数指针寄存器SP:Stack Pointer,堆栈寄存器,存放栈的偏移地址可以看到这几个寄存器都和内存地址有关系(指针),在32位的寄存器中能表示的最大内存就是232/1024/1024/1024=4GB,所以32位系统能寻址的最大内存就是4GB,超过4GB无法使用,现在的机器一般都是64位的寄存器了,64位的可以算算264/1024/1024/1024=17179869184GB=16777216TB,就目前而言,可以算作无限大了。
  • FLAGS/EFLAGS:表示当前运行状态的标识
  • IP/PC:IP即Instruction Pointer,PC即Program Counter,两者其实是一个东西,程序计数器,保存的是下一条指令的执行地址

你的床边就有这么多个小抽屉,汇编就是可以直接操作这些小抽屉的

二/八/十六进制

虽然汇编让我们脱离了阅读机器码的苦海,但是很多时候书写内存地址等的时候依然会用到十六进制来表示(因为写二进制太长了),比如如下表示写入0x55,0xaa两个字节

DB      0x55,0xaa

那为什么是十六进制而不是十五进制?十四进制呢?因为4个bit刚好有十六种排列组合,8个bit就是一个字节,所以两个十六进制的数就可以表示一个字节,下面是二进制-十六进制对照表,一般十六进制数前面有个0x,比如0x45表示十六进制的45而不是十进制的

二进制 十六进制 二进制 十六进制 二进制 十六进制 二进制 十六进制
0000 0 0100 4 1000 8 1100 C
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码狂魔v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值