5-socket的实践到内核--追踪Unix的socket .

来源:互联网 发布:数据库安全管理原则 编辑:程序博客网 时间:2024/06/03 17:44

http://blog.csdn.net/embeddedfly/article/details/6411714

昨天我们追踪到了unix_create()这个函数处,今天我们继续从此处进行,老习惯先贴代码再做分析,可能有的朋友会说为什么不把分析放在代码中间,其实这是我的经验,因为我在阅读赵炯的内核书时,尽管他对每一行都进行了注释,可是我看完一遍以后,偶而看到实践中的代码却一点印象都没有,因为我读他的书时,逐行注解使我不愿意看代码了,只愿看他用中文写的注解,我不知道其他朋友什么感觉,学习LINUX最重要的基础是阅读代码的能力,我真正体会到天翻地覆的变化是在阅读linux内核情景分析那本书,这本书是毛德操和胡德明老师写的,我感觉他把代码和注释分离的方式很好,这样读者可以练习读代码然后对照“标准答案”,就象是学英语需要的是环境,固然大家都在说中文为什么还有朋友把英语学的那么好呢?关键是学英语时要抛弃汉语。就象我们的分析,分析时就是分析,代码就是代码,他们的位置可以互换,但是朋友们今后在读第二遍时就感觉不同了,聪明的朋友可以完全不依赖我的分析,只看代码部分就能知一二然后也能检验一下“答案”是否正确了,本文是我独立分析进行的,难免有不足之处,请朋友们谅解

static int unix_create(struct net *net, struct socket *sock, int protocol)
{
    if (protocol && protocol != PF_UNIX)
        return -EPROTONOSUPPORT;

    sock->state = SS_UNCONNECTED;

    switch (sock->type) {
    case SOCK_STREAM:
        sock->ops = &unix_stream_ops;
        break;
        /*
         *    Believe it or not BSD has AF_UNIX, SOCK_RAW though
         *    nothing uses it.
         */

    case SOCK_RAW:
        sock->type=SOCK_DGRAM;
    case SOCK_DGRAM:
        sock->ops = &unix_dgram_ops;
        break;
    case SOCK_SEQPACKET:
        sock->ops = &unix_seqpacket_ops;
        break;
    default:
        return -ESOCKTNOSUPPORT;
    }

    return unix_create1(net, sock) ? 0 : -ENOMEM;
}

上面的代码部分可能朋友对我们追踪源头不记得了,再回忆一下我们以前看到的应用程序界面

socket(AF_UNIX, SOCK_STREAM, 0);

中间插进来阅读的朋友,我建议你不要取此学习捷径,当然如果你基本功相当的好除外,我这样说是因为就象是内核是不可分割的整体一样,如果你中间插进来可能会进入迷宫的,所以请尽量把相关的章节看懂,推荐从我前面写的本节的实践部分开始看起。我们看到上面的界面中一路传递下来了SOCK_STRAEM,所以我们会走上面函数中的

case SOCK_STREAM:
        sock->ops = &unix_stream_ops;
        break;

我们前面提过网域是不同的,我们这里重点学习unix网域,网域不同他们的规程不同,每个规程都有一个具体的操作函数,这里就是用一个数据结构struct proto_ops来表示的,上面代码是对我们创建的socket的赋予了具体的规程操作我们看一下

static const struct proto_ops unix_stream_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
    .bind =        unix_bind,
    .connect =    unix_stream_connect,
    .socketpair =    unix_socketpair,
    .accept =    unix_accept,
    .getname =    unix_getname,
    .poll =        unix_poll,
    .ioctl =    unix_ioctl,
    .listen =    unix_listen,
    .shutdown =    unix_shutdown,
    .setsockopt =    sock_no_setsockopt,
    .getsockopt =    sock_no_getsockopt,
    .sendmsg =    unix_stream_sendmsg,
    .recvmsg =    unix_stream_recvmsg,
    .mmap =        sock_no_mmap,
    .sendpage =    sock_no_sendpage,
};

