1 编写malloc(harib20a)

大家早上好,今天心情真不错,让我们来继续努力吧!

昨天我们显示出了窗口并绘制了方块、显示了文字,今天我们要来绘制更多的东西。不过,一开始我们先来做点别的事情。

■■■■■

今天早上在玩“纸娃娃系统”的时候发现一个问题,winhelo2.hrb居然有7.6KB。实现了窗口显示功能之后,可执行文件就一下子变得那么大,这令笔者十分不爽(注:笔者就喜欢短小精悍的程序)。

为了找原因,笔者用二进制编辑器打开winhelo2.hrb看了看,里面居然有很多的“00”,这到底是怎么回事!

于是笔者又回去检查了一遍源代码,发现原因在于winhelo2.c的char buf[150 * 50]; 这一句,这相当于在可执行文件中插入了150×50=7500个字节的“00”,和汇编语言中的RESB 7500是等效的。

只要去掉这句,可执行文件就可以变小很多,问题是怎样才能实现呢?如果应用程序也有一个类似memman_alloc的函数用于分配内存空间就好了。在操作系统中,这样的功能一般被称为malloc,因此我们就来编写一个api_malloc函数吧。

■■■■■

如果api_malloc只是调用操作系统中的memman_alloc,并将分配到的内存空间地址返回给应用程序的话,是行不通的,因为通过memman_alloc所获得的内存空间并不位于应用程序的数据段范围内,应用程序是无法进行读写操作的。如果应用程序在不知情的情况下执行了读写操作,将会产生异常并强制结束。

说到底,应用程序可以进行读写的只是最开始操作系统为它准备好的数据段中的内存空间而已,那么如果我们一开始就将应用程序用的数据段分配得大一点,当需要malloc的时候从多余的空间里面拿出一小部分来交给应用程序不就好了吗?

其实,市面上大多数操作系统中,当请求malloc的时候会根据需要调整应用程序的段大小。而这次我们在“纸娃娃系统”中所采用的事先多分配内存空间的方法,实在算不上是个聪明的办法。

不过由于我们今后还会对操作系统进行各种改良,因此现在就先用这个笨办法将就一下吧,一开始就选择最聪明的办法还是很有难度的。

■■■■■

虽说我们要“事先多分配一些内存空间”,但如果由操作系统单方面定一个值,比如100KB,可能某些情况下不够用,某些情况下又浪费了,因此还是像栈一样,在编写应用程序的时候指定出来比较好。这样一来,当应用程序需要使用很多malloc时可以设定为1MB之类的,而当应用程序完全不需要使用malloc时则可以设定为0。

在哪里指定这个值呢?我们在用bim2hrb的时候指定。之前我们都是像下面这样直接指定为0的。

  1. $(BIM2HRB) winhelo2.bim winhelo2.hrb 0

但这里如果改成3k的话,系统就会为malloc准备3KB的内存空间。

当指定了malloc所需内存大小时,这个数值会和栈等的大小进行累加,并写入.hrb文件最开头的4个字节中。因此,操作系统不需要做任何改动,就可以确保在应用程序段中分配到包括malloc所需部分在内的全部内存空间。

同时,malloc用的内存空间在数据段中的开始位置,被保存在.hrb文件的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。

本次的console.c节选

  1. int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
  2. {
  3. (中略)
  4. } else if (edx == 8) {
  5. memman_init((struct MEMMAN *) (ebx + ds_base));
  6. ecx &= 0xfffffff0; /*以16字节为单位*/
  7. memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
  8. } else if (edx == 9) {
  9. ecx = (ecx + 0x0f) & 0xfffffff0; /*以16字节为单位进位取整*/
  10. reg[7] = memman_alloc((struct MEMMAN *) (ebx + ds_base), ecx);
  11. } else if (edx == 10) {
  12. ecx = (ecx + 0x0f) & 0xfffffff0; /*以16字节为单位进位取整*/
  13. memman_free((struct MEMMAN *) (ebx + ds_base), eax, ecx);
  14. }
  15. return 0;
  16. }

然后我们来编写应用程序。

本次的a_nask.nas节选

  1. _api_initmalloc: ; void api_initmalloc(void);
  2. PUSH EBX
  3. MOV EDX,8
  4. MOV EBX,[CS:0x0020] ; malloc内存空间的地址
  5. MOV EAX,EBX
  6. ADD EAX,32*1024 ; 加上32KB
  7. MOV ECX,[CS:0x0000] ; 数据段的大小
  8. SUB ECX,EAX
  9. INT 0x40
  10. POP EBX
  11. RET
  12. _api_malloc: ; char *api_malloc(int size);
  13. PUSH EBX
  14. MOV EDX,9
  15. MOV EBX,[CS:0x0020]
  16. MOV ECX,[ESP+8] ; size
  17. INT 0x40
  18. POP EBX
  19. RET
  20. _api_free: ; void api_free(char *addr, int size);
  21. PUSH EBX
  22. MOV EDX,10
  23. MOV EBX,[CS:0x0020]
  24. MOV EAX,[ESP+ 8] ; addr
  25. MOV ECX,[ESP+12] ; size
  26. INT 0x40
  27. POP EBX
  28. RET

本次的winhelo3.c

  1. int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
  2. void api_putstrwin(int win, int x, int y, int col, int len, char *str);
  3. void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
  4. void api_initmalloc(void);
  5. char *api_malloc(int size);
  6. void api_end(void);
  7. void HariMain(void)
  8. {
  9. char *buf;
  10. int win;
  11. api_initmalloc();
  12. buf = api_malloc(150 * 50);
  13. win = api_openwin(buf, 150, 50, -1, "hello");
  14. api_boxfilwin(win, 8, 36, 141, 43, 6 /*浅蓝色*/);
  15. api_putstrwin(win, 28, 28, 0 /*黑色*/, 12, "hello, world");
  16. api_end();
  17. }

应该没有什么难点,不过,有一个地方需要注意:malloc用来管理内存的结构(struct MEMMAN)存放在malloc内存空间最开始的地方,因此要多申请出一些malloc所需的空间用于存放这个结构。那么,在winhelo3.hrb中总共申请了40k的空间(32+8=40)。

■■■■■

又到了“make run”的时间,在运行之前我们先用dir来看一下文件的大小。现在只有387个字节了,太好了!然后我们来运行程序……运行成功!

1 编写malloc(harib20a) - 图1

只有387个字节却运行成功了哦

当然,如果用nask来编写的话程序应该会更小,不过那实在能累死人,还是免了吧。