10-socket的实践到内核--UDP的socket数据的接收

来源:互联网 发布:雪梨的淘宝店铺叫什么 编辑:程序博客网 时间:2024/05/16 01:16
我们在本章的第一节中的练习代码中曾经看到socket是通过

write(sockfd, &ch, 1);
read(sockfd, &ch, 1);

来发送和接受数据的,熟悉文件系统的朋友可以知道这二个函数在系统调用中的过程,当然还有另外一对数据发送的界面函数send()和recv(),我们还是考虑大多数朋友的立场上简单的介绍一下,write和read是从当前进程的结构task_struct中,找到通过打开文件号找到对应的file结构,然后通过其钩子指针f_op,则可以找到file_operations钩子结构socket_file_ops,这个结构在/net/socket.c的124行处

static const struct file_operations socket_file_ops = {
    .owner =    THIS_MODULE,
    .llseek =    no_llseek,
    .aio_read =    sock_aio_read,
    .aio_write =    sock_aio_write,
    .poll =        sock_poll,
    .unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = compat_sock_ioctl,
#endif
    .mmap =        sock_mmap,
    .open =        sock_no_open,    /* special open code to disallow open via /proc */
    .release =    sock_close,
    .fasync =    sock_fasync,
    .sendpage =    sock_sendpage,
    .splice_write = generic_splice_sendpage,
    .splice_read =    sock_splice_read,
};

我是无名小卒,2008年3月开始写技术博客,博客中的内核及开发技巧的多篇文章在互联网上被转载流传,得到广泛的关注。http://qinjiana0786.cublog.cn 我们继续分析,2.6.26内核已经增加了二个钩子函数,一个是aio_read另一个是aio_write,所以从上面的结构中我们可以看出相应的write和read是实际上是这里的aio_write和aio_read,为什么会增加这二个新的钩子函数?从字面上aio可以看出应该是async io的意思,也就是说这二个钩子函数专门针对设备的异步操作所使用的,尽管我们没有解释什么能从应用程序到这里的异步操作钩子函数,以及中间从库到系统调用到这里的过程,但是我们将来肯定在文件系统的过程会走到这个过程,在那里我们会详细的分析和总结,朋友们暂且知道这里我们的write和read应用程序会执行上面二个异步操作的钩子函数,首先我们来看sock_aio_write

static ssize_t sock_aio_write(struct kiocb *iocb, const struct iovec *iov,
             unsigned long nr_segs, loff_t pos)
{
    struct sock_iocb siocb, *x;

    if (pos != 0)
        return -ESPIPE;

    x = alloc_sock_iocb(iocb, &siocb);
    if (!x)
        return -ENOMEM;

    return do_sock_write(&x->async_msg, iocb, iocb->ki_filp, iov, nr_segs);
}

我们同时把send()的调用函数一起看一下,先从系统调用总入口sys_socketcall()看一下路径

    case SYS_SEND:
        err = sys_send(a0, (void __user *)a1, a[2], a[3]);

进一步进入sys_send()

asmlinkage long sys_send(int fd, void __user *buff, size_t len, unsigned flags)
{
    return sys_sendto(fd, buff, len, flags, NULL, 0);
}

我们看到他调用的是sys_sendto()函数