以前我讲到过钩子函数,不知道大家还否记得,这里就是很多钩子函数,同时为了学习的方便我们也把另一种规程的定义贴在这里

static const struct proto_ops unix_dgram_ops = {
    .family =    PF_UNIX,
    .owner =    THIS_MODULE,
    .release =    unix_release,
    .bind =        unix_bind,
    .connect =    unix_dgram_connect,
    .socketpair =    unix_socketpair,
    .accept =    sock_no_accept,
    .getname =    unix_getname,
    .poll =        unix_dgram_poll,
    .ioctl =    unix_ioctl,
    .listen =    sock_no_listen,
    .shutdown =    unix_shutdown,
    .setsockopt =    sock_no_setsockopt,
    .getsockopt =    sock_no_getsockopt,
    .sendmsg =    unix_dgram_sendmsg,
    .recvmsg =    unix_dgram_recvmsg,
    .mmap =        sock_no_mmap,
    .sendpage =    sock_no_sendpage,
};

就象情景分析书上讲的那样,前面那个规程是针对有连接的,而后面的则是无连接的,还有的叫法称第一个为流规程,后一个为数据报规程,不管何种叫法,我们可以对比一下上边二个规程的内容。将规程的操作与socket挂上钩以后,函数最后就是

return unix_create1(net, sock) ? 0 : -ENOMEM;

我们看到他是从unix_create1返回的,我们接着看,这个函数的代码在net/unix/Af_unix.c的584行处

static struct sock * unix_create1(struct net *net, struct socket *sock)
{
    struct sock *sk = NULL;
    struct unix_sock *u;

    atomic_inc(&unix_nr_socks);
    if (atomic_read(&unix_nr_socks) > 2 * get_max_files())
        goto out;

    sk = sk_alloc(net, PF_UNIX, GFP_KERNEL, &unix_proto);
    if (!sk)
        goto out;

    sock_init_data(sock,sk);
    lockdep_set_class(&sk->sk_receive_queue.lock,
                &af_unix_sk_receive_queue_lock_key);

    sk->sk_write_space    = unix_write_space;
    sk->sk_max_ack_backlog    = net->unx.sysctl_max_dgram_qlen;
    sk->sk_destruct        = unix_sock_destructor;
    u     = unix_sk(sk);
    u->dentry = NULL;
    u->mnt     = NULL;
    spin_lock_init(&u->lock);
    atomic_set(&u->inflight, 0);
    INIT_LIST_HEAD(&u->link);
    mutex_init(&u->readlock); /* single task reading lock */
    init_waitqueue_head(&u->peer_wait);
    unix_insert_socket(unix_sockets_unbound, sk);
out:
    if (sk == NULL)
        atomic_dec(&unix_nr_socks);
    return sk;
}

我们分析一下,首先看到有一个新的数据结构struct sock。这个结构是对socket的一种扩充,这个数据结构的定义相当的大,但是我们还是贴在下面,不过朋友们没有必要死住它,只要简单了解一下即可,也可跳过去在以后的代码中遇到了回来看一下

struct sock {
    /*
     * Now struct inet_timewait_sock also uses sock_common, so please just
     * don't add nothing before this first member (__sk_common) --acme
     */

    struct sock_common    __sk_common;
#define sk_family        __sk_common.skc_family
#define sk_state        __sk_common.skc_state
#define sk_reuse        __sk_common.skc_reuse
#define sk_bound_dev_if        __sk_common.skc_bound_dev_if
#define sk_node            __sk_common.skc_node
#define sk_bind_node        __sk_common.skc_bind_node
#define sk_refcnt        __sk_common.skc_refcnt
#define sk_hash            __sk_common.skc_hash
#define sk_prot            __sk_common.skc_prot
#define sk_net            __sk_common.skc_net
    unsigned char        sk_shutdown : 2,
                sk_no_check : 2,
                sk_userlocks : 4;
    unsigned char        sk_protocol;
    unsigned short        sk_type;
    int            sk_rcvbuf;
    socket_lock_t        sk_lock;
    /*
     * The backlog queue is special, it is always used with
     * the per-socket spinlock held and requires low latency
     * access. Therefore we special case it's implementation.
     */

