大龙的博客

常用链接

统计

最新评论

Linux TCP/IP协议栈之Socket的实现分析(一 套接字的创建) --- 转

Socket并不是TCP/IP协议的一部份,从广义上来讲,socket是Unix/Linux抽像的进程间通讯的一种方法
网络socket通讯仅仅是其若干协议中的一类,而tcp/ip又是网络协议各类中的一种
从tcp/ip的角度看socket,它更多地体现了用户API与协议栈的一个中间层接口层
用户通过调用socket API将报文递交给协议栈,或者从协议栈中接收报文

系统总入口
Linux内核为所有的与socket有关操作的API,提供了一个统一的系统调用入口,其代码在net/socket.c中
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
    ...
    /* copy_from_user should be SMP safe. */
    if (copy_from_user(a, args, nargs[call]))
        return -EFAULT;

    a0=a[0];
    a1=a[1];
    switch(call)
    {
        case SYS_SOCKET:
            err = sys_socket(a0,a1,a[2]);
            break;
        case SYS_BIND:
            err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
            break;
        case SYS_CONNECT:
            err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
            break;
        case SYS_LISTEN:
            err = sys_listen(a0,a1);
            break;
        case SYS_ACCEPT:
            err = sys_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
            break;
        case SYS_GETSOCKNAME:
            err = sys_getsockname(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
            break;
        case SYS_GETPEERNAME:
            err = sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
            break;
        case SYS_SOCKETPAIR:
            err = sys_socketpair(a0,a1, a[2], (int __user *)a[3]);
            break;
        case SYS_SEND:
            err = sys_send(a0, (void __user *)a1, a[2], a[3]);
            break;
        case SYS_SENDTO:
            err = sys_sendto(a0,(void __user *)a1, a[2], a[3],
                     (struct sockaddr __user *)a[4], a[5]);
            break;
        case SYS_RECV:
            err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
            break;
        case SYS_RECVFROM:
            err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
                       (struct sockaddr __user *)a[4], (int __user *)a[5]);
            break;
        case SYS_SHUTDOWN:
            err = sys_shutdown(a0,a1);
            break;
        case SYS_SETSOCKOPT:
            err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
            break;
        case SYS_GETSOCKOPT:
            err = sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]);
            break;
        case SYS_SENDMSG:
            err = sys_sendmsg(a0, (struct msghdr __user *) a1, a[2]);
            break;
        case SYS_RECVMSG:
            err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
            break;
        default:
            err = -EINVAL;
            break;
    }
    return err;
}
首先调用 copy_from_user将用户态参数拷贝至数组a,但是问题在于每个被调用的 API 的参数不尽相同,
那么每次拷贝的字节在小如何判断.来看其第三个参数 nargs[call],其中 call 是操作码,后面有个大大的
switch...case 就是判断它。对应的操作码定义在 include/linux/net.h
#define SYS_SOCKET                     1                /* sys_socket(2)                */
#define SYS_BIND                         2                /* sys_bind(2)                        */
#define SYS_CONNECT                  3                /* sys_connect(2)                */
#define SYS_LISTEN                       4                /* sys_listen(2)                */
#define SYS_ACCEPT                     5                /* sys_accept(2)                */
#define SYS_GETSOCKNAME        6                /* sys_getsockname(2)                */
#define SYS_GETPEERNAME         7                /* sys_getpeername(2)                */
#define SYS_SOCKETPAIR             8                /* sys_socketpair(2)                */
#define SYS_SEND                          9                /* sys_send(2)                        */
#define SYS_RECV                         10                /* sys_recv(2)                        */
#define SYS_SENDTO                    11                /* sys_sendto(2)                */
#define SYS_RECVFROM               12                /* sys_recvfrom(2)                */
#define SYS_SHUTDOWN              13                /* sys_shutdown(2)                */
#define SYS_SETSOCKOPT           14                /* sys_setsockopt(2)                */
#define SYS_GETSOCKOPT          15                /* sys_getsockopt(2)                */
#define SYS_SENDMSG                 16                /* sys_sendmsg(2)                */
#define SYS_RECVMSG                 17                /* sys_recvmsg(2)                */
而数组nargs则根据操作码的不同,计算对应的参数的空间大小:
/* Argument list sizes for sys_socketcall */
#define AL(x) ((x) * sizeof(unsigned long))
static unsigned char nargs[18]={AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
                                 AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
                                 AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)};
#undef AL
当拷贝完成参数后,就进入一个switch...case...判断操作码,跳转至对应的系统接口.

sys_socket函数
操作码 SYS_SOCKET 是由 sys_socket()实现的:
1239 asmlinkage long sys_socket(int family, int type, int protocol)
1240 {
1241     int retval;
1242     struct socket *sock;
1243
1244     retval = sock_create(family, type, protocol, &sock);
1245     if (retval < 0)
1246         goto out;
1247
1248     retval = sock_map_fd(sock);
1249     if (retval < 0)
1250         goto out_release;
1251
1252 out:
1253     /* It may be already another descriptor 8) Not kernel problem. */
1254     return retval;
1255
1256 out_release:
1257     sock_release(sock);
1258     return retval;
1259 }
在分析这段代码之前, 首先来看创建一个Socket, 对内核而言,究竟意味着什么?究竟需要内核干什么事?
当用户空间要创建一个 socke 接口时,会调用 API 函数
int socket(int domain, int type, int protocol)
其三个参数分别表示协议族,协议类型(面向连接或无连接)以及协议

