第4天 C语言与画面显示的练习

第4天 C语言与画面显示的练习

2020.3.28

1. 用C语言实现内存写入

  • projects/04_day下的harib01a下的naskfunc.nas

    ; naskfunc
    ; TAB=4
    
    [FORMAT "WCOFF"]				; 制作目标文件的模式	
    [INSTRSET "i486p"]				; 告诉nask,这个程序是给486用的
    [BITS 32]						; 制作32位模式用的机器语言
    [FILE "naskfunc.nas"]			; 源文件名信息
    
            GLOBAL	_io_hlt,_write_mem8
    
    [SECTION .text]
    
    _io_hlt:	; void io_hlt(void);
            HLT
            RET
    
    _write_mem8:	; void write_mem8(int addr, int data);
            MOV		ECX,[ESP+4]	; [ESP+4]中存放的是地址addr,将其读入ECX
            MOV		AL,[ESP+8]	; [ESP+8]中存放的是数据data,将其读入AL
            MOV		[ECX],AL
            RET
    
  • wrire_mem8(int addr, int data)是用于将一个数据data直接写入内存地址addr的函数。(似乎,C语言中的memcpy等函数或指针亦可实现)

  • 比如,代码write_mem8(0x1234, 0x56)就相当于MOV BYTE[0x1234], 0x56

  • 调用函数时,参数指定的数字存放在内存中,分别是:

    第1个数字的存放地址:[ESP + 4]
    第2个数字存放的地址:[ESP + 8]
    ……
    第n个数字存放的地址:[ESP + 4*n]

  • 我们现在是32位模式,因此要尽可能地使用32位的寄存器,因为使用16位的寄存器可能出错。如果和C语言联合使用的话,只有EAX、ECX、EDX三个寄存器能自由使用,其余的只能使用其值,但是不能改变其值。

  • [INSTRSET "i486p"]
    这句代码是告诉nask,这个程序是给486用的。

  • CPU(英特尔系列家谱):
    8086 -> 80186 -> 286 -> 386 -> **486** -> Pentium -> PentiumPro -> PentiumII -> PentiumIII -> Pentium4 -> ……
    到286为止的CPU是16位,386以后的是32位。

  • projects/04_day下的harib01a下的bootpack.c

    void io_hlt(void);
    void write_mem8(int addr, int data);
    
    void HariMain(void)
    {
        int i; /* 声明一个变量,i是一个32位整数 */
    
        for (i = 0xa0000; i <= 0xaffff; i++) {
    	    write_mem8(i, 15); /* MOV BYTE [i],15 */
        }
    
        for (;;) {
            io_hlt();
        }
    }
    
  • 0xa0000是图像缓冲区开始的地址, 0xaffff是结束地址。其实页面大小为320 * 200 = 0xfa00。那么,结束地址其实可以是0xaf9ff。(至于为什么是0xaffff,可能是作者预留出来的防止出错吧)

  • 15的含义:画面的全部像素的颜色都是第15种颜色,第15种颜色是白色。

  • make run之后QEMU显示纯白的画面。

2. 条形图案(harib01b)

  • 将代码write_mem8(i, 15);改成write_mem8(i, i & 0x0f);

