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

全部博文(107)

文章存档

2010年(1)

2009年(1)

2008年(105)

分类: LINUX

2008-10-22 09:00:32

有些事情非常的有意思,开始这节之前我想谈一下“吹牛的人”,某企业为了获取高额利润请来一位所谓的高人,而此人更是大吹其专业,一边挺起自信的啤酒肚一边海吹自己开公司时如何自主研发的业绩,谈吐之间不乏高昂的旋律,不时摆出一副官人的态势,第一次其曰“核心板是买来的”第二次改曰“核心板是自己做的”,一阵胡吹海捧之后,企业授职“副总”,某日,此兄偷偷从采购那里将公司另一位工程师的核心板文件拷走,即日,此兄更以“副总”身份向该工程师“名正言顺”要来核心板产品,与偷来的核心板文件一起发给深圳其熟悉的工程师代工去了。我不想说太多,只想给朋友们提个醒,千万不要做这种“吹牛的人”,狐狸的尾巴总会露出来,做学问如果都象这种人,不仅仅是欺骗更严重损害了他人的利益,对公司发展极为不利甚至于“庸才挡道”的反作用。我还想提醒企业,不要只看文凭和学历或者个人的精彩简历,要知道擅长此类的“吹牛的人”不在少数,这也是造假行业倔起的原因,这种人万一进入企业,就象一条蛀虫会搞空企业的生存支柱,严重者更会阻碍企业引进人才,因为他们不想被人揪出自己的狐狸尾巴。开始今天的分析,我们还是先从应用程序界面看一下   

connect(sockfd, (struct sockaddr *)&address, len)

我们看到第二个参数是从应用程序中提前初始化了

address.sin_family = AF_INET;

address.sin_addr.s_addr = inet_addr("127.0.0.1");

address.sin_port = htons(9734);

sockaddr结构我们之前说过了,这里我们直接进入系统调用总入口sys_socketcall()看一下

case SYS_CONNECT:

 err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);

 break;

上面应用程序界面的参数全部传递到了这里,我们追踪sys_connect()

asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
             int addrlen)
{
    struct socket *sock;
    char address[MAX_SOCK_ADDR];
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;
    err = move_addr_to_kernel(uservaddr, addrlen, address);
    if (err < 0)
        goto out_put;

    err =
     security_socket_connect(sock, (struct sockaddr *)address, addrlen);
    if (err)
        goto out_put;

    err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
                 sock->file->f_flags);
out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

我们之前一直强调了关于unix的二个协议钩子函数

static const struct proto_ops unix_stream_ops

static const struct proto_ops unix_dgram_ops

socket中的ops根据确定的协议来将这二个钩子结构挂入,这里将要即看tcp协议的数据流式的连接,也要看udp的数据报的连接,有的资料称为非数据流。我们知道QQ就是采用的非数据流的协议。这也就是他为什么能不受“网络尖兵”等程序的限制。好了sys_connect()中前面我们在以前都分析过了,只接走到关键的部分

err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,sock->file->f_flags);

也就是我们上面的钩子结构中unix_stream_ops中,一会我们再看另一个unix_dgram_ops

static const struct proto_ops unix_stream_ops =

{

......

 .connect = unix_stream_connect,

......

};

很显然通过钩子结构进入了unix_stream_connect函数。这个结构在/net/unix/Af_unix.c的1004行处

static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
             int addr_len, int flags)
{
    struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct unix_sock *u = unix_sk(sk), *newu, *otheru;
    struct sock *newsk = NULL;
    struct sock *other = NULL;
    struct sk_buff *skb = NULL;
    unsigned hash;
    int st;
    int err;
    long timeo;

    err = unix_mkname(sunaddr, addr_len, &hash);
    if (err < 0)
        goto out;
    addr_len = err;

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

    timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);

    /* First of all allocate resources.
     If we will make it after state is locked,
     we will have to recheck all again in any case.
     */


    err = -ENOMEM;

    /* create new sock for complete connection */
    newsk = unix_create1(sock_net(sk), NULL);
    if (newsk == NULL)
        goto out;

    /* Allocate skb for sending to listening sock */
    skb = sock_wmalloc(newsk, 1, 0, GFP_KERNEL);
    if (skb == NULL)
        goto out;

