Linux TCP/IP 协议栈之 Socket的实现分析(数据包的接收)

简介:    前面了解过 sk 有一个接收队列,用于存储接  收到的 skb,对于 socket 层面上来讲,数据接收,就是要把数据从这个队列中取出来,交给上层用户态。这里涉及到出队操作,但是,要了解如何出队,就  得了解传输层协议如何入队。
   前面了解过 sk 有一个接收队列,用于存储接  收到的 skb,对于 socket 层面上来讲,数据接收,就
是要把数据从这个队列中取出来,交给上层用户态。这里涉及到出队操作,但是,要了解如何出队,
就  得了解传输层协议如何入队。前面一直用 tcp协议来分析,现在还没有把整个 tcp栈分析出来,
要再继续用 tcp 协议来分析,就有点问题了,所以,数据的接  收和发送,都将以 udp 协议来分析。
虽然它很简单,但同样也反应了 socket 层数据与接收的全部核心内容与思路。我以希望,下一步拿
下 tcp 协议后,再 把这部份的 tcp 实现补上来。
 
udp 层的数据接收
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->dst;
        u32 saddr = skb->nh.iph->saddr;
        u32 daddr = skb->nh.iph->daddr;
        int len = skb->len;
 
        /*
         *         数据包至少应有 UDP 首部长度.
         */
        if (!pskb_may_pull(skb, sizeof(struct udphdr)))
                goto no_header;
                
        /*获取 udp 首部指针*/
        uh = skb->h.uh;
        
        /*  数据长度,含首部长度 */
        ulen = ntohs(uh->len);
        
        /*  数据包长度不够:UDP 长度比 skb 长度小,意味着数据的丢失,而 udp 长度比 udp 首部
         *  还要小,好像这个不太可能,除非封包出错 ^o^*/
        if (ulen > 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->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
                return udp_v4_mcast_deliver(skb, uh, saddr, daddr);
        
        /*  查找数据段对应的 socket结构的 sk */
        sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);
 
        if (sk != NULL) {
                /*  找到了,数据包进入 UDP 的 socket 的接收队列*/
                int ret = udp_queue_rcv_skb(sk, skb);
                sock_put(sk);
 
                /* a return value > 0 means to resubmit the input, but                  * it it wants the return to be -protocol, or 0
                 */
                if (ret > 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->source),
                        ulen,
                        len,
                        NIPQUAD(daddr),
                        ntohs(uh->dest)));
no_header:
        UDP_INC_STATS_BH(UDP_MIB_INERRORS);
        kfree_skb(skb);
        return(0);
 
csum_error:
        /* 
         * RFC1122: OK.  Discards the bad packet silently (as far as 
         * the network is concerned, anyway) as per 4.1.3.4 (MUST).           */
        NETDEBUG(if (net_ratelimit())
                 printk(KERN_DEBUG "UDP: bad checksum. From %d.%d.%d.%d:%d to
                 %d.%d.%d.%d:%d ulen %d/n",
                        NIPQUAD(saddr),
                        ntohs(uh->source),
                        NIPQUAD(daddr),
                        ntohs(uh->dest),
                        ulen));
drop:
        UDP_INC_STATS_BH(UDP_MIB_INERRORS);
        kfree_skb(skb);
        return(0);
}
 
函数的核心思想,是根据 skb,查找到与之对应的 sk,调用 udp_v4_lookup()函数实现——udp 与 tcp
一样,socket有一个 hash表,这个查找,就是查找 hash 表的过程。
 
如果找到了对应的 sk,则进入 udp_queue_rcv_skb()函数:
 
