Linux 網(wǎng)絡(luò)設(shè)備驅(qū)動開發(fā)(一) —— linux內(nèi)核網(wǎng)絡(luò)分層結(jié)構(gòu)
Linux內(nèi)核對網(wǎng)絡(luò)驅(qū)動程序使用統(tǒng)一的接口,并且對于網(wǎng)絡(luò)設(shè)備采用面向?qū)ο蟮乃枷朐O(shè)計。
Linux內(nèi)核采用分層結(jié)構(gòu)處理網(wǎng)絡(luò)數(shù)據(jù)包。分層結(jié)構(gòu)與網(wǎng)絡(luò)協(xié)議的結(jié)構(gòu)匹配,既能簡化數(shù)據(jù)包處理流程,又便于擴展和維護。
一、內(nèi)核網(wǎng)絡(luò)結(jié)構(gòu)
在Linux內(nèi)核中,對網(wǎng)絡(luò)部分按照網(wǎng)絡(luò)協(xié)議層、網(wǎng)絡(luò)設(shè)備層、設(shè)備驅(qū)動功能層和網(wǎng)絡(luò)媒介層的分層體系設(shè)計。
網(wǎng)絡(luò)驅(qū)動功能層主要通過網(wǎng)絡(luò)驅(qū)動程序?qū)崿F(xiàn)。
在Linux內(nèi)核,所有的網(wǎng)絡(luò)設(shè)備都被抽象為一個接口處理,該接口提供了所有的網(wǎng)絡(luò)操作。
net_device結(jié)構(gòu)表示網(wǎng)絡(luò)設(shè)備在內(nèi)核中的情況,也就是網(wǎng)絡(luò)設(shè)備接口。網(wǎng)絡(luò)設(shè)備接口既包括軟件虛擬的網(wǎng)絡(luò)設(shè)備接口,如環(huán)路設(shè)備,也包括了網(wǎng)絡(luò)硬件設(shè)備,如以太網(wǎng)卡。
Linux內(nèi)核有一個dev_base的全局指針,指向一個設(shè)備鏈表,包括了系統(tǒng)內(nèi)的所有網(wǎng)絡(luò)設(shè)備。該設(shè)備鏈表每個節(jié)點是一個網(wǎng)絡(luò)設(shè)備。
在net_device結(jié)構(gòu)中提供了許多供系統(tǒng)訪問和協(xié)議層調(diào)用的設(shè)備方法,包括初始化、打開關(guān)閉設(shè)備、數(shù)據(jù)包發(fā)送和接收等。
二、與網(wǎng)絡(luò)有關(guān)的數(shù)據(jù)結(jié)構(gòu)
內(nèi)核對網(wǎng)絡(luò)數(shù)據(jù)包的處理都是基于sk_buff結(jié)構(gòu)的,該結(jié)構(gòu)是內(nèi)核網(wǎng)絡(luò)部分最重要的數(shù)據(jù)結(jié)構(gòu)。
網(wǎng)絡(luò)協(xié)議棧中各層協(xié)議都可以通過對該結(jié)構(gòu)的操作實現(xiàn)本層協(xié)議數(shù)據(jù)的添加或者刪除。使用sk_buff結(jié)構(gòu)避免了網(wǎng)絡(luò)協(xié)議棧各層來回復(fù)制數(shù)據(jù)導(dǎo)致的效率低下。
sk_buff結(jié)構(gòu)可以分為兩個部分,一部分是存儲數(shù)據(jù)包緩存,在圖中表示為PackertData,另一部分是由一組用于內(nèi)核管理的指針組成。
sk_buff管理的指針最主要的是下面4個:
-
head指向數(shù)據(jù)緩沖(PackertData)的內(nèi)核首地址;
-
data指向當前數(shù)據(jù)包的首地址;
-
tail指向當前數(shù)據(jù)包的尾地址;
-
end 指向數(shù)據(jù)緩沖的內(nèi)核尾地址。
數(shù)據(jù)包的大小在內(nèi)核網(wǎng)絡(luò)協(xié)議棧的處理過程中會發(fā)生改變,因此data和tail指針也會不斷變化,而head和tail指針是不會發(fā)生改變的。
對于一個TCP數(shù)據(jù)包為例,sk_buff還提供了幾個指針直接指向各層協(xié)議頭。mac指針指向數(shù)據(jù)的mac頭;nh指針指向網(wǎng)絡(luò)協(xié)議頭,一般是IP協(xié)議頭;h指向傳輸層協(xié)議頭,在本例中是TCP協(xié)議頭。
對各層設(shè)置指針的是方便了協(xié)議棧對數(shù)據(jù)包的處理。
三、net_device結(jié)構(gòu)
Linux內(nèi)核中網(wǎng)絡(luò)設(shè)備最重要的數(shù)據(jù)結(jié)構(gòu)就是net_device結(jié)構(gòu)了,它是網(wǎng)絡(luò)驅(qū)動程序最重要的部分。
net_device結(jié)構(gòu)保存在include/linux/netdevices.h頭文件,理解該結(jié)構(gòu)對理解網(wǎng)絡(luò)設(shè)備驅(qū)動有很大幫助。
內(nèi)核中所有網(wǎng)絡(luò)設(shè)備的信息和操作都在net_device設(shè)備中,無論是注冊網(wǎng)絡(luò)設(shè)備,還是設(shè)置網(wǎng)絡(luò)設(shè)備參數(shù),都用到該結(jié)構(gòu)。
下面是主要數(shù)據(jù)成員。
-
設(shè)備名稱
-
總線參數(shù)
-
協(xié)議參數(shù)
-
鏈接層變量
-
接口標志
四、數(shù)據(jù)包接收流程
在Linux內(nèi)核中,一個網(wǎng)絡(luò)數(shù)據(jù)包從網(wǎng)卡接收到用戶空間需要經(jīng)過鏈路層、傳輸層和socket的處理,最終到達用戶空間。
以DM9000網(wǎng)卡為例,當網(wǎng)卡收到數(shù)據(jù)包以后,調(diào)用中斷處理函數(shù) dm9000_interrupt(),該函數(shù)檢查中斷處理類型,如果是接收數(shù)據(jù)包中斷,則調(diào)用 dm9000_rx()函數(shù)接收數(shù)據(jù)包到內(nèi)核空間。
dm9000_rx()函數(shù)收到數(shù)據(jù)包完成后,內(nèi)核會繼續(xù)調(diào)用 netif_rx()函數(shù),函數(shù)的作用是把網(wǎng)卡接收到數(shù)據(jù)提交給協(xié)議棧處理。
協(xié)議棧使用 net_rx_action()函數(shù)處理接收數(shù)據(jù)包隊列,該函數(shù)處理數(shù)據(jù)包后如果是 IP數(shù)據(jù)包則提交給ip_recv()函數(shù)處理。ip_recv()函數(shù)主要是檢查一個數(shù)據(jù)包IP頭的合法性,檢查通過后交給 ip_local_deliver()和 ip_local_deliver_finish()函數(shù)處理,之所以分開處理是因為內(nèi)核中有防火墻相關(guān)的代碼需要動態(tài)加載到此處。
IP頭處理完畢后,以UDP數(shù)據(jù)包為例將交由 udp_recv()函數(shù)處理,與 ip_recv()函數(shù)類億,該函數(shù)檢查 UDP頭的合法性,然后交給 udp_queue_recv()函數(shù)處理,最后提交給 sock_queue_recv()函數(shù)處理。
數(shù)據(jù)包進入 socket部分的第一個函數(shù)是 skb_recv_datagram(),該函數(shù)從內(nèi)核的 socket隊列取出數(shù)據(jù)包,交給 socket部分的 udp_recvmsg()函數(shù),該函數(shù)負責(zé)處理UDP的數(shù)據(jù),sock_recvmsg()處理提交給 sock_read()函數(shù)。
sock_read()函數(shù)讀取接收到的數(shù)據(jù)緩沖,把數(shù)據(jù)返回給 sys_read()系統(tǒng)調(diào)用。sys_read()函數(shù)調(diào)用最終把數(shù)據(jù)復(fù)制到用戶空間,供用戶使得。
五、數(shù)據(jù)包發(fā)送流程
以UDP數(shù)據(jù)包發(fā)送流程為例,在DM9000網(wǎng)卡上如何發(fā)送一個數(shù)據(jù)包。
當用戶空間的應(yīng)用程序通過 socket函數(shù) sento()發(fā)送一個UDP數(shù)據(jù)后,會調(diào)用內(nèi)核空間的 sock_writev()函數(shù),然后通過 sock_sendmsg()函數(shù)處理。
sock_sendmsg()函數(shù)調(diào)用 inet_sendmsg()函數(shù)處理,inet_sendmsg()函數(shù)會把要發(fā)送的數(shù)據(jù)交給傳輸層的 udp_sendmsg()函數(shù)處理。
udp_sendmsg()函數(shù)在數(shù)據(jù)前加入UDP頭,然后把數(shù)據(jù)交給 ip_build_xmit()函數(shù)處理,該函數(shù)根據(jù) socket提供的目的 IP和端口信息構(gòu)造IP頭,然后調(diào)用 output_maybe_reroute()函數(shù)處理。out_maybe_reroute()函數(shù)檢查數(shù)據(jù)包是否需要經(jīng)過路由,最后交給 ip_output()函數(shù)寫入到發(fā)送隊列,寫入完成后由 ip_finish_output()函數(shù)處理后續(xù)工作。
鏈路層的 dev_queue_xmit()函數(shù)處理發(fā)送隊列,調(diào)用 DM9000網(wǎng)卡的發(fā)送數(shù)據(jù)包函數(shù) dm9000_xmit()發(fā)送數(shù)據(jù)包,發(fā)送完畢后,調(diào)用 dm9000_xmit_done函數(shù)處理發(fā)送結(jié)果。