asmlinkage long sys_sendto(int fd, void __user *buff, size_t len,
             unsigned flags, struct sockaddr __user *addr,
             int addr_len)
{
    struct socket *sock;
    char address[MAX_SOCK_ADDR];
    int err;
    struct msghdr msg;
    struct iovec iov;
    int fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    iov.iov_base = buff;
    iov.iov_len = len;
    msg.msg_name = NULL;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_namelen = 0;
    if (addr) {
        err = move_addr_to_kernel(addr, addr_len, address);
        if (err < 0)
            goto out_put;
        msg.msg_name = address;
        msg.msg_namelen = addr_len;
    }
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    msg.msg_flags = flags;
    err = sock_sendmsg(sock, &msg, len);

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

好我们暂且追踪到这里停一下,我们再看上边说到的sock_aio_write,可以看到他最后调用了另一个函数

static ssize_t do_sock_write(struct msghdr *msg, struct kiocb *iocb,
            struct file *file, const struct iovec *iov,
            unsigned long nr_segs)
{
    struct socket *sock = file->private_data;
    size_t size = 0;
    int i;

    for (i = 0; i < nr_segs; i++)
        size += iov[i].iov_len;

    msg->msg_name = NULL;
    msg->msg_namelen = 0;
    msg->msg_control = NULL;
    msg->msg_controllen = 0;
    msg->msg_iov = (struct iovec *)iov;
    msg->msg_iovlen = nr_segs;
    msg->msg_flags = (file->f_flags & O_NONBLOCK) ? MSG_DONTWAIT : 0;
    if (sock->type == SOCK_SEQPACKET)
        msg->msg_flags |= MSG_EOR;

    return __sock_sendmsg(iocb, sock, msg, size);
}

对比一下上边的sys_sendto的代码,是不是基本相同呢?唯一不同的是最后调用的函数不同,我们把sock_sendmsg()看一下

int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
    struct kiocb iocb;
    struct sock_iocb siocb;
    int ret;

    init_sync_kiocb(&iocb, NULL);
    iocb.private = &siocb;
    ret = __sock_sendmsg(&iocb, sock, msg, size);
    if (-EIOCBQUEUED == ret)
        ret = wait_on_sync_kiocb(&iocb);
    return ret;
}

果然也调用了__sock_sendmsg,那证明了我们上边的猜测,他们的确基本相同,同时我们也可以想象得到read()和recv()他们分别调用的sock_aio_read()与sys_recv()也是基本相同的,不同之处主要是read和write是从文件的角度去找到socket,而sys_send和sys_recv是从hash表中找到socket.进一步我们看到实际上sys_send和sys_recv实际上是调用的sys_sendto和sys_recvfrom来实现的,一般来说使用TCP连接方式的经常使用write和read,面使用UDP连接方式的会使用sys_sendto和sys_recvfrom, 这二种连接在接收数据时都是非常不同的,UDP连接方式接受数据时,不会因为缓存区太小而自动扩充以接收超过缓存区大小的数据,他也不会因为缓存区太大而自动接收下一个数据包的数据以节省空间,但是TCP就可以在遇到较大的数据包时分隔到几次接收中,而如果缓存区较大时他也会把下次要接收的数据包先接收一部分来节省空间。所以二者各有特点,我们以前还讲过UDP的好处,首先是对QQ软件使用的一个例子。所以说,二者是各有利弊。我们从上面的对比之中看到了他们都调用了同一个函数

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);
}

这才是我们今天要追踪的重点,我们可以看到参数中有一个重要的数据结构struct msghdr

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;
};

上面数据结构中的变量我们过一会就会看到其具体的作用了,结构中包含了另一个结构变量iovec

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) */
};

上面结构中的iov_base就是缓冲区的地址指针,而iov_len则是缓冲区的长度,这里请朋友们注意,我在上边称为缓存区其实与缓冲区是一个概念,而iovec我们看到他在msghdr结构中是一个指针,C语言中指针与数组是一样的,可以看其后边的英语注释是数据块,所以这里将是缓冲区的指针数组。可能这里朋友们会将这里的数据缓冲的结构与我们这前讲到的sk_buff结构相混淆,有可能会问这二个都是为数据缓冲区啊,尽管二者都是用于数据缓冲作用,按照不同的协议,sk_buff是用于socket和socket之间的缓冲结构,而这里的msghdr则是用于进程和socket的缓冲结构。我们这里只针对代码没有探讨tcp协议和udp协议,所以不再探讨具体的协议内容。那么我们回到上面的代码中看到调用了

sock->ops->recvmsg()

我们可以回忆以前一直在强调的关于unix的tcp连接协议操作结构

static const struct proto_ops unix_stream_ops

再次把他的内容列一下

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

看到其实是转入了钩子函数unix_stream_recvmsg中,我们暂且在这里放一放这个函数,就象上边我们对比sys_sendto和write一样,我们在这里也看一下sys_recvmsg()