    struct {
        struct sk_buff *head;
        struct sk_buff *tail;
    } sk_backlog;
    wait_queue_head_t    *sk_sleep;
    struct dst_entry    *sk_dst_cache;
    struct xfrm_policy    *sk_policy[2];
    rwlock_t        sk_dst_lock;
    atomic_t        sk_rmem_alloc;
    atomic_t        sk_wmem_alloc;
    atomic_t        sk_omem_alloc;
    int            sk_sndbuf;
    struct sk_buff_head    sk_receive_queue;
    struct sk_buff_head    sk_write_queue;
    struct sk_buff_head    sk_async_wait_queue;
    int            sk_wmem_queued;
    int            sk_forward_alloc;
    gfp_t            sk_allocation;
    int            sk_route_caps;
    int            sk_gso_type;
    unsigned int        sk_gso_max_size;
    int            sk_rcvlowat;
    unsigned long         sk_flags;
    unsigned long     sk_lingertime;
    struct sk_buff_head    sk_error_queue;
    struct proto        *sk_prot_creator;
    rwlock_t        sk_callback_lock;
    int            sk_err,
                sk_err_soft;
    atomic_t        sk_drops;
    unsigned short        sk_ack_backlog;
    unsigned short        sk_max_ack_backlog;
    __u32            sk_priority;
    struct ucred        sk_peercred;
    long            sk_rcvtimeo;
    long            sk_sndtimeo;
    struct sk_filter     *sk_filter;
    void            *sk_protinfo;
    struct timer_list    sk_timer;
    ktime_t            sk_stamp;
    struct socket        *sk_socket;
    void            *sk_user_data;
    struct page        *sk_sndmsg_page;
    struct sk_buff        *sk_send_head;
    __u32            sk_sndmsg_off;
    int            sk_write_pending;
    void            *sk_security;
    __u32            sk_mark;
    /* XXX 4 bytes hole on 64 bit */
    void            (*sk_state_change)(struct sock *sk);
    void            (*sk_data_ready)(struct sock *sk, int bytes);
    void            (*sk_write_space)(struct sock *sk);
    void            (*sk_error_report)(struct sock *sk);
      int            (*sk_backlog_rcv)(struct sock *sk,
                         struct sk_buff *skb); 
    void (*sk_destruct)(struct sock *sk);
};

是不是感觉很恐怖,这么大一个数据结构,当然现在你也对他为什么不与socket放在一起感觉情出此因了,socket必竟与inode文件系统相密切,如果把上面这些结构变量都放在socket中,必然形成一个庞然大物,更不利于文件系统,今后我们会看到文件系统中有很多数据结构都是采取这种分离的办法,即把重要的项放在与文件系统密切的结构里,不重要但又要占大量内存的项会分享出来放在另一个结构中,再让二个结构相关联。同时也有一些项是公用的,所以考虑到公用部分也会采取分享的办法。

sk_alloc(net, PF_UNIX, GFP_KERNEL, &unix_proto);

上面是从内存的高速缓冲池slab中分配一个sock结构,这里我们就不进去看了,这里对大家介绍一下他的主要作用,然后我们对这个sock结构进行初始化数据