我们分段来看,首先在上面的代码中先调用的unix_mkname()这里是根据我们应用程序提供的sockaddr地址对其进行hash处理,我们以前说起过,下边的

test_bit(SOCK_PASSCRED, &sock->flags)

却是因为socket的flags标志中是否有SOCK_PASSCRED标记,如果有就代表此socket可以将自己的身份传递给对方,所以那句if语句的含义就是如果socket要求发送自己的地址给对方而又没有分配过地址的话就要通过unix_autobind()函数自动生成地址。接着我们看到与accept过程分析一样,这里也有一个定时器,这里会根据是否允许阻塞标志O_NONBLOCK来确立定时器。

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

我们可以想一下如果没有得到服务器端的accept()时,这里也会进入睡眠。我们接着看代码接着调用了unix_create1()函数创建了一个新的sock结构,这个结构我们以前介绍过了,实际上这里创建的sock是为服务器端的socket所准备的,因为我们在上一节看到服务器端的accept()时是取得的客户端创建的一个sock,就是在这里创建的,之后传送给服务器端的socket。而有关数据即发送连接请求数据包,则能过下面函数新创建一个

struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force,gfp_t priority)
{
    if (force || atomic_read(&sk->sk_wmem_alloc) < sk->sk_sndbuf) {
        struct sk_buff * skb = alloc_skb(size, priority);
        if (skb) {
            skb_set_owner_w(skb, sk);
            return skb;
        }
    }
    return NULL;
}

这个函数是专门为socket发送数据缓冲区分配一个数据包结构 sk_buff,这个结构我们也看过了,这里再次强调因为我们是从实践到内核的有些结构和函数只要前面讲过我们就不再重复了,所以朋友们如果不明白请看一下前面的章节,我还是推荐朋友们从我的此类的头一篇开始阅读。那样不至于头尾不接。sk->sk_wmem_alloc是对分配sk_buff的计数器,这里检查是否大于sk->sk_sndbuf这个限定要求,这里前面使用了“或”,所以如果使用force即强制的话,就不会检查后边的是否超过限定了,此后分配结构后要进入

static inline void skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
{
    sock_hold(sk);
    skb->sk = sk;
    skb->destructor = sock_wfree;
    atomic_add(skb->truesize, &sk->sk_wmem_alloc);
}

这里对结构进一步初始化,今天我们继续上面的分析,首先我们可以看到参数sk是从系统调用中找到的服务器端中socket所指向的sock,这里就是让数据包的缓存区指针sk指向这个服务器的sock,另外我们看到将它的destructor,英文的意思是释构,也就是将释放的钩子函数确定为sock_wfree,它将负责释放数据包的缓冲区。接下来将数据包的大小添加到服务器端的总缓存数目中。让我们再回到unix_stream_connect中,继续往下看

restart:
    /* Find listening sock. */
    other = unix_find_other(net, sunaddr, addr_len, sk->sk_type, hash, &err);
    if (!other)
        goto out;

    /* Latch state of peer */
    unix_state_lock(other);

    /* Apparently VFS overslept socket death. Retry. */
    if (sock_flag(other, SOCK_DEAD)) {
        unix_state_unlock(other);
        sock_put(other);
        goto restart;
    }

    err = -ECONNREFUSED;
    if (other->sk_state != TCP_LISTEN)
        goto out_unlock;

    if (unix_recvq_full(other)) {
        err = -EAGAIN;
        if (!timeo)
            goto out_unlock;

        timeo = unix_wait_for_peer(other, timeo);

        err = sock_intr_errno(timeo);
        if (signal_pending(current))
            goto out;
        sock_put(other);
        goto restart;
    }

这里首先调用了unix_find_other函数,这个函数的作用我们需要看一下代码来总结

