天天看點

Linux TCP/IP 協定棧之 Socket的實作分析(資料包的接收)

   前面了解過 sk 有一個接收隊列,用于存儲接  收到的 skb,對于 socket 層面上來講,資料接收,就

是要把資料從這個隊列中取出來,交給上層使用者态。這裡涉及到出隊操作,但是,要了解如何出隊,

就  得了解傳輸層協定如何入隊。前面一直用 tcp協定來分析,現在還沒有把整個 tcp棧分析出來,

要再繼續用 tcp 協定來分析,就有點問題了,是以,資料的接  收和發送,都将以 udp 協定來分析。

雖然它很簡單,但同樣也反應了 socket 層資料與接收的全部核心内容與思路。我以希望,下一步拿

下 tcp 協定後,再 把這部份的 tcp 實作補上來。

<b>udp 層的資料接收</b>

udp 層的資料接收,對于 socket 而言,就是接收隊列的入隊操作。在 ip 層中,如果是本地資料,則

會交由 ip_local_deliver_finish()函數處理,它會根據傳輸層協定的類型,交由相應的處理函數,對于udp 協定而言,

就是 udp_rcv():

/*

*        All we need to do is get the socket, and then do a checksum. 

*/

int udp_rcv(struct sk_buff *skb)

{

        struct sock *sk;

        struct udphdr *uh;

        unsigned short ulen;        

        struct rtable *rt = (struct rtable*)skb-&gt;dst;

        u32 saddr = skb-&gt;nh.iph-&gt;saddr;

        u32 daddr = skb-&gt;nh.iph-&gt;daddr;

        int len = skb-&gt;len;

        /*

         *         資料包至少應有 UDP 首部長度.

         */

        if (!pskb_may_pull(skb, sizeof(struct udphdr)))

                goto no_header;

        /*擷取 udp 首部指針*/

        uh = skb-&gt;h.uh;

        /*  資料長度,含首部長度 */

        ulen = ntohs(uh-&gt;len);

        /*  資料包長度不夠:UDP 長度比 skb 長度小,意味着資料的丢失,而 udp 長度比 udp 首部

         *  還要小,好像這個不太可能,除非封包出錯 ^o^*/

        if (ulen &gt; len || ulen                 goto short_packet;

        /*  截去 udp 封包後面的多餘封包 */

        if (pskb_trim(skb, ulen))

                goto short_packet;

        /*  開始 udp 校驗和計算,主要查依賴于 skb 的 ip_summumed 字段的設定來決定是否需要行校驗和計算 */

        if (udp_checksum_init(skb, uh, ulen, saddr, daddr)                 goto csum_error;

        /*  轉換多點傳播或廣播處理例程 */

        if(rt-&gt;rt_flags &amp; (RTCF_BROADCAST|RTCF_MULTICAST))

                return udp_v4_mcast_deliver(skb, uh, saddr, daddr);

        /*  查找資料段對應的 socket結構的 sk */

        sk = udp_v4_lookup(saddr, uh-&gt;source, daddr, uh-&gt;dest, skb-&gt;dev-&gt;ifindex);

        if (sk != NULL) {

                /*  找到了,資料包進入 UDP 的 socket 的接收隊列*/

                int ret = udp_queue_rcv_skb(sk, skb);

                sock_put(sk);

/* a return value &gt; 0 means to resubmit the input,

but                  * it it wants the return to be -protocol, or 0

                 */

                if (ret &gt; 0)

                        return -ret;

                return 0;

        }

        if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))

                goto drop;

        /*  沒有對應的 socket.  如果校驗和錯誤,則丢棄它 */

        if (udp_checksum_complete(skb))

                goto csum_error;

        /*  發送一個目的不可達封包 */

        UDP_INC_STATS_BH(UDP_MIB_NOPORTS);

        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);

         * Hmm.  We got an UDP packet to a port to which we

         * don't wanna listen.  Ignore it.

        kfree_skb(skb);

        return(0);

short_packet:

        NETDEBUG(if (net_ratelimit())

                printk(KERN_DEBUG "UDP: short packet: From %u.%u.%u.%u:%u %d/%d to

                        %u.%u.%u.%u:%u/n",

                        NIPQUAD(saddr),

                        ntohs(uh-&gt;source),

                        ulen,

                        len,

                        NIPQUAD(daddr),

                        ntohs(uh-&gt;dest)));

