Linux TCP/IP 协议栈之 Socket的实现分析

原文地址:http://blog.csdn.net/zyq2007/article/details/6576403
Linux TCP/IP 协议栈之 Socket的实现分析(一  套接字的创建) 
 
[size=6]Linux TCP/IP 协议栈之 Socket的实现分析[/size] 
 
内核版本:2.6.12 
作者:kendo 
版权所有,转载请注明出处[[url]www.skynet.org.cn[/url]]; 
说明:这仅仅是一个笔记,由于偶的水平有限,我甚至不能保证其中内容正确率超过 80%。另外,
我不太习惯在代码中注解来自哪个文件,第几行之类的,因为偶是直接通过 source insight 双击鼠标
跳转之。 
 
[size=5]第一部份 Socket套接字的创建[/size] 
 
socket 并不是 TCP/IP 协议的一部份。 
从广义上来讲,socket 是 Unix/Linux 抽像的进程间通讯的一种方法。网络 socket 通讯仅仅是其若干
协议中的一类。而 tcp/ip又是网络这类中的一种。 
从 tcp/ip 的解度看 socket,它更多地体现了用户 API 与协议栈的一个中间层接口层。用户通过调用
socket API将报文递交给协议栈,或者从协议栈中接收报文件。 
 
[color=Red][size=5]一、系统总入口[/size][/color] 
 
Linux 内核为所有的与 socket 有关的操作的 API,提供了一个统一的系统调用入口,其代码在
net/socket.c中: 
[code]asmlinkage long sys_socketcall(int call, unsigned long __user *args) 

        unsigned long a[6]; 
        unsigned long a0,a1; 
        int err; 
 
        if(call<1||call>SYS_RECVMSG) 
                return -EINVAL; 
 
        /* 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; 
}[/code] 
 
首先调用 copy_from_user将用户态参数拷贝至数组 a。但是问题在于,每个被调用的 API 的参数不
尽相同,那么每次拷贝的字节在小如果断定? 
来看其第三个参数 nargs[call],其中 call 是操作码,后面有个大大的 switch...case 就是判断它。对应
的操作码定义在 include/linux/net.h: 
[code]#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)                */[/code] 
 
而数组 nargs则根据操作码的不同,计算对应的参数的空间大小: 
[code]/* 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[/code] 
 
当拷贝完成参数后,就进入一个 switch...case...判断操作码,跳转至对应的系统接口。 [color=Red][size=5]二、 sys_socket 函数[/size][/color] 
 
操作码 SYS_SOCKET 是由 sys_socket()实现的: 
[code]asmlinkage long sys_socket(int family, int type, int protocol) 

        int retval; 
        struct socket *sock; 
 
        retval = sock_create(family, type, protocol, &sock); 
        if (retval < 0) 
                goto out; 
 
        retval = sock_map_fd(sock); 
        if (retval < 0) 
                goto out_release; 
 
out: 
        /* It may be already another descriptor 8) Not kernel problem. */ 
        return retval; 
 
out_release: 
        sock_release(sock); 
        return retval; 
}[/code] 
 
在分析这段代码之间,首先来看,创建一个 Socket,对内核而言,究竟意味着什么?究竟需要内核
干什么事? 
 
当用户空间要创建一个 socke 接口时,会调用 API 函数: 
[code]int socket(int domain, int type, int protocol);[/code] 
 
函数,其三个参数分别表示协议族、协议类型(面向连接或无连接)以及协议。 
 
对于用户态而言,一个 Scoket,就是一个特殊的,已经打开的文件。为了对 socket抽像出文件的概
念,内核中为 socket 定义了一个专门的文件系统类型 sockfs: 
static struct vfsmount *sock_mnt; 
 
[code]static struct file_system_type sock_fs_type = { 
        .name =                "sockfs", 
        .get_sb =        sockfs_get_sb, 
        .kill_sb =        kill_anon_super, 
};[/code] 
 
在模块初始化的时候,安装该文件系统:  
[code]void __init sock_init(void) 

        …… 
        register_filesystem(&sock_fs_type); 
        sock_mnt = kern_mount(&sock_fs_type);         
}[/code] 
 
稍后还要回来继续分析安装中的一点细节。 
 
有了文件系统后,对内核而言,创建一个 socket, 就是在 sockfs 文件系统中创建一个文件节点(inode),
并建立起为了实现 socket 功能所  需的一整套数据结构,包括 struct inode 和 struct socket 结构。 struct 
socket 结构在内核中,就代表了一个"Socket",当一个 struct socket 数据结构被分配空间后,再将其
与一个已打开的文件“建立映射关系”。这样,用户态就可以用抽像的文件的概念来操作socket了——
当然,由  于网络的特殊性,至少就目前而言,这种抽像,并不如其它模块的抽像那么完美。 
 
这里socket的实现,和文件系统密切相关。这里就不再分析Linux的文件系统了,这里只分配与socket
相关的一些细节,其它的都一一跳过,呵呵,希望也能有水平再写一篇《Linux 文件系统的设计与
实现简析》。 
 
文件系统 struct vfsmount 中有一个成员指针 mnt_sb 指向该文件系统的超级块,而超级块结构 struct 
super_lock 有一个重要的成员 s_op 指向了超级块的操作函数表,其中有函数指针 alloc_inode()即为
在给定的超级块下创建并初始化一 个新的索引节点对像。也就是调用: 
[code]sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);[/code] 
当然,连同相关的处理细节一起,这一操作被层层封装至一个上层函数 new_inode()。 
 
那如何分配一个 struct socket 结构呢?如前所述,一个 socket 总是与一个inode 密切相关的。当然,
在 inode 中,设置一个 socket 成员,是完全可行的,但是  这貌似浪费了空间——毕竟,更多的文件
系统没有 socket 这个东东。所以,内核引入了另一个 socket_alloc 结构: 
[code]struct socket_alloc { 
        struct socket socket; 
        struct inode vfs_inode; 
};[/code] 
显而易见,该结构实现了 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 呢?现在来简要地看一个这个流程——这是文件系统安装中的一个重要步骤: 
[code]struct vfsmount *kern_mount(struct file_system_type *type) 

        return do_kern_mount(type->name, 0, type->name, NULL); 
}[/code] 
 
[code]struct vfsmount * 
do_kern_mount(const char *fstype, int flags, const char *name, void *data) 

        struct file_system_type *type = get_fs_type(fstype); 
        struct super_block *sb = ERR_PTR(-ENOMEM); 
                …… 
                sb = type->get_sb(type, flags, name, data); 
                …… 
               mnt->mnt_sb = sb; 
               …… 
}[/code] 
 
do_kern_mount 函数中,先根据注册的文件系统类型,调用 get_fs_type 获取之,也就是我们之前注
册的 sock_fs_type,然后调用它的 get_sb 成员函数指针,获取相应的超级块 sb。最后,调置文件系
统的超级块成员指针,使之指向对应的值。 
这里 get_sb函数指针,指向之前初始化的 sockfs_get_sb()函数。 
static struct super_block *sockfs_get_sb(struct file_system_type *fs_type, 
        int flags, const char *dev_name, void *data) 

        return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC); 

 
注意其第三个参数 sockfs_ops,它封装了 sockfs 的功能函数表: 
[code]static struct super_operations sockfs_ops = { 
        .alloc_inode =        sock_alloc_inode, 
        .destroy_inode =sock_destroy_inode, 
        .statfs =        simple_statfs, 
};[/code] 
 
[code]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; 
}[/code]  
这里就是先获取/分配一个超级块,然后初始化超级块的各成员,包括 s_op,我们前面提到过它,
它封装了对应的功能函数表。这里 s_op 自然就指向了 sockfs_ops。那前面提到的 new_inode()函数
分配 inode 时调用的: 
[code]sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);[/code] 
这个 alloc_inode 函数指针也就是 sockfs_ops的 sock_alloc_inode()函数——转了一大圈,终于指到它
了。 
来看看 sock_alloc_inode 是如何分配一个 inode 节点的: 
[code]static struct inode *sock_alloc_inode(struct super_block *sb) 

        struct socket_alloc *ei; 
        ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL); 
        if (!ei) 
                return NULL; 
        init_waitqueue_head(&ei->socket.wait); 
         
        ei->socket.fasync_list = NULL; 
        ei->socket.state = SS_UNCONNECTED; 
        ei->socket.flags = 0; 
        ei->socket.ops = NULL; 
        ei->socket.sk = NULL; 
        ei->socket.file = NULL; 
        ei->socket.flags = 0; 
 
        return &ei->vfs_inode; 
}[/code] 
 
函数先分配了一个用于封装 socket 和 inode 的 ei,然后在高速缓存中为之申请了一块空间。这样,
inode 和 socket 就同时都被分配了。接下来初始化 socket 的各个成员,这些成员,在后面都会一一
提到。 
[code]/** 
*  struct socket - general BSD socket 
*  @state: socket state (%SS_CONNECTED, etc) 
*  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc) 
*  @ops: protocol specific socket operations 
*  @fasync_list: Asynchronous wake up list 
*  @file: File back pointer for gc 
*  @sk: internal networking protocol agnostic socket representation 
*  @wait: wait queue for several uses 
*  @type: socket type (%SOCK_STREAM, etc) 
*/ 
struct socket { 
        socket_state                state; 
        unsigned long                flags;         struct proto_ops        *ops; 
        struct fasync_struct        *fasync_list; 
        struct file                *file; 
        struct sock                *sk; 
        wait_queue_head_t        wait; 
        short                        type; 
};[/code] 
 
OK,至目前为止,分配 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方法(前面说socket
的文件界面的抽像并不完美,这应该是一个佐证 吧?),所以不能期望用户界面通过调用 open() API
来分配一个 struct file,而是通过调用 get_empty_filp 来获取: 
[code]struct file *file = get_empty_filp();[/code] 
 
同样地: 
[code]int fd; 
fd = get_unused_fd();[/code] 
获取一个空间的文件描述符 
 
然后,让 current 的 files指针的 fd 数组的 fd 索引项指向该 file: 
 
[code]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); 
}[/code] 
 
OK,做到这一步,有了一个文件描述符 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就完整地串起来了。 
 
基本要分析的一些前导的东东都一一罗列了,虽然已尽量避免陷入文件系统的细节分析,但是还是
不可避免地进入其中,因为它们关系实现太紧密了。现在可以来看套接字的创建过程了: 
 
[code]asmlinkage long sys_socket(int family, int type, int protocol) 

        int retval; 
        struct socket *sock; 
 
        retval = sock_create(family, type, protocol, &sock); 
        if (retval < 0) 
                goto out; 
 
        retval = sock_map_fd(sock); 
        if (retval < 0) 
                goto out_release; 
 
out: 
        /* It may be already another descriptor 8) Not kernel problem. */ 
        return retval; 
 
out_release:         sock_release(sock); 
        return retval; 
}[/code] 
 
[code]int sock_create(int family, int type, int protocol, struct socket **res) 

        return __sock_create(family, type, protocol, res, 0); 
}[/code] 
 
[color=Red][size=5]三、af_inet 协议簇的协议封装[/size][/color] 
 
接下来,函数调用之前已经注的 inet_family_ops的函数指针 create,也就是inet_create()函数,前面,
可以说一个通用的 socket 已经创建好了,这里要完成与协议本身相关的一些创建 socket 的工作。
这一部份的工作比较复杂,还是先来看看 af_inet.c 中的模块初  始化时候,做了哪些与此相关的工
作。 
 
要引入的第一个数据结构是 struct inet_protosw,它封装了一个协议类型(如 SOCK_STREAM、
SOCK_DGRAM 等)与 ip 协议中对应的传输层协议: 
[code]/* This is used to register socket interfaces for IP protocols.  */ 
struct inet_protosw { 
        struct list_head list; 
 
        /* These two fields form the lookup key.  */ 
        unsigned short         type;           /* This is the 2nd argument to socket(2). */ 
        int                 protocol; /* This is the L4 protocol number.  */ 
 
        struct proto         *prot; 
        struct proto_ops *ops; 
   
        int              capability; /* Which (if any) capability do 
                                      * we need to use this socket 
                                      * interface? 
                                      */ 
        char             no_check;   /* checksum on rcv/xmit/none? */ 
        unsigned char         flags;      /* See INET_PROTOSW_* below.  */ 
}; 
#define INET_PROTOSW_REUSE 0x01             /* Are ports automatically reusable? */ 
#define INET_PROTOSW_PERMANENT 0x02  /* Permanent protocols are unremovable. */[/code] 
 
type  是协议类型,对于 ipv4 而言,就是 SOCK_STREAM、SOCK_DGRAM 或者是 SOCK_RAW 之
一。protocol 是传输层的协议号。 prot 用于描述一个具体的传输层协议,而 ops 指向对应的当前协
议类型的操作函数集。针对不同的协议类型,定义了不同的 ops: 
 
[code]struct proto_ops inet_stream_ops = {         .family =        PF_INET, 
        .owner =        THIS_MODULE, 
        .release =        inet_release, 
        .bind =                inet_bind, 
        .connect =        inet_stream_connect, 
        .socketpair =        sock_no_socketpair, 
        .accept =        inet_accept, 
        .getname =        inet_getname, 
        .poll =                tcp_poll, 
        .ioctl =        inet_ioctl, 
        .listen =        inet_listen, 
        .shutdown =        inet_shutdown, 
        .setsockopt =        sock_common_setsockopt, 
        .getsockopt =        sock_common_getsockopt, 
        .sendmsg =        inet_sendmsg, 
        .recvmsg =        sock_common_recvmsg, 
        .mmap =                sock_no_mmap, 
        .sendpage =        tcp_sendpage 
};[/code] 
 
[code]struct proto_ops inet_dgram_ops = { 
        .family =        PF_INET, 
        .owner =        THIS_MODULE, 
        .release =        inet_release, 
        .bind =                inet_bind, 
        .connect =        inet_dgram_connect, 
        .socketpair =        sock_no_socketpair, 
        .accept =        sock_no_accept, 
        .getname =        inet_getname, 
        .poll =                udp_poll, 
        .ioctl =        inet_ioctl, 
        .listen =        sock_no_listen, 
        .shutdown =        inet_shutdown, 
        .setsockopt =        sock_common_setsockopt, 
        .getsockopt =        sock_common_getsockopt, 
        .sendmsg =        inet_sendmsg, 
        .recvmsg =        sock_common_recvmsg, 
        .mmap =                sock_no_mmap, 
        .sendpage =         inet_sendpage, 
};[/code] 
 
[code]/* 
* For SOCK_RAW sockets; should be the same as inet_dgram_ops but without 
* udp_poll */ 
static struct proto_ops inet_sockraw_ops = { 
        .family =        PF_INET, 
        .owner =        THIS_MODULE, 
        .release =        inet_release, 
        .bind =                inet_bind, 
        .connect =        inet_dgram_connect, 
        .socketpair =        sock_no_socketpair, 
        .accept =        sock_no_accept, 
        .getname =        inet_getname, 
        .poll =                datagram_poll, 
        .ioctl =        inet_ioctl, 
        .listen =        sock_no_listen, 
        .shutdown =        inet_shutdown, 
        .setsockopt =        sock_common_setsockopt, 
        .getsockopt =        sock_common_getsockopt, 
        .sendmsg =        inet_sendmsg, 
        .recvmsg =        sock_common_recvmsg, 
        .mmap =                sock_no_mmap, 
        .sendpage =         inet_sendpage, 
};[/code] 
 
从各个函数指针的名称,我们就可以大约知道它们是做什么事的了。进一步进以看到,它们的函数
指针指向的函数差不多都是相同的。除了一些细节上的区别,例如后面两种协议类型并不支持listen。  
 