void sock_init_data(struct socket *sock, struct sock *sk)
{
    skb_queue_head_init(&sk->sk_receive_queue);
    skb_queue_head_init(&sk->sk_write_queue);
    skb_queue_head_init(&sk->sk_error_queue);
#ifdef CONFIG_NET_DMA
    skb_queue_head_init(&sk->sk_async_wait_queue);
#endif

    sk->sk_send_head    =    NULL;

    init_timer(&sk->sk_timer);

    sk->sk_allocation    =    GFP_KERNEL;
    sk->sk_rcvbuf        =    sysctl_rmem_default;
    sk->sk_sndbuf        =    sysctl_wmem_default;
    sk->sk_state        =    TCP_CLOSE;
    sk->sk_socket        =    sock;

    sock_set_flag(sk, SOCK_ZAPPED);

    if (sock) {
        sk->sk_type    =    sock->type;
        sk->sk_sleep    =    &sock->wait;
        sock->sk    =    sk;
    } else
        sk->sk_sleep    =    NULL;

    rwlock_init(&sk->sk_dst_lock);
    rwlock_init(&sk->sk_callback_lock);
    lockdep_set_class_and_name(&sk->sk_callback_lock,
            af_callback_keys + sk->sk_family,
            af_family_clock_key_strings[sk->sk_family]);

    sk->sk_state_change    =    sock_def_wakeup;
    sk->sk_data_ready    =    sock_def_readable;
    sk->sk_write_space    =    sock_def_write_space;
    sk->sk_error_report    =    sock_def_error_report;
    sk->sk_destruct        =    sock_def_destruct;

    sk->sk_sndmsg_page    =    NULL;
    sk->sk_sndmsg_off    =    0;

    sk->sk_peercred.pid     =    0;
    sk->sk_peercred.uid    =    -1;
    sk->sk_peercred.gid    =    -1;
    sk->sk_write_pending    =    0;
    sk->sk_rcvlowat        =    1;
    sk->sk_rcvtimeo        =    MAX_SCHEDULE_TIMEOUT;
    sk->sk_sndtimeo        =    MAX_SCHEDULE_TIMEOUT;

    sk->sk_stamp = ktime_set(-1L, 0);

    atomic_set(&sk->sk_refcnt, 1);
    atomic_set(&sk->sk_drops, 0);
}

sock结构中设置了几个头,它是sk_buff_head结构

struct sk_buff_head {
    /* These two members must be first. */
    struct sk_buff    *next;
    struct sk_buff    *prev;

    __u32        qlen;
    spinlock_t    lock;
};

这个结构内部包含了sk_buff结构,它是非常大的,sk_buff结构按照资料上介绍上的是用于报文的包使用的,每一个数据包都要用一个sk_buff数据结构,所以我们可以看到上面为sk_receive_queue是已经接收到的包,而sk_write_queue则为要发送的的包,上面函数中所有的赋值操作我们暂且不详细分析了,这里是钩子挂钩的过程,当我们遇到具体的操作时,可以回来查看这里的钩子上到底挂了哪个函数,我们再回到unix_creat1函数继续看,下面也是对sock的一些赋值,特别注意一个钩子函数语句

sk->sk_write_space    = unix_write_space;

这里挂钩的函数是unix_write_space(),我们还看到有一个数据结构

u     = unix_sk(sk);

这里有一个struct unix_sock

struct unix_sock {
    /* WARNING: sk has to be the first member */
    struct sock        sk;
        struct unix_address *addr;
        struct dentry        *dentry;
        struct vfsmount        *mnt;
    struct mutex        readlock;
        struct sock        *peer;
        struct sock        *other;
    struct list_head    link;
        atomic_t inflight;
        spinlock_t        lock;
    unsigned int        gc_candidate : 1;
        wait_queue_head_t peer_wait;
};

结构中有一个关于unix_address

struct unix_address {
    atomic_t    refcnt;
    int        len;
    unsigned    hash;
    struct sockaddr_un name[0];
};

这是一个我前边练习中曾经看到过给socket操作bind地址的sockaddr_un,是保存在这里的。我们看一下内核对他的定义

struct sockaddr_un {
    sa_family_t sun_family;    /* AF_UNIX */
    char sun_path[UNIX_PATH_MAX];    /* pathname */
};

#define UNIX_PATH_MAX    108

可见这个数据结构中采用了char类型用来保存路径名称,就象一个文件的路径名称,同时我们也看到规定了他的长度是108,与梁山好汉的数字吻和,我们看到上边unix_address 中初始为0长度,这是为了节省空间,内核中很多数组都是采取这种方式避免了占用空间而浪费腐败,这种方式非常清兼。接着最重要的是

unix_insert_socket(unix_sockets_unbound, sk);

这里追踪

static inline void unix_insert_socket(struct hlist_head *list, struct sock *sk)
{
    spin_lock(&unix_table_lock);
    __unix_insert_socket(list, sk);
    spin_unlock(&unix_table_lock);
}

