当前位置:首页 » 《随便一记》 » 正文

30天自制操作系统:第四天 C语言与画面显示的练习_nepu_bin的博客

14 人参与  2021年11月14日 11:03  分类 : 《随便一记》  评论

点击全文阅读


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

章节开始展示了一个用于写入内存的汇编函数: _write_mem8
在这里插入图片描述
对于注释: [ESP + 4]存放的是地址解释:
函数调用的时候参数的压栈顺序是从右到左的,因此有如下结构:

  1. 初始情况假设栈底位置为0x1008,(SS不重要,这里只是做出了标注,并没有给出数值),那么SP寄存器的值应该是栈的最高地址 + 1,0x1008 + 1 = 0x1009,如图

在这里插入图片描述

  1. 栈压入一个参数,如果有汇编基础这块应该好理解,没有也没关系,这里给出压栈CPU执行的指令:
    在这里插入图片描述
    其实很简单,首先SP指针往“上”移动参数大小(字节)个位置,在这里因为用的是ESP,32位拓展寄存器,因此操作的单位数为4字节,也就是SP = SP - 4,然后再将参数data写入到栈中,如图:
    在这里插入图片描述

  2. 压入第二个参数 addr,与第个二步骤一样:
    在这里插入图片描述

到此,就能够理解为什么第一个参数存放的位置是 [ESP + 4] 了吧,跟栈的特点有关(栈从高地址向低地址压入元素)以及push指令的执行。
依次类推,要访问data参数,那么需要偏移2 * 4 = 8:[ESP + 8]

函数类似于C语言中的"write_mem8(0x1234, 0x56);“语句,执行动作相当于"MOV BYTE[0x1234], 0x56”,也就是将数值0x56写入到0x1234内存字节单元中。

想要取得用参数指定的数值0x1234或0x56的内容,就用MOV读入寄存器。因为CPU已经是32位模式,所以我们积极用32位寄存器,16位寄存器也可以使用, 不过机器语言的字节数会增加,执行速度也会变慢。

在指定内存的时候,如果使用16位寄存器:[CX] 或 [SP] 之类的就会出错,但使用32位寄存器,类似:[ECX]、[ESP]都没问题。

需要注意:如果与C语言联合使用,有的寄存器可以自由使用,有的寄存器不能随便使用,能自由使用的只有 EAX、ECX、EDX 这三个,至于其他寄存器,只能使用其值,而不能改变其值,因为这些寄存器在C语言编译后后生成的机器语言中,用于记忆非常重要的值。因此这次我们只用EAX和ECX。


如下图,作者还在naskfunc.nas增加了这条语句(描黑部分):
在这里插入图片描述
INSTRSET指令用于指明该程序是给486用的,nask从此就可以进入到32位模式下进行编译,比如见到EAX这种32位寄存器就可以正确的编译。

虽然表明是486用,但并不是说会出现仅能在486中执行的机器语言,所以该模式下只使用16位寄存器,也能成为8086中可以执行的机器语言。
在这里插入图片描述
到286为止CPU是16位,386以后CPU是32位。


在这里插入图片描述
这个程序不难,一个负责赋值的for循环,唯一的一条语句 write_mem8(i, 16); 作者也给出了等价的汇编语言:MOV BYTE [i], 15


打开 !cons_nt.bat 文件,输入make run 指令:
在这里插入图片描述
显示全是白色,因为在刚刚的bootpack.c程序中将0xa0000 ~ 0xaffff(VRAM) 的位置都填上了15,意思是全部像素的第15种颜色,也就是纯白,所以画面就成了这般景象。

2.条纹图案(harib01b)

这里对bootpack.c程序做了一点修改:
在这里插入图片描述
之前write_mem8函数 写入的内容是15,而这次不一样了,写入 i & 0x0f,这里的&并不是逻辑与,而是位与运算,也就是两个二进制数按位进行与运算。一个数与0x0f = 0000 1111 进行位与相当于高四位全部置为0,第四位保留原来的值,


打开 harib01b下的!cons_nt.bat文件,输入 make run 指令:
在这里插入图片描述


3.挑战指针(harib01c)

如果将 write_mem(i, i & 0x0f **); 替换成 i = i & 0x0f;按照之前执行的步骤输入make run,会发现出现 “invalid type argument of ‘unary *’”,翻译过来就是无效类型,出现了类型错误。

先看看这条汇编语句:
mov [0x1234], 0x56;
很明显这是不允许的,因为对于一块内存空间没有指明大小(内存是连续的,可以将0x1234这一字节单元赋值为0x56, 当然也可以将0x1234以及相邻的内存单元0x1235赋值成0x56),如何告诉计算机这是BYTE?
对于汇编语言,使用 mov byte ptr [0x1234], 0x56即可
对于C语言,使用char* p,1字节类型的指针即可。
**char *p: 用于BYTE类地址,short p:用于WORD类地址,int p:用于DWORD类地址。

注:“char i;” 类似AL的1字节变量,“short i;” 是类似AX的2字节变量,“int i;” 是类似EAX的4字节变量。
而不论是 “char *p”, 还是 “short *p”, 或是 “int *p”, 变量p都是4字节,这是因为p是用于记录地址的变量。在汇编语言中,地址也像ECX一样,用4字节的寄存器来指定,所以也是4字节。


使用make run执行以下程序:

void HariMain(void)
{
	int i; /* 変数宣言。iという変数は、32ビットの整数型 */
	char *p; /* pという変数は、BYTE [...]用の番地 */

	for (i = 0xa0000; i <= 0xaffff; i++) {

		p = i; /* 番地を代入 */
		*p = i & 0x0f;

		/* これで write_mem8(i, i & 0x0f); の代わりになる */
	}

	for (;;) {
		io_hlt();
	}
}

编译时会有一个警告:bootpack.c:10: warning: assignment makes pointer from integer without a cast
在这里插入图片描述
这是由于程序中用了一个整数给指针类型变量p赋值没有进行类型转换。
在这里插入图片描述
这里的内容比较基础,直接引用作者的原解释↑


COLUMN-2

如果定义了:p = (char*) i;
将上式代入到 *p = i & 0x0f中,可以得到:
((char) i) = i & 0x0f,这样的写法也没问题,而且还省去了p变量,与汇编语言:BYTE[i] = i & 0x0f; 有点类似。就是理解起来比较困难。

COLUMN-3 还是不能理解指针

使用C语言完成以下功能:
MOV BYTE [i], (i & 0x0f),也就是向内存的第 i 号地址写入 i & 0x0f 的结果,C语言程序可以写为:
在这里插入图片描述
如果假设p是ECX,那么后两句就可以写成:
MOV ECX, i
MOV BYTE [ECX], (i & 0x0f)
它们区别的区别很明显:一个是给ECX赋值,一个是给ECX号地址赋值 。
这完全是两码事,存储它们的半导体不一样,一个在CPU里,一个在内存芯片里,在C语言中,虽然p与*p只有一字之差,但意思上差别却如此之大。


另一个关于声明的问题:在C语言中,如果不声明变量就不能使用。在C语言中,声明了十个变量,就可以使用十个变量,没问题。

既然如此,那为什么只声明了 “char p;” 却不仅能使用p,还可以使用p,回到汇编语言:
MOV ECX, I
MOV BYTE [ECX], (i & 0x0f)

在这里插入图片描述在这里插入图片描述

对于指针的总结(截自原书)

在这里插入图片描述
在这里插入图片描述

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

说明了绘制条纹图案的部分还可以写成这样:
在这里插入图片描述

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

在这里插入图片描述
同4,这是另一种可行的写法。

6.色号设定(harib01f)

这里使用了320 x 200的8位颜色模式,该模式允许程序员随意指定0 ~ 255 的数字所对应的颜色,比如25号颜色对应#ffffff, 26号颜色对应#123456,这种方式叫做调色板(palette)。
在这里插入图片描述
在这里插入图片描述

需要注意的是set_palette函数的第三个参数rgb指针必须是无符号类型(unsigned char), 否则如果想表达255会被记为-1。

函数set_palette

在这里插入图片描述
io_out8:往指定装置里传送数据的函数。

CPU的管脚与内存相连,如果仅仅是与内存相连,CPU只能完成计算和存储的功能。实际上,CPU还要对键盘的输入有响应,要通过网卡从网络取得信息,通过声卡发送音乐数据,向软盘写入信息等,这些都是设备,它们当然也需要连接到CPU上。

既然CPU与设备相连,那么就有向这些设备发送电信号,或者从这些设
备取得信息的指令。向设备发送电信号的是OUT指令;从设备取得电气
信号的是IN指令。正如为了区别不同的内存要使用内存地址一样,在
OUT指令和IN指令中,为了区别不同的设备,也要使用设备号码。设备
号码在英文中称为port(端口)。port原意为“港口”,这里形象地将CPU
与各个设备交换电信号的行为比作了船舶的出港和进港。

0x03c8、0x03c9的解释

上述程序中,为什么使用了这些地址。
在这里插入图片描述
在这里插入图片描述

CLT、STI

在这里插入图片描述
CLI,是将中断标志(interrupt flag)置为0的指
令(clear interrupt flag)。STI是要将这个中断标志置为1的指令(set
interrupt flag)。而标志,是指像以前曾出现过的进位标志一样的各种
标志,也就是说在CPU中有多种多样的标志。更改中断标志有什么好处
呢?正如其名所示,它与CPU的中断处理有关系。当CPU遇到中断请求
时,是立即处理中断请求(中断标志为1),还是忽略中断请求(中断
标志为0),就由这个中断标志位来设定。

EFLAGS标志

这是由名为FLAGS的16位寄存器扩展而来的32位寄存器。FLAGS是存储进位标志和中断标志等标志的寄存器。进位标志可以通过JC或JNC等跳转指令来简单地判断到底是0还是1。但对于中断标志,没有类似的JI或JNI命令,所以只能读入EFLAGS,再检查第9位是0还是1。顺便说一下,进位标志是EFLAGS的第0位。
在这里插入图片描述
EFLAGS是不能够直接读取的,只能通过指令 PUSHFD(“push flags double-word”,将标志位的值按双字长压入栈),就是将EFLAGS压入栈中,POPFD,同理,就是将EFLAGS弹出。

感受

今天的博客有点缩水(后面两小节没啥可以写的,所以直接删除了),主要原因是很多内容如今并没有能力自己清晰写出来,只能是照搬原书的解析,笔者是想借助此种方式融入一些自己的想法,可以加深自己的理解,也是为了记录做成一个OS的路程。Over


点击全文阅读


本文链接:http://zhangshiyu.com/post/30855.html

寄存器  标志  指令  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1