天天看點

簡單談一點linux核心中套接字的bind機制--2.6.30核心代碼的改進

在2.6.30之前的核心版本中,如果一個要求綁定随機端口的套接字在周遊了所有的哈希表中的元素之後發現沒有合适的,那麼直接出錯傳回,這裡有幾個問題,第一就是如果目前系統的綁定套接字已經足夠多了,那麼很明顯的事實就是等待漫長的周遊結束之後仍然會無功而返,第二個問題就是如果付出了這麼大的代價無功而返顯然對調用者不公平,于是2.6.30做了改進,部分代碼如下:

int inet_csk_get_port(struct sock *sk, unsigned short snum)

{

struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;

struct inet_bind_hashbucket *head;

struct hlist_node *node;

struct inet_bind_bucket *tb;

int ret, attempts = 5;

struct net *net = sock_net(sk);

int smallest_size = -1, smallest_rover;

local_bh_disable();

if (!snum) {

int remaining, rover, low, high;

again:

...//smallest_size = -1;之外和老版本相同,注意括号

inet_bind_bucket_for_each(tb, node, &head->chain)

if (ib_net(tb) == net && tb->port == rover) {

if (tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != TCP_LISTEN &&(tb->num_owners < smallest_size || smallest_size == -1)) { //如果目前套接字沒有設定reuse或者狀态是listen,或者...那麼就不做下一步的抉擇,因為那樣沒有意義,所謂下一步的抉擇主要就是尋找一個擁有最小使用者的inet_bind_bucket,然後如果綁定套接字的數量已經足夠多,那麼就用這個找到的inet_bind_bucket進行抉擇

smallest_size = tb->num_owners;

smallest_rover = rover;

if (atomic_read(&hashinfo->bsockets) > (high - low) + 1) {//如果綁定的套接字數量已經超過了設定的最大數量,那麼就不再繼續搜尋了,為何不在周遊的時候直接進行這個判斷而非要等到這裡呢?我想這裡面做了一個權衡,就是說如果周遊前就進行這個判斷的話,那麼一旦套接字數量超越保持哈希查找高效率界限的話,對新的目前套接字不公平,畢竟它還是可以重用一些端口的,如果不進行這個調用的話,那麼周遊一遍哈希會很耗時,為了使得程式邏輯盡量滿足每一個消費者的需求同時又保持高效,那麼就用這裡的處理方式,當查找到一個可以公用端口的bucket的時候再進行數量判斷,一旦超越界限馬上跳出周遊,但是在下面的bind_conflict中很可能會沖突失敗,當失敗的時候程式流程回到again繼續查找,此時的smallest_size被重新指派,然後重新標明随機的起始端口。這個過程看起來在最壞情況下也會導緻全部周遊,但是無論怎樣在這最壞的情況下周遊期間插入了一些抉擇點,繞過來說最壞情況就是這些抉擇點都失敗的情況,然而都失敗的可能性是非常小的,這又是一個權衡

spin_unlock(&head->lock);

snum = smallest_rover;

goto have_snum;

}

...//同老版本代碼,注意括号

} while (--remaining > 0);

ret = 1;

if (remaining <= 0) { //這裡給了随機綁定需求一次機會,隻要找到了合适的可以重用的端口,那麼就盡可能的使用它,為了兼顧其它的綁定,盡量使用共享者比較少的綁定

if (smallest_size != -1) {

goto fail;

snum = rover;

} else {

have_snum:

head = &hashinfo->bhash[inet_bhashfn(net, snum, hashinfo->bhash_size)];

tb_found:

if (!hlist_empty(&tb->owners)) {

if (tb->fastreuse > 0 && sk->sk_reuse && //想成功必須保證smallest_size等于-1

sk->sk_state != TCP_LISTEN && smallest_size == -1) {

goto success;

ret = 1; //如果是随機綁定需求,并且套接字數量已經足夠多,或者找了一圈隻找到可以重用的inet_bind_bucket,那麼就有機會使用這些可以重用的inet_bind_bucket,而且會到達這裡并且下面的bind_conflict往往會傳回NULL,也就是意味着沒有沖突,接下來随機綁定的需求就可以滿足了,就是重用這個bucket

if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb)) {

if (sk->sk_reuse && sk->sk_state != TCP_LISTEN &&

smallest_size != -1 && --attempts >= 0) {

goto again;

goto fail_unlock;

...//接下來的邏輯和以前的幾乎相同,注意括号

可以看到,新的2.6.30核心解決了以上提出的兩個問題,首先,随機綁定套接字的需求可以使用可重用的套接字了,其次,當已經綁定的套接字數量達到一定量的時候不會再進行全部周遊了,節省了很多的時間。這就是說,2.6.30的改進在代碼上就可以看出來,不過具體是否性能提升了,還要看具體的測試環境,用眼靜态的看代碼隻能得出代碼在理想情況下執行的一般結論,程式的實際執行環境是很複雜的,比如說,有些極端情況幾乎是不會出現的,不采用一些大壓力測試方案很難了解代碼的意圖,這也可以說,以上的版本靜态觀看看似很不錯,但是可能真的要到綁定套接字達到一定數量的時候優勢才會展現出來。行文最後可以看到套接字在綁定的時候所做的可否共享判斷,一共是兩個層次的判斷,首先在所謂的協定族的底層容器中周遊先找到有可能共享的bucket,該bucket其實也是一個容器,這個有可能的判斷是以目前sock為中心的,就是判斷目前的sock是否允許reuse以及是否處于listen狀态等等,單方面通過判斷以後,還有進行另一個層次的判斷,所謂雙方合作一個巴掌拍不響,這另一個方面的判斷是以這個找到的bucket中的各個元素為中心的,如果有一個元素不同意reuse,那麼最終還是會失敗的,2.6.30之前的核心版本完全是按照這個邏輯來的,2.6.30的改進在于在第一層判斷的期間插入了第二層的很容易進行的判斷,同時進行了一些資訊搜集,該資訊儲備作為後期抉擇之用,比如在第一層的判斷中進行第二層的tb->fastreuse值的判斷,搜集的資訊就是smallest_size和smallest_rover,一旦周遊結束或者中途終止,那麼就可以用這些資訊進行抉擇,不管怎樣沒有白周遊一場,這個效率的提升是很明顯的。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1273480

繼續閱讀