static struct sock *unix_find_other(struct net *net,
                 struct sockaddr_un *sunname, int len,
                 int type, unsigned hash, int *error)
{
    struct sock *u;
    struct nameidata nd;
    int err = 0;

    if (sunname->sun_path[0]) {
        err = path_lookup(sunname->sun_path, LOOKUP_FOLLOW, &nd);
        if (err)
            goto fail;
        err = vfs_permission(&nd, MAY_WRITE);
        if (err)
            goto put_fail;

        err = -ECONNREFUSED;
        if (!S_ISSOCK(nd.path.dentry->d_inode->i_mode))
            goto put_fail;
        u = unix_find_socket_byinode(net, nd.path.dentry->d_inode);
        if (!u)
            goto put_fail;

        if (u->sk_type == type)
            touch_atime(nd.path.mnt, nd.path.dentry);

        path_put(&nd.path);

        err=-EPROTOTYPE;
        if (u->sk_type != type) {
            sock_put(u);
            goto fail;
        }
    } else {
        err = -ECONNREFUSED;
        u=unix_find_socket_byname(net, sunname, len, type, hash);
        if (u) {
            struct dentry *dentry;
            dentry = unix_sk(u)->dentry;
            if (dentry)
                touch_atime(unix_sk(u)->mnt, dentry);
        } else
            goto fail;
    }
    return u;

put_fail:
    path_put(&nd.path);
fail:
    *error=err;
    return NULL;
}

可以看出他的主要做用是根据我们用户空间传递过来的服务器端的socket地址,找到对应的sock结构。这里还是关系到文件系统的内容,我们还是建议朋友们在这里读时先有一个大概的了解,不要求你一定理解,“意会”即可,达到“面熟”的效果就足以了。对了解文件系统的朋友可能觉得谈的不够深入,还是等待将来的分析再深入吧,“顾全大局”出发的看一下,首先上边的代码中根据地址能过path_lookup找到相应的目录项和目录项的索引节点,然后验证一下目录项的权限以及索引节点是否是socket,查找过程中会在内存中建立相应的目录项结构和索引节点结构,进一步能过unix_find_socket_byinode利用找到的索引节点在以前我们建立hash表中找到服务器端的socket。那个hash表就是unix_socket_table,如果找到了服务器端的socket就会通过sock_hold()函数增加他的使用计数,之后回到unix_stream_connect主体中,我们的other就指向了服务器端的socket。接着就对我们找到的socket进行相关的检查,要检查他的状态,为什么这里要检查socket的SOCK_DEAD状态,那是因为我们的程序有可能是在多处理器smp框架结构中运行的,面对多个cpu,有可能我们在这边要对socket进行连接,而另一边的处理器已经将其删除了,尽管我们现在谈的是unix的socket,可是必须要考虑到多处理器的情况,因为linux是多用途的高级操作系统。所以我们可以看到程序中有很多的宏判断,甚至于很多的我们以前没有见过的结构和变量,这些都是考虑全局的应用,即把linux的各种应用环境都包含了。正象网上所说linux从来不是为哪一种专门的cpu而开发的。我们上边提到过sock_hold()对socket的计数器加操作,那是为了避免这种情况的主要方法,就象是锁,只要计数器不为0就不能删除,这就避免了上面情况的出现,另外操作完后,何时会将计数器减操作呢。那是通过下边操作完成的

static inline void sock_put(struct sock *sk)
{
    if (atomic_dec_and_test(&sk->sk_refcnt))
        sk_free(sk);
}

我们看到调用了sk_free()函数

void sk_free(struct sock *sk)
{
    struct sk_filter *filter;

    if (sk->sk_destruct)
        sk->sk_destruct(sk);

    filter = rcu_dereference(sk->sk_filter);
    if (filter) {
        sk_filter_uncharge(sk, filter);
        rcu_assign_pointer(sk->sk_filter, NULL);
    }

    sock_disable_timestamp(sk);

    if (atomic_read(&sk->sk_omem_alloc))
        printk(KERN_DEBUG "%s: optmem leakage (%d bytes) detected.\n",
         __func__, atomic_read(&sk->sk_omem_alloc));

    put_net(sock_net(sk));
    sk_prot_free(sk->sk_prot_creator, sk);
}

