本文共 7901 字,大约阅读时间需要 26 分钟。
简介
上一节我们实现了从实模式到保护模式下字符复制到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安装
下载后进入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 3 dw %2 & 0xffff dw %1 & 0xffff db (%1>>16) & 0xff dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff) db (%1>>24) & 0xff %endmacro DA_32 EQU 0x4000 ; 32 位段 DA_CODE EQU 0x98 ; 只执行代码段属性值 DA_RW EQU 0x92 ; 可读写数据段属性值 org 0x9000 jmp entry [SECTION .gdt] ;定义全局描述符 段基址 段偏移上限 段属性 LABEL_GDT: GDescriptor 0, 0, 0 LABEL_DESC_CODE: GDescriptor 0, SegCodeLen-1, DA_CODE+DA_32 LABEL_DESC_VIDEO: GDescriptor 0xb8000, 0xffff, DA_RW LABEL_DESC_STACK: GDescriptor 0, STACK_TOP-1, DA_32+DA_RW LABEL_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_RW ;gdt 表大小 GdtLen equ $-LABEL_GDT ;gdt表偏移上限和基地址 GdtPtr dw GdtLen-1 dd 0 ;cpu开机进入实模式时使用的段寄存器 cs,ds,es,ss 和偏移地址组成内存地址,内存地址=段寄存器 * 16 + 偏移地址 ;保护模式中段寄存器保存的是gdt 描述表中各个描述符的偏移,也叫选择子 SelectorCode32 EQU LABEL_DESC_CODE-LABEL_GDT SelectorVideo EQU LABEL_DESC_VIDEO-LABEL_GDT SelectorStack EQU LABEL_DESC_STACK-LABEL_GDT SelectorVRAM EQU LABEL_DESC_VRAM-LABEL_GDT [SECTION .s16] [BITS 16]entry: mov ax,cs mov ds,ax mov es,ax mov ss,ax mov sp,0x100 ;设置屏幕色彩模式 mov al,0x13 mov ah,0 int 0x10 ;设置LABEL_DESC_CODE描述符段基址 mov eax,0 mov ax,cs shl eax,4 add eax,SEG_CODE32 mov word [LABEL_DESC_CODE+2],ax shr eax,16 mov [LABEL_DESC_CODE+4],al mov [LABEL_DESC_CODE+7],ah ;设置栈空间 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 mov eax,0 mov ax,ds shl eax,4 add eax,LABEL_GDT mov dword [GdtPtr+2],eax ;设置GDTR 寄存器 lgdt [GdtPtr] cli ;关闭可可屏蔽中断,如键盘中断 in al,0x92 or al,0x02 out 0x92,al mov eax,cr0 or eax,1 mov cr0,eax jmp dword SelectorCode32:0 [SECTION .s32] [BITS 32]SEG_CODE32: mov ax,SelectorStack mov ss,ax mov esp,STACK_TOP mov ax,SelectorVRAM mov ds,ax call _showChar ;调用C语言设置图形功能global _io_hlt ;申明该函数能被外界调用,主要给C语言调用, void io_hlt(); _io_hlt: hlt ret %include "os.s" ;导入C语言编写的功能模块 ;32位模式代码长度SegCodeLen EQU $-SEG_CODE32[SECTION .gs]ALIGN 32 [BITS 32]LABEL_STACK: times 512 db 0STACK_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 begin push ebp ; 0000 _ 55 mov ebp, esp ; 0001 _ 89. E5 sub esp, 8 ; 0003 _ 83. EC, 08 mov eax, 655360 ; 0006 _ B8, 000A0000 mov dword [ebp-8H], eax ; 000B _ 89. 45, F8 mov 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 extension jg ?_002 ; 001C _ 0F 8F, 00000024 mov eax, dword [ebp-4H] ; 0022 _ 8B. 45, FC and eax, 0FH ; 0025 _ 83. E0, 0F mov cl, al ; 0028 _ 88. C1 mov eax, dword [ebp-8H] ; 002A _ 8B. 45, F8 mov byte [eax], cl ; 002D _ 88. 08 mov eax, dword [ebp-8H] ; 002F _ 8B. 45, F8 add eax, 1 ; 0032 _ 83. C0, 01 mov dword [ebp-8H], eax ; 0035 _ 89. 45, F8 mov eax, dword [ebp-4H] ; 0038 _ 8B. 45, FC add eax, 1 ; 003B _ 83. C0, 01 mov dword [ebp-4H], eax ; 003E _ 89. 45, FC; Note: Immediate operand could be made smaller by sign extension jmp ?_001 ; 0041 _ E9, FFFFFFCF?_002:; Note: Immediate operand could be made smaller by sign extension jmp ?_003 ; 0046 _ E9, 00000000?_003: call _io_hlt ; 004B _ E8, FFFFFFB0(rel); Note: Immediate operand could be made smaller by sign extension jmp ?_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#include #include #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],ah2、设置图形模式段和选择子为简单起见把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语言设置图形功能转载地址:https://blog.csdn.net/Zllvincent/article/details/83588101 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!