Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1324209
  • 博文数量: 107
  • 博客积分: 10155
  • 博客等级: 上将
  • 技术积分: 2166
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-25 16:57
文章分类

全部博文(107)

文章存档

2010年(1)

2009年(1)

2008年(105)

分类: LINUX

2008-10-28 08:06:54

我们接着上一节数据的接收来自看UDP的数据是如何发送的,上一节中我们贴出有关发送的代码,在那里只是为了让大家有一个印象

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

最终我们在上一节中看到发送数据都是调用的__sock_sendmsg来完成的,朋友们忘记了可以回去看一下,我们直接贴出这个代码

static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
                 struct msghdr *msg, size_t size)
{
    struct sock_iocb *si = kiocb_to_siocb(iocb);
    int err;

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

    err = security_socket_sendmsg(sock, msg, size);
    if (err)
        return err;

    return sock->ops->sendmsg(iocb, sock, msg, size);
}

代码前面对比一下接收的__sock_recvmsg,可以发现基本相同,只是不同的地方是这里调用了UDP的sendmsg来发送数据,我们再次把钩子结构贴出来

static const struct proto_ops unix_dgram_ops = {
。。。。。。
    .sendmsg =    unix_dgram_sendmsg,
。。。。。。
};

可以看到调用了uinx_dgram_sendmsg

static int unix_dgram_sendmsg(struct kiocb *kiocb, struct socket *sock,
             struct msghdr *msg, size_t len)
{
    struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct unix_sock *u = unix_sk(sk);
    struct sockaddr_un *sunaddr=msg->msg_name;
    struct sock *other = NULL;
    int namelen = 0; /* fake GCC */
    int err;
    unsigned hash;
    struct sk_buff *skb;
    long timeo;
    struct scm_cookie tmp_scm;

    if (NULL == siocb->scm)
        siocb->scm = &tmp_scm;
    err = scm_send(sock, msg, siocb->scm);
    if (err < 0)
        return err;

    err = -EOPNOTSUPP;
    if (msg->msg_flags&MSG_OOB)
        goto out;

    if (msg->msg_namelen) {
        err = unix_mkname(sunaddr, msg->msg_namelen, &hash);
        if (err < 0)
            goto out;
        namelen = err;
    } else {
        sunaddr = NULL;
        err = -ENOTCONN;
        other = unix_peer_get(sk);
        if (!other)
            goto out;
    }

    if (test_bit(SOCK_PASSCRED, &sock->flags)
        && !u->addr && (err = unix_autobind(sock)) != 0)
        goto out;

    err = -EMSGSIZE;
    if (len > sk->sk_sndbuf - 32)
        goto out;

上面代码中除了象接收socket一样做了必要的检查外调用了scm_send函数

static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,
             struct scm_cookie *scm)
{
    struct task_struct *p = current;
    scm->creds.uid = p->uid;
    scm->creds.gid = p->gid;
    scm->creds.pid = task_tgid_vnr(p);
    scm->fp = NULL;
    scm->seq = 0;
    unix_get_peersec_dgram(sock, scm);
    if (msg->msg_controllen <= 0)
        return 0;
    return __scm_send(sock, msg, scm);
}

这里将进程的信息设置进scm_cookie中的creds身份结构中。

int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
{
    struct cmsghdr *cmsg;
    int err;

    for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg))
    {
        err = -EINVAL;

        /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */
        /* The first check was omitted in <= 2.2.5. The reasoning was
         that parser checks cmsg_len in any case, so that
         additional check would be work duplication.
         But if cmsg_level is not SOL_SOCKET, we do not check
         for too short ancillary data object at all! Oops.
         OK, let's add it...
         */

        if (!CMSG_OK(msg, cmsg))
            goto error;

        if (cmsg->cmsg_level != SOL_SOCKET)
            continue;

        switch (cmsg->cmsg_type)
        {
        case SCM_RIGHTS:
            err=scm_fp_copy(cmsg, &p->fp);
            if (err<0)
                goto error;
            break;
        case SCM_CREDENTIALS:
            if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
                goto error;
            memcpy(&p->creds, CMSG_DATA(cmsg), sizeof(struct ucred));
            err = scm_check_creds(&p->creds);
            if (err)
                goto error;
            break;
        default:
            goto error;
        }
    }

    if (p->fp && !p->fp->count)
    {
        kfree(p->fp);
        p->fp = NULL;
    }
    return 0;

