12-socket的實踐到內核--TCP的socket數據的接收

我們繼續看TCP協議下的unix的數據接收和發送,我們知道以前說過的鉤子函數中

static const struct proto_ops unix_stream_ops = {
。。。。。。
    .recvmsg =    unix_stream_recvmsg,
。。。。。。
};

還是先看接收的過程

static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
             struct msghdr *msg, size_t size,
             int flags)
{
    struct sock_iocb *siocb = kiocb_to_siocb(iocb);
    struct scm_cookie tmp_scm;
    struct sock *sk = sock->sk;
    struct unix_sock *u = unix_sk(sk);
    struct sockaddr_un *sunaddr=msg->msg_name;
    int copied = 0;
    int check_creds = 0;
    int target;
    int err = 0;
    long timeo;

    err = -EINVAL;
    if (sk->sk_state != TCP_ESTABLISHED)
        goto out;

    err = -EOPNOTSUPP;
    if (flags&MSG_OOB)
        goto out;

    target = sock_rcvlowat(sk, flags&MSG_WAITALL, size);
    timeo = sock_rcvtimeo(sk, flags&MSG_DONTWAIT);

    msg->msg_namelen = 0;

首先是對服務器端的sock狀態進行檢測,是否處於可接收狀態,接着進入

static inline int sock_rcvlowat(const struct sock *sk, int waitall, int len)
{
    return (waitall ? len : min_t(int, sk->sk_rcvlowat, len)) ? : 1;
}

我們以前分析過如果接收數據包第一不滿會從第二個數據包中先接收一部分數據以填満第一個,現在target就是數據量的要求,所以如果接收隊列中已經沒有數據了,但是還沒達到要求的數據長度就要進入睡眠等待了,我們看到waitall是從接收標誌中判斷出的,如果允許等待接收到規定的長度就要返回規定的長度,否則就要返回接收的數據長度。

if (!siocb->scm) {
        siocb->scm = &tmp_scm;
        memset(&tmp_scm, 0, sizeof(tmp_scm));
    }

    mutex_lock(&u->readlock);

    do
    {
        int chunk;
        struct sk_buff *skb;

        unix_state_lock(sk);
        skb = skb_dequeue(&sk->sk_receive_queue);
        if (skb==NULL)
        {
            if (copied >= target)
                goto unlock;

            /*
             *    POSIX 1003.1g mandates this order.
             */


            if ((err = sock_error(sk)) != 0)
                goto unlock;
            if (sk->sk_shutdown & RCV_SHUTDOWN)
                goto unlock;

            unix_state_unlock(sk);
            err = -EAGAIN;
            if (!timeo)
                break;
            mutex_unlock(&u->readlock);

            timeo = unix_stream_data_wait(sk, timeo);

            if (signal_pending(current)) {
                err = sock_intr_errno(timeo);
                goto out;
            }
            mutex_lock(&u->readlock);
            continue;
 unlock:
            unix_state_unlock(sk);
            break;
        }
        unix_state_unlock(sk);

        if (check_creds) {
            /* Never glue messages from different writers */
            if (memcmp(UNIXCREDS(skb), &siocb->scm->creds, sizeof(siocb->scm->creds)) != 0) {
                skb_queue_head(&sk->sk_receive_queue, skb);
                break;
            }
        } else {
            /* Copy credentials */
            siocb->scm->creds = *UNIXCREDS(skb);
            check_creds = 1;
        }

        /* Copy address just once */
        if (sunaddr)
        {
            unix_copy_addr(msg, skb->sk);
            sunaddr = NULL;
        }

        chunk = min_t(unsigned int, skb->len, size);
        if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
            skb_queue_head(&sk->sk_receive_queue, skb);
            if (copied == 0)
                copied = -EFAULT;
            break;
        }
        copied += chunk;
        size -= chunk;

        /* Mark read part of skb as used */
        if (!(flags & MSG_PEEK))
        {
            skb_pull(skb, chunk);

            if (UNIXCB(skb).fp)
                unix_detach_fds(siocb->scm, skb);

            /* put the skb back if we didn't use it up.. */
            if (skb->len)
            {
                skb_queue_head(&sk->sk_receive_queue, skb);
                break;
            }

            kfree_skb(skb);

            if (siocb->scm->fp)
                break;
        }
        else
        {
            /* It is questionable, see note in unix_dgram_recvmsg.
             */

            if (UNIXCB(skb).fp)
                siocb->scm->fp = scm_fp_dup(UNIXCB(skb).fp);

            /* put message back and return */
            skb_queue_head(&sk->sk_receive_queue, skb);
            break;
        }
    } while (size);

    mutex_unlock(&u->readlock);
    scm_recv(sock, msg, siocb->scm, flags);