对于用户态而言, 一个Socket, 就是一个特殊的已经打开的文件,为了对socket抽像出文件的概念,
内核中为socket定义了一个专门的文件系统类型sockfs.
 344 static struct vfsmount *sock_mnt __read_mostly;
 345
 346 static struct file_system_type sock_fs_type = {                                                                        
 347     .name =     "sockfs",
 348     .get_sb =   sockfs_get_sb,
 349     .kill_sb =    kill_anon_super,
 350 };
在模块初始化的时候,安装该文件系统: 
void __init sock_init(void)
{
        ……
        register_filesystem(&sock_fs_type);
        sock_mnt = kern_mount(&sock_fs_type);        
}
稍后还要回来继续分析安装中的一点细节

有了文件系统后,对内核而言,创建一个socket,就是在sockfs文件系统中创建一个文件节点(inode),并建立起为了实现
socket功能所需的一整套数据结构,包括struct inode和struct socket结构.
struct socket结构在内核中,就代表了一个"Socket",当一个struct socket数据结构被分配空间后,再将其与一个已打开
的文件“建立映射关系”.这样,用户态就可以用抽像的文件的概念来操作socket了
——当然由于网络的特殊性,至少就目前而言,这种抽像,并不如其它模块的抽像那么完美.

文件系统struct vfsmount中有一个成员指针mnt_sb指向该文件系统的超级块,而超级块结构struct super_lock
有一个重要的成员s_op指向了超级块的操作函数表,其中有函数指针alloc_inode()即为在给定的超级块下创建并初始化
一个新的索引节点对像. 也就是调用:
sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);
当然,连同相关的处理细节一起,这一操作被层层封装至一个上层函数new_inode()

那如何分配一个struct socket结构呢?
如前所述,一个socket总是与一个inode 密切相关的.当然,在 inode 中设置一个socket成员是完全可行的,
但是这貌似浪费了空间——毕竟更多的文件系统没有socket这个东东.
所以,内核引入了另一个socket_alloc结构
struct socket_alloc {
        struct socket socket;
        struct inode vfs_inode;
};
显而易见,该结构实现了inode和socket的封装.已知一个inode可以通过宏SOCKET_I来获取
与之对应的 socket:
sock = SOCKET_I(inode);
static inline struct socket *SOCKET_I(struct inode *inode)
{
        return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}
但是这样做也同时意味着在分配一个inode后,必须再分配一个socket_alloc结构,并实现对应的封装.
否则container_of又能到哪儿去找到socket呢?
现在来简要地看一个这个流程——这是文件系统安装中的一个重要步骤

881 struct vfsmount *kern_mount(struct file_system_type *type)
882 {
883     return vfs_kern_mount(type, 0, type->name, NULL);
884 }

817 struct vfsmount *
818 vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
819 {
820     struct vfsmount *mnt;
821     char *secdata = NULL;
822     int error;
           ....
828     mnt = alloc_vfsmnt(name);
829     if (!mnt)
830         goto out;
841     ....
842     error = type->get_sb(type, flags, name, data, mnt);
843     if (error < 0)
844         goto out_free_secdata;
849
850     mnt->mnt_mountpoint = mnt->mnt_root;
851     mnt->mnt_parent = mnt;
852     up_write(&mnt->mnt_sb->s_umount);
853     free_secdata(secdata);
854     return mnt;
855     .....
865 }
申请文件系统mnt结构, 调用之前注册的sock_fs_type的get_sb成员函数指针, 获取相应的超级块sb.
并将mnt->mnt_sb指向sock_fs_type中的超级块

337 static int sockfs_get_sb(struct file_system_type *fs_type,
338     int flags, const char *dev_name, void *data, struct vfsmount *mnt)
339 {
340     return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC,                                                
341                  mnt);
342 }
注意其第三个参数 sockfs_ops,它封装了 sockfs 的功能函数表
331 static struct super_operations sockfs_ops = {
332     .alloc_inode =  sock_alloc_inode,
333     .destroy_inode =sock_destroy_inode,
334     .statfs =   simple_statfs,
335 };

struct super_block *
get_sb_pseudo(struct file_system_type *fs_type, char *name,
        struct super_operations *ops, unsigned long magic)
{
        struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
                ……
        s->s_op = ops ? ops : &default_ops;
}
这里就是先获取/分配一个超级块,然后初始化超级块的各成员,包括s_op,它封装了对应的功能函数表.
s_op自然就指向了sockfs_ops,那前面提到的new_inode()函数分配inode时调用的
sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);
这个alloc_inode函数指针也就是sockfs_ops的sock_alloc_inode()函数——转了一大圈,终于指到它了.