socket() API 第二个参数是协议类型,第三个参数是该协议类型下的协议——不过对于 ipv4 而言,
它们都是一一对应的。但是从抽像封装的角度看,数据结构的设计本  身应该满足一个协议类型下
边,可能存在多个不同的协议,即一对多的情况。而一一对应,仅是它们的特例: 
[code]/* Upon startup we insert all the elements in inetsw_array[] into 
* the linked list inetsw. 
*/ 
static struct inet_protosw inetsw_array[] = 

        { 
                .type =       SOCK_STREAM, 
                .protocol =   IPPROTO_TCP, 
                .prot =       &tcp_prot, 
                .ops =        &inet_stream_ops, 
                .capability = -1, 
                .no_check =   0, 
                .flags =      INET_PROTOSW_PERMANENT, 
        }, 
 
        {                 .type =       SOCK_DGRAM, 
                .protocol =   IPPROTO_UDP, 
                .prot =       &udp_prot, 
                .ops =        &inet_dgram_ops, 
                .capability = -1, 
                .no_check =   UDP_CSUM_DEFAULT, 
                .flags =      INET_PROTOSW_PERMANENT, 
       }, 
         
 
       { 
               .type =       SOCK_RAW, 
               .protocol =   IPPROTO_IP,        /* wild card */ 
               .prot =       &raw_prot, 
               .ops =        &inet_sockraw_ops, 
               .capability = CAP_NET_RAW, 
               .no_check =   UDP_CSUM_DEFAULT, 
               .flags =      INET_PROTOSW_REUSE, 
       } 
};[/code] 
 
数组的每一个元素,就是支持的一种协议名称,例如 IPOROTO_TCP,但是由于 IPV4 本身协议类
型跟协议是一一对应的,所以没有更多的.type= SOCK_xxx 了。这样数组实现了对 PF_INET 协议族
下支持的协议类型,以及协议类型下边的协议进行了封装,虽然事实上它们是一一对应的关系,不
过  理论上,完全可能存在一对多的可能。 
 
数组内,封装的一个具体的协议,由 struct proto 结构来描述: 
[code]/* Networking protocol blocks we attach to sockets. 
* socket layer -> transport layer interface 
* transport -> network interface is defined by struct inet_proto 
*/ 
struct proto { 
        void                        (*close)(struct sock *sk,  
                                        long timeout); 
        int                        (*connect)(struct sock *sk, 
                                        struct sockaddr *uaddr,  
                                        int addr_len); 
        int                        (*disconnect)(struct sock *sk, int flags); 
 
        struct sock *                 (*accept) (struct sock *sk, int flags, int *err); 
 
        int                        (*ioctl)(struct sock *sk, int cmd, 
                                         unsigned long arg); 
        int                        (*init)(struct sock *sk);         int                        (*destroy)(struct sock *sk); 
        void                        (*shutdown)(struct sock *sk, int how); 
        int                        (*setsockopt)(struct sock *sk, int level,  
                                        int optname, char __user *optval, 
                                        int optlen); 
        int                        (*getsockopt)(struct sock *sk, int level,  
                                        int optname, char __user *optval,  
                                        int __user *option);            
        int                        (*sendmsg)(struct kiocb *iocb, struct sock *sk, 
                                           struct msghdr *msg, size_t len); 
        int                        (*recvmsg)(struct kiocb *iocb, struct sock *sk, 
                                           struct msghdr *msg, 
                                        size_t len, int noblock, int flags,  
                                        int *addr_len); 
        int                        (*sendpage)(struct sock *sk, struct page *page, 
                                        int offset, size_t size, int flags); 
        int                        (*bind)(struct sock *sk,  
                                        struct sockaddr *uaddr, int addr_len); 
 
        int                        (*backlog_rcv) (struct sock *sk,  
                                                struct sk_buff *skb); 
 
        /* Keeping track of sk's, looking them up, and port selection methods. */ 
        void                        (*hash)(struct sock *sk); 
        void                        (*unhash)(struct sock *sk); 
        int                        (*get_port)(struct sock *sk, unsigned short snum); 
 
        /* Memory pressure */ 
        void                        (*enter_memory_pressure)(void); 
        atomic_t                *memory_allocated;        /* Current allocated memory. */ 
        atomic_t                *sockets_allocated;        /* Current number of sockets. */ 
        /* 
         * Pressure flag: try to collapse. 
         * Technical note: it is used by multiple contexts non atomically. 
         * All the sk_stream_mem_schedule() is of this nature: accounting 
         * is strict, actions are advisory and have some latency. 
         */ 
        int                        *memory_pressure; 
        int                        *sysctl_mem; 
        int                        *sysctl_wmem; 
        int                        *sysctl_rmem; 
        int                        max_header; 
 
        kmem_cache_t                *slab;         unsigned int                obj_size; 
 
        struct module                *owner; 
 
        char                        name[32]; 
 
        struct list_head        node; 
 
        struct { 
                int inuse; 
                u8  __pad[SMP_CACHE_BYTES - sizeof(int)]; 
        } stats[NR_CPUS]; 
};[/code] 
 
以 TCP协议为例,TCP协议的 sokcet 操作函数都被封装在这里了。 
 
[code]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), };[/code] 
 
[color=Red][size=5]四、分配 struct sock(sk)[/size][/color] 
 
浏览到这里,看完了 PF_INET 的协议簇、协议类型和协议(也就是 socket调用的三个参数)的封装关
系,它们通过了两个数据结构 inet_protosw、struct proto 来描述,被一个数组 inetsw_array所封装。
接下来看它的初始化工作: 
[code]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); 
…… 
}[/code] 
 
inetsw 是一个数组,其每一个元素,都是一个链表首部,前面一个循环初始化之。后一个循环就值
得注意了,也就是函数 
inet_register_protosw: 
 
[code]void inet_register_protosw(struct inet_protosw *p) 

        struct list_head *lh; 
        struct inet_protosw *answer; 
        int protocol = p->protocol; 
        struct list_head *last_perm; 
 
        spin_lock_bh(&inetsw_lock); 
 
        if (p->type >= SOCK_MAX) 
                goto out_illegal; 
 
        /* If we are trying to override a permanent protocol, bail. */ 
        answer = NULL; 
        last_perm = &inetsw[p->type]; 
        list_for_each(lh, &inetsw[p->type]) { 
                answer = list_entry(lh, struct inet_protosw, list); 
 
                /* Check only the non-wild match. */ 
                if (INET_PROTOSW_PERMANENT & answer->flags) {                         if (protocol == answer->protocol) 
                                break; 
                        last_perm = lh; 
                } 
 
                answer = NULL; 
        } 
        if (answer) 
                goto out_permanent; 
 
        /* Add the new entry after the last permanent entry if any, so that 
         * the new entry does not override a permanent entry when matched with 
         * a wild-card protocol. But it is allowed to override any existing 
         * non-permanent entry.  This means that when we remove this entry, the  
         * system automatically returns to the old behavior. 
         */ 
        list_add_rcu(&p->list, last_perm); 
out: 
        spin_unlock_bh(&inetsw_lock); 
 
        synchronize_net(); 
 
        return; 
 
out_permanent: 
        printk(KERN_ERR "Attempt to override permanent protocol %d./n", 
               protocol); 
        goto out; 
 
out_illegal: 
        printk(KERN_ERR 
               "Ignoring attempt to register invalid socket type %d./n", 
               p->type); 
        goto out; 
}[/code] 
 
这个函数完成的工作,就是把 inetsw_array 数组中,相同的协议类型下边的协议,加入到 inetsw 对
应的协议类型的链表中去。因为事实上一对一  的关系,所以这个函数要简单得多:因为不存在其
它成员,所以每一次 list_entry 都为空值,所以不存在覆盖和追加的情况,直接调用 
list_add_rcu(&p->list, last_perm);把协议类型节点(struct inet_protosw  类型的数组的某个元素)添加到
链表(链表首部本身是一个数组,数组索引是协议对应的协议类型的值)的第一个成员。 
 
来做一个假设,如果 SOCK_STREAM 协议类型下边还有另一个协议,IPPROTO_123,那么
inetsw_array数组中就会多出一个元素: [code]        { 
                .type =       SOCK_STREAM, 
                .protocol =   IPPROTO_123, 
                .prot =       &123_prot, 
                …… 
        },[/code] 
 
这样,当遍历 inetsw_array,再次进入 inet_register_protosw 函数后,因为 SOCK_STREAM 类型下
已经注册了 IPPROTO_TCP,所以, 
[code]        list_for_each(lh, &inetsw[p->type]) { 
                answer = list_entry(lh, struct inet_protosw, list); 
 
                /* Check only the non-wild match. */ 
                if (INET_PROTOSW_PERMANENT & answer->flags) { 
                        if (protocol == answer->protocol)        /*  已经注册了相同协议号,退出循环,
因为没有置 answer 为 NULL,所以后面会直接退出函数 */ 
                                break; 
                        last_perm = lh;                                /*  移动位置指针,指向链表中最后一
个元素 */ 
                } 
 
                answer = NULL; 
        }[/code] 
这个循环,answer 就会指向之前注册的 TCP 的链表节点,然后根据标志,如果是
INET_PROTOSW_PERMANENT,则 last_perm  指向链表中最后一个节点,也就是 TCP,之后同样
的道理,再把 123 追加到 TCP 之后,如果是 INET_PROTOSW_REUSE,因为位置指针 last_perm
没有移动,则之前注册的元素会被覆盖。 
 
OK,绕了这么大一圈子,了解了协议的封装及链表的注册。现在回到 inet_create 中来: 
 
[code]/* 
*        Create an inet socket. 
*/ 
 
static int inet_create(struct socket *sock, int protocol) 

        struct sock *sk; 
        struct list_head *p; 
        struct inet_protosw *answer; 
        struct inet_sock *inet; 
        struct proto *answer_prot; 
        unsigned char answer_flags; 
        char answer_no_check; 
        int err;          
        sock->state = SS_UNCONNECTED;[/code] 
socket 的初始状态设置为“未连接”,这意味着面向连接的协议类型,如 tcp,在使用之前必须建立连
接修改状态位。 
 
[code]        answer = NULL; 
        rcu_read_lock(); 
        list_for_each_rcu(p, &inetsw[sock->type]) { 
                answer = list_entry(p, struct inet_protosw, list); 
 
                /* Check the non-wild match. */ 
                if (protocol == answer->protocol) { 
                        if (protocol != IPPROTO_IP) 
                                break; 
                } else { 
                        /* Check for the two wild cases. */ 
                        if (IPPROTO_IP == protocol) { 
                                protocol = answer->protocol; 
                                break; 
                        } 
                        if (IPPROTO_IP == answer->protocol) 
                                break; 
                } 
                answer = NULL; 
        }[/code] 
 
这个循环,根据 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 了。 
 
[code]        err = -ESOCKTNOSUPPORT; 
        if (!answer) 
                goto out_rcu_unlock; 
        err = -EPERM; 
        if (answer->capability > 0 && !capable(answer->capability)) 
                goto out_rcu_unlock; 
        err = -EPROTONOSUPPORT; 
        if (!protocol) 
                goto out_rcu_unlock; 
        /*  找到了组织,将创建的 socket 的 ops 函数指针集,指向协议类型的。例如创建的是SOCK_STREAM,那么就指向了 inet_stream_ops */ 
        sock->ops = answer->ops; 
        /* answer_prot 指针指向了当前要创建的 socket 的协议类型下边的协议,如上例,它就是
IPPROTO_TCP 的 tcp_prot 结构 */ 
        answer_prot = answer->prot; 
        answer_no_check = answer->no_check; 
        answer_flags = answer->flags; 
        rcu_read_unlock(); 
 
        BUG_TRAP(answer_prot->slab != NULL); 
[/code] 
 
接下来一个重要的工作,就是为 socket 分配一个sock,并初始化它: 
[code]        err = -ENOBUFS; 
        sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1); 
        if (sk == NULL) 
                goto out; 
 
        err = 0; 
        sk->sk_no_check = answer_no_check; 
        if (INET_PROTOSW_REUSE & answer_flags) 
                sk->sk_reuse = 1; 
 
        inet = inet_sk(sk); 
 
        if (SOCK_RAW == sock->type) { 
                inet->num = protocol; 
                if (IPPROTO_RAW == protocol) 
                        inet->hdrincl = 1; 
        } 
 
        if (ipv4_config.no_pmtu_disc) 
                inet->pmtudisc = IP_PMTUDISC_DONT; 
        else 
                inet->pmtudisc = IP_PMTUDISC_WANT; 
 
        inet->id = 0; 
 
        sock_init_data(sock, sk); 
 
        sk->sk_destruct           = inet_sock_destruct; 
        sk->sk_family           = PF_INET; 
        sk->sk_protocol           = protocol; 
        sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;  
        inet->uc_ttl        = -1; 
        inet->mc_loop        = 1; 
        inet->mc_ttl        = 1; 
        inet->mc_index        = 0; 
        inet->mc_list        = NULL; 
 
#ifdef INET_REFCNT_DEBUG 
        atomic_inc(&inet_sock_nr); 
#endif 
 
        if (inet->num) { 
                /* It assumes that any protocol which allows 
                 * the user to assign a number at socket 
                 * creation time automatically 
                 * shares. 
                 */ 
                inet->sport = htons(inet->num); 
                /* Add to protocol hash chains. */ 
                sk->sk_prot->hash(sk); 
        } 
 
        if (sk->sk_prot->init) { 
                err = sk->sk_prot->init(sk); 
                if (err) 
                        sk_common_release(sk); 
        } 
out: 
        return err; 
out_rcu_unlock: 
        rcu_read_unlock(); 
        goto out; 
}[/code] 
 
虽然 create的代码就到这儿了,不过要说清楚 sk 的分配,还得费上大力气。 
每一个 Socket 套接字,都有一个对应的 struct socket 结构来描述(内核中一般使用名称为 sock),但
是同时又一个 struct sock 结构(内核中一般使用名称为 sk)。两者之间是一一对应的关系。在后面
的 sock_init_data 函数中,可以看到 
[code]sk->sk_socket                =        sock; 
sock->sk        =        sk;[/code]这样的代码。 
 
socket 结构和 sock 结构实际上是同一个事物的两个方面。不妨说,socket 结构是面向进程和系统调
用界面的侧面,而 sock 结构则是面向底层驱  动程序的侧面。设计者把 socket 套接字中,与文件系
统关系比较密切的那一部份放在 socket 结构中,而把与通信关系比较密切的那一部份,则单独成为 一个数结结构,那就是 sock 结构。由于这两部份逻辑上本来就是一体的,所以要通过指针互相指向
对方,形成一对一的关系。 
 
再暂时回到 inet_init 中来,初始化工作中,有如下代码: 
[code]        rc = proto_register(&tcp_prot, 1); 
        if (rc) 
                goto out; 
 
        rc = proto_register(&udp_prot, 1); 
        if (rc) 
                goto out_unregister_tcp_proto; 
 
        rc = proto_register(&raw_prot, 1); 
        if (rc) 
                goto out_unregister_udp_proto;[/code] 
 
这里为每个 protocol 都调用了 proto_register 函数,其重要功能之一,就是根据协议的 obj_size 成员
的大小,为协议创建高速缓存: 
[code]static DEFINE_RWLOCK(proto_list_lock); 
static LIST_HEAD(proto_list); 
 