static void __unix_insert_socket(struct hlist_head *list, struct sock *sk)
{
    BUG_TRAP(sk_unhashed(sk));
    sk_add_node(sk, list);
}

这里的调用非常深层了,我们还是在此叙述一下吧,有兴趣的朋友可以跟进看一下,这个函数的作用主要是sock结构插入到一个队列中

#define unix_sockets_unbound    (&unix_socket_table[UNIX_HASH_SIZE])

就是上面的unix_socket_table[UNIX_HASH+SIZE]

static struct hlist_head unix_socket_table[UNIX_HASH_SIZE + 1];

我们看到他是一个队列头数组,如果朋友们真正跟进上面的插入操作会到达这句

hlist_add_head(&sk->sk_node, list);

可以看出是将sock中的sk_node队列头链入到上面的数组中。这个数组将来是为了方便快速找到sock结构使用的。接着函数层层返回到sys_socket()函数中,我们看到接下来调用了

retval = sock_map_fd(sock);

追踪它

int sock_map_fd(struct socket *sock)
{
    struct file *newfile;
    int fd = sock_alloc_fd(&newfile);

    if (likely(fd >= 0)) {
        int err = sock_attach_fd(sock, newfile);

        if (unlikely(err < 0)) {
            put_filp(newfile);
            put_unused_fd(fd);
            return err;
        }
        fd_install(fd, newfile);
    }
    return fd;
}

这个函数内部调用了一个与文件系统有关联的操作函数

static int sock_alloc_fd(struct file **filep)
{
    int fd;

    fd = get_unused_fd();
    if (likely(fd >= 0)) {
        struct file *file = get_empty_filp();

        *filep = file;
        if (unlikely(!file)) {
            put_unused_fd(fd);
            return -ENFILE;
        }
    } else
        *filep = NULL;
    return fd;
}

上面是分配一个文件数据结构,并申请一个文件号与他挂钩,之后返回文件号。接着进入

static int sock_attach_fd(struct socket *sock, struct file *file)
{
    struct dentry *dentry;
    struct qstr name = { .name = "" };

    dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
    if (unlikely(!dentry))
        return -ENOMEM;

    dentry->d_op = &sockfs_dentry_operations;
    /*
     * We dont want to push this dentry into global dentry hash table.
     * We pretend dentry is already hashed, by unsetting DCACHE_UNHASHED
     * This permits a working /proc/$pid/fd/XXX on sockets
     */

    dentry->d_flags &= ~DCACHE_UNHASHED;
    d_instantiate(dentry, SOCK_INODE(sock));

    sock->file = file;
    init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE,
         &socket_file_ops);
    SOCK_INODE(sock)->i_fop = &socket_file_ops;
    file->f_flags = O_RDWR;
    file->f_pos = 0;
    file->private_data = sock;

    return 0;
}

在这个函数中我们看到在socket文件系统中分配了一个目录项,这些是关于文件系统的概念,朋友们不知道可以暂且了解一下,然后将socket与文件指针挂上关系,不过我们在上面看到对目录项的操作函数挂钩的是

dentry->d_op = &sockfs_dentry_operations;

static struct dentry_operations sockfs_dentry_operations = {
    .d_delete = sockfs_delete_dentry,
    .d_dname = sockfs_dname,
};

这里的主要操作是删除目录项。最关键的地方是上边对文件操作指针的挂钩

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

这个挂钩可以使我们在能过open(),read()和write()象操作文件那样对socket进行读写操作,而另一遍我们可以继续能过sys_socketcall()系统调用对socket进行send和recv操作,从这里我们可以看出socket是与文件系统紧密相关的,这也就印证了之前我说的那句话,内核是一个整体,尽管很多朋友觉得自己只要学某一部分就行了,其实,你走到一定的程序就会发现其实错宗复杂的,而解决这里面的环节就是需要我们不断的提高自己阅读代码的能力更需要对内核全面学习。好了,今天先到此,明天继续。。。。。。


原创粉丝点击