no_header:

        UDP_INC_STATS_BH(UDP_MIB_INERRORS);

csum_error:

        /* 

         * RFC1122: OK.  Discards the bad packet silently (as far as 

         * the network is concerned, anyway) as per 4.1.3.4 (MUST).           */

                 printk(KERN_DEBUG "UDP: bad checksum. From %d.%d.%d.%d:%d to

                 %d.%d.%d.%d:%d ulen %d/n",

                        ntohs(uh-&gt;dest),

                        ulen));

drop:

}

函數的核心思想,是根據 skb,查找到與之對應的 sk,調用 udp_v4_lookup()函數實作——udp 與 tcp

一樣,socket有一個 hash表,這個查找,就是查找 hash 表的過程。

如果找到了對應的 sk,則進入 udp_queue_rcv_skb()函數:

/* returns:

*  -1: error

*   0: success

*  &gt;0: "udp encap" protocol resubmission

*

* Note that in the success and error cases, the skb is assumed to

* have either been requeued or freed.

static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)

        /*  擷取 sk 對應的udp_sock 結構指針 */

        struct udp_sock *up = udp_sk(sk);

         *        Charge it to the socket, dropping if the queue is full.

        if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {

                kfree_skb(skb);

                return -1;

        if (up-&gt;encap_type) {

                /*

                 * This is an encapsulation socket, so let's see if this is                 

                 * an encapsulated packet.

                 * If it's a keepalive packet, then just eat it.

                 * If it's an encapsulateed packet, then pass it to the

                 * IPsec xfrm input and return the response

                 * appropriately.  Otherwise, just fall through and

                 * pass this up the UDP socket.

                int ret;

                ret = udp_encap_rcv(sk, skb);

                if (ret == 0) {

                        /* Eat the packet .. */

                        kfree_skb(skb);

                        return 0;

                }

                if (ret                         /* process the ESP packet */

                        ret = xfrm4_rcv_encap(skb, up-&gt;encap_type);

                        UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);

                /* FALLTHROUGH -- it's a UDP Packet */

        /*  如果需要校驗 */

        if (sk-&gt;sk_filter &amp;&amp; skb-&gt;ip_summed != CHECKSUM_UNNECESSARY) {

                /*  那就校驗它吧 */

                if (__udp_checksum_complete(skb)) {

                        /*  結果校驗出錯,那就算了吧 */

                        UDP_INC_STATS_BH(UDP_MIB_INERRORS);

                        return -1;

                /*  已經校驗過了,就設定不用再校驗了 */

                skb-&gt;ip_summed = CHECKSUM_UNNECESSARY;

        /*  設用 sock_queue_rcv_skb 入隊 */

        if (sock_queue_rcv_skb(sk,skb)                UDP_INC_STATS_BH(UDP_MIB_INERRORS);

        UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);

        return 0; }[/code]

encap_type字段用于判斷 udp 包,是否是一個 IPSEC 協定的封裝封包,這裡不關心 ipsec,是以接下

來的工作,就是校驗和計算,然後緊跟着調用 sock_queue_rcv_skb():

static inline int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)

        int err = 0;

        int skb_len;

        /* Cast skb-&gt;rcvbuf to unsigned... It's pointless, but reduces

           number of warnings when compiling with -W --ANK

        if (atomic_read(&amp;sk-&gt;sk_rmem_alloc) + skb-&gt;truesize &gt;=

            (unsigned)sk-&gt;sk_rcvbuf) {

                err = -ENOMEM;

                goto out;

        /*  進入 socket 層的包過濾        */

        err = sk_filter(sk, skb, 1);

        if (err)

        /*  設定 skb 的一些必要的指針和計數器變量

         *  dev:關連備設指針;

         *  sk:所對應的 sk 結構;

         *  destructor:函數指針可以初始化成一個在緩沖區釋放時完成某些動作的函數。

         *  如果 skb 不屬于一個 socket,則其常為 NULL。反之,這個函數會被指派為 sock_rfree 或sock_wfree.

         *  用于更新 socket的隊列中的記憶體容量。*/

        skb-&gt;dev = NULL;

        skb_set_owner_r(skb, sk);

        /* 在skb 入隊之前,儲存其長度至臨時變量 skb_len,這是因為一旦 skb 入隊後,它将被

         * 其它線程處理,skb 命運未知。。。。。。,而 len 值後面還會用到。

        skb_len = skb-&gt;len;

        /* 将skb 加入 sk的接收隊列 */

        skb_queue_tail(&amp;sk-&gt;sk_receive_queue, skb);

        /*  喚醒 socket 上的接收線程? */

        if (!sock_flag(sk, SOCK_DEAD))

                sk-&gt;sk_data_ready(sk, skb_len); out:

        return err;

對于 udp 而言,直接調用 skb_queue_tail(),将 skb 加入至 sk的 sk_receive_queue 隊列即可。

<b>udp 層的資料出隊操作</b>

了解了資料包如何被加入隊列,上層 socket 的資料接收,就是從這個隊列中來取資料。udp 對應的

取資料的函數 udp_recvmsg(),後面再來看它是如何被調用的,現在先來分析它的實作:

*         This should be easy, if there is something there we

*         return it, otherwise we block.

static int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,

                       size_t len, int noblock, int flags, int *addr_len)

        struct inet_sock *inet = inet_sk(sk);                /*  取得 sk對應的 inet_sock 指針 */

        struct sockaddr_in *sin = (struct sockaddr_in *)msg-&gt;msg_name;

        struct sk_buff *skb;

        int copied, err;

         *         校驗位址長度

        if (addr_len)

                *addr_len=sizeof(*sin);

        /*  如果 sk 隊列中有錯誤的資訊,則設用 ip_recv_error函數 */

        if (flags &amp; MSG_ERRQUEUE)

                return ip_recv_error(sk, msg, len);

