5 IPL的改良(harib27e)

进入本章以后(尤其是在测试mmlplay.hrb的时候),笔者在真机环境下运行了几次“纸娃娃系统”,发现启动时读取磁盘的时间还是有点长。现在我们是读取20个柱面,如果能减少到14或者18个这样勉强够用的柱面数的话,启动速度就可以加快了(不过代价是,以后新增文件的时候又要重新调整)。

于是我们先用二进制编辑器查看一下harib27d的haribote.img文件,发现数据占用的部分是到028C98为止。额,这是多少个柱面呢?一个柱面是512×18×2=18432字节(当然,这是用calc.hrb计算的哟!……笑),因此0x28c99 / 18432=9个柱面。

什么!只要9个柱面就够了吗?!真奇怪,当初明明是因为10个柱面不够用我们才特地抛弃了ipl10.nas改用ipl20.nas的呀?哦,对了,我们用tek对nihongo.fnt进行了压缩,因此大幅度减少了容量,真开心呀。那我们将ipl10.nas还原回去就好了呢,这样启动时间就只有现在的一半了。

不过且慢,0x28c99 % 18432=1193字节(求余数运算用calc.hrb也可以轻松搞定,真方便),也就是说,如果我们能再将文件容量缩减1KB左右,就可以将读取的范围缩小到9个柱面了,这样一来启动时间又可以缩短一成。好,我们加油试试看,有什么好办法呢?啊,有了,我们将ipl20.nas也用tek压缩一下就好了呢!如果可以减小3个扇区的话,ipl09.nas就足够了。

■■■■■

嗯,既然要做ipl09.nas的话,不如顺便对磁盘读取做个优化。这里所说的优化,是将AL = 1这样逐个扇区读取的方法,改为同时读取多个扇区(参见3.1节和3.3节)。至于这种优化是否会带来速度上的提升则是因人而异的。在大部分内置软驱的电脑上,逐个扇区读取的速度可能已经相当快了,优化之后速度也不会有什么提升。不过像笔者这样使用USB1.1外置软驱来启动的话,优化之后大约可以比原来的速度提高10倍左右。

