简介

上一节我们实现了从实模式到保护模式下字符复制到1M内存空间外的显示。直观感受从实模式到保护模式地址寻址的变化。

目标

显示器基本都有字符模式和图形显示模式,用C语言实现色彩斑斓的图形显示。引入C语言开发操作系统需要对C语言函数参数传递机制有基本认识。

1、为了能使用C语言,我们需要设置栈空间。C语言函数体中的变量就是使用栈管理的,默认情况下C语言编译后生成的代码是能在该操作系统下运行的可执行文件,在链接中链接器会插入相关的描述信息。但我们要开发的是系统内核,如果将内核编译成可执行的文件,那么就不能直接将内核加载到内存直接执行。所以需要想新的办法。

2、用反汇编结合C语言和汇编语言
gcc 编译器给我们提供了相关的编译选项,我们把C语言编译成2进制文件后使用反汇编器得到nasm 汇编器能编译的汇编文件。

用以下命令编译C代码模块,以便后面反汇编:
gcc -m32 -c -fno-asynchronous-unwind-tables os.c -o os.o

-m32:编译的指令是32位指令集
-c:编译出2进制机器指令

反汇编工具objconv安装 https://github.com/vertis/objconv.git
下载后进入objconv目录,编译该工具,运行下面的命令:
g++ -o objconv -O2 src/*cpp , -O2中的圆圈是大写字母O

用objconv 反汇编C语言生成的目标文件os.o,命令如下:
objconv -fnasm os.o -o os.s,目录下便有一个反汇编文件os.s

修改os.s 汇编文件,删除多余的 SECTION 等不合适的汇编伪指令
在kernel.s 汇编文件中使用 %include "os.s" 导入os.s汇编文件

至此,我们便可使用nasm 正常编译该文件生成2进制内核文件

3.kernel.s 文件内容如下:

;全局描述符结构 8字节; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0; byte6低四位和 byte1 byte0 表示段偏移上限; byte7 byte4 byte3 byte2 表示段基址;定义全局描述符数据结构;3 表示有3个参数分别用 %1、%2、%3引用参数;%1:段基址     %2:段偏移上限  %3:段属性%macro GDescriptor  3dw %2 & 0xffffdw %1 & 0xffffdb (%1>>16) & 0xff dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff)db (%1>>24) & 0xff %endmacroDA_32       EQU 0x4000   ; 32 位段DA_CODE     EQU 0x98     ; 只执行代码段属性值DA_RW       EQU 0x92     ; 可读写数据段属性值org 0x9000 jmp entry[SECTION .gdt];定义全局描述符                                段基址           段偏移上限       段属性LABEL_GDT:              GDescriptor         0,              0,             0LABEL_DESC_CODE:        GDescriptor         0,              SegCodeLen-1,  DA_CODE+DA_32 LABEL_DESC_VIDEO:       GDescriptor         0xb8000,        0xffff,        DA_RWLABEL_DESC_STACK:       GDescriptor         0,              STACK_TOP-1,   DA_32+DA_RWLABEL_DESC_VRAM:        GDescriptor         0,              0xffffffff,    DA_RW;gdt 表大小GdtLen  equ     $-LABEL_GDT;gdt表偏移上限和基地址GdtPtr  dw      GdtLen-1dd      0;cpu开机进入实模式时使用的段寄存器 cs,ds,es,ss 和偏移地址组成内存地址,内存地址=段寄存器 * 16 + 偏移地址 ;保护模式中段寄存器保存的是gdt 描述表中各个描述符的偏移,也叫选择子SelectorCode32      EQU     LABEL_DESC_CODE-LABEL_GDTSelectorVideo       EQU     LABEL_DESC_VIDEO-LABEL_GDTSelectorStack       EQU     LABEL_DESC_STACK-LABEL_GDTSelectorVRAM        EQU     LABEL_DESC_VRAM-LABEL_GDT[SECTION .s16][BITS 16]
entry:mov ax,cs mov ds,axmov es,axmov ss,axmov sp,0x100 ;设置屏幕色彩模式mov al,0x13mov ah,0int 0x10;设置LABEL_DESC_CODE描述符段基址mov eax,0 mov ax,cs shl eax,4add eax,SEG_CODE32mov word [LABEL_DESC_CODE+2],axshr eax,16mov [LABEL_DESC_CODE+4],almov [LABEL_DESC_CODE+7],ah;设置栈空间xor eax,eaxmov ax,cs shl eax,4add eax,LABEL_STACKmov word [LABEL_DESC_STACK+2],axshr eax,16mov byte [LABEL_DESC_STACK+4],almov byte [LABEL_DESC_STACK+7],ahmov eax,0mov ax,dsshl eax,4 add eax,LABEL_GDTmov dword [GdtPtr+2],eax;设置GDTR 寄存器lgdt [GdtPtr]cli     ;关闭可可屏蔽中断,如键盘中断in al,0x92 or al,0x02out 0x92,al mov eax,cr0or eax,1 mov cr0,eaxjmp dword SelectorCode32:0[SECTION .s32][BITS 32]
SEG_CODE32:mov ax,SelectorStackmov ss,ax mov esp,STACK_TOPmov ax,SelectorVRAMmov ds,ax call _showChar  ;调用C语言设置图形功能global _io_hlt      ;申明该函数能被外界调用,主要给C语言调用, void io_hlt();
_io_hlt:hltret%include "os.s"    ;导入C语言编写的功能模块;32位模式代码长度
SegCodeLen  EQU $-SEG_CODE32[SECTION .gs]
ALIGN 32
[BITS 32]LABEL_STACK:times 512 db 0
STACK_TOP   EQU $ - LABEL_STACK

4.os.c 文件内容如下:

//申明需要调用汇编实现的hlt 功能
extern void io_hlt(); void showChar(){int i;char *p = (char *)0xa0000;for (i = 0; i <= 0xffff; i++) {*p = i & 0x0f;p++;}for(; ;){io_hlt();}
}

运行:gcc -m32 -c -fno-asynchronous-unwind-tables os.c -o os.o 编译该os.c文件为32位指令集2进制文件os.o

运行:./objconv -fnasm os.o -o os.s 反汇编成nasm 能编译的汇编文件os.s ,删除多余的汇编伪指令后如下:

_showChar:; Function beginpush    ebp                                     ; 0000 _ 55mov     ebp, esp                                ; 0001 _ 89. E5sub     esp, 8                                  ; 0003 _ 83. EC, 08mov     eax, 655360                             ; 0006 _ B8, 000A0000mov     dword [ebp-8H], eax                     ; 000B _ 89. 45, F8mov     dword [ebp-4H], 0                       ; 000E _ C7. 45, FC, 00000000
?_001:  cmp     dword [ebp-4H], 65535                   ; 0015 _ 81. 7D, FC, 0000FFFF
; Note: Immediate operand could be made smaller by sign extensionjg      ?_002                                   ; 001C _ 0F 8F, 00000024mov     eax, dword [ebp-4H]                     ; 0022 _ 8B. 45, FCand     eax, 0FH                                ; 0025 _ 83. E0, 0Fmov     cl, al                                  ; 0028 _ 88. C1mov     eax, dword [ebp-8H]                     ; 002A _ 8B. 45, F8mov     byte [eax], cl                          ; 002D _ 88. 08mov     eax, dword [ebp-8H]                     ; 002F _ 8B. 45, F8add     eax, 1                                  ; 0032 _ 83. C0, 01mov     dword [ebp-8H], eax                     ; 0035 _ 89. 45, F8mov     eax, dword [ebp-4H]                     ; 0038 _ 8B. 45, FCadd     eax, 1                                  ; 003B _ 83. C0, 01mov     dword [ebp-4H], eax                     ; 003E _ 89. 45, FC
; Note: Immediate operand could be made smaller by sign extensionjmp     ?_001                                   ; 0041 _ E9, FFFFFFCF?_002:
; Note: Immediate operand could be made smaller by sign extensionjmp     ?_003                                   ; 0046 _ E9, 00000000?_003:  call    _io_hlt                                 ; 004B _ E8, FFFFFFB0(rel)
; Note: Immediate operand could be made smaller by sign extensionjmp     ?_003                                   ; 0050 _ E9, FFFFFFF6
; _showChar End of function

5.运行:nasm kernel.s -o kernel.bat 生成kernel.bat 内核文件

此时我们只差使用C语言把内核加载器和内核文件写入虚拟软盘,我们生成的kernel.bat 文件已经超过了512字节,我们需要修改内核加载器boot.s和C语言写入模块功能,让内核文件能完整写入虚拟软盘。
main.c 文件修改如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>#include "floppy.h"int main(int argc,char **argv){FILE *fp = initFloppy("floppy.img");if(fp == NULL) {printf("初始化磁盘失败");exit(0);}FILE *src = fopen("boot.bat", "r");if(src == NULL) {printf("文件打开失败");exit(0);}char buf[512];memset(buf, 0, 512);fread(buf, 512, 1, src);buf[510] = 0x55;buf[511] = 0xaa;writeFloppy(0, 0, 1, fp, buf);fclose(src);src = fopen("kernel.bat", "r");if(src == NULL) {printf("文件打开失败");exit(0);}//可正确处理16个扇区的数据内容for(int i=0;!feof(src);i++){memset(buf, 0, 512);fread(buf, 512, 1, src);writeFloppy(1, 0, 2+i, fp, buf);printf("第%d次读取kernel.bat\n",i+1);}fclose(src);fclose(fp);return 1;
}

boot.s 修改扇区连续读取数如下:
mov al,3 ;表示连续读取扇区数

使用nasm 生成boot.bat 内核加载器2进制文件

6.gcc 编译main.c 并执行,在目录下可生成floppy.img 虚拟磁盘文件,使用虚拟机加载该虚拟软盘文件效果如下:

至此,我们使用C语言实现图形界面成功完成!

总结

1、kernel.s 文件中我们添加了栈描述符LABEL_DESC_STACK,栈选择子SelectorStack,并在代码中设置段基址、段界限、段属性:
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_STACK
mov word [LABEL_DESC_STACK+2],ax
shr eax,16
mov byte [LABEL_DESC_STACK+4],al
mov byte [LABEL_DESC_STACK+7],ah

2、设置图形模式段和选择子为简单起见把4G的内存空间都这是为可读写
LABEL_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_RW
SelectorVRAM EQU LABEL_DESC_VRAM-LABEL_GDT

图形模式下显示基址为:0xa0000

3、使用如下汇编代码调用BIOS功能设置显示器为图形模式:
mov al,0x13
mov ah,0
int 0x10

其中al 的值决定了要设置显卡的色彩模式,下面是一些常用的模式设置:
1)0x03, 16色字符模式
2) 0x12, VGA图形模式, 640 * 480 * 4位彩色模式,独特的4面存储模式
3) 0x13, VGA图形模式, 320 * 200 * 8位彩色模式,调色板模式
4) 0x6a, 扩展VGA图形模式, 800 * 600 * 4彩色模式

4、32位模式代码中设置栈选择子:
mov ax,SelectorStack
mov ss,ax
mov esp,STACK_TOP
mov ax,SelectorVRAM
mov ds,ax
call _showChar ;调用C语言设置图形功能

08.C语言绘制系统界面相关推荐

  1. 09.调色板绘制系统界面

    简介 上一节我们使用C语言绘制了简单的图形界面,实现了汇编语言和C语言共同开发操作系统,只有当C语言力不能逮,特别是需要操作硬件时,才会使用汇编语言. 我们实现图像绘制的办法是,给每一个像素设定指定的 ...

  2. 使用调色板绘制系统界面

    相关代码可视频可在网易云课堂下载: Linux kernel Hacker, 从零构建自己的内核 上一节,我们已经可以使用C语言实现图像绘制,但操作系统的用户界面不可能是那种扎眼的条纹图案,这一节,我 ...

  3. 30天自制操作系统 pdf_30天自制操作系统:第四天:系统界面绘制

    第四天: OUT:让cpu给设备发送电信号. IN:让cpu从设备获取电信号. 为了区别不同的设备,要使用设备号码,用port表示. pushad: 将所有的32位通用寄存器压入堆栈 pusha:将所 ...

  4. C语言画伯德图程序,已知系统的传递函数,试绘制系统的伯德图。 (1) (2)

    已知系统的传递函数,试绘制系统的伯德图. (1) (2) 更多相关问题 [单选] 女劳疸见何症知其预后不良() [填空题] 气利,()主之. [多选] 黄疸病篇具有清热利湿作用的方剂有() [填空题] ...

  5. C语言 函数图形绘制系统

    系统功能简述: 初等函数曲线图形的简易绘制:设屏幕显示文本是25行, 80列,可以用"+"和"--〉"号画坐标系,用"*"号画曲线上的点.用 ...

  6. C语言嵌入式系统编程修炼(经典中的经典)

    C语言嵌入式系统编程修炼      http://blog.chinaunix.net/u/25764/showart_326589.html转载自这里,真是太经典了. C语言嵌入式系统编程修炼   ...

  7. r语言绘制精美pcoa图_R语言绘制交互式热图

    热图 通过热图可以简单地聚合大量数据,并使用一种渐进的色带来优雅地表现,最终效果一般优于离散点的直接显示,可以很直观地展现空间数据的疏密程度或频率高低.但也由于很直观,热图在数据表现的准确性并不能保证 ...

  8. 纯C语言写计算器界面

    纯C语言写计算器界面,GDI绘制控件,HOOK拦截键盘按键. 主文件源码预览: // Calculator.cpp : 定义应用程序的入口点. //#include "stdafx.h&qu ...

  9. C语言嵌入式系统编程

    不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑,汇编语言具备这样的特质.但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发 ...

  10. R语言绘制环形树状图

    R语言绘制环形树状图 1.主要用到dendextend和circlize包绘图: library(dendextend) library(circlize)# 距离矩阵 d <- dist(US ...

最新文章

  1. 炉石整活拔线方法_炉石传说:采访仰天莫笑——黄金总决赛再度捧杯后的变化与成长...
  2. java 基本的数据类型_Java的基本数据类型介绍
  3. python操作mongo(2)
  4. [No0000176]Git常用命令速查表(收藏大全)
  5. DebugView调试C#程序 学习总结
  6. C++根据输入日期YYYY-MM-DD判断是否星期几
  7. Java嵌入式数据库H2学习总结(二)——在Web应用程序中使用H2数据库
  8. Redis zset(ziplist,skiplist)内部实现
  9. 用libtommath实现RSA算法
  10. shell 批量修改文件名字
  11. java matcher方法_Java正则表达式入坑指南:正则表达式使用的类有哪些吗?
  12. vc12对应的php版本,vc和vs的区别
  13. 2021年认证杯SPSSPRO杯数学建模B题(第一阶段)依巴谷星表中的毕星团求解全过程文档及程序
  14. HarmonyOS笔记
  15. python计算公司销售额的同比增长率
  16. 【计算机】数据结构-严蔚敏/清华大学P1
  17. 什么是零信任?零信任的好处有哪些?
  18. AV1时域滤波相关代码
  19. 6、FFmpeg 视频处理
  20. RCA清洗系统及清洗液自适应预测温度控制

热门文章

  1. 存在链接注入漏洞_【安全提示】CNVD发布上周关注度较高的产品安全漏洞(20200817-20200823)...
  2. 密码学基础(四):OpenSSL命令详解
  3. Leetcode804.Unique Morse Code Words唯一摩尔斯密码词
  4. 我的BRF+自学教程(一):公式(formula)
  5. 刷新代码IOS 上拉分页刷新--
  6. C# abstract ,virtual ,override,new --比较好的文章
  7. uploadify 返回值(回调函数)总结
  8. 现实世界的 Windows Azure:Davide Bedin讲述aKite零售管理解决方案
  9. 构造方法传参数的小心得
  10. Python向数据库表格里插入万级数据