int proto_register(struct proto *prot, int alloc_slab) 

        int rc = -ENOBUFS; 
 
        if (alloc_slab) { 
        /*  可以看到,函数最重要的功能就是根据 prot 的obj_size 成员的大小,为协议创建高速缓存 
*/         
                prot->slab = kmem_cache_create(prot->name, prot->obj_size, 0, 
                if (prot->slab == NULL) { 
                        printk(KERN_CRIT "%s: Can't create sock SLAB cache!/n", 
                               prot->name); 
                        goto out; 
                } 
        } 
 
        /*  顺便看到它的另一个重要的功能,是维护一个以 proto_list 为首的链表 */ 
        write_lock(&proto_list_lock); 
        list_add(&prot->node, &proto_list); 
        write_unlock(&proto_list_lock); 
        rc = 0; 
out: 
        return rc; 
}[/code]  
这里要注意的是:prot->obj_size 的大小,它它非仅仅是一个 sk 的大小!! !以 TCP为例:.obj_size = 
sizeof(struct tcp_sock)。稍后再来分析这个东东。 
 
回到 inet_create()函数中来,其调用 sk_alloc()分配一个 sk: 
[code]sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);[/code] 
 
[code]struct sock *sk_alloc(int family, int priority, struct proto *prot, int zero_it) 

        struct sock *sk = NULL; 
        kmem_cache_t *slab = prot->slab; 
 
        if (slab != NULL) 
                sk = kmem_cache_alloc(slab, priority); 
        else 
                sk = kmalloc(prot->obj_size, priority); 
 
        if (sk) { 
                if (zero_it) { 
                        memset(sk, 0, prot->obj_size); 
                        sk->sk_family = family; 
                        /* 
                         * See comment in struct sock definition to understand 
                         * why we need sk_prot_creator -acme 
                         */ 
                        sk->sk_prot = sk->sk_prot_creator = prot; 
                        ……[/code] 
 
在之前创建的高速缓存中申请分配一个 slab 缓存项。并清零。然后设置协议族、并把 sk 中的 sk_prot
与对应的协议关联起来。 
 
[color=Red][size=5]五、初始化 sk[/size][/color] 
 
分配完成 sk 后,另一个重要的功能就是初始化它,sk 的成员相当复杂,其主要的初始化工作是在
函数 sock_init_data()中完成的: 
[code]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); 
}[/code] 
 
sock 结构中,有三个重要的双向队列,分别是 sk_receive_queue、sk_write_queue和 sk_error_queue。
从它们的名字就可以看出来其作用了。 
队列并非采用通用的 list_head 来维护,而是使用 skb_buffer 队列: 
[code]struct sk_buff_head { 
        /* These two members must be first. */ 
        struct sk_buff        *next; 
        struct sk_buff        *prev; 
 
        __u32                qlen; 
        spinlock_t        lock; 
};[/code] 
 
这样,队列中指向的每一个 skb_buffer,就是一个数据包,分别是接收、发送和投递错误。 
 
剩余的就是初始化其它成员变量了。后面再来专门分析这些成员的作用。 
 
inet_create 函数中,除了初始化 sk 成员的值,还有一部份代码,是初始化一个 inet的东东: 
[code]                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 结构类型,来看它的定义: 
[code]struct inet_sock { 
        /* sk and pinet6 has to be the first two members of inet_sock */ 
        struct sock                sk; 
               …… 
}[/code] 
只留意它的第一个成员就足够了。 
 
我们说 sock 是面向用户态调用,而 sk 是面向内核驱动调用的,那 sk 是如何与协议栈交互的呢?对
于每一个类型的协议,为了与 sk 联系起来,都定义了一个 struct XXX_sock 结构,XXX是协议名,
例如: 
[code]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; 
};[/code] 
 
很明显,它们的结构定构是“af_inet一般属性+自己的私有属性”,因为它们的第一个成员总是 inet。  
 
呵呵,现在回头来照一下起初在 af_inet.c 中,封装协议注册的时候,size成员,对于 tcp 而言: 
.obj_size                = sizeof(struct tcp_sock), 
其它协议类似。 
 
以 obj_size 来确定每个 slab 缓存项分配的大小,所以,我们就可说,每次申请分配的,实际上是一
个 struct XXX_sock 结构大小的结构。因为都是定义于上层结构的第一个成员,可以使用强制类型
转换来使用这块分配的内存空间。例如: 
[code]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; 
}[/code] 
 
OK,inet_create()运行完,一个 socket 套接字基本上就创建完毕了,剩下的就是与文件系统挂钩,回到最初的 sys_socket()函数中来,它在调用完 sock_create()后,紧接着调用 sock_map_fd()函数: 
[code] 
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; 
}[/code] 
 
这个函数的核心思想,在一开始,就已经分析过了。从进程的角度来讲,一个socket 套接字就是一
个特殊的,已打开的文件。前面分配好一个 socket  后,这里要做的就是将它与文件系统拉上亲戚关
系。首先获取一个空闲的文件描述符号和 file 结构。然后在文件系统中分配一个目录项(d_alloc),
使  其指向已经分配的 inode 节点(d_add),然后把其目录项挂在 sockfs 文件系统的根目录之下。并
且把目录项的指针 d_op设置成指向 sockfs_dentry_operati,这个数据结构通过函数指针提供他与文
件路径有关的操作: 
[code]static struct dentry_operations sockfs_dentry_operations = { 
        .d_delete =        sockfs_delete_dentry, 
};[/code] 
 
最后一步,就是将 file 结构中的 f_op 和 sock 结构中的 i_fop 都指向 socket_file_ops,它是一个函数
指针集,指向了 socket面向文件系统的用户态调用的一些接口函数: 
[code]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 
};[/code] 
 
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 中的大多数成员变量的作用,也被我跳出
过了。呵呵,还好,终于还是把这块流程给初步分析出来了。另外,当时写的时候,没有想到会写
这么长,大大超出了每贴的字限制。所以,每个小节内容跟标题可能会有点对不上号。 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Linux TCP/IP 协议栈之 Socket的实现分析(二 Socket套接字的地址绑定) 
 
[color=Red][size=6]第二部份 Socket的bind(2),绑定地址[/size][/color] 
 
[size=5][color=Red]1、bind(2)[/color][/size] 
 
当创建了一个 Socket 套接字后,对于服务器来说,接下来的工作,就是调用 bind(2)为服务器指明
本地址、协议端口号,常常可以看到这样的代码: 
strut sockaddr_in sin; 
 
sin.sin_family = AF_INET; 
sin.sin_addr.s_addr = xxx; 
sin.sin_port = xxx; 
 bind(sock, (struct sockaddr *)&sin, sizeof(sin)); 
 
从这个系统调用中,可以知道当进行 SYS_BIND  操作的时候,: 
1、对于 AF_INET 协议簇来讲,其地址格式是 strut sockaddr_in,而对于 socket 来讲,strut sockaddr 
结构表示的地址格式实现了更高层次 
的抽像,因为每种协议长簇的地址不一定是相同的,所以,系统调用的第三个参数得指明该协议簇
的地址格式的长度。 
 
2、进行 bind(2)系统调用时,除了地址长度外,还得向内核提供:sock 描述符、协议簇名称、本地
地址、端口这些参数; 
 
[size=5][color=Red]2、sys_bind()[/color][/size] 
操作 SYS_BIND  是由 sys_bind()实现的: 
 
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) 

        struct socket *sock; 
        char address[MAX_SOCK_ADDR]; 
        int err; 
 
        if((sock = sockfd_lookup(fd,&err))!=NULL) 
        { 
                if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0) { 
                        err = security_socket_bind(sock, (struct sockaddr *)address, addrlen); 
                        if (err) { 
                                sockfd_put(sock); 
                                return err; 
                        } 
                        err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen); 
                } 
                sockfd_put(sock); 
        }                         
        return err; 

 
在 socket 的创建中,已经反复分析了 socket 与文件系统的关系,现在已知 socket的描述符号,要找
出与之相关的 socket 结构,应该是件容易的事情: 
 
struct socket *sockfd_lookup(int fd, int *err) 

        struct file *file; 
        struct inode *inode; 
        struct socket *sock; 
         if (!(file = fget(fd))) 
        { 
                *err = -EBADF; 
                return NULL; 
        } 
 
        inode = file->f_dentry->d_inode; 
        if (!S_ISSOCK(inode->i_mode)) { 
                *err = -ENOTSOCK; 
                fput(file); 
                return NULL; 
        } 
 
        sock = SOCKET_I(inode); 
        if (sock->file != file) { 
                printk(KERN_ERR "socki_lookup: socket file changed!/n"); 
                sock->file = file; 
        } 
        return sock; 

 
fget 从当前进程的 files 指针中,根据 sock 对应的描述符号,找到已打开的文件 file,再根据文件的
目录项中的 inode,利用inode 与 sock 被封装在同一个结构中的事实,调用宏 SOCKET_I找到待查
的 sock 结构。最后做一个小小的判断,因为正常情况下,sock 的 file 指针是  回指与之相关的 file。  
 
接下来的工作是把用户态的地址拷贝至内核中来: 
int move_addr_to_kernel(void __user *uaddr, int ulen, void *kaddr) 

        if(ulen<0||ulen>MAX_SOCK_ADDR) 
                return -EINVAL; 
        if(ulen==0) 
                return 0; 
        if(copy_from_user(kaddr,uaddr,ulen)) 
                return -EFAULT; 
        return 0; 

 
bind(2)第三个参数必须存在的原因之一,copy_from_user 必须知道拷贝的字节长度。 
 
因为 sock 的 ops 函数指针集,在创建之初,就指向了对应的协议类型,例如如果类型是
SOCK_STREAM,那么它就指向 inetsw_array[0].ops。也就是 inet_stream_ops: 
struct proto_ops inet_stream_ops = { 
        .family =        PF_INET, 
        ……         .bind =                inet_bind, 
        …… 
}; 
 
sys_bind()在做完了一个通用的 socket bind 应该做的事情,包括查找对应 sock 结构,拷贝地址。就
调用对应协议族的对应协议类型的 bind 函数,也就是 inet_bind。 
 
[size=5][color=Red]3、inet_bind[/color][/size] 
说 bind(2)的最重要的作用,就是为套接字绑定地址和端口,那么要分析 inet_bind()之前,要搞清楚
的一件事情就是,这个绑定,是绑定到哪儿?或者说,是绑定到内核的哪个数据结构的哪个成员变
量上面?? 
有三个地方是可以考虑的:socket 结构,包括 sock 和 sk,inet结构,以及 protoname_sock 结构。绑
定在 socket 结构上是可行  的,这样可以实现最高层面上的抽像,但是因为每一类协议簇 socket 的
地址及端口表现形式差异很大,这样就得引入专门的转换处理功能。绑定在 protoname_sock 也是
可行的,但是却是最笨拙的,因为例如 tcp 和 udp,它们的地址及端口表现形式是一样的,这样就
浪费了空间,加大了代码  处理量。所以,inet 做为一个协议类型的抽像,是最理想的地方了,再来
回顾一下它的定义: 
 
[code]struct inet_sock { 
        …… 
        /* Socket demultiplex comparisons on incoming packets. */ 
        __u32                        daddr;                /* Foreign IPv4 addr */ 
        __u32                        rcv_saddr;        /* Bound local IPv4 addr */ 
        __u16                         dport;                /* Destination port */ 
        __u16                        num;                /* Local port */ 
        __u32                        saddr;                /* Sending source */ 
        …… 
};[/code] 
 
去掉了其它成员,保留了与地址及端口相关的成员变量,从注释中,可以清楚地了解它们的作用。
所以,我们说的 bind(2)之绑定,主要就是对这几个成员变量赋值的过程了。 
 
[code]int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) 

        /*  获取地址参数 */ 
        struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; 
        /*  获取 sock 对应的 sk */ 
        struct sock *sk = sock->sk; 
        /*  获取 sk 对应的inet */ 
        struct inet_sock *inet = inet_sk(sk); 
        /*  这个临时变量用来保存用户态传递下来的端口参数 */ 
        unsigned short snum; 
        int chk_addr_ret; 
        int err;  
        /*  如果协议簇对应的协议自身还有 bind 函数,调用之,例如 SOCK_RAW 就还有一个
raw_bind */ 
        if (sk->sk_prot->bind) { 
                err = sk->sk_prot->bind(sk, uaddr, addr_len); 
                goto out; 
        } 
        /*  校验地址长度 */ 
        err = -EINVAL; 
        if (addr_len < sizeof(struct sockaddr_in)) 
                goto out; 
 
        /*  判断地址类型:广播?多播?单播? */ 
        chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr); 
         
        /* ipv4 有一个 ip_nonlocal_bind标志,表示是否绑定非本地址 IP地址,可以通过  
         * cat /proc/sys/net/ipv4/ip_nonlocal_bind 查看到。它用来解决某些服务绑定 
         *  动态 IP地址的情况。作者在注释中已有详细说明: 
         * Not specified by any standard per-se, however it breaks too 
         * many applications when removed.  It is unfortunate since 
         * allowing applications to make a non-local bind solves 
         * several problems with systems using dynamic addressing. 
         * (ie. your servers still start up even if your ISDN link 
         *  is temporarily down) 
         *  这里判断,用来确认如果没有开启“绑定非本地址 IP”,地址值及类型是正确的 
         */ 
        err = -EADDRNOTAVAIL; 
        if (!sysctl_ip_nonlocal_bind && 
            !inet->freebind && 
            addr->sin_addr.s_addr != INADDR_ANY && 
            chk_addr_ret != RTN_LOCAL && 
            chk_addr_ret != RTN_MULTICAST && 
            chk_addr_ret != RTN_BROADCAST) 
                goto out; 
         
        /*  获取协议端口号 */ 
        snum = ntohs(addr->sin_port); 
        err = -EACCES; 
        /*  校验当前进程有没有使用低于 1024 端口的能力 */ 
        if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE)) 
                goto out; 
 
        /*      We keep a pair of addresses. rcv_saddr is the one 
         *      used by hash lookups, and saddr is used for transmit.          * 
         *      In the BSD API these are the same except where it 
         *      would be illegal to use them (multicast/broadcast) in 
         *      which case the sending device address is used. 
         */ 
        lock_sock(sk); 
 
        /*  检查 socket 是否已经被绑定过了:用了两个检查项,一个是 sk 状态,另一个是是否已经
绑定过端口了 
         当然地址本来就可以为 0,所以,不能做为检查项 */ 
        err = -EINVAL; 
        if (sk->sk_state != TCP_CLOSE || inet->num) 
                goto out_release_sock; 
         
        /*  绑定 inet 的接收地址(地址服务绑定地址)和来源地址为用户态指定地址 */ 
        inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr; 
        /*  若地址类型为广播或多播,则将地址置 0,表示直接使用网络设备 */ 
        if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST) 
                inet->saddr = 0;  /* Use device */ 
 
        /*  
         *  调用协议的 get_port 函数,确认是否可绑定端口,若可以,则绑定在 inet->num 之上,注
意,这里虽然没有 
         *  把inet 传过去,但是第一个参数 sk,它本身和 inet是可以互相转化的 
         */ 
        if (sk->sk_prot->get_port(sk, snum)) { 
                inet->saddr = inet->rcv_saddr = 0; 
                err = -EADDRINUSE; 
                goto out_release_sock; 
        } 
         
        /*  如果端口和地址可以绑定,置标志位 */ 
        if (inet->rcv_saddr) 
                sk->sk_userlocks |= SOCK_BINDADDR_LOCK; 
        if (snum) 
                sk->sk_userlocks |= SOCK_BINDPORT_LOCK; 
        /* inet的 sport(来源端口)成员也置为绑定端口 */ 
        inet->sport = htons(inet->num); 
        inet->daddr = 0; 
        inet->dport = 0; 
        sk_dst_reset(sk); 
        err = 0; 