error:
    scm_destroy(p);
    return err;
}

我们看到上面的代码与前面一节的scm_recv是对应的,回到上面的主函数unix_dgram_sendmsg中,我们看到要判断msg->msg_namelen来确定是否提供了目标方的地址,如果有的话就要使用unix_mkname“格式化”地址。如果没有提供地址就说明已经通过connect已经设置了,所以要通过unix_peer_get得到目标地址的sock,并赋值给other指针。接着判断是否要求传送身份信息,但是要求了又没有为指定地址的话就要通过unix_autobind自动生成一个地址。len > sk->sk_sndbuf - 32是因为sk_sndbuf是保存缓存区大小的结构变量,其中要留出32个字节的控制信息。继续往下看

skb = sock_alloc_send_skb(sk, len, msg->msg_flags&MSG_DONTWAIT, &err);
    if (skb==NULL)
        goto out;

    memcpy(UNIXCREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
    if (siocb->scm->fp)
        unix_attach_fds(siocb->scm, skb);
    unix_get_secdata(siocb->scm, skb);

    skb_reset_transport_header(skb);
    err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
    if (err)
        goto out_free;

    timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);

首先为发送缓冲区准备一个sk_buff,接着将我们上面准备的身份信息拷贝到这个结构中,然后进入unix_attach_fds中

static void unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
{
    int i;
    for (i=scm->fp->count-1; i>=0; i--)
        unix_inflight(scm->fp->fp[i]);
    UNIXCB(skb).fp = scm->fp;
    skb->destructor = unix_destruct_fds;
    scm->fp = NULL;
}

代码中调用了

void unix_inflight(struct file *fp)
{
    struct sock *s = unix_get_socket(fp);
    if(s) {
        struct unix_sock *u = unix_sk(s);
        spin_lock(&unix_gc_lock);
        if (atomic_inc_return(&u->inflight) == 1) {
            BUG_ON(!list_empty(&u->link));
            list_add_tail(&u->link, &gc_inflight_list);
        } else {
            BUG_ON(list_empty(&u->link));
        }
        unix_tot_inflight++;
        spin_unlock(&unix_gc_lock);
    }
}

也就是说如果file是代表着sock的话,就要“记帐”了,以前我们看到在接收信息后要“冲帐”。然后函数调用了memcpy_fromiovec

int memcpy_fromiovec(unsigned char *kdata, struct iovec *iov, int len)
{
    while (len > 0) {
        if (iov->iov_len) {
            int copy = min_t(unsigned int, len, iov->iov_len);
            if (copy_from_user(kdata, iov->iov_base, copy))
                return -EFAULT;
            len -= copy;
            kdata += copy;
            iov->iov_base += copy;
            iov->iov_len -= copy;
        }
        iov++;
    }

    return 0;
}

这是其实就是将我们的数据拷贝到缓冲区中。数据在iovec结构变量指针处,我们看到是以此为地址拷贝到内核空间的,copy是确定要拷贝的数据大小。从msghdr结构中可以看出用户空间的缓冲区可以是分散的,而iovec负责指向这些数据缓冲区,而我们的sk_buff缓冲区只有一个所以这里的大小是所有用户缓冲区的长度和。而这个长度的变化是由skb_put来完成的

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp = skb_tail_pointer(skb);
    SKB_LINEAR_ASSERT(skb);
    skb->tail += len;
    skb->len += len;
    if (unlikely(skb->tail > skb->end))
        skb_over_panic(skb, len, __builtin_return_address(0));
    return tmp;
}

