Skip to content

W1OS自制操作系统

字数
1448 字
阅读时间
7 分钟
更新日期
8/3/2019

参考链接

前言

做一个自己的操作系统一定是很一件很酷的事。今天仔细读了下上面三篇文章,都非常不错,不过比较遗憾的是除了第一节的hello world之外,后面的数据结构的定义,各种模式的转换都非常脑坑疼。为了能尽快做出一个成品来获得正反馈(zhuangb),我将中间一系列过程能省则省掉了,最终能最简单展示一个helloworld的窗口程序,来增强一下满足感。

普通流程

虽然我力图精简大部分的流程,但是操作系统的制作流程还是需要明白的。

软盘读写

首先BIOS只能读取软盘512个字节,但是操作系统内核明显会超过它,所以一般这512字节都会写一个内核加载器,通过它来加载我们真正的内核。

内核加载器

内核加载器的作用是什么呢,它用来告诉BIOS,“来加载512字节之后的数据吧~”。同时我们还要注意我们编译出来的img的结构大小,前512字节是加载器的二进制代码,不足的用00来代替,后面是我们真正内核的代码。需要注意的是,内核加载器由于硬件(软盘)的结构限制,只能以512字节为单位加载内核,如果内核超出,需要及时修改加载器,告诉它“需要加载更多内核代码”。

保护模式32位寻址

计算机默认进入的实模式,但是它的功能有限,我们需要操作CPU来进入“保护模式”,上面的参考文章也会在一开始告诉你,如何进入保护模式。总之就是定义一堆看不懂的数据结构~

跨平台C语言的编译

进入到保护模式后,就可以访问到更大的内存空间了,接下来就是抛弃汇编,使用C语言编写内核。首先的问题是,C语言编写的程序如何让我们内核识别到?参见https://github.com/cfenollosa/os-tutorial/blob/master/11-kernel-crosscompiler/README.md,使用我们系统的gcc编译一个不带任何依赖的gcc即可(听起来有点好玩,就和go语言用go写的一样),然后在汇编种预留c语言的接口