asmlinkage long sys_recvmsg(int fd, struct msghdr __user *msg,
             unsigned int flags)
{
    struct compat_msghdr __user *msg_compat =
     (struct compat_msghdr __user *)msg;
    struct socket *sock;
    struct iovec iovstack[UIO_FASTIOV];
    struct iovec *iov = iovstack;
    struct msghdr msg_sys;
    unsigned long cmsg_ptr;
    int err, iov_size, total_len, len;
    int fput_needed;

    /* kernel mode address */
    char addr[MAX_SOCK_ADDR];

    /* user mode address pointers */
    struct sockaddr __user *uaddr;
    int __user *uaddr_len;

    if (MSG_CMSG_COMPAT & flags) {
        if (get_compat_msghdr(&msg_sys, msg_compat))
            return -EFAULT;
    }
    else if (copy_from_user(&msg_sys, msg, sizeof(struct msghdr)))
        return -EFAULT;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    err = -EMSGSIZE;
    if (msg_sys.msg_iovlen > UIO_MAXIOV)
        goto out_put;

    /* Check whether to allocate the iovec area */
    err = -ENOMEM;
    iov_size = msg_sys.msg_iovlen * sizeof(struct iovec);
    if (msg_sys.msg_iovlen > UIO_FASTIOV) {
        iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL);
        if (!iov)
            goto out_put;
    }

    /*
     * Save the user-mode address (verify_iovec will change the
     * kernel msghdr to use the kernel address space)
     */


    uaddr = (__force void __user *)msg_sys.msg_name;
    uaddr_len = COMPAT_NAMELEN(msg);
    if (MSG_CMSG_COMPAT & flags) {
        err = verify_compat_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
    } else
        err = verify_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
    if (err < 0)
        goto out_freeiov;
    total_len = err;

    cmsg_ptr = (unsigned long)msg_sys.msg_control;
    msg_sys.msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);

    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    err = sock_recvmsg(sock, &msg_sys, total_len, flags);
    if (err < 0)
        goto out_freeiov;
    len = err;

    if (uaddr != NULL) {
        err = move_addr_to_user(addr, msg_sys.msg_namelen, uaddr,
                    uaddr_len);
        if (err < 0)
            goto out_freeiov;
    }
    err = __put_user((msg_sys.msg_flags & ~MSG_CMSG_COMPAT),
             COMPAT_FLAGS(msg));
    if (err)
        goto out_freeiov;
    if (MSG_CMSG_COMPAT & flags)
        err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr,
                 &msg_compat->msg_controllen);
    else
        err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr,
                 &msg->msg_controllen);
    if (err)
        goto out_freeiov;
    err = len;

out_freeiov:
    if (iov != iovstack)
        sock_kfree_s(sock->sk, iov, iov_size);
out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

以前我们分析了很多代码,上面的代码我们只关注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;
}

我们从上面看到他也是调用的__sock_recvmsg来实现的,我们在上面讲到__sock_recvmsg中的参数以及要调用具体的钩子函数,但是并没有分析函数其他部分的意义,我们再把代码贴过来

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);
}

我们还有一个结构struct sock_iocb没有看

struct sock_iocb {
    struct list_head    list;

    int            flags;
    int            size;
    struct socket        *sock;
    struct sock        *sk;
    struct scm_cookie    *scm;
    struct msghdr        *msg, async_msg;
    struct kiocb        *kiocb;
};

其内部有一个scm_cookie结构

struct scm_cookie
{
    struct ucred        creds;        /* Skb credentials    */
    struct scm_fp_list    *fp;        /* Passed files        */
#ifdef CONFIG_SECURITY_NETWORK
    u32            secid;        /* Passed security ID     */
#endif
    unsigned long        seq;        /* Connection seqno    */
};

这个结构是为了接收信息用的,则由此看出sock_iocb 是封装着接收到的数据,但是在scm_cookie中还有一个结构ucred

struct ucred {
    __u32    pid;
    __u32    uid;
    __u32    gid;
};

熟悉文件系统的朋友可能对这三个结构变量不陌生,他就是保存着的权限的id号,同时上面的scm_cookie还有另一个结构struct scm_fp_list

struct scm_fp_list
{
    int        count;
    struct file    *fp[SCM_MAX_FD];
};

#define SCM_MAX_FD    255

我们又看到了file指针,他是代表着打开的文件,这里我们看到指针数组的最大个数是255。上面其余的结构变量我们将在以后分析过程中明白其意义,但是我们看到上面的sock_iocb结构变量si,需要认真看一下,在chinaunix网站上曾看到有网友对这里的结构不理解其作用并且此问题提出了一年并没有得到答案,我们还是来做这个工作做细一下为以后的分析打下基础,首先我们看到调用了kiocb_to_siocb

static inline struct sock_iocb *kiocb_to_siocb(struct kiocb *iocb)
{
    return (struct sock_iocb *)iocb->private;
}

