计算机自制操作系统:完成总结
本次我从0开始,自制操作系统的探索就到这里要告一段落了。最大的感受就是所幸坚持下来了。这个操作系统的代码是由我编写、调试和验证过的,锲而不舍的最终战胜了所有的困难。
这个过程是极度枯燥和乏味的,有时为了解决疑惑甚至通宵失眠,但一旦破解之后就兴奋不已。最后要感谢自己一路下来通过博客的方式记录下来了当时的所思和所得,不然若干年后就一定会全部遗忘。
让我们来看看最终成果

基本的操作系统
一个基本的操作系统通常包括以下几个主要组成部分:
-
引导加载程序(Bootloader):
- 引导加载程序是操作系统启动的第一个程序,负责将操作系统的核心部分加载到计算机的内存中。它通常存储在**引导扇区(boot sector)**中,是启动过程中的关键组件。
-
内核(Kernel):
- 内核是操作系统的核心部分,负责管理系统的硬件和提供基本的系统服务。内核通常包括处理器管理、内存管理、文件系统、设备驱动程序等模块。它是操作系统与硬件之间的核心交互点。
-
文件系统:
- 文件系统负责管理存储设备上的文件和目录结构。它提供了文件的组织、存储和检索机制,允许用户和应用程序通过文件名访问数据。
-
进程管理:
- 进程管理是操作系统的一个重要功能,涉及到进程的创建、调度、同步和通信。操作系统通过进程管理来确保多个任务(进程)能够共享系统资源,并协调它们的执行。
-
内存管理:
- 内存管理负责分配和释放内存,以及管理虚拟内存。它确保每个进程都有足够的内存空间来执行,并防止进程之间相互干扰。
-
设备驱动程序:
- 设备驱动程序是连接操作系统和硬件设备的桥梁。它们允许操作系统与各种硬件设备(如磁盘驱动器、打印机、键盘等)进行通信。
-
用户界面(可选):
- 操作系统可能包含一个用户界面,用于与用户交互。用户界面可以是命令行界面(CLI)或图形用户界面(GUI),提供用户友好的方式执行命令和访问系统功能。
-
系统调用接口:
- 系统调用是用户程序与操作系统之间的接口,允许应用程序请求操作系统提供的服务,例如文件操作、进程创建等。系统调用是用户空间和内核空间之间的桥梁。
每个部分都承担特定的任务,协同工作以实现计算机系统的各项功能,为用户和应用程序提供了管理和访问计算机系统资源的环境,形成一个完整的操作系统。
自制操作系统的一般步骤和总结
开发一个操作系统是一项复杂的任务,特别是涉及到底层系统编程和硬件交互。本人使用CentOS、VSCode和QEMU进行操作系统开发的基本开发过程和注意事项:
开发过程:
-
设置开发环境:
- 安装CentOS作为开发环境(我的版本是7.4),并确保系统中已经安装了一些基本的工具,通过命令行自行安装,如
GCC编译器、Makefile工具等。 - nasm安装
sudo yum install nasm
- 安装CentOS作为开发环境(我的版本是7.4),并确保系统中已经安装了一些基本的工具,通过命令行自行安装,如
-
安装VSCode:
- 安装VSCode作为主要的集成开发环境,它提供了强大的代码编辑和调试功能,以及通过插件通过远程ssh来实现的。
-
安装QEMU:
- 安装QEMU模拟器,它可以用于在虚拟环境中运行和测试操作系统。
- 运行指令
qemu-system-i386 "F:\shios-master\VMShare\ShiOS.img"
-
编写引导加载程序(Bootloader):
- 创建一个引导加载程序,它是操作系统启动的第一阶段。可以使用汇编语言编写引导加载程序,确保它能够正确地加载操作系统内核。
-
编写内核:
- 使用汇编语言和C语言编写操作系统内核。内核应该包括处理器管理、内存管理、中断处理等基本功能。
-
编写设备驱动程序:
- 开发与硬件设备通信的设备驱动程序,例如磁盘驱动器、键盘驱动等。
-
实现文件系统:
- 开发一个简单的文件系统,以支持文件的读写和管理。
-
配置QEMU启动:
- 配置QEMU以加载你的引导加载程序和内核,以便在虚拟环境中测试操作系统。
- 这里我是生成img文件在本地运行qemu
-
调试和测试:
- 使用VSCode的调试功能和QEMU进行调试,确保各个组件正常运行。
- QEMU + GDB调试
yum install gdb
-
优化和改进:
- 根据测试和调试的结果,不断优化和改进操作系统,处理可能出现的错误和性能问题。
开发目录
|-- ShiOs
|-- applib
| |-- api.asm
| |-- api.h
| |-- Makefile
|-- apps
| |-- notepad
| |-- main.c
| |-- Makefile
|-- bootloader
| |-- boot.asm
| |-- loader.asm
| |-- Makefile
|-- kernel
| |-- Makefile
| |-- src
| |-- bitmap.c
| |-- common.h
| |-- disk.c
| |-- explorer.c
| |-- fat16.c
| |-- fifo.c
| |-- font.asm
| |-- func.asm
| |-- gdt.c
| |-- graph.c
| |-- int.c
| |-- keyboard.c
| |-- layer.c
| |-- main.c
| |-- memory.c
| |-- message.c
| |-- mouse.c
| |-- page.c
| |-- right_menu.c
| |-- stdio.c
| |-- string.c
| |-- syscall.c
| |-- task.c
| |-- timer.c
| |-- window.c
|-- .gitignore
|-- LICENSE
|-- Makefile
|-- README.md
碎片内容讲解
1. x86 架构
- 概述: x86 架构是一种计算机处理器架构,最初由 Intel 公司设计。它广泛用于个人计算机和服务器。x86 支持多种工作模式,包括实模式和保护模式。
2. 实模式介绍
- 实模式: 实模式是 x86 架构的一种工作模式,主要用于系统启动时的初始化。在实模式下,处理器以 16 位模式工作,可以直接访问 1MB 的物理内存。
3. NASM 汇编语言
- NASM: NASM(Netwide Assembler)是一种汇编语言,用于编写 x86 架构的汇编代码。它支持多种指令集,是编写引导加载程序和操作系统内核的常用工具。
4. MBR 介绍
- MBR(Master Boot Record): MBR 是硬盘上的第一个扇区,512 字节大小。它包含引导加载程序和分区表信息,是计算机启动时 BIOS 执行的地方。
5. QEMU+GDB 调试
- QEMU: QEMU 是一款模拟器,可用于模拟多种硬件架构,包括 x86。它支持将操作系统运行在虚拟环境中。
- GDB: GDB(GNU Debugger)是一个调试工具,可与 QEMU 配合使用,用于在模拟环境中调试操作系统代码。
6. 向 MBR 中写入程序
- 目的: 向 MBR 中写入程序是为了将自定义的引导加载程序加载到计算机启动时的第一个扇区,以执行自制操作系统。
- 方法: 使用工具如
dd命令或编写程序通过 BIOS 中断调用将程序写入硬盘。
7. 屏幕显示原理与文本模式
- VGA 文本模式: 在实模式下,屏幕显示通常采用 VGA 文本模式。它通过文本缓冲区和显存地址显示字符。
8. 用汇编向屏幕输出字符
- 目标: 在实模式下,使用汇编语言编写程序,通过 BIOS 中断调用或直接访问显存地址,向屏幕输出字符。
- 汇编指令: 使用汇编指令如
mov、int等实现字符输出。
9. 封装打印字符串函数
- 封装函数: 创建一个汇编函数,接受字符串参数,通过循环逐字符输出到屏幕。
- 重用性: 封装函数可重复使用,方便在操作系统的不同部分调用。
10. 用汇编语言清空屏幕
- 方法: 使用汇编语言编写程序,通过 BIOS 中断调用或直接访问显存地址,将屏幕上的字符清空。
- 指令: 使用汇编指令如
mov、int等实现屏幕清空。
11. 外设和 I/O
- 概念: 外设是连接到计算机系统的设备,包括键盘、鼠标、显示器、硬盘等。I/O(Input/Output)则是指计算机与外设之间的数据输入和输出操作。
- 作用: 外设扩展了计算机的功能,而 I/O 是计算机与外设进行数据交互的方式。
12. 硬盘读写理论知识
- 硬盘结构: 硬盘由多个盘片组成,每个盘片分成多个磁道,每个磁道又分成多个扇区。
- 读写过程: 读写头通过移动到特定的磁道和扇区,进行数据的读写操作。硬盘读写通常是以扇区为基本单位进行的。
13. 汇编读硬盘
- 读取扇区: 使用 BIOS 中断调用或直接访问硬盘控制器端口,加载硬盘扇区的数据到内存。
- 寄存器设置: 设置寄存器参数,包括磁头号、扇区号、磁道号等。
- 数据传输: 通过指令(如
int 13h)进行硬盘读取操作。
14. 汇编写硬盘
- 写入扇区: 类似读取,通过适当的设置寄存器和端口,将数据写入指定的硬盘扇区。
- 数据准备: 在内存中准备待写入的数据,并通过适当的寄存器传递给硬盘控制器。
- 写入操作: 使用适当的指令(如
int 13h)执行硬盘写入操作。
15. 文件系统与 FAT16
- 文件系统: 文件系统是一种用于组织和存储文件的结构。FAT16(File Allocation Table)是一种简单的文件系统,使用 16 位的文件表项来管理文件分配。
- FAT 表: FAT16 中包含一个或多个 FAT 表,记录文件的分配情况。每个表项对应一个簇,用于表示文件存储位置。
16. 从硬盘读取文件
- 读取文件数据: 根据文件的 FAT 表信息,确定文件的簇链,然后通过读取硬盘上对应簇的数据块,将文件加载到内存。
- 扇区与簇: FAT16 中通常将多个扇区组成一个簇,根据簇号从硬盘读取数据。
17. 加载并运行 Loader
- Loader 概念: Loader 是引导加载程序,负责从硬盘读取操作系统内核并加载到内存中。
- 加载过程: Loader 根据操作系统的文件路径和加载地址,从硬盘读取内核数据,然后通过跳转指令将控制权转交给内核。
18. 理解和补充
- 硬盘编程的挑战: 硬盘编程涉及与硬件直接交互,需要理解硬盘结构、寄存器设置和底层 I/O 操作。
- 调试技能: 在汇编语言层面编写硬盘相关代码时,熟悉使用调试工具(如 GDB)来追踪和调试代码是至关重要的。
- 数据结构的影响: 理解文件系统和簇的概念,了解文件的组织方式对于操作系统的整体性能和文件管理至关重要。
注意事项:
-
熟悉目标平台:
- 确保你了解目标平台的体系结构和特性,例如
x86或x86-64。
- 确保你了解目标平台的体系结构和特性,例如
-
版本控制:
- 使用版本控制系统如
Git,来跟踪和管理你的操作系统项目。
- 使用版本控制系统如
-
文档编写:
- 编写详细的文档,包括代码注释和项目文档,以便他人理解和维护你的操作系统。
-
备份和恢复:
- 定期备份你的工作,以防止意外数据丢失。确保你可以轻松地恢复到之前的工作状态。
-
参与社区:
- 加入操作系统开发社区,与其他开发者交流经验、解决问题,好的解决办法是可以加群询问大佬。
-
安全性考虑:
- 考虑操作系统的安全性,避免潜在的安全漏洞。
-
逐步迭代:
- 采用逐步迭代的开发方式,一步一步地增加功能和改进,以确保系统的稳定性和可维护性。
-
学习资源:
- 利用在线教程、操作系统开发书籍和相关论坛,不断学习和提升你的操作系统开发技能。
bochs: <https://bochs.sourceforge.io>
qemu: <https://www.qemu.org/docs/master/>
gcc: <https://gcc.gnu.org/>
gdb: <https://www.sourceware.org/gdb/>
nasm: <https://www.nasm.us/>
binutils: <https://www.gnu.org/software/binutils/>
vmware: <https://www.vmware.com/products/workstation-pro.html>
vscode: <https://code.visualstudio.com/>
ffmpeg: <https://ffmpeg.org>
python: <https://www.python.org/>
pyelftools: <https://github.com/eliben/pyelftools>
说说
第一个操作系统
- 大部分的知识都是从B站得到的🙇,跟着up手敲的第一个操作系统

