1. 程式人生 > 實用技巧 >socket裡面有個即愛即恨的鎖

socket裡面有個即愛即恨的鎖

查一個問題:結果看了一下軟中斷以及系統所耗cpu,心中滿是傷痕啊-------

perf結果一眼可以看到:主要是鎖

那麼這個lock是用來幹什麼的呢??

A:TCP socket的使用者有兩種:程序(執行緒)和軟中斷。同一時間可能會有兩個程序(執行緒),或位於不同CPU的兩個軟中斷,或程序(執行緒)與軟中斷訪問同一個socket。所以為了使socket在同一時刻只能被一個使用者訪問,那麼互斥機制是如何實現的呢?----是使用鎖完成的,也就是這個鎖lock sock

struct sock {
...
    socket_lock_t        sk_lock;
...
}
/* This is the per-socket lock.  The spinlock provides a synchronization
 * between user contexts and software interrupt processing, whereas the
 * mini-semaphore synchronizes multiple users amongst themselves.
 
*/ typedef struct { spinlock_t slock;//該自旋鎖是用於同步程序上下文和軟中斷上下文的關鍵; int owned;//取值為1表示該傳輸控制塊已經被程序上下文鎖定,取值為0表示沒有被程序上下文鎖定; wait_queue_head_t wq;//wq:等待佇列,當程序上下文需要持有該傳輸控制塊,但是其當前又被軟中斷鎖定時,程序會等待 /* * We express the mutex-alike socket_lock semantics * to the lock validator by explicitly managing * the slock as a lock variant (in addition to * the slock itself):
*/ #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif } socket_lock_t;

程序上下文的訪問操作

程序上下文在訪問該傳輸控制塊之前需要呼叫lock_sock()鎖定,在訪問完成後呼叫release_sock()將其釋放

//__lock_sock()將程序掛到sk->sk_lock中的等待佇列wq上,直到沒有程序再持有該該傳輸
    //控制塊時返回。注意:呼叫時已經持有sk->sk_lock,睡眠之前釋放鎖,返回前再次持有鎖
    static void __lock_sock(struct
sock *sk) { //定義一個等待佇列結點 DEFINE_WAIT(wait); //迴圈,直到sock_owned_by_user()返回0才結束 for (;;) { //將呼叫程序掛接到鎖的等待佇列中 prepare_to_wait_exclusive(&sk->sk_lock.wq, &wait, TASK_UNINTERRUPTIBLE); //釋放鎖並開啟下半部 spin_unlock_bh(&sk->sk_lock.slock); //執行一次排程 schedule(); //再次被排程到時會回到這裡,首先持鎖並關閉下半部 spin_lock_bh(&sk->sk_lock.slock); //如果沒有程序再次持有該傳輸控制塊,那麼返回 if (!sock_owned_by_user(sk)) break; } finish_wait(&sk->sk_lock.wq, &wait); } void lock_sock_nested(struct sock *sk, int subclass) { might_sleep();//呼叫lock_sock()可能會導致休眠---------注意 spin_lock_bh(&sk->sk_lock.slock);//持有自旋鎖並關閉下半部 //如果owned不為0,說明有程序持有該傳輸控制塊,呼叫__lock_sock()等待,掛在等待佇列上休眠 if (sk->sk_lock.owned) __lock_sock(sk); //上面__lock_sock()返回後現場已經被還原,即持有鎖並且已經關閉下半部。 //將owned設定為1,表示本程序現在持有該傳輸控制塊 sk->sk_lock.owned = 1; //釋放鎖但是沒有開啟下半部-----還是關閉了 軟中斷 spin_unlock(&sk->sk_lock.slock); /* * The sk_lock has mutex_lock() semantics here:------------這是幹啥?

We express the mutex-alike socket_lock semanticsto the lock validator by explicitly managingthe slock as a lock variant
(in addition tothe slock itself): ------不懂*/
    mutex_acquire(&sk->sk_lock.dep_map, subclass, 0, _RET_IP_);
    
    local_bh_enable();//開啟下半部  軟中斷
}

owned為1之後不再持有自旋鎖,也已經開啟軟中斷。-----作用是協議棧的處理並非立刻就能結束,如果只是簡單的在開始起持有自旋鎖並關閉下半部,在處理結束時釋放自旋鎖並開啟下半部,會降低系統性能,同時長時間關閉軟中斷,還可能使得網絡卡接收軟中斷得不到及時呼叫,導致丟包

release_sock()

程序上下文在結束傳輸控制塊的操作之後,需要呼叫release_sock()釋放傳輸控制塊。釋放的核心是將owned設定為0並通知其它等待該傳輸控制塊的程序

void release_sock(struct sock *sk)
{
    /*
     * The sk_lock has mutex_unlock() semantics:
     */
    //除錯相關,忽略
    mutex_release(&sk->sk_lock.dep_map, 1, _RET_IP_);

    //獲取自旋鎖並關閉下半部
    spin_lock_bh(&sk->sk_lock.slock);
    //如果後備佇列不為空,則呼叫__release_sock()處理後備佇列中的資料包,見資料包的接收過程
    if (sk->sk_backlog.tail)
        __release_sock(sk);
    //設定owned為0,表示呼叫者不再持有該傳輸控制塊
    sk->sk_lock.owned = 0;
    //如果等待佇列不為空,則喚醒這些等待的程序
    if (waitqueue_active(&sk->sk_lock.wq))
        wake_up(&sk->sk_lock.wq);
    //釋放自旋鎖並開啟下半部
    spin_unlock_bh(&sk->sk_lock.slock);
}

(1)軟中斷先訪問程序後訪問

這時軟中斷已經獲取了自旋鎖,程序在獲取自旋鎖時會等待,軟中斷釋放鎖時程序才能成功獲取鎖。

(2)程序先訪問軟中斷後訪問

程序獲取自旋鎖(關軟中斷,防止被軟中斷打斷)時會將sk->sk_lock.owned設定為1後釋放自旋鎖並開啟軟中斷,然後執行對socket的訪問。這時如果軟中斷髮生,則程序的執行被中止,然後軟中斷中將資料放到接收後備佇列中

int tcp_v4_rcv(struct sk_buff *skb)
{
...
process:
...
    //獲取sk->sk_lock.slock自旋鎖
    bh_lock_sock_nested(sk);
    //如果沒有程序鎖定該傳輸控制塊,將資料接收到奧prequeue或者receive_queue中
    if (!sock_owned_by_user(sk)) {
        if (!tcp_prequeue(sk, skb))
            ret = tcp_v4_do_rcv(sk, skb);
    } else
        //如果程序已經鎖定該傳輸控制塊,那麼先將資料接收到後備佇列中----趕緊退出 讓程序處理 然後在release的時候 處理後備佇列
        sk_add_backlog(sk, skb);
    //釋放自旋鎖
    bh_unlock_sock(sk);
...

/* BH context may only use the following locking interface. */
#define bh_lock_sock(__sk)    spin_lock(&((__sk)->sk_lock.slock))
#define bh_lock_sock_nested(__sk) \
                spin_lock_nested(&((__sk)->sk_lock.slock), \
                SINGLE_DEPTH_NESTING)
#define bh_unlock_sock(__sk)    spin_unlock(&((__sk)->sk_lock.slock))

所以這個鎖貌似規避不了,那麼怎麼處理呢???