out_release_sock: 
        release_sock(sk); out: 
        return err; 
}[/code] 
上述分析中,忽略的第一个细节是 capable()函数调用,它是 Linux  安全模块(LSM)的一部份,简单
地讲其用来对权限做出检查, 
检查是否有权对指定的资源进行操作。这里它的参数是 CAP_NET_BIND_SERVICE,表示的含义是:  
[code]/* Allows binding to TCP/UDP sockets below 1024 */ 
/* Allows binding to ATM VCIs below 32 */ 
 
#define CAP_NET_BIND_SERVICE 10[/code] 
 
另一个就是协议的端口绑定,调用了协议的 get_port 函数,如果是 SOCK_STREAM的 TCP  协议,
那么它就是 tcp_v4_get_port()函数。 
 
[size=5][color=Red]4、协议端口的绑定[/color][/size] 
 
要分配这个函数,还是得先绕一些基本的东东。这里涉及到内核中提供 hash 链表的操作的 API。可
以参考其它相关资料。 
[url]http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html[/url] 
这里讲了链表的实现,顺道提了一个 hash 链表,觉得写得还不错,收藏一下。 
 
对于 TCP已注册的端口,是采用一个 hash 表来维护的。hash 桶用 struct tcp_bind_hashbucket 结构来
表示: 
[code]struct tcp_bind_hashbucket { 
        spinlock_t                lock; 
        struct hlist_head        chain; 
};[/code] 
 
hash 表中的每一个 hash节点,用 struct tcp_bind_hashbucket 结构来表示: 
struct tcp_bind_bucket { 
[code]        unsigned short                port;                        /*  节点中绑定的端口 */ 
        signed short                fastreuse; 
        struct hlist_node        node; 
        struct hlist_head        owners; 
};[/code] 
 
tcp_hashinfo 的 hash 表信息,都集中封装在结构 tcp_hashinfo 当中,而维护已注册端口,只是它其
中一部份: 
 
[code]extern struct tcp_hashinfo { 
        …… 
 
        /* Ok, let's try this, I give up, we do need a local binding 
         * TCP hash as well as the others for fast bind/connect.          */ 
        struct tcp_bind_hashbucket *__tcp_bhash; 
 
        int __tcp_bhash_size; 
        …… 
} tcp_hashinfo; 
 
#define tcp_bhash        (tcp_hashinfo.__tcp_bhash) 
#define tcp_bhash_size        (tcp_hashinfo.__tcp_bhash_size)[/code] 
 
其使用的 hash 函数是 tcp_bhashfn: 
[code]/* These are AF independent. */ 
static __inline__ int tcp_bhashfn(__u16 lport) 

        return (lport & (tcp_bhash_size - 1)); 
}[/code] 
 
这样,如果要取得某个端口对应的 hash 链的首部hash 桶节点的话,可以使用: 
[code]struct tcp_bind_hashbucket *head; 
head = &tcp_bhash[tcp_bhashfn(snum)];[/code] 
 
如果要新绑定一个端口,就是先创建一个 struct tcp_bind_hashbucket 结构的 hash 节点,然后把它插
入到对应的 
hash 链中去: 
[code]struct tcp_bind_bucket *tb; 
 
tb = tcp_bucket_create(head, snum); 
 
struct tcp_bind_bucket *tcp_bucket_create(struct tcp_bind_hashbucket *head, 
                                          unsigned short snum) 

        struct tcp_bind_bucket *tb = kmem_cache_alloc(tcp_bucket_cachep, 
                                                      SLAB_ATOMIC); 
        if (tb) { 
                tb->port = snum; 
                tb->fastreuse = 0; 
                INIT_HLIST_HEAD(&tb->owners); 
                hlist_add_head(&tb->node, &head->chain); 
        } 
        return tb; 
}[/code] 
 
另外,sk 中,还级护了一个类似的 hash 链表,同时需要调用 tcp_bind_hash()函数把 hash 节点插入
进去: [code]struct sock { 
        struct sock_common        __sk_common; 
#define sk_bind_node                __sk_common.skc_bind_node 
        …… 
}[/code] 
 
[code]/* @skc_bind_node: bind hash linkage for various protocol lookup tables */ 
struct sock_common { 
        struct hlist_node        skc_bind_node; 
        …… 
}[/code] 
         
[code]if (!tcp_sk(sk)->bind_hash) 
        tcp_bind_hash(sk, tb, snum); 
         
void tcp_bind_hash(struct sock *sk, struct tcp_bind_bucket *tb, 
                   unsigned short snum) 

        inet_sk(sk)->num = snum; 
        sk_add_bind_node(sk, &tb->owners); 
        tcp_sk(sk)->bind_hash = tb; 
}[/code] 
 
这里,就顺道绑定了 inet 的 num成员变量,并置协议的 bind_hash 指针为当前分配的 hash 节点。而
sk_add_bind_node 函数, 
就是一个插入 hash 表节点的过程: 
 
[code]static __inline__ void sk_add_bind_node(struct sock *sk, 
                                        struct hlist_head *list) 

        hlist_add_head(&sk->sk_bind_node, list); 
}[/code] 
 
如果要遍历 hash 表的话,例如在插入之前,先判断端口是否已经在 hash表当中了。就可以调用: 
 
[code]#define tb_for_each(tb, node, head) hlist_for_each_entry(tb, node, head, node) 
 
struct tcp_bind_hashbucket *head; 
struct tcp_bind_bucket *tb; 
 
head = &tcp_bhash[tcp_bhashfn(snum)]; 
spin_lock(&head->lock); 
tb_for_each(tb, node, &head->chain) 
        if (tb->port == snum)                 found,do_something;[/code]                 
有了这些基础知识,再来看 tcp_v4_get_port()的实现,就要容易得多了: 
 
[code]static int tcp_v4_get_port(struct sock *sk, unsigned short snum) 

        struct tcp_bind_hashbucket *head; 
        struct hlist_node *node; 
        struct tcp_bind_bucket *tb; 
        int ret; 
 
        local_bh_disable(); 
         
        /*  如果端口值为 0,意味着让系统从本地可用端口用选择一个,并置 snum为分配的值 */ 
        if (!snum) { 
                int low = sysctl_local_port_range[0]; 
                int high = sysctl_local_port_range[1]; 
                int remaining = (high - low) + 1; 
                int rover; 
 
                spin_lock(&tcp_portalloc_lock); 
                if (tcp_port_rover < low) 
                        rover = low; 
                else 
                        rover = tcp_port_rover; 
                do { 
                        rover++; 
                        if (rover > high) 
                                rover = low; 
                         head = &tcp_bhash[tcp_bhashfn(rover)]; 
                        spin_lock(&head->lock); 
                        tb_for_each(tb, node, &head->chain) 
                                if (tb->port == rover) 
                                        goto next; 
                        break; 
                next: 
                        spin_unlock(&head->lock); 
                } while (--remaining > 0); 
                tcp_port_rover = rover; 
                spin_unlock(&tcp_portalloc_lock); 
 
                /* Exhausted local port range during search? */ 
                ret = 1; 
                if (remaining <= 0) 
                        goto fail;  
                /* OK, here is the one we will use.  HEAD is 
                 * non-NULL and we hold it's mutex. 
                 */ 
                snum = rover; 
        } else { 
                /*  否则,就在 hash 表中,查找端口是否已经存在 */ 
                head = &tcp_bhash[tcp_bhashfn(snum)]; 
                spin_lock(&head->lock); 
                tb_for_each(tb, node, &head->chain) 
                        if (tb->port == snum) 
                                goto tb_found; 
        } 
        tb = NULL; 
        goto tb_not_found; 
tb_found: 
        /*  稍后有对应的代码:第一次分配 tb 后,会调用 tcp_bind_hash加入至相应的 sk,这里先做
一个判断,来确定这一步工作是否进行过*/ 
        if (!hlist_empty(&tb->owners)) { 
/* socket的 SO_REUSEADDR  选项,用来确定是否允许本地地址重用,例如同时启动多个服务器、
多个套接字绑定至同一端口等等, sk_reuse 成员对应其值,因为如果一个绑定的 hash节点已经存在,
而且不允许重用的话,那么则表示因冲突导致出错,调用 tcp_bind_conflict 来处理之 */ 
                if (sk->sk_reuse > 1) 
                        goto success; 
                if (tb->fastreuse > 0 && 
                    sk->sk_reuse && sk->sk_state != TCP_LISTEN) { 
                        goto success; 
                } else { 
                        ret = 1; 
                        if (tcp_bind_conflict(sk, tb)) 
                                goto fail_unlock; 
                } 
        } 
tb_not_found: 
        /*  如果不存在,则分配 hash节点,绑定端口 */ 
        ret = 1; 
        if (!tb && (tb = tcp_bucket_create(head, snum)) == NULL) 
                goto fail_unlock; 
        if (hlist_empty(&tb->owners)) { 
                if (sk->sk_reuse && sk->sk_state != TCP_LISTEN) 
                        tb->fastreuse = 1; 
                else 
                        tb->fastreuse = 0; 
        } else if (tb->fastreuse &&                    (!sk->sk_reuse || sk->sk_state == TCP_LISTEN)) 
                tb->fastreuse = 0; 
success: 
        if (!tcp_sk(sk)->bind_hash) 
                tcp_bind_hash(sk, tb, snum); 
        BUG_TRAP(tcp_sk(sk)->bind_hash == tb); 
        ret = 0; 
 
fail_unlock: 
        spin_unlock(&head->lock); 
fail: 
        local_bh_enable(); 
        return ret; 
}[/code] 
 
到这里,可以为这部份下一个小结了,所谓绑定,就是: 
1、设置内核中 inet 相关变量成员的值,以待后用; 
2、协议中,如 TCP协议,记录绑定的协议端口的信息,采用 hash 链表存储,sk 中也同时维护了这
么一个链表。两者的区别应该是 
前者给协议用。后者给 socket 用。 
 
 
 
 
 
Linux TCP/IP 协议栈之 Scoket实现分析(三 socket的 Listen) 
 
[size=5][color=Red]一、sys_listen[/color][/size] 
 
对面向连接的协议,在调用 bind(2)后,进一步调用 listen(2),让套接字进入监听状态: 
[code]int listen(int sockfd, int backlog);[/code] 
 
backlog 表示新建连接请求时,最大的未处理的积压请求数。 
 
这里说到让套接字进入某种状态,也就是说,涉及到套接字的状态变迁,前面 create 和bind 时,也
遇到过相应的代码。 
 
sock和 sk 都有相应的状态字段,先来看 sock 的: 
[code]typedef enum { 
        SS_FREE = 0,                        /*  套接字未分配                */ 
        SS_UNCONNECTED,                        /*  套接字未连接        */ 
        SS_CONNECTING,                        /*  套接字正在处理连接        */ 
        SS_CONNECTED,                        /*  套接字已连接                */ 
        SS_DISCONNECTING                /*  套接字正在处理关闭连接 */ } socket_state;[/code] 
 
在创建套接字时,被初始化为 SS_UNCONNECTED。 
 
对于面向连接模式的SOCK_TREAM来讲,这样描述状态显然是不够的。 这样, 在sk中, 使用sk_state
维护了一个有限状态机来描述套接字的状态: 
[code]enum { 
  TCP_ESTABLISHED = 1, 
  TCP_SYN_SENT, 
  TCP_SYN_RECV, 
  TCP_FIN_WAIT1, 
  TCP_FIN_WAIT2, 
  TCP_TIME_WAIT, 
  TCP_CLOSE, 
  TCP_CLOSE_WAIT, 
  TCP_LAST_ACK, 
  TCP_LISTEN, 
  TCP_CLOSING,         /* now a valid state */ 
 
  TCP_MAX_STATES /* Leave at the end! */ 
};[/code] 
 
还有一个相应的用来进行状态位运算的枚举结构: 
[code]enum { 
  TCPF_ESTABLISHED = (1 << 1), 
  TCPF_SYN_SENT  = (1 << 2), 
  TCPF_SYN_RECV  = (1 << 3), 
  TCPF_FIN_WAIT1 = (1 << 4), 
  TCPF_FIN_WAIT2 = (1 << 5), 
  TCPF_TIME_WAIT = (1 << 6), 
  TCPF_CLOSE     = (1 << 7), 
  TCPF_CLOSE_WAIT = (1 << 8), 
  TCPF_LAST_ACK  = (1 << 9), 
  TCPF_LISTEN    = (1 << 10), 
  TCPF_CLOSING   = (1 << 11)  
};[/code] 
 
值得一提的是,sk 的状态不等于 TCP的状态,虽然 sk 是面向协议栈,但它的状态并不能同 TCP状
态一一直接划等号。虽然这些状态值都用 TCP-XXX 来表式,但是只是因为 TCP协议状态非常复杂。
sk 结构只是利用它的一个子集来抽像描述而已。 
 
同样地,操作码 SYS_LISTEN的任务会落到 sys_listen()函数身上: 
[code] 
/* Maximum queue length specifiable by listen.  */ #define SOMAXCONN        128 
int sysctl_somaxconn = SOMAXCONN; 
 
asmlinkage long sys_listen(int fd, int backlog) 

        struct socket *sock; 
        int err; 
         
        if ((sock = sockfd_lookup(fd, &err)) != NULL) { 
                if ((unsigned) backlog > sysctl_somaxconn) 
                        backlog = sysctl_somaxconn; 
 
                err = security_socket_listen(sock, backlog); 
                if (err) { 
                        sockfd_put(sock); 
                        return err; 
                } 
 
                err=sock->ops->listen(sock, backlog); 
                sockfd_put(sock); 
        } 
        return err; 
}[/code] 
 
同样地,函数会最终转向协议簇的 listen 函数,也就是 inet_listen(): 
 
[code]/* 
*        Move a socket into listening state. 
*/ 
int inet_listen(struct socket *sock, int backlog) 

        struct sock *sk = sock->sk; 
        unsigned char old_state; 
        int err; 
 
        lock_sock(sk); 
 
        err = -EINVAL; 
        /* 在 listen 之前,sock 必须为未连接状态,且只有 SOCK_STREAM 类型,才有 listen(2)*/ 
        if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM) 
                goto out; 
 
        /*  临时保存状态机状态 */ 
        old_state = sk->sk_state;         /*  只有状态机处于 TCP_CLOSE  或者是 TCP_LISTEN  这两种状态时,才可能对其调用
listen(2)  ,这个判断证明了 listen(2)是可以重复调用地(当然是在转向 TCP_LISTEN 后没有再进行
状态变迁*/ 
        if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN))) 
                goto out; 
 
        /*  如果接口已经处理 listen 状态,只修改其 max_backlog,否则先调用 tcp_listen_start,继续
设置协议的 listen 状态 
         */ 
        if (old_state != TCP_LISTEN) { 
                err = tcp_listen_start(sk); 
                if (err) 
                        goto out; 
        } 
        sk->sk_max_ack_backlog = backlog; 
        err = 0; 
 
out: 
        release_sock(sk); 
        return err; 
}[/code] 
 
inet_listen 函数在确认 sock->state 和 sk->sk_state 状态后,会进一步调用 tcp_listen_start 函数,最且
最后设置 sk_max_ack_backlog  。 
 
tcp 的 tcp_listen_start 函数,完成两个重要的功能,一个是初始化 sk 的一些相关成员变量,另一方
面是切换有限状态机的状态。 sk_max_ack_backlog表示监听时最大的 backlog 数量,它由用户空间
传递的参数决定。而 sk_ack_backlog表示当前的的 backlog数量。 
 