3. 挑战指针(harib01c)

  • write_mem8(i, i & 0x0f)相当于代码*i = i & 0x0f?
    会报类型错误。
    因为,如果我们在写汇编代码MOV [0x1234], 0x56时,会出错。这是因为,在指定内存时,不知道是BYTE、WORD还是DWORD。只有在另一方也是寄存器时才能省略。

  • 其实,代码write_mem8(i, i & 0x0f)相当于汇编代码MOV [i], (i & 0x0f),显然我们未指定[i]是BYTE、WORD还是DWORD。

  • 此时,我们只需要声明一个变量p,便可以实现:

    char *p; /*变量p是用于内存地址的专用变量*/
    *p = i & 0x0f;
    

    上述代码便相当于write_mem8(i, i & 0x0f)。这样,C语言编译器便会认为p是地址专用变量,而且时用于存放字符(char)的,所以就是BYTE

  • 拓展

    char *p; /*用于BYTE类的地址*/
    short *p; /*用于WORD类的地址*/
    int *p; /*用于DWORD类的地址*/
    
  • 为什么使用char?
    因为我们是一个字节一个字节地写入,所以是char。

    char i;是类似于AL的1字节变量;
    short i;是类似于AX的2字节变量;
    int i;是类似于EAX的4字节变量;
    不管是char *pshort *pint *p,变量p都是4字节变量。因为p是用于记录地址的变量,在汇编语言中,地址向ECX一样,用4字节的寄存器来指定。

  • 修改projects/04_day下的harib01b的bootpack.c:

    void io_hlt(void);
    
    void HariMain(void)
    {
        int i; /* 变量声明,变量i是32位整数 */
        char *p; /* 变量p,用于BYTE类型地址 */
    
        for (i = 0xa0000; i <= 0xaffff; i++) {
    
            p = i; /* 带入地址 */
            /* 写成p = (char *) i;还可以防止C语言编译器报警告。*/
            *p = i & 0x0f;
    
            /* 上述代码可以代替 write_mem8(i, i & 0x0f); */
        }
    
        for (;;) {
            io_hlt();
        }
    }
    

    这样我们便实现了只用C语言写入内存的功能了。

  • 只使用类型转换的写法

    *((char *) i) = i & 0x0f;
    
  • 深入理解两句代码

    p = (char *) i;
    *p = i & 0x0f;
    
    • 假设p相当于ECX,那么上述代码相当于:
      MOV ECX, i
      MOV BYTE[ECX], (i & 0x0f)
      
    • 它们的区别很明显,一个是给ECX寄存器赋值,一个是给ECX号内存地址赋值。从存储的半导体来看,一个发生在CPU,一个发生在内存芯片。
    • *p不是一个变量, p才是变量
  • C语言小知识

    • char *p声明的是p,而不是*p。
    • char *pchar* p的区别:
      在C语言编译时二者没有差别。但是建议写成char *p
    • char* p, q;这样声明的话,p会被认为是指针,而q只会被认为是一个1字节的变量。因而,声明两个地址变量的方式:char *p, *q;
    • 这里,地址变量=指针

4. 指针的应用(1)(harib01d)

  • 其实,bootpack.c还可以修改成如下:
    ……
    p = (char *) 0xa0000; /* 给地址变量赋值 */
    
    for (i = 0; i <= 0xffff; i++) {
    	*(p + i) = i & 0x0f;
    }
    ……
    

5. 指针的应用(2)(harib01e)

  • *(p + i)等价于p[i]

  • 注意:p[i]不是数组!!