/* returns:
*  -1: error
*   0: success
*  >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->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->encap_type);
                        UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
                        return -ret;
                }
                /* FALLTHROUGH -- it's a UDP Packet */
        }
        /*  如果需要校验 */
        if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
                /*  那就校验它吧 */
                if (__udp_checksum_complete(skb)) {
                        /*  结果校验出错,那就算了吧 */
                        UDP_INC_STATS_BH(UDP_MIB_INERRORS);
                        kfree_skb(skb);
                        return -1;
                }
                /*  已经校验过了,就设置不用再校验了 */
                skb->ip_summed = CHECKSUM_UNNECESSARY;
        }
 
        /*  设用 sock_queue_rcv_skb 入队 */
        if (sock_queue_rcv_skb(sk,skb)                UDP_INC_STATS_BH(UDP_MIB_INERRORS);
                kfree_skb(skb);
                return -1;
        }
        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->rcvbuf to unsigned... It's pointless, but reduces
           number of warnings when compiling with -W --ANK
         */
        if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
            (unsigned)sk->sk_rcvbuf) {
                err = -ENOMEM;
                goto out;
        }
 
        /*  进入 socket 层的包过滤        */
        err = sk_filter(sk, skb, 1);
        if (err)
                goto out;
        
        /*  设置 skb 的一些必要的指针和计数器变量
         *  dev:关连备设指针;
         *  sk:所对应的 sk 结构;
         *  destructor:函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。
         *  如果 skb 不属于一个 socket,则其常为 NULL。反之,这个函数会被赋值为 sock_rfree 或sock_wfree.
         *  用于更新 socket的队列中的内存容量。*/
        skb->dev = NULL;
        skb_set_owner_r(skb, sk);
 
        /* 在skb 入队之前,保存其长度至临时变量 skb_len,这是因为一旦 skb 入队后,它将被
         * 其它线程处理,skb 命运未知。。。。。。,而 len 值后面还会用到。
         */
        skb_len = skb->len;
        
        /* 将skb 加入 sk的接收队列 */
        skb_queue_tail(&sk->sk_receive_queue, skb);
        
        /*  唤醒 socket 上的接收线程? */
        if (!sock_flag(sk, SOCK_DEAD))
                sk->sk_data_ready(sk, skb_len); out:
        return err;
}
对于 udp 而言,直接调用 skb_queue_tail(),将 skb 加入至 sk的 sk_receive_queue 队列即可。
 
udp 层的数据出队操作
 
了解了数据包如何被加入队列,上层 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->msg_name;
        struct sk_buff *skb;
        int copied, err;
 
        /*
         *         校验地址长度
         */
        if (addr_len)
                *addr_len=sizeof(*sin);

        /*  如果 sk 队列中有错误的信息,则设用 ip_recv_error函数 */
        if (flags & MSG_ERRQUEUE)
                return ip_recv_error(sk, msg, len);
 
try_again:
        /*  出队 */
        skb = skb_recv_datagram(sk, flags, noblock, &err);
        if (!skb)
                goto out;
         
        /*  需要拷贝的数据不包含 UDP 首部 */
        copied = skb->len - sizeof(struct udphdr);
       
        /*  如果用户提供的缓存不够,则设置为只拷贝用户需要的长度,并设置截短数据标志 */
        if (copied > len) {
                copied = len;
                msg->msg_flags |= MSG_TRUNC;         }
        
        if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
                /*  拷贝数据至用户空间 */
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov, copied);
        } else if (msg->msg_flags&MSG_TRUNC) {
                if (__udp_checksum_complete(skb))
                        goto csum_copy_err;
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
                                              copied);
        } else {
                err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
 
                if (err == -EINVAL)
                        goto csum_copy_err;
        }
 
        if (err)
                goto out_free;

        /*  更新 sk 的接收时间戳,根据 SOCK_RCVTSTAMP标志的设置来决定:
         *  选择当前时间还是skb buffer的接收时间 */
        sock_recv_timestamp(msg, sk, skb);
 
        /*  拷贝地址等信息. */
        if (sin)
        {
                sin->sin_family = AF_INET;
                sin->sin_port = skb->h.uh->source;
                sin->sin_addr.s_addr = skb->nh.iph->saddr;
                memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
          }

        /*  查看是否设置了一些控制标志,查看某些IP socket选项是否被设置,例如,设置了IP_TOS socket 选项,
         *  把 IP首部中的tos字段拷贝至用户空间等等,这个工作是由 ip_cmsg_recv 函数完成的 */
        if (inet->cmsg_flags)
                ip_cmsg_recv(msg, skb);
        
        /*  设置拷贝的字节数,如果数据段已经被截短,则返回原始实际的长度 */
        err = copied;
        if (flags & MSG_TRUNC)
                err = skb->len - sizeof(struct udphdr);
  
out_free:          
        skb_free_datagram(sk, skb);
out:
        return err;
 
