部落格:blog.focus-linux.net linuxfocus.blog.chinaunix.net
Linux版本:2.6.36
好久沒有看Linux源代碼了,今天先回顧了一下以前的筆記,基本上把發送和接收資料包的流程學習了一遍。如果需要看以前的筆記,請看linuxfocus.blog.chinaunix.net上面的筆記。
不過以前的接收流程,隻學習到kernel如何将資料包分發到對應的socket上。接收流程的其它部分沒有太多值得關注的。我想可以這樣去看源碼,将一些有意思的部分單拿出來學習。
下面看一下UDP讀取資料包的關鍵函數:
struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
int *peeked, int *err)
{
struct sk_buff *skb;
long timeo;
/*
* Caller is allowed not to check sk->sk_err before skb_recv_datagram()
*/
int error = sock_error(sk);
if (error)
goto no_packet;
/* 當socket為阻塞時,擷取timeout的值 */
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
do {
/* Again only user level code calls this function, so nothing
* interrupt level will suddenly eat the receive_queue.
*
* Look at current nfs client by the way...
* However, this function was corrent in any case. 8)
*/
unsigned long cpu_flags;
/*
當檢視socket是否有資料包時,需要上鎖,因為需要保證其它線程不會将資料包取走。
*/
spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
/* 檢視在socket的buffer中是否有資料包 */
skb = skb_peek(&sk->sk_receive_queue);
if (skb) {
*peeked = skb->peeked;
if (flags & MSG_PEEK) {
/*
設定MSG_PEEK,表示使用者不是真的要讀取資料,隻是一個peek調用。
那麼并不真正讀取資料
*/
skb->peeked = 1;
atomic_inc(&skb->users);
} else
__skb_unlink(skb, &sk->sk_receive_queue); //從隊列中取出資料,即可看作讀出資料
}
spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
// 有資料包,傳回skb
if (skb)
return skb;
/* User doesn't want to wait */
error = -EAGAIN;
/*
timeo為0,有2中情況:1種是socket為非阻塞的,第2種,即socket阻塞的時間已經超過了timeo的值,
那麼就跳到no_packet處理
*/
if (!timeo)
goto no_packet;
} while (!wait_for_packet(sk, err, &timeo)); //阻塞程序,等待資料包
return NULL;
no_packet:
*err = error;
}
下面看wait_for_packet:
static int wait_for_packet(struct sock *sk, int *err, long *timeo_p)
int error;
/*
前面的操作都是初始化wait,為将socket加入wait隊列作準備,這部分代碼牽涉到程序排程。關于程序排程,我 隻是知道一些皮毛,留在以後學習。這裡隻需要将其看作是一些加入wait隊列的準備工作即可,并不影響了解代碼 。
*/
DEFINE_WAIT_FUNC(wait, receiver_wake_function);
prepare_to_wait_exclusive(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
/* Socket errors? */
error = sock_error(sk);
goto out_err;
/* 一個完備檢測。在決定wait和調用wait之間,有資料包到了,那麼就不需要wait,是以這裡再次檢查socket 的隊列是否為空 */
if (!skb_queue_empty(&sk->sk_receive_queue))
goto out;
/* 完備檢測。也許socket無資料包讀取,因為socket已經被另外的線程關閉了。這樣可以保證關閉socket的時 候,不會導緻其他的socket的讀寫操作被阻塞。*/
/* Socket shut down? */
if (sk->sk_shutdown & RCV_SHUTDOWN)
goto out_noerr;
/* 對于面向連接配接的socket進行檢查。如果是面向連接配接的socket,如果不是已經建立連接配接或者正在監聽狀态的so cket是不可能有資料包的。不然即出錯*/
/* Sequenced packets can come disconnected.
* If so we report the problem
error = -ENOTCONN;
if (connection_based(sk) &&
!(sk->sk_state == TCP_ESTABLISHED || sk->sk_state == TCP_LISTEN))
/* 檢查是否有pending的signal,保證阻塞時,程序可以被signal喚醒 */
/* handle signals */
if (signal_pending(current))
goto interrupted;
error = 0;
/* sleep本程序,直至滿足喚醒條件或者被信号喚醒——因為前面設定了TASK_INTERRUPTIBLE*/
*timeo_p = schedule_timeout(*timeo_p);
out:
/* wait隊列的清理工作 */
finish_wait(sk_sleep(sk), &wait);
return error;
interrupted:
error = sock_intr_errno(*timeo_p);
out_err:
goto out;
out_noerr:
*err = 0;
error = 1;
看完了這兩個函數,個人感覺這種針對一些有意義的函數進行學習,比流水賬似的從系統調用開始學習的效果要好。因為後者會浪費很多精力在不太重要的代碼或者流程上,而前者直接聚焦于比較關鍵的地方。