[汇编] 你没看错:动手开发GUI简单操作系统(一)

a1辅助网提供[汇编] 你没看错:动手开发GUI简单操作系统(一)的下载地址,长期提供破解软件,各种线报福利等,55393是一个很好的福利资源网站

TLHorse 发表于 2021-2-10 22:49
好的,你们推荐的东西,我一定学习学习,毕竟在下也不是什么高级人才,也是个小白
没有GUI咱不怕,毕竟g …

https://github.com/chyyuu/os_course_info 这是总览,东西还是挺多的,期待你的后续

你以为我看的懂?

涛之雨 发表于 2021-2-10 19:19
好的作品需要时间来检测,而不是多少人查看和多少人去回复。
与其满眼都是回复“感谢楼主”,“学到了, …

好,你这回复给了我不少信心啊,一定坚持

你没看错:动手开发GUI简单操作系统(一)
你没看错:动手开发GUI简单操作系统(二)
正在更新……

前言

今天我终于想好发布这篇文章,以前自己一直在摸索开发,保证100%原创。这个操作系统异常简单,没有Windows的高级,没有OS X的华丽,更没有Linux的强大——也别指望了,对于个人来说根本没多少生产力,只能用来学习知识,自己整着玩。但是,OS开发的资料太少了,“你没看错”系列中的每一行代码,确实是作者我本人摸滚打爬才得来的。

或许我的文字在各位大佬眼中会很简单。所以说,我尽力吧,简明易懂,不加废话。如果有不专业的地方,直接留言改正,谢谢。

我准备出一系列“你没看错”文章,一定会有后续的。OS一篇文章讲不完,我的写法是理论和实践相辅相成,一点点讲。

学习目标

第一天我们的目标很简单,主要是写启动扇区:

  1. 实现在启动扇区打印字符串
  2. 在启动扇区打印地址
  3. 添加读取磁盘的功能

这些实现主要是为以后加载内核、出现错误调试做准备。

要求知识

  1. 汇编语言不要求精通,但一定要熟悉,有基本了解;
  2. C语言要会,写内核要用;
  3. shell必须会敲命令,没得说;
  4. 可以先修一些附加技能,比如gdb、Makefile等,也可以先了解相关概念。

环境配置

在开发之前,我们需要配置开发环境。我使用的是Mac,终端用的是zsh。如果有能力,可以用Linux,因为Linux包含开发过程中大部分的工具。如果是Windows……那就去论坛下载个虚拟机,使用Linux吧。为了不让诸位一上来就被各种安装震慑住,我们开发一点安装一点。首先(假设你有Homebrew,一定要换源):

brew install qemu nasm # 怎么样?很简单吧

简单认识一下:qemu是个开源的模拟器,nasm是Netwide汇编编译器。

加载启动扇区

我们的操作系统,从bootsector写起。这个bootsector是个启动扇区。当这个分区被识别有效后,系统就会启动。我们的首要目标是创建能被识别的bs。

为了检测磁盘是可启动的,BIOS会检测第511和512字节是否为十六进制AA55。记住0xAA55,这个数字是硬件开发者所设置的。

新建你的项目文件夹,给你自己的系统起个名字,比如我的叫Venus。创建bootsect.asm

loop:     jmp loop ; 开始递归,在这里做无限循环。其实也可以用hlt或者jmp $实现。  times 510 - ($-$$) db 0 ; 在bs前放上510个0 dw 0xAA55 ; 在第511字节处,定义0xAA55,覆盖两个字节

我们编译、模拟两步走:

$ nasm -fbin boot_sect.asm -o boot_sect.bin  $ qemu-system-x86_64 boot_sect.bin # 如果错误,改成qemu boot_sect.bin

至此,你迈开了第一步!系统在引导之后,进入了无限循环。

输出至屏幕

先来了解一下中断:

在点击鼠标或键盘时(正如我现在在做的事情),计算机会立即给我反馈处理结果,计算机与我们之间是在进行实时交互的。而实时性的实现便是依赖了中断,中断是为了顺应人们对实时性交互的需求而产生的技术。中断之所以有用,是因为它会立刻停下当前的程序(软件)去做另外一件事。

我们希望启动后,让系统在屏幕上输出几个字符:’Venus’。我们需要用到int 0x10。这个中断用于控制屏幕输出,它好比一个约定俗成的函数,有两个参数,ax寄存器的低位al就是要输出的字符,高位ah就是控制输出模式的指示符。代码如下:

mov ah, 0x0E ; 指示符为0x0E代表tty模式(你应该知道tty是什么,TeleTYpe) mov al, 'V'  ; 把al赋值'V' int 0x10     ; 终端输出 mov al, 'e'  ; 重复以上流程 int 0x10 mov al, 'n' int 0x10 mov al, 'u' int 0x10 mov al, 's' int 0x10  jmp $  ; BIOS识别的数字 times 510 - ($-$$) db 0 dw 0xAA55 

我们编译、模拟两步走:

完善打印功能

