利用C语言绘制操作系统图像界面
有了c语言这一利器后,不多多拿来用,那就太对不起前面的一系列努力了。那么怎么表现c语言的强大功能呢,如果还只是一味的在界面上输出几行字符,那太没意思,考虑到,我们的目标是做出像windows那样具备舒心的图像用户界面那样的,所以在这一节,我们由字符模式切换入画面模式,初步体验下,那些绚丽多彩的图像界面是如何发展而成的。
要想由字符模式转入图形模式,我们需要操作硬件,特别是向显卡发送命令,让其进入图形显示模式,就如同前面我们所做的,要操作硬件,一般需要使用bios调用,以下几行就是打开vga显卡色彩功能的代码:
mov al, 0x13h mov ah, 0x00 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彩色模式
我们采用的是0x13模式,其中320*200*8 中,最后的数值8表示的是色彩值得位数,也就是我们可以用8位数值表示色彩,总共可以显示256种色彩。
系统显存的地址是0x000a0000,当我们执行上面几句代码后,望显存地址写入数据,那么屏幕就会出现相应的变化了。
我们先看看内核的汇编代码部分(kernel.asm):
%include "pm.inc" org 0x9000 jmp label_begin [section .gdt] ; 段基址 段界限 属性 label_gdt: descriptor 0, 0, 0 label_desc_code32: descriptor 0, segcode32len - 1, da_c + da_32 label_desc_video: descriptor 0b8000h, 0ffffh, da_drw label_desc_vram: descriptor 0, 0ffffffffh, da_drw label_desc_stack: descriptor 0, topofstack, da_drwa+da_32 gdtlen equ $ - label_gdt gdtptr dw gdtlen - 1 dd 0 selectorcode32 equ label_desc_code32 - 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] label_begin: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov al, 0x13 mov ah, 0 int 0x10 xor eax, eax mov ax, cs shl eax, 4 add eax, label_seg_code32 mov word [label_desc_code32 + 2], ax shr eax, 16 mov byte [label_desc_code32 + 4], al mov byte [label_desc_code32 + 7], ah ;set stack for c language 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 xor eax, eax mov ax, ds shl eax, 4 add eax, label_gdt mov dword [gdtptr + 2], eax lgdt [gdtptr] cli ;关中断 in al, 92h or al, 00000010b out 92h, al mov eax, cr0 or eax , 1 mov cr0, eax jmp dword selectorcode32: 0 [section .s32] [bits 32] label_seg_code32: ;initialize stack for c code mov ax, selectorstack mov ss, ax mov esp, topofstack mov ax, selectorvram mov ds, ax c_code_entry: %include "write_vga.asm" io_hlt: ;void io_hlt(void); hlt ret segcode32len equ $ - label_seg_code32 [section .gs] align 32 [bits 32] label_stack: times 512 db 0 topofstack equ $ - label_stack
解释下上面代码,我们设置了一个描述符,label_desc_vram, 这个描述符对应的内存起始地址是0,长度是0xffffffff,也就是我们把整个4g内存当做一段可读可写的内存,有了这个设置后,我们在c语言里就可以随意读写内存的任何地方。
label_desc_stack 这个描述符用来设置一段可读可写的内存,它的起始地址是label_stack, 可以看到,程序通过语句:times 512 db 0
初始化了512字节的内存。c语言的运行,特别是函数调用时,是需要一个堆栈来传递参数的,所以,要运行c语言,我们首先需要为其配置一个堆栈,该描述符所对应的这512自己内存就是给c语言使用的,由于堆栈只有512字节,在后面我们使用c语言写的代码中,函数的局部变量大小不能超过512字节,例如下面的代码可能就要出错了:
void fun() { char buf[513]; }
语句%include write_vga.asm”, 表明,我们要开发的c代码文件叫write_vga.c, 我们写完c代码后,会使用上一节的步骤将它编译成汇编,然后include到我们当前的汇编文件里,统一编译成可执行内核。
最后一小块代码:
io_hlt: ;void io_hlt(void);
hlt
ret
作用是进入死循环,hlt指令会让系统进入休眠状态。
c语言">导入c语言
硬件,堆栈等基层设施通过汇编准备就绪后,我们可以使用c语言开发图形功能了。显示器的每一个像素对应一个点,一个点可以显示256种不同的颜色,因此,只要我们给每个点设置成相应的颜色,那么最终就可以绘制出特定的图像。
我们看看如何用c语言写入显存从而操作屏幕图像,write_ram.c:
void cmain(void) { int i; char*p = 0; for (i = 0xa0000; i <= 0xaffff; i++) { p = i; *p = i & 0x0f; } for(;;) { io_hlt(); } }
代码中,我们将指针p指向地址0xa0000, 这个地址正好就是vga显存地址,vga显存地址从0xa0000开始,直到0xaffff结束,总共64k.接着语句:
*p = i & 0x0f 将一个数值写入显存,这个值可以是0-256中任意一个数值,我们代码里是将i的最后4位作为像素颜色写入显存,这个值是任意的,大家可以随意设置。
在ubuntu中写出上面代码后,通过命令编译成二进制文件:
gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga.asm write_vga.c
于是在目录下会生成write_vga.o二进制文件,接着使用objconv进行反汇编:
./objconv -fnasm write_vga.asm write_vga.o
反汇编后代码如下:
; disassembly of file: write_vga.o ; tue sep 13 10:30:14 2016 ; mode: 32 bits ; syntax: yasm/nasm ; instruction set: 80386 global cmain: function extern io_hlt ; near section .text align=1 execute ; section number 1, code cmain: ; function begin push ebp ; 0000 _ 55 mov ebp, esp ; 0001 _ 89. e5 sub esp, 24 ; 0003 _ 83. ec, 18 mov dword [ebp-0ch], 0 ; 0006 _ c7. 45, f4, 00000000 mov dword [ebp-10h], 655360 ; 000d _ c7. 45, f0, 000a0000 jmp ?_002 ; 0014 _ eb, 17 ?_001: mov eax, dword [ebp-10h] ; 0016 _ 8b. 45, f0 mov dword [ebp-0ch], eax ; 0019 _ 89. 45, f4 mov eax, dword [ebp-10h] ; 001c _ 8b. 45, f0 and eax, 0fh ; 001f _ 83. e0, 0f mov edx, eax ; 0022 _ 89. c2 mov eax, dword [ebp-0ch] ; 0024 _ 8b. 45, f4 mov byte [eax], dl ; 0027 _ 88. 10 add dword [ebp-10h], 1 ; 0029 _ 83. 45, f0, 01 ?_002: cmp dword [ebp-10h], 720895 ; 002d _ 81. 7d, f0, 000affff jle ?_001 ; 0034 _ 7e, e0 ?_003: call io_hlt ; 0036 _ e8, fffffffc(rel) jmp ?_003 ; 003b _ eb, f9 ; cmain end of function section .data align=1 noexecute ; section number 2, data section .bss align=1 noexecute ; section number 3, bss
在上面代码中去掉以section 开始的指令,这些指令会影响我们把当前汇编结合入内核kerne.asm.
同时去掉开头的两句:
global cmain: function
extern io_hlt
因为我们要把两个汇编文件结合成一个,所以这两句声明是多余的。做完这些后,再用nasm编译kernel.asm:
nasm -o kernel.bat kernel.asm
于是本地目录下,内核文件就编译好了。
接着运行java工程,生成虚拟软盘,运行结果如下:
大家注意看,kernel.bat写入了两个扇区,也就是说,我们内核的大小已经超过了512字节。此时我们需要修改一下内核加载器,让内核加载器一次读入两个扇区才能把内核完全加载入内存,打开boot.asm,将readflz喎?/kf/ware/vc/" target="_blank" class="keylink">vchb51tc1xko6pgjyic8+dqptb3ygywgsidb4mdi8yniglz4ncm1vdibhbcwgmtxiciavpg0kumszyao6pgjyic8+dqptb3ygywwsidi8yniglz4nctkyvs3kx9k7tm62wcihwb249snix/i1xmtayn2jrndeums689tztm6x4nlrym9vdc5hc206pgjyic8+dqpuyxntic1vigjvb3quymf0igjvb3quyxntpc9wpg0kpha+1+6689tztm7uy9dqamf2ybpm0pkjrltlyrhj+rpjtctq6ctiyo3fznbqo6yyxbvhspy6rm3q1fu1xmtausvoxlz+oapg9lav0one4rv6o6y809ty0one4sjtxcy686os1mvq0mfpv/bi58/co7o8yniglz4ncjxpbwcgywx0pq=="这里写图片描述" src="/uploadfile/collfiles/20160914/20160914095342302.png" title="" />
大家可以看到,屏幕显示出了条纹状图像。
上一篇: 记得我上初中那会