上面代码我们看到他调用了sk_destruct,释放sock,这函数钩子函数是在分配sock时挂入相应的函数的。上边代码最后调用

static void sk_prot_free(struct proto *prot, struct sock *sk)
{
    struct kmem_cache *slab;
    struct module *owner;

    owner = prot->owner;
    slab = prot->slab;

    security_sk_free(sk);
    if (slab != NULL)
        kmem_cache_free(slab, sk);
    else
        kfree(sk);
    module_put(owner);
}

这个函数是释放slab高速缓存中占用的空间,我们回到unix_stream_connect()函数中,代码中我们跳过了很多关于锁的操作,这是为了减轻朋友阅读代码的压力,我们只需要知道使用锁是为了更好的保护临界区使这里的操作更加的串行化,如果不明白这些术语请参阅资料。接着我们看到判断服务器端的socket是否处理监听状态了

    if (other->sk_state != TCP_LISTEN)
        goto out_unlock;

如果服务器端的socket还没有处在监听状态的话,就不能连接了,此后还要通过unix_recvq_full()检测是否服务器端的socket还能接受本次连接,判断是否超过了最大的连接数

static inline int unix_recvq_full(struct sock const *sk)
{
    return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog;
}

static inline __u32 skb_queue_len(const struct sk_buff_head *list_)
{
    return list_->qlen;
}

上面的代码很简单了,继续往下看,if (!timeo),是检查是否到时间了,也就是说如果还没有超时的话而且服务器端的socket已经满员就要进入unix_wait_for_peer()睡眠等待了

static long unix_wait_for_peer(struct sock *other, long timeo)
{
    struct unix_sock *u = unix_sk(other);
    int sched;
    DEFINE_WAIT(wait);

    prepare_to_wait_exclusive(&u->peer_wait, &wait, TASK_INTERRUPTIBLE);

    sched = !sock_flag(other, SOCK_DEAD) &&
        !(other->sk_shutdown & RCV_SHUTDOWN) &&
        unix_recvq_full(other);

    unix_state_unlock(other);

    if (sched)
        timeo = schedule_timeout(timeo);

    finish_wait(&u->peer_wait, &wait);
    return timeo;
}

可能朋友们觉得太长了,内核就是这么错宗交叉的,我是无名小卒,感谢大家的支持,这里的代码象我们上一节看到的一样,首先是为当前进程分配一个wait_queue_t,将结构,然后在prepare_to_wait_exclusive()函数中挂入unix_sock的等待队列peer_wait中,此后进入schedule_timeout函数中定时睡眠,等时间到了以后就返回到这里继续往下执行finish_wait函数

void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
    unsigned long flags;

    __set_current_state(TASK_RUNNING);
    /*
     * We can check for list emptiness outside the lock
     * IFF:
     * - we use the "careful" check that verifies both
     * the next and prev pointers, so that there cannot
     * be any half-pending updates in progress on other
     * CPU's that we haven't seen yet (and that might
     * still change the stack area.
     * and
     * - all other users take the lock (ie we can only
     * have _one_ other CPU that looks at or modifies
     * the list).
     */

    if (!list_empty_careful(&wait->task_list)) {
        spin_lock_irqsave(&q->lock, flags);
        list_del_init(&wait->task_list);
        spin_unlock_irqrestore(&q->lock, flags);
    }
}

函数中的注释很长,不过函数的总体作用就是从上面队列中摘链,回到unix_stream_connect中,我们看到有对信号的检测,先不管他,我们看到下边又跳回到restart处重新执行一遍连接,为什么这样呢?我们看到跳转的情形有二种,一种是我们上面介绍过的当服务器端的socket已经被删除时,另一种情形就是我们服务器端的满员的情况也就是上边我们进入睡眠的情况后,时间到了醒来了,醒来以后还要重新检查一遍。因为睡眠的过程中情况可能有变化。正常情况下会找到服务器端的socket并且可以顺利经过上边的代码“层层审查”,这之后我们继续往下看unic_stream_connect的代码

