Linux內(nèi)核源代碼:tcp/ip協(xié)議棧的調(diào)用
這里共維護了三個隊列:prequeue、backlog、receive_queue,分別為預(yù)處理隊列,后備隊列和接收隊列,在連接建立后,若沒有數(shù)據(jù)到來,接收隊列為空,進程會在sk_busy_loop函數(shù)內(nèi)循環(huán)等待,知道接收隊列不為空,并調(diào)用函數(shù)數(shù)skb_copy_datagram_msg將接收到的數(shù)據(jù)拷貝到用戶態(tài),實際調(diào)用的是__skb_datagram_iter,這里同樣用了struct msghdr *msg來實現(xiàn)。__skb_datagram_iter函數(shù)如下:
int __skb_datagram_iter(const struct sk_buff *skb, int offset,
struct iov_iter *to, int len, bool fault_short,
size_t (*cb)(const void *, size_t, void *, struct iov_iter *),
void *data)
{
int start = skb_headlen(skb);
int i, copy = start - offset, start_off = offset, n;
struct sk_buff *frag_iter;
拷貝tcp頭部
if (copy > 0) {
if (copy > len)
copy = len;
n = cb(skb->data + offset, copy, data, to);
offset += n;
if (n 。 copy)
goto short_copy;
if ((len -= copy) == 0)
return 0;
}
拷貝數(shù)據(jù)部分
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
WARN_ON(start > offset + len);
end = start + skb_frag_size(frag);
if ((copy = end - offset) > 0) {
struct page *page = skb_frag_page(frag);
u8 *vaddr = kmap(page);
if (copy > len)
copy = len;
n = cb(vaddr + frag->page_offset +
offset - start, copy, data, to);
kunmap(page);
offset += n;
if (n 。 copy)
goto short_copy;
if (!(len -= copy))
return 0;
}
start = end;
}
拷貝完成后,函數(shù)返回,整個接收的過程也就完成了。
用一張函數(shù)間的相互調(diào)用圖可以表示:
通過gdb調(diào)試驗證如下:
Breakpoint 1, __sys_recvfrom (fd=5, ubuf=0x7ffd9428d960, size=1024, flags=0,
addr=0x0 <fixed_percpu_data>, addr_len=0x0 <fixed_percpu_data>)
at net/socket.c:1990
1990 {
(gdb) c
Continuing.
Breakpoint 2, sock_recvmsg (sock=0xffff888006df1900, msg=0xffffc900001f7e28,
flags=0) at net/socket.c:891
891 {
(gdb) c
Continuing.
Breakpoint 3, tcp_recvmsg (sk=0xffff888006479100, msg=0xffffc900001f7e28,
len=1024, nonblock=0, flags=0, addr_len=0xffffc900001f7df4)
at net/ipv4/tcp.c:1933
1933 {
(gdb) cBreakpoint 1, __sys_recvfrom (fd=5, ubuf=0x7ffd9428d960, size=1024, flags=0,
addr=0x0 <fixed_percpu_data>, addr_len=0x0 <fixed_percpu_data>)
at net/socket.c:1990
1990 {
(gdb) c
Continuing.
Breakpoint 2, sock_recvmsg (sock=0xffff888006df1900, msg=0xffffc900001f7e28,
flags=0) at net/socket.c:891
891 {
(gdb) c
Continuing.
Breakpoint 3, tcp_recvmsg (sk=0xffff888006479100, msg=0xffffc900001f7e28,
len=1024, nonblock=0, flags=0, addr_len=0xffffc900001f7df4)
at net/ipv4/tcp.c:1933
1933 {
(gdb) c
Continuing.
Breakpoint 4, __skb_datagram_iter (skb=0xffff8880068714e0, offset=0,
to=0xffffc900001efe38, len=2, fault_short=false,
cb=0xffffffff817ff860 <simple_copy_to_iter>, data=0x0 <fixed_percpu_data>)
at net/core/datagram.c:414
414 {
符合我們之前的分析。
5 IP層流程
5.1 發(fā)送端
網(wǎng)絡(luò)層的任務(wù)就是選擇合適的網(wǎng)間路由和交換結(jié)點, 確保數(shù)據(jù)及時傳送。網(wǎng)絡(luò)層將數(shù)據(jù)鏈路層提供的幀組成數(shù)據(jù)包,包中封裝有網(wǎng)絡(luò)層包頭,其中含有邏輯地址信息- -源站點和目的站點地址的網(wǎng)絡(luò)地址。其主要任務(wù)包括 (1)路由處理,即選擇下一跳 (2)添加 IP header(3)計算 IP header checksum,用于檢測 IP 報文頭部在傳播過程中是否出錯 (4)可能的話,進行 IP 分片(5)處理完畢,獲取下一跳的 MAC 地址,設(shè)置鏈路層報文頭,然后轉(zhuǎn)入鏈路層處理。
IP 頭:
IP ;咎幚磉^程如下圖所示:
首先,ip_queue_xmit(skb)會檢查skb->dst路由信息。如果沒有,比如套接字的第一個包,就使用ip_route_output()選擇一個路由。
接著,填充IP包的各個字段,比如版本、包頭長度、TOS等。
中間的一些分片等,可參閱相關(guān)文檔。基本思想是,當報文的長度大于mtu,gso的長度不為0就會調(diào)用 ip_fragment 進行分片,否則就會調(diào)用ip_finish_output2把數(shù)據(jù)發(fā)送出去。ip_fragment 函數(shù)中,會檢查 IP_DF 標志位,如果待分片IP數(shù)據(jù)包禁止分片,則調(diào)用 icmp_send()向發(fā)送方發(fā)送一個原因為需要分片而設(shè)置了不分片標志的目的不可達ICMP報文,并丟棄報文,即設(shè)置IP狀態(tài)為分片失敗,釋放skb,返回消息過長錯誤碼。
接下來就用 ip_finish_ouput2 設(shè)置鏈路層報文頭了。如果,鏈路層報頭緩存有(即hh不為空),那就拷貝到skb里。如果沒,那么就調(diào)用neigh_resolve_output,使用 ARP 獲取。
具體代碼分析如下:
入口函數(shù)是ip_queue_xmit,函數(shù)如下:
發(fā)現(xiàn)調(diào)用了__ip_queue_xmit函數(shù):
發(fā)現(xiàn)調(diào)用了skb_rtable函數(shù),實際上是開始找路由緩存,繼續(xù)看:
發(fā)現(xiàn)調(diào)用ip_local_out進行數(shù)據(jù)發(fā)送:
發(fā)現(xiàn)調(diào)用__ip_local_out函數(shù):
發(fā)現(xiàn)返回一個nf_hook函數(shù),里面調(diào)用了dst_output,這個函數(shù)實質(zhì)上是調(diào)用ip_finish__output函數(shù):
發(fā)現(xiàn)調(diào)用__ip_finish_output函數(shù):
如果分片就調(diào)用ip_fragment,否則就調(diào)用IP_finish_output2函數(shù):
在構(gòu)造好 ip 頭,檢查完分片之后,會調(diào)用鄰居子系統(tǒng)的輸出函數(shù) neigh_output 進行輸 出。neigh_output函數(shù)如下:
輸出分為有二層頭緩存和沒有兩種情況,有緩存時調(diào)用 neigh_hh_output 進行快速輸 出,沒有緩存時,則調(diào)用鄰居子系統(tǒng)的輸出回調(diào)函數(shù)進行慢速輸出。這個函數(shù)如下:
最后調(diào)用dev_queue_xmit函數(shù)進行向鏈路層發(fā)送包,到此結(jié)束。gdb驗證如下:
5.2 接收端
IP 層的入口函數(shù)在 ip_rcv 函數(shù)。該函數(shù)首先會做包括 package checksum 在內(nèi)的各種檢查,如果需要的話會做 IP defragment(將多個分片合并),然后 packet 調(diào)用已經(jīng)注冊的 Pre-routing netfilter hook ,完成后最終到達 ip_rcv_finish 函數(shù)。
ip_rcv_finish 函數(shù)會調(diào)用 ip_router_input 函數(shù),進入路由處理環(huán)節(jié)。它首先會調(diào)用 ip_route_input 來更新路由,然后查找 route,決定該 package 將會被發(fā)到本機還是會被轉(zhuǎn)發(fā)還是丟棄:
如果是發(fā)到本機的話,調(diào)用 ip_local_deliver 函數(shù),可能會做 de-fragment(合并多個 IP packet),然后調(diào)用 ip_local_deliver 函數(shù)。該函數(shù)根據(jù) package 的下一個處理層的 protocal number,調(diào)用下一層接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。對于 TCP 來說,函數(shù) tcp_v4_rcv 函數(shù)會被調(diào)用,從而處理流程進入 TCP 棧。
如果需要轉(zhuǎn)發(fā) (forward),則進入轉(zhuǎn)發(fā)流程。該流程需要處理 TTL,再調(diào)用 dst_input 函數(shù)。該函數(shù)會
(1)處理 Netfilter Hook
(2)執(zhí)行 IP fragmentation
(3)調(diào)用 dev_queue_xmit,進入鏈路層處理流程。
接收相對簡單,入口在ip_rcv,這個函數(shù)如下:
里面調(diào)用ip_rcv_finish函數(shù):
發(fā)現(xiàn)調(diào)用dst_input函數(shù),實際上是調(diào)用ip_local_deliver函數(shù):
如果分片,就調(diào)用ip_defrag函數(shù),沒有則調(diào)用ip_local_deliver_finish函數(shù):
發(fā)現(xiàn)調(diào)用ip_protocol_deliver_rcu函數(shù):
調(diào)用完畢之后進入tcp棧,調(diào)用完畢,通過gdb驗證如下:

請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達AI統(tǒng)治的開始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 “AI寒武紀”爆發(fā)至今,五類新物種登上歷史舞臺
- 5 國產(chǎn)智駕迎戰(zhàn)特斯拉FSD,AI含量差幾何?
- 6 光計算迎來商業(yè)化突破,但落地仍需時間
- 7 東陽光:2024年扭虧、一季度凈利大增,液冷疊加具身智能打開成長空間
- 8 地平線自動駕駛方案解讀
- 9 封殺AI“照騙”,“淘寶們”終于不忍了?
- 10 優(yōu)必選:營收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機器人東風翻身?