灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:4111回复:0

[系统教程]汇编语言---套装软件制作(1)

楼主#
更多 发布于:2011-10-11 19:56
程式写完后,还要加工成为可执行的套装软件(Package),一般说来,即使是可以执行的程式,一点错误都没有,离套装软件的程度,却还有一段距离。
    当然,程式侦错也是必经过程之一,有时侦错与程式写作可以同时进行。但有经验的程式师,对全面有了充份的认识,往往会等到程式联接后再行侦错。
    程式完成后的全面侦错,最好不要依靠写程式的人。因为程式师经常不是使用者,他们仅在自己设计的条件下,依其理念进行侦错。当然这种错误必须更正,但最容易发生的错误,却是使用者不小心在输入时,或运用指令时,违背了程式师的理念。这种错误的发生,是不能原谅的,程式本来就是为使用者设计的,如果令使用者不便,程式就失去了应有的价值。
    程式的品管,就在于检测程式是否符合使用者的需求。一般说来,应有专人负责,也有让写作手册的人,兼做品管的工作。这样可以同时对照手册所描述的功能及操作方法,检查两者之间是否一致。
    还有一种常见的品管方式,是在产品完成时,交给完全没有参与程式设计的第三者,在客观的立场,作全面的试用,并提出测试报告或建议书。
    品管合格了,才是包装、手册等最后的工作。这并不是说包装和手册要最后才做,相反的,尤其是使用手册,经常要在程式设计的同时,准备妥当,如此程式师才不会任意所之,脱离主题。
    第一节  测试侦错

    不论使用什么工具,侦错时一定要有清晰的头脑,根据所设定的方式,一步一步地查看流程及指令。
    每个人都会有独特的习惯性错误,最好每次将自己发生的错误记下来,不仅错误会渐渐减少,且在错误发生时,很容易就能找到。
    测试侦错是非常重要的手段,有人认为第一流的程式师不应该犯错,即使犯错,也错得很少。我的看法不一样,并非因为我经常犯错,而且错得离谱。真正的理由是为了适时掌握正确的思考方向,在编程时,有意无意地忽略一些细节,这样反而能一气呵成,不致于再而竭三而衰。
    这就像画图一样,有人喜欢先作草图,有人则习惯由细部画起。不论个人的风格如何,重要的是最后的成果。
    我在写程式之前,首先考虑全体的结构,再把各处的支架备妥,然后考虑有共同特性之处,便一口气写完。而且写时力求快速,以免失去当时的感觉。等到结构大体完成了,最后才去填补一些不太重要的细节。至于正确与否,则全靠测试侦错来修正、弥补。
    这种写法要有很强的整体观念,且对每段程式的性质及功能皆瞭解从何下手。
    我由系统程式到应用工具,大大小小的程式写了不少,对这种写作方法有很深的体会。唯一的缺点是,程式如果太大,超过20KB,我的记忆力就难以负担。(年轻人或许不致如此)但其优点则是结构精简,制作时间极短。以我们的中文系统程式而言,8KB 的程式,连侦错在内,只花了两个月的功夫。
    然而,在训练程式师的过程中,我发现到还是循序渐进较好。每次写完了一段程式,立刻侦错,此段程式正确了,再写下一段。除非有绝对的把握,自信没有问题,否则千万不要等全部程式都写完了,再来调试。到那时,如果发现问题,在各段错综复杂的程式中,要找到错误所在,那是大海捞针了。
    程式发生「当机」的情况,常常是 PUSH 及 POP不平衡所致,也有在做回路时,计数器为负值,以致锁在其中。如果程式分「段」太多,则要特别注意各「段」改变的情况。
    诸如这些细节,最好随时把编程时的假想值记录下来,不要太过相信自己的记忆力,时间一长,程式一大,就什么都忘记了。
    还有一点,在侦错时千万要养成习惯,记录追踪的过程。因为侦错是一种很琐碎的工作,很难一次就发现问题所在,第一次应该是第二次改进的经验。如果不详加记录,每次都要从头做起,将是一种极为痛苦的事。
    不妨把侦错视为猜谜,一种智力的挑战,在遵守一定的规则下,应该是一种有趣的享受。
    第二节  研究改进

    想要把程式写好,一定要不断地研究、改进,由错误中学习,由改进中得到经验,培养出敏锐的观察能力和良好的写作习惯。
    在开始时,这种过程需要付出不少时间,但对一位程式师来说,写程式是终身职业,能不精益求精吗?
    以下举两个实例,以说明如何研究改进已完成的程式。
  1,指令的运用:
    以下面这段通讯处理程式而论,不仅语法及指令完全正确,执行时也毫无错误,是不是还可以加以改进呢?
    1-1 按照前面规定,说明项中已用简化的字串:
    SND-传送  RCV-接收  LET-左  
        RGT-右    VER-直    HOR-横
    1-2 程式师代号为'C'。
    1-3 段名省略。

    1: CSND0:
    2:        MOV    DX,03FDH      ; 输出埠
    3:        MOV    AL,80H
    4:        OUT    DX,AL          ; 输出指令
    5:        MOV    DX,03F8H      ; LSB 速度控制
    6:        MOV    AL,06H          ; 速度=19200/秒
    7:        OUT    DX,AL
    8:        MOV    DX,03F9H      ; MSB 速度控制
    9:        MOV    AL,0          ; 速度=19200/秒
   10:        OUT    DX,AL
   11:        MOV    DX,03FBH      ; 行控制暂存器
   12:        MOV    AL,03H          ; NO PARITY,1
                                      ; STOP,8
   13:        OUT    DX,AL
   14:        MOV    DX,03FCH      ; 通讯控制
   15:        OUT    DX,AL
   16:        MOV    DX,03F9H      ; 中断有效
   17:        MOV    AL,0
   18:        OUT    DX,AL
   19: CSND1:
   20:        MOV    DX,03FDH      ; 状态暂存器
   21:        IN    AL,DX
   22:        TEST    AL,10H          ; 是否可接收?
   23:        JNZ    CRCV0          ; 可
   24:        TEST    AL,20H          ; 通道已清否?
   25:        JZ    CSND1          ; 8250未清
   26:        MOV    AH,1          ; 键盘有输入?
   27:        INT    16H
   28:        CMP    AL,07H          ;  ='CTRL+G'
   29:        JE    CEND          ; 是,完毕
   30:        MOV    DX,03F8H
   31:        OUT    DX,AL          ; 送输入字符
   32:        JMP    CSND1
   33: CRCV0:                  ; 接收
   34:        MOV    DX,03FCH      ; 通讯控制
   35:        MOV    AL,08H          ; 暂停中断
   36:        OUT    DX,AL
   37:        MOV    DX,3F8H
   38:        IN    AL,DX          ; 收字符
   39:        MOV    AH,0EH
   40:        INT    10H          ; 萤屏显示
   41:        MOV    DX,03FCH
   42:        MOV    AL,0BH
   43:        OUT    DX,AL          ; 继续接受
   44:        JMP    CSND1          ; 循环工作
   45: CEND:
   46:        RET              ; 完成

    本段程式共 84 个字元,非常精简,但仍然有节省的余地,要点在DX的数值上。
    DX值由 03F8H到 03FDH,可知 DH 之值不变,只需改变 DL 即可。每改变DX一次,需要三个字元,如仅变DL,只需两个字元。这一指令共用了十一次,除第一次有必要外,其他十次就可以省下10个字元。
    再要斤斤计较,还可以榨出二个字元来,在5至8条中,若用INC  DX 只需要一个字元。