assembly
[bits 32]
[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $

之后用编译出来的gcc编译c语言代码,汇编编译汇编代码,将两个文件合起来即可。

绘制界面

如果要绘制界面,需要用汇编调用BIOS的中断,操作硬件打开显卡色彩功能(可以理解为用汇编操作BIOS的API)。

打开VGA显卡

mov  al, 13h
mov  ah, 0x00
int  0x10

画像素点

mov  ah,0ch
mov  al,10;颜色值 1~256
mov  dx,1 ;x坐标
mov  cx,1 ;y坐标
mov  bh,0
int  0x10

颜色值

1920px-VGA_palette_with_black_borders.svg

通过一个像素点,就可以画整个图形界面了(有点以前OpenGL写游戏的味道)

精简流程

为了最快自制一个带界面的操作系统,我精简了一个流程(虽然还是花了一天)。我们直接用汇编操作BIOS的显卡功能,什么加载器,什么GDT,统统不要。

直接上代码

org  0x7c00;

jmp  entry

entry:
    mov  ax, 0
    mov  ss, ax
    mov  ds, ax
    mov  es, ax
    mov  si,font_X
    mov  di,font_Y



putloop:
    mov  al, 13h
    mov  ah, 0x00
    int  0x10

    mov cx,0
    mov dx,0
    x:
      cmp cx,320
      je y
      ; mov dx, 2
      call draw
      add cx,1
      jmp x
    y:
      add dx,1
      mov cx,0
      cmp dx,200
      je draw_menu
      jmp x
    draw_menu:
      mov cx,0
      mov dx,183
      x2:
        cmp cx,320
        je y2
        call draw_greem
        add cx,1
        jmp x2
      y2:
        add dx,1
        mov cx,0
        cmp dx,200
        je draw_hello
        jmp x2

draw_hello:
    mov cx,0
    mov dx,0
    _loop_x:

        mov  cl, [si]
        add  si, 1
        mov  dl, [di]
        add  di, 1
        cmp  cl, 0
        je   fin
        call draw_font
        ; mov  ah, 0x0e
        ; mov  bx, 15
        ; int  0x10
        jmp  _loop_x
    ; _loop_y:
    jmp fin



draw:
    pusha
    mov  ah,0ch
    mov  al,8
    mov  bh,0
    int  0x10
    popa
    ret

font_X:
    DB 13,12,13,11,13,13,13,13,13,13,13,13,13,13,11,12,13,14,15,1,2,3,6,7,8,2,7,2,7,2,7,2,4,5,7,2,4,5,7,2,4,5,7,2,4,5,7,3,6,3,6,3,6,3,6,3,6,20,21,19,20,21,22,19,20,21,22,20,21,27,28,29,26,30,25,31,25,31,25,31,25,31,25,31,25,31,25,31,25,31,25,31,26,30,27,28,29,35,36,37,39,34,38,39,33,39,33,39,33,34,35,36,37,38,39,33,39,33,39,33,34,38,33,35,36,37,50,51,53,54,49,50,51,52,53,54,55,49,50,51,52,53,54,55,49,50,51,52,53,54,55,50,51,52,53,54,51,52,53,52
    db 0

font_Y:
    DB 1,2,2,3,3,4,5,6,7,8,9,10,11,12,13,13,13,13,13,1,1,1,1,1,1,2,2,3,3,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,10,10,11,11,12,12,13,13,6,6,7,7,7,7,8,8,8,8,9,9,1,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,13,1,1,1,1,2,2,2,3,3,4,4,5,6,7,7,7,8,9,10,10,11,11,12,12,12,13,13,13,13,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,7,7,7,7,7,7,7,8,8,8,8,8,9,9,9,10
    db 0
draw_greem:
    ; pop dx ;坐标y
    ; pop cx ;坐标x
    pusha
    mov  ah,0ch
    mov  al,7
    mov  bh,0
    int  0x10
    popa
    ret

draw_font:
    ; pop dx ;坐标y
    ; pop cx ;坐标x
    pusha
    mov  ah,0ch
    mov  al,10
    mov  bh,0
    int  0x10
    popa
    ret

fin:
    HLT
    jmp  fin

times 510-($-$$) db 0
dw 0xaa55

通过nasm编译

bash
nasm kernel.asm -f bin -o kernal.img

最终效果图

WX20190803-191527<a href=">

font_X,font_Y是什么呢,是文字的像素坐标,因为不会用汇编操作多维数组,只能自己用Python写个转换程序,将文字转换为坐标 - =

Python转换程序

python
def draw_font(chars, pianyi_x=0, pianyi_y=0):
    chars = chars.strip().splitlines()
    y = 0 + pianyi_y
    x = 0 + pianyi_x
    _x = []
    _y = []
    output = []
    for line in chars:
        x = 0 + pianyi_x
        for i in line:
            if i == '*':
                output.append((x, y))
            x += 1
        y += 1
    print(output)

    s = ""
    for point in output:
        s += str(point[0]) + ","
    print("x:DB " + s.strip(","))

    s = ""
    for point in output:
        s += str(point[1]) + ","
    print("y:DB " + s.strip(","))


charsH = '''
........
........
........
........
.**.**..
*******.
*******.
*******.
.*****..
..***...
...*....
........
........
........
........
........
'''
charW = '''

'''
draw_font(charsH, 1 + 16 + 16 + 8 + 8)

它将输出

x:DB 50,51,53,54,49,50,51,52,53,54,55,49,50,51,52,53,54,55,49,50,51,52,53,54,55,50,51,52,53,54,51,52,53,52
y:DB 4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,7,7,7,7,7,7,7,8,8,8,8,8,9,9,9,10

将这个数值放到那段代码中即可。最后,其实可以把界面画的更好看点,但是因为没有做内核加载器,我们的程序只能512字节的大小,我这段代码刚刚差不多。我明天可以试试做个内核加载器将界面完善一下~

撰写

布局切换

调整 VitePress 的布局样式,以适配不同的阅读习惯和屏幕环境。

全部展开
使侧边栏和内容区域占据整个屏幕的全部宽度。
全部展开,但侧边栏宽度可调
侧边栏宽度可调,但内容区域宽度不变,调整后的侧边栏将可以占据整个屏幕的最大宽度。
全部展开,且侧边栏和内容区域宽度均可调
侧边栏宽度可调,但内容区域宽度不变,调整后的侧边栏将可以占据整个屏幕的最大宽度。
原始宽度
原始的 VitePress 默认布局宽度

页面最大宽度

调整 VitePress 布局中页面的宽度,以适配不同的阅读习惯和屏幕环境。

调整页面最大宽度
一个可调整的滑块,用于选择和自定义页面最大宽度。

内容最大宽度

调整 VitePress 布局中内容区域的宽度,以适配不同的阅读习惯和屏幕环境。

调整内容最大宽度
一个可调整的滑块,用于选择和自定义内容最大宽度。

聚光灯

支持在正文中高亮当前鼠标悬停的行和元素,以优化阅读和专注困难的用户的阅读体验。

ON开启
开启聚光灯。
OFF关闭
关闭聚光灯。

聚光灯样式

调整聚光灯的样式。

置于底部
在当前鼠标悬停的元素下方添加一个纯色背景以突出显示当前鼠标悬停的位置。
置于侧边
在当前鼠标悬停的元素旁边添加一条固定的纯色线以突出显示当前鼠标悬停的位置。