try_again:

        /*  出隊 */

        skb = skb_recv_datagram(sk, flags, noblock, &amp;err);

        if (!skb)

        /*  需要拷貝的資料不包含 UDP 首部 */

        copied = skb-&gt;len - sizeof(struct udphdr);

        /*  如果使用者提供的緩存不夠,則設定為隻拷貝使用者需要的長度,并設定截短資料标志 */

        if (copied &gt; len) {

                copied = len;

                msg-&gt;msg_flags |= MSG_TRUNC;         }

        if (skb-&gt;ip_summed==CHECKSUM_UNNECESSARY) {

                /*  拷貝資料至使用者空間 */

                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg-&gt;msg_iov, copied);

        } else if (msg-&gt;msg_flags&amp;MSG_TRUNC) {

                if (__udp_checksum_complete(skb))

                        goto csum_copy_err;

                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg-&gt;msg_iov,

                                              copied);

        } else {

                err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg-&gt;msg_iov);

                if (err == -EINVAL)

                goto out_free;

        /*  更新 sk 的接收時間戳,根據 SOCK_RCVTSTAMP标志的設定來決定:

         *  選擇目前時間還是skb buffer的接收時間 */

        sock_recv_timestamp(msg, sk, skb);

        /*  拷貝位址等資訊. */

        if (sin)

        {

                sin-&gt;sin_family = AF_INET;

                sin-&gt;sin_port = skb-&gt;h.uh-&gt;source;

                sin-&gt;sin_addr.s_addr = skb-&gt;nh.iph-&gt;saddr;

                memset(sin-&gt;sin_zero, 0, sizeof(sin-&gt;sin_zero));

          }

        /*  檢視是否設定了一些控制标志,檢視某些IP socket選項是否被設定,例如,設定了IP_TOS socket 選項,

         *  把 IP首部中的tos字段拷貝至使用者空間等等,這個工作是由 ip_cmsg_recv 函數完成的 */

        if (inet-&gt;cmsg_flags)

                ip_cmsg_recv(msg, skb);

        /*  設定拷貝的位元組數,如果資料段已經被截短,則傳回原始實際的長度 */

        err = copied;

        if (flags &amp; MSG_TRUNC)

                err = skb-&gt;len - sizeof(struct udphdr);

out_free:          

        skb_free_datagram(sk, skb);

out:

csum_copy_err:

        /* Clear queue. */

        if (flags&amp;MSG_PEEK) {

                int clear = 0;

                spin_lock_bh(&amp;sk-&gt;sk_receive_queue.lock);

                if (skb == skb_peek(&amp;sk-&gt;sk_receive_queue)) {

                        __skb_unlink(skb, &amp;sk-&gt;sk_receive_queue);

                        clear = 1;

                spin_unlock_bh(&amp;sk-&gt;sk_receive_queue.lock);

                if (clear)

        if (noblock)

                return -EAGAIN;        

        goto try_again;

}[/code]

skb 的出隊,是通過調用 skb_recv_datagram()函數,出隊操作時,隊列中可能沒有資料,如果是阻塞,

則需要一直睡眠等待,直到逾時或隊列中有資料而被喚醒。

另外出隊操作,還分為兩種情況,一種是将資料包從隊列中取中來,将其從原有隊列中删除,還有一種可能是設定了

MSG_PEEK 标志,它意味着:“檢視目前資料,資料将被複制到緩沖區中,但并不從輸入隊列中删除”。

struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags,

                                  int noblock, int *err)

        long timeo;

         * Caller is allowed not to check sk-&gt;sk_err before skb_recv_datagram()

        int error = sock_error(sk);

        if (error)

                goto no_packet;

        /*  擷取逾時時間*/

        timeo = sock_rcvtimeo(sk, noblock);

        /*  這個循環将一直等到隊列中有資料包,直到逾時 */

        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)

                if (flags &amp; MSG_PEEK) {

                        unsigned long cpu_flags;

                        /*  如果設定了 MSG_PEEK,則設用 skb_peek,這裡要對接收隊列加鎖的原因在于*/

                        spin_lock_irqsave(&amp;sk-&gt;sk_receive_queue.lock,

                                          cpu_flags);

                        skb = skb_peek(&amp;sk-&gt;sk_receive_queue);

                        if (skb)

                                atomic_inc(&amp;skb-&gt;users);

                        spin_unlock_irqrestore(&amp;sk-&gt;sk_receive_queue.lock,

                                               cpu_flags);

                } else

                        skb = skb_dequeue(&amp;sk-&gt;sk_receive_queue);                /* 直接出隊 */

                /*  找到要收獲的葫蘆了,摘下來 */

                if (skb)

                        return skb;

                /*  如果是非阻塞,就不等了,直接跳出循環,傳回 */

                error = -EAGAIN;

                if (!timeo)

                        goto no_packet;

        } while (!wait_for_packet(sk, err, &amp;timeo));

        return NULL;

no_packet:

        *err = error;

繼續回到 udp_recvmsg 函數中來,當取出 skb 後,需要将它拷貝至使用者空間。使用者空間的資料緩存,

用了一個很重要的資料結構 struct msghdr來表示:

[code]struct msghdr {

        void *msg_name;        /* Socket name */

        int msg_namelen;        /* Length of name */

        struct iovec *msg_iov; /* Data blocks */

        __kernel_size_t msg_iovlen;  /* Number of blocks */

        void *msg_control;       /* Per protocol magic (eg BSD file descriptor passing) */

        __kernel_size_t msg_controllen; /* Length of cmsg list */

        unsigned msg_flags;

};

結構中的 msg_name、 msg_namelen 以及 msg_flags分别對應于 sys_sendto()[其它接收/發送函數類似]

的參數 addr、addr_len 以及 flags。指針 msg_control 可以指向一個附加的資料結構,用來提供一些

附加的控制資訊。後面可以看到 ip_cmsg_recv()使用了它。最重要的是,結構中的指針 msg_iov,

指向一個 iovec 結構資料,而 msg_iovlen 則指明了這個數組  的大小:

struct iovec

        void __user *iov_base;        /* BSD uses caddr_t (1003.1g requires void *) */

        __kernel_size_t iov_len;       /* Must be size_t (1003.1g) */

數組中的每一個元互素,都是 struct iovec 結構, 稱之為"io 向量",由指向資料緩沖區的指針 iov_base

和表示緩沖區中資料長度的 iov_len構成。這樣,一個 msghdr中的資料,就由控制資訊 msg_control

和若幹個"io向量"組成。

一個疑問是,為什麼不設定為一個緩沖區,而要分為若幹個緩沖區呢?一個很重要的原因就是,每

個資料封包的大小一緻,對于 ip 包而言。46-1500 都是為  可能的,那麼如果是一個緩沖區的話,

就得定義為一個“最大值”,但是如果絕大部份的包,都小于這個最大值,例如為 256 或 512,那麼

記憶體空間就浪費得太  多了……。是以,一個好的辦法是,用一個個小的緩沖區将資料切分,要浪

