David.Ko

          Follow my heart!
          posts - 100, comments - 11, trackbacks - 0, articles - 0
             :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理
          四 基本的駐留程序
          4.1 一個(gè)基本的COM程序
           DOS之下有兩種形式的可執(zhí)行文件,這兩種文件分別是COM文件和EXE文件.其中,COM文件可以迅速地加載和執(zhí)行,但是其大小不能超過(guò)64K字節(jié),只能有一個(gè)段,代碼段.而且起始地址為100H指令必須為程序的啟動(dòng)指令.EXE文件可以加載到許多個(gè)段中,因此程序的大小沒(méi)有限制,但是程序加載的過(guò)程就比較慢,而且對(duì)于內(nèi)存駐留程序來(lái)說(shuō)還會(huì)造成更大的麻煩.
           以下是一個(gè)可以正確執(zhí)行的COM文件,但其內(nèi)容是空的;只是一個(gè)COM文件的框架,可以把你寫(xiě)的任何應(yīng)用部分加在這個(gè)文件中,形成一個(gè)COM格式的內(nèi)存駐留程序:
            ;Section 1
            cseg segment
             assume cs:cseg,ds:cseg
              org 100h
            ;Section 2
            start: 
             ret
            ;Section  3
            cseg ends
             end start
           上面的程序可以分成三部分,第一部分定義了代碼段和數(shù)據(jù)段分別放在程序中的位置,以及執(zhí)行代碼的起始地址.第二部分是可執(zhí)行的程序,在這個(gè)例子只一個(gè)RET指令而已.第三部分是程序包段的終結(jié),其中END敘述包含了程序開(kāi)始執(zhí)行地址.
           若是把上面的程序經(jīng)過(guò)匯編連接,你會(huì)發(fā)現(xiàn)所產(chǎn)生的COM文件只有一個(gè)字節(jié)長(zhǎng).這是因?yàn)樗a(chǎn)生的COM文件沒(méi)有程序段前綴(Programsegmetn  profix),因?yàn)樵贒OS下所有和COM文件都有相同的程序段前綴.當(dāng)DOS加載一個(gè)COM文件到內(nèi)存中時(shí),就會(huì)自動(dòng)地產(chǎn)生一份正確的程序段前綴.一個(gè)程序在執(zhí)行的過(guò)程中,可以根據(jù)需要修改其程序段前綴,但是在一開(kāi)始,所有COM文件的程序前綴都是相同的.下面是程序前綴的格式.
              偏移位置    含義
              0000H     程序終止處理子程序地址(INT 20H)
              0002H     分配段的結(jié)束地址,段值
              0004H     保留
              0005H     調(diào)用DOS的服務(wù)
              000AH     前一個(gè)父程序的IP和CS
              000EH     前一個(gè)父程序的CONTROL_C處理子程序地址
              0012H     前一個(gè)父程序包的硬件錯(cuò)誤處理子程序地址
              0016H     保留
              002CH     環(huán)境段的地址值
              005EH     保留
              005CH     FCB1
              006CH `    FCB2
              0080H     命令行的參數(shù)和磁盤轉(zhuǎn)移區(qū)域
          4.2 一個(gè)最小的內(nèi)存駐留程序
           上面的程序只是一個(gè)一般的DOS程序而已.并不是內(nèi)存駐留的.以下是一個(gè)基本的內(nèi)存駐留程序結(jié)構(gòu):
                   ;Section 1
               cseg segment
                assume cs:cseg;ds:cseg
                org 100h
               start:    ;Section 2
                nop   
               done:    ;Section 3
                mov dx,offset done
                int 27h
                   ;Section 4
               cseg ends
                end start
           和前一個(gè)程序相比,這個(gè)程序只是增加了一個(gè)DONE部分.這個(gè)部分使用了INT 27H這個(gè)中斷調(diào)用,來(lái)終止并駐留在內(nèi)存(Terminate and Stay Resident)中.使用INT 27H這個(gè)中斷調(diào)用時(shí),必須設(shè)定好一個(gè)指針,讓這個(gè)指針指向內(nèi)存中可以使用的部分,事實(shí)上,這就相當(dāng)于設(shè)置一個(gè)COM文件可加載的位置.另外DOS還提供了INT 21H,AH=31H(駐留程序,Keep process),但是使用這個(gè)中斷調(diào)用時(shí),我們必須設(shè)定所保留的內(nèi)存大小,而不是設(shè)定一個(gè)指針;另外這個(gè)中斷調(diào)用會(huì)送出退出碼.
           使用INT 27H時(shí),必須設(shè)定一個(gè)指針指向可用存儲(chǔ)位置的開(kāi)頭,以便讓DOS用來(lái)加載稍后執(zhí)行的程序.DOS本身有一個(gè)指針,這個(gè)指針是加載COM文件或EXE文件時(shí)的基準(zhǔn)地址值.INT尿27H 會(huì)改變這個(gè)指針或?yàn)樾碌臄?shù)值.同時(shí)造成新指針和舊指針之間的存儲(chǔ)空間無(wú)法讓DOS使用因此這樣做會(huì)造成可用存儲(chǔ)位置愈來(lái)愈少.
           調(diào)用INT 27H時(shí)所使用的指針是個(gè)FAR指針,其中DX存放的是位移指針(Offset pointer),它可以指到64K字節(jié)之內(nèi)的范圍.而DOS是段指針(Segment pointer),它可以指到IBM PC中640K字節(jié)的任何一個(gè)段.在上面的例子中,DS的內(nèi)容不必另外設(shè)定,因?yàn)楫?dāng)COM文件加載時(shí),DS的內(nèi)容就CS的內(nèi)容相同了.
           經(jīng)常在編寫(xiě)匯編程序時(shí),常犯的一個(gè)錯(cuò)誤就是:把a(bǔ)ssume ds:cseg這個(gè)敘述誤認(rèn)為是,存放某一預(yù)設(shè)值到DS中,事實(shí)上,匯編語(yǔ)言程序中的Assume敘述不會(huì)產(chǎn)生任何的程序代碼,這個(gè)功能是告訴匯編器做某些必要的假設(shè),以便正確地匯編程序.譬如以下的程序:
               cseg segment
                .............
                assume ds:cseg
                mov ah,radix
                .............
               radix db 16
                .............
               cseg ends
           上面的程序匯編時(shí),當(dāng)匯編器看到mov ah,radix這個(gè)指令時(shí),它就根據(jù)assume ds:cseg來(lái)產(chǎn)生一定形式的賦值指令.在面的Assume ds:cseg敘述是告訴匯編器,數(shù)據(jù)段就位于目前的代碼段中.這是內(nèi)存駐留程序的一項(xiàng)重要關(guān)鍵.如果DS的內(nèi)容和CS不相同時(shí),無(wú)論是否有assume 敘述,程序執(zhí)行時(shí)都會(huì)失敗.
          4.3 改良的內(nèi)存駐留程序
           上面所介紹的內(nèi)存駐留程序?qū)嶋H上沒(méi)有做任何事,只是駐留在內(nèi)存中而已.事實(shí)上,在START和END之間放入任何程序代碼,都只會(huì)執(zhí)行一次而已然后就永遠(yuǎn)駐留在內(nèi)存中,除非是使用轉(zhuǎn)移指令轉(zhuǎn)到START的地址去,否則將永遠(yuǎn)無(wú)法被使用.還要注意一點(diǎn),START的地址值并非固定不變,它會(huì)根據(jù)程序執(zhí)行時(shí)計(jì)算機(jī)的狀態(tài)而改變.
           下面的這個(gè)程序只是把需要駐留的程序代碼裝載好,但是并不會(huì)執(zhí)行.
                   ;Section 1
               cseg segment
                assume cs:cseg,ds:cseg
                org  100h
                   ;Section 2
               start: 
                jmp initialize
                   ;Section 3
               app_start:
                nop
               initialize:
                   ;Section 4
                mov dx,offset initialize
                int  27h
                   ;Section 5
               cseg ends
                end start
           上面的程序一開(kāi)始執(zhí)行時(shí)就傳到initialize標(biāo)志的地方,裝置好駐留在內(nèi)存的應(yīng)用部分.原先的DONE已經(jīng)改成initialize,而駐留在內(nèi)存的程序代碼則放在App_Start 和Initialize之間.
           另外,你也許注意到了,程序的起始地址并不是Initialize而是Start.這是因?yàn)樗蠧OM程序的起始地址都是100H;而上面的程序中Start是放在100H的地方.如果把Initialize放在End之后,Initialize就變成起始地址,但是這樣的程序無(wú)法透過(guò)EXE2BIN轉(zhuǎn)換成COM文件了.如果無(wú)法產(chǎn)生COM文件時(shí),那么就必須直接處理段的內(nèi)容.
          4.4 減少內(nèi)存的額外負(fù)擔(dān)
           到目前為止,都沒(méi)有接觸到程序前綴,當(dāng)使用INT 27H時(shí),事實(shí)上是把指針以前的東西都保留在內(nèi)存中,這也包括了COM的程序段前綴.因?yàn)镃OM文件執(zhí)行完畢后,才可以把程序段前綴移掉.
           從上面的事實(shí)可以看出:如果程序段前綴只能在COM裝置程序結(jié)束后才可以移去,那么就可以由駐留在內(nèi)存中的程序代碼完成.要做到這一點(diǎn),可以把整個(gè)程序往下移動(dòng)256個(gè)字節(jié).但又如何做到這一點(diǎn)呢?我們可以設(shè)定一個(gè)標(biāo)志(Flag),用來(lái)指示這個(gè)程序是否執(zhí)行過(guò).如果這個(gè)駐留程序或是第一次執(zhí)行時(shí),就把整個(gè)程序往下移動(dòng)256個(gè)字節(jié),以便把程序段前綴移去.但是如果駐留程序在裝置好之后,經(jīng)過(guò)一段長(zhǎng)時(shí)間仍然沒(méi)有被執(zhí)行時(shí),怎么辦呢?如果同時(shí)載入了好幾個(gè)駐留程序時(shí),雙該如何呢?這些重要的事情都需要使用不同的程序代碼來(lái)解決.如果說(shuō)這些程序代碼超出了256字節(jié)時(shí),那么所占用的存儲(chǔ)位置就超出程序段前綴所浪費(fèi)的空間.有些人用一些比較簡(jiǎn)短的代碼來(lái)解決這個(gè)問(wèn)題,但是還是比較麻煩.因此對(duì)于大部分的內(nèi)存駐留程序而言,除非存儲(chǔ)空間太少,以至于256字節(jié)變得很重要,否則最好不要去處理程序段前綴,這樣子會(huì)讓你的程序簡(jiǎn)潔而且容易閱讀.
          4.5 使用駐留程序
           上面介紹了如何把程序加載到內(nèi)存,并且讓它永遠(yuǎn)留在內(nèi)存中,接下來(lái),介紹如何來(lái)使用駐留在內(nèi)存中的程序.
           內(nèi)存駐留程序的使用方法和它原先的設(shè)計(jì)有密切的關(guān)系.譬如,截獲鍵盤輸入的程序就必須通過(guò)鍵盤輸入的軟件中斷,或是敲鍵盤所產(chǎn)生的硬件中斷來(lái)使用.其它的駐留程序可能就必須靠:系統(tǒng)時(shí)鐘,系統(tǒng)調(diào)用,或是其它的中斷才有辦法使用.這些駐留程序必須要和以上的使用方法連結(jié);而且在駐留程序安裝好之后,至少必須建立一種使用的管道,否則駐留程序?qū)o(wú)法使用.
           IBM PC必須經(jīng)由事件來(lái)驅(qū)動(dòng),譬如:鍵盤,系統(tǒng)時(shí)鐘,或是軟件中斷.這些事件可以被截獲,然后根據(jù)所發(fā)生的事件來(lái)執(zhí)行一定的動(dòng)作.因此必須讓中斷事件發(fā)生時(shí),先執(zhí)行我們的程序,而非系統(tǒng)的程序.
           譬如,當(dāng)我們?cè)O(shè)計(jì)一個(gè)截獲鍵盤輸入的駐留程序時(shí),就必須把駐留程序和執(zhí)行鍵盤輸入的系統(tǒng)調(diào)用連結(jié)起來(lái).當(dāng)DOS或是應(yīng)用程序希望從鍵盤讀取一個(gè)字符時(shí),它就必須執(zhí)行INT 16H調(diào)用.因此如果我們能夠在調(diào)用INT 16H時(shí),先執(zhí)行我們的駐留程序,那么駐留程序就可能變成應(yīng)用程序和操作系統(tǒng)間的橋梁.
           可以使用INT 21H中斷調(diào)用中AH=25H來(lái)完成以上的要求.設(shè)置中斷矢量可以更改INT 16H原先的中斷矢量?jī)?nèi)容,讓它改為指向我們的程序.譬如以下的例子所示:
               cseg segment
                assume cs:cseg,ds:cseg
                org  100h
               start: 
                jmp Initialize 
                     ;Section 1
               new_keyboard_io proc far
                sti
                nop 
                iret
               new_keyboard_io endp 
                     ;Section 2
               Initialize:
                mov dx,offset new_keyboard_io
                mov al,16h
                mov ah,25h
                int 21h
                     ;Section 3
                mov dx,offset Initialize
                int 27h
               cseg ends
                end start
           上面的程序和4.3的程序結(jié)構(gòu)是一樣的,但是仍然有一些重要的改變.在Section 1和Section 2.在Section 1把駐留部分修改成子程序形式(Procedure),這樣做是為了增加程序的可讀性.另外,駐留部分多加了兩個(gè)指令,STI和IRET.其中STI是設(shè)置中斷標(biāo)志(Set Interrupt Flag)和起始中斷(Enable interrupts).
           當(dāng)CPU發(fā)生中斷時(shí),它就關(guān)閉中斷標(biāo)志,因此CPU就不再接受中斷.事實(shí)上,CPU會(huì)專心地為目前發(fā)生的中斷服務(wù).當(dāng)CPU停止接受中斷時(shí),任何硬件中斷的信號(hào)都會(huì)被忽略,譬如:鍵盤,時(shí)鐘脈沖,磁盤機(jī)信號(hào),調(diào)制解調(diào)器的中斷.如果CPU一直不接受中斷,那么就會(huì)漏掉一些重要的信息,計(jì)算機(jī)系統(tǒng)也可能因此而死機(jī).因此雖然CPU可以停止接受中斷一段時(shí)間,但是卻不能夠久.
           第二個(gè)重要的指令是IRET,從中斷返回(Return from interrupt).IRET的功能和RET極相似,RET是用來(lái)從被調(diào)用 的子程序中返回,而IRET則是用來(lái)從中斷程序返回.但是使用IRET返回時(shí),它會(huì)從堆棧中先取出返回的地址值,然后再取出CPU的狀態(tài)標(biāo)志(State Flag).CPU的狀態(tài)標(biāo)志在CPU接受中斷時(shí),會(huì)自動(dòng)地推入堆棧中.因此執(zhí)行IRET指令后,CPU的狀態(tài)就恢復(fù)成未中斷前的狀態(tài);也就是說(shuō)CPU就可以繼續(xù)接受外界的中斷(CPU狀態(tài)標(biāo)志中斷包括了中斷標(biāo)志).嚴(yán)格地說(shuō),STI和IRET在這個(gè)例子中都是多余的,但是對(duì)于實(shí)際的中斷處理程序而言,這兩個(gè)指令都很重要.
           另外,使用設(shè)置中斷矢量的中斷調(diào)用時(shí),暫存器AL必須存入所要設(shè)置的中斷矢量,而中斷矢量指針則必須放到暫存器DS:DX中.
          4.6 連接中斷處理程序
           若是把前一節(jié)的程序拿來(lái)執(zhí)行時(shí),鍵盤是無(wú)法輸入的,事實(shí)上,處理鍵盤的硬件中斷處理程序會(huì)繼續(xù)地讀取敲入的字符,并且放到等待隊(duì)列中,直到隊(duì)列填滿為止;但是由于讀取等待隊(duì)列的軟件中斷INT 16H已經(jīng)被改變了,因此隊(duì)列的內(nèi)容就永遠(yuǎn)取不出來(lái).
           現(xiàn)在寫(xiě)一個(gè)中斷處理程序,這個(gè)中斷處理程序只是調(diào)用原先的鍵盤中斷處理程序,一旦做到這一點(diǎn)之后,接下來(lái)就可以根據(jù)鍵盤的輸入做修改.以下就是調(diào)用原先鍵盤處理程序的駐留程序: 
               cseg segment
                assume cs:cseg,ds:cseg
                org  100h
               start: 
                jmp Initialize
               Old_Keyboard_IO dd ? 
          ;          ;Section 1
               new_keyboard_io proc far
                sti     
                     ;Section 2
                pushf
                assume ds:nothing
                call  Old_Keyboard_IO
                nop 
                iret
               new_keyboard_io endp 
                     ;Section 3
               Initialize:
                assume cs:cseg,ds:cseg
                mov bx,cs
                mov ds,bx
                mov al,16h
                mov ah,35h
                int 21h
                mov word ptr Old_Keyboard_IO,bx
                mov word ptr Old_Keyboard_IO[2],es
                     ;End Section 3
                mov dx,offset new_keyboard_io
                mov al,16h
                mov ah,25h
                int 21h
                mov dx,offset Initialize
                int 27h
               cseg ends
                end start
           上面的程序中,第一部分是兩個(gè)字(Double word),這是用來(lái)存放舊的鍵盤中斷矢量.因?yàn)镃OM的程序都只限制在一個(gè)段中,因此數(shù)據(jù)段和代碼段都在同一段中.而原先的中斷處理程序和我們所編寫(xiě)的中斷處理程序未必會(huì)在同一段中,所以必須使用雙字來(lái)儲(chǔ)存地址值.
           雙字Old_Keyboard_IO可以放在駐留程序中的任何地方;但是一般來(lái)說(shuō),放在Jmp Initialize 之后會(huì)比較方便;因?yàn)槿绻仨毷褂肈EBUG來(lái)檢查程序的話,可以比較容易調(diào)試.
           上面程序中的第二部分是駐留程序的主體,其中包括了一個(gè)調(diào)用原先鍵盤中斷處理程序的模擬中斷.因?yàn)樵鹊逆I盤中斷處理程序必須使用INT的方式調(diào)用,而不是使用CALL的指令調(diào)用;因此必須先使用PUSHF把CPU狀態(tài)標(biāo)志壓入堆棧中,然后配合上CALL來(lái)模擬INT的動(dòng)作.
           注意一點(diǎn),assume ds:nothing這一行是匯編指示,而不是程序代碼.它是用來(lái)告訴匯編器在產(chǎn)生下一行機(jī)器碼時(shí),不要更會(huì)目前DS的內(nèi)容;這樣做才可以讓匯編器為下一個(gè)指令產(chǎn)生雙字的地址值.
           當(dāng)Call Old_Keyboard_IO指令執(zhí)行時(shí),控制權(quán)就轉(zhuǎn)移到舊的鍵盤中斷處理程序.而當(dāng)這個(gè)中斷調(diào)用執(zhí)行完時(shí),它就執(zhí)行IRET指令,于是控制權(quán)又交還到目前的駐留程序.這樣做,不但可以讓原先的鍵盤中斷程序包為我們工作,同時(shí)也可以掌握控制權(quán).如果只使用IMP指令,跳到舊的鍵盤中斷處理程序包去,而不把CPU狀態(tài)標(biāo)志推入堆棧中,那么一旦執(zhí)行到IRET時(shí),就真正返回到中斷的狀態(tài).
           上面程序中的第三部分是啟動(dòng)代碼部分,在這一部分中,設(shè)定好新的中斷矢量,同時(shí)把舊的中斷矢量存放在駐留程序代碼中,以便讓駐留程序使用.
          4.7 檢查駐留程序
           到目前為止,已經(jīng)成功地把駐留程序加在應(yīng)用程序和DOS的鍵盤輸入之間;接下來(lái)可以修改輸入的字符.在這一節(jié)中,我們準(zhǔn)備截獲鍵盤的輸入,并且把"Y"改成"y","y"改成"Y".
           以下是程序代碼:
               cseg segment
                assume cs:cseg,ds:cseg
                org  100h
               start: 
                jmp Initialize
               Old_Keyboard_IO dd ?           
               new_keyboard_io proc far
                assume cs:cseg,ds:cseg
                sti     
                     ;Section 1
                cmp ah,0
                je ki0
                assume ds:nothing
                jmp  Old_Keyboard_IO
                     ;Section 2
               ki0:
                pushf
                assume  ds:nothing
                call  Old_Keyboard_IO
                cmp  al,'y'
                jne  ki1
                mov  al,'y'
                jmp  kidone
               ki1: 
                cmp  al,'Y'
                jne  kidone
                mov  al,'y'
               kidone:      
                iret
               new_keyboard_io endp 
                     ;Section 3
               Initialize:
                assume cs:cseg,ds:cseg
                mov bx,cs
                mov ds,bx
                mov al,16h
                mov ah,35h
                int 21h
                mov word ptr Old_Keyboard_IO,bx
                mov word ptr Old_Keyboard_IO[2],es
                     ;End Section 3
                mov dx,offset new_keyboard_io
                mov al,16h
                mov ah,25h
                int 21h
                mov dx,offset Initialize
                int 27h
               cseg ends
                end start
           在面的程序第一部分主要是檢查AH是否等于0(讀取字符).如果AH不等于0,就用舊的中斷處理程序來(lái)處理其它的功能:1H(讀取鍵盤狀態(tài)),2H(讀取鍵盤標(biāo)志).在這里,使用JMP指令,而非使用CALL來(lái)模擬軟件中斷;因此原先的中斷處理程序結(jié)束后,就直接返回到中斷前的狀態(tài).
           程序的第二部分是處理AH=0H時(shí)的情形.首先程序中斷模擬一個(gè)軟件中斷來(lái)調(diào)用舊的鍵盤處理程序,是為了在讀完字符之后,控制權(quán)能交還到我們的駐留程序,接下來(lái)的幾行程序是檢查讀到的字符是不是"Y"和"y",如果是的話就修改它.
           可以借執(zhí)行這個(gè)程序,來(lái)驗(yàn)證其是否正確.除此之外,也可以證明,在操作系統(tǒng)和應(yīng)用程序之間可以加入一層控制碼.這一層控制碼可以先選擇性地加強(qiáng)或取代某些DOS的功能,修改結(jié)果以滿足我們的要求.
          主站蜘蛛池模板: 获嘉县| 炉霍县| 富阳市| 河津市| 昆山市| 淅川县| 江门市| 瑞丽市| 拉萨市| 松原市| 厦门市| 绥滨县| 呼玛县| 甘德县| 井研县| 十堰市| 商都县| 康马县| 汝州市| 嫩江县| 民勤县| 清新县| 涿州市| 兴文县| 肃宁县| 佛山市| 青铜峡市| 大埔县| 舟曲县| 巩义市| 曲水县| 屏东县| 仪征市| 泰安市| 阳山县| 大冶市| 仪陇县| 襄樊市| 永胜县| 水城县| 连平县|