为了方便我们今后的调试,我们需要完善打印功能,这样出了什么差错直接print就OK了。我们的打印分为两种:打印字符串和打印地址。

打印字符串

都知道,C语言中的字符串结构长这样:

"Venus" -> 'V' 'e' 'n' 'u' 's' '0x0'

都是几个字符再加上一个空字节0x0。如果要打印字符串,而不是单个字符,在汇编里面,可以对应成一个栈来处理。同目录新建一个print.asm

print:     pusha ; 将所有东西压入栈  ; 记住:一直循环打印栈的字符,直到碰到字符串末0x0 ; while (string[i] != 0) { print string[i]; i++ }  start:     mov al, [bx] ; bx相当于字符串参数,是字符串的首位     cmp al, 0    ; al和0比较     je done      ; 如果相等,就到了字符串末尾,跳转到结束done      mov ah, 0x0E ; 如果不相等,开始打印,先进入tty模式     int 0x10     ; 直接中断。因为al参数已经有字符了      add bx, 1    ; 如果你把这个栈+1,相当于地址后移一位,这样再打印就是下一个字符串     jmp start    ; 递归  done:     popa         ; 弹出栈       ret          ; 返回主程序

我们再空几行,实现一个附加功能——换行:

print_nl:        ; print NewLine     pusha      mov ah, 0x0E ; tty模式     mov al, 0x0A ; 把0x0A和0x0D合起来相当于n     int 0x10     mov al, 0x0D ; 把0x0A和0x0D合起来相当于n     int 0x10      popa     ret

打印地址(4位)

打印地址也很有用的。但它涉及到一个把指定字符转换为ASCII的问题。因为传入的参数不是带引号的字符串,而是譬如0x1234这样的地址,那到底应该打印什么呢?转换方法如下:

字符与ASCII对应关系

数字转换:0~9是0x30~0x39,所以把数字加上0x30即是ASCII;
字母转换:A~F(当成1~6)是0x41~0x46,所以把字母加上0x40

代码