費,也浪費最後一個緩沖區的空間。——這種設計跟 Unix上著名的 mbuf好像是一 緻的。

OK,了解了 msghdr 的結構後,再來看資料的拷貝工作,拷貝的核心函數是 memcpy_toiovec:

*        Copy kernel to iovec. Returns -EFAULT on error.

*        Note: this modifies the original iovec.

int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len)

        while (len &gt; 0) {

                if (iov-&gt;iov_len) {                        

                        int copy = min_t(unsigned int, iov-&gt;iov_len, len);

                        if (copy_to_user(iov-&gt;iov_base, kdata, copy))

                                return -EFAULT;

                        kdata += copy;

                        len -= copy;

                        iov-&gt;iov_len -= copy;

                        iov-&gt;iov_base += copy;

                iov++;

        return 0;

因為每個 io向量的緩沖區可能很小,例如 100位元組,而要拷貝的資料很長,例如 1000 位元組。這樣,需要在一個循環裡,

将資料拷貝至若幹個 io 向量數組元素當中去。

回到 udp_recvmsg 中來,它通過設用 skb_copy_datagram_iovec()函數完成資料的拷貝工作。來看看

它是如何調用 memcpy_toiovec 的,當然,類似于這樣:

int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,

                            struct iovec *to, int len)

        return memcpy_toiovec(to, skb-&gt;data+offset, len);

這樣的調用,該是多麼美好呀,這樣的日子曾經是有過,不過已經一去不複返了。

考慮到 skb 結構的複雜性,例如分片,拷貝工作要複雜得多:

/**

*        skb_copy_datagram_iovec - Copy a datagram to an iovec.

*        @skb: buffer to copy

*        @offset: offset in the buffer to start copying from

*        @to: io vector to copy to

*        @len: amount of data to copy from buffer to iovec

*        Note: the iovec is modified during the copy.

int start = skb_headlen(skb); /* start 表示要從 skb

的哪裡開始拷貝資料 */       

        int i, copy = start - offset; /*

offset  表示調用者指出的緩存開始拷貝的偏移位元組,一般情況下,

                                                   *  它 與 start 是重疊的,表示不需要拷貝資料包的首部 */

        /*  需要拷貝首部. */

        if (copy &gt; 0) {

                if (copy &gt; len)

                        copy = len;

                if (memcpy_toiovec(to, skb-&gt;data + offset, copy))

                        goto fault;

                if ((len -= copy) == 0)

                offset += copy;

        /*  周遊每一個分片 */

        for (i = 0; i nr_frags; i++) {

                int end;

                BUG_TRAP(start  

                end = start + skb_shinfo(skb)-&gt;frags[i].size;

                if ((copy = end - offset) &gt; 0) {

                        int err;

                        u8  *vaddr;

                        skb_frag_t *frag = &amp;skb_shinfo(skb)-&gt;frags[i];

                        struct page *page = frag-&gt;page;

                        if (copy &gt; len)

                                copy = len;

                        vaddr = kmap(page);

                        err = memcpy_toiovec(to, vaddr + frag-&gt;page_offset +

                                             offset - start, copy);

                        kunmap(page);

                        if (err)

                                goto fault;

                        if (!(len -= copy))

                                return 0;

                        offset += copy;

                start = end;

        if (skb_shinfo(skb)-&gt;frag_list) {                

                struct sk_buff *list = skb_shinfo(skb)-&gt;frag_list;

                for (; list; list = list-&gt;next) {

                        int end;

                        BUG_TRAP(start                         end = start + list-&gt;len;

                        if ((copy = end - offset) &gt; 0) {

                                if (copy &gt; len)

                                        copy = len;

                                if (skb_copy_datagram_iovec(list, offset - start, to, copy))

                                        goto fault;

                                if ((len -= copy) == 0)

                                        return 0;

                                offset += copy;

                        }

                        start = end;

        if (!len)

fault:

        return -EFAULT;

現在還沒有分析 ip 的分片,是以,要完全了解這個函數的代碼有點難度,等到 ip 分片分析完了,再來補充完整它。

<b>socket 層的資料接收</b>

recv(2)、recvfrom(2)和 recvmsg(3),在 sys_socketcall()中,最終都會歸于一個統一的系統調用sock_recvmsg。

例如:

asmlinkage long sys_recv(int fd, void __user * ubuf, size_t size, unsigned flags)

        return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);

sys_recv 轉向了 sys_recvfrom():

*        Receive a frame from the socket and optionally record the address of the 

*        sender. We verify the buffers are writable and if needed move the *        sender address from kernel to user space.

asmlinkage long sys_recvfrom(int fd, void __user * ubuf, size_t size, unsigned flags,

                             struct sockaddr __user *addr, int __user *addr_len)

        struct socket *sock;

        struct iovec iov;

        struct msghdr msg;

        char address[MAX_SOCK_ADDR];

        int err,err2;

        sock = sockfd_lookup(fd, &amp;err);

        if (!sock)

        msg.msg_control=NULL;

        msg.msg_controllen=0;

        msg.msg_iovlen=1;

        msg.msg_iov=&amp;iov;

        iov.iov_len=size;

        iov.iov_base=ubuf;

        msg.msg_name=address;

        msg.msg_namelen=MAX_SOCK_ADDR;

        if (sock-&gt;file-&gt;f_flags &amp; O_NONBLOCK)

                flags |= MSG_DONTWAIT;

        err=sock_recvmsg(sock, &amp;msg, size, flags);

        if(err &gt;= 0 &amp;&amp; addr != NULL)

                err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);

                if(err2                        err=err2;

        sockfd_put(sock);                        

函數先調用 sockfd_lookup,根據 socket 描述符查找以相應的 sock 結構,然後封裝了一個 msghdr結

構後,接着調用 sock_recvmsg()。

int sock_recvmsg(struct socket *sock, struct msghdr *msg, 

                 size_t size, int flags)

{         struct kiocb iocb;

        struct sock_iocb siocb;

        int ret;

        init_sync_kiocb(&amp;iocb, NULL);

        iocb.private = &amp;siocb;

        ret = __sock_recvmsg(&amp;iocb, sock, msg, size, flags);

        if (-EIOCBQUEUED == ret)

                ret = wait_on_sync_kiocb(&amp;iocb);

        return ret;

iocb 和 siocb 用于核心和 socket 的 io 控制,函數中主要初始化了 iocb,siocb 的初始化,在

__sock_recvmsg 中完成。 進一步的按收動作,是通過__sock_recvmsg 函數設用完成的:

[code]static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock, 

                                 struct msghdr *msg, size_t size, int flags)

        int err;

        struct sock_iocb *si = kiocb_to_siocb(iocb);

        si-&gt;sock = sock;

        si-&gt;scm = NULL;

        si-&gt;msg = msg;

        si-&gt;size = size;

        si-&gt;flags = flags;

        err = security_socket_recvmsg(sock, msg, size, flags);

                return err;

        return sock-&gt;ops-&gt;recvmsg(iocb, sock, msg, size, flags);

初始化完 siocb,也就是 si 後,調用協定簇的 recvmsg 函數,對于 UDP 而言,是

sock_common_recvmsg():

int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock,

                        struct msghdr *msg, size_t size, int flags)

        struct sock *sk = sock-&gt;sk;    /*取得 sock 結構對應的 sk指針*/

        int addr_len = 0;

err = sk-&gt;sk_prot-&gt;recvmsg(iocb, sk, msg, size, flags &amp;

MSG_DONTWAIT,

                                                       flags &amp;

~MSG_DONTWAIT, &amp;addr_len);

        if (err &gt;= 0)

                msg-&gt;msg_namelen = addr_len;

于是,udp_recvmsg 就被調用了。整個過程也就結束了。

最後再回到 sys_recvfrom中來,如果使用者調用時,需要傳回對方的位址資訊:

就需要調用 move_addr_to_user()  函數來完成,前面分析 msghdr 結構時已經提到其 msg_name 成員

變量,它包含了位址的相應資訊,msg.msg_namelen 成員變量,決定了位址的長度資訊:

int move_addr_to_user(void *kaddr, int klen, void __user *uaddr, int __user *ulen)

        int len;

        if((err=get_user(len, ulen)))     //緩存長度校驗

        if(len&gt;klen)

                len=klen;

        if(len MAX_SOCK_ADDR)

                return -EINVAL;

        if(len)

                if(copy_to_user(uaddr,kaddr,len))   //拷貝位址資訊

                        return -EFAULT;

         *        "fromlen shall refer to the value before truncation.."

         *                        1003.1g

        return __put_user(klen, ulen); 

繼續閱讀