Day4—自制操作系统

用C语言实现内存写入

  • naskfunc.nas中创建一个往指定内存写入内存地址的语句
    ; naskfunc
    ; TAB=4
    
    [FORMAT "WCOFF"]				; 制作目标文件的模式
    [INSTRSET "i486p"]				; 用来告诉nask,这个程序是给486用的;如果不指定,就会认为是只有16位寄存器的CPU
    [BITS 32]						; 制作32位模式用的机械语言
    
    ; 制作目标文件的信息
    [FILE "naskfunc.nas"]			; 源文件名信息
    
    		GLOBAL	_io_hlt,_write_mem8     ; 程序中包含是的函数名
    
    ; 以下是实际的函数
    [SECTION .text]                 ; 目标文件中写了这些之后再写程序
    
    _io_hlt:	; void io_hlt(void);
    		HLT
    		RET
    
    ; 将数据放到某个地址空间
    ; 如果在C语言中用到了write_mem8函数,就会跳转到_write_mem8
    _write_mem8:	; void write_mem8(int addr, int data); 
    		MOV		ECX,[ESP+4]		; [ESP+4]中存放的是地址,将其读入ECX
    		MOV		AL,[ESP+8]		; [ESP+8]中存放的是数据,将其读入AL
    		MOV		[ECX],AL
    		RET
    
  • 在指定内存地址的地方,不可以使用[CX]或[SP],但使用32位寄存器,[ECX]、[ESP]等都OK。
  • 如果与C语言联合使用,可以自由使用的只有EAX、ECX、EDX这3个。其它寄存器只能使用其值,而不能改变其值。
  • 这里虽然指定的是486,但并不是386中就不能用。到286为止CPU是16位,而386以后CPU是32位。
    在这里插入图片描述
  • C语言—bootpack.c程序
    void io_hlt(void);
    void write_mem8(int addr, int data);
    
    void HariMain(void)
    {
           
    	int i; /* 变量声明; i是一个32位整数 */
    	int j = 1; // 不同颜色所代表的整数
    
    	for (i = 0xa0000; i <= 0xaffff; i++) {
           
    		write_mem8(i, j++); /* MOV BYTE [i],15 */
    	}
    
    	for (;;) {
           
    		io_hlt();
    	}
    }
    
  • 结果:
    Day4—自制操作系统_第1张图片

条纹图案

  • 修改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++) {
           
    		/* 低四位原封保留,而高四位全部都变成0,所以每个16个像素,色号就会反复一次 */
    		write_mem8(i, i & 0x0f);
    	}
    
    	for (;;) {
           
    		io_hlt();
    	}
    }
    
  • 结果:
    Day4—自制操作系统_第2张图片

挑战指针

  • 替代write_mem3的指针语句
    在这里插入图片描述
    在这里插入图片描述
  • 指定变量的类型
    在这里插入图片描述
    char占用的是1个字节 8位
    short占用2个字节 16位
    int占用4字节 32
  • 所以使用指针之前要定义变量的类型
    在这里插入图片描述
  • 而不管是“char *p”,还是“short *p”,还是“int *p”,变量p都是4字节,因为p是用于记录地址的变量。
  • 修改bootpack.c
    void io_hlt(void);
    
    void HariMain(void)
    {
           
    	int i; /* 变量声明; i是一个32位整数 */
    	char *p; /* *,变量p是用于内存地址到额专用变量 */
    
    	for (i = 0xa0000; i <= 0xaffff; i++) {
           
    
    		p = i; /* 带入地址 */
    		*p = i & 0x0f;
    
    		/*  这可以代替write_mem8(i, i & 0x0f);  */
    	}
    
    	for (;;) {
           
    		io_hlt();
    	}
    }
    
  • 类型转换
    在执行make run之后,会有警告。所以要进行类型转换
    在这里插入图片描述
    也可以改成
    在这里插入图片描述
  • 指针和汇编
    在这里插入图片描述
    在这里插入图片描述
  • 什么只声明了“char *p; ”却不仅能使用p,还可以使用*p
    在这里插入图片描述

