自制操作系统(一) Hello, OS World!


今天开始学习自制操作系统课程,任务是搭建实验环境和简单了解汇编基础语法。 本次使用环境版本如下: https://gitee.com/zj1007/img/raw/master/img/image-20231206181209298.png

  • Ubuntu 23.10
  • VScode 1.84.2
  • VirtualBox 7.0.6

如果使用Windows环境建议安装msys2或WSL,以便在Windows中使用Linux命令工具。

安装nasm汇编工具

# ubuntu安装nasm汇编编译器
sudo apt install nasm
# 也可以在Windows+msys2环境中使用pacman包管理器安装
# pacman -S nasm
nasm -v
# 显示版本表示安装完成
NASM version 2.16.01

创建项目

mkdir -p ~/Code/myOSCode
cd ~/Code/myOSCode
touch hello.asm
# vscode 打开编辑编辑如下汇编程序
code hello.asm
org 07c00h ; 告诉编译器程序将装载至0x7c00处

    mov  ax, cs
    mov  ds, ax
    mov  es, ax  ; 将ds es设置为cs的值因为此时字符串存在代码段内
    call DispStr ; 显示字符函数
    jmp  $       ; 死循环

DispStr:
    mov ax, BootMessage
    mov bp, ax          ; es前面设置过了所以此处的bp就是串地址
    mov cx, 16          ; 字符串长度
    mov ax, 01301h      ; 显示模式
    mov bx, 000ch       ; 显示属性
    mov dl, 0           ; 显示坐标这里只设置列因为行固定是0
    int 10h             ; 显示
    ret

BootMessage: db "Hello, OS world!"
times 510 - ($ - $$) db 0 ;前510字节减去被使用的空间,剩余空间写入0
db 0x55, 0xaa ; 确保最后两个字节是0x55AA,才能被识别为可启动文件

这是一段简单的系统启动程序,占用磁盘的前512字节,功能是在屏幕上输出"Hello,OS World!"

编译程序并制作启动盘

# 将源文件编译为二进制程序
nasm hello.asm -o hello.bin
mkdir -p ~/VM/myOS/demo1/
# 使用dd命令将程序创建虚拟软盘并写入大小为512字节的启动程序
dd if=~/Code/myOSCode/hello.bin of=~/VM/myOS/demo1/mydisk.img  bs=512 count=1

使用VirtualBox注册该软盘

  • 找到虚拟软盘的路径并完成注册。

创建虚拟机

  • 系统类型为Other其他

https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-26-37.png

  • 内存大小为64M以内

https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-27-24.png

  • 不添加磁盘,之后再设置。

https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-27-35.png https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-27-47.png

  • 再设置->存储->添加控制器->I82078(软盘)

https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-29-30.png https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-29-48.png

  • 在软盘控制器中添加注册好的软盘

https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-30-12.png

启动虚拟机显示Hello, OS World!

https://gitee.com/zj1007/img/raw/master/img/2023-12-06-05-30-41.png

汇编

由于操作的系统的启动程序都是由汇编语言实现的,所以需要掌握基础的汇编知识。 汇编语言与CPU指令集是对应的,不同指令的CPU的汇编语言也不同。 目前主流架构的CPU有:

  • X86架构 1978年诞生于英特尔,之后由英特尔和AMD共同研发,是复杂指令集架构CPU,分为x86(32位)和amd64(64位),目前还是PC主流架构。
  • ARM架构 1985年ARM项目正式启动,它一款低功耗精简指令集(RISC)的CPU,目前是移动端的主流架构。
  • Power架构 1990年由IBM研发的精简指令集(RISC)架构,主要用于大型服务器和超算。
  • RISC-V架构 RISC-V(发音为“risk-five”)是一个基于精简指令集(RISC)开源指令集架构(ISA)。该项目2010年始于加州大学伯克利分校。
  • LoongArch架构 2020年,龙芯中科基于二十年的CPU研制和生态建设积累推出了龙架构(LoongArch™) 龙芯架构 LoongArch 是一种精简指令集计算机(RISC)风格的。龙芯架构分为 32 位和 64 位两个版本,分别称为 LA32 架构和 LA64 架构。

