socket裡面有個即愛即恨的鎖
阿新 • • 發佈:2020-08-27
查一個問題:結果看了一下軟中斷以及系統所耗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(structsock *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))
所以這個鎖貌似規避不了,那麼怎麼處理呢???