当然如果非常精通TCP的朋友可以会与协议栈相联系,我们只分析代码,这里我们看到struct kiocb结构,这个结构非常的大,我们稍后看,我们先谈一个概念是关于异步IO操作的,也是LINUX中的AIO,从字面上看应该是async I/O,我们以前说过异步是为了保证节省系统资源,既然进程等待的IO操作还没有到来就让他让出CPU,这样保证了多个进程同时对IO的操作,详细的关于AIO的知识和理论请朋友们看http://blog.csdn.net/wildwolf113/archive/2007/04/26/1585258.aspx,这篇文章写到了异步操作大大提高了应用程序性能,其中细讲了关于AIO的概念,理解AIO的概念直接影响我们对kiocb结构的分析,我们看他的结构

struct kiocb {
    struct list_head    ki_run_list;
    unsigned long        ki_flags;
    int            ki_users;
    unsigned        ki_key;        /* id of this request */

    struct file        *ki_filp;
    struct kioctx        *ki_ctx;    /* may be NULL for sync ops */
    int            (*ki_cancel)(struct kiocb *, struct io_event *);
    ssize_t            (*ki_retry)(struct kiocb *);
    void            (*ki_dtor)(struct kiocb *);

    union {
        void __user        *user;
        struct task_struct    *tsk;
    } ki_obj;

    __u64            ki_user_data;    /* user's data for completion */
    wait_queue_t        ki_wait;
    loff_t            ki_pos;

    void            *private;
    /* State that we remember to be able to restart/retry */
    unsigned short        ki_opcode;
    size_t            ki_nbytes;     /* copy of iocb->aio_nbytes */
    char             __user *ki_buf;    /* remaining iocb->aio_buf */
    size_t            ki_left;     /* remaining bytes */
    struct iovec        ki_inline_vec;    /* inline vector */
     struct iovec        *ki_iovec;
     unsigned long        ki_nr_segs;
     unsigned long        ki_cur_seg;

    struct list_head    ki_list;    /* the aio core uses this
                         * for cancellation */


    /*
     * If the aio_resfd field of the userspace iocb is not zero,
     * this is the underlying file* to deliver event to.
     */

    struct file        *ki_eventfd;
};

感觉非常的庞大了,不要担心,有问题先放一放,在代码的分析过程中自然会解开,简而言之这个数据结构就是为了异步的AIO操作所准备的,其内部的结构变量都是与AIO息息相关的,谈到这里有朋友可会疑问,我们讲的是socket,不是异步的AIO操作,如果我们阅读了文件系统的相关读写文件的内容,朋友们将会在那里看到也大量使用了这个数据结构,我们暂且把这个疑问带到将来去回答,必竟2.6的内核不断的在更新很多数据结构的作用不再是单一的了,就象file结构,linux将很多驱动都当做文件来操作,必须数据结构大量的得到利用,如果出现一种新的设备就在内核中建立新的结构,那内核就变的相当的庞大而且不易维护,那样的情况不是我们愿意看到的。我是无名小卒,与大家共享学习的快乐是我最大的愿望,尽管2008年3月开始写技术博客,博客中的内核及开发技巧的多篇文章在互联网上被转载流传,得到广泛的关注。http://qinjiana0786.cublog.cn ,其实我从事it行业已经十年整了,那时候还没有博客,如果早有的话,我会也朋友们共享更多的知识。好了我们继续看kiocb_to_siocb()函数,我们看到将kiocb的结构与sock_iocb结构联系起来了,回到__sock_recvmsg中我们看到

    si->sock = sock;
    si->scm = NULL;
    si->msg = msg;
    si->size = size;
    si->flags = flags;

其实也就是设置进了iocb变量的private指针处了,注意iocb是kiocb结构,然后我们接着看到在__sock_recvmsg的最后会将这个iocb结构变量传递给具体我们选择的哪种协议的钩子函数去执行,象资料中那样说的,我们先从简单的udp协议入手分析,理解了udp的协议就好分析tcp的协议的接收过程了,上面我们是贴出了tcp的钩子函数,我们看到那里是进入unix_stream_recvmsg去执行的,我们这里再看一下udp的

static const struct proto_ops unix_dgram_ops = {
。。。。。。
    .recvmsg =    unix_dgram_recvmsg,
。。。。。。
    };

