欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

利用C语言绘制操作系统图像界面

程序员文章站 2022-05-16 10:02:48
有了c语言这一利器后,不多多拿来用,那就太对不起前面的一系列努力了。那么怎么表现c语言的强大功能呢,如果还只是一味的在界面上输出几行字符,那太没意思,考虑到,我们的目标是做出像windows那样具备...

有了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语言开发图形功能了。显示器的每一个像素对应一个点,一个点可以显示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工程,生成虚拟软盘,运行结果如下:
利用C语言绘制操作系统图像界面

大家注意看,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="" />

大家可以看到,屏幕显示出了条纹状图像。