st = sk->sk_state;

    switch (st) {
    case TCP_CLOSE:
        /* This is ok... continue with connect */
        break;
    case TCP_ESTABLISHED:
        /* Socket is already connected */
        err = -EISCONN;
        goto out_unlock;
    default:
        err = -EINVAL;
        goto out_unlock;
    }

    unix_state_lock_nested(sk);

    if (sk->sk_state != st) {
        unix_state_unlock(sk);
        unix_state_unlock(other);
        sock_put(other);
        goto restart;
    }

上面的代码首先是对找到的我们客户端client的socket的状态,正如资料上有讲的那样,放在这里检查确实是为了上边提到的“情况有变”的原因,这里可以防止睡眠后的改变所以检查在这里比较合适,接下来是加锁的操作,读到上面的时候有朋友可能混淆了sk到底是服务器端还是客户端,服务器我们在上边看到是用other来表示的,所以如果觉得自己对某段代码没有把握就往前看一下,多对照定能理解准确而把握充分。我们继续往下

err = security_unix_stream_connect(sock, other->sk_socket, newsk);
    if (err) {
        unix_state_unlock(sk);
        goto out_unlock;
    }

    /* The way is open! Fastly set all the necessary fields... */

    sock_hold(sk);
    unix_peer(newsk)    = sk;
    newsk->sk_state        = TCP_ESTABLISHED;
    newsk->sk_type        = sk->sk_type;
    newsk->sk_peercred.pid    = task_tgid_vnr(current);
    newsk->sk_peercred.uid    = current->euid;
    newsk->sk_peercred.gid    = current->egid;
    newu = unix_sk(newsk);
    newsk->sk_sleep        = &newu->peer_wait;
    otheru = unix_sk(other);

    /* copy address information from listening to new sock*/
    if (otheru->addr) {
        atomic_inc(&otheru->addr->refcnt);
        newu->addr = otheru->addr;
    }
    if (otheru->dentry) {
        newu->dentry    = dget(otheru->dentry);
        newu->mnt    = mntget(otheru->mnt);
    }

    /* Set credentials */
    sk->sk_peercred = other->sk_peercred;

    sock->state    = SS_CONNECTED;
    sk->sk_state    = TCP_ESTABLISHED;
    sock_hold(newsk);

    smp_mb__after_atomic_inc();    /* sock_hold() does an atomic_inc() */
    unix_peer(sk)    = newsk;

    unix_state_unlock(sk);

    /* take ten and and send info to listening sock */
    spin_lock(&other->sk_receive_queue.lock);
    __skb_queue_tail(&other->sk_receive_queue, skb);
    spin_unlock(&other->sk_receive_queue.lock);
    unix_state_unlock(other);
    other->sk_data_ready(other, 0);
    sock_put(other);
    return 0;

out_unlock:
    if (other)
        unix_state_unlock(other);

out:
    if (skb)
        kfree_skb(skb);
    if (newsk)
        unix_release_sock(newsk, 0);
    if (other)
        sock_put(other);
    return err;
}

这里可能真的要混淆了,到底谁是客户端的谁是服务器端的,我们看一下上边出现的newsk是我们新建立用于与服务器端socket连接用的sock,other是服务器端的socket结构变量,sock是客户端的socket结构变量,sk是客户端的sock结构变量,我们看到上边

unix_peer(newsk)    = sk;

#define unix_peer(sk) (unix_sk(sk)->peer)

#define unix_sk(__sk) ((struct unix_sock *)__sk)

我们可以从上边看到将我们新建立的sock的peer指针指向我们客户端的sk,因为newsk是为了传递给服务器端的socket用的。所以这之后,newsk就带有客户端的sock指针了。那就是这里的sk结构变量。而接着不远处我们看到

unix_peer(sk)    = newsk;