print_hex:     pusha     mov cx, 0 ; cx在循环指令和重复前缀中,作循环次数计数器  ; 参数dx:要打印的地址 hex_loop:     cmp cx, 4 ; cx是不是已经循环了四次?     je end    ; 如果是,跳转到end结束      ; 如果不是:开始处理      mov ax, dx     ; 在ax上对字符处理,(dx是我们的地址参数)     and ax, 0x000F ; 先把这个地址只保留最后一位。比如0x1234就变成0x0004     add al, 0x30   ; 加上30,这样4就会变成ASCII:34(别忘了这个al是ax的一部分,是一个寄存器——     cmp al, 0x39   ; 如果发现这个数字>9,不是0~9,那么这个数字就是字母,加上7,就会是A~F中的一个     jle step2      ; Jump if Lower or Equal:al小于等于0x39跳转至step2     add al, 7  step2:     ; 第二步:我们的ASCII字符应该放在哪个地址呢?     ; 地址BX:基地址+字符串长度(5位,别忘了还有最后的0x0)-字符索引     mov bx, HEX_OUT + 5 ; 基+长     sub bx, cx          ; -索引     mov [bx], al        ; 把al中的字符移到[bx],中括号表示地址的内容     ror dx, 4           ; ROll Right:0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234. ror帮我们实现类似遍历字符串的效果。你可以去掉这行指令,看看会发生什么      add cx, 1           ; 循环计数器+1     jmp hex_loop        ; 回到循环  end:     mov bx, HEX_OUT     ; 把HEX_OUT设置到bx里,作为下一个call的参数     call print          ; 调用写好的print.asm      popa     ret  HEX_OUT:     db '0x0000', 0 ; 这是我们输出的地址,先定义下来

尝试使用打印功能

编写bootsect.asm

[org 0x7C00]  mov bx, GREETINGS ; 设置参数 call print        ; 打印 call print_nl     ; 换行  mov dx, 0x4567    ; 设置参数(地址) call print_hex    ; 打印十六进制  mov bx, SHUTDOWN  ; 同上 call print call print_nl  jmp $             ; 挂起程序,无限循环(hlt也行)  %include "boot_sect_print.asm" %include "boot_sect_print_hex.asm"  ; 定义两个数据,注意末尾一定要带0字节 GREETINGS:     db 'Welcome to Venus', 0  SHUTDOWN:     db 'Shutdown', 0  times 510-($-$$) db 0 dw 0xAA55

说明两个地方:

  1. [org 0x7C00]:org是用来设置程序基址的。因为BIOS将bs加载到0x7C00的位置,所以我们设置基址为0x7C00。这行指令的中括号去掉也行。
  2. %include:用来引用文件,后面跟上空格和双引号,双引号里写文件名称。值得注意的是,%include命令相当于把引用的文件直接替换到程序中,不做任何操作。

还是按老办法编译、模拟:

读取磁盘

好了,最枯燥却最有用的功能来了,读取磁盘。我们总不能神经质地把整个系统都放在启动扇区。我们先了解一下磁盘(这个部分必须看):

磁盘基础

盘片、片面和磁头

硬盘中一般会有多个盘片组成,每个盘片包含两个面,每个盘面都对应地有一个读写磁头。受到硬盘整体体积和生产成本的限制,盘片数量都受到限制,一般都在5片以内。盘片的编号自下向上从0开始,如最下边的盘片有0面和1面,再上一个盘片就编号为2面和3面。

扇区(sector)和磁道(track)

上图显示的是一个盘面,盘面中一圈圈灰色同心圆为一条条磁道,从圆心向外画直线,可以将磁道划分为若干个弧段,每个磁道上一个弧段被称之为一个扇区(图践绿色部分)。扇区是磁盘的最小组成单元,通常是512字节。(由于不断提高磁盘的大小,部分厂商设定每个扇区的大小是4096字节)。

磁头(head)和柱面(cylinder)

硬盘通常由重叠的一组盘片构成,每个盘面都被划分为数目相等的磁道,并从外缘的“0”开始编号,具有相同编号的磁道形成一个圆柱,称之为磁盘的柱面。磁盘的柱面数与一个盘面上的磁道数是相等的。由于每个盘面都有自己的磁头,因此,盘面数等于总的磁头数。

开始读取吧!

我就直接放代码了,没什么技术含量,只不过有一些关键的寄存器数值与中断号码需要明白:

; 参数: ;   - dh:扇区个数 ;   - dl:磁盘 ; 读取的数据存入es:bx  disk_load:     pusha              ; 压入栈                        ; 将dx也压入栈     push dx            ; dx一会会被读取磁盘的操作覆盖,所以先压入栈保存      mov ah, 0x02       ; BIOS 读取扇区的功能编号     mov al, dh         ; AL - 扇区读取个数,也就是我们的dh     mov cl, 0x02       ; CL - 从哪里开始读取,因为第一个扇区是启动扇区,所以这里是0x02     mov ch, 0x00       ; CH - 柱面编号(0x0-0x3FF)     mov dh, 0x00       ; DH - 磁头编号(0x0-0xF)      int 0x13           ; 读取磁盘的中断标号     jc disk_error      ; Jump if Carry:如果CF被设置,就是出现了错误,跳转      ; 如果没有错误     pop dx             ; dx我们用完了,弹出栈     cmp al, dh         ; 此时bios会把al设置为扇区个数,对比一下     jne sectors_error  ; 如果两者不一样,读取扇区出现了错误,跳转     popa               ; 如果一样,停止程序     ret  ; 剩下的是错误处理部分,大家都明白 disk_error:     mov bx, DISK_ERROR     call print     call print_nl     mov dh, ah     call print_hex     jmp disk_loop  sectors_error:     mov bx, SECTORS_ERROR     call print  disk_loop:     jmp $  DISK_ERROR: db "Disk read error", 0 SECTORS_ERROR: db "Incorrect number of sectors read", 0

我们将启动扇区代码bootsect.asm做出如下更改:

[org 0x7C00] mov bp, 0x8000 ; 把栈顶设成0x8000,这样不与BIOS相干 mov sp, bp     ; 同上 mov bx, 0x9000 ; es:bx == 0x0000:0x9000 == 0x09000  ; 现在我们要设置disk_load参数 mov dh, 2 ; 读取两个扇区 ; 此处不用设置dl,BIOS已经帮我们设置过了 call disk_load ; 调用  mov dx, [0x9000] ; 获取第一扇区 call print_hex call print_nl  mov dx, [0x9000 + 512] ; 获取第二扇区(注意偏移地址,跟下面数据对应) call print_hex  jmp $  %include "print.asm" %include "print_hex.asm" %include "disk.asm"  times 510 - ($-$$) db 0 dw 0xAA55  ; 上面是bs(第一个扇区) times 256 dw 0x1234 ; 第2 times 256 dw 0x5678 ; 第3 ; 上面的第二第三也不一定,因为有的磁盘一个扇区512,现在有的4096 ; …………

后记

别着急,这只是第一天呢,离加载内核还远着呢,项目里只有四个文件。我给大家指指路,我们已经可以读取磁盘,接下来我们需要:

  1. 加载启动扇区
  2. 读取磁盘,加载内核
  3. 从命令行转成GUI图形界面
  4. 设置GDT(代码最简单,但是最困难的部分,也消耗了我的大部分研究时间)
  5. 切换到32bit保护模式
  6. 执行内核:kernel_main
  7. 正式切换到C语言!

剩下的几个步骤我会划分成几天的内容,发布文章讲解。

其实我写着写着突然想到这不就跟革命斗争一样吗,在执行内核前是多么煎熬,执行内核切换C语言后跟解放了一样。

一点点来吧。

相信我!一定有后续!!!很快就出

THE END

部分文章来自互联网,侵权删除www.a1fz.com/

www.a1fz.com A1fz网专注于福利分享,各种破解软件学习资料,视频教程等等,如有侵权告知管理员删除
A1fz.com,福利吧,宅男福利,宅男,福利社,福利,有福利 » [汇编] 你没看错:动手开发GUI简单操作系统(一)

发表评论