6. 色号设定(harib01f)

  • 我们使用的是320*200的8位颜色模式,色号使用8位(2进制)数,也就是只能使用0~255的数。一般来讲,我们指定颜色是使用RGB方式,用6位16进制数,也就是24位(2进制)数来指定。

  • 这个8位彩色模式,是由程序员随意指定0~255的数字所对应的颜色。比如,25号颜色对应#ffffff。这种方式叫做调色板(palette)

  • 倘若程序员不做任何设定,那么默认的是:0号颜色是#000000, 15号颜色是#ffffff。其余的不清楚了。

  • 我们使用OSAKA描绘一个OS画面需要的16种颜色:

  • projects/04_day下的harib01f的rootpack.c:

    void io_hlt(void);
    void io_cli(void);
    void io_out8(int port, int data);
    int io_load_eflags(void);
    void io_store_eflags(int eflags);
    
    /* 就算写在同一个原文件里,如果想在定义之前使用,还是需要先声明一下。 */
    
    void init_palette(void);
    void set_palette(int start, int end, unsigned char *rgb);
    
    void HariMain(void)
    {
        int i; /* 声明变量,变量i是32位整数 */
        char *p; /* 变量p是BYTE类型的地址 */
    
        init_palette(); /* 设定调色板 */
    
        p = (char *) 0xa0000; /* 指定初始地址 */
    
        for (i = 0; i <= 0xffff; i++) {
            p[i] = i & 0x0f;
        }
    
        for (;;) {
            io_hlt();
        }
    }
    
    void init_palette(void)
    {
        static unsigned char table_rgb[16 * 3] = {
            0x00, 0x00, 0x00,	/*  0:黑 */
            0xff, 0x00, 0x00,	/*  1:亮红 */
            0x00, 0xff, 0x00,	/*  2:亮绿 */
            0xff, 0xff, 0x00,	/*  3:亮黄 */
            0x00, 0x00, 0xff,	/*  4:亮蓝 */
            0xff, 0x00, 0xff,	/*  5:亮紫 */
            0x00, 0xff, 0xff,	/*  6:浅亮蓝 */
            0xff, 0xff, 0xff,	/*  7:白 */
            0xc6, 0xc6, 0xc6,	/*  8:亮灰 */
            0x84, 0x00, 0x00,	/*  9:暗红 */
            0x00, 0x84, 0x00,	/* 10:暗绿 */
            0x84, 0x84, 0x00,	/* 11:暗黄 */
            0x00, 0x00, 0x84,	/* 12:暗青 */
            0x84, 0x00, 0x84,	/* 13:暗紫 */
            0x00, 0x84, 0x84,	/* 14:浅暗蓝 */
            0x84, 0x84, 0x84	/* 15:暗灰 */
        };
        set_palette(0, 15, table_rgb);
        return;
        /* C语言中的static char只能用于数据,相当于汇编中的DB指令 */
    }
    
    void set_palette(int start, int end, unsigned char *rgb)
    {
        int i, eflags;
        eflags = io_load_eflags();	/* 记录中断许可标志的值 */
        io_cli(); 					/* 将中断许可标志置为0,禁止中断 */
        io_out8(0x03c8, start);
        for (i = start; i <= end; i++) {
            io_out8(0x03c9, rgb[0] / 4);
            io_out8(0x03c9, rgb[1] / 4);
            io_out8(0x03c9, rgb[2] / 4);
            rgb += 3;
        }
        io_store_eflags(eflags);	/* 复原中断许可标志 */
        return;
    }
    
  • io_hlt、io_cli、io_out8、io_load_eflags、io_store_eflags这些都需要在naskfunc.nas中编写代码。

  • init_palette很简单。我们来看一下set_palette。

    • 中间的for循环可以看成是借助函数io_out8往设备里传输数据即可。(io_out8函数的详解先跳过。)

    • 调色板的访问步骤

    • CLI和STI
      CLI:clear interrupt flag, 将中断标志置为0的指令
      STI:set interrupt flag, 将中断标志置为1的指令
      当CPU遇到中断请求时,是立即处理中断请求(中断标志为1),还是忽略中断请求(中断标志位0)。

    • EFLAGS寄存器

      • 是由名为FLAGS的16位寄存器拓展而来的32位寄存器。
      • 存储进位标志中断标志等标志。
      • 进位标志可以通过JC或者JNC等跳转指令来简单地判断到底是0还是1.
      • 中断标志没有类似JC或者JNC的指令。所以只能**读入EFLAGS,再检查第9位上是0还是1.(By the way,进位标志是EFLAGS的第0位)
      • io_load_eflags的作用就是读取最初的eflags值;o_store_eflags是把最初的eflags值带入到EFLAGS中,这样中断标志位就恢复成原来的样子了。
  • projects/04_day下的harib01f的naskfunc.nas:

    ; naskfunc
    ; TAB=4
    
    [FORMAT "WCOFF"]				; 制作目标文件的模式	
    [INSTRSET "i486p"]				; 使用486为止的指令
    [BITS 32]						; 制作32位模式用的机器语言
    [FILE "naskfunc.nas"]			; 源程序文件名
    
            GLOBAL	_io_hlt, _io_cli, _io_sti, _io_stihlt
            GLOBAL	_io_in8,  _io_in16,  _io_in32
            GLOBAL	_io_out8, _io_out16, _io_out32
            GLOBAL	_io_load_eflags, _io_store_eflags
    
    [SECTION .text]
    
    _io_hlt:	; void io_hlt(void);
            HLT
            RET
    
    _io_cli:	; void io_cli(void);
            CLI
            RET
    
    _io_sti:	; void io_sti(void);
            STI
            RET
    
    _io_stihlt:	; void io_stihlt(void);
            STI
            HLT
            RET
    
    _io_in8:	; int io_in8(int port);
            MOV		EDX,[ESP+4]		; port
            MOV		EAX,0
            IN		AL,DX
            RET
    
    _io_in16:	; int io_in16(int port);
            MOV		EDX,[ESP+4]		; port
            MOV		EAX,0
            IN		AX,DX
            RET
    
    _io_in32:	; int io_in32(int port);
            MOV		EDX,[ESP+4]		; port
            IN		EAX,DX
            RET
    
    _io_out8:	; void io_out8(int port, int data);
            MOV		EDX,[ESP+4]		; port
            MOV		AL,[ESP+8]		; data
            OUT		DX,AL
            RET
    
    _io_out16:	; void io_out16(int port, int data);
            MOV		EDX,[ESP+4]		; port
            MOV		EAX,[ESP+8]		; data
            OUT		DX,AX
            RET
    
    _io_out32:	; void io_out32(int port, int data);
            MOV		EDX,[ESP+4]		; port
            MOV		EAX,[ESP+8]		; data
            OUT		DX,EAX
            RET
    
    _io_load_eflags:	; int io_load_eflags(void);
            PUSHFD		; PUSH EFLAGS 
            POP		EAX
            RET
    
    _io_store_eflags:	; void io_store_eflags(int eflags);
            MOV		EAX,[ESP+4]
            PUSH	EAX
            POPFD		; POP EFLAGS
            RET
    
  • IN: CPU从设备取得电气信号的指令

  • OUT: CPU向设备发送电信号的指令

  • 没有MOV EAX, EFLAGS的命令,所以,只能借助栈来实现,PUSHFD和POPFD。

  • PUSHFD: push flags double-word,将标志位按双字长压入栈。其实,它所做的就是PUSH EFLAGS

  • POPFD: push flags double-word,将标志位按双字长弹出栈。其实,它所做的就是POP EFLAGS

  • io_load_eflags代码含义
    PUSHFD:将标志位压入栈;POP EAX,将栈中的数据(标志位)弹出给EAX。
    注意到:这个函数是有返回值的。
    根据C语言的规约,执行RET时,EAX中的值就被看做成函数的返回值。

  • io_store_eflags代码含义
    MOV EAX,[ESP+4]:将eflags放入EAX中;PUSH EAX:将EAX弹入栈中;POPFD:将栈中的数据弹出,给EFLAGS

  • make run:

    条纹的颜色改变了。这证明程序正确。