ipl09.nas节选

  1. ; haribote-ipl
  2. ; TAB=4
  3. CYLS EQU 9 ; 要读取多少内容
  4. ; (中略)
  5. ; 读取磁盘
  6. MOV AX,0x0820
  7. MOV ES,AX
  8. MOV CH,0 ; 0柱面
  9. MOV DH,0 ; 0磁头
  10. MOV CL,2 ; 2扇区
  11. MOV BX,18*2*CYLS-1 ; 要读取的合计扇区数 从此开始
  12. CALL readfast ; 告诉读取
  13. ; 读取结束,运行haribote.sys
  14. MOV BYTE [0x0ff0],CYLS ; 记录IPL实际读取了多少内容 到此结束
  15. JMP 0xc200
  16. error:
  17. MOV AX,0
  18. MOV ES,AX
  19. MOV SI,msg
  20. putloop:
  21. MOV AL,[SI]
  22. ADD SI,1 ; SI1
  23. CMP AL,0
  24. JE fin
  25. MOV AH,0x0e ; 显示一个字符的函数
  26. MOV BX,15 ; 颜色代码
  27. INT 0x10 ; 调用显示BIOS
  28. JMP putloop
  29. fin:
  30. HLT ; 暂时让CPU停止运行
  31. JMP fin ; 无限循环
  32. msg:
  33. DB 0x0a, 0x0a ; 两个换行
  34. DB "load error"
  35. DB 0x0a ; 换行
  36. DB 0
  37. readfast: ; 使用AL尽量一次性读取数据 从此开始
  38. ; ES:读取地址, CH:柱面, DH:磁头, CL:扇区, BX:读取扇区数
  39. MOV AX,ES ; < 通过ES计算AL的最大值 >
  40. SHL AX,3 ; AX除以32,将结果存入AHSHL是左移位指令)
  41. AND AH,0x7f ; AHAH除以128所得的余数(512*128=64K
  42. MOV AL,128 ; AL = 128 - AH; AHAH除以128所得的余数(512*128=64K
  43. SUB AL,AH
  44. MOV AH,BL ; < 通过BX计算AL的最大值并存入AH >
  45. CMP BH,0 ; if (BH != 0) { AH = 18; }
  46. JE .skip1
  47. MOV AH,18
  48. .skip1:
  49. CMP AL,AH ; if (AL > AH) { AL = AH; }
  50. JBE .skip2
  51. MOV AL,AH
  52. .skip2:
  53. MOV AH,19 ; < 通过CL计算AL的最大值并存入AH >
  54. SUB AH,CL ; AH = 19 - CL;
  55. CMP AL,AH ; if (AL > AH) { AL = AH; }
  56. JBE .skip3
  57. MOV AL,AH
  58. .skip3:
  59. PUSH BX
  60. MOV SI,0 ; 计算失败次数的寄存器
  61. retry:
  62. MOV AH,0x02 ; AH=0x02 : 读取磁盘
  63. MOV BX,0
  64. MOV DL,0x00 ; A
  65. PUSH ES
  66. PUSH DX
  67. PUSH CX
  68. PUSH AX
  69. INT 0x13 ; 调用磁盘BIOS
  70. JNC next ; 没有出错的话则跳转至next
  71. ADD SI,1 ; SI1
  72. CMP SI,5 ; SI5比较
  73. JAE error ; SI >= 5则跳转至error
  74. MOV AH,0x00
  75. MOV DL,0x00 ; A
  76. INT 0x13 ; 驱动器重置
  77. POP AX
  78. POP CX
  79. POP DX
  80. POP ES
  81. JMP retry
  82. next:
  83. POP AX
  84. POP CX
  85. POP DX
  86. POP BX ; ES的内容存入BX
  87. SHR BX,5 ; BX16字节为单位转换为512字节为单位
  88. MOV AH,0
  89. ADD BX,AX ; BX += AL;
  90. SHL BX,5 ; BX512字节为单位转换为16字节为单位
  91. MOV ES,BX ; 相当于EX += AL * 0x20;
  92. POP BX
  93. SUB BX,AX
  94. JZ .ret
  95. ADD CL,AL ; CL加上AL
  96. CMP CL,18 ; CL18比较
  97. JBE readfast ; CL <= 18则跳转至readfast
  98. MOV CL,1
  99. ADD DH,1
  100. CMP DH,2
  101. JB readfast ; DH < 2则跳转至readfast
  102. MOV DH,0
  103. ADD CH,1
  104. JMP readfast
  105. .ret:
  106. RET ; 到此结束
  107. RESB 0x7dfe-$ ; 0x7dfe为止用0x00填充的指令
  108. DB 0x55, 0xaa

完工,代码中加入了很多注释,应该没有什么特别难懂的地方了。

■■■■■

哎呀呀,糟糕!我们修改了ipl09.nas之后,为了实现对磁盘读取的优化,结果代码却变长了。和ipl20.nas相比,从2992字节增加到了4081字节。不过大家也不用担心,我们还有tek5压缩呢,压缩之后就变成了1778字节。你看,变小了很多吧。

我们修改一下Makefile,将这个新文件装入磁盘映像,然后“make”,再用二进制编辑器打开生成好的磁盘映像确认一下。唔,貌似数据占用的部分到028898。0x28899 / 18432 = 9个柱面,0x28899 % 18432 = 153字节……什么!居然超出了153字节?!不是吧!

如果在这里乖乖就范,换回ipl10.nas的话也太憋屈了,于是我们可以抛弃其中一个应用程序,比如sosu或者hello之类的。能抛弃的应用程序实在是太多了(苦笑),反而不知道该抛弃哪个比较好了1。呜呜呜!

1 大家可千万别说“这种程序全部丢掉算了”这种话,虽然这个主意挺正确的,但实在是太“残忍”了。这些应用程序中可满载着这些日子我们开发工作中所留下的回忆啊!

既然如此,我们就下定决心,一定要在一个都不能少的前提下,解决这个问题。唔,到底怎么办才好呢……有了!我们将invader.hrb修改一下,如果不用sprintf函数的话,程序就可以变小了!

■■■■■

invader.c中只有一处调用了sprintf,因此修改起来也很简单,首先替换下面一句:

  1. sprintf(s, "%08d", score); setdec8(s, score);

然后再添加下面这个函数:

本次的invader.c节选

  1. void setdec8(char *s, int i)
  2. /*将i用十进制表示并存入s*/
  3. {
  4. int j;
  5. for (j = 7; j >= 0; j--) {
  6. s[j] = '0' + i % 10;
  7. i /= 10;
  8. }
  9. s[8] = 0;
  10. return;
  11. }

这样就完工了,仅通过这一点修改,invader.hrb就从2335字节变成了1509字节,大幅度瘦身了,大获全胜!反过来说,sprintf这个函数是如此之大,真是减肥瘦身的大敌呢(笑)。

当然,修改了之后的invader.hrb运行起来不会有任何变化,不信的话可以“make run”试试看哦。

■■■■■

修改完成之后,我们再重新“make”一下磁盘映像,并用二进制编辑器打开看看,数据占用的部分到028498。0x28499 / 18432 = 8个柱面,0x28499 % 18432 = 17561字节,正好缩减到9个柱面的范围内,撒花!

到此为止,我们的应用程序编写活动也该告一段落了,我们来总结一下本章的成果吧。

invader.hrb……1509字节:外星人游戏

calc.hrb……1668字节:命令行计算器

tview.hrb……1753字节:文本阅览器

mmlplay.hrb……1975字节:MML播放器

gview.hrb……3865字节:图片阅览器

看起来从上到下文件的大小是在不断递增的呢。虽然很有趣,不过这只是个巧合啦。

最后我们来一张纪念截图吧。

5 IPL的改良(harib27e) - 图1

大家来拍个全家福