(1.西安精密機械研究所,西安 710075; 2.陜西海泰電子有限責(zé)任公司,西安 710075)
隨著嵌入式技術(shù)快速發(fā)展,功能更加齊全,性能更加強大的臺式儀器已經(jīng)面世,例如LXI儀器,其數(shù)據(jù)傳輸吞吐率,基于時間觸發(fā),組建遠程分布式測控系統(tǒng)都具有明顯優(yōu)勢,但GPIB經(jīng)歷了幾十年的發(fā)展,總線協(xié)議已經(jīng)相當(dāng)成熟,仍然受到傳統(tǒng)用戶的青睞,因此眾多國內(nèi)外測試測量研發(fā)商開發(fā)高端的臺式儀器時,都會保留傳統(tǒng)的GPIB接口。由于LINUX操作系統(tǒng)內(nèi)核是開源的,使用者可以根據(jù)自身的需求,對其進行裁減配置,形成高效的嵌入式操作系統(tǒng),使得LINUX成為當(dāng)前最流行的嵌入式操作系統(tǒng)之一,但不足之處是,它不像WINDOWS,有微軟專門的研發(fā)團隊對其進行不斷優(yōu)化和升級,LINUX并非以商業(yè)盈利為目的,沒有專門組織對其進行官方維護,從而LINUX內(nèi)核在集成各個芯片廠商的驅(qū)動時,不少驅(qū)動在一些應(yīng)用情況下,傳輸性能還不夠理想,尤其像GPIB這種只是測試測量領(lǐng)域里的專用總線,相比應(yīng)用廣泛的USB、LAN,更具典型性。
為了提升LINUX系統(tǒng)下GPIB傳輸性能,文章對其驅(qū)動進行了優(yōu)化。將GPIB中斷服務(wù)程序分割成頂半部和底半部,在底半部里開辟了配置成非原子操作的工作隊列,同時采用睡眠機制,高效的實現(xiàn)了驅(qū)動運行效率;在數(shù)據(jù)接收流程里,采用FIFO半滿位判斷標(biāo)準(zhǔn),使得從FIFO中收數(shù)據(jù)到內(nèi)核緩沖區(qū)和向FIFO里傳數(shù)據(jù)可以并發(fā)進行,從而提升了數(shù)據(jù)傳輸速率;設(shè)備文件操作中的讀寫函數(shù)可能會同時操作臨界資源,應(yīng)用信號量避免了競態(tài)的發(fā)生,增強了驅(qū)動可靠性。
LINUX字符設(shè)備驅(qū)動程序[5]一般包括3部分:初始化、中斷服務(wù)、設(shè)備文件操作。在驅(qū)動程序初始化時,要向系統(tǒng)注冊此驅(qū)動程序,系統(tǒng)后續(xù)才能調(diào)用驅(qū)動里各設(shè)備文件操作接口。在Linux系統(tǒng)里,是通過調(diào)用register_chrdev向系統(tǒng)注冊設(shè)備驅(qū)動程序,初始化部分除了注冊設(shè)備驅(qū)動程序,一般還需要給驅(qū)動程序申請系統(tǒng)資源,包括內(nèi)存、時鐘、I/O端口等,芯片的初始化也在這里進行,另外還要設(shè)置清除,它是讓在初始化里注冊的資源,全部從內(nèi)核里注銷掉。對于設(shè)備經(jīng)常會提出請求給CPU,來執(zhí)行設(shè)備需要完成的操作,這就需要有中斷服務(wù),驅(qū)動程序通過調(diào)用request_irq函數(shù)來申請中斷,其原型:int request_irq(unsigned int irq,void(*handler)(int irq,void dev_id,struct pt_regs*regs),unsigned long flags,const char*device,void*dev_id);參數(shù)irq表示所要申請的硬件中斷號,handler為向系統(tǒng)申請的中斷服務(wù)程序,中斷產(chǎn)生時由系統(tǒng)來調(diào)用,調(diào)用時所帶參數(shù)irq為中斷號,dev_id為申請時告訴系統(tǒng)的設(shè)備標(biāo)識,regs為中斷響應(yīng)時信息寄存器地址;flag是申請時的選項,它決定中斷處理程序的一些特性,其中最重要的是影響處理速率和資源開銷的中斷響應(yīng)方式;device為設(shè)備名,在/proc/interrupts文件里被記錄,中斷服務(wù)具體的內(nèi)容由設(shè)備需要完成的操作來決定。設(shè)備文件操作是指file_operations結(jié)構(gòu)中的成員,它們?nèi)渴呛瘮?shù)指針,每個函數(shù)都完成一種設(shè)備操作,如讀寫操作,選擇操作,控制操作等。
字符設(shè)備驅(qū)動程序的開發(fā)流程是:
1)查看原理圖及芯片資料理解設(shè)備的工作原理;
2)定義主設(shè)備號、注冊資源、注銷設(shè)備號及資源;
3)設(shè)計芯片初始化流程;
4)設(shè)計中斷服務(wù);
5)定義file_operations結(jié)構(gòu),設(shè)計所要實現(xiàn)的文件操作;
6)編寫用戶態(tài)測試程序,調(diào)試設(shè)備驅(qū)動。
調(diào)試時是通過insmod和rmmod命令實現(xiàn)驅(qū)動的加載和卸載,加載與卸載的原理如圖1所示。
圖1 加載與卸載的原理
TNT4882是美國NI公司的一款高速且聽講功能兼?zhèn)涞腉PIB(General purpose interface bus)接口專用芯片。它內(nèi)部集成了Turbo488(高速傳輸電路)及NAT4882(IEEE488.2兼容電路),能夠兼容ANSI IEEE Standard 488.1和ANSI IEEEStandard 488.2規(guī)范,其內(nèi)置還具有16個增強型IEEE488兼容收發(fā)器[6]。
TNT4882 內(nèi)部寄存器[7]共32個,每個寄存器的控制字都是8位,地址通常是TNT4882的基地址加上各個寄存器所對應(yīng)的偏移量而確定的。在GPIB驅(qū)動設(shè)計中,只有對寄存器進行正確設(shè)置,才能實現(xiàn)對GPIB的各種操作。工作模式可分為單芯片模式和Turbo + 9914模式,工作模式的選擇和轉(zhuǎn)換,由寄存器的設(shè)置來決定。Turbo+9914相當(dāng)于TMS9914A芯片的工作模式,目地是為了兼容此款芯片,但此時功能更加強大,單芯片工作模式是NI公司推薦的TNT4882工作模式,工作時的內(nèi)部結(jié)構(gòu)如圖2所示。由于單芯片模式采用的是最簡單又是最快速的結(jié)構(gòu),并且是推薦的TNT4882工作模式,因此,本驅(qū)動是在這種模式下進行設(shè)計。
圖2 單芯片工作模式
在初使化函數(shù)里除了注冊設(shè)備號以外,還要有以下方面要實現(xiàn):寄存器硬地址映射到內(nèi)存的虛擬地址;分別初始化讀寫互斥的信號量、中斷服務(wù)程序底半部的工作隊列、阻塞的等待隊列;初始化芯片。
由于內(nèi)核無法訪問TNT4882寄存器硬地址,所以要通過映射到內(nèi)存的虛擬地址,來訪問TNT4882寄存器,映射的實現(xiàn)是通過調(diào)用內(nèi)核函數(shù)ioremap()來實現(xiàn),此函數(shù)第一個參數(shù)設(shè)置為TNT4882芯片的首地址,TNT4882芯片的首地址是由硬件使用的片選信號線決定,函數(shù)的返回值便是首地址在內(nèi)存的映射地址,由于芯片手冊給出了每個寄存器的偏移量,這樣TNT4882的首地址在內(nèi)存的映射地址加上偏移量就是每個寄存器在內(nèi)存的地址。
信號量的本質(zhì)是一個整數(shù)值,它和一對函數(shù)聯(lián)合使用,這一對函數(shù)通常稱為P和V,P函數(shù)使信號量的值增加,V函數(shù)使信號量的值減少。如果信號量的值大于零,則進程可以操作臨界區(qū)的資源,反之,如果值小于零,則進程不能操作臨界區(qū)的資源[8]。由于讀寫函數(shù)可能會操作同一資源,這種情況會產(chǎn)生競態(tài),嚴(yán)重時會導(dǎo)致系統(tǒng)內(nèi)核的崩潰,所以需要通過信號量互斥,使用信號量之前要對其進行初使化,一般放在驅(qū)動初使化函數(shù)里,調(diào)用的內(nèi)核函數(shù)為init_MUTEX();由于中斷處理是獨占CPU的,這樣如果整個任務(wù)需要別的進程在中斷后很短時間立刻執(zhí)行,那么中斷處理就不能耗時過長,所以經(jīng)常把中斷處理分為頂半部和底半部,頂半部只用來響應(yīng)中斷而底半部通過開辟個內(nèi)核進程進行真正的處理,而內(nèi)核進程是不獨占CPU的,這樣別的進程也可同時執(zhí)行,在底半部開辟進程常用的機制就是工作隊列,而且工作隊列還允許睡眠機制,使用工作隊列時同樣也需進行初使化,它通過調(diào)用INIT_WORK ()完成;阻塞是實現(xiàn)進程睡眠的機制,進程在運行時如果需要等待一個事件來到才能繼續(xù)運行,這時就需要用阻塞來使進程睡眠,由于應(yīng)用層的進程需要等待數(shù)據(jù)來到才能繼續(xù)處理,所以在內(nèi)核的讀函數(shù)里需要添加阻塞,內(nèi)核給驅(qū)動提供的最簡便的阻塞,就是等待隊列機制,使用前一樣要進行初使化,調(diào)用的函數(shù)是init_waitqueue_head()。
TNT4882芯片在上電運行前,要進行初使化,初使化的流程為:復(fù)位TNT4882芯片中的Turbo電路;將 TNT4882設(shè)置成Turbo+7210式;將TNT4882設(shè)置成單芯片模式;使Local_ PowerOn信號有效;設(shè)置TNT4882的GPIB主地址,屏蔽副地址以及設(shè)置GPIB握手參數(shù);清除Local_ PowerOn信號后,開始GPIB操作。此過程全部是給寄存器賦值,這可通過內(nèi)核iowrite8()函數(shù)進行。
中斷函數(shù)的流程如圖3所示,ATN(Attention)代表注意命令,REN(Remote Enable)代表遠控使能狀態(tài),LACS(Listener Active State)代表聽者作用狀態(tài),TACS(Talker Active State)代表講者作用狀態(tài)。GPIB主設(shè)備發(fā)來ATN命令,TNT4882產(chǎn)生中斷,如果GPIB接口處是遠控使能和聽者作用狀態(tài),那么GPIB接口就準(zhǔn)備接收數(shù)據(jù),如果GPIB接口處是遠控使能和講者作用狀態(tài),那么GPIB接口就可以發(fā)送數(shù)據(jù)。具體實現(xiàn)的方法是:利用linux字符設(shè)備驅(qū)動中斷注冊接口request_irq,將中斷響應(yīng)注冊到內(nèi)核里,中斷號設(shè)置成硬件設(shè)計時占用中斷向量表中的中斷號,從參數(shù)*device得到的GPIB設(shè)備名,寫入/proc/interrupts文件里,同時將中斷響應(yīng)屬性flags配置成中斷底半部獨立運行,用iowrite8()將TNT4882中斷使能寄存器中有關(guān)的ATN、REN、TAC、SLACS全部使能,在中斷服務(wù)程序中,用ioread8()讀TNT4882中斷響應(yīng)信息寄存器的信息,出現(xiàn)與LACS相同的信息,調(diào)用收操作,出現(xiàn)與TACS一致的信息,調(diào)用發(fā)操作。
對于TNT4882芯片,收發(fā)操作首先要對收發(fā)數(shù)據(jù)狀態(tài)初始化,然后循環(huán)收發(fā)數(shù)據(jù),最后退出收發(fā)數(shù)據(jù)狀態(tài)。對于具體流程,發(fā)送數(shù)據(jù)流程,如圖4所示。由于GPIB是通過接口之間的握手協(xié)議來進行通信,所以接收和發(fā)送流程的初始化和退出要完全按照協(xié)議規(guī)定來設(shè)計,他們是一致的,這樣在接收數(shù)據(jù)的具體流程中,只詳細(xì)分析了數(shù)據(jù)接收過程,如圖5所示。TNT4882的FIFO是一個16字節(jié)深的緩沖區(qū),TNT4882不僅提供了它的全滿標(biāo)志位而且還提供了半滿標(biāo)志位,此流程中沒有使用傳統(tǒng)的全滿位作為接收數(shù)據(jù)的判斷位,而是采用了FIFO半滿位進行判斷,這樣從FIFO中收數(shù)據(jù)到內(nèi)核緩沖區(qū)和向FIFO里傳數(shù)據(jù)可以同時并發(fā)進行,而如果用全滿位進行判斷,只有先收數(shù)據(jù)后,有了空間才能向FIFO里傳數(shù)據(jù),所以采用半滿位可以提高數(shù)據(jù)接收速率。
圖3 中斷函數(shù)的流程
圖4 數(shù)據(jù)發(fā)送流程 圖5 數(shù)據(jù)接收流程
在中斷函數(shù)里,數(shù)據(jù)收發(fā)操作是通過調(diào)用自定義的兩個函數(shù):short Receive(void),short Send(void)來實現(xiàn)的。對于收是直接調(diào)用收函數(shù),而對于發(fā)則是間接調(diào)用,通過調(diào)用工作隊列來調(diào)用發(fā)函數(shù),如果直接調(diào)用數(shù)據(jù)發(fā)送函數(shù),那么根據(jù)中斷服務(wù)程序運行是獨占CPU的原理,導(dǎo)致用戶態(tài)的應(yīng)用程序只能等中斷服務(wù)程序結(jié)束才能執(zhí)行,這樣數(shù)據(jù)還沒有過來,就先執(zhí)行數(shù)據(jù)發(fā)送,反復(fù)輪詢,這顯然是低效的,而調(diào)用工作隊列,等于內(nèi)核開辟了一個進程,便釋放了CPU,并配置成非原子操作模式[9],這樣內(nèi)核態(tài)的工作隊列和用戶態(tài)的進程可同時運行,當(dāng)在工作隊列開始處再加上睡眠,數(shù)據(jù)到達內(nèi)核后,喚醒工作隊列,此時再調(diào)用發(fā)函數(shù),數(shù)據(jù)進行發(fā)送,此時運行效率明顯提升。
數(shù)據(jù)收發(fā)操作具體實現(xiàn)的方法是:在數(shù)據(jù)發(fā)操作方面,用creat_workque創(chuàng)建工作隊列,將該創(chuàng)建的工作隊列賦給queue_work函數(shù)第一個參數(shù),將Send(void)函數(shù)指針賦給queue_work函數(shù)第二個參數(shù),從而將Send(void)函數(shù)插入工作隊列,并將workqueue_struct 類型的結(jié)構(gòu)體變量中的屬性成員配置成非原子操作,中斷服務(wù)程序運行到queue_work接口處,內(nèi)核將自動開辟進程執(zhí)行Send(void),在開辟進程開始處,調(diào)入wait_event_interruptible()使Send(void)睡眠,在文件操作寫函數(shù)結(jié)束處,調(diào)入wake_up_interruptible()來喚醒該睡眠;對于Send(void)本身,用memset函數(shù)將TNT4882的FIFO清空,并用iowrite8()在傳輸寄存器里配置傳輸參數(shù)和傳輸字節(jié)數(shù),配置完成后,再用ioread8()讀取傳輸就緒位,等待傳輸就緒,最后使能傳輸位,完成初始化;設(shè)置傳輸字符計數(shù)變量,讀取FIFO狀態(tài)寄存器,判斷是否FIFO為滿,不為滿時往里面循環(huán)寫數(shù)據(jù),直到計數(shù)變量到達此次發(fā)送字符數(shù),退出發(fā)送操作,注意發(fā)操作結(jié)束后,要利用的destroy_workque注銷工作隊列。在數(shù)據(jù)收操作方面,初始化與發(fā)操作一致,不做贅述,用ioread8()讀取中斷使能寄存器的聽狀態(tài)位,如果是聽狀態(tài),并且FIFO至少半滿,設(shè)置接收計數(shù)變量,開始從FIFO中用循環(huán)取數(shù)據(jù),直至取FIFO半滿的數(shù)據(jù),然后再判斷中斷使能寄存器的聽狀態(tài)位,如果仍是聽狀態(tài),重復(fù)以上操作,如果聽狀態(tài)結(jié)束,F(xiàn)IFO中仍有數(shù)據(jù),用ioread8()將數(shù)據(jù)取回,退出收操作。
GPIB驅(qū)動里的讀寫函數(shù)即read和write分別實現(xiàn)用戶態(tài)從內(nèi)核收數(shù)據(jù)和用戶態(tài)向內(nèi)核發(fā)數(shù)據(jù),實際上就是在read和write里分別調(diào)用內(nèi)核的copy_to_user和copy_from_user來實現(xiàn)的。根據(jù)在初使化里的分析,讀寫要進行互斥,采用的方式是信號量。 具體實現(xiàn)的思路是:設(shè)信號量為驅(qū)動全局變量并調(diào)用內(nèi)核函數(shù)DECLARE MUTEX()使其初始值為1,在讀寫函數(shù)里的開始處調(diào)用down()使信號量減1,結(jié)束處調(diào)用up()使信號量加1。這樣如果寫進程操作了臨界資源,信號量的值變?yōu)榱悖x進程則無法操作臨界資源,當(dāng)寫進程結(jié)束后,信號量的值恢復(fù)為1,此時讀進程可以操作臨界資源,信號量的值又變?yōu)榱悖瑢戇M程則無法操作臨界資源,當(dāng)讀進程結(jié)束后,信號量的值又恢復(fù)為1,此時寫進程又可再次操作臨界資源,這樣就實現(xiàn)了讀寫互斥,消除了競態(tài)。
除此之外,由于內(nèi)核的讀函數(shù)是給應(yīng)用程序調(diào)用的函數(shù)接口,為了實現(xiàn)應(yīng)用程序在數(shù)據(jù)沒有到來的時候能夠被掛起,內(nèi)核的讀函數(shù)還要實現(xiàn)阻塞,具體實現(xiàn)的思路是:在讀函數(shù)里的開始處可以通過調(diào)用wait_event_interruptible()來使讀函數(shù)進入睡眠狀態(tài),wait_event_interruptible()要放到down()前面,否則會產(chǎn)生死鎖,在GPIB接口處聽者作用狀態(tài)結(jié)束時,通過wake_up_interruptible()來喚醒睡眠狀態(tài),這樣就實現(xiàn)了讀阻塞。
將儀器與計算機通過GPIB電纜相連,將mknod建立設(shè)備結(jié)點命令和insmod向內(nèi)核插入驅(qū)動命令的參數(shù)均設(shè)置為優(yōu)化后的GPIB驅(qū)動模塊,然后將以上命令編寫成腳本,在LINUX終端下運行此腳本,GPIB驅(qū)動便加載到LINUX內(nèi)核,并運行測試程序,因為在GPIB驅(qū)動的read函數(shù)里用了阻塞機制,所以在數(shù)據(jù)沒有到來之前,程序在阻塞狀態(tài)。從圖6可以看到GPIB設(shè)備已打開,驅(qū)動被成功加載到內(nèi)核。
圖6 優(yōu)化后的GPIB驅(qū)動被成功加載到內(nèi)核
接著在計算機上運行GPIB管理器軟件,*IDN?[10]命令隨軟件啟動自動發(fā)送,枚舉當(dāng)前在線儀器,圖7展示了儀器的信息成功返回;最后再對GPIB驅(qū)動進行命令快速連續(xù)發(fā)送測試,如圖8所示。
圖7 儀器信息成功返回
圖8 命令快速連續(xù)發(fā)送測試
從以上測試步驟可以看到,優(yōu)化后的GPIB驅(qū)動加載到LINUX內(nèi)核,系統(tǒng)運行正常,說明與系統(tǒng)兼容性良好,利用GPIB管理器軟件對在線GPIB儀器進行枚舉,設(shè)備被正常識別,說明驅(qū)動通信正常,以上兩方面的測試,反映了優(yōu)化后的GPIB驅(qū)動并沒有影響驅(qū)動原有的正常運行和功能,最后再針對向儀器發(fā)命令的頻率比較高時,GPIB驅(qū)動傳輸性能不是很理想的問題,進行命令快速連續(xù)發(fā)送測試,所有命令返回值均立即并正確返回,GPIB管理器軟件并無警告或異常報錯提示,這表明優(yōu)化后的GPIB驅(qū)動,使此問題得到了有效的改善。
本文主要從中斷服務(wù)程序和設(shè)備文件操作讀寫函數(shù)對GPIB驅(qū)動進行了優(yōu)化設(shè)計。在中斷服務(wù)程序底半部里,利用非原子操作工作隊列,實現(xiàn)了內(nèi)核態(tài)的進程和用戶態(tài)的進程并行,并結(jié)合了睡眠機制,從而提高了驅(qū)動運行效率;受并行工作思想的啟發(fā),提出了FIFO半滿位作為接收數(shù)據(jù)流程中的判斷標(biāo)準(zhǔn),實現(xiàn)了FIFO中收數(shù)據(jù)到內(nèi)核緩沖區(qū)和向FIFO里傳數(shù)據(jù)可以并發(fā)進行,對加快數(shù)據(jù)傳輸速率起到了較好的效果;在讀寫函數(shù)里,應(yīng)用了信號量,避免了讀寫可能同時操作臨界資源的隱患,對驅(qū)動的可靠運行進行了加固。在對驅(qū)動運行效率,傳輸速率,可靠性幾方面的優(yōu)化后,GPIB驅(qū)動傳輸性能得到了一定的提升。