来看看sock_alloc_inode是如何分配一个inode节点的
 283 static kmem_cache_t * sock_inode_cachep __read_mostly;
 284
 285 static struct inode *sock_alloc_inode(struct super_block *sb)
 286 {
 287     struct socket_alloc *ei;
 288     ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL);                                      
 289     if (!ei)
 290         return NULL;
 291     init_waitqueue_head(&ei->socket.wait);
 292    
 293     ei->socket.fasync_list = NULL;
 294     ei->socket.state = SS_UNCONNECTED;
 295     ei->socket.flags = 0;
 296     ei->socket.ops = NULL;
 297     ei->socket.sk = NULL;
 298     ei->socket.file = NULL;
 299     ei->socket.flags = 0;
 300
 301     return &ei->vfs_inode;
 302 }
函数先分配了一个用于封装socket和inode的 ei,然后在高速缓存中为之申请了一块空间.
这样inode和socket就同时都被分配了,接下来初始化socket的各个成员,这些成员在后面都会一一提到
 96 /**
 97  *  struct socket - general BSD socket
 98  *  @state: socket state (%SS_CONNECTED, etc)
 99  *  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
100  *  @ops: protocol specific socket operations
101  *  @fasync_list: Asynchronous wake up list
102  *  @file: File back pointer for gc
103  *  @sk: internal networking protocol agnostic socket representation
104  *  @wait: wait queue for several uses
105  *  @type: socket type (%SOCK_STREAM, etc)
106  */
107 struct socket {
108     socket_state        state;
109     unsigned long       flags;
110     const struct proto_ops  *ops;
111     struct fasync_struct    *fasync_list;
112     struct file     *file;
113     struct sock     *sk;                                                                                                
114     wait_queue_head_t   wait;
115     short           type;
116 };
至目前为止,分配inode,socket以及两者如何关联,都已一一分析了.
最后一个关键问题,就是如何把socket与一个已打开的文件,建立映射关系.

在内核中,用struct file结构描述一个已经打开的文件,指向该结构的指针内核中通常用file或filp来描述.
我们知道,内核中可以通过全局项current来获得当前进程,它是一个struct task_struct类型的指针.
tastk_struct有一个成员:
struct files_struct *files;指向一个已打开的文件.
当然,由于一个进程可能打开多个文件,所以,struct files_struct 结构有
struct file *fd_array[NR_OPEN_DEFAULT]成员,
这是个数组,以文件描述符为下标,即current->files->fd[fd],可以找到与当前进程指定文件描述符的文件

有了这些基础,如果要把一个socket与一个已打开的文件建立映射,首先要做的就是为socket分配一个struct file,
并申请分配一个相应的文件描述符fd. 因为socket并不支持open方法,所以不能期望用户界面通过调用open() API
来分配一个struct file,而是通过调用get_empty_filp来获取
struct file *file = get_empty_filp()
fd = get_unused_fd();获取一个空间的文件描述符
然后让current的files指针的fd数组的fd索引项指向该file
void fastcall fd_install(unsigned int fd, struct file * file)
{
        struct files_struct *files = current->files;
        spin_lock(&files->file_lock);
        if (unlikely(files->fd[fd] != NULL))
                BUG();
        files->fd[fd] = file;
        spin_unlock(&files->file_lock);
}
做到这一步,有了一个文件描述符fd和一个打开的文件file,它们与当前进程相连,但是好像与创建的socket并无任何瓜葛.
要做的映射还是没有进展,struct file或者文件描述述fd或current都没有任何能够与 inode或者是socket相关的东东
这需要一个中间的桥梁,目录项:struct dentry结构

