怎么开发操作系统?
作者:卡卷网发布时间:2024-12-24 00:56浏览数量:101次评论数量:0次
为了装杯,纪念Linus Torvalds开发的linux,我将自己开发的简易os命名为LIUNUXOS。
该系统源码接近5万行,麻雀虽小五脏俱全,具有完整的进程、线程调度、虚拟内存管理、文件读写(支持硬盘和光盘,fat32和ntfs等文件系统,主要是读),VESA支持下高分辨率的32位颜色的图形操作界面(虽然不美观)、图形字体(支持汉字点阵字体)、键盘鼠标驱动、SB声卡(播放wav)、cmd命令行、画图、右键菜单、图形化的文件资源浏览器、时钟等几个常用的程序。上述描述客观真实,如果大家觉得我说的太夸张,可以自行下载编译测试。
LIUNUXOS其原码分为两个部分,汇编工程和c/c++工程,地址分别为:
[点击此处,查看LIUNUXOS汇编工程原码](https://github.com/satadriver/LiunuxOS)
[点击此处,查看LIUNUXOS c/c++工程原码](https://github.com/satadriver/LiunuxOS_C)
在这些工程中,源程序的文件名和功能一一对应,从名字就可以猜出功能。
LIUNUXOS截图:
1. 汇编工程
汇编部分用16/32位x86汇编编写,开发工具是微软的masm和link,整个工程又分3个模块:
MBR:com格式程序,开机启动后执行,加载执行loader.com程序。x86架构规定,Bios开机自检后,加载硬盘第一个逻辑扇区到内存地址0:7c00h,cpu从该地址开始取指令并执行。mbr末尾包含64字节的DPT,在DPT前面是4字节的扇区号,存放着LIUNUX_OS_DATA结构体的扇区号,mbr调用bios功能int13h读写扇区。
loader.com:com格式程序,功能是加载执行kernel.exe。因为mbr只有一个扇区,除去64字节的DPT、末尾的结束标志0x55aa、4字节的LIUNUX_OS_DATA结构体的扇区号,只有446字节大小,无法实现kernel.exe模块的加载执行(可执行文件的格式一般比较复杂,内存加载运行前要读文件到内存、设置参数、重定向等),所以专门设计了loader程序来加载执行kernel.exe程序。
masm和link生成的exe是16位程序,该格式头部带有各种数据结构,而mbr扇区被加载到0:7c00后,cpu会直接跳转到该地址取指令并执行,所以写到mbr的内容需要是一个无任何格式的二进制指令流,16位的com程序编译后是一个纯指令文件,满足我们的要求。
由于微软的masm和link开发年代久远,已经不被64位操作系统支持,只能在32位系统下运行,故mbr和loader这两个模块,需要在windows xp 或者windows vista/7的32位平台下编译,编译方法是,在cmd中cd命令切换到汇编工程目录,执行如下命令编译链接:
masm mbr;
link mbr;
exe2bin mbr.exe mbr.com
masm loader;
link loader;
exe2bin loader.exe loader.com
注意不要忘记命令中的";",这是命令中的特殊符号。
这样编译出来的两个程序就是com格式的,没接触过com格式的请自行百度。
kernel.exe:一个既有16位又有32位代码段的混合程序,最新版已经将汇编基本去除,主要使用vs c/c++开发。最早的版本主要使用汇编,现在实际使用代码大约2000行。该模块功能主要有:
- 硬件初始化和设置。实模式下设置cmos时钟、8254计时器、ps2鼠标、8042/8048键盘、rs232串口、8259中断控制器、中断门、陷阱门、任务门、IDT、GDT、TSS、LDT等。8254中断周期1毫秒并作为任务切换的动力。8259采用完全嵌套模式(FNM)。桌面显示的时间来自于CMOS实时钟。GDT采用平坦模式(段基地址0,长度4GB),4个4GB段,其中2个代码段(一个内核代码段和一个用户代码段)和2个数据段(一个内核数据段一个用户数据段)。IDT中,8259芯片对应的硬件中断向量被映射到0x40-0x50,中断向量除了第0x40号和第8号采用任务门,其他中断向量都采用中断门和陷阱门。
- 中断向量入口。中断发生后进入本模块的相应中断向量执行,并跳转进入c语言开发的kernel.dll中的函数地址中执行。kernel.dll模块是进入保护模式后,通过内存加载实现的,并将其导出函数保存在0-1M的物理地址空间中,能被实模式和保护模式共同访问的地址空间中。
- 调用bios中VESA的int10h接口,设置图形化显示模式。
- 进入32位保护模式,用32位汇编实现了windows PE文件的加载执行功能(主要包括虚拟段内存映射、导入导出表设置、重定位、设置入口点并跳转执行)。主要是在保护模式中加载kernel.dll并跳转到__kernelEntry函数开始执行。
- __kernelEntry函数做完操作系统的初始化工作,比如进程\线程切换、虚拟内存的初始化等,执行sti指令打开中断,待第一个时钟中断到来后,系统进入多进程/多线程模式,cpu被调度。
- 汇编代码对IDE扇区的读写。
- v86模式下,基于int13h实现的扇区读写模块。因为磁盘驱动器有ACHI,sata等多种,本系统的硬件驱动支持比较简陋,因此使用v86模式下的扇区读写模式,作为文件读写的底层实现,并通过软中断被上层程序调用,实现全兼容的文件读写。
- 文本和图形化的日志/错误输出功能,发生系统异常时会显示异常信息。
kernel.exe程序的32位代码是整个系统的核心。现代计算机一般都是基于中断和异常驱动,该程序正是中断和异常的触发、中转中心,比如说,基于8254时钟计时器和cmos时钟计时器的中断被当作cpu时间片轮转切换的动力,时钟中断的时间间隔就是一个时间片,在中断到来时,在中断服务程序中完成进程和线程的切换和调度;键盘鼠标的中断被分发到kernel32.dll中的__kKeyboardProc函数和__kMouseProc函数中处理;所有的异常被分发到kernel32.dll中的__kException函数处理。当然,由于kernel.exe使用汇编编写,难以实现复杂功能,因此只是中断和异常的分发中心,中断服务的功能都是跳转到kernel.dll中完成。
整个汇编工程没有很复杂的模块,最大的单个文件也就500百多行,汇编虽然晦涩难懂,但只要花点时间,对有经验的同学,还是很容易上手的。另外,开发目录中包含debug.exe和debug32.exe,这两个工具可以帮助汇编的开发调试。
kernel.exe虽然是整个系统的核心,但却是用汇编编写的,正常的设计,应该是使用汇编进入保护模式并加载C/C++模块,然后在C/C++模块中设置GDT和中断描述符等CPU中断异常所需依赖的硬件设置,也就是说尽量少的使用汇编代码。
当开发目标、模块较小、功能简单时,汇编和高级语言的差别还不明显,但是当开发目标、模块比较复杂、逻辑功能比较强大时,汇编的开发速度、可调试性、可扩展性、可维护性等直线下降。因此,操作系统必须使用高级语言开发,这也是必须费大力气在汇编中实现pe文件加载执行的原因,剩下的几个核心模块如kernel.dll、main.dll以及系统的安装模块liunuxSetup.exe、liunux_seutp,都是在visual stuidio中使用c/c++或者linux gcc中开发的。
liunuxos开机启动界面:
启动时,系统要选择一种分辨率的图形显示模式,按数字2选择1600x1200x32位显示模式:
如图中所示,系统左下角是不断刷新的时间日期,右下角是一个右键菜单,右边是我的电脑、光盘、软盘等文件浏览器入口,点击即可进入文件浏览器界面:
按F1启动命令行窗口程序(窗口一般只有边框、客户区和一个关闭按钮),并从键盘输入输入"hello liunuxos!",按ESC退出cmd:
2. c/c++工程
c/c++工程包含4个模块:liunuxSetup.exe(windows系统下的安装程序)、linux_setup(linux系统下的安装程序)、kernel.dll、main.dll。其中,windows安装程序和linux安装程序用于把程序安装到windows或者linux系统中。kernel.dll和main.dll是liunuxos的功能核心,几乎所有的复杂功能,包括进程\线程的切换、虚拟内存机制、文件读写、内核功能都是在kernel.dll中完成的,而几乎所有的应用模块,包括文件浏览器、命令行程序以及几十条命令的实现,都是在main.dll中完成的。
liunuxSetup.exe
liunuxos系统只能使用virtualbox虚拟机,其他虚拟机如vmware不支持vesa图形模式,故无法支持图形模式。
liunuxos安装时需要如下几个程序在用一个目录中:mbr.com、loader.com、kernel.exe、 kernel.dll、main.dll、liunuxSetup.exe(windows系统)、linux_setup(linux系统)、font.db(英文字体)、HZK16(汉字16X16字体,可选)。BIOS从内存地址0xffa6e开始的区域中,按照ascii字符顺序存放着所有ASCII编码的8x8点阵字体,font.db中的字体正是从该地址中提取出来的。HZK16是从网上搜集到的汉字16x16点阵字体,显示方式和ASCII的一致。
下图是liunux安装时需要的程序截图:
点击安装程序liunuxSetup.exe(Linux系统中运行linux_setup)即可完成LIUNUXOS的安装,安装时截图如下:
安装程序liunuxSetup.exe和kernel.dll、kernel.exe借助LIUNUX_OS_DATA结构体,完成新系统的启动以及安装文件的查找,LIUNUX_OS_DATA结构体定义如下:
typedef struct
{
int flag; //0 标志 LJG0
short loaderSecCnt; //4 loader占用扇区数
int loaderSecOff; //6 loader扇区号偏移
short kernelSecCnt; //10 kernel.exe占用扇区数
int kernelSecOff; //12 kernel.exe扇区号偏移
int mbrSecOff; //16 mbr扇区号偏移
int mbr2SecOff; //20 mbr2扇区号偏移
short fontSecCnt; //24 字体占用扇区数
int fontSecOff; //26 字体扇区号偏移
short kerdllSecCnt; //30 kernel.dll占用扇区数
int kerdllSecOff; //32 kernel.dll扇区号偏移
short maindllSecCnt; //36 main.dll占用扇区数
int maindllSecOff; //38 main.dll扇区号偏移
char reserved[22]; //42 保留
}LIUNUX_OS_DATA,*LPLIUNUX_OS_DATA;
liunuxSetup.exe双击运行后,在磁盘上查找超过512kb的连续的扇区块(扇区一般默认是512B大小),一般是利用ntfs或者fat32文件系统中的磁盘间隙。安装时,这片空闲扇区的第一个扇区为起始扇区,根据这个值和各个安装文件的大小,分别计算LIUNUX_OS_DATA结构中各个字段的值,然后,将LIUNUX_OS_DATA结构体写入空闲扇区的第一个扇区,并将安装需要的各个文件的起始扇区号和扇区数依次写入结构体中的响应字段。最后,将http://mbr.com扩展到512字节(当前的MBR代码不到400字节大小),64字节DPT 从设备"\\\\.\\PHYSICALDRIVE0"(windows系统)或者"/dev/sda"(linux系统)读取,在DPT前面4字节记录了LIUNUX_OS_DATA所在的扇区号,mbr扇区末尾写入X55AA,并将此扇区写入MBR中,下次启动时,mbr代码将会从LIUNUX_OS_DATA中找到并加载执行http://loader.com,http://loader.com又会从LIUNUX_OS_DATA中加载执行kernel.exe,同时将kernel.dll、main.dll、font.db读入内存中。
因为http://loader.com是纯的二进制指令流,加载执行非常简单,几十行汇编即可完成,但是加载执行kernel.exe就比较复杂,无法在MBR中完成,因此设计了loader.com,主要功能是,使用汇编代码实现16位exe文件的内存加载、重定位和执行。
linux_setup
linux系统下的安装程序linux_setup大约也是相似功能。但是其读写的磁盘设备是/dev/sda。
kernel32.dll
kernel32.dll是最主要的功能模块,也是代码最多的模块。其主要包括以下模块:
1. 进程、线程的创建和调度。时钟计时器每过一个计时周期就会触发一次中断,这就是liunuxos的线程切换频率,大概为1ms。系统支持两种进程/线程切换方式:
第一种只需要一个tss任务状态段。进入时钟中断后,中断程序保存如eax,ecx,edx,ebx,esi,edi,ebp,esp,eip,ds,es,cs,fs,gs,ss,eflags等通用寄存器、浮点寄存器等所有硬件寄存器,确保当前的执行现场环境被完全保存,在切换后的新的内核堆栈中依次push进新线程的上述被保存的现场寄存器,然后执行iret指令,这样新的所有现场寄存器被弹出到新线程中,实现进程/线程的无缝切换。
第二种需要两个tss,一个是所有进程共享的,另一个在时钟中断任务门中。时钟中断发生时,硬件自动完成任务切换,当前被挂起进程的现场寄存器被自动保存在所有进程共享的tss中,在中断任务门指向的中断服务程序中,将所有进程共享的tss保存的现场内容拷贝到内存中的该进程的信息表中(进程信息表基地址在系统启动时被设置,通过pid可以找到进程的所有信息和寄存器、现场数据等,相当于eprocess),然后把要被调度执行的进程的tss数据复制到共享tss中,时钟中断程序执行iret指令返回后,硬件自动完成任务切换,新的进程/线程现场自动被切换。进程调度使用简单的轮转调度,按照pid进程号,每个进程依次顺序执行/挂起。
应用进程栈的初始化大小是1MB,系统进程的栈的初始化大小是64kb。
进程是资源的载体,其每个线程都是单独调度的,中断程序调度时,会判断执行线程和挂起线程的pid,如果不同则修改cr3,相同则不修改cr3,跟windows/linux一样,利用pid这一标识判断是否是同一个进程。
该模块支持任意pe文件的加载执行。
liunuxos不支持apic模式,也就是说,虽然支持多进程多线程,但是只支持一个cpu。
2. 文件读写。主要是支持ntfs、fat32、iso9660文件系统的读操作。网上文件系统的资料很多,简单讲就是从扇区到文件结构的映射关系、以及硬件端口的扇区读写操作,当然,为了逃避硬件局限,代码中专门内置了一个v86模式的进程,主要是用虚拟16位模式下的int13h来调用bios的扇区读写接口,以便实现文件读取,至于v86模式的细节,可自行查看代码,探究实现过程,总之,这里面的道道很多。另外需要注意的是,在virtualbox中创建虚拟机时,只能选择ATA或者SATA模式。
3. 内存管理。主要是内存分配和虚拟内存的实现。关于虚拟内存,网上资料很多,众说纷纭但是缺乏切实可行的方法。我的做法分为两步:首先,kernel.exe在保护模式中,开启分页,具体的分页策略是:将物理地址和线性地址一一对应,具体实现也很简单,只要明白了分页的原理,实现起来比较容易。第二步,类似与windows和Linux系统中应用程序共享系统空间的策略,在创建进程时,复制填充系统空间的部分页表页目录表,其他页表设置为0,同时调用系统空间中的内存分配函数(内存分配采用slab算法,系统空间中内存的物理地址等于线性地址,因此分配的内存即是物理地址又是线性地址),分配的内存大小页面对齐,然后修改应用进程的页表项,并将其映射为从4gb依次往低地址递减的虚拟线性地址。
此种分页下,系统进程的线性地址都是物理地址,并且可以被每个用户进程访问,但是应用程序的代码段、数据段、堆栈段地址都是私有且线性递减的。
liunuxos虽然支持虚拟内存,但是不支持内存交换等功能,因此远没有主流操作系统那么强大。
liunuxos具体的内存地址分布和布局,可以查看def.h文件。
4. 键盘/鼠标功能。支持按键输入、读取、点击、位置等信息。键盘是对8042芯片的编程,涉及扫描码和ascii码的转换和CTRL、ALT、SHIRT以及各种控制按键的组合处理等,鼠标涉及到鼠标图像的擦除和显示,鼠标按键的信息存取。键盘/鼠标按键信息的存取通过软中断接口实现,应用程序调用软中断可以获取/插入键盘鼠标信息。只能支持ps/2鼠标,virtualbox中设置如下:
5. soundblaster声卡播放wav格式音乐。虚拟机创建时必须指定声卡为soundblaster型,主要涉及DMA和SB芯片的编程。声卡需要DMA的支持,需要注意的是,ISA总线能访问的地址空间在0-16M之间。需要在virtualbox中创建soundblaster声卡:
6. 单步断点、调试断点的中断服务程序,另外涉及调试寄存器的编程。
7. 图形接口。窗口(类似于windows中的窗口)、矩形、圆、点、线的绘制填充等简单图形接口。比较简单,涉及简单的数学知识,通过一些简单的数学公式在显存中生成图形。
8. 浮点中断的简单处理。主要是中断的接口,没什么实质内容,功能包括浮点指令的保存和恢复、浮点异常的打印输出。
9. elf文件和pe文件的加载执行。主要是这两种文件格式的内存加载和执行,包括段的拷贝、导入导出地址计算、重定位、修改入口地址、设置参数、跳转入口地址、执行等步骤,实现可执行文件的内存加载和执行。都是一些细节,网上资料很多,可自行百度。
10. 文本的图像模式显示,bios中从地址0xfae6c存放了所有ascii的8x8点阵图形字体 。另外,也支持汉字16x16点阵字体。通过读取PCI总线配置寄存器0xcf8、0xcfc可以获取显示设备的显存地址,通过直接写显存的方式,一行一行写入字体的像素值显示图形字体。
11. bmp文件的显示。打开并显示任意格式bmp文件。bmp文件的一个特点是像素数据按行倒序存放,按照文件位置的先后顺序读取像素数据后,第一行存放的的是最后一行的像素数据。
12. 串口支持。需要在virtualbox中创建串口:
13. 右键菜单。
14. 汉字点阵字体。
15. 画图程序
16. 16位dos程序的支持。兼容dos程序的运行。
17. 其他一些自己实现小程序。
(1). 三球体螺线运动动画。
(2). 自由落体小球
(3). 时钟
(4). 唐诗
(5). 动态渐变图形
main.dll
1. 实现了类似于windows cmd的shell环境,支持几十条命令的执行,都是一些琐碎的细节,详细内容自行查看代码。
2.实现了类似于windows桌面程序explorer.exe。右边的三个棕色图标是磁盘、光盘、软盘,点击可以分别打开,类似于文件管理的效果。桌面右下角有一个右键菜单,支持多个命令操作。最主要的还是图形化的文件浏览器,通过鼠标双击可以层次化的浏览磁盘中的文件,点击显示文本文件、bmp文件、运行可执行程序等。
在liunuxos开发过程中,有以下基本问题未解决:
- 多核机制未实现。
- 未实现64位模式,这是另一个的遗憾。
- 硬盘硬件接口资料少,未实现健壮的硬件相关的磁盘控制器读写命令接口,另外未支持linux/unix系统类ext文件系统接口,这是第三个遗憾。
- 感受最深的就是,对硬件设备的了解非常少。虽然虚拟机中可供驱动的设备很多,但是硬件设备需要了解的细节过多、操作过于繁琐,对电气特性不够了解,也看不懂硬件电路和文档,是liunuxos开发中的最大障碍。
免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。
相关推荐

你 发表评论:
欢迎