Linux核心網路協議棧7 socket埠管理

2021-06-17 22:11:11 字數 3959 閱讀 6849

一、前情回顧

上一節《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 或者收完網絡卡上的所有報文之後,重新開啟中斷。網絡卡啟用...