csum_copy_err:
        UDP_INC_STATS_BH(UDP_MIB_INERRORS);
 
        /* Clear queue. */
        if (flags&MSG_PEEK) {
                int clear = 0;
                spin_lock_bh(&sk->sk_receive_queue.lock);
                if (skb == skb_peek(&sk->sk_receive_queue)) {
                        __skb_unlink(skb, &sk->sk_receive_queue);
                        clear = 1;
                }
                spin_unlock_bh(&sk->sk_receive_queue.lock);
                if (clear)
                        kfree_skb(skb);
        }
 
        skb_free_datagram(sk, skb);
 
        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)
{
        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;
         
        /*  获取超时时间*/
        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 & MSG_PEEK) {
                        unsigned long cpu_flags;
                        /*  如果设置了 MSG_PEEK,则设用 skb_peek,这里要对接收队列加锁的原因在于*/
                        spin_lock_irqsave(&sk->sk_receive_queue.lock,
                                          cpu_flags);
                        skb = skb_peek(&sk->sk_receive_queue);
                        if (skb)
                                atomic_inc(&skb->users);
                        spin_unlock_irqrestore(&sk->sk_receive_queue.lock,
                                               cpu_flags);
                } else
                        skb = skb_dequeue(&sk->sk_receive_queue);                /* 直接出队 */
                
                /*  找到要收获的葫芦了,摘下来 */
                if (skb)
                        return skb;
 
                /*  如果是非阻塞,就不等了,直接跳出循环,返回 */
                error = -EAGAIN;
                if (!timeo)
                        goto no_packet;
 
        } while (!wait_for_packet(sk, err, &timeo));
 
        return NULL;
 