这说明我们的客户端sock中的peer指向了送达给服务器端的sock,也就是将来连接成功后客户端的sock有指向服务器的sock指针了。但是这必须是服务器端的socket接收了newsk之后才行。那是在服务器端的socket调用了accept()中实现的,我们以前一节讲过了。这里的客户端新建的sock就是为了送给服务器端用的。我们看到代码中将客户端的socket状态改变成了TCP_ESTABLISHED,ESTABLISHED中文意思是确定的,也就是客户端的socket已经确立了“对象”。这样再对这个socket调用connect()时,就会走到上边我们看到的代码处

    case TCP_ESTABLISHED:
        /* Socket is already connected */
        err = -EISCONN;
        goto out_unlock;

也就是说就会退出了,客户端已经连接了,不能对其同时连接二次操作。上面代码还有关于文件系统的操作

    if (otheru->dentry) {
        newu->dentry    = dget(otheru->dentry);
        newu->mnt    = mntget(otheru->mnt);
    }

不要担心将来我们分析了文件系统后,这段代码看起来会非常的简单,这里看到他是增加服务器端的sock所在的目录计数器和其安装点的计数器,这样是为了防止服务器端的sock被意外删除。最后到了关键的部分了

__skb_queue_tail(&other->sk_receive_queue, skb);

这个函数需要我们分析一下

static inline void __skb_queue_tail(struct sk_buff_head *list,
                 struct sk_buff *newsk)
{
    __skb_queue_before(list, (struct sk_buff *)list, newsk);
}

static inline void __skb_queue_before(struct sk_buff_head *list,
                 struct sk_buff *next,
                 struct sk_buff *newsk)
{
    __skb_insert(newsk, next->prev, next, list);
}

static inline void __skb_insert(struct sk_buff *newsk,
                struct sk_buff *prev, struct sk_buff *next,
                struct sk_buff_head *list)
{
    newsk->next = next;
    newsk->prev = prev;
    next->prev = prev->next = newsk;
    list->qlen++;
}

层层进入后我们看到是插入队列的操作,也就是将我们创建的数据包结构sk_buff变量skb,挂入了服务器端的sk_receive_queue队列中。紧接着我们看到对服务器端的sock的操作,首先是对其加锁,接着执行

other->sk_data_ready(other, 0);

这里的sk_data_ready是个钩子函数,什么时候挂钩的?我们回忆一下sock_init_data()函数,就是在创建socket中看过这个函数的代码,其中有一句

sk->sk_data_ready    =    sock_def_readable;

看到这里是,肯定会进入sock_drf_readable钩子函数中执行

static void sock_def_readable(struct sock *sk, int len)
{
    read_lock(&sk->sk_callback_lock);
    if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
        wake_up_interruptible_sync(sk->sk_sleep);
    sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
    read_unlock(&sk->sk_callback_lock);
}

这里可以看到是唤醒服务器端的socket进程,原因是在我们上节讲到的服务器的socket在调用accept()时由于没有连接请求会进入睡眠状态, 这里我们发出请示连接了当然要唤醒服务器的socket进程,让他来接受这里的客户端连接。时间关系,我们看到上边代码中首先判断服务器进程是不是在睡眠并且是否可以唤醒就使用同步唤醒wake_up_interruptible_sync(),这个函数调用的非常深,朋友们自己可以读一下,他的作用不言而喻了,如果是异步调用呢则进入下边的函数中

static inline void sk_wake_async(struct sock *sk, int how, int band)
{
    if (sk->sk_socket && sk->sk_socket->fasync_list)
        sock_wake_async(sk->sk_socket, how, band);
}