Github中30’s自制操作系统
- 该系统是Github上大佬开源的30天自制操作系统,从这里学到了很多知识

Onix操作系统源码分析
- 该系统@StevenBaby,硬核知识讲解


目前
- 截止到如今简单的操作系统,我对操作系统的探索就到这里了,数个日夜的开发调试让我觉得热爱可抵岁月漫长,这里引用稚晖君的一段话
立于皓月之边,不弱星光之势。


- 该项目的系统镜像文件部分所需的源程序制作makefile配置表
.PHONY:all clean
#定义变量#
diskName=ShiOs.img
diskPath=/media/VMShare/$(diskName)
#共享文件夹中的软盘镜像文件mount后,向其复制较大文件时会会使Linux奔溃重启。在非共享文件夹中复制一个代理镜像进行mount操作。
diskAgent=/root/$(diskName)
mountPath=/mnt/
ENTRY_POINT=0x2000
SRC_DIR=src
BUILD_DIR=build
C_FILES=$(wildcard $(SRC_DIR)/*.c)
ASM_FILES=$(wildcard $(SRC_DIR)/*.asm)
C_O_FILES=$(patsubst $(SRC_DIR)%.c,$(BUILD_DIR)%.o,$(C_FILES))
ASM_O_FILES=$(patsubst $(SRC_DIR)%.asm,$(BUILD_DIR)%.o,$(ASM_FILES))
ELF_FILE=$(BUILD_DIR)/kernel.elf
BIN_FILE=$(BUILD_DIR)/kernel.bin
AS=nasm
CC=gcc
LD=ld
ASFLAGS=-f elf32 -g # -g产生调试信息
#-fno-builtin 不采用C语言的内建函数,解决与内建函数重名问题。
#不要被-Wall的名字迷惑,它并没有开启所有告警,-Wextra用于启用一些未由-Wall启用的额外警告标志。 (此选项过去称为-W ,旧名称仍然受支持,但更新的名称更具描述性。
# -g产生调试信息
CFLAGS=-m32 -c -fno-builtin -g -std=c99 -Wall -Wextra
#-Map顺便生成map文件,可以查看符号的内存布局等信息。-Ttext设置.text节的地址。
LDFLAGS=-m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
all: $(BIN_FILE)
# $<表示规则中依赖文件中的第1个文件
# $@表示规则中的目标文件名集合
#c文件转.o文件
$(BUILD_DIR)/%.o:$(SRC_DIR)/%.c
$(CC) $(CFLAGS) $< -o $@
#汇编文件转.o文件
$(BUILD_DIR)/%.o:$(SRC_DIR)/%.asm
$(AS) $(ASFLAGS) $< -o $@
#链接所有目标文件,生成可执行文件。#链接时必须把main.o文件放在第一个,源代码中main函数也必须是main.c中的第1个函数。
$(ELF_FILE): build/main.o $(ASM_O_FILES) $(C_O_FILES)
$(LD) $(LDFLAGS) $^ -o $@
objdump -s -d $(ELF_FILE) >$(ELF_FILE).dis -M intel
#从ELF格式文件中抽取出纯二进制文件
$(BIN_FILE):$(ELF_FILE)
objcopy -I elf32-i386 -O binary $(ELF_FILE) $(BIN_FILE)
rm -f $(diskAgent)
cp $(diskPath) $(diskAgent)
mount $(diskAgent) $(mountPath) -t msdos -o loop
cp $(BIN_FILE) $(mountPath)
sync
umount $(mountPath)
rm -f $(diskPath)
cp $(diskAgent) $(diskPath)
clean:
rm -rf $(BUILD_DIR)/*
- 运行系统
all:
make -C ./bootloader
make -C ./kernel
make -C ./applib
make -C ./apps/notepad
clean:
make clean -C ./bootloader
make clean -C ./kernel
make clean -C ./applib
make clean -C ./apps/notepad
newdisk:
rm -f /media/VMShare/ShiOS.img
dd if=/dev/zero of=/media/VMShare/ShiOS.img bs=1M count=4
结束语
在这个过程中,我不仅仅学到了如何构建一个操作系统,更深刻地理解了计算机科学的奥妙。每一个Bug都是一次挑战,每一行代码都是一次成长。用汗水和坚持书写了这段代码的篇章,让操作系统的每一个模块都闪耀着智慧和毅力。
这篇博客没有开放该系统的源码,我认为任何一个做底层系统的开发者都会享受解决bug后的喜悦,所以希望大家从0开始构建属于自己的OS
这个操作系统不仅仅是代码的堆砌,更是我们对未来技术发展的一份贡献。它可能只是一个小小的实验,但它代表了创新的力量和对未知的探索。我珍惜这段经历,保持对技术的热情,不断追求卓越。无论是在代码的海洋中徜徉,还是在Bug的迷雾中前行,都要坚信,每一步都是通向成功的阶梯。
愿每一个创造者都能在技术的世界中发光发热,书写属于自己的传奇!
[Mr.Shi]