当 tcp 服务器收到一个 syn 报文时,它表示了一个连接请求的到达。内核使用了一个 hash 表来维护
这个连接请求表: 
struct tcp_listen_opt 

        u8                        max_qlen_log;        /* log_2 of maximal queued SYNs */ 
        int                        qlen; 
        int                        qlen_young; 
        int                        clock_hand; 
        u32                        hash_rnd; 
        struct open_request        *syn_table[TCP_SYNQ_HSIZE]; 
}; 
 
syn_table,  是open_request结构,就是连接请求表,连中的最大项,也就是最大允许的 syn 报文的数
量,由 max_qlen_log 来决定。当套接字进入 listen 状态,也就是说可以接收 syn 报文了,那么在此
之前,需要先初始化这个表:  
[code] 
int tcp_listen_start(struct sock *sk) 

        struct inet_sock *inet = inet_sk(sk);                //获取 inet结构指针 
        struct tcp_sock *tp = tcp_sk(sk);                //获取协议指针 
        struct tcp_listen_opt *lopt; 
         
        //初始化 sk 相关成员变量 
        sk->sk_max_ack_backlog = 0; 
        sk->sk_ack_backlog = 0; 
         
        tp->accept_queue = tp->accept_queue_tail = NULL; 
        rwlock_init(&tp->syn_wait_lock); 
        tcp_delack_init(tp); 
         
        //初始化连接请求 hash 表 
        lopt = kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL); 
        if (!lopt) 
                return -ENOMEM; 
 
        memset(lopt, 0, sizeof(struct tcp_listen_opt)); 
        //初始化 hash 表容量,最小为 6,其实际值由 sysctl_max_syn_backlog 决定 
        for (lopt->max_qlen_log = 6; ; lopt->max_qlen_log++) 
                if ((1 << lopt->max_qlen_log) >= sysctl_max_syn_backlog) 
                        break; 
        get_random_bytes(&lopt->hash_rnd, 4); 
 
        write_lock_bh(&tp->syn_wait_lock); 
        tp->listen_opt = lopt; 
        write_unlock_bh(&tp->syn_wait_lock); 
 
        /* There is race window here: we announce ourselves listening, 
         * but this transition is still not validated by get_port(). 
         * It is OK, because this socket enters to hash table only 
         * after validation is complete. 
         */ 
         /*  修改状态机状态,表示进入 listen 状态,根据作者注释,当宣告自己进入 listening 状态后,
但是这个状态转换并没有得到 get_port 的确  认。所以需要调用 get_port()函数。但是对于一点,暂
时还没有完全搞明白,只有留待后面再来分析它 */ 
        sk->sk_state = TCP_LISTEN; 
        if (!sk->sk_prot->get_port(sk, inet->num)) { 
                inet->sport = htons(inet->num); 
                 sk_dst_reset(sk); 
                sk->sk_prot->hash(sk); 
 
                return 0; 
        } 
 
        sk->sk_state = TCP_CLOSE; 
        write_lock_bh(&tp->syn_wait_lock); 
        tp->listen_opt = NULL; 
        write_unlock_bh(&tp->syn_wait_lock); 
        kfree(lopt); 
        return -EADDRINUSE; 
}[/code] 
 
在切换了有限状态机状态后,调用了 
[code]sk->sk_prot->hash(sk);[/code] 
也就是 tcp_v4_hash()函数。这里涉到到另一个 hash 表:TCP监听 hash 表。 
 
[size=5][color=Red]二、TCP监听 hash表[/color][/size] 
所谓 TCP 监听表,指的就内核维护“当前有哪些套接字在监听”的一个表,当一个数据包进入 TCP
栈的时候,内核查询这个表中对应的 sk,以找到相应的数据  结构。 (因为 sk 是面向网络栈调用的,
找到了 sk,就找到了 tcp_sock,就找到了 inet_sock,就找到了 sock,就找到了 fd……就到了组  织
了)。 
 
TCP所有的 hash 表都用了tcp_hashinfo来封装,前面分析 bind已见过它: 
[code]extern struct tcp_hashinfo { 
                 …… 
        /* All sockets in TCP_LISTEN state will be in here.  This is the only 
         * table where wildcard'd TCP sockets can exist.  Hash function here 
         * is just local port number. 
         */ 
        struct hlist_head __tcp_listening_hash[TCP_LHTABLE_SIZE]; 
 
                 …… 
        spinlock_t __tcp_portalloc_lock; 
} tcp_hashinfo; 
 
#define tcp_listening_hash (tcp_hashinfo.__tcp_listening_hash)[/code] 
 
函数 tcp_v4_hash 将一个处理监听状态下的 sk 加入至这个 hash 表: 
[code]static void tcp_v4_hash(struct sock *sk) 

        if (sk->sk_state != TCP_CLOSE) { 
                local_bh_disable();                 __tcp_v4_hash(sk, 1); 
                local_bh_enable(); 
        } 
}[/code] 
 
因为__tcp_v4_hash 不只用于监听 hash 表,它也用于其它 hash 表,其第二个参数 listen_possible 为
真的时候,表示处理的是监听 hash表: 
[code]static __inline__ void __tcp_v4_hash(struct sock *sk, const int listen_possible) 

        struct hlist_head *list; 
        rwlock_t *lock; 
 
        BUG_TRAP(sk_unhashed(sk)); 
        if (listen_possible && sk->sk_state == TCP_LISTEN) { 
                list = &tcp_listening_hash[tcp_sk_listen_hashfn(sk)]; 
                lock = &tcp_lhash_lock; 
                tcp_listen_wlock(); 
        } else { 
                                …… 
        } 
        __sk_add_node(sk, list); 
        sock_prot_inc_use(sk->sk_prot); 
        write_unlock(lock); 
        if (listen_possible && sk->sk_state == TCP_LISTEN) 
                wake_up(&tcp_lhash_wait); 
}[/code] 
else 中的部份用于另一个 hash 表,暂时不管它。代表很简单,如果确认是处理的是监听 hash 表。
则先根据 sk计算一个 hash 值,在hash 桶中找到入口。再调用__sk_add_node 加入至该 hash 链。 
 
tcp_sk_listen_hashfn()函数事实上是 tcp_lhashfn 的包裹,前面已经分析过了。 
 
__sk_add_node()函数也就是一个简单的内核 hash处理函数 hlist_add_head()的包裹: 
[code]static __inline__ void __sk_add_node(struct sock *sk, struct hlist_head *list) 

        hlist_add_head(&sk->sk_node, list); 
}[/code] 
 
小结 
 
一个套接字的 listen,主要需要做的工作有以下几件: 
1、初始化 sk 相关的成员变量,最重要的是 listen_opt,也就是连接请求 hash 表。 
2、将 sk 的有限状态机转换为 TCP_LISTEN,即监听状态; 
3、将 sk 加入监听 hash表; 
4、设置允许的最大请求积压数,也就是 sk 的成员 sk_max_ack_backlog 的值。  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Linux TCP/IP 协议栈之 Socket的实现分析(四 Accept 接受一个连接) 
 
[color=Red][size=5]一、tcp 栈的三次握手简述[/size][/color] 
 
进一步的分析,都是以 tcp 协议为例,因为 udp要相对简单得多,分析完 tcp,udp的基本已经被覆
盖了。 
 
这里主要是分析 socket,但是因为它将与 tcp/udp传输层交互,所以不可避免地接触到这一层面的代
码,这里只是摘取其主要流程的一些代码片段,以更好地分析 accept的实现过程。 
 
当套接字进入 LISTEN后,意味着服务器端已经可以接收来自客户端的请求。当一个 syn 包到达后,
服务器认为它是一个 tcp  请求报文,根据 tcp 
协议,TCP 网络栈将会自动应答它一个 syn+ack 报文,并且将它放入 syn_table 这个 hash 表中,静
静地等待客户端第三次握手报文的来到。一个 tcp 的 syn 报文进入 tcp 堆栈后,会按以下函数调用,
最终进入 tcp_v4_conn_request: 
 
[code]tcp_v4_rcv 
        ->tcp_v4_do_rcv 
                ->tcp_rcv_state_process 
                        ->tp->af_specific->conn_request[/code] 
 tcp_ipv4.c 中,tcp_v4_init_sock 初始化时,有 
 
tp->af_specific = &ipv4_specific; 
 
[code]struct tcp_func ipv4_specific = { 
        .queue_xmit        =        ip_queue_xmit, 
        .send_check        =        tcp_v4_send_check, 
        .rebuild_header        =        tcp_v4_rebuild_header, 
        .conn_request        =        tcp_v4_conn_request, 
        .syn_recv_sock        =        tcp_v4_syn_recv_sock, 
        .remember_stamp        =        tcp_v4_remember_stamp, 
        .net_header_len        =        sizeof(struct iphdr), 
        .setsockopt        =        ip_setsockopt, 
        .getsockopt        =        ip_getsockopt, 
        .addr2sockaddr        =        v4_addr2sockaddr, 
        .sockaddr_len        =        sizeof(struct sockaddr_in), 
};[/code] 
 
所以 af_specific->conn_request实际指向的是 tcp_v4_conn_request: 
 
[code]int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb) 

        struct open_request *req; 
        …… 
        /*  分配一个连接请求 */ 
        req = tcp_openreq_alloc(); 
        if (!req) 
                goto drop; 
        ……         
        /*  根据数据包的实际要素,如来源/目的地址等,初始化它*/ 
        tcp_openreq_init(req, &tmp_opt, skb); 
 
        req->af.v4_req.loc_addr = daddr; 
        req->af.v4_req.rmt_addr = saddr; 
        req->af.v4_req.opt = tcp_v4_save_options(sk, skb); 
        req->class = &or_ipv4;                 
        …… 
        /*  回送一个 syn+ack 的二次握手报文 */ 
        if (tcp_v4_send_synack(sk, req, dst)) 
                goto drop_and_free; 
 
        if (want_cookie) { 
                …… 
        } else {                 /*  将连接请求 req 加入连接监听表 syn_table */ 
                tcp_v4_synq_add(sk, req); 
        } 
        return 0;         
}[/code] 
 
syn_table 在前面分析的时候已经反复看到了。它的作用就是记录 syn 请求报文,构建一个 hash 表。
这里调用的 tcp_v4_synq_add()就完成了将请求添加进该表的操作: 
[code] 
static void tcp_v4_synq_add(struct sock *sk, struct open_request *req) 

        struct tcp_sock *tp = tcp_sk(sk); 
        struct tcp_listen_opt *lopt = tp->listen_opt; 
        /*  计算一个 hash值 */ 
        u32 h = tcp_v4_synq_hash(req->af.v4_req.rmt_addr, req->rmt_port, lopt->hash_rnd); 
 
        req->expires = jiffies + TCP_TIMEOUT_INIT; 
        req->retrans = 0; 
        req->sk = NULL; 
        /*指针移到 hash 链的未尾*/ 
        req->dl_next = lopt->syn_table[h]; 
 
        write_lock(&tp->syn_wait_lock); 
        /*加入当前节点*/ 
        lopt->syn_table[h] = req; 
        write_unlock(&tp->syn_wait_lock); 
 
        tcp_synq_added(sk); 
}[/code] 
 
这样,所以的 syn 请求都被放入这个表中,留待第三次 ack 的到来的匹配。当第三次 ack 来到后,
会进入下列函数: 
[code]tcp_v4_rcv 
        ->tcp_v4_do_rcv[/code] 
 
[code]int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) 

        …… 
         
        if (sk->sk_state == TCP_LISTEN) { 
                struct sock *nsk = tcp_v4_hnd_req(sk, skb); 
        …… 
}[/code] 
 因为目前 sk还是 TCP_LISTEN状态,所以会进入 tcp_v4_hnd_req: 
[code]static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb) 

        struct tcphdr *th = skb->h.th; 
        struct iphdr *iph = skb->nh.iph; 
        struct tcp_sock *tp = tcp_sk(sk); 
        struct sock *nsk; 
        struct open_request **prev; 
        /* Find possible connection requests. */ 
        struct open_request *req = tcp_v4_search_req(tp, &prev, th->source, 
                                                     iph->saddr, iph->daddr); 
        if (req) 
                return tcp_check_req(sk, skb, req, prev); 
        …… 
}[/code] 
 
tcp_v4_search_req 就是查找匹配 syn_table 表: 
[code]static struct open_request *tcp_v4_search_req(struct tcp_sock *tp, 
                                              struct open_request ***prevp, 
                                              __u16 rport, 
                                              __u32 raddr, __u32 laddr) 

        struct tcp_listen_opt *lopt = tp->listen_opt; 
        struct open_request *req, **prev; 
 
        for (prev = &lopt->syn_table[tcp_v4_synq_hash(raddr, rport, lopt->hash_rnd)]; 
             (req = *prev) != NULL; 
             prev = &req->dl_next) { 
                if (req->rmt_port == rport && 
                    req->af.v4_req.rmt_addr == raddr && 
                    req->af.v4_req.loc_addr == laddr && 
                    TCP_INET_FAMILY(req->class->family)) { 
                        BUG_TRAP(!req->sk); 
                        *prevp = prev; 
                        break; 
                } 
        } 
 
        return req; 
}[/code] 
 
hash 表的查找还是比较简单的,调用 tcp_v4_synq_hash 计算出 hash 值,找到 hash 链入口,遍历该
链即可。 
 排除超时等意外因素,刚才加入 hash 表的 req 会被找到,这样,tcp_check_req()函数将会被继续调
用: 
[code]struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb, 
                           struct open_request *req, 
                           struct open_request **prev) 

        …… 
        tcp_acceptq_queue(sk, req, child); 
        …… 
}[/code] 
 
req 被找到,表明三次握手已经完成,连接已经成功建立,tcp_check_req 最终将调用
tcp_acceptq_queue(),把这个建立好的连接加入至 tp->accept_queue 队列,等待用户调用 accept(2)来
读取之。 
 
[code]static inline void tcp_acceptq_queue(struct sock *sk, struct open_request *req, 
                                         struct sock *child) 

        struct tcp_sock *tp = tcp_sk(sk); 
 
        req->sk = child; 
        sk_acceptq_added(sk); 
 
        if (!tp->accept_queue_tail) { 
                tp->accept_queue = req; 
        } else { 
                tp->accept_queue_tail->dl_next = req; 
        } 
        tp->accept_queue_tail = req; 
        req->dl_next = NULL; 
}[/code] 
 
[size=5][color=Red]二、sys_accept[/color][/size] 
如上,当 listen(2)调用准备就绪的时候,服务器可以通过调用 accept(2)接受或等待(注意这个“或等
待”是相当的重要)连接队列中的第一个请求: 
[code]int accept(int s, struct sockaddr * addr ,socklen_t *addrlen);[/code] 
 
accept(2)调用,只是针对有连接模式。socket 一旦经过 listen(2)调用进入监听状态后,就被动地调用
accept(2),接受来自客  户端的连接请求。accept(2)调用是阻塞的,也就是说如果没有连接请求到达,
它会去睡觉,等到连接请求到来后(或者是超时),才会返回。同样地,操  作码 SYS_ACCEPT 对
应的是函数 sys_accept: 
 