因为一个文件都有与其对应的目录项:
struct file {
        struct list_head        f_list;
        struct dentry          *f_dentry;
        ……
而一个目录项:
struct dentry {
        ……
        struct inode *d_inode;                /* Where the name belongs to - NULL is negative */
d_inode 成员指向了与之对应的 inode节点

之前已经创建了一个inode节点和与之对应的 socket. 所以现在要做的就是:
“先为当前文件分配一个对应的目录项,再将已创建的 inode节点安装至该目录项”
这样一个完成的映射关系:
进程,文件描述符,打开文件,目录项,inode节点,socket就完整地串起来了
 
基本要分析的一些前导的东东都一一罗列了,虽然已尽量避免陷入文件系统的细节分析,但是还是不可避免地进入其中,
因为它们关系实现太紧密了,现在可以来看套接字的创建过程了
1239 asmlinkage long sys_socket(int family, int type, int protocol)
1240 {
1241     int retval;
1242     struct socket *sock;
1243
1244     retval = sock_create(family, type, protocol, &sock);                                                               
1245     if (retval < 0)
1246         goto out;
1247
1248     retval = sock_map_fd(sock);
1249     if (retval < 0)
1250         goto out_release;
1251
1252 out:
1253     /* It may be already another descriptor 8) Not kernel problem. */
1254     return retval;
1255
1256 out_release:
1257     sock_release(sock);
1258     return retval;
1259 }
1229 int sock_create(int family, int type, int protocol, struct socket **res)
1230 {
1231     return __sock_create(family, type, protocol, res, 0);
1232 }

AF_INET协议簇的协议封装
接下来,函数调用之前已经注册的inet_family_ops的函数指针create,也就是inet_create()函数.
前面可以说一个通用的socket已经创建好了,这里要完成与协议本身相关的一些创建socket的工作.
这一部份的工作比较复杂,还是先来看看af_inet.c中的模块初始化时候,做了哪些与此相关的工作.

要引入的第一个数据结构是struct inet_protosw,它封装了一个协议类型(如 SOCK_STREAM,SOCK_DGRAM等)
与IP协议中对应的传输层协议.
 68 /* This is used to register socket interfaces for IP protocols.  */                                                     
 69 struct inet_protosw {     
 70     struct list_head list;
 71
 72     /* These two fields form the lookup key.  */
 73     unsigned short   type;     /* This is the 2nd argument to socket(2). */
 74     int      protocol; /* This is the L4 protocol number.  */                                                           
 75
 76     struct proto     *prot;
 77     const struct proto_ops *ops;                                                                                        
 78  
 79     int                capability;   /* Which (if any) capability do we need to use this socket interface*/            
 83     char             no_check;   /* checksum on rcv/xmit/none? */
 84     unsigned char    flags;    /* See INET_PROTOSW_* below.  */                                                       
 85 };
type是协议类型,对于 ipv4 而言就是SOCK_STREAM,SOCK_DGRAM或者是SOCK_RAW之一.
protocol是传输层的协议号,prot用于描述一个具体的传输层协议,而ops指向对应的当前协议类型的操作函数集
针对不同的协议类型,定义了不同的 ops:
791 const struct proto_ops inet_stream_ops = {
 792     .family        = PF_INET,
 793     .owner         = THIS_MODULE,
 794     .release       = inet_release,
 795     .bind          = inet_bind,
 796     .connect       = inet_stream_connect,
 797     .socketpair    = sock_no_socketpair,
 798     .accept        = inet_accept,
 799     .getname       = inet_getname,
 800     .poll          = tcp_poll,
 801     .ioctl         = inet_ioctl,
 802     .listen        = inet_listen,
 803     .shutdown      = inet_shutdown,
 804     .setsockopt    = sock_common_setsockopt,
 805     .getsockopt    = sock_common_getsockopt,
 806     .sendmsg       = inet_sendmsg,
 807     .recvmsg       = sock_common_recvmsg,
 808     .mmap          = sock_no_mmap,
 809     .sendpage      = tcp_sendpage,
 810 #ifdef CONFIG_COMPAT
 811     .compat_setsockopt = compat_sock_common_setsockopt,
 812     .compat_getsockopt = compat_sock_common_getsockopt,
 813 #endif
 814 };
 815
 816 const struct proto_ops inet_dgram_ops = {
 817     .family        = PF_INET,
 818     .owner         = THIS_MODULE,
 819     .release       = inet_release,
 820     .bind          = inet_bind,
 821     .connect       = inet_dgram_connect,
 822     .socketpair    = sock_no_socketpair,
 823     .accept        = sock_no_accept,
 824     .getname       = inet_getname,
 825     .poll          = udp_poll,
 826     .ioctl         = inet_ioctl,
 827     .listen        = sock_no_listen,
828     .shutdown      = inet_shutdown,
 829     .setsockopt    = sock_common_setsockopt,
 830     .getsockopt    = sock_common_getsockopt,
 831     .sendmsg       = inet_sendmsg,
 832     .recvmsg       = sock_common_recvmsg,
 833     .mmap          = sock_no_mmap,
 834     .sendpage      = inet_sendpage,
 835 #ifdef CONFIG_COMPAT
 836     .compat_setsockopt = compat_sock_common_setsockopt,
 837     .compat_getsockopt = compat_sock_common_getsockopt,
 838 #endif
 839 };
 840
 841 /*
 842  * For SOCK_RAW sockets; should be the same as inet_dgram_ops but without
 843  * udp_poll
 844  */
845 static const struct proto_ops inet_sockraw_ops = {
 846     .family        = PF_INET,
 847     .owner         = THIS_MODULE,
 848     .release       = inet_release,
 849     .bind          = inet_bind,
 850     .connect       = inet_dgram_connect,
 851     .socketpair    = sock_no_socketpair,
 852     .accept        = sock_no_accept,
 853     .getname       = inet_getname,
 854     .poll          = datagram_poll,
 855     .ioctl         = inet_ioctl,                                                                                       
 856     .listen        = sock_no_listen,
 857     .shutdown      = inet_shutdown,
 858     .setsockopt    = sock_common_setsockopt,
 859     .getsockopt    = sock_common_getsockopt,
 860     .sendmsg       = inet_sendmsg,
 861     .recvmsg       = sock_common_recvmsg,
 862     .mmap          = sock_no_mmap,
 863     .sendpage      = inet_sendpage,
 864 #ifdef CONFIG_COMPAT
 865     .compat_setsockopt = compat_sock_common_setsockopt,
 866     .compat_getsockopt = compat_sock_common_getsockopt,
 867 #endif
 868 };
从各个函数指针的名称,我们就可以大约知道它们是做什么事的了.进一步进以看到,
它们的函数指针指向的函数差不多都是相同的.除了一些细节上的区别,例如后面两种协议类型并不支持listen.

socket()API第二个参数是协议类型,第三个参数是该协议类型下的协议——不过对于ipv4而言,
它们都是一一对应的,但是从抽像封装的角度看,数据结构的设计本身应该满足一个协议类型下边可能存在多个不同的协议,
即一对多的情况.而一一对应,仅是它们的特例:
876 /* Upon startup we insert all the elements in inetsw_array[] into
 877  * the linked list inetsw.
 878  */
 879 static struct inet_protosw inetsw_array[] =
 880 {
 881         {
 882                 .type =       SOCK_STREAM,
 883                 .protocol =   IPPROTO_TCP,
 884                 .prot =       &tcp_prot,
 885                 .ops =        &inet_stream_ops,
 886                 .capability = -1,
 887                 .no_check =   0,
 888                 .flags =      INET_PROTOSW_PERMANENT |
 889                   INET_PROTOSW_ICSK,
 890         },
 892         {
 893                 .type =       SOCK_DGRAM,
 894                 .protocol =   IPPROTO_UDP,
 895                 .prot =       &udp_prot,
 896                 .ops =        &inet_dgram_ops,
 897                 .capability = -1,
 898                 .no_check =   UDP_CSUM_DEFAULT,
 899                 .flags =      INET_PROTOSW_PERMANENT,
 900        },
 903        {
 904                .type =       SOCK_RAW,
 905                .protocol =   IPPROTO_IP,    /* wild card */
 906                .prot =       &raw_prot,
 907                .ops =        &inet_sockraw_ops,
 908                .capability = CAP_NET_RAW,
 909                .no_check =   UDP_CSUM_DEFAULT,
 910                .flags =      INET_PROTOSW_REUSE,
 911        }
 912 };
数组的每一个元素,就是支持的一种协议名称,例如IPOROTO_TCP,但是由于IPV4本身协议类型跟协议是一一对应的,
所以没有更多的.type= SOCK_xxx 了.这样数组实现了对PF_INET协议族下支持的协议类型,
以及协议类型下边的协议进行了封装,虽然事实上它们是一一对应的关系,不过理论上完全可能存在一对多的可能.

数组内,封装的一个具体的协议,由 struct proto 结构来描述
以 TCP协议为例,TCP协议的 sokcet 操作函数都被封装在这里了。 
struct proto tcp_prot = {
        .name                        = "TCP",
        .owner                        = THIS_MODULE,
        .close                        = tcp_close,
        .connect                = tcp_v4_connect,
        .disconnect                = tcp_disconnect,
        .accept                        = tcp_accept,
        .ioctl                        = tcp_ioctl,
        .init                        = tcp_v4_init_sock,
        .destroy                = tcp_v4_destroy_sock,
        .shutdown                = tcp_shutdown,
        .setsockopt                = tcp_setsockopt,
        .getsockopt                = tcp_getsockopt,
        .sendmsg                = tcp_sendmsg,
        .recvmsg                = tcp_recvmsg,
        .backlog_rcv                = tcp_v4_do_rcv,
        .hash                        = tcp_v4_hash,
        .unhash                        = tcp_unhash,
        .get_port                = tcp_v4_get_port,
        .enter_memory_pressure        = tcp_enter_memory_pressure,
        .sockets_allocated        = &tcp_sockets_allocated,
        .memory_allocated        = &tcp_memory_allocated,
        .memory_pressure        = &tcp_memory_pressure,
        .sysctl_mem                = sysctl_tcp_mem,
        .sysctl_wmem                = sysctl_tcp_wmem,
        .sysctl_rmem                = sysctl_tcp_rmem,
        .max_header                = MAX_TCP_HEADER,
        .obj_size                = sizeof(struct tcp_sock),
}
分配struct sock
看完了PF_INET的协议簇,协议类型和协议(也就是socket调用的三个参数)的封装关系,它们通过了两个数据结构
inet_protosw,struct proto来描述,被一个数组inetsw_array所封装.接下来看它的初始化工作:
static struct list_head inetsw[SOCK_MAX];
static int __init inet_init(void)
{
        ……
        /* Register the socket-side information for inet_create. */
        for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
                INIT_LIST_HEAD(r);
        for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
                inet_register_protosw(q);
        ……
}
inetsw是一个数组,其每一个元素都是一个链表首部,前面一个循环初始化之.后一个循环就值得注意了,
也就是函数
916 void inet_register_protosw(struct inet_protosw *p)
 917 {
 918     struct list_head *lh;
 919     struct inet_protosw *answer;
 920     int protocol = p->protocol;
 921     struct list_head *last_perm;
 922
 923     spin_lock_bh(&inetsw_lock);
 925     if (p->type >= SOCK_MAX)
 926         goto out_illegal;
 928     /* If we are trying to override a permanent protocol, bail. */
 929     answer = NULL;
 930     last_perm = &inetsw[p->type];
 931     list_for_each(lh, &inetsw[p->type]) {
 932         answer = list_entry(lh, struct inet_protosw, list);
 934         /* Check only the non-wild match. */
 935         if (INET_PROTOSW_PERMANENT & answer->flags) {
 936             if (protocol == answer->protocol)
 937                 break;
 938             last_perm = lh;
 939         }
 941         answer = NULL;
 942     }
 943     if (answer)
 944         goto out_permanent;
 943     if (answer)
 944         goto out_permanent;
 945
 946     /* Add the new entry after the last permanent entry if any, so that
 947      * the new entry does not override a permanent entry when matched with
 948      * a wild-card protocol. But it is allowed to override any existing
 949      * non-permanent entry.  This means that when we remove this entry, the
 950      * system automatically returns to the old behavior.
 951      */
 952     list_add_rcu(&p->list, last_perm);
 953 out:
 954     spin_unlock_bh(&inetsw_lock);
 955
 956     synchronize_net();
 957
 958     return;
 .....................
这个函数完成的工作就是把inetsw_array数组中相同的协议类型下边的协议,
加入到inetsw对应的协议类型的链表中去,因为事实上一对一的关系,所以这个函数要简单得多
因为不存在其它成员,所以每一次list_entry都为空值,所以不存在覆盖和追加的情况,直接调用
list_add_rcu(&p->list, last_perm);
把协议类型节点(struct inet_protosw类型的数组的某个元素)添加到链表(链表首部本身是一个数组,
数组索引是协议对应的协议类型的值)的第一个成员.

OK,绕了这么大一圈子,了解了协议的封装及链表的注册. 现在回到inet_create中来
 220 /*
 221  *  Create an inet socket.
 222  */
 223
 224 static int inet_create(struct socket *sock, int protocol)
 225 {
 226     struct sock *sk;
 227     struct list_head *p;
 228     struct inet_protosw *answer;
 229     struct inet_sock *inet;
 230     struct proto *answer_prot;
 231     unsigned char answer_flags;
 232     char answer_no_check;
 233     int try_loading_module = 0;
 234     int err;
 235
 236     sock->state = SS_UNCONNECTED;
socket的初始状态设置为“未连接”,这意味着面向连接的协议类型,如 tcp,在使用之前必须建立连接, 修改状态位.

 237
 238     /* Look for the requested type/protocol pair. */
 239     answer = NULL;
 240 lookup_protocol:
 241     err = -ESOCKTNOSUPPORT;
 242     rcu_read_lock();
 243     list_for_each_rcu(p, &inetsw[sock->type]) {
 244         answer = list_entry(p, struct inet_protosw, list);
 245
 246         /* Check the non-wild match. */
 247         if (protocol == answer->protocol) {
 248             if (protocol != IPPROTO_IP)
 249                 break;
 250         } else {
 251             /* Check for the two wild cases. */
 252             if (IPPROTO_IP == protocol) {
 253                 protocol = answer->protocol;
 254                 break;
 255             }
 256             if (IPPROTO_IP == answer->protocol)
 257                 break;
 258         }
 259         err = -EPROTONOSUPPORT;
 260         answer = NULL;
 261     }                                                                                                                  
这个循环根据socket(2)调用的protocol把之前在链表中注册的协议节点找出来.
一个问题是,因为一一对应关系的存在,用户态调用socket(2)的时候,常常第三个参数直接就置 0 了.
也就是这里protocol为 0.那内核又如何处理这一默认值呢?
也就是protocol != answer->protocol,而是被if (IPPROTO_IP == protocol) 所匹配了.
这样将protocol置为链表中第一个协议,而当循环结束时,answer自然也是指向这个链表中的第一个注册节点.
假设SOCK_STREAM下同时注册了TCP和123,那么这里默认就取TCP了.当然如果把123在inetsw_array数组中的
位置调前,那么就 默认取123了.

将创建的socket的ops函数指针集指向具体协议类型的.例如创建的是SOCK_STREAM,
那么就指向了inet_stream_ops.
 289     sock->ops = answer->ops;
answer_prot指针指向当前要创建的socket的协议类型下边的协议,如上例它就是IPPROTO_TCP的tcp_prot结构
 290     answer_prot = answer->prot;
 291     answer_no_check = answer->no_check;
 292     answer_flags = answer->flags;
 293     rcu_read_unlock();
 294
 295     BUG_TRAP(answer_prot->slab != NULL);

接下来一个重要的工作,就是为 socket 分配一个sock,并初始化它
 298     sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
 299     if (sk == NULL)
 300         goto out;
 301
 302     err = 0;
 303     sk->sk_no_check = answer_no_check;
 304     if (INET_PROTOSW_REUSE & answer_flags)
 305         sk->sk_reuse = 1;
 306
 307     inet = inet_sk(sk);
 308     inet->is_icsk = INET_PROTOSW_ICSK & answer_flags;
 309
 310     if (SOCK_RAW == sock->type) {
 311         inet->num = protocol;
 312         if (IPPROTO_RAW == protocol)
 313             inet->hdrincl = 1;
 314     }
 315
 316     if (ipv4_config.no_pmtu_disc)
 317         inet->pmtudisc = IP_PMTUDISC_DONT;
 318     else
 319         inet->pmtudisc = IP_PMTUDISC_WANT;

 321     inet->id = 0;
 322
 323     sock_init_data(sock, sk);
 324
 325     sk->sk_destruct    = inet_sock_destruct;
 326     sk->sk_family      = PF_INET;
 327     sk->sk_protocol    = protocol;
 328     sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
 329
 330     inet->uc_ttl    = -1;
 331     inet->mc_loop   = 1;
 332     inet->mc_ttl    = 1;
 333     inet->mc_index  = 0;
 334     inet->mc_list   = NULL;
 335
 336     sk_refcnt_debug_inc(sk);
 337
 338     if (inet->num) {
 339         /* It assumes that any protocol which allows
 340          * the user to assign a number at socket
 341          * creation time automatically
 342          * shares.
 343          */
 344         inet->sport = htons(inet->num);
 345         /* Add to protocol hash chains. */
 346         sk->sk_prot->hash(sk);
 347     }
 348
 349     if (sk->sk_prot->init) {
 350         err = sk->sk_prot->init(sk);
 351         if (err)
 352             sk_common_release(sk);
 353     }
 354 out:
 355     return err;
 356 out_rcu_unlock:
 357     rcu_read_unlock();  
 358     goto out;
 359 }
虽然create的代码就到这儿了,不过要说清楚sk的分配,还得费上大力气.每一个 Socket 套接字,
都有一个对应的struct socket结构来描述(内核中一般使用名称为sock),但是同时又有一个
struct sock结构(内核中一般使用名称为 sk).两者之间是一一对应的关系.在后面的sock_init_data函数中可以看到
sk->sk_socket=sock;
sock->sk=sk;
这样的代码.

socket结构和sock结构实际上是同一个事物的两个方面.不妨说socket结构是面向进程和系统调用界面的侧面,
而sock结构则是面向底层驱动程序的侧面.设计者把socket套接字中与文件系统关系比较密切的那一部份放在
socket结构中而把与通信关系比较密切的那一部份,则单独成为一个数结结构,那就是sock结构.
由于这两部份逻辑上本来就是一体的,所以要通过指针互相指向对方形成一对一的关系.

再暂时回到inet_init中来,初始化工作中有如下代码:
1262     rc = proto_register(&tcp_prot, 1);
1263     if (rc)
1264         goto out;
1265
1266     rc = proto_register(&udp_prot, 1);
1267     if (rc)
1268         goto out_unregister_tcp_proto;
1269
1270     rc = proto_register(&raw_prot, 1);
1271     if (rc)
1272         goto out_unregister_udp_proto;
这里为每个protocol都调用了proto_register函数,其重要功能之一就是根据协议的obj_size成员的大小,
为协议创建高速缓存.
1701 static DEFINE_RWLOCK(proto_list_lock);
1702 static LIST_HEAD(proto_list);
1703
1704 int proto_register(struct proto *prot, int alloc_slab)
1705 {
1706     char *request_sock_slab_name = NULL;
1707     char *timewait_sock_slab_name;
1708     int rc = -ENOBUFS;
1709
1710     if (alloc_slab) {

可以看到函数最重要的功能就是根据prot的obj_size成员的大小为协议创建高速缓存
1711         prot->slab = kmem_cache_create(prot->name, prot->obj_size, 0,
1712                            SLAB_HWCACHE_ALIGN, NULL, NULL);
1713
1714         if (prot->slab == NULL) {
1715             printk(KERN_CRIT "%s: Can't create sock SLAB cache!\n",
1716                    prot->name);
1717             goto out;
1718         }
1719
顺便看到它的另一个重要的功能是维护一个以proto_list为首的链表
1758     write_lock(&proto_list_lock);
1759     list_add(&prot->node, &proto_list);
1760     write_unlock(&proto_list_lock);
这里要注意的是prot->obj_size的大小,它它非仅仅是一个sk的大小,以 TCP为例:
.obj_size = sizeof(struct tcp_sock)。稍后再来分析这个东东

回到inet_create()函数中来,其调用sk_alloc()分配一个sk
sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
840 struct sock *sk_alloc(int family, gfp_t priority,
 841               struct proto *prot, int zero_it)
 842 {
 843     struct sock *sk = NULL;
 844     kmem_cache_t *slab = prot->slab;
 845
 846     if (slab != NULL)
 847         sk = kmem_cache_alloc(slab, priority);
 848     else
 849         sk = kmalloc(prot->obj_size, priority);
 850
 851     if (sk) {
 852         if (zero_it) {
 853             memset(sk, 0, prot->obj_size);
 854             sk->sk_family = family;
 855             /*
 856              * See comment in struct sock definition to understand
 857              * why we need sk_prot_creator -acme
 858              */
 859             sk->sk_prot = sk->sk_prot_creator = prot;
 860             sock_lock_init(sk);
 861         }
 862        
 863         if (security_sk_alloc(sk, family, priority))
 864             goto out_free;
 865
 866         if (!try_module_get(prot->owner))
 867             goto out_free;
 868     }
 869     return sk;
 870
 871 out_free:
 872     if (slab != NULL)
 873         kmem_cache_free(slab, sk);
 874     else
 875         kfree(sk);
 876     return NULL;               
 877 }
在之前创建的高速缓存中申请分配一个slab缓存项并清零,然后设置协议族,并把sk中的sk_prot与对应的协议关联起来

初始化sk

分配完成sk后另一个重要的功能就是初始化它,sk的成员相当复杂,其主要的初始化工作是在函数sock_init_data()
中完成的.
1477 void sock_init_data(struct socket *sock, struct sock *sk)
1478 {
1479     skb_queue_head_init(&sk->sk_receive_queue);
1480     skb_queue_head_init(&sk->sk_write_queue);
1481     skb_queue_head_init(&sk->sk_error_queue);
sock结构中有三个重要的双向队列分别是sk_receive_queue,sk_write_queue和sk_error_queue
从它们的名字就可以看出来其作用了
队列并非采用通用的list_head来维护而是使用skb_buffer队列
// 109 struct sk_buff_head {                                                                                                  
// 110     /* These two members must be first. */
// 111     struct sk_buff  *next;
// 112     struct sk_buff  *prev;
// 113    
// 114     __u32       qlen;
// 115     spinlock_t  lock;
// 116 }; 
这样队列中指向的每一个skb_buffer就是一个数据包,分别是接收、发送和投递错误
剩余的就是初始化其它成员变量了,后面再来专门分析这些成员的作用
1482 #ifdef CONFIG_NET_DMA
1483     skb_queue_head_init(&sk->sk_async_wait_queue);
1484 #endif
1485
1486     sk->sk_send_head    =   NULL;
1487
1488     init_timer(&sk->sk_timer);
1489    
1490     sk->sk_allocation   =   GFP_KERNEL;
1491     sk->sk_rcvbuf       =   sysctl_rmem_default;
1492     sk->sk_sndbuf       =   sysctl_wmem_default;
1493     sk->sk_state        =   TCP_CLOSE;
1494     sk->sk_socket       =   sock;
1495
1496     sock_set_flag(sk, SOCK_ZAPPED);
1497
1498     if(sock)
1499     {
1500         sk->sk_type =   sock->type;
1501         sk->sk_sleep    =   &sock->wait;
1502         sock->sk    =   sk;
1503     } else
1504         sk->sk_sleep    =   NULL;
1505
1506     rwlock_init(&sk->sk_dst_lock);
1507     rwlock_init(&sk->sk_callback_lock);
1508     lockdep_set_class(&sk->sk_callback_lock,
1509                af_callback_keys + sk->sk_family);
1510
1511     sk->sk_state_change =   sock_def_wakeup;
1512     sk->sk_data_ready   =   sock_def_readable;
1513     sk->sk_write_space  =   sock_def_write_space;  
1514     sk->sk_error_report =   sock_def_error_report;
1515     sk->sk_destruct     =   sock_def_destruct;
1516
1517     sk->sk_sndmsg_page  =   NULL;
1518     sk->sk_sndmsg_off   =   0;
1519
1520     sk->sk_peercred.pid     =   0;
1521     sk->sk_peercred.uid =   -1;
1522     sk->sk_peercred.gid =   -1;
1523     sk->sk_write_pending    =   0;
1524     sk->sk_rcvlowat     =   1;
1525     sk->sk_rcvtimeo     =   MAX_SCHEDULE_TIMEOUT;
1526     sk->sk_sndtimeo     =   MAX_SCHEDULE_TIMEOUT;
1527
1528     sk->sk_stamp.tv_sec     = -1L;
1529     sk->sk_stamp.tv_usec    = -1L;
1530
1531     atomic_set(&sk->sk_refcnt, 1);
1532 }

inet_create函数中除了初始化sk成员的值还有一部份代码是初始化一个inet的东东
 307     inet = inet_sk(sk);
 308     inet->is_icsk = INET_PROTOSW_ICSK & answer_flags;

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()函数
 422 int sock_map_fd(struct socket *sock)
 423 {
 424     struct file *newfile;
 425     int fd = sock_alloc_fd(&newfile);
 426
 427     if (likely(fd >= 0)) {
 428         int err = sock_attach_fd(sock, newfile);
 429
 430         if (unlikely(err < 0)) {           
 431             put_filp(newfile);
 432             put_unused_fd(fd);
 433             return err;  
 434         }                
 435         fd_install(fd, newfile);   
 436     }                    
 437     return fd;           
 438 }
这个函数的核心思想在一开始就已经分析过了.从进程的角度来讲一个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,所以这三者可以通过强制类型转换来获取需要的指针
  4. 由于水平有限,文件系统的一些细节被我跳过了,sk 中的大多数成员变量的作用,也被我跳出过了.
    呵呵,还好,终于还是把这块流程给初步分析出来了.另外当时写的时候,没有想到会写这么长,
    大大超出了每贴的字限制。所以,每个小节内容跟标题可能会有点对不上号。

posted on 2013-02-16 21:51 大龙 阅读(1171) 评论(1)  编辑 收藏 引用

评论

# re: Linux TCP/IP协议栈之Socket的实现分析(一 套接字的创建) --- 转 2013-08-09 15:31 wtd321@163.com

sadsd  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理