自制操作系统日志——第二十三天
今天呢,主要进行图形处理API相关的描绘。加油加油!
一、编写malloc
我们使用二进制编辑器打开winhelo2.hrb看看,会发现这里面有很多0存在:
这里,之所以会出现这样子,是由于我们前面定义的buf中留下了很多的空白之处。这就会造成极大的冗余,因此这里想要编写一个程序以便能够动态的分配空间大小。即buf这一句相当于汇编的resb,将7500字节的值全部赋值为0。
这里,我们先从简单的开始,我们应用程序可以进行读写的知识最开始操作系统为它准备好的数据段的内存空间中,因此我们这里从简单的开始设计起。我们将一开始分配多一点的内存空间,当我们malloc时候就可以从多余的空间里拿出来。
至于在哪里进行指定呢? 我们这里主要是在bim2hrb里进行指定的:
当我们在这里指定好了后,这个数值就会和栈的大小进行累加,并写入hrb文件开头的四字节中。当然malloc在内存中的起始地址也会被保存在0x0020处。
于是乎,我们设计如下的API:
memman初始化:
- EDX = 8
- EBX = memman的地址
- EAX = memman所管理的内存空间的起始地址
- ECX = memman所管理的内存空间字节数
malloc:
- EDX = 9
- EBX = memman的地址
- ECX = 需请求的字节数
- EAX = 分配到的内存空间地址
free:
- EDX = 10
- EBX = memman的地址
- EAX = 所释放的内存空间地址
- ECX = 所释放的字节数
修改:
console.c:
else if (edx == 8){
memman_init((struct MEMMAN *) (ebx + ds_base));
ecx &= 0xfffffff0;//以16字节为单位
memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
}else if (edx == 9){
ecx = (ecx + 0x0f) & 0xfffffff0;//以16字节为单位取整
reg[7] = memman_alloc((struct MEMMAN *) (ebx + ds_base), ecx);
}else if (edx == 10){
ecx = (ecx + 0x0f) & 0xfffffff0;//以16字节为单位取整
memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
}
a_nask.nas:
_api_initmalloc: ; void api_initmalloc(void);
PUSH EBX
MOV EDX,8
MOV EBX,[CS:0x0020] ; malloc的内存地址空间
MOV EAX,EBX
ADD EAX,32*1024 ; 因为管理内存空间的结构大小也要在其中存储,因此实际的内存起始地址在32kb之后
MOV ECX,[CS:0x0000] ; 数据段大小
SUB ECX,EAX
INT 0x40
POP EBX
RET
_api_malloc: ; char *api_malloc(int size);
PUSH EBX
MOV EDX,9
MOV EBX,[CS:0x0020]
MOV ECX,[ESP+8] ; size
INT 0x40
POP EBX
RET
_api_free: ; void api_free(char *addr, int size);
PUSH EBX
MOV EDX,10
MOV EBX,[CS:0x0020]
MOV EAX,[ESP+ 8] ; addr
MOV ECX,[ESP+12] ; size
INT 0x40
POP EBX
RET
上面这些代码我相信已在座的各位,经历了那么多天应该不难了。
winhole3.c:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_end(void);
void HariMain(void)
{
char *buf;
int win;
api_initmalloc();
buf = api_malloc(150 * 50);
win = api_openwin(buf, 150, 50, -1, "hello");
api_boxfilwin(win, 8, 36, 141, 43, 6 /*
F */);
api_putstrwin(win, 28, 28, 0 /* • */, 12, "hello, world");
api_end();
}
然后运行,嘿:
看吧,果然小了不少吧,嘿嘿嘿!!!
二、图形处理
1.画点
API指示如下:
- EX = 11
- EBX = 窗口句柄
- ESI = 显示位置的x坐标
- EDI = 显示位置的y坐标
- EAX = 色号
console.c:
else if (edx == 11){
sht = (struct SHEET *) ebx;
sht->buf[sht->bxsize * edi + esi] = eax;
sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
}
a_nask.nas:
_api_point: ; void api_point(int win, int x, int y, int col);
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDX,11
MOV EBX,[ESP+16] ; win
MOV ESI,[ESP+20] ; x
MOV EDI,[ESP+24] ; y
MOV EAX,[ESP+28] ; col
INT 0x40
POP EBX
POP ESI
POP EDI
RET
至此,让我们来看看程序的运行效果吧:
star1:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_point(int win, int x, int y, int col);
void api_end(void);
void HariMain(void)
{
char *buf;
int win;
api_initmalloc();
buf = api_malloc(150 * 100);
win = api_openwin(buf, 150, 100, -1, "star1");
api_boxfilwin(win, 6, 26, 143, 93, 0 /* 黑色• */);
api_point(win, 75, 59, 3 /* 黄色 */);
api_end();
}
stars:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_point(int win, int x, int y, int col);
void api_end(void);
int rand(void); /* 产生随机数 */
void HariMain(void)
{
char *buf;
int win, i, x, y;
api_initmalloc();
buf = api_malloc(150 * 100);
win = api_openwin(buf, 150, 100, -1, "stars");
api_boxfilwin(win, 6, 26, 143, 93, 0 /* 黑色*/);
for (i = 0; i < 50; i++) {
x = (rand() % 137) + 6;
y = (rand() % 67) + 26;
api_point(win, x, y, 3 /* 黄色*/);
}
api_end();
}
2.刷新窗口
其实吧,想上面能够程序,如果我们每调用一次画点,就刷新一次这样子的做法,实在是太墨迹了。。。于是乎,我们想利用一个办法,等待所有点描绘完以后呢,我们在进行刷新。
由于我们知道struct SHEET的地址(因为前面图层地址都是4k进行分配的。)一定是一个偶数,因此,我们可以利用此制作一个刷新的函数。当我们传入的图层地址不为偶数时,就不用刷新。
具体代码如下:
else if (edx == 6) {
sht = (struct SHEET *) (ebx & 0xfffffffe) ;//与0xffffffe相与,就能按2的倍数取整
putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base);
if((ebx & 1 ) == 0){
sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);
}
} else if (edx == 7) {
sht = (struct SHEET *) (ebx & 0xfffffffe) ;
boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);
if((ebx & 1 ) == 0){
sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
}
略
else if (edx == 11){
sht = (struct SHEET *) (ebx & 0xfffffffe) ;
sht->buf[sht->bxsize * edi + esi] = eax;
if((ebx & 1 ) == 0){
sheet_refresh(sht, esi, edi, esi + 1, edi + 1);
}
}else if (edx == 12){
sht = (struct SHEET *) ebx ;
sheet_refresh(sht, eax, ecx, esi, edi);
}
a_nask.nas:
_api_refreshwin: ; void api_refreshwin(int win, int x0, int y0, int x1, int y1);
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDX,12
MOV EBX,[ESP+16] ; win
MOV EAX,[ESP+20] ; x0
MOV ECX,[ESP+24] ; y0
MOV ESI,[ESP+28] ; x1
MOV EDI,[ESP+32] ; y1
INT 0x40
POP EBX
POP ESI
POP EDI
RET
stars2:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_point(int win, int x, int y, int col);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_end(void);
int rand(void); /* 0`32767‚Ě”ÍˆÍ‚Ĺ—”‚𔜠*/
void HariMain(void)
{
char *buf;
int win, i, x, y;
api_initmalloc();
buf = api_malloc(150 * 100);
win = api_openwin(buf, 150, 100, -1, "stars2");
api_boxfilwin(win + 1, 6, 26, 143, 93, 0 /* • */);
for (i = 0; i < 50; i++) {
x = (rand() % 137) + 6;
y = (rand() % 67) + 26;
api_point(win + 1, x, y, 3 /* ‰Š */);
}
api_refreshwin(win, 6, 26, 144, 94);
api_end();
}
嗯嗯,我是感觉快了一些,嘿嘿嘿!!
画直线
直线的绘画,主要还是利用了前面的点(毕竟有点就可以描绘一切)。我们进行描绘的图形有以下几个变量:x、y表示直线的起始地址;dx、dy表示直线延伸的方向;len表示直线的长度。一般来说x、y、dx、dy是需要支持小数的,不然可能画出来的就是虚线了。但是目前,我们还没做好使用小数的准备,因此我们想把这四个整数预先进行扩大1024倍(为什么是1024呢?因为这样子可以直接利用移位进行运算,而非除法了,效率更高)。
这里我们用1000来进行解释扩大的作用,以便更好的理解:
for( i= 0; i <len; i++){
api_point(win,x / 1000, y / 1000);
x += dx;
y += dy;
}
假设x一开始是100000,dx为123,则相当于用整数实现了将整数100每次累加0.123这样子的运算。
关于直线的API如下:
- EDX = 13
- EBX = 窗口句柄
- EAX = x0
- ECX = y0
- ESI = x1
- EDI = y1
- EBP = 色号
console.c:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
略
else if (edx == 13){
sht = (sturct SHEET *) (ebx & 0xfffffffe);
hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);
if((ebx & 1) == 0){
sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
}
略
}
//画直线,自动计算len,与合适的dx、dy
void hrb_api_linewin(struct SHEET *sht, int x0, int y0, int x1, int y1, int col)
{
int i, x, y, len, dx, dy;
dx = x1 - x0;
dy = y1 - y0;
x = x0 << 10; //相当于除以1024
y = y0 << 10;
if(dx < 0){
dx = -dx;
}
if(dy < 0){
dy = -dy;
}
if(dx >= dy){
len = dx + 1;
if( x0 > x1){
dx = -1024;
}else{
dx = 1024;
}
if(y0 <= y1){
dy = ((y1 - y0 + 1) << 10) /len;
}else{
dy = ((y1 - y0 - 1) << 10) /len;
}
}else{
len = dy + 1;
if(y0 > y1){
dy = -1024;
}else{
dy = 1024;
}
if(x0 <= x1){
dx = ((x1 - x0 + 1) << 10) /len;
}else{
dx = ((x1 - x0 - 1) << 10) /len;
}
}
for(i = 0; i < len; i++){
sht->buf[(y >> 10) * sht->bxsize + (x >> 10)] = col;
x += dx;
y += dy;
}
return;
}
以上,解释一下:
- 我们通过比较直线的起点与终点的坐标,将变化较大的作为len+1(因为不加的话,终点位置的那一个点可能无法描绘了)。
- 接着,我们将变化较大的以防设为1024或者-1024 即(1 或 -1),变化较小的一方设为 变化量+1 再除以len。
由于,dx、dy的值过大的话,可能会导致画出的点不连续,过小的话可能会在原地重复多次,浪费cpu的运行时间。因此,我们这里将x、y分成变化大和变化小的部分。变化大的每次递增1,变化小的则利用变化增量来进行计算,这样子就可以画出一个肉眼可见,且效率相对较高的直线了。
编写程序:
a_nask.nas:
_api_linewin: ; void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
PUSH EDI
PUSH ESI
PUSH EBP
PUSH EBX
MOV EDX,13
MOV EBX,[ESP+20] ; win
MOV EAX,[ESP+24] ; x0
MOV ECX,[ESP+28] ; y0
MOV ESI,[ESP+32] ; x1
MOV EDI,[ESP+36] ; y1
MOV EBP,[ESP+40] ; col
INT 0x40
POP EBX
POP EBP
POP ESI
POP EDI
RET
lines.c:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);
void HariMain(void)
{
char *buf;
int win, i;
api_initmalloc();
buf = api_malloc(160 * 100);
win = api_openwin(buf, 160, 100, -1, "lines");
for (i = 0; i < 8; i++) {
api_linewin(win + 1, 8, 26, 77, i * 9 + 26, i);
api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
}
api_refreshwin(win, 6, 26, 154, 90);
api_end();
}
然后,运行看看:
喔喔,成功了很好嘛!!!
关闭窗口
由于,我们之前再给应用程序分配图层时,将应用程序的数据段,作为了存放窗口图层的内存空间,而我们目前没有制作释放这段内存空间的功能,因此窗口会一直存在,那么接下来让我们进一步的进行窗口的关闭功能吧。
API:
- EDX = 14
- EBX = 窗口句柄
else if (edx == 14){
sheet_free((struct SHEET *) ebx);
}
a_nask.nas:
_api_closewin: ;void api_closewin( int win)
PUSH EBX
MOV EDX,14
MOV EBX,[ESP+8] ;Win
INT 0x40
POP EBX
RET
lines.c:
api_refreshwin(win, 6, 26, 154, 90);
api_colsewin(win);
api_end();
当然,如果只是上面这样子,那将会关闭的很快,没啥意义了,因此我们再改进一下,即当命令行再接收一个回车符时候,才进行关闭:
API:
- EDX = 15
- EAX = 0 ⇒ 没有键盘输入时,返回-1,不休眠; 1 ⇒ 休眠直到键盘输入; 其他字符的输入;
console.c:
}else if (edx == 15){
for(;;){
io_cli();
if(fifo32_status(&task->fifo) == 0){
if(eax != 0){
task_sleep(task); //FIFO为空,休眠并等待
}else{
io_sti();
reg[7] = -1;//eax的值
return 0;
}
}
i = fifo32_get(&task->fifo);
io_sti();
if(i <= 1){//光标用定时器
//运行时不需要光标,因此总是置为1即可
timer_init(cons->timer, &task->fifo, 1);
timer_settime(cons->timer, 50);
}
if(i == 2){//光标ON
cons->cur_c = COL8_FFFFFF;
}
if(i == 3){//光标OFF
cons->cur_c = -1;
}
if(256 <= i && i<=511){
reg[7] = i - 256;
return 0;
}
}
}
a_nask.nas:
_api_getkey: ;int appi_getkey(int mode)
MOV EDX,15
MOV EAX,[ESP+4]
INT 0x40
RET
lines.c:
api_refreshwin(win, 6, 26, 154, 90);
for(;;){
if ( api_getkey(1) == 0x0a) //api_getkey里可以通过EAX进行将值返回给应用程序的!0x0a就是回车符
{
break;
}
}
api_closewin(win);
api_end();
那么在运行看看:
输入回车符后:
噢噢噢噢,成功了!!皆大欢喜呀。
娱乐一下:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_closewin(int win);
int api_getkey(int mode);
void api_end(void);
void HariMain(void)
{
char *buf;
int win, i, x, y;
api_initmalloc();
buf = api_malloc(160 * 100);
win = api_openwin(buf, 160, 100, -1, "walk");
api_boxfilwin(win, 4, 24, 155, 95, 0 /* 黑色 */);
x = 76;
y = 56;
api_putstrwin(win, x, y, 3 /* 黄色*/, 4, "yuan");
for (;;) {
i = api_getkey(1);
api_putstrwin(win, x, y, 0 /* 黑色 */, 4, "yuan"); /* 黄色 */
if (i == '4' && x > 4) { x -= 8; }
if (i == '6' && x < 148) { x += 8; }
if (i == '8' && y > 24) { y -= 8; }
if (i == '2' && y < 80) { y += 8; }
if (i == 0x0a) { break; } /* 黑色 */
api_putstrwin(win, x, y, 3 /* 黄色*/, 4, "yuan");
}
api_closewin(win);
api_end();
}
嘿嘿嘿给,按下上下左右,他就会自己移动了喔:
bug-强制结束无法关闭图层
这里,我们尝试执行以下shift + F1发现,虽然将程序结束了,但是图层还留在原地:
这里是因为,我们强制结束的话根本没执行到closewin嘛,嘿嘿嘿,那我们稍作修改看看:
那么,我们就利用再图层里添加一个task成员,当应用程序结束时查询所有图层,如果有task为将要结束的话,就关闭该图层:
bootpack.h:
struct SHEET
{
unsigned char *buf;//记录图层上所描绘图画的地址
int bxsize, bysize, vx0, vy0, col_inv, height, flags;
//图层整体大小用bxsize、bysize表示;vx0, vy0 图层在vram画面上位置的相对坐标
//col_inv 表颜色的透明度; height 表图层高度;flag记录图层各种设定信息
struct SHTCTL *ctl;
struct TASK *task;
};
sheet.c:
struct SHEET *sheet_alloc(struct SHTCTL *ctl)
{
struct SHEET *sht; //建立一个新的图层,以便进行后续的返回
int i;
for(i = 0; i < MAX_SHEETS; i++) //开始循环,直到找到可用的图层为止
{
if(ctl->sheets0[i].flags == 0)
{
sht = &ctl->sheets0[i];//将该可用图层的地址传入给sht
sht->flags = SHEET_USE; //标记为正在使用
sht->height = -1; //隐藏,先将高度设为-1,置于底层
sht->task = 0;//不使用自动关闭功能
return sht;
}
}
return 0; //所有的sheet都在使用当中
}
console.c:
*hrb_api:
}else if (edx == 5){
sht = sheet_alloc(shtctl);
sht->task = task;
cmd_app:
略
struct SHTCTL *shtctl;
struct SHEET *sht;
略
if (finfo != 0) {
略
start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
shtctl = (struct SHTCTL *) *((int *) 0xfe4);
for(i = 0; i <MAX_SHEETS; i++){
sht = &(shtctl->sheets0[i]);
if(sht->flags !=0 && sht->task == task)
{
//找到被应用程序遗留的窗口
sheet_free(sht);//关闭
}
}
略
成功了!!!很好,上面的代码一定要写在start_app之后,因为只有这样子,才能再应用程序终止返回到下面继续执行。
总结
以上就是今天的内容了。关于图像显示之类的还是很有意思的,嘿嘿嘿。