此外,31,32及43 ,44是浪费的作法,只要在第18条加一标号,就可以省却两个字元输出的指令。另外,还有35及39两条指令,应该合并,一次即将AX设妥,于是,又省下了一个字元。
        先令 DH=3
    1: CSEND0:
    2:        MOV    DL,0FDH       ; 输出埠
    3:        MOV    AL,80H
    4:        OUT    DX,AL          ; 输出指令
    5:        MOV    DL,0F8H       ; LSB 速度控制
    6:        MOV    AL,06H          ; 速度=19200/秒
    7:        OUT    DX,AL
    8:        INC    DX          ; MSB 速度控制
    9:        SUB    AL,AL          ; 速度=19200/秒
   10:        OUT    DX,AL
   11:        MOV    DL,0FBH       ; 行控制暂存器
   12:        MOV    AL,DH          ;NO PARITY,1
                      ; STOP,8
   13:        OUT    DX,AL
   14:        INC    DX          ; 通讯控制
   15:        OUT    DX,AL
   16:        MOV    DL,0F9H       ; 中断有效
   17:        SUB    AL,AL
   18: CSNDA:
   19:        OUT    DX,AL
   20: CSND1:
   21:        MOV    DL,0FDH       ; 状态暂存器
   22:        IN    AL,DX
   23:        TEST    AL,10H          ; 是否可接收?
   24:        JNZ    CRCV0          ; 可
   25:        TEST    AL,20H          ; 通道已清否?
   26:        JZ    CSND1          ; 8250未清
   27:        MOV    AH,1          ; 键盘有输入?
   28:        INT    16H
   29:        CMP    AL,07H          ;  ='CTRL+G'
   30:        JE    CEND          ; 是,完毕
   31:        MOV    DL,0F8H
   32:        JMP    CSNDA          ; 送输入字符
   33: CRCV0:                  ; 接收
   34:        MOV    DL,0FCH       ; 通讯控制
   35:        MOV    AX,0E08H      ; 暂停中断
   36:        OUT    DX,AL          ; 及显示
   37:        MOV    DL,0F8H
   38:        IN    AL,DX          ; 收字符
   39:        INT    10H          ; 萤屏显示
   40:        MOV    DL,0FCH
   41:        MOV    AL,0BH
   42:        JMP    CSNDA          ; 循环工作
   43: CEND:
   44:        RET              ; 完成
    看来似乎这样太小气,可是所谓艺术,就要具备丝毫不苟且的态度,再说由84个字元变成66个字元,省了近百分之廿,而且,速度也快了。这种程式原本就很精简,只有训练有素,追求完美的程式师,才做得到。
    另一种做法,便是将重复的过程写成回路,约可节省廿几个字元。但是,由于时间定律限制,通讯程式颇重时效,回路是否值得,尚要多方面分析,不可轻率决定。

  2,回路的实例:
    前面曾经讨论过,程式的效率,经常决定于回路的处理方式及其技巧。其对空间上影响比较小,但是良好的设计理念,常使速度上有高达十倍,甚至百倍的差异,读者想必已经知道,但是如何能应用已知的技巧,来改进设计的程式呢?
    回路是利用计数器,反复进行相同的程序作业,这种程式,目的就是为了节省空间,相对地,时间上难免有所损失。
        因此,在设计回路时,必须先行考虑清楚: 空间的节省与时间的交换是否值得? 其次,则要充份掌握回路的特色,要用得恰到好处,不可掉以轻心。
        原则上,在回路中,指令要用得精简,流程要非常明确,尤其重要的是,应力求避免在回路中使用缓冲器,最好充份利用暂存器。如果时间效率极为重要,则不妨放弃回路方式。
        有一个显示程式,目的是要将 16*16点阵字形送到萤幕上。对象是Hercules 640*400的图形卡,计分四区交互传送,这是另一个「高科技」界的新鲜奇事,在IBM PC推出时,最高密度的图形态,只有 640X200点阵,那是迁就电视萤幕的扫描方式,先送单线的水平讯号,再送双线,故分两区。Hercules卡为了加高密度,应用interlace 技术,又在单双水平扫描线中各加了一行,遂成了四区。
        Hercules很适宜中文的显示,如用 16X16字形,正好显示25行,每行40字,与英文完全兼容。若希望有一状态显示栏,则可用 15X15字形,留出24条线供做状态栏。
        遗憾的是在最需要中文的国内,却偏爱CGA,EGA 等密度不足的显示设备。不但售价偏高,功能也不足,弄得不伦不类。
        最理想的还是VGA 显示,计有 640X480之萤幕点阵,不仅空间大,在记忆体中,只有一区,应用非常灵活。
        下面,我们先介绍 Hercules 的显示方法,同时探讨回路的处理方式。
    1: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    2: ;HERCULES 中文显示处理程式。            ;
    3: ;输入参数:SI=点阵字形,DI=萤幕位置。        ;
    4: ;          DS =CG,ES= 0B800H(萤幕段)。      ;
    5: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    6: CDSP0:
    7:         MOV     CX,16       ;高16点
    8: CDSP1:
    9:         MOVSW           ;移至萤幕上
   10:         ADD     DI,1FFEH       ;加一区,每区=2000H
   11:         JNS     CDSP2       ;未超越区限,继续
   12:         ADD     DI,8050H       ;超越区限,换区加行
   13: CDSP2:
   14:         LOOP    CDSP1
   15:         RET
    程式到此结束,相当精简,技巧在第10至12条区限的检测方式。一般做法是在检查区限时,用:
         ADD     DI,1FFEH       ; 加区值
         CMP     DI,8000H       ; 最大区限值
         JB      CDSP2       ; 未超过
         SUB     DI,8000H       ; 减去区限
    如此则多了一条4字元的指令,加上4个时钟脉冲,做16次回路就损失64个时钟脉冲值。在全萤幕显示时,以1,000 个字来算,为数就不少了。
    当然,取消了回路速度还可以加快,其结果,则要增加130 个字元,时间则快了 272个时钟脉冲,是否值得,就要看实际需要而定了。
    另一个方法,要增加2个字元,但可快上36个时钟脉冲,其法在第11条上:
   11:         JS      CDSP3
   12:CDSP2:
   13:         LOOP    CDSP1
   14:         RET
   15:CDSP3:
   16:         ADD     DI,8050H
   17:         JMP     CDSP2
    再换一个方法,如果先使 BX 为1FFEH,DX为8050H,则在原程式中,将第10条及12条分别改为:
   10:         ADD     DI,BX
   12:         ADD     DI,DX
    这一来,时钟脉冲快了2个,16次则快更多,如果再加上取消回路,其意义更大。空间原增加 130字元,现仅94字元,时间则省下 304个时钟脉冲。如果全萤幕显示了1,000 个字,在8MHZ频率下,将会加快 1/25 秒的速度。
    在回路中,如果讲求时间效益,应极力避免使用PUSH及POP ,因PUSH需15个时钟脉冲,而POP 则要12个,两者相加是27个时钟脉冲,非常不值得。
    解决方法之一是:设法将欲保留之值贮存在没有用到的暂存器中;再若是固定的常数,也不妨在每次要用时重新置入,祇不过是4个时钟而已。最麻烦是变数值,除了在设计模组之前,妥当地安排外,别无良策。


 





喜欢0 评分0
游客

返回顶部