深入剖析網(wǎng)絡(luò )發(fā)送過(guò)程 - 驅動(dòng)開(kāi)發(fā) - Linux論壇
在TCP協(xié)議上,當通過(guò)三方握手建立了連接之后,就進(jìn)入數據包的實(shí)質(zhì)發(fā)送階段,在本文中以send命令來(lái)闡述。當通過(guò)send將數據包發(fā)送之后,glibc函數庫會(huì )啟用另外一個(gè)其定義的別用名函數__libc_sendto(),該函數最后會(huì )間接執行到sendto系統調用:
inline_syscall##nr(name, args);// ##nr說(shuō)明是該系統調用帶有nr個(gè)args參數sendto系統調用的參數值是6,而name就是sendto
從上面的分析可以看出glibc將要執行的下面一條語(yǔ)句是
inline_syscall6(name,arg1,arg2,arg3,arg4,arg5,arg6)
在該函數中一段主要功能實(shí)現代碼如下:
__asm__ __volatile__ \
("callsys # %0 %1 <= %2 %3 %4 %5 %6 %7 %8" \
: inline_syscall_r0_out_constraint (_sc_0), \
"=r"(_sc_19), "=r"(_sc_16), "=r"(_sc_17), \
"=r"(_sc_18), "=r"(_sc_20), "=r"(_sc_21) \
: "0"(_sc_0), "2"(_sc_16), "3"(_sc_17), "4"(_sc_18), \
"1"(_sc_19), "5"(_sc_20), "6"(_sc_21) \
: inline_syscall_clobbers); \
_sc_ret = _sc_0, _sc_err = _sc_19;
該代碼采用了嵌入匯編(詳細介紹查閱嵌入匯編相關(guān)書(shū)籍),其中:
_sc_0=sendto;
_sc_19 --_sc_21分別是arg1—arg6;
inline_syscall_r0_out_constraint:功能相當于"=r",選用一個(gè)寄存器來(lái)存儲輸出變量。
"0"--"6"分別是%0--%6,代表_sc_0--_sc_21
接下來(lái)函數最終通過(guò)Linux中頂頂有名的INT 0X80陷入系統核心。具體的過(guò)程可以參考內核相關(guān)書(shū)籍。下面是一個(gè)兄弟對INT 0X80的簡(jiǎn)要介紹:
http://blog.chinaunix.net/u2/65427/showart_712571.html
在陷入系統內核以后,最終會(huì )調用系統所提供的系統調用函數sys_sendto(),該函數直接調用了__sock_sendmsg(),該函數對進(jìn)程做一個(gè)簡(jiǎn)單的權限檢查之后就觸發(fā)套接字(socket)中定義的虛擬sendmsg的函數,進(jìn)而進(jìn)入到下一層傳輸層處理。
Layer 4: 傳輸層(Transport Layer)
由上層的討論可知,系統觸發(fā)了sendmsg虛擬接口函數,其實(shí)就是傳輸層中的tcp_sendmsg或是udp_sendmsg,看你所使用的協(xié)議而定。本文介紹tcp_sendmsg().
該函數需要做如下工作:
1) 為sk_buff(后面簡(jiǎn)稱(chēng)skb)分配空間,該函數首先嘗試在套接字緩沖隊列中尋找空閑空間,如果找不到就使用tcp_alloc_pskb()為其重新分配空間。
2) 下面這步就會(huì )tcp_sendmsg函數的主要部分了,將數據拷貝到緩沖區。它分為如下兩種情況:
2.1)如果skb還有剩余空間的話(huà),就使用skb_add_data()來(lái)向skb尾部添加數據包。代碼如下:
if (skb_tailroom(skb) > 0) {
/* We have some space in skb head. Superb! */
if (copy > skb_tailroom(skb))
copy = skb_tailroom(skb);
if ((err = skb_add_data(skb, from, copy)) != 0)
goto do_fault;
}
2.2)如果skb沒(méi)有了可用空間,內核會(huì )使用TCP_PAGE宏來(lái)為發(fā)送的數據包分配一個(gè)高速緩存頁(yè)空間,當該頁(yè)被正確地分配后就調用Copy_from_user(to(page地址),from(usr空間),n)將用戶(hù)空間數據包復制到page所在的地址空間。
但是我們都知道數據包在協(xié)議層之間的傳輸是通過(guò)skb的,難道將數據包復制到這個(gè)新分配的page中,內核就可以去睡大覺(jué)了嗎?當然不是!接下來(lái)內核就要來(lái)處理這個(gè)問(wèn)題了,那么怎樣來(lái)處理呢?
此時(shí)就需要使用到skb中的另外一個(gè)數據區struct skb_shared_info[],但是該數據區在創(chuàng )建skb時(shí)是沒(méi)有為其分配空間的,也就是說(shuō)它開(kāi)始純粹就是個(gè)指針,而沒(méi)有具體的告訴它要指向什么地方。這時(shí)大家應該知道它可以指向什么地方了,對,就是page!在內核中對這種情況的具體是通過(guò)fill_page_desc(struct sk_buff *skb,int I,struct page *page,int off,int size)來(lái)實(shí)現的,代碼如下:
static inline void fill_page_desc(struct sk_buff *skb, int i,
struct page *page, int off, int size)
{
skb_frag_t *frag = &skb_shinfo(skb)->frags;
frag->page = page;
frag->page_offset = off;
frag->size = size;
skb_shinfo(skb)->nr_frags = i + 1;
}
這里需要注意的是struct skb_shared_info[]只能通過(guò)skb_shinfo來(lái)獲取,在該結構體中skb_flag_t類(lèi)型的flags就是具體指向page的數組。
2.3)至此skb數據包的裝載工作算是結束了,接下來(lái)就需要做一些后續工作,包括是否要分片,以及后來(lái)的TCP協(xié)議頭的添加。先看在tcp_sendmsg()中的最后一個(gè)重要函數tcp_push,它的調用格式如下:
static inline void tcp_push(struct sock *sk, struct tcp_opt *tp, int flags,
int mss_now, int nonagle)
細心的朋友會(huì )發(fā)現,在該函數中傳輸的竟然不是skb,而是一個(gè)名為sock的結構體,那這又是什么東東呢?個(gè)人理解是它在頂層協(xié)議層之間(例如:應用層和傳輸層之間)的傳輸起著(zhù)非常重要的作用,相當于溝通兩層之間的紐帶。再深入查找下該結構體的構成,我們很容易發(fā)現這樣一個(gè)結構體變量:struct sk_buff_head,有名稱(chēng)我們可以知道它是用來(lái)描述skb頭部信息的一個(gè)結構體,它指向了buffer的數據區。這下我們也明白了點(diǎn),這個(gè)結構體其實(shí)還充當了一個(gè)隊列作用,是用來(lái)存儲skb的數據區。協(xié)議層之間傳輸完之后,具體到該層處理時(shí)內核就會(huì )從sk_buff_head逐個(gè)中取出skb數據區來(lái)處理,例如添加協(xié)議頭等。
好了,tcp_sendmsg到此結束了它的使命了,下面將要需要的一個(gè)函數就是在tcp_push()中直接用到的一個(gè)函數:__tcp_push_pending_frames(),該函數又直接調用tcp_write_xmit()函數來(lái)進(jìn)一步對數據包處理,它包括一下兩步:
1) 檢查是否需要對數據包進(jìn)行分片,條件是只要skb中全部數據長(cháng)度大于當前路由負荷量就需要分片。
2) 采用skb_clone(skb,GFP_ATOMIC)為T(mén)CP_HEAD分配一個(gè)sk_buff空間,這里需要注意的是skb_clone分配空間的特點(diǎn),它首先是依照參數skb來(lái)來(lái)復制出一個(gè)新的sk_buff,新的skb和舊的skb共享數據變量緩存區,但是結構體緩沖區不是共享的,這似乎和copy on write機制有些相似。
3) 在分配了一個(gè)新的skb之后,內核就會(huì )執行tcp_transmit_skb().其實(shí)內核中是將2,3步合在一起的,如下:
tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC))
接下來(lái)就是tcp_transmit_skb函數的實(shí)現過(guò)程了。
1) 通過(guò)skb_push()在skb前面加入tcp協(xié)議頭信息。這包括序列號,源地址,目的地址,校驗和等。
2) 通過(guò)tcp_opt結構體(它是在該函數的開(kāi)始部分從sock結構體中獲得的)來(lái)訪(fǎng)問(wèn)tcp_func結構體中的.queue_xmit虛擬功能函數,在IPV4中是調用了ip_queue_xmit(),這樣就進(jìn)入了下一層——網(wǎng)絡(luò )層。
未貼完,完整版在附件中。
關(guān)于驅動(dòng)程序部分的聲明:
由于本人沒(méi)有實(shí)際開(kāi)發(fā)過(guò)驅動(dòng)程序,都是看了別人的驅動(dòng)程序之后的體會(huì )和總結,所以難免會(huì )有錯誤,請各位不嗇賜教!
接收過(guò)程待續。。。。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。