天天看點

TCP/IP源碼學習(43)——__skb_recv_datagram學習

作者:[email protected]

部落格: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;

看完了這兩個函數,個人感覺這種針對一些有意義的函數進行學習,比流水賬似的從系統調用開始學習的效果要好。因為後者會浪費很多精力在不太重要的代碼或者流程上,而前者直接聚焦于比較關鍵的地方。

繼續閱讀