7. 绘制矩形(harib01g)

  • VRAM画面大小为320*200,那么就有64000个像素点,左上角的坐标为(0, 0),右下角的坐标为(319, 199) .

  • bootpack.c节选

    #define COL8_000000		0
    #define COL8_FF0000		1
    #define COL8_00FF00		2
    #define COL8_FFFF00		3
    #define COL8_0000FF		4
    #define COL8_FF00FF		5
    #define COL8_00FFFF		6
    #define COL8_FFFFFF		7
    #define COL8_C6C6C6		8
    #define COL8_840000		9
    #define COL8_008400		10
    #define COL8_848400		11
    #define COL8_000084		12
    #define COL8_840084		13
    #define COL8_008484		14
    #define COL8_848484		15
    
    void HariMain(void)
    {
        char *p; 
        init_palette(); 
        p = (char *) 0xa0000; 
    
        boxfill8(p, 320, COL8_FF0000,  20,  20, 120, 120);/*亮红色*/
        boxfill8(p, 320, COL8_00FF00,  70,  50, 170, 150);/*亮绿色*/
        boxfill8(p, 320, COL8_0000FF, 120,  80, 220, 180);/*亮蓝色*/
    
        for (;;) {
            io_hlt();
        }
    }
    
    void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1){
        int x, y;
        for (y = y0; y <= y1; y++) {
    	    for (x = x0; x <= x1; x++)
    		    vram[y * xsize + x] = c;
        }
        return;
    }
    
  • vram[y * xsize + x] = c;其中,c代表第几种颜色,调色板我们已经设置好了。

  • make run:

8. 今天的成果(harib01f)

  • harib01f的bootpack.c中的HariMain:
    void HariMain(void)
    {
        char *vram;
        int xsize, ysize;
    
        init_palette();
        vram = (char *) 0xa0000;
        xsize = 320;
        ysize = 200;
    
        boxfill8(vram, xsize, COL8_008484,  0,         0,          xsize -  1, ysize - 29);
        boxfill8(vram, xsize, COL8_C6C6C6,  0,         ysize - 28, xsize -  1, ysize - 28);
        boxfill8(vram, xsize, COL8_FFFFFF,  0,         ysize - 27, xsize -  1, ysize - 27);
        boxfill8(vram, xsize, COL8_C6C6C6,  0,         ysize - 26, xsize -  1, ysize -  1);
    
        boxfill8(vram, xsize, COL8_FFFFFF,  3,         ysize - 24, 59,         ysize - 24);
        boxfill8(vram, xsize, COL8_FFFFFF,  2,         ysize - 24,  2,         ysize -  4);
        boxfill8(vram, xsize, COL8_848484,  3,         ysize -  4, 59,         ysize -  4);
        boxfill8(vram, xsize, COL8_848484, 59,         ysize - 23, 59,         ysize -  5);
        boxfill8(vram, xsize, COL8_000000,  2,         ysize -  3, 59,         ysize -  3);
        boxfill8(vram, xsize, COL8_000000, 60,         ysize - 24, 60,         ysize -  3);
    
        boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize -  4, ysize - 24);
        boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize -  4);
        boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize -  3, xsize -  4, ysize -  3);
        boxfill8(vram, xsize, COL8_FFFFFF, xsize -  3, ysize - 24, xsize -  3, ysize -  3);
    
        for (;;) {
            io_hlt();
        }
    }
    
  • make run:

    有点操作系统的样子了。(不过显得缺少美感)

9. 拓展:画心(harib01i_ex)

  • bootpack.c节选
    void HariMain(void)
    {
        char *vram;
        int xsize, ysize;
    
        init_palette();
        vram = (char *) 0xa0000;
        xsize = 320;
        ysize = 200;
    
        int rand[32] = {254,53,238,59,291,14,43,114,131,184,131,146,76,156,74,42,58,54,263,36,58,55,293,129,246,119,71,34,75,120,147,154};
    
        int i;
        for (i = 0; i < 16; i ++){
            drawheart(vram, xsize, ysize, i, rand[i*2], rand[i*2+1]);
        }
    
        int rand2[32] ={68,149,229,45,213,71,13,12,97,171,299,114,76,41,32,88,99,39,205,0,143,146,277,177,139,177,32,112,221,139,167,31};
    
        for (i = 0; i < 16; i ++){
            drawheart(vram, xsize, ysize, i, rand2[i*2], rand2[i*2+1]);
        }
    
        for (;;) {
            io_hlt();
        }
    }
    
    void drawheart(unsigned char *vram, int xsize, int ysize, unsigned char c, int sx, int sy){
        const int SIZE_ = 11;
        int Heart[11][11]=
        {
            {0,0,1,0,0,0,0,0,1,0,0},
            {0,1,1,1,0,0,0,1,1,1,0},
            {1,1,1,1,1,0,1,1,1,1,1},
            {1,1,1,1,1,1,1,1,1,1,1},
            {0,1,1,1,1,1,1,1,1,1,0},
            {0,0,1,1,1,1,1,1,1,0,0},
            {0,0,0,1,1,1,1,1,0,0,0},
            {0,0,0,0,1,1,1,0,0,0,0},
            {0,0,0,0,0,1,0,0,0,0,0},
            {0,0,0,0,0,0,0,0,0,0,0},
            {0,0,0,0,0,0,0,0,0,0,0}
        };
    
        if (sx > xsize - SIZE_ || sy > ysize - SIZE_){
            return ;// do noting
        }
    
        int i, j;
        for (i = 0; i < SIZE_; i++){
            for (j = 0; j < SIZE_; j++){
                if (Heart[i][j] == 1){
                    vram[(sy + i) * xsize + (sx + j)] = c;
                }
                else{
    
                }
            }
        }
        return ;
    }`
    
  • make run

10. 题外话:使用MFC画心(harib01i_ex)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值