指针的应用

  • 可以将下面代码进行改写
    void io_hlt(void);
    
    void HariMain(void)
    {
           
    	int i; /* 变量声明; i是一个32位整数 */
    	char *p; /* *,变量p是用于内存地址到额专用变量 */
    
    	for (i = 0xa0000; i <= 0xaffff; i++) {
           
    
    		p = i; /* 带入地址 */
    		*p = i & 0x0f;
    
    		/*  这可以代替write_mem8(i, i & 0x0f);  */
    	}
    
    	for (;;) {
           
    		io_hlt();
    	}
    }
    
  • *(p + i )是6个字符,而p[i]只有4个字符
    Day4—自制操作系统_第3张图片
    Day4—自制操作系统_第4张图片
  • 将p[i]写成i[p]也是可以的

色号设定

  • 使用的是320 * 200的8位色彩模式,色号使用8位(二进制)数,也就是只能使用0 ~ 255的数
  • 8位色彩模式,0 ~ 255的数字和颜色一一对应(比如25号颜色对应#ffffff)

修改bootpack.c文件

修改HariMain
  • 代码
    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质量*/
    }
    
  • char a[3]的含义

    相当于
    a:
    RESB 3
    ————————————————————
    “RESB”, “RESW”, “RESD”, “RESQ” , “REST”
    它们声明 未初始化的存储空间

  • char a[3] = {1, 2, 3};的含义

    相当于:
    char a[3];
    a[0] = 1;
    a[1] = 2;
    a[2] = 3;
    但是这种写法会多耗费很多字节

  • static可以控制变量的作用域在局部作用域内
  • signedunsigned未指定型
    char、int、short都分signed和unsigned

    signed型用于处理-128~127的整数
    unsigned型能够处理0~255的整数
    未指定型是指没有特别指定时,可由编译器决定是unsigned还是signed

  • 0xff就是255,表示最大亮度
修改set_palette
  • 代码:
    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;
    }
    
  • CPU的管脚与很多设备相连(内存、键盘、网卡、声卡、软盘)。向设备发送电信号的是OUT指令;从设备取得电信号是IN指令。为了区别不同的设备,需要使用设备号码【port】。在C语言中没有IN或OUT指令相当的语句。
调色板功能的简单实现
  • 硬件在显示像素颜色时,从像素对应的显存读取这个八位数,然后把这个数当做下标,在RGB颜色组中找到对应的RGB颜色值,再把这个颜色值显示到对应的像素上
  • 显存调色板模式的设置方式:
  1. 关闭中断,防止CPU被干扰
  2. 将调色板的号码写入端口0x03c8, 由于我们现在只有一个调色板,因此调色板的编号默认为0,如果要设置多个调色板,那么其他调色板的编号可以一次为1,2…等
  3. 将RGB的颜色数值依次写入端口0x3c9, 假设我们要写入的RGB颜色值是
    0x848484 暗灰
    那么在C语言中,要分3次调用io_out8, 例如:
    io_out(0x3c9, 0x84);
    io_out(0x3c9, 0x84);
    io_out(0x3c9, 0x84);
  • 完整代码:
    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;
    }
    

修改naskfunc.nas文件

端口读写数据原理
  • 读取指定端口8位数据
  • 根据C语言的规约,执行RET语句时,EAX中的值就被看作是函数的返回值
    ;读取指定端口8位数据  int io_in8(int port);
    io_in8:
        mov edx, [esp+4]  ; 将端口号放入edx中
        mov eax, 0	      ; 先将eax中原来的内容清空
        in al, dx		  ; 将此端口号中的内容读取到al中
        ret
    
  • 向指定端口号写入8位数据
    ;向指定端口写入8位数据 void io_out8(int port, int value);
    io_out8:
        mov edx, [esp+4]    ; 将端口号放入edx中
        mov al, [esp+8]	    ; 将数据放入esp中
        out dx, al			; 将数据输出到某端口号中
        ret
    
eflags寄存器
  • 各个标志所在EFLAGS中的位置
    Day4—自制操作系统_第5张图片
  • CF(进位标志):产生进位或借位时CF = 1,否则CF = 0
  • PF(奇偶标志):1的个数为偶数,PF = 1,否则 PF = 0
  • ZF(零标志):相关指令执行后结果为0,那么ZF = 1,否则ZF = 0
  • SF(符号标志):结果为负SF = 1,非负 SF = 0
  • OF(溢出标志):有符号运算的结果是否发生了溢出,溢出OF = 1,否则OF = 0
  • AF(辅助进位标志):当进行加减运算时,若运算结果的低字节的低四位向高四位有进位或借位,则AF为1,否则为0
  • DF(方向标志):当DF为1时,每次操作后使变址寄存器SI和DI减小。这样就使串处理从高地址向低地址方向进行,当DF = 0,反之
  • IF(中断标志):当IF为1,允许CPU响应可屏蔽中断请求,否则,反之。
  • TF(跟踪标志):当TF为1时,每条指令执行完后产生中断,CPU处于单步运行方式。当TF位为0时,CPU正常工作,程序连续执行
  • PUSHFD:将标志位的值按双字节长压入栈。
  • POPFD:按双字节长将标志位从栈弹出
    Day4—自制操作系统_第6张图片