异步的好处是能让服务器端的socket不至于浪费cpu的资源,我们知道硬件的异步方式主要是中断,而软件的异步就是靠信号,尽管我们还没有分析linux的信号内容,它其实相当于软件的中断,就是客户端能过信号唤醒服务器端的socket进程,平时服务器端的socket进程可以节省资源,主动让位,当接到信号后这个进程再来accept()处理客户端的socket连接请求,如果熟悉文件系统的朋友或者做过linux编程的朋友肯定熟悉一个系统调用ioctl()这个系统调用就可以通过设置FIOASYNC,就能使某个进程与操作的文件异步通讯了,当文件的条件满足时就会唤醒相应的进程,而socket也有一个异步操作队列fasync_list,当服务器端的socket允许异步操作时就通过ioctl()挂入到fasync_list队列中,在代表着socket的文件file指针中有一个f_op指针,而它是一个file_operations钩子结构,挂入这个钩子的是socket_file_ops,里面有一个钩子函数fasync,它挂入的是sock_fasync(),当ioctl()异步操作时就会通过这个钩子函数将socket挂入fasync_list队列。所以我们上面的sk_wake_async()函数就是将这个队列中的进程发出信号

int sock_wake_async(struct socket *sock, int how, int band)
{
    if (!sock || !sock->fasync_list)
        return -1;
    switch (how) {
    case SOCK_WAKE_WAITD:
        if (test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
            break;
        goto call_kill;
    case SOCK_WAKE_SPACE:
        if (!test_and_clear_bit(SOCK_ASYNC_NOSPACE, &sock->flags))
            break;
        /* fall through */
    case SOCK_WAKE_IO:
call_kill:
        __kill_fasync(sock->fasync_list, SIGIO, band);
        break;
    case SOCK_WAKE_URG:
        __kill_fasync(sock->fasync_list, SIGURG, band);
    }
    return 0;
}

这里我们看到上边调用时参数是SOCK_WAKE_WAITD,并且另一个参数是POLL_IN, 这里会进入

call_kill:
        __kill_fasync(sock->fasync_list, SIGIO, band);
        break;

然后扫描整个fasync_list队列向队列中的进程发出SIGIO信号

void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
    while (fa) {
        struct fown_struct * fown;
        if (fa->magic != FASYNC_MAGIC) {
            printk(KERN_ERR "kill_fasync: bad magic number in "
             "fasync_struct!\n");
            return;
        }
        fown = &fa->fa_file->f_owner;
        /* Don't send SIGURG to processes which have not set a
         queued signum: SIGURG has its own default signalling
         mechanism. */

        if (!(sig == SIGURG && fown->signum == 0))
            send_sigio(fown, fa->fa_fd, band);
        fa = fa->fa_next;
    }
}

我们看到这个函数里是关于文件系统的我们还是那句话,朋友们感到困难可以先放一放,等到我们分析完文件系统时可以在下一遍的阅读中细看,这也能考验你的学习能力。上面代码中是通过send_sigio()向进程发出信号的。这里我们也可以看出服务器端的必须事先设置信号的处理程序,到这里我们的connect基本就可算分析完了,回忆上一节我们讲到的服务器端的accept()中,服务器端的socket正在等待客户端的连接到来,当服务器端的进程唤醒后,我们再贴一下上一节中的代码在这里

static int wait_for_packet(struct sock *sk, int *err, long *timeo_p)
{

。。。。。。
        if (signal_pending(current))
        goto interrupted;

    error = 0;
    *timeo_p = schedule_timeout(*timeo_p);
out:
    finish_wait(sk->sk_sleep, &wait);
    return error;
interrupted:
    error = sock_intr_errno(*timeo_p);
out_err:
    *err = error;
    goto out;
out_noerr:
    *err = 0;
    error = 1;
    goto out;
}

很明显我们看到服务器端的socket当接收到信号时会从这里继续往下执行然后返回到__skb_recv_datagram()函数再返回到skb_recv_datagram()再至unix_accept()函数中,这里说明接收到了数据,所以我们会在以前中看到走到sock_graft(tsk, newsock);如果朋友们不记得可以回到上一节accept()内容中看一下,那里的newsock是服务器端创建的一个新的socket结构,而tsk则是客户端传递过去用于连接用的sock结构,刚刚上面看到了,注意tsk我们说了是target sock的意思。到那里二个socket就真正完成了连接,如果服务器端的socket还没有调用accept()则客户端的连接请求已经在sk_receive_queue队列中等待了,所以不需要睡眠就可以直接连接了。至此,我们的unix的TCP的连接方式分析完了,我们上节说过要看一下unix的另一个UDP的连接方式,这种连接方式应该我们常用的,象QQ就是采用这种方式,现在网络尖兵对QQ无法封杀的原因就是网络尖兵针对TCP有连接方式的检测,所以QQ就是多机共享一条宽带时还能够使用的原因,有些网络游戏也是使用的这种UDP的连接方式,资料上也称为无连接的方式,不明白这些区别的朋友可以查一资料,这里不介绍了,其中的关于系统调用的过程我们不重复了,直接到达关键的函数处,我们直接看钩子结构