no_packet:
        *err = error;
        return NULL;
}
继续回到 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 > 0) {
                if (iov->iov_len) {                        
                        int copy = min_t(unsigned int, iov->iov_len, len);
                        if (copy_to_user(iov->iov_base, kdata, copy))
                                return -EFAULT;
 
                        kdata += copy;
                        len -= copy;
                        iov->iov_len -= copy;
                        iov->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->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 skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
                            struct iovec *to, int len)
{
        int start = skb_headlen(skb); /* start 表示要从 skb 的哪里开始拷贝数据 */       
        int i, copy = start - offset; /* offset  表示调用者指出的缓存开始拷贝的偏移字节,一般情况下,
                                                   *  它 与 start 是重叠的,表示不需要拷贝数据包的首部 */
 
        /*  需要拷贝首部. */
        if (copy > 0) {
                if (copy > len)
                        copy = len;
                if (memcpy_toiovec(to, skb->data + offset, copy))
                        goto fault;
                if ((len -= copy) == 0)
                        return 0;
                offset += copy;
        }
 
        /*  遍历每一个分片 */
        for (i = 0; i nr_frags; i++) {
                int end;
 
                BUG_TRAP(start  
                end = start + skb_shinfo(skb)->frags[i].size;
                if ((copy = end - offset) > 0) {
                        int err;
                        u8  *vaddr;
                        skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
                        struct page *page = frag->page;
 
                        if (copy > len)
                                copy = len;
                        vaddr = kmap(page);
                        err = memcpy_toiovec(to, vaddr + frag->page_offset +
                                             offset - start, copy);
                        kunmap(page);
                        if (err)
                                goto fault;
                        if (!(len -= copy))
                                return 0;
                        offset += copy;
                }
                start = end;
        }
 
        if (skb_shinfo(skb)->frag_list) {                
                struct sk_buff *list = skb_shinfo(skb)->frag_list;
                for (; list; list = list->next) {
                        int end;
                        BUG_TRAP(start                         end = start + list->len;
                        if ((copy = end - offset) > 0) {
                                if (copy > 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)
                return 0;
fault:
        return -EFAULT;
}
 
现在还没有分析 ip 的分片,所以,要完全理解这个函数的代码有点难度,等到 ip 分片分析完了,再来补充完整它。
 
socket 层的数据接收
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, &err);
        if (!sock)
                goto out;
 
        msg.msg_control=NULL;
        msg.msg_controllen=0;
        msg.msg_iovlen=1;
        msg.msg_iov=&iov;
        iov.iov_len=size;
        iov.iov_base=ubuf;
        msg.msg_name=address;
        msg.msg_namelen=MAX_SOCK_ADDR;
        if (sock->file->f_flags & O_NONBLOCK)
                flags |= MSG_DONTWAIT;
        err=sock_recvmsg(sock, &msg, size, flags);
 
        if(err >= 0 && addr != NULL)
        {
                err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
                if(err2                        err=err2;
        }
        sockfd_put(sock);                        
out:
        return err;
}
 
函数先调用 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(&iocb, NULL);
        iocb.private = &siocb;
        ret = __sock_recvmsg(&iocb, sock, msg, size, flags);
        if (-EIOCBQUEUED == ret)
                ret = wait_on_sync_kiocb(&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->sock = sock;
        si->scm = NULL;
        si->msg = msg;
        si->size = size;
        si->flags = flags;
 
        err = security_socket_recvmsg(sock, msg, size, flags);
        if (err)
                return err;
 
        return sock->ops->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->sk;    /*取得 sock 结构对应的 sk指针*/
        int addr_len = 0;
        int err;
 
        err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
                                                       flags & ~MSG_DONTWAIT, &addr_len);
        if (err >= 0)
                msg->msg_namelen = addr_len;
        return err;
}
 
于是,udp_recvmsg 就被调用了。整个过程也就结束了。
 
最后再回到 sys_recvfrom中来,如果用户调用时,需要返回对方的地址信息:
        if(err >= 0 && addr != NULL)
        {
                err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
                if(err2                        err=err2;
        }
就需要调用 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 err;
        int len;
 
        if((err=get_user(len, ulen)))     //缓存长度校验
                return err;
        if(len>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); 
}
目录
相关文章
|
26天前
|
网络协议 安全 Java
Java网络编程入门涉及TCP/IP协议理解与Socket通信。
【6月更文挑战第21天】Java网络编程入门涉及TCP/IP协议理解与Socket通信。TCP/IP协议包括应用层、传输层、网络层和数据链路层。使用Java的`ServerSocket`和`Socket`类,服务器监听端口,接受客户端连接,而客户端连接指定服务器并交换数据。基础示例展示如何创建服务器和发送消息。进阶可涉及多线程、NIO和安全传输。学习这些基础知识能助你构建网络应用。
26 1
|
2月前
|
Ubuntu Linux
linux怎么查看自己的ip地址
在Linux系统中,有多种方法可以查看自己的IP地址。
218 2
|
12天前
|
SQL 自然语言处理 网络协议
【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
TCP(Transmission Control Protocol)连接是互联网上最常用的一种面向连接、可靠的、基于字节流的传输层通信协议。建立TCP连接需要经过著名的“三次握手”过程: 1. SYN(同步序列编号):客户端发送一个SYN包给服务器,并进入SYN_SEND状态,等待服务器确认。 2. SYN-ACK:服务器收到SYN包后,回应一个SYN-ACK(SYN+ACKnowledgment)包,告诉客户端其接收到了请求,并同意建立连接,此时服务器进入SYN_RECV状态。 3. ACK(确认字符):客户端收到服务器的SYN-ACK包后,发送一个ACK包给服务器,确认收到了服务器的确
125 1
|
20天前
|
网络协议 Linux 网络安全
Linux配置SSH允许TCP转发
Linux配置SSH允许TCP转发
21 1
|
24天前
|
网络协议 Java 程序员
TCP/IP协议栈是网络通信基础,Java的`java.net`包提供工具,使开发者能利用TCP/IP创建网络应用
【6月更文挑战第23天】 **TCP/IP协议栈是网络通信基础,它包含应用层(HTTP, FTP等)、传输层(TCP, UDP)、网络层(IP)、数据链路层(帧, MAC地址)和物理层(硬件信号)。Java的`java.net`包提供工具,使开发者能利用TCP/IP创建网络应用,如Socket和ServerSocket用于客户端和服务器通信。**
32 3
|
12天前
|
网络协议 Linux
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
|
12天前
|
网络协议 Linux 开发工具
配置Linux固定IP地址,为什么要固定IP,因为他是通DHCP服务获取的,DHCP服务每次重启都会重新获取一次ip,VMware编辑中有一个虚拟网络编辑器
配置Linux固定IP地址,为什么要固定IP,因为他是通DHCP服务获取的,DHCP服务每次重启都会重新获取一次ip,VMware编辑中有一个虚拟网络编辑器
|
14天前
|
监控 算法 Linux
Linux下工具tc详细讲解及限制IP和端口实例
TC (Traffic Control) 是Linux内核中提供的一个用于控制和管理网络流量的强大工具,它允许用户实现QoS(Quality of Service)策略,包括带宽限制、优先级控制、延迟保证等。TC基于内核的队列 discipline (qdisc) 和流量类别(class) 体系结构,允许对进入或离开网络接口的数据流进行复杂的整形和过滤。
|
14天前
|
域名解析 网络协议 Linux
linux网络-- 手动配置ip地址
linux网络-- 手动配置ip地址
|
20天前
|
网络协议 算法 Linux
技术笔记:Linux学习:TCP粘包问题
技术笔记:Linux学习:TCP粘包问题
17 0