常 鋒, 孟傳良
為了實現 Linux操作系統的諸多網絡功能,需要設計出合適的網絡驅動程序。為了更好地設計出適合的網絡驅動程序,需要對其運行機理有所了解[1]。由于Linux是開放源代碼的,這使得在Linux內核中對相關的網絡設備驅動部分進行修改提供了方便,可以對其網絡驅動程序部分進行分析和改造,使其滿足特殊的應用需要。這里就對 Linux內核中的網絡驅動程序部分進行了詳細的討論,并結合一個具體的實例,探討了如何設計出適合的網絡驅動程序。
Linux中將設備分為3種類型:字符設備、塊設備和網絡設備。它們的編寫方法大致相同,其中字符設備和塊設備可以像文件一樣被訪問,網絡設備的驅動是 Linux操作系統中一種比較特殊的應用,它通常是通過套接字(Socket)這種接口來實現的[2]。在套接字機制中,系統和驅動程序之間通過專門的網絡接口負責數據的傳遞。應用程序不能直接訪問網絡驅動程序,而只能通過網絡子系統與它進行交互,網絡驅動程序異步地接收來自外界的數據[3]。
Linux系統的優點之一是它具有豐富而穩定的網絡協議棧,不同于網絡理論中一般的OSI模型,Linux系統的網絡協議棧一般分為4層。從上到下分別是:網絡協議接口層、網絡設備接口層、設備驅動功能層,以及網絡設備和網絡媒介層[4]。
在這里,網絡設備和網絡媒介層提供訪問物理設備的驅動程序。設備驅動功能層的作用是為上層程序提供訪問外部設備的操作接口,并且實現設備的驅動程序,它通過hard_start_xmit()函數啟動發送操作,并通過網絡設備上的中斷觸發接受操作。網絡設備接口層向協議接口層提供統一的用于描述具體網絡設備屬性和操作的結構體net_device,該結構體是網絡設備驅動功能層中各函數的容器。網絡設備接口層負責對硬件設備驅動功能層的宏觀規劃。網絡協議接口層為網絡層協議提供了一個統一的數據包收發接口,而無需考慮上層協議類型,其中dev_queue_xmit()函數用來發送數據,netif_rx()函數用來接受數據。這一層的設計可以使得上層協議無需考慮具體硬件的實現[5]。