看到其实进入了unix_dgram_recvmsg()钩子函数中了

static int unix_dgram_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);
    int noblock = flags & MSG_DONTWAIT;
    struct sk_buff *skb;
    int err;

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

    msg->msg_namelen = 0;

    mutex_lock(&u->readlock);

    skb = skb_recv_datagram(sk, flags, noblock, &err);
    if (!skb) {
        unix_state_lock(sk);
        /* Signal EOF on disconnected non-blocking SEQPACKET socket. */
        if (sk->sk_type == SOCK_SEQPACKET && err == -EAGAIN &&
         (sk->sk_shutdown & RCV_SHUTDOWN))
            err = 0;
        unix_state_unlock(sk);
        goto out_unlock;
    }

    wake_up_interruptible_sync(&u->peer_wait);

    if (msg->msg_name)
        unix_copy_addr(msg, skb->sk);

    if (size > skb->len)
        size = skb->len;
    else if (size < skb->len)
        msg->msg_flags |= MSG_TRUNC;

    err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, size);
    if (err)
        goto out_free;

    if (!siocb->scm) {
        siocb->scm = &tmp_scm;
        memset(&tmp_scm, 0, sizeof(tmp_scm));
    }
    siocb->scm->creds = *UNIXCREDS(skb);
    unix_set_secdata(siocb->scm, skb);

    if (!(flags & MSG_PEEK))
    {
        if (UNIXCB(skb).fp)
            unix_detach_fds(siocb->scm, skb);
    }
    else
    {
        /* It is questionable: on PEEK we could:
         - do not return fds - good, but too simple 8)
         - return fds, and do not return them on read (old strategy,
         apparently wrong)
         - clone fds (I chose it for now, it is the most universal
         solution)

         POSIX 1003.1g does not actually define this clearly
         at all. POSIX 1003.1g doesn't define a lot of things
         clearly however!

        */

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

    scm_recv(sock, msg, siocb->scm, flags);

out_free:
    skb_free_datagram(sk,skb);
out_unlock:
    mutex_unlock(&u->readlock);
out:
    return err;
}

我们看到这里又进一步将我们上面已经设置进kiocb中的值再次转换成sock_iocb结构变量siocb,接着我们看到他调用了skb_recv_datagram

struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags,
                 int noblock, int *err)
{
    int peeked;

    return __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
                 &peeked, err);
}

又进入了__skb_recv_datagram上面的代码对我们讲已经在前面的sys_accept()过程中阅读过了,不记得的朋友可以返回查看一下,这个函数的作用就是等待着,走到有数据包来时从sk_receive_queue队列中摘下一个数据的sk_buff结构,回到unix_dgram_recvmsg函数中,我们知道这里的skb已经是从队列中得到的sk_buff了,接着我们看到

wake_up_interruptible_sync(&u->peer_wait);

唤醒对方进程,是因为现在摘走了一个sk_buff结构必须队列空闲了一个位置,让发送进程继续往队列中发送数据包sk_buff,接着

    if (msg->msg_name)
        unix_copy_addr(msg, skb->sk);

如果我们这里的接收进程为发送进程的socket地址准备了一个缓冲区的话,这里就会通过

static void unix_copy_addr(struct msghdr *msg, struct sock *sk)
{
    struct unix_sock *u = unix_sk(sk);

    msg->msg_namelen = 0;
    if (u->addr) {
        msg->msg_namelen = u->addr->len;
        memcpy(msg->msg_name, u->addr->name, u->addr->len);
    }
}

将发送进程即服务进程的socket地址拷贝到了我们上面准备好的msghdr结构中msg_name处。接着我们看到要对接受的数据长度进行检查

    if (size > skb->len)
        size = skb->len;
    else if (size < skb->len)
        msg->msg_flags |= MSG_TRUNC;

    err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, size);

如果接收的数据长度大于数据缓冲的大小这里就要截掉了多余的数据,确定长度后就会进入skb_copy_datagram_iovec中了

int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
             struct iovec *to, int len)
{
    int start = skb_headlen(skb);
    int i, copy = start - offset;

    /* Copy header. */
    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;
    }

    /* Copy paged appendix. Hmm... why does this look so complicated? */
    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
        int end;

        BUG_TRAP(start <= offset + len);

        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 <= offset + len);

            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;
}