[code]asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user 
*upeer_addrlen) { 
        struct socket *sock, *newsock; 
        int err, len; 
        char address[MAX_SOCK_ADDR]; 
 
        sock = sockfd_lookup(fd, &err); 
        if (!sock) 
                goto out; 
 
        err = -ENFILE; 
        if (!(newsock = sock_alloc()))  
                goto out_put; 
 
        newsock->type = sock->type; 
        newsock->ops = sock->ops; 
 
        err = security_socket_accept(sock, newsock); 
        if (err) 
                goto out_release; 
 
        /* 
         * We don't need try_module_get here, as the listening socket (sock) 
         * has the protocol module (sock->ops->owner) held. 
         */ 
        __module_get(newsock->ops->owner); 
 
        err = sock->ops->accept(sock, newsock, sock->file->f_flags); 
        if (err < 0) 
                goto out_release; 
 
        if (upeer_sockaddr) { 
                if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) { 
                        err = -ECONNABORTED; 
                        goto out_release; 
                } 
                err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen); 
                if (err < 0) 
                        goto out_release; 
        } 
 
        /* File flags are not inherited via accept() unlike another OSes. */ 
 
        if ((err = sock_map_fd(newsock)) < 0) 
                goto out_release;  
        security_socket_post_accept(sock, newsock); 
 
out_put: 
        sockfd_put(sock); 
out: 
        return err; 
out_release: 
        sock_release(newsock); 
        goto out_put; 
}[/code] 
 
代码稍长了点,逐步来分析它。 
 
一个 socket,经过 listen(2)设置成 server 套接字后,就永远不会再与任何客户端套接字建立连接了。
因为一旦它接受了一个连接请求,就会  创建出一个新的 socket,新的 socket 用来描述新到达的连
接,而原先的 server套接字并无改变,并且还可以通过下一次 accept(2)调用  再创建一个新的出来,
就像母鸡下蛋一样,“只取蛋,不杀鸡”,server 套接字永远保持接受新的连接请求的能力。 
 
函数先通过 sockfd_lookup(),根据 fd,找到对应的 sock,然后通过 sock_alloc分配一个新的 sock。
接着就调用协议簇的 accept()函数: 
[code]/* 
*        Accept a pending connection. The TCP layer now gives BSD semantics. 
*/ 
 
int inet_accept(struct socket *sock, struct socket *newsock, int flags) 

        struct sock *sk1 = sock->sk; 
        int err = -EINVAL; 
        struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err); 
 
        if (!sk2) 
                goto do_err; 
 
        lock_sock(sk2); 
 
        BUG_TRAP((1 << sk2->sk_state) & 
                 (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)); 
 
        sock_graft(sk2, newsock); 
 
        newsock->state = SS_CONNECTED; 
        err = 0; 
        release_sock(sk2); do_err: 
        return err; 
}[/code] 
 
函数第一步工作是调用协议的 accept 函数,然后调用 sock_graft()函数,接下来,设置新的套接字的
状态为 SS_CONNECTED。 
 
[code]/* 
*        This will accept the next outstanding connection. 
*/ 
 
struct sock *tcp_accept(struct sock *sk, int flags, int *err) 

        struct tcp_sock *tp = tcp_sk(sk); 
        struct open_request *req; 
        struct sock *newsk; 
        int error; 
 
        lock_sock(sk); 
 
        /* We need to make sure that this socket is listening, 
         * and that it has something pending. 
         */ 
        error = -EINVAL; 
        if (sk->sk_state != TCP_LISTEN) 
                goto out; 
 
        /* Find already established connection */ 
        if (!tp->accept_queue) { 
                long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); 
 
                /* If this is a non blocking socket don't sleep */ 
                error = -EAGAIN; 
                if (!timeo) 
                        goto out; 
 
                error = wait_for_connect(sk, timeo); 
                if (error) 
                        goto out; 
        } 
 
        req = tp->accept_queue; 
        if ((tp->accept_queue = req->dl_next) == NULL) 
                tp->accept_queue_tail = NULL;  
        newsk = req->sk; 
        sk_acceptq_removed(sk); 
        tcp_openreq_fastfree(req); 
        BUG_TRAP(newsk->sk_state != TCP_SYN_RECV); 
        release_sock(sk); 
        return newsk; 
 
out: 
        release_sock(sk); 
        *err = error; 
        return NULL; 
}[/code] 
 
tcp_accept()函数,当发现 tp->accept_queue 准备就绪后,就直接调用 
[code]        req = tp->accept_queue; 
        if ((tp->accept_queue = req->dl_next) == NULL) 
                tp->accept_queue_tail = NULL; 
 
        newsk = req->sk;[/code] 
出队,并取得相应的 sk。 
否则,就在获取超时时间后,调用 wait_for_connect 等待连接的到来。这也是说,强调“或等待”的
原因所在了。 
 
OK,继续回到 inet_accept 中来,当取得一个就绪的连接的 sk(sk2)后,先校验其状态,再调用
sock_graft()函数。 
 
在 sys_accept 中,已经调用了 sock_alloc,分配了一个新的 socket 结构(即 newsock),但 sock_alloc
必竟不是 sock_create,它并不能为 newsock 分配一个对应的 sk。所以这个套接字并不完整。 
另一方面,当一个连接到达到,根据客户端的请求,产生了一个新的 sk(即 sk2,但这个分配过程
没有深入 tcp 栈去分析其实现,只分析了它对应的 req 入队的代码)。呵呵,将两者一关联,就 OK
了,这就是 sock_graft 的任务: 
[code]static inline void sock_graft(struct sock *sk, struct socket *parent) 

        write_lock_bh(&sk->sk_callback_lock); 
        sk->sk_sleep = &parent->wait; 
        parent->sk = sk; 
        sk->sk_socket = parent; 
        write_unlock_bh(&sk->sk_callback_lock); 
}[/code] 
这样,一对一的联系就建立起来了。这个为 accept 分配的新的 socket 也大功告成了。接下来将其状
态切换为 SS_CONNECTED,表示已连接就绪,可以来读取数据了——如果有的话。 
 
顺便提一下,新的 sk 的分配,是在: ]tcp_v4_rcv 
        ->tcp_v4_do_rcv 
                     ->tcp_check_req 
                              ->tp->af_specific->syn_recv_sock(sk, skb, req, NULL); 
即 tcp_v4_syn_recv_sock函数,其又调用 tcp_create_openreq_child()来分配的。 
[code]struct sock *tcp_create_openreq_child(struct sock *sk, struct open_request *req, struct sk_buff *skb) 

        /* allocate the newsk from the same slab of the master sock, 
         * if not, at sk_free time we'll try to free it from the wrong 
         * slabcache (i.e. is it TCPv4 or v6?), this is handled thru sk->sk_prot -acme */ 
        struct sock *newsk = sk_alloc(PF_INET, GFP_ATOMIC, sk->sk_prot, 0); 
 
        if(newsk != NULL) { 
                          …… 
                         memcpy(newsk, sk, sizeof(struct tcp_sock)); 
                         newsk->sk_state = TCP_SYN_RECV; 
                          …… 
}[/code] 
等到分析 tcp 栈的实现的时候,再来仔细分析它。但是这里新的 sk 的有限状态机被切换至了 
TCP_SYN_RECV(按我的想法,似乎应进入 establshed 才对呀,是不是哪儿看漏了,只有看了后头
的代码再来印证了) 
 
回到 sys_accept 中来,如果调用者要求返回各户端的地址,则调用新的 sk 的getname 函数指针,也
就是 inet_getname: 
[code]/* 
*        This does both peername and sockname. 
*/ 
int inet_getname(struct socket *sock, struct sockaddr *uaddr, 
                        int *uaddr_len, int peer) 

        struct sock *sk                = sock->sk; 
        struct inet_sock *inet        = inet_sk(sk); 
        struct sockaddr_in *sin        = (struct sockaddr_in *)uaddr; 
 
        sin->sin_family = AF_INET; 
        if (peer) { 
                if (!inet->dport || 
                    (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_SYN_SENT)) && 
                     peer == 1)) 
                        return -ENOTCONN; 
                sin->sin_port = inet->dport; 
                sin->sin_addr.s_addr = inet->daddr; 
        } else { 
                __u32 addr = inet->rcv_saddr;                 if (!addr) 
                        addr = inet->saddr; 
                sin->sin_port = inet->sport; 
                sin->sin_addr.s_addr = addr; 
        } 
        memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); 
        *uaddr_len = sizeof(*sin); 
        return 0; 
}[/code] 
 
函数的工作是构建珍上 struct sockaddr_in  结构出来,接着在 sys_accept中,调用 move_addr_to_user()
函数来拷贝至用户空间: 
[code]int move_addr_to_user(void *kaddr, int klen, void __user *uaddr, int __user *ulen) 

        int err; 
        int len; 
 
        if((err=get_user(len, ulen))) 
                return err; 
        if(len>klen) 
                len=klen; 
        if(len<0 || len> MAX_SOCK_ADDR) 
                return -EINVAL; 
        if(len) 
        { 
                 if(copy_to_user(uaddr,kaddr,len)) 
                        return -EFAULT; 
        } 
        /* 
         *        "fromlen shall refer to the value before truncation.." 
         *                        1003.1g 
         */ 
        return __put_user(klen, ulen); 
}[/code] 
也就是调用 copy_to_user的过程了。 
 
sys_accept 的最后一步工作,是将新的 socket 结构,与文件系统挂钩: 
[code]        if ((err = sock_map_fd(newsock)) < 0) 
                goto out_release;[/code] 
 
函数 sock_map_fd 在创建 socket 中已经见过了。 
 
小结: 
accept 有几件事情要做 1、要 accept,需要三次握手完成,连接请求入 tp->accept_queue 队列(新为客户端分析的 sk,也在
其中),其才能出队; 
2、为 accept分配一个 sokcet 结构,并将其与新的 sk 关联; 
3、如果调用时,需要获取客户端地址,即第二个参数不为 NULL,则从新的 sk 中,取得其想的葫
芦; 
4、将新的 socket 结构与文件系统挂钩; 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Linux TCP/IP 协议栈之 Socket的实现分析(五 Connect客户端发起连接请求) 
 
[color=Red][size=5]1、sys_connect[/size][/color] 
 
对于客户端来说,当创建了一个套接字后,就可以连接它了。 
[code]                case SYS_CONNECT: 
                        err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]); 
                        break;[/code] 
 
[code]asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen) 

        struct socket *sock; 
        char address[MAX_SOCK_ADDR]; 
        int err; 
 
        sock = sockfd_lookup(fd, &err); 
        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: 
        sockfd_put(sock); 
out: 
        return err; 
}[/code] 
 
跟其它操作类似,sys_connect 接着调用 inet_connect: 
 
[code]/* 
*        Connect to a remote host. There is regrettably still a little 
*        TCP 'magic' in here. 
*/ 
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, 
                        int addr_len, int flags) 

        struct sock *sk = sock->sk; 
        int err; 
        long timeo; 
 
        lock_sock(sk); 
 
        if (uaddr->sa_family == AF_UNSPEC) { 
                err = sk->sk_prot->disconnect(sk, flags); 
                sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED; 
                goto out; 
        }[/code] 
 
提交的协议簇不正确,则断开连接。 
 
[code]        switch (sock->state) { 
        default: 
                err = -EINVAL; 
                goto out; 
        case SS_CONNECTED: 
                err = -EISCONN; 
                goto out; 
        case SS_CONNECTING: 
                err = -EALREADY; 
                /* Fall out of switch with err, set for this state */ 
                break;[/code] socket 处于不正确的连接状态,返回相应的错误值。 
 
[code]        case SS_UNCONNECTED: 
                err = -EISCONN; 
                if (sk->sk_state != TCP_CLOSE) 
                        goto out; 
                /*调用协议的连接函数*/ 
                err = sk->sk_prot->connect(sk, uaddr, addr_len); 
                if (err < 0) 
                        goto out; 
                /*协议方面的工作已经处理完成了,但是自己的一切工作还没有完成,所以切换至正
在连接中*/ 
                  sock->state = SS_CONNECTING; 
 
                /* Just entered SS_CONNECTING state; the only 
                 * difference is that return value in non-blocking 
                 * case is EINPROGRESS, rather than EALREADY. 
                 */ 
                err = -EINPROGRESS; 
                break; 
        }[/code] 
 
对于 TCP的实际的连接,是通过调用 tcp_v4_connect()函数来实现的。 
 
[size=5][color=Red]二、tcp_v4_connect函数[/color][/size] 
对于 TCP 协议来说,其连接,实际上就是发送一个 SYN 报文,在服务器的应到到来时,回答它一
个 ack 报文,也就是完成三次握手中的第一和第三次。 
 
要发送 SYN 报文,也就是说,需要有完整的来源/目的地址,来源/目的端口,目的地址/端口由用户
态提交,但是问题是没有自己的地址和端口,因为并没有调  用过 bind(2),一台主机,对于端口,
可以像 sys_bind()那样,从本地未用端口中动态分配一个,那地址呢?因为一台主机可能会存在多
个 IP地  址,如果随机动态选择,那么有可能选择一个错误的来源地址,将不能正确地到达目的地
址。换句话说,来源地址的选择,是与路由相关的。 
 
调用路由查找的核心函数 ip_route_output_slow(),在没有提供来源地址的情况下,会根据实际情况,
调用 inet_select_addr()函数来选择一个合适的。同时,如果路由查找命中,会生成一个相应的路由
缓存项,这个缓存项,不但对当前发送 SYN 报  文有意义,对于后续的所有数据包,都可以起到一
个加速路由查找的作用。这一任务,是通过 ip_route_connect()函数完成的,它返回相应的路由缓存
项(也就是说,来源地址也在其中了): 
 
[code]static inline int ip_route_connect(struct rtable **rp, u32 dst, 
                                   u32 src, u32 tos, int oif, u8 protocol, 
                                   u16 sport, u16 dport, struct sock *sk) 
{         struct flowi fl = { .oif = oif, 
                            .nl_u = { .ip4_u = { .daddr = dst, 
                                                 .saddr = src, 
                                                 .tos   = tos } }, 
                            .proto = protocol, 
                            .uli_u = { .ports = 
                                       { .sport = sport, 
                                         .dport = dport } } }; 
 
        int err; 
        if (!dst || !src) { 
                err = __ip_route_output_key(rp, &fl); 
                if (err) 
                        return err; 
                fl.fl4_dst = (*rp)->rt_dst; 
                fl.fl4_src = (*rp)->rt_src; 
                ip_rt_put(*rp); 
                *rp = NULL; 
        } 
        return ip_route_output_flow(rp, &fl, sk, 0); 
}[/code] 
 
首先,构建一个搜索 key fl,在搜索要素中,来源地址/端口是不存在的。所以,当通过
__ip_route_output_key 进行查找时,第一次是不会命中缓存的。 __ip_route_output_key 将继续调用
ip_route_output_slow()函数,在路由表中搜索,并返回一个合适的来源地址,  并且生成一个路由缓
存项。 
路由查找的更多细节,我会在另一个贴子中来分析。 
 
[code]/* This will initiate an outgoing connection. */ 
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) 

        struct inet_sock *inet = inet_sk(sk); 
        struct tcp_sock *tp = tcp_sk(sk); 
        struct sockaddr_in *usin = (struct sockaddr_in *)uaddr; 
        struct rtable *rt; 
        u32 daddr, nexthop; 
        int tmp; 
        int err; 
 
        if (addr_len < sizeof(struct sockaddr_in)) 
                return -EINVAL; 
 
        if (usin->sin_family != AF_INET) 
                return -EAFNOSUPPORT;[/code] 校验地址长度和协议簇。 
 
[code]nexthop = daddr = usin->sin_addr.s_addr;[/code] 
将下一跳地址和目的地址的临时变量都暂时设为用户提交的地址。 
 
[code]        if (inet->opt && inet->opt->srr) { 
                if (!daddr) 
                        return -EINVAL; 
                nexthop = inet->opt->faddr; 
        }[/code] 