eflags寄存器的读写
  • 汇编语言能直接访问EFLAGS寄存器,因为汇编中没有 "mov eax, EFLAGS" 这种指令,pushfd 指令是专门把EFLAGS寄存器的内容压入堆栈的,使用指令 pushfdEFLAGS的数据压入堆栈,然后再从堆栈中,把压入的数据弹出到eax寄存器里面。
  • 根据C语言的规约,执行RET语句时,EAX中的值就被看作是函数的返回值
    ; 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   ; 该指令的作用是禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,这样可以保证当前运行的代码不被打断,起到保护代码运行的作用
    		RET   
    
    _io_sti:	; void io_sti(void);
    		STI   ; 允许中断发生,在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
    

绘制矩形

  • 在当前画面模式中,画面上有320 * 200个像素
  • 假设左上点的坐标是(0,0),右下点的坐标是(319,199),那么像素坐标(x, y)对应的VRAM地址应按下式计算
    在这里插入图片描述
  • 修改bootpack.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 boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
    
    #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; /* p变量的地址 */
    
    	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();
    	}
    }
    
    // VRAM 的地址;屏幕的长度;颜色;
    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;
    }
    

Day4—自制操作系统_第7张图片

今天的成果

  • 代码
    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 boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
    
    #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 *vram;
    	int xsize, ysize;
    
    	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();
    	}
    }
    
    
    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;
    }
    

Day4—自制操作系统_第8张图片

问题

  • 在set_palette函数中,为什么要将每个颜色的值除以4?
    Day4—自制操作系统_第9张图片
    最开始看到这个除以4,我首先想到的是在opencv在处理像素时,会通过一个简单的除法来进行颜色空间缩减以提高性能,但是重新读了代码后,知道不是一回事,将除数的值增大,色彩的亮度会变暗,如果不设除数,后8种色彩很难与黑色分辨,接着我去查阅了一些资料:
    电脑屏幕上的所有颜色,都由这红色绿色蓝色三种色光按照不同的比例混合而成的。一组红色绿色蓝色就是一个最小的显示单位。屏幕上的任何一个颜色都可以由一组RGB值来记录和表达。每三个确定一个颜色,分别代表红绿蓝的分配比例,分配的越多,代表的颜色就越深。这样就似乎明白作者为什么说只要16种颜色了。确实,16*16=256,
    我们只要修改这个/号除的数,就可以得到不同的颜色。就不进行除法操作那个来说,就是调色板的原色,而进行了除法操作的都是相应色调降低了的。色调是各种图像色彩模式下原色的明暗程度,级别范围从0到255,共256级色调。例如对灰度图像,当色调级别为255时,就是白色,当级别为0时,就是黑色,中间是各种程度不同的灰色。在RGB模式中,色调代表红、绿、蓝三种原色的明暗程度,对绿色就有淡绿、浅绿、深绿等不同的色调。色调
    是指色彩外观的基本倾向。在明度、纯度、色相这三个要素中,某种因素起主导作有用,可以称之为某种色调。实验的结果似乎是证明的原色的明暗程度的改变。
  • 为什么在设定调色板的时候要禁止中断?
    我没有查到具体的解释,只查到了在什么情况下需要关闭中断:
    1、操作的对象是共享资源(全局变量),其他任务可能在整个操作流程中打断你的操作,并且会更改你的全局变量,最终会引起非常严重的后果的情况下需要关中断。
    2、当任务在执行对时序要求非常高的操作,因为被其它任务中断浪费了很多时间,当回来的时候错过了dead
    time,最终导致操作失败。
    我只能推测在向端口写入信息的时候,要通过禁止中断,防止因中断而导致向端口写入了其他信息。

你可能感兴趣的:(自制操作系统)