上面的代码尽管有些长,其实很简单就是将sk_buff中的数据拷贝到我们为接收准备好的iovec指针队列也可以称为数组中了,我们说过iovec代表着缓冲区,回到上面的unix_dgram_recvmsg处,我们接下看到

siocb->scm->creds = *UNIXCREDS(skb);

#define UNIXCREDS(skb)    (&UNIXCB((skb)).creds)

#define UNIXCB(skb)     (*(struct unix_skb_parms*)&((skb)->cb))

struct unix_skb_parms {
    struct ucred        creds;        /* Skb credentials    */
    struct scm_fp_list    *fp;        /* Passed files        */
#ifdef CONFIG_SECURITY_NETWORK
    u32            secid;        /* Security ID        */
#endif
};

也就是将接收到的数据包中的载运着服务器端的身份信息也拷贝到我们这里提到过的scm_cookie接收缓冲区中,注意这里层层的指针就代表着总体是在siocb结构中的,而等号后边指针符号*是取后面指针处的内容,这里有些朋友可容易混淆,这就是指针的一次性复制,将所以数据结构中的内容都复制给了scm_cookie中的creds处的结构。通过上面我们可以看出数据复制到了我们提供的缓冲区中iovec中了,而附加的信息都在scm_cookie结构中了,我们继续往下看代码

unix_detach_fds(siocb->scm, skb);

调用了unix_detach_fds来把所有服务器进程传递过来的关于对文件的授权全部进行“冲帐”操作,按照资料上介绍,服务器进程可以将文件的授权通过sk_buff传递给客户端的socket进程,客户端必须义务对正在传送的的这些文件的授权进行清除工作。这句代码有一个判断语句中

!(flags & MSG_PEEK)

也就是说假如只是为了查看一下,而不是真正来接收数据的,就不要对文件的传递授权清理了。我们看清理的函数

static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb)
{
    int i;

    scm->fp = UNIXCB(skb).fp;
    skb->destructor = sock_wfree;
    UNIXCB(skb).fp = NULL;

    for (i=scm->fp->count-1; i>=0; i--)
        unix_notinflight(scm->fp->fp[i]);
}

上面,我们看到他将struct unix_skb_parms 结构中的struct scm_fp_list文件列表也一次复制给了我们这里的scm_cookie接收附加信息的结构,同时为数据缓冲区准备了释放函数

scm->fp = UNIXCB(skb).fp;

skb->destructor = sock_wfree;

接着看到他调用了

void unix_notinflight(struct file *fp)
{
    struct sock *s = unix_get_socket(fp);
    if(s) {
        struct unix_sock *u = unix_sk(s);
        spin_lock(&unix_gc_lock);
        BUG_ON(list_empty(&u->link));
        if (atomic_dec_and_test(&u->inflight))
            list_del_init(&u->link);
        unix_tot_inflight--;
        spin_unlock(&unix_gc_lock);
    }
}

函数中首先调用了unix_get_socket

static struct sock *unix_get_socket(struct file *filp)
{
    struct sock *u_sock = NULL;
    struct inode *inode = filp->f_path.dentry->d_inode;

    /*
     *    Socket ?
     */

    if (S_ISSOCK(inode->i_mode)) {
        struct socket * sock = SOCKET_I(inode);
        struct sock * s = sock->sk;

        /*
         *    PF_UNIX ?
         */

        if (s && sock->ops && sock->ops->family == PF_UNIX)
            u_sock = s;
    }
    return u_sock;
}

这个函数是根据file文件指针找到文件的inode节点并判断是否socket的inode,如果检测通过就unix_notinflight中对其操作,我们看到inflight和link进行了“冲帐”操作。并且unix_tot_inflight计数器递减代表“飞行中”数目减少了。我们看到如果上面不是查看呢,那么就会执行

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

这里仅仅把服务器进程的打列文件列表复制过来,而没有进行清理操作。接着我们看到 scm_recv(sock, msg, siocb->scm, flags);

static __inline__ void scm_recv(struct socket *sock, struct msghdr *msg,
                struct scm_cookie *scm, int flags)
{
    if (!msg->msg_control)
    {
        if (test_bit(SOCK_PASSCRED, &sock->flags) || scm->fp)
            msg->msg_flags |= MSG_CTRUNC;
        scm_destroy(scm);
        return;
    }