如果使用了来源地址路由,选择一个合适的下一跳地址。 
 
[code]        tmp = ip_route_connect(&rt, nexthop, inet->saddr, 
                               RT_CONN_FLAGS(sk), sk->sk_bound_dev_if, 
                               IPPROTO_TCP, 
                               inet->sport, usin->sin_port, sk); 
        if (tmp < 0) 
                return tmp; 
 
        if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) { 
                ip_rt_put(rt); 
                return -ENETUNREACH; 
        }[/code] 
进行路由查找,并校验返回的路由的类型,TCP是不被允许使用多播和广播的。 
 
[code]        if (!inet->opt || !inet->opt->srr) 
                daddr = rt->rt_dst;[/code] 
更新目的地址临时变量——使用路由查找后返回的值。 
 
[code]        if (!inet->saddr) 
                inet->saddr = rt->rt_src; 
        inet->rcv_saddr = inet->saddr;[/code] 
如果还没有设置源地址,和本地发送地址,则使用路由中返回的值。 
 
[code]        if (tp->rx_opt.ts_recent_stamp && inet->daddr != daddr) { 
                /* Reset inherited state */ 
                tp->rx_opt.ts_recent           = 0; 
                tp->rx_opt.ts_recent_stamp = 0; 
                tp->write_seq                   = 0; 
        } 
 
        if (sysctl_tcp_tw_recycle && 
            !tp->rx_opt.ts_recent_stamp && rt->rt_dst == daddr) { 
                struct inet_peer *peer = rt_get_peer(rt);  
                /* VJ's idea. We save last timestamp seen from 
                 * the destination in peer table, when entering state TIME-WAIT 
                 * and initialize rx_opt.ts_recent from it, when trying new connection. 
                 */ 
 
                if (peer && peer->tcp_ts_stamp + TCP_PAWS_MSL >= xtime.tv_sec) { 
                        tp->rx_opt.ts_recent_stamp = peer->tcp_ts_stamp; 
                        tp->rx_opt.ts_recent = peer->tcp_ts; 
                } 
        }[/code] 
这个更新初始状态方面的内容,还没有去分析它。 
 
[code]        inet->dport = usin->sin_port; 
        inet->daddr = daddr;[/code] 
保存目的地址及端口。 
 
[code]        tp->ext_header_len = 0; 
        if (inet->opt) 
                tp->ext_header_len = inet->opt->optlen;[/code] 
 
[code]tp->rx_opt.mss_clamp = 536;[/code] 
设置最小允许的 mss 值 
[code] 
tcp_set_state(sk, TCP_SYN_SENT);[/code] 
套接字状态被置为 TCP_SYN_SENT, 
 
[code]        err = tcp_v4_hash_connect(sk); 
        if (err) 
                goto failure;[/code] 
动态选择一个本地端口,并加入 hash 表,与bind(2)选择端口类似。 
 
[code]        err = ip_route_newports(&rt, inet->sport, inet->dport, sk); 
        if (err) 
                goto failure; 
 
        /* OK, now commit destination to socket.  */ 
        __sk_dst_set(sk, &rt->u.dst); 
        tcp_v4_setup_caps(sk, &rt->u.dst);[/code] 
因为本地端口已经改变,使用新端口,重新查找路由,并用新的路由缓存项更新 sk 中保存的路由
缓存项。 
 
[code]        if (!tp->write_seq) 
                tp->write_seq = secure_tcp_sequence_number(inet->saddr,                                                            inet->daddr, 
                                                           inet->sport, 
                                                           usin->sin_port);[/code] 
为 TCP报文计算一个 seq值(实际使用的值是 tp->write_seq+1)。 
 
inet->id = tp->write_seq ^ jiffies; 
 
[code]        err = tcp_connect(sk); 
        rt = NULL; 
        if (err) 
                goto failure; 
 
        return 0;[/code] 
tp_connect()函数用来根据 sk 中的信息,构建一个完成的 syn 报文,并将它发送出去。在分析 tcp栈
的实现时再来分析它。 
 
根据 TCP协议,接下来的问题是, 
1、可能收到了服务器的应答,则要回送一个 ack 报文; 
2、如果超时还没有应答,则使用超时重发定时器; 
 
Linux TCP/IP 协议栈之 Socket的实现分析(六  数据包的接收) 
 
前面了解过 sk 有一个接收队列,用于存储接  收到的 skb,对于 socket 层面上来讲,数据接收,就
是要把数据从这个队列中取出来,交给上层用户态。这里涉及到出队操作,但是,要了解如何出队,
就  得了解传输层协议如何入队。前面一直用 tcp协议来分析,现在还没有把整个 tcp栈分析出来,
要再继续用 tcp 协议来分析,就有点问题了,所以,数据的接  收和发送,都将以 udp 协议来分析。
虽然它很简单,但同样也反应了 socket 层数据与接收的全部核心内容与思路。我以希望,下一步拿
下 tcp 协议后,再  把这部份的 tcp 实现补上来。 
 
[color=Red][size=5]一、udp 层的数据接收[/size][/color] 
udp 层的数据接收,对于 socket 而言,就是接收队列的入队操作。在 ip 层中,如果是本地数据,则
会交由 ip_local_deliver_finish()函数处理,它会根据传输层协议的类型,交由相应的处理函数,对于
udp 协议而言,就是 udp_rcv(): 
 
[code] 
/* 
*        All we need to do is get the socket, and then do a checksum.  
*/ 
 
int udp_rcv(struct sk_buff *skb) 

          struct sock *sk; 
          struct udphdr *uh; 
        unsigned short ulen;         struct rtable *rt = (struct rtable*)skb->dst; 
        u32 saddr = skb->nh.iph->saddr; 
        u32 daddr = skb->nh.iph->daddr; 
        int len = skb->len; 
 
        /* 
         *         数据包至少应有 UDP 首部长度. 
         */ 
        if (!pskb_may_pull(skb, sizeof(struct udphdr))) 
                goto no_header; 
                 
        /*获取 udp 首部指针*/ 
        uh = skb->h.uh; 
         
        /*  数据长度,含首部长度 */ 
        ulen = ntohs(uh->len); 
         
        /*  数据包长度不够:UDP 长度比 skb 长度小,意味着数据的丢失,而 udp 长度比 udp 首部
还要小,好像这个不太可能,除非封包出错 ^o^*/ 
        if (ulen > len || ulen < sizeof(*uh)) 
                goto short_packet; 
                 
        /*  截去 udp 报文后面的多余报文 */ 
        if (pskb_trim(skb, ulen)) 
                goto short_packet; 
         
        /*  开始 udp 校验和计算,主要查依赖于 skb 的 ip_summumed 字段的设置来决定是否需要进
行校验和计算 */ 
        if (udp_checksum_init(skb, uh, ulen, saddr, daddr) < 0) 
                goto csum_error; 
         
        /*  转换多播或广播处理例程 */ 
        if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST)) 
                return udp_v4_mcast_deliver(skb, uh, saddr, daddr); 
         
        /*  查找数据段对应的 socket结构的 sk */ 
        sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex); 
 
        if (sk != NULL) { 
                /*  找到了,数据包进入 UDP 的 socket 的接收队列*/ 
                int ret = udp_queue_rcv_skb(sk, skb); 
                sock_put(sk); 
 
                /* a return value > 0 means to resubmit the input, but                  * it it wants the return to be -protocol, or 0 
                 */ 
                if (ret > 0) 
                        return -ret; 
                return 0; 
        } 
 
        if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) 
                goto drop; 
 
        /*  没有对应的 socket.  如果校验和错误,则丢弃它 */ 
        if (udp_checksum_complete(skb)) 
                goto csum_error; 
         
        /*  发送一个目的不可达报文 */ 
        UDP_INC_STATS_BH(UDP_MIB_NOPORTS); 
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); 
 
        /* 
         * Hmm.  We got an UDP packet to a port to which we 
         * don't wanna listen.  Ignore it. 
         */ 
        kfree_skb(skb); 
        return(0); 
 
short_packet: 
        NETDEBUG(if (net_ratelimit()) 
                printk(KERN_DEBUG "UDP: short packet: From %u.%u.%u.%u:%u %d/%d to 
%u.%u.%u.%u:%u/n", 
                        NIPQUAD(saddr), 
                        ntohs(uh->source), 
                        ulen, 
                        len, 
                        NIPQUAD(daddr), 
                        ntohs(uh->dest))); 
no_header: 
        UDP_INC_STATS_BH(UDP_MIB_INERRORS); 
        kfree_skb(skb); 
        return(0); 
 
csum_error: 
        /*  
         * RFC1122: OK.  Discards the bad packet silently (as far as  
         * the network is concerned, anyway) as per 4.1.3.4 (MUST).           */ 
        NETDEBUG(if (net_ratelimit()) 
                 printk(KERN_DEBUG "UDP: bad checksum. From %d.%d.%d.%d:%d to 
%d.%d.%d.%d:%d ulen %d/n", 
                        NIPQUAD(saddr), 
                        ntohs(uh->source), 
                        NIPQUAD(daddr), 
                        ntohs(uh->dest), 
                        ulen)); 
drop: 
        UDP_INC_STATS_BH(UDP_MIB_INERRORS); 
        kfree_skb(skb); 
        return(0); 
}[/code] 
 
函数的核心思想,是根据 skb,查找到与之对应的 sk,调用 udp_v4_lookup()函数实现——udp 与 tcp
一样,socket有一个 hash表,这个查找,就是查找 hash 表的过程。 
 
如果找到了对应的 sk,则进入 udp_queue_rcv_skb()函数: 
 
[code]/* returns: 
*  -1: error 
*   0: success 
*  >0: "udp encap" protocol resubmission 

* Note that in the success and error cases, the skb is assumed to 
* have either been requeued or freed. 
*/ 
static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb) 

        /*  获取 sk 对应的udp_sock 结构指针 */ 
        struct udp_sock *up = udp_sk(sk); 
 
        /* 
         *        Charge it to the socket, dropping if the queue is full. 
         */ 
        if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) { 
                kfree_skb(skb); 
                return -1; 
        } 
 
        if (up->encap_type) { 
                /* 
                 * This is an encapsulation socket, so let's see if this is                  * an encapsulated packet. 
                 * If it's a keepalive packet, then just eat it. 
                 * If it's an encapsulateed packet, then pass it to the 
                 * IPsec xfrm input and return the response 
                 * appropriately.  Otherwise, just fall through and 
                 * pass this up the UDP socket. 
                 */ 
                int ret; 
 
                ret = udp_encap_rcv(sk, skb); 
                if (ret == 0) { 
                        /* Eat the packet .. */ 
                        kfree_skb(skb); 
                        return 0; 
                } 
                if (ret < 0) { 
                        /* process the ESP packet */ 
                        ret = xfrm4_rcv_encap(skb, up->encap_type); 
                        UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS); 
                        return -ret; 
                } 
                /* FALLTHROUGH -- it's a UDP Packet */ 
        } 
        /*  如果需要校验 */ 
        if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) { 
                /*  那就校验它吧 */ 
                if (__udp_checksum_complete(skb)) { 
                        /*  结果校验出错,那就算了吧 */ 
                        UDP_INC_STATS_BH(UDP_MIB_INERRORS); 
                        kfree_skb(skb); 
                        return -1; 
                } 
                /*  已经校验过了,就设置不用再校验了 */ 
                skb->ip_summed = CHECKSUM_UNNECESSARY; 
        } 
 
        /*  设用 sock_queue_rcv_skb 入队 */ 
        if (sock_queue_rcv_skb(sk,skb)<0) { 
                UDP_INC_STATS_BH(UDP_MIB_INERRORS); 
                kfree_skb(skb); 
                return -1; 
        } 
        UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS); 
        return 0; }[/code] 
encap_type字段用于判断 udp 包,是否是一个 IPSEC 协议的封装报文,这里不关分 ipsec,所以接下
来的工作,就是校验和计算,然后紧跟着调用 sock_queue_rcv_skb(): 
 
[code]static inline int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) 

        int err = 0; 
        int skb_len; 
 
        /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces 
           number of warnings when compiling with -W --ANK 
         */ 
        if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >= 
            (unsigned)sk->sk_rcvbuf) { 
                err = -ENOMEM; 
                goto out; 
        } 
 
        /*  进入 socket 层的包过滤        */ 
        err = sk_filter(sk, skb, 1); 
        if (err) 
                goto out; 
         
        /*  设置 skb 的一些必要的指针和计数器变量 
        dev:关连备设指针; 
        sk:所对应的 sk 结构; 
        destructor:函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。 
         如果 skb 不属于一个 socket,则其常为 NULL。反之,这个函数会被赋值为 sock_rfree 或
sock_wfree. 
         用于更新 socket的队列中的内存容量。*/ 
        skb->dev = NULL; 
        skb_set_owner_r(skb, sk); 
 
        /* 在skb 入队之前,保存其长度至临时变量 skb_len,这是因为一旦 skb 入队后,它将被 
         其它线程处理,skb 命运未知。。。。。。,而 len 值后面还会用到。 
         */ 
        skb_len = skb->len; 
         
        /* 将skb 加入 sk的接收队列 */ 
        skb_queue_tail(&sk->sk_receive_queue, skb); 
         
        /*  唤醒 socket 上的接收线程? */ 
        if (!sock_flag(sk, SOCK_DEAD)) 
                sk->sk_data_ready(sk, skb_len); out: 
        return err; 
}[/code] 
对于 udp 而言,直接调用 skb_queue_tail(),将 skb 加入至 sk的 sk_receive_queue 队列即可。 
 
[color=Red][size=5]一、udp 层的数据出队操作[/size][/color] 
 
了解了数据包如何被加入队列,上层 socket 的数据接收,就是从这个队列中来取数据。udp 对应的
取数据的函数 udp_recvmsg(),后面再来看它是如何被调用的,现在先来分析它的实现: 
 
[code] 
/* 
*         This should be easy, if there is something there we 
*         return it, otherwise we block. 
*/ 
 
static int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, 
                       size_t len, int noblock, int flags, int *addr_len) 

        struct inet_sock *inet = inet_sk(sk);                /*  取得 sk对应的 inet_sock 指针 */ 
          struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name; 
          struct sk_buff *skb; 
          int copied, err; 
 
        /* 
         *         校验地址长度 
         */ 
        if (addr_len) 
                *addr_len=sizeof(*sin); 
        /*  如果 sk 队列中有错误的信息,则设用 ip_recv_error函数 */ 
        if (flags & MSG_ERRQUEUE) 
                return ip_recv_error(sk, msg, len); 
 
