Linux网络协议栈 -- socket创建(3)

五、初始化 sk
 
分配完成 sk 后,另一个重要的功能就是初始化它,sk 的成员相当复杂,其主要的初始化工作是在函数 sock_init_data()中完成的: 


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); 
 
        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; 
        /*  指向对应的 socket 结构 */ 
        sk->sk_socket                =        sock; 
 
        sock_set_flag(sk, SOCK_ZAPPED); 
 
        if(sock) 
        { 
                sk->sk_type        =        sock->type; 
                sk->sk_sleep        =        &sock->wait; 
                /*  回指对应的 scok 结构 */ 
                sock->sk        =        sk; 
        } else 
                sk->sk_sleep        =        NULL; 
 
        rwlock_init(&sk->sk_dst_lock); 
        rwlock_init(&sk->sk_callback_lock); 
 
        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.tv_sec     = -1L; 
        sk->sk_stamp.tv_usec    = -1L; 
         atomic_set(&sk->sk_refcnt, 1); 
}
 
sock 结构中,有三个重要的双向队列,分别是 sk_receive_queue、sk_write_queue和 sk_error_queue。从它们的名字就可以看出来其作用了。 队列并非采用通用的 list_head 来维护,而是使用 skb_buffer 队列: 


struct sk_buff_head { 

        /* These two members must be first. */ 
        struct sk_buff        *next; 
        struct sk_buff        *prev; 
 
        __u32                qlen; 
        spinlock_t        lock; 
};
 
这样,队列中指向的每一个 skb_buffer,就是一个数据包,分别是接收、发送和投递错误。 
 
剩余的就是初始化其它成员变量了。后面再来专门分析这些成员的作用。 
 
inet_create 函数中,除了初始化 sk 成员的值,还有一部份代码,是初始化一个 inet的东东: 
        inet = inet_sk(sk); 
        inet->uc_ttl        = -1; 
        inet->mc_loop        = 1; 
        inet->mc_ttl        = 1; 
        inet->mc_index        = 0; 
        inet->mc_list        = NULL;[/code] 
 
inet 是一个 struct inet_sock 结构类型,来看它的定义: 


struct inet_sock { 

        /* sk and pinet6 has to be the first two members of inet_sock */ 
        struct sock                sk; 
               …… 
}
只留意它的第一个成员就足够了。 
 

我们说 sock 是面向用户态调用,而 sk 是面向内核驱动调用的,那 sk 是如何与协议栈交互的呢?对于每一个类型的协议,为了与 sk 联系起来,都定义了一个 struct XXX_sock 结构,XXX是协议名,例如:

 

struct tcp_sock { 
        /* inet_sock has to be the first member of tcp_sock */ 
        struct inet_sock        inet; 
        int        tcp_header_len;        /* Bytes of tcp header to send                */ 
        …… 
}  
struct udp_sock { 
        /* inet_sock has to be the first member */ 
        struct inet_sock inet; 
        int                 pending;        /* Any pending frames ? */ 
        unsigned int         corkflag;        /* Cork is required */ 
          __u16                 encap_type;        /* Is this an Encapsulation socket? */ 
        /* 
         * Following member retains the infomation to create a UDP header 
         * when the socket is uncorked. 
         */ 
        __u16                 len;                /* total length of pending frames */ 
}; 
 
struct raw_sock { 
        /* inet_sock has to be the first member */ 
        struct inet_sock   inet; 
        struct icmp_filter filter; 
};
 
很明显,它们的结构定构是“af_inet一般属性+自己的私有属性”,因为它们的第一个成员总是 inet。  
 
呵呵,现在回头来照一下起初在 af_inet.c 中,封装协议注册的时候,size成员,对于 tcp 而言: 
.obj_size                = sizeof(struct tcp_sock), 
其它协议类似。 
 
以 obj_size 来确定每个 slab 缓存项分配的大小,所以,我们就可说,每次申请分配的,实际上是一个 struct XXX_sock 结构大小的结构。因为都是定义于上层结构的第一个成员,可以使用强制类型转换来使用这块分配的内存空间。例如: 


inet = inet_sk(sk); 

 
static inline struct inet_sock *inet_sk(const struct sock *sk) 

        return (struct inet_sock *)sk; 

 
struct tcp_sock *tp = tcp_sk(sk); 
 
static inline struct tcp_sock *tcp_sk(const struct sock *sk) 

        return (struct tcp_sock *)sk; 
}
 