x86汇编指令

在汇编中,x86指令通常的助记符形式为:

# Intel语法表示的指令如下,目的操作数在前面:
助记符  目标地址,源地址
mov     ax,cs ;将段寄存器cs中的值赋值给累加器ax

助记符是人类可读的机器指令表示,源地址和目标地址是指令的操作数。如汇编指令mov rbx,rax就是将寄存器rax的值赋给rbx。注意并非所有的指令都有两个操作数,有些指令甚至没有操作数。

常见的x86指令

数据传输

  • mov dst,src ;将src赋给dst
  • xchg dst1,dst2 ;互换dst1和dst2
  • push src ;将src压栈,并递减rsp
  • pop dst ;出栈赋给dst,并递增rsp

算术

  • add dst, src ;dst +=src
  • sub dst, src ;dst –= src
  • inc dst ;dst += 1
  • dec dst ;dst –= 1
  • neg dst ;dst = –dst
  • cmp src1, src2 ;根据src1−src2设置状态标志位

逻辑/按位

  • and dst, src ;dst &= src
  • or dst, src ;dst |= src
  • xor dst, src ;dst ˆ= src
  • not dst ;dst = ~dst
  • test src1, src2 ;根据src1 & src2设置状态标志位

无条件分支

  • jmp addr ;跳转到地址
  • call addr ;压入返回地址到栈上,然后调用函数地址
  • ret ;从栈上弹出返回地址,然后跳转到该地址
  • syscall ;进入内核执行系统调用
  • 跳转分支(基于状态标志位)jcc addr仅在条件cc成立时才跳转到该地址,否则进入jncc相反条件,在条件cc不成立时跳转
  • je addr / jz addr ;如果设置ZF零标志位则跳转(如当上一个cmp中的操作数相同时)
  • ja addr ;上一次比较中,如果dst大于src则跳转(无符号)
  • jb addr ;上一次比较中,如果dst小于src则跳转(无符号)
  • jg addr ;上一次比较中,如果dst大于src则跳转(有符号)
  • jl addr ;上一次比较中,如果dst小于src则跳转(有符号)
  • jge addr ;上一次比较中,如果dst大于等于src则跳转(有符号)
  • jle addr ;上一次比较中,如果dst小于等于src则跳转(有符号)
  • js addr ;上一次比较中,如果结果为负则跳转,符号位置1

杂项

  • lea dst, src ;将内存地址加载到dst中,(dst=&src,其中src必须在内存)
  • nop ;空指令,不执行操作(用作代码填充)

首先,需要注意mov这个词并不准确,因为从技术上来讲不是将源操作数移动到目的操作数,而是对其进行复制,但是源操作数保持不变。其次,关于栈管理和函数调用,push和pop指令具有特殊意义。

通用寄存器

  • AX,BX,CX,DX称作为数据寄存器 AX (Accumulator):累加寄存器,也称之为累加器;
  • BX (Base):基地址寄存器;
  • CX (Count):计数器寄存器;
  • DX (Data):数据寄存器;
  • SP 和 BP又称作为指针寄存器 SP (Stack Pointer):堆栈指针寄存器;
  • BP (Base Pointer):基指针寄存器;
  • SI 和 DI又称作为变址寄存器 SI (Source Index):源变址寄存器;
  • DI (Destination Index):目的变址寄存器;
  • 控制寄存器    IP (Instruction Pointer):指令指针寄存器;    FLAG:标志寄存器;
  • 段寄存器   CS (Code Segment):代码段寄存器;   DS (Data Segment):数据段寄存器;   SS (Stack Segment):堆栈段寄存器;   ES (Extra Segment):附加段寄存器;