try_again: 
        /*  出队 */ 
        skb = skb_recv_datagram(sk, flags, noblock, &err); 
        if (!skb) 
                goto out; 
          /*  需要拷贝的数据不包含 UDP 首部 */ 
          copied = skb->len - sizeof(struct udphdr); 
          /*  如果用户提供的缓存不够,则设置为只拷贝用户需要的长度,并设置截短数据标志 */ 
        if (copied > len) { 
                copied = len; 
                msg->msg_flags |= MSG_TRUNC;         } 
         
        if (skb->ip_summed==CHECKSUM_UNNECESSARY) { 
                /*  拷贝数据至用户空间 */ 
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov, 
                                              copied); 
        } else if (msg->msg_flags&MSG_TRUNC) { 
                if (__udp_checksum_complete(skb)) 
                        goto csum_copy_err; 
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov, 
                                              copied); 
        } else { 
                err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov); 
 
                if (err == -EINVAL) 
                        goto csum_copy_err; 
        } 
 
        if (err) 
                goto out_free; 
        /*  更新 sk 的接收时间戳,根据 SOCK_RCVTSTAMP标志的设置来决定:选择当前时间还是
skb buffer的接收时间 */ 
        sock_recv_timestamp(msg, sk, skb); 
 
        /*  拷贝地址等信息. */ 
        if (sin) 
        { 
                sin->sin_family = AF_INET; 
                sin->sin_port = skb->h.uh->source; 
                sin->sin_addr.s_addr = skb->nh.iph->saddr; 
                memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); 
          } 
          /*  查看是否设置了一些控制标志,查看某些IP socket选项是否被设置,例如,设置了IP_TOS 
socket 选项,把 IP首部中的 
          tos字段拷贝至用户空间等等,这个工作是由 ip_cmsg_recv 函数完成的 */ 
        if (inet->cmsg_flags) 
                ip_cmsg_recv(msg, skb); 
         
        /*  设置拷贝的字节数,如果数据段已经被截短,则返回原始实际的长度 */ 
        err = copied; 
        if (flags & MSG_TRUNC) 
                err = skb->len - sizeof(struct udphdr); 
   
out_free:           skb_free_datagram(sk, skb); 
out: 
          return err; 
 
csum_copy_err: 
        UDP_INC_STATS_BH(UDP_MIB_INERRORS); 
 
        /* Clear queue. */ 
        if (flags&MSG_PEEK) { 
                int clear = 0; 
                spin_lock_bh(&sk->sk_receive_queue.lock); 
                if (skb == skb_peek(&sk->sk_receive_queue)) { 
                        __skb_unlink(skb, &sk->sk_receive_queue); 
                        clear = 1; 
                } 
                spin_unlock_bh(&sk->sk_receive_queue.lock); 
                if (clear) 
                        kfree_skb(skb); 
        } 
 
        skb_free_datagram(sk, skb); 
 
        if (noblock) 
                return -EAGAIN;         
        goto try_again; 
}[/code] 
 
skb 的出队,是通过调用 skb_recv_datagram()函数,出队操作时,队列中可能没有数据,如果是阻
塞,则需要一直睡眠等待,直到超时或队  列中有数据而被唤醒。另外出队操作,还分为两种情况,
一种是将数据包从队列中取中来,将其从原有队列中删除,还有一种可能是设置了 MSG_PEEK 标
志,  它意味着:“查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除”。 
 
[code]struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, 
                                  int noblock, int *err) 

        struct sk_buff *skb; 
        long timeo; 
        /* 
         * Caller is allowed not to check sk->sk_err before skb_recv_datagram() 
         */ 
        int error = sock_error(sk); 
 
        if (error) 
                goto no_packet;          
        /*  获取超时时间*/ 
        timeo = sock_rcvtimeo(sk, noblock); 
         
        /*  这个循环将一直等到队列中有数据包,直到超时 */ 
        do { 
                /* Again only user level code calls this function, so nothing 
                 * interrupt level will suddenly eat the receive_queue. 
                 * 
                 * Look at current nfs client by the way... 
                 * However, this function was corrent in any case. 8) 
                 */ 
                if (flags & MSG_PEEK) { 
                        unsigned long cpu_flags; 
                        /*  如果设置了 MSG_PEEK,则设用 skb_peek,这里要对接收队列加锁的原因
在于: 
                        */ 
                        spin_lock_irqsave(&sk->sk_receive_queue.lock, 
                                          cpu_flags); 
                        skb = skb_peek(&sk->sk_receive_queue); 
                        if (skb) 
                                atomic_inc(&skb->users); 
                        spin_unlock_irqrestore(&sk->sk_receive_queue.lock, 
                                               cpu_flags); 
                } else 
                        skb = skb_dequeue(&sk->sk_receive_queue);                /* 直接出队 */ 
                 
                /*  找到要收获的葫芦了,摘下来 */ 
                if (skb) 
                        return skb; 
 
                /*  如果是非阻塞,就不等了,直接跳出循环,返回 */ 
                error = -EAGAIN; 
                if (!timeo) 
                        goto no_packet; 
 
        } while (!wait_for_packet(sk, err, &timeo)); 
 
        return NULL; 
 
no_packet: 
        *err = error; 
        return NULL; 
}[/code]  
继续回到 udp_recvmsg 函数中来,当取出 skb 后,需要将它拷贝至用户空间。用户空间的数据缓存,
用了一个很重要的数据结构 struct msghdr来表示: 
 
[code]struct msghdr { 
        void        *        msg_name;        /* Socket name                        */ 
        int                msg_namelen;        /* Length of name                */ 
        struct iovec *        msg_iov;        /* Data blocks                        */ 
        __kernel_size_t        msg_iovlen;        /* Number of blocks                */ 
        void         *        msg_control;        /* Per protocol magic (eg BSD file descriptor passing) */ 
        __kernel_size_t        msg_controllen;        /* Length of cmsg list */ 
        unsigned        msg_flags; 
};[/code] 
结构中的 msg_name、 msg_namelen 以及 msg_flags分别对应于 sys_sendto()[其它接收/发送函数类似]
的参数 addr、addr_len 以及 flags。指针 msg_control 可以指向一个附加的数据结构,用来提供一些
附加的控制信息。后面可以看到 ip_cmsg_recv()使用了它。最重要的是,结构中的指针 msg_iov,
指向一个 iovec 结构数据,而 msg_iovlen 则指明了这个数组  的大小: 
[code]struct iovec 

        void __user *iov_base;        /* BSD uses caddr_t (1003.1g requires void *) */ 
        __kernel_size_t iov_len; /* Must be size_t (1003.1g) */ 
};[/code] 
 
数组中的每一个元互素,都是 struct iovec 结构, 称之为"io 向量",由指向数据缓冲区的指针 iov_base
和表示缓冲区中数据长度的 iov_len构成。这样,一个 msghdr中的数据,就由控制信息 msg_control
和若干个"io向量"组成。 
 
一个疑问是,为什么不设置为一个缓冲区,而要分为若干个缓冲区呢?一个很重要的原因就是,每
个数据报文的大小一致,对于 ip 包而言。46-1500 都是为  可能的,那么如果是一个缓冲区的话,
就得定义为一个“最大值”,但是如果绝大部份的包,都小于这个最大值,例如为 256 或 512,那么
内存空间就浪费得太  多了……。所以,一个好的办法是,用一个个小的缓冲区将数据切分,要浪
费,也浪费最后一个缓冲区的空间。——这种设计跟 Unix上著名的 mbuf好像是一 致的。 
 
OK,了解了 msghdr 的结构后,再来看数据的拷贝工作,拷贝的核心函数是 memcpy_toiovec: 
[code]/* 
*        Copy kernel to iovec. Returns -EFAULT on error. 

*        Note: this modifies the original iovec. 
*/ 
 
int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len) 

        while (len > 0) { 
                if (iov->iov_len) {                         int copy = min_t(unsigned int, iov->iov_len, len); 
                        if (copy_to_user(iov->iov_base, kdata, copy)) 
                                return -EFAULT; 
 
                        kdata += copy; 
                        len -= copy; 
                        iov->iov_len -= copy; 
                        iov->iov_base += copy; 
                } 
                iov++; 
        } 
 
        return 0; 
}[/code] 
 
因为每个 io向量的缓冲区可能很小,例如 100字节,而要拷贝的数据很长,例如 1000 字节。这样,
需要在一个循环里,将数据拷贝至若干个 
io 向量数组元素当中去。 
 
回到 udp_recvmsg 中来,它通过设用 skb_copy_datagram_iovec()函数完成数据的拷贝工作。来看看
它是如何调用 memcpy_toiovec 的,当然,类似于这样: 
[code]int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset, 
                            struct iovec *to, int len) 

        return memcpy_toiovec(to, skb->data+offset, len); 
}[/code] 
 
这样的调用,该是多么美好呀,这样的日子曾经是有过,不过已经一去不复返了。 
 
考虑到 skb 结构的复杂性,例如分片,拷贝工作要复杂得多: 
[code] 
/** 
*        skb_copy_datagram_iovec - Copy a datagram to an iovec. 
*        @skb: buffer to copy 
*        @offset: offset in the buffer to start copying from 
*        @to: io vector to copy to 
*        @len: amount of data to copy from buffer to iovec 

*        Note: the iovec is modified during the copy. 
*/ 
int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset, 
                            struct iovec *to, int len) 

        int start = skb_headlen(skb);                /* start 表示要从 skb 的哪里开始拷贝数据 */         int i, copy = start - offset;                /* offset  表示调用者指出的缓存开始拷贝的偏移字节,
一般情况下,它 
                                                 与 start 是重叠的,表示不需要拷贝数据包的首部 */ 
 
        /*  需要拷贝首部. */ 
        if (copy > 0) { 
                if (copy > len) 
                        copy = len; 
                if (memcpy_toiovec(to, skb->data + offset, copy)) 
                        goto fault; 
                if ((len -= copy) == 0) 
                        return 0; 
                offset += copy; 
        } 
 
        /*  遍历每一个分片 */ 
        for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { 
                int end; 
 
                BUG_TRAP(start <= offset + len); 
 
                end = start + skb_shinfo(skb)->frags[i].size; 
                if ((copy = end - offset) > 0) { 
                        int err; 
                        u8  *vaddr; 
                        skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; 
                        struct page *page = frag->page; 
 
                        if (copy > len) 
                                copy = len; 
                        vaddr = kmap(page); 
                        err = memcpy_toiovec(to, vaddr + frag->page_offset + 
                                             offset - start, copy); 
                        kunmap(page); 
                        if (err) 
                                goto fault; 
                        if (!(len -= copy)) 
                                return 0; 
                        offset += copy; 
                } 
                start = end; 
        } 
 
        if (skb_shinfo(skb)->frag_list) {                 struct sk_buff *list = skb_shinfo(skb)->frag_list; 
 
                for (; list; list = list->next) { 
                        int end; 
 
                        BUG_TRAP(start <= offset + len); 
 
                        end = start + list->len; 
                        if ((copy = end - offset) > 0) { 
                                if (copy > len) 
                                        copy = len; 
                                if (skb_copy_datagram_iovec(list, 
                                                            offset - start, 
                                                            to, copy)) 
                                        goto fault; 
                                if ((len -= copy) == 0) 
                                        return 0; 
                                offset += copy; 
                        } 
                        start = end; 
                } 
        } 
        if (!len) 
                return 0; 
 
fault: 
        return -EFAULT; 
}[/code] 
 
现在还没有分析 ip 的分片,所以,要完全理解这个函数的代码有点难度,等到 ip 分片分析完了,
再来补充完整它。 
 
[code]三、socket 层的数据接收[/code] 
recv(2)、recvfrom(2)和 recvmsg(3),在 sys_socketcall()中,最终都会归于一个统一的系统调用
sock_recvmsg。例如: 
[code]asmlinkage long sys_recv(int fd, void __user * ubuf, size_t size, unsigned flags) 

        return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL); 
}[/code] 
sys_recv 转向了 sys_recvfrom(): 
[code] 
/* 
*        Receive a frame from the socket and optionally record the address of the  
*        sender. We verify the buffers are writable and if needed move the *        sender address from kernel to user space. 
*/ 
 
asmlinkage long sys_recvfrom(int fd, void __user * ubuf, size_t size, unsigned flags, 
                             struct sockaddr __user *addr, int __user *addr_len) 

        struct socket *sock; 
        struct iovec iov; 
        struct msghdr msg; 
        char address[MAX_SOCK_ADDR]; 
        int err,err2; 
 
        sock = sockfd_lookup(fd, &err); 
        if (!sock) 
                goto out; 
 
        msg.msg_control=NULL; 
        msg.msg_controllen=0; 
        msg.msg_iovlen=1; 
        msg.msg_iov=&iov; 
        iov.iov_len=size; 
        iov.iov_base=ubuf; 
        msg.msg_name=address; 
        msg.msg_namelen=MAX_SOCK_ADDR; 
        if (sock->file->f_flags & O_NONBLOCK) 
                flags |= MSG_DONTWAIT; 
        err=sock_recvmsg(sock, &msg, size, flags); 
 
        if(err >= 0 && addr != NULL) 
        { 
                err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len); 
                if(err2<0) 
                        err=err2; 
        } 
        sockfd_put(sock);                         
out: 
        return err; 
}[/code] 
 
函数先调用 sockfd_lookup,根据 socket 描述符查找以相应的 sock 结构,然后封装了一个 msghdr结
构后,接着调用 sock_recvmsg()。 
[code]int sock_recvmsg(struct socket *sock, struct msghdr *msg,  
                 size_t size, int flags) 
{         struct kiocb iocb; 
        struct sock_iocb siocb; 
        int ret; 
 
        init_sync_kiocb(&iocb, NULL); 
        iocb.private = &siocb; 
        ret = __sock_recvmsg(&iocb, sock, msg, size, flags); 
        if (-EIOCBQUEUED == ret) 
                ret = wait_on_sync_kiocb(&iocb); 
        return ret; 
}[/code] 
 
iocb 和 siocb 用于内核和 socket 的 io 控制,函数中主要初始化了 iocb,siocb 的初始化,在
__sock_recvmsg 中完成。 
进一步的按收动作,是通过__sock_recvmsg 函数设用完成的: 
[code]static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,  
                                 struct msghdr *msg, size_t size, int flags) 

        int err; 
        struct sock_iocb *si = kiocb_to_siocb(iocb); 
 
        si->sock = sock; 
        si->scm = NULL; 
        si->msg = msg; 
        si->size = size; 
        si->flags = flags; 
 
        err = security_socket_recvmsg(sock, msg, size, flags); 
        if (err) 
                return err; 
 
        return sock->ops->recvmsg(iocb, sock, msg, size, flags); 

[/code] 
初始化完 siocb,也就是 si 后,调用协议簇的 recvmsg 函数,对于 UDP 而言,是
sock_common_recvmsg(): 
[code]int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock, 
                        struct msghdr *msg, size_t size, int flags) 

        struct sock *sk = sock->sk;    /*取得 sock 结构对应的 sk指针*/ 
        int addr_len = 0; 
        int err; 
 
        err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,                                    flags & ~MSG_DONTWAIT, &addr_len); 
        if (err >= 0) 
                msg->msg_namelen = addr_len; 
        return err; 
}[/code] 
 
于是,udp_recvmsg 就被调用了。整个过程也就结束了。 
 
最后再回到 sys_recvfrom中来,如果用户调用时,需要返回对方的地址信息: 
[code]        if(err >= 0 && addr != NULL) 
        { 
                err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len); 
                if(err2<0) 
                        err=err2; 
        }[/code] 
 
就需要调用 move_addr_to_user()  函数来完成,前面分析 msghdr 结构时已经提到其 msg_name 成员
变量,它包含了地址的相应信息,msg.msg_namelen 成员变量,决定了地址的长度信息: 
[code]int move_addr_to_user(void *kaddr, int klen, void __user *uaddr, int __user *ulen) 

        int err; 
        int len; 
 
        if((err=get_user(len, ulen)))     //缓存长度校验 
                return err; 
        if(len>klen) 
                len=klen; 
        if(len<0 || len> MAX_SOCK_ADDR) 
                return -EINVAL; 
        if(len) 
        { 
                if(copy_to_user(uaddr,kaddr,len))   //拷贝地址信息 
                        return -EFAULT; 
        } 
        /* 
         *        "fromlen shall refer to the value before truncation.." 
         *                        1003.1g 
         */ 
        return __put_user(klen, ulen);  
}[/code] 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值