一、前情回顧
上一節《socket 位址繫結 》中提到,應用程式傳遞過來的埠在核心中需要檢查埠是否可用:
if (sk->sk_prot->get_port(sk, snum))
按照前面的例子來分析,這裡是呼叫了 tcp_prot 結構變數中的 get_prot 函式指標,該函式位於net/ipv4/inet_connection_sock.c 中;這個函式比較長,也是我們今天要分析的重點;
二、埠的管理
1 、埠管理資料結構
linux 核心將所有 socket 使用時的埠通過乙個雜湊表來管理,該雜湊表存放在全域性變數 tcp_hashinfo 中,通過 tcp_prot變數的 h 成員引用,該成員是乙個聯合型別;對於 tcp 套接字型別,其引用存放在 h. hashinfo 成員中;下面是tcp_hashinfo 的結構體型別:
struct inet_hashinfo
埠管理相關的,目前可以只關注加注釋的這三個成員,其中 bhash 為已經雜湊表結構, bhash_size 為雜湊表的大小;所有雜湊表中的節點記憶體都是在 bind_bucket_cachep 快取記憶體中分配;
下面看一下 inet_bind_hashbucket 結構體:
struct inet_bind_hashbucket ;
struct hlist_head ;
struct hlist_node ;
inet_bind_hashbucket 是雜湊桶結構, lock 成員是用於操作時對桶進行加鎖, chain 成員是相同雜湊值的節點的鍊錶;示意圖如下:
2 、預設埠的分配
當應用程式沒有指定埠時(如 socket 客戶端連線到服務端時,會由核心從可用埠中分配乙個給該 socket );
看看下面的** ( 參見 net/ipv4/inet_connection_sock.c: inet_csk_get_port() 函式 ) :
if (!snum) while (--remaining > 0);
ret = 1;
if (remaining <= 0)
goto fail;
snum = rover;
}
這裡,隨機埠的範圍是 32768~61000 ;上面**的邏輯如下:
1) 從 [32768, 61000] 中隨機取乙個埠 rover ;
2) 計算該埠的 hash 值,然後從全域性變數 tcp_hashinfo 的雜湊表 bhash 中取出相同雜湊值的鍊錶 head ;
3) 遍歷鍊錶 head ,檢查每個節點的網路裝置是否和當前網路設定相同,同時檢查節點的埠是否和 rover 相同;
4) 如果相同,表明埠被占用,繼續下乙個埠;如果和鍊錶 head 中的節點都不相同,則跳出迴圈,繼續後面的邏輯;
inet_bind_bucket_foreach 巨集利用《 建立 socket 》一文中提到的 container_of 巨集來實現 的,大家可以自己看看;
3 、埠重用
else
此時同樣會檢查該埠有沒有被占用;如果被占用,會檢查埠重用(跳轉到 tb_found ):
tb_found:
if (!hlist_empty(&tb->owners)) else
}
1) 埠節點結構
struct inet_bind_bucket ;
前面提到的雜湊桶結構中的 chain 鍊錶中的每個節點,其宿主結構體是 inet_bind_bucket ,該結構體通過成員 node 鏈入鍊錶;
2) 檢查埠是否可重用
這裡涉及到兩個屬性,乙個是 socket 的 sk_reuse ,另乙個是 inet_bind_bucket 的 fastreuse ;
sk_reuse 可以通過 setsockopt() 庫函式進行設定,其值為 0 或 1 ,當為 1 時,表示當乙個 socket 進入 tcp_time_wait狀態 ( 連線關閉已經完成 ) 後,它所占用的埠馬上能夠被重用,這在除錯伺服器時比較有用,重啟程式不用進行等待;而fastreuse 代表該埠是否允許被重用:
l 當該埠第一次被使用時( owners 為空),如果 sk_reuse 為 1 且 socket 狀態不為 tcp_listen ,則設定fastreuse 為 1 ,否則設定為 0 ;
l 當該埠同時被其他 socket 使用時( owners 不為空),如果當前埠能被重用,但是當前 socket 的 sk_reuse 為0 或其狀態為 tcp_listen ,則將 fastreuse 設定為 0 ,標記為不能重用;
3) 當不能重用時,再次檢查衝突
此時會呼叫 inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb) 再次檢查埠是否衝突;回想《 建立 socket 》一文中提到,建立 socket 成功後,要使用相應的協議來初始化 socket ,對於 tcp 協議來說,其初始化方法是net/ipv4/tcp_ipv4.c:tcp_v4_init_sock() ,其中就做了如下一步的設定:
icsk->icsk_af_ops = &ipv4_specific;
struct inet_connection_sock_af_ops ipv4_specific = ;
下面看看這裡再次檢查衝突的**:
int inet_csk_bind_conflict(const struct sock *sk,const struct inet_bind_bucket *tb)
} }
return node != null;
}
上面函式的邏輯是:從 owners 中遍歷繫結在該埠上的 socket ,如果某 socket 跟當前的 socket 不是同乙個,並且是繫結在同乙個網路裝置介面上的,並且它們兩個之中至少有乙個的 sk_reuse 表示自己的埠不能被重用或該 socket 已經是tcp_listen 狀態了,並且它們兩個之中至少有乙個沒有指定接收 ip 位址,或者兩個都指定接收位址,但是接收位址是相同的,則衝突產生,否則不衝突。
也就是說,不使用同乙個接收位址的 socket 可以共用埠號,繫結在不同的網路裝置介面上的 socket 可以共用埠號,或者兩個 socket 都表示自己可以被重用,並且還不在 tcp_listen 狀態,則可以重用埠號。
4 、新建 inet_bind_bucket
當在 bhash 中沒有找到指定的埠時,需要建立新的桶節點,然後掛入 bhash 中:
tb_not_found:
ret = 1;
if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,net, head, snum)) == null)
goto fail_unlock;
if (hlist_empty(&tb->owners)) else if (tb->fastreuse &&(!sk->sk_reuse || sk->sk_state == tcp_listen))
tb->fastreuse = 0;
success:
if (!inet_csk(sk)->icsk_bind_hash)
inet_bind_hash(sk, tb, snum);
有興趣的可以自己看看這段**的實現,這裡就不再展開了。 核心網路協議棧offload功能盤點
tso tcp segmentation offload的縮寫,只針對tcp包傳送,超過 mtu 大小的報文不需要在協議棧分段,直接offload到網絡卡,由網絡卡硬體實現分段,降低 cpu 負載。除了tso,還有乙個lso large segment offload lso的定義相對於tso更加寬...
Linux核心網路協議棧8 socket監聽
幾個問題 了解以下幾個問題的同學可以直接忽略下文 1 listen 庫函式主要做了什麼?2 什麼是最大併發連線請求數?3 什麼是等待連線佇列?socket 監聽相對還是比較簡單的,先看下應用程式 listen server sockfd,5 其中,第乙個引數 server sockfd為服務端 so...
linux核心網路協議棧 網絡卡報文收發(十六)
linux版本 3.10.103 網絡卡驅動 ixgbev 網絡卡驅動預設採用的是napi的報文處理方式。即中斷 輪詢的方式,網絡卡收到乙個報文之後會產生接收中斷,並且遮蔽中斷,直到收夠了netdev max backlog個報文 預設300 或者收完網絡卡上的所有報文之後,重新開啟中斷。網絡卡啟用...