static const struct proto_ops unix_dgram_ops = {
。。。。。。    

    .connect =    unix_dgram_connect,
。。。。。。
};

有关的过程不再啰嗦了,不知道为什么走到这里的朋友看一下前边吧,我们看到这里是进入钩子函数unix_dgram_connect()中了

static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
             int alen, int flags)
{
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct sockaddr_un *sunaddr=(struct sockaddr_un*)addr;
    struct sock *other;
    unsigned hash;
    int err;

    if (addr->sa_family != AF_UNSPEC) {
        err = unix_mkname(sunaddr, alen, &hash);
        if (err < 0)
            goto out;
        alen = err;

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

restart:
        other=unix_find_other(net, sunaddr, alen, sock->type, hash, &err);
        if (!other)
            goto out;

        unix_state_double_lock(sk, other);

        /* Apparently VFS overslept socket death. Retry. */
        if (sock_flag(other, SOCK_DEAD)) {
            unix_state_double_unlock(sk, other);
            sock_put(other);
            goto restart;
        }

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

        err = security_unix_may_send(sk->sk_socket, other->sk_socket);
        if (err)
            goto out_unlock;

    } else {
        /*
         *    1003.1g breaking connected state with AF_UNSPEC
         */

        other = NULL;
        unix_state_double_lock(sk, other);
    }

    /*
     * If it was connected, reconnect.
     */

    if (unix_peer(sk)) {
        struct sock *old_peer = unix_peer(sk);
        unix_peer(sk)=other;
        unix_state_double_unlock(sk, other);

        if (other != old_peer)
            unix_dgram_disconnected(sk, old_peer);
        sock_put(old_peer);
    } else {
        unix_peer(sk)=other;
        unix_state_double_unlock(sk, other);
    }
    return 0;

out_unlock:
    unix_state_double_unlock(sk, other);
    sock_put(other);
out:
    return err;
}

上面多数的函数我们都分析过了,不再重复了,这里有一个函数我们没有分析,我们看一下unix_may_send

static inline int unix_may_send(struct sock *sk, struct sock *osk)
{
    return (unix_peer(osk) == NULL || unix_our_peer(sk, osk));
}

#define unix_peer(sk) (unix_sk(sk)->peer)

static inline int unix_our_peer(struct sock *sk, struct sock *osk)
{
    return unix_peer(osk) == sk;
}

代码的意思是检查一下对方的peer是否没有设置,如果设置了就会进一步检查是否设置的这个sock指针是自己,如果不是自己的话,就从

if (!unix_may_send(sk, other))
            goto out_unlock;

也就是从上方检查不是自己的话就返回了,这就要求目标的socket必须没有建立连接,

    if (unix_peer(sk)) {
        struct sock *old_peer = unix_peer(sk);
        unix_peer(sk)=other;
        unix_state_double_unlock(sk, other);

        if (other != old_peer)
            unix_dgram_disconnected(sk, old_peer);
        sock_put(old_peer);
    } else {
        unix_peer(sk)=other;
        unix_state_double_unlock(sk, other);
    }

接着要检查自己的设置的peer目标socket是否与已经连接的socket是同一个,如果不是就用重新连接。这里的连接只是将自己的peer指针指向对方的sock结构,并不会影响目标的socket连接,所以资料上称为是“单向的连接”,另外,也没有象TCP那样的状态标志及相应的检查,目标的socket或者服务器的socket更不会以繁殖的形式创建新的socket。所以这段代码相对TCP真的是简单很多。
阅读(4538) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~