    if (test_bit(SOCK_PASSCRED, &sock->flags))
        put_cmsg(msg, SOL_SOCKET, SCM_CREDENTIALS, sizeof(scm->creds), &scm->creds);

    scm_passec(sock, msg, scm);

    if (!scm->fp)
        return;
    
    scm_detach_fds(msg, scm);
}

我们首先看到要对msghdr是否为附加信息准备了缓冲区,如果没有scm_destroy(scm);就销毁了我们的scm_cookie结构,实际上就是把其缓冲区释放掉而结构体因为是局部变量所以不需要释放。接着put_cmsg把这些附加信息复制到msghdr结构中。下面是对传递过来的打开文件授权进行处理scm_detach_fds

void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
{
    struct cmsghdr __user *cm
        = (__force struct cmsghdr __user*)msg->msg_control;

    int fdmax = 0;
    int fdnum = scm->fp->count;
    struct file **fp = scm->fp->fp;
    int __user *cmfptr;
    int err = 0, i;

    if (MSG_CMSG_COMPAT & msg->msg_flags) {
        scm_detach_fds_compat(msg, scm);
        return;
    }

    if (msg->msg_controllen > sizeof(struct cmsghdr))
        fdmax = ((msg->msg_controllen - sizeof(struct cmsghdr))
             / sizeof(int));

    if (fdnum < fdmax)
        fdmax = fdnum;

    for (i=0, cmfptr=(__force int __user *)CMSG_DATA(cm); i<fdmax;
     i++, cmfptr++)
    {
        int new_fd;
        err = security_file_receive(fp[i]);
        if (err)
            break;
        err = get_unused_fd_flags(MSG_CMSG_CLOEXEC & msg->msg_flags
                     ? O_CLOEXEC : 0);
        if (err < 0)
            break;
        new_fd = err;
        err = put_user(new_fd, cmfptr);
        if (err) {
            put_unused_fd(new_fd);
            break;
        }
        /* Bump the usage count and install the file. */
        get_file(fp[i]);
        fd_install(new_fd, fp[i]);
    }

    if (i > 0)
    {
        int cmlen = CMSG_LEN(i*sizeof(int));
        err = put_user(SOL_SOCKET, &cm->cmsg_level);
        if (!err)
            err = put_user(SCM_RIGHTS, &cm->cmsg_type);
        if (!err)
            err = put_user(cmlen, &cm->cmsg_len);
        if (!err) {
            cmlen = CMSG_SPACE(i*sizeof(int));
            msg->msg_control += cmlen;
            msg->msg_controllen -= cmlen;
        }
    }
    if (i < fdnum || (fdnum && fdmax <= 0))
        msg->msg_flags |= MSG_CTRUNC;

    
/*
     * All of the files that fit in the message have had their
     * usage counts incremented, so we just free the list.
     */

    __scm_destroy(scm);
}

上面程序中,fdmax是msghdr需要为这些文件指针准备的接收缓冲区数量,fdnum是服务器端传过来的总共打开的文件数。这段代码就是将接受到的文件ID号安装到进程的打开文件号列表中。主要是一系列的复制操作,建议朋友们有以后的文件系统的分析后再回来读一下这段代码,如果有基础的朋友这段代码不是难事。

最后我们看到通过skb_free_datagram(sk,skb);把sk_buff的缓冲区释放掉了。因为数据和附加信息都已经保存在msghdr以及scm_cookie中了,从这里可以看出sk_buff是与底层密切相关的数据缓冲区,而msghdr是与上层密切了,所以我们说到缓冲区结构时,总是给一种感觉“不是已经有一个缓冲区结构了”的误解,必竟TCP协议和UDP协议是这样规定的。我们层层返回到sock_recvmsg()函数中,下一步是调用了wait_on_sync_kiocb

ssize_t wait_on_sync_kiocb(struct kiocb *iocb)
{
    while (iocb->ki_users) {
        set_current_state(TASK_UNINTERRUPTIBLE);
        if (!iocb->ki_users)
            break;
        io_schedule();
    }
    __set_current_state(TASK_RUNNING);
    return iocb->ki_user_data;
}

到这里我们暂且不再追踪io_schedule函数了,因为他是属于文件系统AIO部分内容了,其实相当于告诉内核进程异步等待IO,其功能等同于进程的调度schedule()函数。
原创粉丝点击