函数的意思是根据数据的长度调整tail指针和数据长度len结构变量。回到发送函数中,下面是调用了sock_sndtimeo

static inline long sock_sndtimeo(const struct sock *sk, int noblock)
{
    return noblock ? 0 : sk->sk_sndtimeo;
}

代码很简单,要是不允许阻塞就要返回0了,否则就要返回设定的时间。回到函数往下看,接着要发送了

restart:
    if (!other) {
        err = -ECONNRESET;
        if (sunaddr == NULL)
            goto out_free;

        other = unix_find_other(net, sunaddr, namelen, sk->sk_type,
                    hash, &err);
        if (other==NULL)
            goto out_free;
    }

    unix_state_lock(other);
    err = -EPERM;
    if (!unix_may_send(sk, other))
        goto out_unlock;

    if (sock_flag(other, SOCK_DEAD)) {
        /*
         *    Check with 1003.1g - what should
         *    datagram error
         */

        unix_state_unlock(other);
        sock_put(other);

        err = 0;
        unix_state_lock(sk);
        if (unix_peer(sk) == other) {
            unix_peer(sk)=NULL;
            unix_state_unlock(sk);

            unix_dgram_disconnected(sk, other);
            sock_put(other);
            err = -ECONNREFUSED;
        } else {
            unix_state_unlock(sk);
        }

        other = NULL;
        if (err)
            goto out_free;
        goto restart;
    }

代码到这里朋友们如果前面一直跟着学习的话,并不难理解这里,如果我们上面已经找到了通过connect建立的sock,那么这时候other就已经指向了目标的sock,但是如果没有指向的话,就说明我们还没有走connect,而通过指定地址设置的目标的sock,所以这里unix_find_other找到目标的sock,此函数我们以前看过了,找到了目标的sock结构后,就要unix_may_send判断一下是否可以发送,以前我们也说过这个函数了。紧接着一个比较长的if判断语句段,首先我们看到是if (sock_flag(other, SOCK_DEAD)) 判断的目标方的sock是否还能够使用,接着我们继续往下看

err = -EPIPE;
    if (other->sk_shutdown & RCV_SHUTDOWN)
        goto out_unlock;

    if (sk->sk_type != SOCK_SEQPACKET) {
        err = security_unix_may_send(sk->sk_socket, other->sk_socket);
        if (err)
            goto out_unlock;
    }

    if (unix_peer(other) != sk && unix_recvq_full(other)) {
        if (!timeo) {
            err = -EAGAIN;
            goto out_unlock;
        }

        timeo = unix_wait_for_peer(other, timeo);

        err = sock_intr_errno(timeo);
        if (signal_pending(current))
            goto out_free;

        goto restart;
    }

    skb_queue_tail(&other->sk_receive_queue, skb);
    unix_state_unlock(other);
    other->sk_data_ready(other, len);
    sock_put(other);
    scm_destroy(siocb->scm);
    return len;

out_unlock:
    unix_state_unlock(other);
out_free:
    kfree_skb(skb);
out:
    if (other)
        sock_put(other);
    scm_destroy(siocb->scm);
    return err;
}

结合以前章节中介绍的这里重点将缓冲区挂入到目标方的sock的sk_receive_queue队列头中,这是通过

void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)
{
    unsigned long flags;

    spin_lock_irqsave(&list->lock, flags);
    __skb_queue_tail(list, newsk);
    spin_unlock_irqrestore(&list->lock, flags);
}

其余代码很简单了,到这里我们对UDP的socket数据的发送分析完了,因为UDP的数据发送和接收较TCP的简单,所以我们先介绍了,下一节我们对TCP的数据发送和接收分析一下。
阅读(4838) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

xiekai_aiai2014-01-14 15:50:14

高人,请教一个问题,对于非阻塞的udp socket,网上有人说,udp 是没有发送缓冲区的,那么调用setsockopt设置udp socket的发送缓冲区还有作用吗?  还有就是,如果没有udp socket没有发送缓冲区,那么所有的udp socket发送缓冲区是不是都对于同一个系统缓冲区呢?