out:
    return copied ? : err;
}

儘管代碼很長但是我們還應該學會將複雜的問題目錄化,我是無名小卒,雖然自08年3月份纔開始寫作日誌,但是我很早幾年就開始研究分析linux內核了,如果轉載請注意出處,http://qinjiana0786.cublog.cn,這些資料分析是基於2.6.26內核進行的,下面我們分析一下,

我們可以看到上面的代碼是在一個循環的do-wile語句,循環語句中,先是skb = skb_dequeue(&sk->sk_receive_queue);從隊列中摘取一個skb的結構,然後(memcpy_toiovec(msg->msg_iov, skb->data, chunk)數據到msghdr中的iovec的用戶空間中的緩衝區,直到size -= chunk;size0時結束,在循環中還有另一種情況就是if (copied >= target)這句和copied += chunk;這句聯合起來看,也就是當複製到用戶空間中的數據達到我們前面要求數據大小時,也會跳出循環。並且我們看到這樣一段

            if (skb->len)
            {
                skb_queue_head(&sk->sk_receive_queue, skb);
                break;
            }

這段意思是說如果複製到用戶空間後還有數據沒有複製完畢就會把skb再次掛回到sock的接收隊列中,然後跳出,等待下一次接收。如果數據已經全部接收但是還未達到要求的數據長度,此時則根據是否可以睡眠而進入timeo = unix_stream_data_wait(sk, timeo);上面代碼中用子不少鎖。我們在上面很多函數都在前面幾節分析過了,我們需要分析一下幾個重點,首先是看到這裏

if (!(flags & MSG_PEEK))else語句後面的意思就是說如果只是爲了查看一下數據的話就要再把數據掛回隊列。另一處是看到if (check_creds)語句段是檢查數據中的身份信息進一步比較是否上次數據包與這次接收數據包中身份信息不同,也就是出現在進程fork()新的進程後的情形中,所以這種情況也會把數據包掛回到隊列中。接收完成一個數據包後會接着調用kfree_skb釋放掉數據包占用的空間。

void kfree_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;
    if (likely(atomic_read(&skb->users) == 1))
        smp_rmb();
    else if (likely(!atomic_dec_and_test(&skb->users)))
        return;
    __kfree_skb(skb);
}

進一步看到他調用了

void __kfree_skb(struct sk_buff *skb)
{
    skb_release_all(skb);
    kfree_skbmem(skb);
}

先看其中調用的第一個函數

static void skb_release_all(struct sk_buff *skb)
{
    dst_release(skb->dst);
#ifdef CONFIG_XFRM
    secpath_put(skb->sp);
#endif
    if (skb->destructor) {
        WARN_ON(in_irq());
        skb->destructor(skb);
    }
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    nf_conntrack_put(skb->nfct);
    nf_conntrack_put_reasm(skb->nfct_reasm);
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    nf_bridge_put(skb->nf_bridge);
#endif
/* XXX: IS this still necessary? - JHS */
#ifdef CONFIG_NET_SCHED
    skb->tc_index = 0;
#ifdef CONFIG_NET_CLS_ACT
    skb->tc_verd = 0;
#endif
#endif
    skb_release_data(skb);
}

我們可以看他這裏調用了關鍵的一步skb->destructor(skb);如果我們知道這是在發送的進程中,對數據包設置的“釋構函數”,這裏是sock_wfree()函數。最後調用

static void kfree_skbmem(struct sk_buff *skb)
{
    struct sk_buff *other;
    atomic_t *fclone_ref;

    switch (skb->fclone) {
    case SKB_FCLONE_UNAVAILABLE:
        kmem_cache_free(skbuff_head_cache, skb);
        break;

    case SKB_FCLONE_ORIG:
        fclone_ref = (atomic_t *) (skb + 2);
        if (atomic_dec_and_test(fclone_ref))
            kmem_cache_free(skbuff_fclone_cache, skb);
        break;

    case SKB_FCLONE_CLONE:
        fclone_ref = (atomic_t *) (skb + 1);
        other = skb - 1;

        /* The clone portion is available for
         * fast-cloning again.
         */

        skb->fclone = SKB_FCLONE_UNAVAILABLE;

        if (atomic_dec_and_test(fclone_ref))
            kmem_cache_free(skbuff_fclone_cache, other);
        break;
    }
}

釋放掉skb結構佔用的緩衝空間。

發佈了37 篇原創文章 · 獲贊 7 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章