写点什么

自制操作系统日记(6):静态桌面初步

作者:
  • 2022-11-16
    四川
  • 本文字数:4642 字

    阅读完需:约 15 分钟

代码仓库地址:https://github.com/freedom-xiao007/operating-system

简介

在上篇中我们成功的加载跳转执行了 C 语言的代码,本篇中将跟随书籍,初步展示了一个系统页面的初步界面,看到桌面那刻还是有点意思的

最终结果展示

不多啰嗦,这里先直接展示结果,最终的运行界面如下:



可以看到有点模样了,令人激动啊。当最终到这一步的时候,心里开心极了,哈哈

相关代码说明

本篇中的代码修改基本上是抄《30 天自制操作系统》中的,但可能是 nask 和 nasm 的区别问题,我们需要单独修改的地方也不少,下面我们详细说明下需要修改的地方

setup.asm 文件修改

这个文件需要添加界面相关的参数设置,我们将其插入到 start 的下面,如下:


start:    ; 画面モードを設定    MOV    AL,0x13      ; VGA显卡,320x200x8bit    MOV    AH,0x00    INT    0x10    MOV    BYTE [VMODE],8  ; 屏幕的模式(参考C语言的引用)    MOV    WORD [SCRNX],320    MOV    WORD [SCRNY],200    MOV    DWORD [VRAM],0x000a0000
复制代码

func.asm 文件修改

这次新增了比较多的函数,但不复杂,也不多,这个文件的内容如下:


[BITS 32]                          ; 制作32位模式用的机器语言        GLOBAL  _io_hlt, _io_cli, _io_sti, io_stihlt        GLOBAL  _io_in8,  _io_in16,  _io_in32        GLOBAL  _io_out8, _io_out16, _io_out32        GLOBAL  _io_load_eflags, _io_store_eflags[SECTION .text]_io_hlt:     ; void io_hlt(void);        HLT        RET_io_cli:     ; void io_cli(void);        CLI        RET_io_sti:     ; void io_sti(void);        STI        RET_io_stihlt: ; void io_stihlt(void);        STI        HLT        RET_io_in8:     ; int io_in8(int port);        MOV      EDX, [ESP+4]      ; port        MOV      EAX,0        IN       AL, DX        RET_io_in16:   ; int io_in16(int port);
MOV EDX, [ESP+4] ; port MOV EAX,0 IN AX, DX RET_io_in32: ; int io_in32(int port); MOV EDX, [ESP+4] ; port IN EAX, DX RET_io_out8: ; void io_out8(int port, int data); MOV EDX, [ESP+4] ; port MOV AL, [ESP+8] ; data OUT DX, AL RET_io_out16: ; void io_out16(int port, int data); MOV EDX, [ESP+4] ; port MOV EAX, [ESP+8] ; data OUT DX, AX RET_io_out32: ; void io_out32(int port, int data); MOV EDX, [ESP+4] ; port MOV EAX, [ESP+8] ; data OUT DX, EAX RET_io_load_eflags: ; int io_load_eflags(void); PUSHFD ; 指PUSH EFLAGS POP EAX RET_io_store_eflags: ; void io_store_eflags(int eflags); MOV EAX, [ESP+4] PUSH EAX POPFD ; 指POP EFLAGS RET
复制代码

c 入口文件修改

本次直接使用书中的 C 相关代码,但有几处不适配,需要我们单独进行修改


  1. 第一处是原书中的 boxfill8 函数,用于绘制矩形的:原书中是选循环 y 再循环 x,但在博主运行时,总是会多出一个 memset 函数,导致运行失败,系统不断重启


需要将其修改下,替换为先循环 x 再循环 y(不断地尝试后发现的,但没有搞懂为啥会这样)


  1. 第二处是调用 boxfill8 时的线条高度问题:原书中下发的两个矩形都是线条出度为 1 左右(我猜测),但在博主尝试中,小于 3 的高度不会显示


这里就需要修改下传入 boxfill8 的 y0 和 y1 的差值最小为 3


经过修改后,文件的所有内容如下:


#define COL8_000000 0#define COL8_FF0000 1#define COL8_00FF00 2#define COL8_FFFF00 3#define COL8_0000FF      4#define COL8_FF00FF      5#define COL8_00FFFF      6#define COL8_FFFFFF      7#define COL8_C6C6C6      8#define COL8_840000      9#define COL8_008400      10#define COL8_848400      11#define COL8_000084      12#define COL8_840084      13#define COL8_008484      14#define COL8_848484      15
/*就算写在同一个源文件里,如果想在定义前使用,还是必须事先声明一下。*/void io_hlt(void);void io_cli(void);void io_out8(int port, int data);int io_load_eflags(void);void io_store_eflags(int eflags);
void init_palette(void);void set_palette(int start, int end, unsigned char *rgb);void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void start(void){ init_palette(); /* 设定调色板 */
char *vram; /* 变量p是BYTE [...]用的地址 */ vram = (char *) 0xa0000; /* 指定地址 */ int xsize = 320; int ysize = 200;
boxfill8(vram, xsize, COL8_008484, 0, 0, xsize, ysize - 29); boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize, ysize - 28); boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize, ysize - 27); boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize, ysize - 1);
// y距离有时候需要大于3 boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 21); boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4); boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 1); boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5); boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 0); boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 21); boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4); boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 0); boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3);
for (; ; ) { io_hlt(); }}
void init_palette(void){ static unsigned char table_rgb[16 * 3] = { 0x00, 0x00, 0x00, /* 0:黑 */ 0xff, 0x00, 0x00, /* 1:亮红 */ 0x00, 0xff, 0x00, /* 2:亮绿 */ 0xff, 0xff, 0x00, /* 3:亮黄 */ 0x00, 0x00, 0xff, /* 4:亮蓝 */ 0xff, 0x00, 0xff, /* 5:亮紫 */ 0x00, 0xff, 0xff, /* 6:浅亮蓝 */ 0xff, 0xff, 0xff, /* 7:白 */ 0xc6, 0xc6, 0xc6, /* 8:亮灰 */ 0x84, 0x00, 0x00, /* 9:暗红 */ 0x00, 0x84, 0x00, /* 10:暗绿 */ 0x84, 0x84, 0x00, /* 11:暗黄 */ 0x00, 0x00, 0x84, /* 12:暗青 */ 0x84, 0x00, 0x84, /* 13:暗紫 */ 0x00, 0x84, 0x84, /* 14:浅暗蓝 */ 0x84, 0x84, 0x84 /* 15:暗灰 */ }; set_palette(0, 15, table_rgb); return;
/* C语言中的static char语句只能用于数据,相当于汇编中的DB指令 */}
void set_palette(int start, int end, unsigned char *rgb){ int i, eflags; eflags = io_load_eflags(); /* 记录中断许可标志的值*/ io_cli(); /* 将中断许可标志置为0,禁止中断 */ io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); /* 复原中断许可标志 */ return;}
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1){ int x, y; for (x = x0; x <= x1; x++) { for (y = y0; y <= y1; y++) { vram[y * xsize + x] = c; } } return;}
复制代码


脚本还是使用原来的,本篇中没有变化,一键运行后,出现文章开头令人激动的画面

初步感受 C 代码转汇编

在书中开始时介绍了不同手段实现同一个效果:给内存赋值


第一个方式是使用 wirte_mem8 时的编译 nasm 代码:使用写汇编函数的方式,根据传入的值,写到相应的内存中


void write_mem8(int addr, int data);void start(void) {    int i;    for (i = 0xa0000; i < 0xaffff; i++) {  write_mem8(i, i & 0x0f);    }}
复制代码


ALIGN   8?_001:  mov     eax, ebx                                ; 0018 _ 89. D8        mov     dword [esp], ebx                        ; 001A _ 89. 1C 24        add     ebx, 1                                  ; 001D _ 83. C3, 01        and     eax, 0FH                                ; 0020 _ 83. E0, 0F        mov     dword [esp+4H], eax                     ; 0023 _ 89. 44 24, 04        call    _write_mem8                             ; 0027 _ E8, 00000000(rel)        cmp     ebx, 720895                             ; 002C _ 81. FB, 000AFFFF        jnz     ?_001                                   ; 0032 _ 75, E4
_write_mem8: ; void write_mem8(int addr, int data); MOV ECX, [ESP+4] ; [ESP + 4]中存放的是地址,将其读入ECX MOV AL, [ESP+8] ; [ESP + 8]中存放的是数据,将其读入AL MOV [ECX], AL RET
复制代码


第二个是指针,使用 char*时的编译 nasm 代码:突然有种回到以前学习 C 的感觉,想到初期使用指针时的痛苦


void start(void) {    int i;    char *p;    for (i = 0xa0000; i < 0xaffff; i++) {  p = (char *) i;  *p = i & 0x0f;    }
/* 或者下面这样写,生成的汇编码也一样 */ char *p; p = (char *) 0xa0000; /*将地址赋值进去*/ for (i = 0; i <= 0xffff; i++) { p[i] = i & 0x0f; }}
复制代码


?_001:  mov     edx, eax                                ; 0018 _ 89. C2        add     eax, 1                                  ; 001A _ 83. C0, 01        and     edx, 0FH                                ; 001D _ 83. E2, 0F        mov     byte [eax-1H], dl                       ; 0020 _ 88. 50, FF        cmp     eax, 720895                             ; 0023 _ 3D, 000AFFFF        jnz     ?_001                                   ; 0028 _ 75, EE
复制代码


可以看到两种不同的实现方式,对 C 的指针有了更深的理解,了解底层确实是有一定帮助的

总结

本文相对而言还是比较顺利的,虽然有一点坎坷,但在不断的调试中,顺利解决了


还进一步理解了 C 的指针


但关于书中的控制屏幕显示部分,目前还不是很通透,很需要再刷刷

发布于: 刚刚阅读数: 3
用户头像

关注

还未添加个人签名 2018-09-09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
自制操作系统日记(6):静态桌面初步_操作系统_萧_InfoQ写作社区