OK,inet_create()运行完,一个 socket 套接字基本上就创建完毕了,剩下的就是与文件系统挂钩,回到最初的 sys_socket()函数中来,它在调用完 sock_create()后,紧接着调用 sock_map_fd()函数: 

int sock_map_fd(struct socket *sock) 

        int fd; 
        struct qstr this; 
        char name[32]; 
 
        /* 
         *        Find a file descriptor suitable for return to the user.  
         */ 
 
        fd = get_unused_fd(); 
        if (fd >= 0) { 
                struct file *file = get_empty_filp(); 
 
                if (!file) { 
                        put_unused_fd(fd); 
                        fd = -ENFILE; 
                        goto out; 
                } 
 
                sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino); 
                this.name = name; 
                this.len = strlen(name); 
                this.hash = SOCK_INODE(sock)->i_ino; 
 
                file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this); 
                if (!file->f_dentry) { 
                        put_filp(file); 
                        put_unused_fd(fd); 
                        fd = -ENOMEM; 
                        goto out; 
                } 
                file->f_dentry->d_op = &sockfs_dentry_operations; 
                d_add(file->f_dentry, SOCK_INODE(sock)); 
                file->f_vfsmnt = mntget(sock_mnt); 
                file->f_mapping = file->f_dentry->d_inode->i_mapping; 
 
                sock->file = file; 
                file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops; 
                file->f_mode = FMODE_READ | FMODE_WRITE; 
                file->f_flags = O_RDWR; 
                file->f_pos = 0;                 fd_install(fd, file); 
        } 
 
out: 
        return fd; 
}
 
这个函数的核心思想,在一开始,就已经分析过了。从进程的角度来讲,一个socket 套接字就是一个特殊的,已打开的文件。前面分配好一个 socket  后,这里要做的就是将它与文件系统拉上亲戚关系。首先获取一个空闲的文件描述符号和 file 结构。然后在文件系统中分配一个目录项(d_alloc),使  其指向已经分配的 inode 节点(d_add),然后把其目录项挂在 sockfs 文件系统的根目录之下。并且把目录项的指针 d_op设置成指向 sockfs_dentry_operati,这个数据结构通过函数指针提供他与文件路径有关的操作: 


static struct dentry_operations sockfs_dentry_operations = { 

        .d_delete =        sockfs_delete_dentry, 
};
 
最后一步,就是将 file 结构中的 f_op 和 sock 结构中的 i_fop 都指向 socket_file_ops,它是一个函数指针集,指向了 socket面向文件系统的用户态调用的一些接口函数: 


static 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, 
        .mmap =                sock_mmap, 
        .open =                sock_no_open,        /* special open code to disallow open via /proc */ 
        .release =        sock_close, 
        .fasync =        sock_fasync, 
        .readv =        sock_readv, 
        .writev =        sock_writev, 
        .sendpage =        sock_sendpage 
};
 

OK,到这里,整个 socket 套接字的创建工作,就宣告完成了。

写到这里,可以为 socket 的创建下一个小结了: 
1、所谓创建socket,对内核而言,最重要的工作就是分配 sock 与 sk; 
2、 sock 面向上层系统调用,主要是与文件系统交互。通过进程的 current 指针的 files,结合创建 socket时返回的文件描符述,可以找到内  核中对应的 struct file,再根据 file的 f_dentry可以找到对应的目录项,而目录项 struct dentry 中,有 d_inode 指针,指向与 sock 封装在一起的 inode。sock 又与 sk指针互指,一一对应。在这串结构中,有两个重要的函数集  指针,一个是文件系统 struct file 中的f_op指针,它指向了,对应的用户态调用的read,write等操调用,但不支持open,另一个是struct socket结构,即 sock 的ops 指针,它在 inet_create()中被置为 sock->ops = answer->ops; 指向具体协议类型的 ops;例如,inet_stream_ops、inet_dgram_ops 或者是 inet_sockraw_ops 等等。它用来支持上层的 socket 的其它 API 调用。 
3、sk 面向内核协议栈,协议栈与它的接口数据结构是 struct protoname_sock,该结构中包含了一般性的 inet 结构和自己的私有成员,struct inet_sock 的第一个成员就是一个 sk 指针,而分配的 sk,实际上空间大小是 struct protoname_sock,所以,这三者可以通过强制类型转换来获取需要的指针。  

你可能感兴趣的:(Linux网络协议栈 -- socket创建(3))