圖1 Linux網絡驅動體系結構
網絡驅動程序完成的主要功能包括系統的初始化、數據包的發送和接收等[6]。系統的初始化是通過定義數據結構net_device中的初始化函數init()實現的。初始化函數init()會檢測設備的硬件信息來確定是否對該設備分配資源空間,檢測完成后會往數據結構 net_device中填充該設備的信息,并將該新設備添加到新的設備列表中。
數據包的發送與接收是網絡設備驅動程序的設計中最主要的內容,對該部分驅動程序編寫的好壞會直接影響到驅動程序的整體質量。網絡設備的發送需要用到協議接口層函數dev_queue_xmit(),通過它來調用數據結構net_device中的hard_header函數指針,讀取存放在套接字緩沖區sk_buff里的數據,并將數據發送到物理設備上。數據包的接收通常使用的是 netif_rx()函數,當底層硬件設備驅動程序收到數據包時,硬件會產生一個中斷信號,隨后系統就會通過調用netif_rx()函數將套接字緩沖區sk_buff里的數據上傳至網絡層。然后,套接字緩沖區sk_buff中的數據會被 netif_rx_schedule函數在上層協議隊列里進行排隊,以便接下來的處理。
實例中,網絡控制器選用的是CS8900A局域網處理芯片。CS8900A是一款由CIRRUS LOGIC公司生產的16位以太網控制器,它具有功能強大、功耗較低、性能優越等特點。該芯片的突出優點是適應性強、配置方便。它可對物理接口、數據傳輸模式和工作模式等根據不同的需要進行動態的調整,亦可通過對其內部寄存器的調整使之滿足不同應用環境的需求。
該芯片的內部功能模塊所使用的訪問控制塊(MAC)的設計完全依照以太網IEEE 802.3標準,具有通用性好的優點,可與當今主流的嵌入式系統兼容。該芯片在處理發送和接收以太網數據幀時主要完成的工作有:檢測數據幀的發送和接收是否存在沖突,對發送的數據幀生成幀頭信息,對收到的數據幀的幀頭信息進行檢測。此外,它還可對數據幀進行CSC校驗碼的生成和檢測。當對其發送控制寄存器(TxCMD)進行初始化配置后,可以使得訪問控制塊能夠自動檢測數據幀是否有沖突否則要求重傳。對于不符合 802.3標準的數據幀,比如少于46 Byte的數據幀,它可自動填充字段以補足不足部分使之滿足標準需求[7]。另外,CS8900A網卡還支持多種傳輸模式,包括I/O模式(該模式為CS8900A存儲區的默認模式,配置較方便)和 Memory模式以及DMA模式。
網絡設備驅動程序的設計需要完成的主要工作是網絡設備的注冊、網絡設備的初始化和注銷,以及對發送和接收的數據進行處理等,當遇到傳輸超時和中斷的情況時還要求其能即時做出處理。由于Linux系統是開源的,可以很方便地查看其內核的設備驅動源代碼,其中數據結構和函數的模板已由Linux提供。因此,設計驅動程序時,要做的主要工作就是根據具體的網絡設備填充相應的操作即可。這里,以CS8900A網卡驅動程序設計的實例來具體說明網絡設備驅動程序的具體實現過程。開發環境使用了arm-linux-gcc交叉工具鏈,采用Linux2.6.26內核以及S3C2410開發板。
2.2.1 網絡設備的初始化
網絡設備的初始化是設計網絡驅動程序的第一步,該過程主要是通過結構體net_device中的init()函數實現的。下面就以實例來說明初始化函數要完成的主要工作,這里將初始化函數設定為__init cs8900_probe():
首先,通過以太網接口的函數ether_setup()的設置,告知系統需要使用的以太網接口,這里選擇默認值即可;接下來,需要對 net_device結構體中的大部分的設備成員進行填充;然后,調用 check_mem_region()函數,用來檢測要使用的 I/O地址空間,并向系統申請地址空間,這里使用的是以base_addr為起始地址的16個連續的I/O地址空間;接著還需要調用cs8900_read()函數,用以檢測CS8900A的網卡信息,這里,設置網卡的INTRQ0為中斷信號的輸出引腳,并將網卡的 MAC地址寫入到CS8900A網卡的IA寄存器之中;最后,通過對初始化函數的定義在 register_netdev時的調用完成CS8900A網卡在Linux系統的設備鏈表中的注冊。
2.2.2 網絡設備的打開與關閉
網絡設備的打開和關閉是網絡驅動程序設計中必不可少的一環。由于Linux系統會響應ifconfig命令,打開或者關閉一個網絡接口。這時,ifconfig命令會調用控制I/O設備的ioctl函數獲得設備信息和向設備發送控制參數,該調用會設置flag的IFF_UP位對設備進行打開操作,它可通過驅動程序的open方法來實現。這里,就以 cs8900_start()函數為實例來說明如何打開網絡設備,該函數要完成的主要工作包括如下:
首先,使用request_irp()函數申請中斷,選擇中斷類型以及要申請中斷的網絡設備;然后,通過cs8900_set()函數對 CS8900A網卡中的各個寄存器進行設置,并啟動該設備;最后,利用 netif_start_queue()函數指定要啟動發送隊列的設備,以便實現網絡的數據傳輸。
2.2.3 網絡數據包的發送
網絡數據包的發送和接收是驅動程序設計中要實現的兩個最重要的任務。發送數據包的一般過程是:首先,當網絡設備加載到 Linux系統上時,會使用到結構體 net_device中的 open方法,并通過hard_header()函數的調用來建立所用到的網絡設備的硬件幀頭信息。當需要發送一個數據包時,它會調用hard_start_transmit函數,該函數將會使用結構體net_device中的hard_start_xmit()函數,這是一個指向緩沖區sk_buff的指針,用以實現將緩沖區里的數據取出并發送到網絡設備上去。如果數據發送成功,hard_start_xmit()方法會釋放緩沖區sk_buff里的數據,并返回0;如若硬件設備暫時無法處理,比如硬件忙,則返回為1。這時,如果tbusy的值是非0,則被系統任務認為是硬件忙,需要等到 tbusy為 0時才可發送數據。一般,tbusy的置0任務是由系統的中斷來完成的。當硬件發送數據任務結束后,會產生一個中斷,這時可以將tbusy置為0,以便通知系統進行下次發送。這里以cs8900_send_start()發送函數為實例,介紹發送數據時所要完成的主要工作:
首先,嘗試獲取自旋鎖,隨后中止網絡設備的發送隊列;然后,告知CS8900A需要發送數據,以及數據的長度;接下來,通過讀取 PP_BusST寄存器,獲得CS8900A的狀態,這要分三種情況,若寄存器TxBidErr置位,表示數據長度不合法,則返回錯誤,若寄存器Rdy4TxNOW沒被置位,表示硬件還沒準備好,則返回錯誤,若一切正常,則開始發送數據幀;還要調用 cs8900_frame_write()函數發送數據;最后,記錄數據幀的發送時間,以及釋放緩沖區。
2.2.4 網絡數據包的接受和中斷處理
數據包的接收過程一般是,當數據包到達網絡設備時,會由硬件產生一個中斷,通知驅動程序進行數據包的接收。中斷處理程序會向系統申請一段地址作為sk_buff緩沖區,用以存放接收到的數據。當接收到的數據存放到緩沖區后,還需要對其部分信息進行填充,比如指定設備的結構體、數據幀類型以及鏈路層數據類型等。最后,通過協議接口層函數 netif_rx()的調用,把數據包交由上層來處理。這里,以接收函數cs8900_receive()為實例,說明網絡設備的接收數據過程:
首先,通過讀取硬件寄存器,獲得CS8900A的狀態,以及接收的數據長度;然后,狀態解析,若不是RxOK狀態,還需判斷錯誤類型,分為三種情況:若為Runt或者Extradata狀態,則表示數據包長度不合法,若為CRCerror狀態,則表示數據包校驗錯誤,若一切正常,則開始數據的接收;接下來,申請一塊緩沖區,并調用 cs8900_frame_read()函數來接收數據;最后,當數據接收完成后,調用netif_rx()函數通知上層。
網絡驅動程序設計好之后,還需要對這個內核模塊進行編譯,以自定義的這個內核模塊作為Linux系統源碼的一部分編譯出新的系統內核。下面就以Linux2.6.26內核的開發環境為例,介紹如何將CS8900的網卡驅動模塊編譯進Linux系統內核。
首先,在Linux2.6.26內核的源碼主目錄(這里為/usr/src/linux2.6.26)中的drivers目錄下創建新的目錄 cs8900;然后,將之前編寫好的 cs8900.c和cs8900.h這兩個文件拷貝到該目錄下;接著,就可以編寫Makefile文件:
#CS8900的Makefile文件
obj -$(CONFIG_DRIVER_CS8900) +=cs8900.o
同樣,Kconfig文件的編寫如下:
#CS8900的網絡接口
menu "網絡接口支持"
config DRIVER_CS8900
tristate "CS8900支持"
source "drivers/cs8900/Kconfig"
endmenu
設置好之后,當使用make menuconfig命令時,可以發現Device Drivers選項下會出現“網絡接口支持”選項,選擇里面的 “CS8900支持”項,可以看到它包括三種狀態:即未選中,表示不編譯;選中(M),表示編譯該模塊;選中(*),表示將該模塊編譯為新系統的一部分。
接下來,對該Linux2.6.26內核進行重新編譯,使得該內核可以支持CS8900網卡驅動,然后,將編譯后的Linux內核上載到S3C2410的開發板上,配置好相應的網絡參數,就可以對網卡驅動程序進行測試了。
網絡設備驅動測試主要是從底層硬件測試和上層網絡測試這兩個方面進行。底層硬件測試的方法如下:在內存中開辟兩塊區域M1和M2,并按照網絡數據包的格式設置初始值,將區域 M1中的數據通過網卡發送出去,通過回環的方式,最終由主機端接收到,存放到區域M2中;再將區域M2中的數據也通過網卡發送出去,并且接收回來。經過多次的循環,比較最終得到的數據包和之前設置的數據包是否是一致的。經過測試后可以發現,最底層的數據發送和接收是正確無誤的。
上層網絡測試的方法如下:通過網線將目標機和PC主機連接起來,并在目標機端開啟tftp服務,主機端則使用FlashFXP軟件,從主機分別傳送數個小于100 MB的大小不同的文件到目標機,再從目標機將文件傳回主機,這個過程進行數次。最后查看平均的網絡傳輸速度。測試結果表明網卡驅動的工作正常,速度穩定在5 Mbit/s左右。
該設計結合CS8900A網絡芯片為例,分析了網絡設備驅動程序運行的機理,詳細介紹了實現Linux網絡設備驅動程序的一般步驟和關鍵過程,重點說明每步中關鍵步驟設置和處理。一般 Linux網絡設備驅動程序的設計都有一定的模式[8-9],遵循這個模式,可以快速地設計出需要的驅動程序。分析了Linux內核中的網絡驅動程序的工作機理,并結合一個實例,說明如何具體設計網絡驅動程序使之滿足應用要求。通過對CS8900A網絡驅動程序設計的研究,對于其他相類似的網絡驅動程序的設計具有一定的參考價值。
[1] 宋寶華.Linux設備驅動開發詳解[M].北京:人民郵電出版社,2006.
[2] 唐六華,唐建明,向紅權.嵌入式Linux下OLED顯示功能模塊實現[J] .信息安全與通信保密,2009(06): 55-57.
[3] 韋東山.嵌入式Linux應用開發完全手冊[M].北京:人民郵電出版社,2008.
[4] 李善平,劉文蜂. Linux內核2.4版源代碼分析大全[M].北京:機械工業出版社,2002.
[5] 科爾特.LINUX設備驅動程序[M].北京:中國電力出版社,2005.
[6] 朱小可.尚觀科技[M].北京:尚觀科技,2008.
[7] 沈金河.Web協議與實現[M].北京:科學出版社,2003.
[8] 陳新, 翁秋華.基于Linux+ARM9的Wi-Fi網絡圖形化設計與實現[J].通信技術,2012,45(03):60-62,65.
[9] 楊永,萬曉榆,樊自甫..基于 ARM9嵌入式網管系統的設計與實現[J].通信技術,2008,41(03):68-70.