Linux操做系統網絡驅動程序編寫
一.Linux系統設備驅動程序概述
1.1 Linux設備驅動程序分類
1.2 編寫驅動程序的一些基本概念
二.Linux系統網絡設備驅動程序
2.1 網絡驅動程序的結構
2.2 網絡驅動程序的基本方法
2.3 網絡驅動程序中用到的數據結構
2.4 經常使用的系統支持
三.編寫Linux網絡驅動程序中可能遇到的問題
3.1 中斷共享
3.2 硬件發送忙時的處理
3.3 流量控制(flow control)
3.4 調試
四.進一步的閱讀
五.雜項 linux
一.Linux系統設備驅動程序概述
1.1 Linux設備驅動程序分類
Linux設備驅動程序在Linux的內核源代碼中佔有很大的比例,源代碼的長度日
益增長,主要是驅動程序的增長。在Linux內核的不斷升級過程當中,驅動程序的結構
仍是相對穩定。在2.0.xx到2.2.xx的變更裏,驅動程序的編寫作了一些改變,可是
從2.0.xx的驅動到2.2.xx的移植只需作少許的工做。
Linux系統的設備分爲字符設備(char device),塊設備(block device)和網絡
設備(network device)三種。字符設備是指存取時沒有緩存的設備。塊設備的讀寫
都有緩存來支持,而且塊設備必須可以隨機存取(random access),字符設備則沒有
這個要求。典型的字符設備包括鼠標,鍵盤,串行口等。塊設備主要包括硬盤軟盤
設備,CD-ROM等。一個文件系統要安裝進入操做系統必須在塊設備上。
網絡設備在Linux裏作專門的處理。Linux的網絡系統主要是基於BSD unix的socket
機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系
統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。
1.2 編寫驅動程序的一些基本概念編程
不管是什麼操做系統的驅動程序,都有一些通用的概念。操做系統提供給驅動 程序的支持也大體相同。下面簡單介紹一下網絡設備驅動程序的一些基本要求。 c#
1.2.1 發送和接收
這是一個網絡設備最基本的功能。一塊網卡所作的無非就是收發工做。因此驅
動程序裏要告訴系統你的發送函數在哪裏,系統在有數據要發送時就會調用你的發
送程序。還有驅動程序因爲是直接操縱硬件的,因此網絡硬件有數據收到最早能得
到這個數據的也就是驅動程序,它負責把這些原始數據進行必要的處理而後送給系
統。這裏,操做系統必需要提供兩個機制,一個是找到驅動程序的發送函數,一個
是驅動程序把收到的數據送給系統。
1.2.2 中斷
數組
中斷在現代計算機結構中有重要的地位。操做系統必須提供驅動程序響應中斷 的能力。通常是把一箇中斷處理程序註冊到系統中去。操做系統在硬件中斷髮生後 緩存
調用驅動程序的處理程序。Linux支持中斷的共享,即多個設備共享一箇中斷。
1.2.3 時鐘
在實現驅動程序時,不少地方會用到時鐘。如某些協議裏的超時處理,沒有中
斷機制的硬件的輪詢等。操做系統應爲驅動程序提供定時機制。通常是在預約的時
間過了之後回調註冊的時鐘函數。在網絡驅動程序中,若是硬件沒有中斷功能,定
時器能夠提供輪詢(poll)方式對硬件進行存取。或者是實現某些協議時須要的超時
重傳等。 服務器
二.Linux系統網絡設備驅動程序
2.1 網絡驅動程序的結構
全部的Linux網絡驅動程序遵循通用的接口。設計時採用的是面向對象的方法。
一個設備就是一個對象(device 結構),它內部有本身的數據和方法。每個設備的
方法被調用時的第一個參數都是這個設備對象自己。這樣這個方法就能夠存取自身
的數據(相似面向對象程序設計時的this引用)。
一個網絡設備最基本的方法有初始化、發送和接收。
------------------- ---------------------
|deliver packets | |receive packets queue|
|(dev_queue_xmit()) | |them(netif_rx()) |
------------------- ---------------------
| | /
/ | |
-------------------------------------------------------
| methods and variables(initialize,open,close,hard_xmit,|
| interrupt handler,config,resources,status...) |
-------------------------------------------------------
| | /
/ | |
----------------- ----------------------
|send to hardware | |receivce from hardware|
----------------- ----------------------
| | /
/ | |
-----------------------------------------------------
| hardware media |
-----------------------------------------------------
初始化程序完成硬件的初始化、device中變量的初始化和系統資源的申請。發送
程序是在驅動程序的上層協議層有數據要發送時自動調用的。通常驅動程序中不對發
送數據進行緩存,而是直接使用硬件的發送功能把數據發送出去。接收數據通常是通
過硬件中斷來通知的。在中斷處理程序裏,把硬件幀信息填入一個skbuff結構中,然
------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
後調用netif_rx()傳遞給上層處理。
2.2 網絡驅動程序的基本方法
網絡設備作爲一個對象,提供一些方法供系統訪問。正是這些有統一接口的方法,
掩蔽了硬件的具體細節,讓系統對各類網絡設備的訪問都採用統一的形式,作到硬件
無關性。
下面解釋最基本的方法。
2.2.1 初始化(initialize)
驅動程序必須有一個初始化方法。在把驅動程序載入系統的時候會調用這個初
始化程序。它作如下幾方面的工做。檢測設備。在初始化程序裏你能夠根據硬件的
特徵檢查硬件是否存在,而後決定是否啓動這個驅動程序。配置和初始化硬件。在
初始化程序裏你能夠完成對硬件資源的配置,好比即插即用的硬件就能夠在這個時
候進行配置(Linux內核對PnP功能沒有很好的支持,能夠在驅動程序裏完成這個功
能)。配置或協商好硬件佔用的資源之後,就能夠向系統申請這些資源。有些資源是
能夠和別的設備共享的,如中斷。有些是不能共享的,如IO、DMA。接下來你要初始
化device結構中的變量。最後,你可讓硬件正式開始工做。
2.2.2 打開(open)
open這個方法在網絡設備驅動程序裏是網絡設備被激活的時候被調用(即設備狀
態由down-->up)。因此實際上不少在initialize中的工做能夠放到這裏來作。好比資
源的申請,硬件的激活。若是dev->open返回非0(error),則硬件的狀態仍是down。
open方法另外一個做用是若是驅動程序作爲一個模塊被裝入,則要防止模塊卸載時
設備處於打開狀態。在open方法裏要調用MOD_INC_USE_COUNT宏。
2.2.3 關閉(stop)
close方法作和open相反的工做。能夠釋放某些資源以減小系統負擔。close是在
設備狀態由up轉爲down時被調用的。另外若是是作爲模塊裝入的驅動程序,close裏
應該調用MOD_DEC_USE_COUNT,減小設備被引用的次數,以使驅動程序能夠被卸載。
另外close方法必須返回成功(0==success)。
2.2.4 發送(hard_start_xmit)
全部的網絡設備驅動程序都必須有這個發送方法。在系統調用驅動程序的xmit
時,發送的數據放在一個sk_buff結構中。通常的驅動程序把數據傳給硬件發出去。
也有一些特殊的設備好比loopback把數據組成一個接收數據再回送給系統,或者
dummy設備直接丟棄數據。
若是發送成功,hard_start_xmit方法裏釋放sk_buff,返回0(發送成功)。若是
設備暫時沒法處理,好比硬件忙,則返回1。這時若是dev->tbusy置爲非0,則系統
認爲硬件忙,要等到dev->tbusy置0之後纔會再次發送。tbusy的置0任務通常由中斷
完成。硬件在發送結束後產生中斷,這時能夠把tbusy置0,而後用mark_bh()調用通
知系統能夠再次發送。在發送不成功的狀況下,也能夠不置dev->tbusy爲非0,這樣
系統會不斷嘗試重發。若是hard_start_xmit發送不成功,則不要釋放sk_buff。
傳送下來的sk_buff中的數據已經包含硬件須要的幀頭。因此在發送方法裏不需
要再填充硬件幀頭,數據能夠直接提交給硬件發送。sk_buff是被鎖住的(locked),
確保其餘程序不會存取它。
2.2.5 接收(reception)
驅動程序並不存在一個接收方法。有數據收到應該是驅動程序來通知系統的。
通常設備收到數據後都會產生一箇中斷,在中斷處理程序中驅動程序申請一塊
sk_buff(skb),從硬件讀出數據放置到申請好的緩衝區裏。接下來填充sk_buff中
的一些信息。skb->dev = dev,判斷收到幀的協議類型,填入skb->protocol(多協
議的支持)。把指針skb->mac.raw指向硬件數據而後丟棄硬件幀頭(skb_pull)。還要
設置skb->pkt_type,標明第二層(鏈路層)數據類型。能夠是如下類型:
PACKET_BROADCAST : 鏈路層廣播
PACKET_MULTICAST : 鏈路層組播
PACKET_SELF : 發給本身的幀
PACKET_OTHERHOST : 發給別人的幀(監聽模式時會有這種幀)
最後調用netif_rx()把數據傳送給協議層。netif_rx()裏數據放入處理隊列而後返
回,真正的處理是在中斷返回之後,這樣能夠減小中斷時間。調用netif_rx()之後,
驅動程序就不能再存取數據緩衝區skb。
2.2.6 硬件幀頭(hard_header)
硬件通常都會在上層數據發送以前加上本身的硬件幀頭,好比以太網(Ethernet)
就有14字節的幀頭。這個幀頭是加在上層ip、ipx等數據包的前面的。驅動程序提供
一個hard_header方法,協議層(ip、ipx、arp等)在發送數據以前會調用這段程序。
硬件幀頭的長度必須填在dev->hard_header_len,這樣協議層回在數據以前保留好
硬件幀頭的空間。這樣hard_header程序只要調用skb_push而後正確填入硬件幀頭就
能夠了。
在協議層調用hard_header時,傳送的參數包括(2.0.xx):數據的sk_buff,
device指針,protocol,目的地址(daddr),源地址(saddr),數據長度(len)。數據
長度不要使用sk_buff中的參數,由於調用hard_header時數據可能還沒徹底組織好。
saddr是NULL的話是使用缺省地址(default)。daddr是NULL代表協議層不知道硬件目
的地址。若是hard_header徹底填好了硬件幀頭,則返回添加的字節數。若是硬件幀
頭中的信息還不徹底(好比daddr爲NULL,可是幀頭中須要目的硬件地址。典型的情
況是以太網須要地址解析(arp)),則返回負字節數。hard_header返回負數的狀況
下,協議層會作進一步的build header的工做。目前Linux系統裏就是作arp
(若是hard_header返回正,dev->arp=1,代表不須要作arp,返回負,dev->arp=0,
作arp)。
對hard_header的調用在每一個協議層的處理程序裏。如ip_output。
2.2.7 地址解析(xarp)
有些網絡有硬件地址(好比Ethernet),而且在發送硬件幀時須要知道目的硬件
地址。這樣就須要上層協議地址(ip、ipx)和硬件地址的對應。這個對應是經過地址
解析完成的。須要作arp的的設備在發送以前會調用驅動程序的rebuild_header方
法。調用的主要參數包括指向硬件幀頭的指針,協議層地址。若是驅動程序可以解
析硬件地址,就返回1,若是不能,返回0。
對rebuild_header的調用在net/core/dev.c的do_dev_queue_xmit()裏。
2.2.8 參數設置和統計數據
在驅動程序裏還提供一些方法供系統對設備的參數進行設置和讀取信息。通常
只有超級用戶(root)權限才能對設備參數進行設置。設置方法有:
dev->set_mac_address()
當用戶調用ioctl類型爲SIOCSIFHWADDR時是要設置這個設備的mac地址。通常
對mac地址的設置沒有太大意義的。
dev->set_config()
------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
當用戶調用ioctl時類型爲SIOCSIFMAP時,系統會調用驅動程序的set_config
方法。用戶會傳遞一個ifmap結構包含須要的I/O、中斷等參數。
dev->do_ioctl()
若是用戶調用ioctl時類型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之間,系統
會調用驅動程序的這個方法。通常是設置設備的專用數據。
讀取信息也是經過ioctl調用進行。除次以外驅動程序還能夠提供一個
dev->get_stats方法,返回一個enet_statistics結構,包含發送接收的統計信息。
ioctl的處理在net/core/dev.c的dev_ioctl()和dev_ifsioc()裏。
2.3 網絡驅動程序中用到的數據結構
最重要的是網絡設備的數據結構。定義在include/linux/netdevice.h裏。它
的註釋已經足夠詳盡。
struct device
{
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char *name;
/* I/O specific fields - FIXME: Merge these and struct ifmap into one */
unsigned long rmem_end; /* shmem "recv" end */
unsigned long rmem_start; /* shmem "recv" start */
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned char irq; /* device IRQ number */
/* Low-level status flags. */
volatile unsigned char start, /* start an operation */
interrupt; /* interrupt arrived */
/* 在處理中斷時interrupt設爲1,處理完清0。 */
unsigned long tbusy; /* transmitter busy must be
long
for
bitops */
struct device *next;
/* The device initialization function. Called only once. */
/* 指向驅動程序的初始化方法。 */
int (*init)(struct device *dev);
/* Some hardware also needs these fields, but they are not part of the
usual set specified in Space.c. */
/* 一些硬件能夠在一塊板上支持多個接口,可能用到if_port。 */
unsigned char if_port; /* Selectable AUI, TP,..*/
unsigned char dma; /* DMA channel */
struct enet_statistics* (*get_stats)(struct device *dev);
/*
* This marks the end of the "visible" part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will).
*/
/* These may be needed for future network-power-down code. */
/* trans_start記錄最後一次成功發送的時間。能夠用來肯定硬件是否工做正常。*/
unsigned long trans_start; /* Time (in jiffies) of last Tx */
unsigned long last_rx; /* Time of last Rx */
/* flags裏面有不少內容,定義在include/linux/if.h裏。*/
unsigned short flags; /* interface flags (a la BSD) */
unsigned short family; /* address family ID (AF_INET) */
unsigned short metric; /* routing metric (not used) */
unsigned short mtu; /* interface MTU value */
/* type標明物理硬件的類型。主要說明硬件是否須要arp。定義在
include/linux/if_arp.h裏。 */
unsigned short type; /* interface hardware type */
/* 上層協議層根據hard_header_len在發送數據緩衝區前面預留硬件幀頭空間。*/
unsigned short hard_header_len; /* hardware hdr length */
/* priv指向驅動程序本身定義的一些參數。*/
void *priv; /* pointer to private data */
/* Interface address info. */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */
unsigned char pad; /* make dev_addr ali
gned
to 8
bytes */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */
unsigned char addr_len; /* hardware address length */
unsigned long pa_addr; /* protocol address */
unsigned long pa_brdaddr; /* protocol broadcast addr */
unsigned long pa_dstaddr; /* protocol P-P other side addr */
unsigned long pa_mask; /* protocol netmask */
unsigned short pa_alen; /* protocol address length */
struct dev_mc_list *mc_list; /* Multicast mac addresses */
int mc_count; /* Number of installed mcasts */
struct ip_mc_list *ip_mc_list; /* IP multicast filter chain */
__u32 tx_queue_len; /* Max frames per queue allowed */
------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
/* For load balancing driver pair support */
unsigned long pkt_queue; /* Packets queued */
struct device *slave; /* Slave device */
struct net_alias_info *alias_info; /* main dev alias info */
struct net_alias *my_alias; /* alias devs */
/* Pointer to the interface buffers. */
struct sk_buff_head buffs[DEV_NUMBUFFS];
/* Pointers to interface service routines. */
int (*open)(struct device *dev);
int (*stop)(struct device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,
struct device *dev);
int (*hard_header) (struct sk_buff *skb,
struct device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
int (*rebuild_header)(void *eth, struct device *dev,
unsigned long raddr, struct sk_buff *skb);
#define HAVE_MULTICAST
void (*set_multicast_list)(struct device *dev);
#define HAVE_SET_MAC_ADDR
int (*set_mac_address)(struct device *dev, void *addr)
;
#define HAVE_PRIVATE_IOCTL
int (*do_ioctl)(struct device *dev, struct ifreq *ifr,
int
cmd);
#define HAVE_SET_CONFIG
int (*set_config)(struct device *dev, struct ifmap *ma
p);
#define HAVE_HEADER_CACHE
void (*header_cache_bind)(struct hh_cache **hhp, struct
dev
ce
*dev, unsigned short htype, __u32 daddr);
void (*header_cache_update)(struct hh_cache *hh, struct
dev
ce
*dev, unsigned char * haddr);
#define HAVE_CHANGE_MTU
int (*change_mtu)(struct device *dev, int new_mtu);
struct iw_statistics* (*get_wireless_stats)(struct device *dev);
};
2.4 經常使用的系統支持
2.4.1 內存申請和釋放
include/linux/kernel.h裏聲明瞭kmalloc()和kfree()。用於在內核模式下申
請和釋放內存。
void *kmalloc(unsigned int len,int priority);
void kfree(void *__ptr);
與用戶模式下的malloc()不一樣,kmalloc()申請空間有大小限制。長度是2的整
次方。能夠申請的最大長度也有限制。另外kmalloc()有priority參數,一般使用
時能夠爲GFP_KERNEL,若是在中斷裏調用用GFP_ATOMIC參數,由於使用GFP_KERNEL
則調用者可能進入sleep狀態,在處理中斷時是不容許的。
kfree()釋放的內存必須是kmalloc()申請的。若是知道內存的大小,也能夠用
kfree_s()釋放。
2.4.2 request_irq()、free_irq()
這是驅動程序申請中斷和釋放中斷的調用。在include/linux/sched.h裏聲明。
request_irq()調用的定義:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs
),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq是要申請的硬件中斷號。在Intel平臺,範圍0--15。handler是向系統登記
的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用這個函數,傳入的參
數包括硬件中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞
給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,
標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)仍是慢速處理程序(不設置
SA_INTERRUPT)。快速處理程序被調用時屏蔽全部中斷。慢速處理程序不屏蔽。還有
一個SA_SHIRQ屬性,設置了之後運行多個設備共享中斷。dev_id在中斷共享時會用
到。通常設置爲這個設備的device結構自己或者NULL。中斷處理程序能夠用dev_id
找到相應的控制這個中斷的設備,或者用irq2dev_map找到中斷對應的設備。
void free_irq(unsigned int irq,void *dev_id);
2.4.3 時鐘
時鐘的處理相似中斷,也是登記一個時間處理函數,在預約的時間事後,系統
會調用這個函數。在include/linux/timer.h裏聲明。
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
void init_timer(struct timer_list * timer);
使用時鐘,先聲明一個timer_list結構,調用init_timer對它進行初始化。
time_list結構裏expires是標明這個時鐘的週期,單位採用jiffies的單位。
jiffies是Linux一個全局變量,表明時間。它的單位隨硬件平臺的不一樣而不一樣。
系統裏定義了一個常數HZ,表明每秒種最小時間間隔的數目。這樣jiffies的單位
就是1/HZ。Intel平臺jiffies的單位是1/100秒,這就是系統所能分辨的最小時間
間隔了。因此expires/HZ就是以秒爲單位的這個時鐘的週期。
function就是時間到了之後的回調函數,它的參數就是timer_list中的data。
data這個參數在初始化時鐘的時候賦值,通常賦給它設備的device結構指針。
在預置時間到系統調用function,同時系統把這個time_list從定時隊列裏清
除。因此若是須要一直使用定時函數,要在function裏再次調用add_timer()把這
------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
個timer_list加進定時隊列。
2.4.4 I/O
I/O端口的存取使用:
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);
在include/adm/io.h裏定義。
inb_p()、outb_p()與inb()、outb_p()的不一樣在於前者在存取I/O時有等待
(pause)一適應慢速的I/O設備。
爲了防止存取I/O時發生衝突,Linux提供對端口使用狀況的控制。在使用端口
以前,能夠檢查須要的I/O是否正在被使用,若是沒有,則把端口標記爲正在使用,
使用完後再釋放。系統提供如下幾個函數作這些工做。
int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent,const char *n
ame)
void release_region(unsigned int from, unsigned int extent);
其中的參數from表示用到的I/O端口的起始地址,extent標明從from開始的端
口數目。name爲設備名稱。
2.4.5 中斷打開關閉
系統提供給驅動程序開放和關閉響應中斷的能力。是在include/asm/system.h
中的兩個定義。
#define cli() __asm__ __volatile__ ("cli"::)
#define sti() __asm__ __volatile__ ("sti"::)
2.4.6 打印信息
相似普通程序裏的printf(),驅動程序要輸出信息使用printk()。在include
/linux/kernel.h裏聲明。
int printk(const char* fmt, ...);
其中fmt是格式化字符串。...是參數。都是和printf()格式同樣的。
2.4.7 註冊驅動程序
若是使用模塊(module)方式加載驅動程序,須要在模塊初始化時把設備註冊
到系統設備表裏去。再也不使用時,把設備從系統中卸除。定義在drivers/net/net_init
.h
裏的兩個函數完成這個工做。
int register_netdev(struct device *dev);
void unregister_netdev(struct device *dev);
dev就是要註冊進系統的設備結構指針。在register_netdev()時,dev結構一
般填寫前面11項,即到init,後面的暫時能夠不用初始化。最重要的是name指針和
init方法。name指針空(NULL)或者內容爲''或者name[0]爲空格(space),則系統
把你的設備作爲以太網設備處理。以太網設備有統一的命名格式,ethX。對以太網
這麼特別對待大概和Linux的歷史有關。
init方法必定要提供,register_netdev()會調用這個方法讓你對硬件檢測和
設置。
register_netdev()返回0表示成功,非0不成功。
2.4.8 sk_buff
Linux網絡各層之間的數據傳送都是經過sk_buff。sk_buff提供一套管理緩衝
區的方法,是Linux系統網絡高效運行的關鍵。每一個sk_buff包括一些控制方法和一
塊數據緩衝區。控制方法按功能分爲兩種類型。一種是控制整個buffer鏈的方法,
另外一種是控制數據緩衝區的方法。sk_buff組織成雙向鏈表的形式,根據網絡應用
的特色,對鏈表的操做主要是刪除鏈表頭的元素和添加到鏈表尾。sk_buff的控制
方法都很短小以儘可能減小系統負荷。(translated from article written by Alan
Cox)
經常使用的方法包括:
.alloc_skb() 申請一個sk_buff並對它初始化。返回就是申請到的sk_buff。
.dev_alloc_skb()相似alloc_skb,在申請好緩衝區後,保留16字節的幀頭空
間。主要用在Ethernet驅動程序。
.kfree_skb() 釋放一個sk_buff。
.skb_clone() 複製一個sk_buff,但不復制數據部分。
.skb_copy()徹底複製一個sk_buff。
.skb_dequeue() 從一個sk_buff鏈表裏取出第一個元素。返回取出的sk_buff,
若是鏈表空則返回NULL。這是經常使用的一個操做。
.skb_queue_head() 在一個sk_buff鏈表頭放入一個元素。
.skb_queue_tail() 在一個sk_buff鏈表尾放入一個元素。這也是經常使用的一個
操做。網絡數據的處理主要是對一個先進先出隊列的管理,skb_queue_tail()
和skb_dequeue()完成這個工做。
.skb_insert() 在鏈表的某個元素前插入一個元素。
.skb_append() 在鏈表的某個元素後插入一個元素。一些協議(如TCP)對沒按
順序到達的數據進行重組時用到skb_insert()和skb_append()。
.skb_reserve() 在一個申請好的sk_buff的緩衝區裏保留一塊空間。這個空間
通常是用作下一層協議的頭空間的。
.skb_put() 在一個申請好的sk_buff的緩衝區裏爲數據保留一塊空間。在
alloc_skb之後,申請到的sk_buff的緩衝區都是處於空(free)狀態,有一個
tail指針指向free空間,實際上開始時tail就指向緩衝區頭。skb_reserve()
在free空間裏申請協議頭空間,skb_put()申請數據空間。見下面的圖。
.skb_push() 把sk_buff緩衝區裏數據空間往前移。即把Head room中的空間移
一部分到Data area。
.skb_pull() 把sk_buff緩衝區裏Data area中的空間移一部分到Head room中。
--------------------------------------------------
| Tail room(free) |
--------------------------------------------------
After alloc_skb()
--------------------------------------------------
| Head room | Tail room(free) |
--------------------------------------------------
After skb_reserve()
--------------------------------------------------
| Head room | Data area | Tail room(free) |
--------------------------------------------------
After skb_put()
--------------------------------------------------
|Head| skb_ | Data | Tail room(free) |
|room| push | | |
| | Data area | |
--------------------------------------------------
After skb_push()
--------------------------------------------------
| Head | skb_ | Data area | Tail room(free) |
| | pull | | |
| Head room | | |
--------------------------------------------------
After skb_pull()
------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------
三.編寫Linux網絡驅動程序中須要注意的問題
3.1 中斷共享
Linux系統運行幾個設備共享同一個中斷。須要共享的話,在申請的時候指明
共享方式。系統提供的request_irq()調用的定義:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs
),
unsigned long irqflags,
const char * devname,
void *dev_id);
若是共享中斷,irqflags設置SA_SHIRQ屬性,這樣就容許別的設備申請同一個
中斷。須要注意全部用到這個中斷的設備在調用request_irq()都必須設置這個屬
性。系統在回調每一箇中斷處理程序時,能夠用dev_id這個參數找到相應的設備。一
般dev_id就設爲device結構自己。系統處理共享中斷是用各自的dev_id參數依次調
用每個中斷處理程序。
3.2 硬件發送忙時的處理
主CPU的處理能力通常比網絡發送要快,因此常常會遇到系統有數據要發,但
上一包數據網絡設備還沒發送完。由於在Linux裏網絡設備驅動程序通常不作數據
緩存,不能發送的數據都是通知系統發送不成功,因此必需要有一個機制在硬件不
忙時及時通知系統接着發送下面的數據。
通常對發送忙的處理在前面設備的發送方法(hard_start_xmit)裏已經描述過,
即若是發送忙,置tbusy爲1。處理完髮送數據後,在發送結束中斷裏清tbusy,同
時用mark_bh()調用通知系統繼續發送。
但在具體實現個人驅動程序時發現,這樣的處理系統好象並不能及時地知道硬
件已經空閒了,即在mark_bh()之後,系統要等一段時間纔會接着發送。形成發送
效率很低。2M線路只有10%不到的使用率。內核版本爲2.0.35。
我最後的實現是不把tbusy置1,讓系統始終認爲硬件空閒,可是報告發送不成
功。系統會一直嘗試重發。這樣處理就運行正常了。可是遍循內核源碼中的網絡驅
動程序,彷佛沒有這樣處理的。不知道癥結在哪裏。
3.3 流量控制(flow control)
網絡數據的發送和接收都須要流量控制。這些控制是在系統裏實現的,不須要
驅動程序作工做。每一個設備數據結構裏都有一個參數dev->tx_queue_len,這個參數
標明發送時最多緩存的數據包。在Linux系統裏以太網設備(10/100Mbps)
tx_queue_len通常設置爲100,串行線路(異步串口)爲10。實際上若是看源碼能夠
知道,設置了dev->tx_queue_len並非爲緩存這些數據申請了空間。這個參數只是
在收到協議層的數據包時判斷髮送隊列裏的數據是否是到了tx_queue_len的限度,
以決定這一包數據加不加進發送隊列。發送時另外一個方面的流控是更高層協議的發
送窗口(TCP協議裏就有發送窗口)。達到了窗口大小,高層協議就不會再發送數據。
接收流控也分兩個層次。netif_rx()緩存的數據包有限制。另外高層協議也會
有一個最大的等待處理的數據量。
發送和接收流控處理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()
中。
3.4 調試
不少Linux的驅動程序都是編譯進內核的,造成一個大的內核文件。但對調試
來講,這是至關麻煩的。調試驅動程序能夠用module方式加載。支持模塊方式的
驅動程序必須提供兩個函數:int init_module(void)和void cleanup_module(void)。
init_module()在加載此模塊時調用,在這個函數裏能夠register_netdev()註冊
設備。init_module()返回0表示成功,返回負表示失敗。cleanup_module()在驅動
程序被卸載時調用,清除佔用的資源,調用unregister_netdev()。
模塊能夠動態地加載、卸載。在2.0.xx版本里,還有kerneld自動加載模塊,
可是2.2.xx中已經取消了kerneld。手工加載使用insmod命令,卸載用rmmod命令,
看內核中的模塊用lsmod命令。
編譯驅動程序用gcc,主要命令行參數-DKERNEL -DMODULE。而且做爲模塊加載
的驅動程序,只編譯成obj形式(加-c參數)。編譯好的目標文件放在/lib/modules
/2.x.xx/misc下,在啓動文件裏用insmod加載。
四.進一步的閱讀
Linux程序設計資料能夠從網上得到。這就是開放源代碼的好處。而且沒有什
麼「未公開的祕密」。我編寫驅動程序時參閱的主要資料包括:
Linux內核源代碼
<> by Michael K. Johnson
<> by Ori Pomerantz
<> by olly in BBS水木清華站
能夠選擇一個模板做爲開始,內核源代碼裏有一個網絡驅動程序的模板,
drivers/net/skeleton.c。裏面包含了驅動程序的基本內容。但這個模板是以以太
網設備爲對象的,以太網的處理在Linux系統裏有特殊「待遇」,因此若是不是以
太網設備,有些細節上要注意,主要在初始化程序裏。
最後,多參照別人寫的程序,聽聽其餘開發者的經驗之談大概是最有效的幫助
了。
Linux的源碼裏,網絡接口的實現部份是很是值得一讀的,經過讀源碼,不只對網絡協議會有更深的瞭解,也有助於在網絡編程的時候,對應用函數有更精確的瞭解和把握。
本文把重點放在網絡接口程序的整體結構上,但願能做爲讀源碼時一些指導性的文字。
本文以Linux2.4.16內核做爲講解的對象,內核源碼能夠在http://www.kernel.org上下載。我讀源碼時參考的是http://lxr.linux.no/這個交差參考的網站,我我的認爲是一個很好的工具,若是有條件最好上這個網站。
二.網絡接口程序的結構
Linux的網絡接口分爲四部份:網絡設備接口部份,網絡接口核心部份,網絡協議族部份,以及網絡接口socket層。
網絡設備接口部份主要負責從物理介質接收和發送數據。實現的文件在linu/driver/net目錄下面。
網絡接口核心部份是整個網絡接口的關鍵部位,它爲網絡協議提供統一的發送接口,屏蔽各類各樣的物理介質,同時有負責把來自下層的包向合適的協議配送。它 是網絡接口的中樞部份。它的主要實現文件在linux/net/core目錄下,其中linux/net/core/dev.c爲主要管理文件。
網絡協議族部份是各類具體協議實現的部份。Linux支持TCP/IP,IPX,X.25,AppleTalk等的協議,各類具體協議實現的源碼在 linux/net/目錄下相應的名稱。在這裏主要討論TCP/IP(IPv4)協議,實現的源碼在linux/net/ipv4,其中linux /net/ipv4/af_inet.c是主要的管理文件。
網絡接口Socket層爲用戶提供的網絡服務的編程接口。主要的源碼在linux/net/socket.c
三.網絡設備接口部份
物理層上有許多不一樣類型的網絡接口設備, 在文件include/linux/if_arp.h的28行裏定義了ARP能處理的各類的物理設備的標誌符。網絡設備接口要負責具體物理介質的控制,從 物理介質接收以及發送數據,並對物理介質進行諸如最大數據包之類的各類設置。這裏咱們以比較簡單的3Com3c501 太網卡的驅動程序爲例,大概講一下這層的工做原理。源碼在Linux/drivers/net/3c501.c。
咱們從直覺上來考慮,一個網卡固然最主要的是完成數據的接收和發送,在這裏咱們來看看接收和發送的過程是怎麼樣的。
發送相對來講比較簡單,在Linux/drivers/net/3c501.c的行475 開始的el_start_xmit()這個函數就是實際向3Com3c501以太網卡發送數據的函數,具體的發送工做不外乎是對一些寄存器的讀寫,源碼的 註釋很清楚,你們能夠看看。
接收的工做相對來講比較複雜。一般來講,一個新的包到了,或者一個包發送完成了,都會產生一箇中斷。 Linux/drivers/net/3c501.c的572開始el_interrupt()的函數裏面,前半部份處理的是包發送完之後的彙報,後半部 份處理的是一個新的包來的,就是說接收到了新的數據。el_interrupt()函數並無對新的包進行太多的處理,就交給了接收處理函數 el_receive()。el_receive()首先檢查接收的包是否正確,若是是一個「好」包就會爲包分配一個緩衝結構 (dev_alloc_skb()),這樣驅動程序對包的接收工做就完成了,經過調用上層的函數netif_rx()(net/core /dev.c1214行) ,把包交給上層。
如今驅動程序有了發送和接收數據的功能了,驅動程序怎麼樣和上層創建聯繫呢?就是說接收到包之後怎麼送給上層,以及上層怎麼能調用驅動程序的發送函數呢?
由下往上的關係,是經過驅動程序調用上層的netif_rx()(net/core/dev.c 1214行)函數實現的,驅動程序經過這個函數把接到的數據交給上層,請注意全部的網卡驅動程序都須要調用這個函數的,這是網絡接口核心層和網絡接口設備聯繫的橋樑。
由上往下的關係就複雜點。網絡接口核心層須要知道有多少網絡設備能夠用,每一個設備的函數的入口地址等都要知道。網絡接口核心層會大聲喊,「嘿,有多少設 備能夠幫我發送數據包?能發送的請給我排成一隊!」。這一隊就由dev_base開始,指針structnet_device *dev_base (Linux/include/linux/netdevice.h 436行)就是保存了網絡接口核心層所知道的全部設備。對於網絡接口核心層來講,全部的設備都是一個net_device結構,它在 include/linux/netdevice.h,line 233裏被定義,這是從網絡接口核心層的角度看到的一個抽象的設備,咱們來看看網絡接口核心層的角度看到的網絡設備具備的功能:
struct net_device {
………
open()
stop()
hard_start_xmit()
hard_header()
rebuild_header()
set_mac_address()
do_ioctl()
set_config()
hard_header_cache()
header_cache_update()
change_mtu()
tx_timeout()
hard_header_parse()
neigh_setup()
accept_fastpath()
………
}
若是網絡接口核心層須要由下層發送數據的時候,在dev_base找到設備之後,就直接調dev->hard_start_xmit()的這個函數來讓下層發數據包。
驅動程序要讓網絡接口核心層知道本身的存在,固然要加入dev_base所指向的指針鏈,而後把本身的函數以及各類參數和net_device裏的相應 的域對應起來。加入dev_base所指向的指針鏈是經過函數register_netdev(&dev_3c50) (linux/drivers/net/net_init.c, line 532)
創建的。而把本身的函數以和net_device裏的相應的域及各類參數關係的創建是在el1_probe1()(Linux/drivers/net/3c501.c)裏進行的:
el1_probe1(){
………
dev->open = &el_open;
dev->hard_start_xmit = &el_start_xmit;
dev->tx_timeout = &el_timeout;
dev->watchdog_timeo = HZ;
dev->stop = &el1_close;
dev->get_stats = &el1_get_stats;
dev->set_multicast_list = &set_multicast_list;
………
ether_setup(dev);
………
}
進一步的對應工做在ether_setup(dev) (drivers/net/net_init.c, line 405 )裏進行。咱們注意到dev->hard_start_xmit =&el_start_xmit,這樣發送函數的關係就創建了,上層只知道調用dev->hard_start_xmit這個來發送數據, 上面的語句就把驅動程序實際的發送函數告訴了上層。
四.網絡接口核心部分
剛纔談論了驅動程序怎麼和網絡接口核心層銜接的。網絡接口核心層知道驅動程序以及驅動程序的函數的入口是經過*dev_base指向的設備鏈的,而下層是經過調用這一層的函數netif_rx()(net/core/dev.c
1214行) 把數據傳遞個這一層的。
網絡接口核心層的上層是具體的網絡協議,下層是驅動程序,咱們以及解決了下層的關係,但和上層的關係沒有解決。先來討論一下網絡接口核心層和網絡協議族部份的關係,這種關係不外乎也是接收和發送的關係。
網絡協議,例如IP,ARP等的協議要發送數據包的時候會把數據包傳遞給這層,那麼這種傳遞是經過什麼函數來發生的呢?網絡接口核心層經過 dev_queue_xmit()(net/core/dev.c,line975)這個函數向上層提供統一的發送接口,也就是說不管是IP,仍是ARP 協議,經過這個函數把要發送的數據傳遞給這一層,想發送數據的時候就調用這個函數就能夠了。dev_queue_xmit()作的工做最後會落實到 dev->hard_start_xmit(),而dev->hard_start_xmit()會調用實際的驅動程序來完成發送的任務。例 如上面的例子中,調用dev->hard_start_xmit()實際就是調用了el_start_xmit()。
如今討論 接收的狀況。網絡接口核心層經過的函數netif_rx()(net/core/dev.c 1214行)接收了上層發送來的數據,這時候固然要把數據包往上層派送。全部的協議族的下層協議都須要接收數據,TCP/IP的IP協議和ARP協 議,SPX/IPX的IPX協議,AppleTalk的DDP和AARP協議等都須要直接從網絡接口核心層接收數據,網絡接口核心層接收數據是如何把包發 給這些協議的呢?這時的情形和於下層的關係很類似,網絡接口核心層的下面可能有許多的網卡的驅動程序,爲了知道怎麼向這些驅動程序發數據,前面以及講過 時,是經過*dev_base這個指針指向的鏈解決的,如今解決和上層的關係是經過static struct packet_ptype_base[16]( net/core/dev.c line 164)這個數組解決的。這個數組包含了須要接收數據包的協議,以及它們的接收函數的入口。
從上面能夠看到,IP協議接收數據是經過ip_rcv()函數的,而ARP協議是經過arp_rcv()的,網絡接口核心層只要經過這個數組就能夠把數據交給上層函數了。
若是有協議想把本身添加到這個數組,是經過dev_add_pack()(net/core/dev.c, line233)函數,從數組刪除是經過dev_remove_pack()函數的。Ip層的註冊是在初始化函數進行的void __init ip_init(void) (net/ipv4/ip_output.c, line 1003)
{
………
dev_add_pack(&ip_packet_type);
………
}
從新到回咱們關於接收的討論,網絡接口核心層經過的函數netif_rx()(net/core/dev.c 1214行)接收了上層發送來的數據,看看這個函數作了些什麼。
因爲如今仍是在中斷的服務裏面,全部並不可以處理太多的東西,剩下的東西就經過cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ)
交給軟中斷處理, 從open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL)能夠知道NET_RX_SOFTIRQ軟中斷的處理函數是net_rx_action()(net/core/dev.c, line 1419),net_rx_action()根據數據包的協議類型在數組ptype_base[16]裏找到相應的協議,並從中知道了接收的處理函數,然 後把數據包交給處理函數,這樣就交給了上層處理,實際調用處理函數是經過net_rx_action()裏的pt_prev->func()這一 句。例如若是數據包是IP協議的話,ptype_base[ETH_P_IP]->func()(ip_rcv()),這樣就把數據包交給了IP協 議。
五.網絡協議部分
協議層是真正實現是在這一層。在linux/include/linux/socket.h裏面,Linux的BSD
Socket 定義了多至32支持的協議族,其中PF_INET就是咱們最熟悉的TCP/IP協議族(IPv4, 如下沒有特別聲明都指IPv4)。以這個協議族爲例,看看這層是怎麼工做的。實現TCP/IP協議族的主要文件在inux/net/ipv4/目錄下 面,Linux/net/ipv4/af_inet.c爲主要的管理文件。
在Linux2.4.16裏面,實現了TCP/IP協議族裏面的的IGMP,TCP,UDP,ICMP,ARP,IP。咱們先討論一下這些協議之間的關係。IP和ARP協議是須要直接和網絡設備接口打交道的協議,也就是須要從網絡核心模塊(core)
接 收數據和發送數據的。而其它協議TCP,UDP,IGMP,ICMP是須要直接利用IP協議的,須要從IP協議接收數據,以及利用IP協議發送數據,同時 還要向上層Socket層提供直接的調用接口。能夠看到IP層是一個核心的協議,向下須要和下層打交道,又要向上層提供因此的傳輸和接收的服務。
先來看看IP協議層。網絡核心模塊(core) 若是接收到IP層的數據,經過ptype_base[ETH_P_IP] 數組的IP層的項指向的IP協議的ip_packet_type->ip_rcv()函數把數據包傳遞給IP層,也就是說IP層經過這個函數 ip_rcv()(linux/net/ipv4/ip_input.c)接收數據的。ip_rcv()這個函數只對IP數據保作了一些checksum 的檢查工做,若是包是正確的就把包交給了下一個處理函數ip_rcv_finish()(注意調用是經過NF_HOOK這個宏實現的)。現 在,ip_rcv_finish()這個函數真正要完成一些IP層的工做了。IP層要作的主要工做就是路由,要決定把數據包往那裏送。路由的工做是經過函 數ip_route_input()(/linux/net/ipv4/route.c,line 1622)實現的。對於進來的包可能的路由有這些:
屬於本地的數據(便是須要傳遞給TCP,UDP,IGMP這些上層協議的) ;
須要要轉發的數據包(網關或者NAT服務器之類的);
不可能路由的數據包(地址信息有誤);
咱們如今關心的是若是數據是本地數據的時候怎麼處理。ip_route_input()調用ip_route_input_slow() (net/ipv4/route.c, line 1312),在ip_route_input_slow()裏面的1559行rth->u.dst.input=
ip_local_deliver,這就是判斷到IP包是本地的數據包,並把本地數據包處理函數的地址返回。好了,路由工做完成了,返回到 ip_rcv_finish()。ip_rcv_finish()最後調用拉skb->dst->input(skb),從上面能夠看到,這 其實就是調用了ip_local_deliver()函數,而ip_local_deliver(),接着就調用了 ip_local_deliver_finish()。如今真正到了往上層傳遞數據包的時候了。
如今的情形和網絡核心模塊層 (core) 往上層傳遞數據包的情形很是類似,怎麼從多個協議選擇合適的協議,而且往這個協議傳遞數據呢?網絡網絡核心模塊層(core) 經過一個數組ptype_base[16]保存了註冊了的全部能夠接收數據的協議,一樣網絡協議層也定義了這樣一個數組struct net_protocol*inet_protos[MAX_INET_PROTOS](/linux/net/ipv4 /protocol.c#L102),它保存了全部須要從IP協議層接收數據的上層協議(IGMP,TCP,UDP,ICMP)的接收處理函數的地址。我 們來看看TCP協議的數據結構是怎麼樣的:
linux/net/ipv4/protocol.c line67
static struct inet_protocol tcp_protocol = {
handler: tcp_v4_rcv,// 接收數據的函數
err_handler: tcp_v4_err,// 出錯處理的函數
next: IPPROTO_PREVIOUS,
protocol: IPPROTO_TCP,
name: "TCP"
};
第一項就是咱們最關心的了,IP層能夠經過這個函數把數據包往TCP層傳的。在linux/net/ipv4/protocol.c的上部,咱們能夠看到其它協議層的處理函數是igmp_rcv(),
udp_rcv(), icmp_rcv()。一樣在linux/net/ipv4/protocol.c,往數組inet_protos[MAX_INET_PROTOS] 裏面添加協議是經過函數inet_add_protocol()實現的,刪除協議是經過 inet_del_protocol()實現的。inet_protos[MAX_INET_PROTOS]初始化的過程在linux/net/ipv4 /af_inet.c inet_init()初始化函數裏面。
inet_init(){
……
printk(KERN_INFO "IP Protocols: ");
for (p = inet_protocol_base; p != NULL;) {
struct inet_protocol *tmp = (struct inet_protocol *) p->next;
inet_add_protocol(p);// 添加協議
printk("%s%s",p->name,tmp?", ":"\n");
p = tmp;
………
}
若是你在Linux啓動的時候有留意啓動的信息, 或者在linux下打命令dmesg就能夠看到這一段程序輸出的信息:
IP Protocols: ICMP,UDP,TCP,IGMP也就是說如今數組inet_protos[]裏面有了ICMP,UDP,TCP,IGMP四個協議的inet_protocol數據結構,數據結構包含了它們接收數據的處理函數。
Linux 2.4.16在linux/include/linux/socket.h裏定義了32種支持的BSDsocket協議,常見的有TCP/IP,IPX /SPX,X.25等,而每種協議還提供不一樣的服務,例如TCP/IP協議經過TCP協議支持鏈接服務,而經過UDP協議支持無鏈接服務,面對這麼多的協 議,向用戶提供統一的接口是必要的,這種統一是經過socket來進行的。
在BSD socket網絡編程的模式下,利用一系列的統一的函數來利用通訊的服務。例如一個典型的利用TCP協議通訊程序是這樣:
sock_descriptor = socket(AF_INET,SOCK_STREAM,0);
connect(sock_descriptor, 地址,) ;
send(sock_descriptor,」hello world」);
recv(sock_descriptor,buffer,1024,0);
第一個函數指定了協議Inet協議,即TCP/IP協議,同時是利用面向鏈接的服務,這樣就對應到TCP協議,之後的操做就是利用socket的標準函數進行的。
從上面咱們能夠看到兩個問題,首先socket層須要根據用戶指定的協議族(上面是AF_INET)
從下面32種協議中選擇一種協議來完成用戶的要求,當協議族肯定之後,還要把特定的服務映射到協議族下的具體協議,例如當用戶指定的是面向鏈接的服務時,Inet協議族會映射到TCP協議。
從多個協議中選擇用戶指定的協議,並把具體的出理交給選中的協議,這和一塊兒網絡核心層向上和向下銜接的問題本質上是同樣的,因此解決的方法也是同樣的, 一樣仍是經過數組。在Linux/net/socket.c定義了這個數組staticstruct net_proto_family *net_families[NPROTO] 。數組的元素已經肯定了,net_families[2] 是TCP/IP協議,net_families[3]
是X.25協議,具體那一項對應什麼協議,在include/linux /socket.h有定義。可是每一項的數據結構net_proto_family的ops是空的,也就是具體協議處理函數的地址是不知道的。協議的處理 函數和ops創建聯繫是經過sock_register()(Linux/net/socket.c)這個函數創建的,例如TCP/IP協議的是這樣創建 關係的:
int __init inet_init(void) (net/ipv4/af_inet.c)
{
(void) sock_register(&inet_family_ops);
}
只要給出AF_INET(在宏裏定義是2),就能夠找到net_failies[2] 裏面的處理函數了。
協議的映射完成了,如今要進行服務的映射了。上層固然不可能知道下層的什麼協議能對應特定的服務,因此這種映射天然由協議族本身完成。在TCP/IP協議族裏,這種映射是經過struct
list_head inetsw[SOCK_MAX]( net/ipv4/af_inet.c)
這個數組進行映射的,在談論這個數組以前咱們來看另一個數組inetsw_array[](net/ipv4/af_inet.c)
static struct inet_protosw inetsw_array[] =
{
{
type: SOCK_STREAM,
protocol: IPPROTO_TCP,
prot: &tcp_prot,
ops: &inet_stream_ops,
capability: -1,
no_check: 0,
flags: INET_PROTOSW_PERMANENT,
},
{
type: SOCK_DGRAM,
protocol: IPPROTO_UDP,
prot: &udp_prot,
ops: &inet_dgram_ops,
capability: -1,
no_check: UDP_CSUM_DEFAULT,
flags: INET_PROTOSW_PERMANENT,
},
{
type: SOCK_RAW,
protocol: IPPROTO_IP, /* wild card */
prot: &raw_prot,
ops: &inet_dgram_ops,
capability: CAP_NET_RAW,
no_check: UDP_CSUM_DEFAULT,
flags: INET_PROTOSW_REUSE,
}
};
咱們看到,SOCK_STREAM映射到了TCP協議,SOCK_DGRAM映射到了UDP協議,SOCK_RAW映射到了IP協議。如今只要把 inetsw_array裏的三項添加到數組inetsw[SOCK_MAX]就能夠了,添加是經過函數inet_register_protosw() 實現的。在inet_init()
(net/ipv4/af_inet.c) 裏完成了這些工做。
還有一個須要映射的就是socket其它諸如accept,send(),
connect(),release(),bind()等的操做函數是怎麼映射的呢?咱們來看一下上面的數組的TCP的項
{
type: SOCK_STREAM,
protocol: IPPROTO_TCP,
prot: &tcp_prot,
ops: &inet_stream_ops,
capability: -1,
no_check: 0,
flags: INET_PROTOSW_PERMANENT,
},
咱們看到這種映射是經過ops,和prot來映射的,咱們再來看看 tcp_prot這一項:
struct proto tcp_prot = {
name: "TCP",
close: tcp_close,
connect: tcp_v4_connect,
disconnect: tcp_disconnect,
accept: tcp_accept,
ioctl: tcp_ioctl,
init: tcp_v4_init_sock,
destroy: tcp_v4_destroy_sock,
shutdown: tcp_shutdown,
setsockopt: tcp_setsockopt,
getsockopt: tcp_getsockopt,
sendmsg: tcp_sendmsg,
recvmsg: tcp_recvmsg,
backlog_rcv: tcp_v4_do_rcv,
hash: tcp_v4_hash,
unhash: tcp_unhash,
get_port: tcp_v4_get_port,
};
因此的映射都已經完成了,用戶調用connect()函數,其實就是調用了tcp_v4_connect()函數,按照這幅圖,讀起源碼來就簡單了不少了。
六 Socket層
上一節把socket層大多數要討論的東西都談論了,如今只講講socket 層和用戶的銜接。
系統調用socket(),bind(),connect(),accept,send(),release()等是在Linux/net/socket.c裏面的實現的,系統調用實現的函數是相應的函數名加上sys_的前綴。
如今看看當用戶調用socket()這個函數,到底下面發生了什麼。
Socket(AF_INET,SOCK_STREAM,0)調用了sys_socket(),sys_socket()接着調用 socket_creat(),socket_creat()就要根據用戶提供的協議族參數在net_families[]裏尋找合適的協議族,若是協議 族沒有被安裝就要請求安裝該協議族的模塊,而後就調用該協議族的create()函數的處理句柄。根據參數AF_INET,inet_creat()就被 調用了,在inet_creat()根據服務類型在inetsw[SOCK_MAX]
選擇合適的協議,並把協議的操做集賦給socket就是了,根據SOCK_STREAM,TCP協議被選中,
inet_creat(){
answer=inetsw [用戶要求服務服務] ;
sock->ops = answer->ops;
sk->prot = answer->prot
}
到此爲止,上下都打通了,該是你們都源碼的時候了。網絡