下面就是它的代码片段:
- asmlinkage long sys_socketcall(int call, unsigned long __user *args)
- {
- unsigned long a[6];
- unsigned long a0, a1;
- int err;
- ..........................................
- 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 =
- do_accept(a0, (struct sockaddr __user *)a1,
- (int __user *)a[2], 0);
- break;
- case SYS_GETSOCKNAME:
- err =
- sys_getsockname(a0, (struct sockaddr __user *)a1,
- (int __user *)a[2]);
- break;
- .....................................
- return err;
- }
可以看到代码比较简单,就是通过传递进来的call类型,来调用相应的socket相关的函数.
这里你可能注意到了,那就是一般文件句柄相关的操作,比如write,read,aio,poll这些并没有看到(也就是file_operations).这是因为socket上面其实还有一层vfs层,内核把socket当做一个文件系统来处理,并实现了相应的vfs方法.因此下面我们先来了解下vfs.然后会描述下进程如何通过vfs存取句柄.
vfs其实就相当于对下层的文件系统和上层应用之间的粘合层,它定义了文件系统需要实现的相关的操作,然后下层的文件系统只需要实现这些方法就可以了,也就是说在内核其他部分和上层应用看来,所有的文件系统没有任何区别.
下面的这张图就是从用户空间调用write的大体流程:
vfs中有4种主要的数据结构:
1 超级块对象,代表一个已安装的文件系统.super_block
2 索引节点对象,代表一个文件.inode
3 目录项对象,代表一个目录项.dentry
4 文件对象,表示一个被进程打开的文件.file
其中每种对象都包含一个操作对象.依次为super_operations,inode_operations,dentry_operations以及file_operations.各自操作不同的层次.然后我们的文件系统只需要实现这些方法,然后注册到内核就可以了.
接下来我们来看和vfs相应的结构:
第一个就是file_system_type结构,这个结构表示了一个文件系统:
- struct file_system_type {
- const char *name;
- int fs_flags;
- ///最关键的函数,得到文件系统的超级块.
- int (*get_sb) (struct file_system_type *, int,
- const char *, void *, struct vfsmount *);
- void (*kill_sb) (struct super_block *);
- ...........................................
- };
然后是vfsmount结构,它表示了一个安装点,换句话说也就是一个文件系统实例.
第三个是files_struct结构,它主要是为每个进程来维护它所打开的句柄.这里只需要注意一个就是fd_array和fstable中的fd的区别.当进程数比较少也就是小于NR_OPEN_DEFAULT(32)时,句柄就会存放在fd_array中,而当句柄数超过32则就会重新分配数组,然后将fd指针指向它(然后我们通过fd就可以取得相应的file结构).
而且files_struct是每个进程只有一个的.
- struct files_struct {
- /*
- * read mostly part
- */
- atomic_t count;
- struct fdtable *fdt;
- struct fdtable fdtab;
- /*
- * written part on a separate cache line in SMP
- */
- spinlock_t file_lock ____cacheline_aligned_in_smp;
- int next_fd;
- struct embedded_fd_set close_on_exec_init;
- struct embedded_fd_set open_fds_init;
- ///所打开的所有文件
- struct file * fd_array[NR_OPEN_DEFAULT];
- };
- struct fdtable {
- unsigned int max_fds;
- struct file ** fd; /* current fd array */
- fd_set *close_on_exec;
- fd_set *open_fds;
- struct rcu_head rcu;
- struct fdtable *next;
- };
还有两个一个是fs_struct,一个是namespace也都是进程相关的.这里就不一一介绍了.
我这里vfs介绍只是个大概,需要详细了解的,可以去看ulk的vfs相关章节和linux内核设计与实现的相关章节.
因此下面的图表示了进程和socket的关系:
上面的这张图有些老了,新的内核中的inode节点中已经没有u这个联合体了,对应的是会有一个包含socket和inode的一个结构体,然后我们通过inode,而inode中专门有个i_mode域来判断相应的inode类型,比如socket就是 S_IFSOCK.就可以直接计算出相应的socket的地址,然后就可以存取socket了.后面我们会介绍.
内核中标售socket有两个数据结构,一个是socket,另一个是sock,其中socket是一个general BSD socket, 它也就是应用程序和4层协议之间的一个接口,屏蔽掉了相关的4层协议部分.而在内核中,socket所需要使用的相关的4层协议的信息全部是保存在sock结构当中的,而socket和sock这两个结构都有保存对方的指针,因此可以很容易的存取对方.
还有一个就是ops域,这个域保存了所有的相关的4层协议的操作函数..
而在sock中有一个sk_common保存了一个skc_prot域,这个域保存的是相应的协议簇的操作函数的集合.
后面介绍到socket创建的时候,我们会分析proto_ops和proto的区别.其实proto相当于对proto_ops的一层封装,最终会在proto中调用proto_ops.
- /**
- * struct socket - general BSD socket
- * @state: socket state (%SS_CONNECTED, etc)
- * @type: socket type (%SOCK_STREAM, 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
- */
- struct socket {
- socket_state state;
- short type;
- unsigned long flags;
- const struct proto_ops *ops;
- struct fasync_struct *fasync_list;
- struct file *file;
- struct sock *sk;
- wait_queue_head_t wait;
- };
- struct sock_common {
- unsigned short skc_family;
- volatile unsigned char skc_state;
- unsigned char skc_reuse;
- int skc_bound_dev_if;
- struct hlist_node skc_node;
- struct hlist_node skc_bind_node;
- atomic_t skc_refcnt;
- unsigned int skc_hash;
- struct proto *skc_prot;
- #ifdef CONFIG_NET_NS
- struct net *skc_net;
- #endif
- };
- struct proto_ops {
- int family;
- struct module *owner;
- int (*release) (struct socket *sock);
- int (*bind) (struct socket *sock,
- struct sockaddr *myaddr,
- int sockaddr_len);
- int (*connect) (struct socket *sock,
- struct sockaddr *vaddr,
- int sockaddr_len, int flags);
- ...................................................
- };
然后我们来看sock_init的实现,在这个函数中,将socket注册为一个伪文件系统,并安装相应的mount点:
- ///相应的mount对象
- static struct vfsmount *sock_mnt __read_mostly;
- ///文件系统对象.
- static struct file_system_type sock_fs_type = {
- .name = "sockfs",
- .get_sb = sockfs_get_sb,
- .kill_sb = kill_anon_super,
- };
- static int __init sock_init(void)
- {
- /*
- * Initialize sock SLAB cache.
- */
- sk_init();
- /*
- * Initialize skbuff SLAB cache
- */
- skb_init();
- ///初始化一个inodecache.
- init_inodecache();
- ///注册文件系统到内核.
- register_filesystem(&sock_fs_type);
- ///安装mount点.
- sock_mnt = kern_mount(&sock_fs_type);
- #ifdef CONFIG_NETFILTER
- netfilter_init();
- #endif
- return 0;
- }
我们知道每次创建一个socket,都是要依赖于当前的protocol family类型的(后面会分析sys_socket的源码的时候会看到).而在内核中,每种类型的protocol family都会有一个相对应的net_proto_family结构,然后将这个结构注册到内核的net_families数组中,这样我们创建socket的时候,就可以调用这个数组来创建socket.
我们先来看sock_register的源码,也就是如何将一个net_proto_family注册到相应的数组:
- static const struct net_proto_family *net_families[NPROTO] __read_mostly;
- int sock_register(const struct net_proto_family *ops)
- {
- int err;
- if (ops->family >= NPROTO) {
- printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family,
- NPROTO);
- return -ENOBUFS;
- }
- spin_lock(&net_family_lock);
- ///代码非常简单,就是根据类型,然后放到相应的位置.
- if (net_families[ops->family])
- err = -EEXIST;
- else {
- net_families[ops->family] = ops;
- err = 0;
- }
- spin_unlock(&net_family_lock);
- printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family);
- return err;
- }
我们知道每个协议簇和相应的套接口都对应有好多种组合,因此在协议簇的实现中保存了一个相应的结构来保存这些组合,然后后面就首先通过family然后确定到某个结构,再根据套接口的类型来得到这个结构,并赋值给sock.
这里要注意我们只分析af_inet的实现,其他的协议簇都差不多:
我们来看这个的实现:
- ///可以看到这是一个数组,每个元素都是一个链表,也就是每种类型的socket就是一个链表.而这个链表所包含的是不同4层协议的inetsw.可是在inet中,现在每种类型的socket只对应一个4层协议.这里只是为了以后扩展.
- static struct list_head inetsw[SOCK_MAX];
- ///相应的socket的对应的信息的结构.
- struct inet_protosw {
- struct list_head list;
- ///需要这两个key才能定位一个inet_protosw.
- unsigned short type; /* This is the 2nd argument to socket(2). */
- unsigned short protocol; /* This is the L4 protocol number. */
- ///相应的基于ipv4的4层协议的操作集合.
- struct proto *prot;
- ///相应的协议簇的操作信息.
- const 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. */
- };
- 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;
- .............................................
- answer = NULL;
- last_perm = &inetsw[p->type];
- ///这个操作也很简单,就是将inet_protosw根据套接口类型插入到全局链表数组.
- 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;
- ///插入链表.
- list_add_rcu(&p->list, last_perm);
- ..............................
接下来来分析inet_init的源码.
- ///表示了所有的可能的当前协议簇和套接口类型的组合.
- 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 |
- INET_PROTOSW_ICSK,
- },
- {
- .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,
- }
- };
- ///协议簇的创建函数.
- static struct net_proto_family inet_family_ops = {
- .family = PF_INET,
- .create = inet_create,
- .owner = THIS_MODULE,
- };
- static int __init inet_init(void)
- {
- .............................................
- ///注册相应的proto到全局链表中.
- 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;
- ///注册协议簇的操作函数(后面socket创建的时候会用到).
- (void)sock_register(&inet_family_ops);
- .............................................
- /* Register the socket-side information for inet_create. */
- for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
- INIT_LIST_HEAD(r);
- ///将inetsw_array插入到相应的数组链表.
- for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
- inet_register_protosw(q);
- ...........................................
- }
接下来我们来通过分析创建socket的函数sys_socket,来更加好的理解socket的实现.
- asmlinkage long sys_socket(int family, int type, int protocol)
- {
- ...............................................
- ///主要是两个函数,一个是创建socket
- retval = sock_create(family, type, protocol, &sock);
- if (retval < 0)
- goto out;
- ///这个是相应的文件系统的操作.
- retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
- ....................................
- }
sock_create的具体流程我们就不分析了,我们只需要知道最终他会通过传递进来的family的值,来取得相应的family中注册的creat函数.然后会调用这个函数来完成socket的创建.而在上面的代码分析中,我们知道在af_inet中,注册的create函数是inet_create函数,因此我们来看这个函数的实现:
- static int inet_create(struct net *net, struct socket *sock, int protocol)
- {
- struct sock *sk;
- struct inet_protosw *answer;
- struct inet_sock *inet;
- struct proto *answer_prot;
- unsigned char answer_flags;
- char answer_no_check;
- int try_loading_module = 0;
- int err;
- ...........................................................
- ///首先给socket状态赋值.
- sock->state = SS_UNCONNECTED;
- /* Look for the requested type/protocol pair. */
- lookup_protocol:
- err = -ESOCKTNOSUPPORT;
- rcu_read_lock();
- ///通过type和protocl的值,来查找到相应的inet_protosw结构.
- list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
- err = 0;
- /* 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;
- }
- err = -EPROTONOSUPPORT;
- }
- ..........................................
- ///开始给socket赋值.这里我们可以看到最终socket的ops域所得到的值就是相应的协议簇的操作集合(比如inet_stream_ops这些)..
- sock->ops = answer->ops;
- answer_prot = answer->prot;
- answer_no_check = answer->no_check;
- answer_flags = answer->flags;
- rcu_read_unlock();
- WARN_ON(answer_prot->slab == NULL);
- err = -ENOBUFS;
- ///alloc一个sock结构,其中将刚才取得的inet_protosw中的pro 域赋值给sock的sk_prot域和sk_prot_creator.以及family域也被相应的赋值.
- sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
- if (sk == NULL)
- goto out;
- ....................................................................
- ///这个函数中会初始化相应的socket中的写队列,读队列以及错误队列.并将sk指针和sock连接起来.而且还将初始化相应的定时器.
- sock_init_data(sock, sk);
- ..........................................
- ///调用相应的初始化.
- if (sk->sk_prot->init) {
- ///其实也就是相对应4层协议的初始化函数,它会初始化一些协议相关的东西.
- err = sk->sk_prot->init(sk);
- if (err)
- sk_common_release(sk);
- }
- out:
- return err;
- out_rcu_unlock:
- rcu_read_unlock();
- goto out;
- }
这里举个例子,来看一下tcp_v4_init_sock的实现,也就是tcp的初始化函数.
- static int tcp_v4_init_sock(struct sock *sk)
- {
- struct inet_connection_sock *icsk = inet_csk(sk);
- struct tcp_sock *tp = tcp_sk(sk);
- skb_queue_head_init(&tp->out_of_order_queue);
- ///初始化定时器,也就是tcp的那3个定时器,write,delay以及keepalive定时器.
- tcp_init_xmit_timers(sk);
- tcp_prequeue_init(tp);
- ...................................
- ///状态赋值.初始状态.
- sk->sk_state = TCP_CLOSE;
- ..................................
- return 0;
- }
上面我们看到有两个新的结构inet_connection_sock以及tcp_sock.我们接下来就来看这两个结构.
inet_connection_sock也就是所有面向连接的协议的socket的相关信息.它的第一个域是inet_sock,因此我们可以很方便的进行转换.而tcp_sock 相当与inet_connection_sock得一个子类,保存有所有tcp相关的socket的信息.它的第一个域就是inet_connection_sock.
可以看到其实tcp_socket类似于inet_sock(前面的blog有介绍),都是保存了本层的相关的信息.
这里就不列出这两个结构了,内核中这两个结构的注释都是很详细的..
在看sock_map_fd实现之前,我们先来看内核中socket类型的inode节点的实现:
这里看到,我们只要拥有了inode节点,通过containof宏我们就可以计算出socket的地址,从而就可以得到整个socket的信息了.
- struct socket_alloc {
- struct socket socket;
- struct inode vfs_inode;
- };
而inode节点的赋值是在sock_alloc中实现的,而这个函数是在__sock_create中被调用的,也就是在init_cteate被调用之前.
- static struct socket *sock_alloc(void)
- {
- struct inode *inode;
- struct socket *sock;
- ///新建一个inode,sock_mnt就是sock_init中被安装的mount点.
- inode = new_inode(sock_mnt->mnt_sb);
- if (!inode)
- return NULL;
- ///然后组合inode和socket结构.
- sock = SOCKET_I(inode);
- ///设置inode类型.
- inode->i_mode = S_IFSOCK | S_IRWXUGO;
- inode->i_uid = current->fsuid;
- inode->i_gid = current->fsgid;
- ///将sockets_in_use(也就是当前创建的socket)加一.
- get_cpu_var(sockets_in_use)++;
- put_cpu_var(sockets_in_use);
- return sock;
- }
然后我们来看sock_map_fd的实现.我们首先要知道,socket是没有open函数的,因此要通过vfs层的调用,必须要在create的时候,映射一个file结构,从而将句柄与这个file关联起来.
- int sock_map_fd(struct socket *sock, int flags)
- {
- struct file *newfile;
- ///找到一个可用的fd,并找到一个可用的file结构并返回.
- int fd = sock_alloc_fd(&newfile, flags);
- if (likely(fd >= 0)) {
- ///初始化这个file结构.
- int err = sock_attach_fd(sock, newfile, flags);
- if (unlikely(err < 0)) {
- put_filp(newfile);
- put_unused_fd(fd);
- return err;
- }
- ///将句柄和文件指针关联起来.
- fd_install(fd, newfile);
- }
- return fd;
- }
sock_alloc_fd实现比较简单,这里就不分析了.
就来看下sock_attach_fd的实现.:
这里要注意,内核通过把socket指针赋值给file的private_data,这样就可以通过句柄,在fdtable中得到file对象,然后轻松取得socket对象.
- ///目录项的操作集合
- static struct dentry_operations sockfs_dentry_operations = {
- .d_delete = sockfs_delete_dentry,
- .d_dname = sockfs_dname,
- };
- ///文件的操作集合.这些函数最终调用的还是socket的ops域中的函数.而我们上面已经提过最终他们调用sock域的proto中的函数.
- static const struct file_operations socket_file_ops = {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .aio_read = sock_aio_read,
- .aio_write = sock_aio_write,
- .poll = sock_poll,
- .unlocked_ioctl = sock_ioctl,
- #ifdef CONFIG_COMPAT
- .compat_ioctl = compat_sock_ioctl,
- #endif
- .mmap = sock_mmap,
- .open = sock_no_open, /* special open code to disallow open via /proc */
- .release = sock_close,
- .fasync = sock_fasync,
- .sendpage = sock_sendpage,
- .splice_write = generic_splice_sendpage,
- .splice_read = sock_splice_read,
- };
- static int sock_attach_fd(struct socket *sock, struct file *file, int flags)
- {
- struct dentry *dentry;
- struct qstr name = { .name = "" };
- ///根据装载点的mnt_sb(super block)的root域来创建一个目录项.
- dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
- if (unlikely(!dentry))
- return -ENOMEM;
- ///将sockfs的目录项操作集合赋值.
- dentry->d_op = &sockfs_dentry_operations;
- /*
- * We dont want to push this dentry into global dentry hash table.
- * We pretend dentry is already hashed, by unsetting DCACHE_UNHASHED
- * This permits a working /proc/$pid/fd/XXX on sockets
- */
- dentry->d_flags &= ~DCACHE_UNHASHED;
- ///将inode和目录项关联起来.
- d_instantiate(dentry, SOCK_INODE(sock));
- sock->file = file;
- ///初始化文件对象,主要就是将socket_file_ops赋值给file结构的f_op域.
- init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE,
- &socket_file_ops);
- SOCK_INODE(sock)->i_fop = &socket_file_ops;
- file->f_flags = O_RDWR | (flags & O_NONBLOCK);
- file->f_pos = 0;
- ///将sock赋值给private_data,这样我们就能通过file轻松获得socket结构(在后面会用到).
- file->private_data = sock;
- return 0;
- }
下面就是sys_socket的流程图:
最终来总结一下.内核中,socket是作为一个伪文件系统来实现的,它在初始化时注册到内核,而每个进程的files_struct域保存了所有的句柄,包括socket的.一般的文件操作的话,内核直接调用vfs层的方法,然后会自动调用socket实现的相关方法.内核通过inode结构的imode域就可以知道当前的句柄所关联的是不是socket类型,这时遇到socket独有的操作,就通过containof方法,来计算出socket的地址,从而就可以进行相关的操作.
最后我们要注意的是,内核在调用相关操作都是直接调用socket的ops域,然后在ops域中调用相应的sock结构体中的sock_common域的skc_prot的操作集中的相对应的函数.
举个例子,假设现在我们使用tcp协议然后调用bind方法,内核会先调用sys_bind方法:
- asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
- {
- ................................................
- if (!err)
- err = sock->ops->bind(sock,
- (struct sockaddr *)
- &address, addrlen);
- ...................................................
- }
可以看到它调用的是ops域的bind方法.而这时我们的ops域是inet_stream_ops,来看它的bind方法:
- int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
- {
- ..............................................
- /* If the socket has its own bind function then use it. (RAW) */
- if (sk->sk_prot->bind) {
- err = sk->sk_prot->bind(sk, uaddr, addr_len);
- goto out;
- }
- ................................................
- }
它最终调用的是sock结构的sk_prot域(也就是sock_common的skc_prot域)的bind方法,而此时我们的skc_prot的值是tcp_prot,因此最终会调用tcp_prot的bind方法.
下面就是示意图:
PS:随便抱怨下,linux kernel的socket实现也太复杂了..不知道其他的操作系统的socket实现的怎么
先来介绍几个地址结构.
struct sockaddr 其实相当于一个基类的地址结构,其他的结构都能够直接转到sockaddr.举个例子比如当sa_family为PF_INET时,sa_data就包含了端口号和ip地址(in_addr结构).
- struct sockaddr {
- sa_family_t sa_family; /* address family, AF_xxx */
- char sa_data[14]; /* 14 bytes of protocol address */
- };
接下来就是sockaddr_in ,它表示了所有的ipv4的地址结构.可以看到他也就相当于sockaddr 的一个子类.
- struct sockaddr_in {
- sa_family_t sin_family; /* Address family */
- __be16 sin_port; /* Port number */
- struct in_addr sin_addr; /* Internet address */
- /* Pad to size of `struct sockaddr'. */
- unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
- sizeof(unsigned short int) - sizeof(struct in_addr)];
- };
这里还有一个内核比较新的地质结构sockaddr_storage,他可以容纳所有类型的套接口结构,比如ipv4,ipv6..可以看到它是强制对齐的,相比于sockaddr.
- struct __kernel_sockaddr_storage {
- unsigned short ss_family; /* address family */
- ///每个协议实现自己的地址结构.
- char __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
- /* space to achieve desired size, */
- /* _SS_MAXSIZE value minus size of ss_family */
- } __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment */
接下来看几个和bind相关的数据结构:
第一个是inet_hashinfo,它主要用来管理 tcp的bind hash bucket(在tcp的初始化函数中会将tcp_hashinfo初始化.然后在tcp_prot中会将tcp_hashinfo付给结构体h,然后相应的我们就可以通过sock中的sock_common域来存取这个值).后面我们会分析这个流程.
- struct inet_hashinfo {
- /* This is for sockets with full identity only. Sockets here will
- * always be without wildcards and will have the following invariant:
- *
- * TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
- *
- * TIME_WAIT sockets use a separate chain (twchain).
- */
- ///下面会分析这个结构.
- struct inet_ehash_bucket *ehash;
- rwlock_t *ehash_locks;
- unsigned int ehash_size;
- unsigned int ehash_locks_mask;
- /* 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.
- */
- ///表示所有的已经在使用的端口号的信息.这里bhash也就是一个hash链表,而链表的元素是inet_bind_bucket,紧接着我们会分析这个结构.
- struct inet_bind_hashbucket *bhash;
- unsigned int bhash_size;
- /* Note : 4 bytes padding on 64 bit arches */
- /* 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.
- */
- ///listening_hash表示所有的处于listen状态的socket.
- struct hlist_head listening_hash[INET_LHTABLE_SIZE];
- /* All the above members are written once at bootup and
- * never written again _or_ are predominantly read-access.
- *
- * Now align to a new cache line as all the following members
- * are often dirty.
- */
- rwlock_t lhash_lock ____cacheline_aligned;
- atomic_t lhash_users;
- wait_queue_head_t lhash_wait;
- struct kmem_cache *bind_bucket_cachep;
- };
struct inet_ehash_bucket管理所有的tcp状态在TCP_ESTABLISHED和TCP_CLOSE之间的socket.这里要注意,twchain表示处于TIME_WAIT的socket.
- struct inet_ehash_bucket {
- struct hlist_head chain;
- struct hlist_head twchain;
- };
inet_bind_bucket结构就是每个使用的端口的信息,最终会把它链接到bhash链表中.
- struct inet_bind_bucket {
- struct net *ib_net;
- ///端口号
- unsigned short port;
- ///表示这个端口是否能够被重复使用.
- signed short fastreuse;
- ///指向下一个端口的inet_bind_bucket 结构.
- struct hlist_node node;
- ///也就是使用这个端口的socket链表
- struct hlist_head owners;
- };
最后一个结构是tcp_hashinfo他在 tcp_init中被初始化,而tcp_init是在inet_init中被初始化的.然后tcp_hashinfo会被赋值给tcp_proto和sock的sk_prot域.
- struct inet_hashinfo __cacheline_aligned tcp_hashinfo = {
- .lhash_lock = __RW_LOCK_UNLOCKED(tcp_hashinfo.lhash_lock),
- .lhash_users = ATOMIC_INIT(0),
- .lhash_wait = __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.lhash_wait),
- };
然后来看bind的实现,bind对应的系统调用是sys_bind:
- asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
- {
- struct socket *sock;
- struct sockaddr_storage address;
- int err, fput_needed;
- ///通过fd查找相应的socket,如果不存在则返回错误.
- sock = sockfd_lookup_light(fd, &err, &fput_needed);
- if (sock) {
- ///用户空间和内核的地址拷贝.
- err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
- if (err >= 0) {
- err = security_socket_bind(sock,
- (struct sockaddr *)&address,
- addrlen);
- if (!err)
- ///调用inet_bind方法.
- err = sock->ops->bind(sock,
- (struct sockaddr *)
- &address, addrlen);
- }
- ///将socket对应的file结构的引用计数.
- fput_light(sock->file, fput_needed);
- }
- return err;
- }
sockfd_lookup_light主要是查找fd对应的socket
- static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
- {
- struct file *file;
- struct socket *sock;
- *err = -EBADF;
- ///通过fd得到对应的file结构
- file = fget_light(fd, fput_needed);
- if (file) {
- ///我们在sock_map_fd通过sock_attach_fd中已经把file的private域赋值为socket,因此这里就直接返回socket.
- sock = sock_from_file(file, err);
- if (sock)
- return sock;
- fput_light(file, *fput_needed);
- }
- return NULL;
- }
然后来看inet_bind的实现.
- int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
- {
- ///取得绑定地址.以及相关的socket和inet_sock.
- struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
- struct sock *sk = sock->sk;
- struct inet_sock *inet = inet_sk(sk);
- unsigned short snum;
- int chk_addr_ret;
- int err;
- /* If the socket has its own bind function then use it. (RAW) */
- 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(sock_net(sk), addr->sin_addr.s_addr);
- err = -EADDRNOTAVAIL;
- ///主要是判断绑定的地址不是本地时的一些条件判断.
- if (!sysctl_ip_nonlocal_bind &&
- !inet->freebind &&
- addr->sin_addr.s_addr != htonl(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;
- ///主要是端口号小于prot_sock(1024)必须得有root权限.如果没有则退出.capable就是用来判断权限的.
- 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);
- /* Check these errors (active socket, double bind). */
- err = -EINVAL;
- ///检测状态是否为close.如果是close状态,说明这个socket前面已经bind过了.而num只有当raw socket时才会不为0
- if (sk->sk_state != TCP_CLOSE || inet->num)
- goto out_release_sock;
- ///设置相应的地址.rcv_saddr是通过hash查找的源地址,而saddr是ip层使用的源地址(ip头的源地址).
- inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
- ///如果是多播或者广播,设置saddr.
- if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
- inet->saddr = 0; /* Use device */
- ///这里get_port用来发现我们绑定的端口,是否被允许使用.而get_port在tcp中,被实例化为inet_csk_get_port,接近着我们会分析它的实现.
- 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 = htons(inet->num);
- ///目的地址和目的端口,暂时设为0
- inet->daddr = 0;
- inet->dport = 0;
- sk_dst_reset(sk);
- err = 0;
- out_release_sock:
- release_sock(sk);
- out:
- return err;
- }
这里我先来介绍下inet_csk_get_port的流程.
当绑定的port为0时,这时也就是说需要kernel来分配一个新的port.
1 首先得到系统的port范围.
2 随机分配一个port.
3 从bhash中得到当前随机分配的端口的链表(也就是inet_bind_bucket链表).
4 遍历这个链表(链表为空的话,也说明这个port没有被使用),如果这个端口已经被使用,则将端口号加一,继续循环,直到找到当前没有被使用的port,也就是没有在bhash中存在的port.
5 新建一个inet_bind_bucket,并插入到bhash中.
当指定port时.
1 从bhash中根据hash值(port计算的)取得当前指定端口对应的inet_bind_bucket结构.
2 如果bhash中存在,则说明,这个端口已经在使用,因此需要判断这个端口是否允许被reuse.
3 如果不存在,则步骤和上面的第5部一样.
- int inet_csk_get_port(struct sock *sk, unsigned short snum)
- {
- struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
- struct inet_bind_hashbucket *head;
- struct hlist_node *node;
- struct inet_bind_bucket *tb;
- int ret;
- struct net *net = sock_net(sk);
- local_bh_disable();
- if (!snum) {
- ///端口为0,也就是需要内核来分配端口.
- int remaining, rover, low, high;
- ///得到端口范围.
- inet_get_local_port_range(&low, &high);
- remaining = (high - low) + 1;
- rover = net_random() % remaining + low;
- ///循环来得到一个当前没有使用的端口.
- do {
- ///通过端口为key,来得到相应的inet_bind_bucket
- head = &hashinfo->bhash[inet_bhashfn(net, rover,
- hashinfo->bhash_size)];
- spin_lock(&head->lock);
- inet_bind_bucket_for_each(tb, node, &head->chain)
- if (tb->ib_net == net && tb->port == rover)
- ///说明这个端口已被使用,因此需要将端口加1,重新查找.
- goto next;
- break;
- next:
- spin_unlock(&head->lock);
- ///如果端口大于最大值,则将它赋值为最小值(这是因为我们这个端口是随机值,因此有可能很多端口就被跳过了),重新查找.
- if (++rover > high)
- rover = low;
- } while (--remaining > 0);
- /* Exhausted local port range during search? It is not
- * possible for us to be holding one of the bind hash
- * locks if this test triggers, because if 'remaining'
- * drops to zero, we broke out of the do/while loop at
- * the top level, not from the 'break;' statement.
- */
- ret = 1;
- if (remaining <= 0)
- goto fail;
- ///将要分配的端口号.
- snum = rover;
- } else {
- ///指定端口号的情况.和上面的方法差不多,只不过只需要一次.
- head = &hashinfo->bhash[inet_bhashfn(net, snum,
- hashinfo->bhash_size)];
- spin_lock(&head->lock);
- inet_bind_bucket_for_each(tb, node, &head->chain)
- if (tb->ib_net == net && tb->port == snum)
- goto tb_found;
- }
- tb = NULL;
- goto tb_not_found;
- tb_found:
- ///用来处理端口号已经被使用的情况.他被使用的socket不为空的情况.
- if (!hlist_empty(&tb->owners)) {
- ///fastreuse大于0说明其他的socket允许另外的socket也使用这个端口,而reuse表示当前的端口也允许和其他的端口分享这个port.并且socket的状态必须是TCP_LISTEN,才能做这个判断.
- if (tb->fastreuse > 0 &&
- sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
- goto success;
- } else {
- ret = 1;
- ///如果出错,调用inet_csk_bind_conflict.主要是有可能一些使用这个端口的socket,有可能使用不同的ip地址.此时,我们是可以使用这个端口的.
- if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
- goto fail_unlock;
- }
- }
- tb_not_found:
- ret = 1;
- ///重新分配一个inet_bind_bucket,并链接到bhash.
- if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
- net, head, snum)) == NULL)
- goto fail_unlock;
- if (hlist_empty(&tb->owners)) {
- ///设置当前端口的fastreuse,这个域也只能是处于listen的socket才能设置.
- 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:
- ///将这个socket加到这个端口的ower中.
- if (!inet_csk(sk)->icsk_bind_hash)
- inet_bind_hash(sk, tb, snum);
- WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
- ret = 0;
- fail_unlock:
- spin_unlock(&head->lock);
- fail:
- local_bh_enable();
- return ret;
- }
在看listen的代码之前.我们也先来看相关的数据结构:
其中inet_connection_sock我们先前已经介绍过了,它包含了一个icsk_accept_queue的域,这个域是一个request_sock_queue类型,.我们就先来看这个结构:
request_sock_queue也就表示一个request_sock队列.这里我们知道,tcp中分为半连接队列(处于SYN_RECVD状态)和已完成连接队列(处于established状态).这两个一个是刚接到syn,等待三次握手完成,一个是已经完成三次握手,等待accept来读取.
这里每个syn分节到来都会新建一个request_sock结构,并将它加入到listen_sock的request_sock hash表中.然后3次握手完毕后,将它放入到request_sock_queue的rskq_accept_head和rskq_accept_tail队列中.这样当accept的时候就直接从这个队列中读取了.
- struct request_sock_queue {
- ///一个指向头,一个指向结尾.
- struct request_sock *rskq_accept_head;
- struct request_sock *rskq_accept_tail;
- rwlock_t syn_wait_lock;
- u8 rskq_defer_accept;
- /* 3 bytes hole, try to pack */
- ///相应的listen_socket结构.
- struct listen_sock *listen_opt;
- };
listen_sock 表示一个处于listening状态的socket.
- struct listen_sock {
- ///log_2 of maximal queued SYNs/REQUESTs ,这里不太理解这个域的作用.
- u8 max_qlen_log;
- /* 3 bytes hole, try to use */
- ///当前的半连接队列的长度.
- int qlen;
- ///也是指当前的半开连接队列长度,不过这个值会当重传syn/ack的时候(这里要注意是这个syn/ack第一次重传的时候才会减一)自动减一.
- int qlen_young;
- int clock_hand;
- u32 hash_rnd;
- ///这个值表示了当前的syn_backlog(半开连接队列)的最大值
- u32 nr_table_entries;
- ///半连接队列.
- struct request_sock *syn_table[0];
- };
最后来看下request_sock,它保存了tcp双方传输所必需的一些域,比如窗口大小,对端速率,对端数据包序列号等等这些值.
- struct request_sock {
- struct request_sock *dl_next; /* Must be first member! */
- ///mss值.
- u16 mss;
- u8 retrans;
- u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */
- /* The following two fields can be easily recomputed I think -AK */
- u32 window_clamp; /* window clamp at creation time */
- ///窗口大小.
- u32 rcv_wnd; /* rcv_wnd offered first time */
- u32 ts_recent;
- unsigned long expires;
- ///这个域包含了发送ack的操作集合.
- const struct request_sock_ops *rsk_ops;
- struct sock *sk;
- u32 secid;
- u32 peer_secid;
- };
listen的对应的系统调用是sys_listen,它首先通过sockfd_lookup_light查找到相应的socket,然后调用inet_listen,大体流程和bind差不多,只不过中间调用的是inet_listen罢了.
这里还有一个概念那就是backlog,在linux中,backlog的大小指的是已完成连接队列的大小.而不是和半连接队列之和.而半开连接的大小一般是和backlog差不多大小.
而半开连接队列的最大长度是根据backlog计算的,我们后面会介绍这个.
因此我们直接来看inet_listen的实现,这个函数主要是进行一些合法性判断,然后调用inet_csk_listen_start来对相关域进行处理:
- int inet_listen(struct socket *sock, int backlog)
- {
- struct sock *sk = sock->sk;
- unsigned char old_state;
- int err;
- lock_sock(sk);
- err = -EINVAL;
- ///判断状态(非连接状态)以及socket类型.
- if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
- goto out;
- old_state = sk->sk_state;
- ///状态必须为close或者listen.
- if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
- goto out;
- /* Really, if the socket is already in listen state
- * we can only allow the backlog to be adjusted.
- */
- ///非listen状态,需要我们处理.
- if (old_state != TCP_LISTEN) {
- err = inet_csk_listen_start(sk, backlog);
- if (err)
- goto out;
- }
- ///将backlog赋值给sk_max_ack_backlog,也就是完全连接队列最大值.
- sk->sk_max_ack_backlog = backlog;
- err = 0;
- out:
- release_sock(sk);
- return err;
- }
然后来看inet_csk_listen_start的实现.
它的主要工作是新分配一个listen socket,将它加入到inet_connection_sock的icsk_accept_queue域的listen_opt中.然后对当前使用端口进行判断.最终返回:
- int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
- {
- struct inet_sock *inet = inet_sk(sk);
- struct inet_connection_sock *icsk = inet_csk(sk);
- ///新分配一个listen socket.
- int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
- if (rc != 0)
- return rc;
- ///先将这两个ack_backlog赋值为0.
- sk->sk_max_ack_backlog = 0;
- sk->sk_ack_backlog = 0;
- inet_csk_delack_init(sk);
- /* 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.
- */
- ///设置状态.
- sk->sk_state = TCP_LISTEN;
- ///get_port上面已经分析过了.这里之所以还要再次判断一下端口,是为了防止多线程,也就是另一个线程在我们调用listen之前改变了这个端口的信息.
- if (!sk->sk_prot->get_port(sk, inet->num)) {
- //端口可用的情况,将端口值付给sport,并加入到inet_hashinfo(上面已经分析过)的listening_hash hash链表中.
- inet->sport = htons(inet->num);
- sk_dst_reset(sk);
- ///这里调用__inet_hash实现的.
- sk->sk_prot->hash(sk);
- return 0;
- }
- ///不可用,则返回错误.
- sk->sk_state = TCP_CLOSE;
- __reqsk_queue_destroy(&icsk->icsk_accept_queue);
- return -EADDRINUSE;
- }
最后我们来看下reqsk_queue_alloc的实现:
- ///半开连接的最大长度.
- int sysctl_max_syn_backlog = 256;
- int reqsk_queue_alloc(struct request_sock_queue *queue,
- unsigned int nr_table_entries)
- {
- size_t lopt_size = sizeof(struct listen_sock);
- struct listen_sock *lopt;
- ///在当前的nr_table_entries(也就是listen传进来的backlog)和sysctl_max_syn_backlog取一个较小的值.
- nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
- ///也就是说nr_table_entries不能小于8.
- nr_table_entries = max_t(u32, nr_table_entries, 8);
- ///其实也就是使nr_table_entries更接近于2的次幂
- nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
- ///最终所要分配的listen_sock 的大小.
- lopt_size += nr_table_entries * sizeof(struct request_sock *);
- if (lopt_size > PAGE_SIZE)
- lopt = __vmalloc(lopt_size,
- GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
- PAGE_KERNEL);
- else
- lopt = kzalloc(lopt_size, GFP_KERNEL);
- if (lopt == NULL)
- return -ENOMEM;
- ///计算max_qlen_log的值,他最小要为3,最大为对nr_table_entries求以2为低的log..
- for (lopt->max_qlen_log = 3;
- (1 << lopt->max_qlen_log) < nr_table_entries;
- lopt->max_qlen_log++);
- get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
- rwlock_init(&queue->syn_wait_lock);
- queue->rskq_accept_head = NULL;
- ///给nr_table_entries赋值.
- lopt->nr_table_entries = nr_table_entries;
- write_lock_bh(&queue->syn_wait_lock);
- ///将listen_socket赋值给queue->listen_opt
- queue->listen_opt = lopt;
- write_unlock_bh(&queue->syn_wait_lock);
- return 0;
- }
首先来看下内核如何处理3次握手的半连接队列和accept队列(其实也就是server端的三次握手的状态变换).而半连接队列和accept队列在内核如何表示,我们上次已经介绍过了,这里就不介绍了.
首先我们知道当3层的数据包到达之后会调用4层的协议handle,tcp的话就是tcp_v4_rcv.如何调用可以看我前面的blog:
而在tcp_v4_rcv中,则最终会调用tcp_v4_do_rcv来处理输入数据包.在看tcp_v4_do_rcv之前,我们先来看在tcp_v4_rcv中,内核如何通过4元组(目的,源端口和地址)来查找对应得sock对象.
在分析之前,我们要知道,当一对tcp连接3次握手完毕后,内核将会重新new一个socket,这个socket中的大部分域都是与主socket相同的.而把这个新的socket的状态设置为established,而主socket的状态依旧为listen状态.
而通过前面的blog分析,我们也知道在inet_hashinfo中将处于listening状态的socket和处于 TCP_ESTABLISHED与TCP_CLOSE之间的状态的socket是分开的,一个是ehash,一个是listening_hash.因此通 过对应的4元组查找socket也是分开在这两个hash链表中操作的.
内核是通过调用__inet_lookup来查找socket的:
- ///在tcp_v4_rcv中的代码片段.
- sk = __inet_lookup(net, &tcp_hashinfo, iph->saddr,
- th->source, iph->daddr, th->dest, inet_iif(skb));
- static inline struct sock *__inet_lookup(struct net *net,
- struct inet_hashinfo *hashinfo,
- const __be32 saddr, const __be16 sport,
- const __be32 daddr, const __be16 dport,
- const int dif)
- {
- u16 hnum = ntohs(dport);
- struct sock *sk = __inet_lookup_established(net, hashinfo,
- saddr, sport, daddr, hnum, dif);
- return sk ? : __inet_lookup_listener(net, hashinfo, daddr, hnum, dif);
- }
tcp_hashinfo我们前面也已经分析过了,包含了所有tcp所用到的hash信息,比如socket,port等等.这里的查找其实就是在tcp_hashinfo中(其实是它的域ehash或者listening_hash)查找相应的socket.
我们可以看到内核在这里进行了两次查找,首先是在established状态的socket中查找,处于established状态,说明3次握手已经完成,因此这个socket可以通过简单的4元组hash在hashinfo的ehash中查找.
而当在__inet_lookup_established中没有找到时,则将会__inet_lookup_listener中查找.也就是在处于listening状态的socket中查找(这里主要是通过daddr也就是目的地址来进行匹配).
当找到对应的socket以后就会进入数据包的处理,也就是进入tcp_v4_do_rcv函数.
- int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
- {
- struct sock *rsk;
- ..................................................
- ///如果为TCP_ESTABLISHED状态,则进入相关处理
- if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
- TCP_CHECK_TIMER(sk);
- if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
- rsk = sk;
- goto reset;
- }
- TCP_CHECK_TIMER(sk);
- return 0;
- }
- ///进行包头的合法性校验.
- if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
- goto csum_err;
- ///进入TCP_LISTEN状态.
- if (sk->sk_state == TCP_LISTEN) {
- struct sock *nsk = tcp_v4_hnd_req(sk, skb);
- if (!nsk)
- goto discard;
- if (nsk != sk) {
- if (tcp_child_process(sk, nsk, skb)) {
- rsk = nsk;
- goto reset;
- }
- return 0;
- }
- }
- TCP_CHECK_TIMER(sk);
- ///进入其他状态的处理.除了ESTABLISHED和TIME_WAIT状态.
- if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
- rsk = sk;
- goto reset;
- }
- TCP_CHECK_TIMER(sk);
- return 0;
- ......................................................................
- }
可以看到当进来之后,会通过判断socket的不同状态来进入不同的处理.这里其实就分了3种状态,TCP_ESTABLISHED,TCP_LISTEN和剩余的的状态.
我们这里先不分析TCP_ESTABLISHED.
我们先来看当第一个syn分解到达后,内核会做怎么样处理.首先它会进入tcp_v4_hnd_req函数,这个函数我们后面会处理,这里只需要 知道当为第一个syn分节时,它会返回当前socket.因此此时nsk == sk,所以我们进入tcp_rcv_state_process函数,这个函数处理除了ESTABLISHED和TIME_WAIT状态之外的所有状态.
我们这里只看他的listen状态处理,后面的话也是遇到一个状态,我们看一个状态的处理:
- int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
- struct tcphdr *th, unsigned len)
- {
- struct tcp_sock *tp = tcp_sk(sk);
- ///取得对应的inet_connection_sock .
- struct inet_connection_sock *icsk = inet_csk(sk);
- int queued = 0;
- tp->rx_opt.saw_tstamp = 0;
- switch (sk->sk_state) {
- case TCP_LISTEN:
- ///当为ack分节,则返回1,而对应内核会发送一个rst给对端.
- if (th->ack)
- return 1;
- ///如果是rst,则忽略这个分组.
- if (th->rst)
- goto discard;
- ///是syn分组,因此调用对应的虚函数conn_request,而这个函数在tcpv4中被初始化为tcp_v4_conn_request.
- if (th->syn) {
- if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
- return 1;
- kfree_skb(skb);
- return 0;
- }
- goto discard;
- ............................................................
- }
可以看到最终会调用tcp_v4_conn_request来处理syn分组,我们接下来就来看这个函数的实现.
先来看几个相关的函数,第一个是reqsk_queue_is_full,他来判断半连接队列是否已满.其实实现很简单,就是判断qlen和max_qlen_log的大小:
- static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
- {
- return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
- }
第二个是sk_acceptq_is_full,它用来判断accept队列是否已满.这个也是很简单,比较当前的队列大小sk_ack_backlog与最大的队列大小sk_max_ack_backlog.
- static inline int sk_acceptq_is_full(struct sock *sk)
- {
- return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
- }
最后一个是tcp_openreq_init,它用来新建一个inet_request_sock,我们知道每次一个syn到达后,我们都会新建一个inet_request_sock,并加入到半连接队列.
- static inline void tcp_openreq_init(struct request_sock *req,
- struct tcp_options_received *rx_opt,
- struct sk_buff *skb)
- {
- struct inet_request_sock *ireq = inet_rsk(req);
- req->rcv_wnd = 0; /* So that tcp_send_synack() knows! */
- req->cookie_ts = 0;
- tcp_rsk(req)->rcv_isn = TCP_SKB_CB(skb)->seq;
- req->mss = rx_opt->mss_clamp;
- req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;
- ireq->tstamp_ok = rx_opt->tstamp_ok;
- ireq->sack_ok = rx_opt->sack_ok;
- ireq->snd_wscale = rx_opt->snd_wscale;
- ireq->wscale_ok = rx_opt->wscale_ok;
- ireq->acked = 0;
- ireq->ecn_ok = 0;
- ireq->rmt_port = tcp_hdr(skb)->source;
- }
接下来来看tcp_v4_conn_request的实现,
- int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
- {
- struct inet_request_sock *ireq;
- struct tcp_options_received tmp_opt;
- struct request_sock *req;
- __be32 saddr = ip_hdr(skb)->saddr;
- __be32 daddr = ip_hdr(skb)->daddr;
- ///这个名字实在是无语,when具体表示什么不太理解,只是知道它是用来计算rtt的.
- __u32 isn = TCP_SKB_CB(skb)->when;
- struct dst_entry *dst = NULL;
- #ifdef CONFIG_SYN_COOKIES
- int want_cookie = 0;
- #else
- #define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
- #endif
- ///如果是广播或者多播,则丢掉这个包.
- if (skb->rtable->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
- goto drop;
- ///判断半连接队列是否已经满掉.如果满掉并且处于非timewait状态,则丢掉这个包(如果设置了SYN Cookie则会继续进行,因为SYN Cookie不需要新分配半连接队列,详细的SYN Cookie请google)
- if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
- #ifdef CONFIG_SYN_COOKIES
- if (sysctl_tcp_syncookies) {
- want_cookie = 1;
- } else
- #endif
- goto drop;
- }
- ///如果accept队列已满,并且qlen_young大于一就丢掉这个包,这里qlen_young大于一表示在syn队列中已经有足够多的(这里不包括重传的syn)请求了.
- if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
- goto drop;
- req = inet_reqsk_alloc(&tcp_request_sock_ops);
- if (!req)
- goto drop;
- ...................................................
- ///对tmp_opt进行初始化,而tcp_options_received中包含了tcp的一些选项信息(比如mss,窗口扩大因子等等)
- tcp_clear_options(&tmp_opt);
- tmp_opt.mss_clamp = 536;
- tmp_opt.user_mss = tcp_sk(sk)->rx_opt.user_mss;
- ///对对端的tcp_options_received进行解析,并对本端得tcp_options_received进行初始化.
- tcp_parse_options(skb, &tmp_opt, 0);
- .......................................................
- ///这里对新的req进行初始化.
- tcp_openreq_init(req, &tmp_opt, skb);
- ...............................................
- ///这里将tcp_options_received保存到req中.
- ireq->opt = tcp_v4_save_options(sk, skb);
- if (!want_cookie)
- TCP_ECN_create_request(req, tcp_hdr(skb));
- if (want_cookie) {
- #ifdef CONFIG_SYN_COOKIES
- syn_flood_warning(skb);
- req->cookie_ts = tmp_opt.tstamp_ok;
- #endif
- isn = cookie_v4_init_sequence(sk, skb, &req->mss);
- }else if (!isn) {
- .............................................
- ///计算当前一个合适的isn,并返回.
- isn = tcp_v4_init_sequence(skb);
- }
- ///赋值发送给对端的isn
- tcp_rsk(req)->snt_isn = isn;
- ///发送syn和ack(如果设置了want_cookie则不会将这个req链接到半连接队列中.
- if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
- goto drop_and_free;
- ///将这个req链接到半连接队列中.
- inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
- return 0;
- drop_and_release:
- dst_release(dst);
- drop_and_free:
- reqsk_free(req);
- drop:
- return 0;
- }
而tcp_v4_hnd_req的主要工作是在半连接队列中看是否存在当前的socket,如果存在则说明这个有可能是最终的ack包,因此将会 做一系列的合法性校验(比如重传,rst,syn等等),最终确定这个是ack后会调用对应的新建socket的虚函数syn_recv_sock.
- static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
- {
- struct tcphdr *th = tcp_hdr(skb);
- const struct iphdr *iph = ip_hdr(skb);
- struct sock *nsk;
- struct request_sock **prev;
- ///通过socket,查找对应request_sock
- struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
- iph->saddr, iph->daddr);
- if (req)
- ///如果存在则进入req的相关处理.
- return tcp_check_req(sk, skb, req, prev);
- ///不存在,则通过inet_lookup_established查找.这是因为有可能当我们进入这个函数之前,socket的状态被改变了,也就是这个socket的状态已经不是listen了.
- nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr,
- th->source, iph->daddr, th->dest, inet_iif(skb));
- if (nsk) {
- if (nsk->sk_state != TCP_TIME_WAIT) {
- ///非tw状态返回新的socket.
- bh_lock_sock(nsk);
- return nsk;
- }
- ///如果是timewait状态则返回空.
- inet_twsk_put(inet_twsk(nsk));
- return NULL;
- }
- #ifdef CONFIG_SYN_COOKIES
- if (!th->rst && !th->syn && th->ack)
- sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
- #endif
- return sk;
- }
tcp_check_req最主要工作就是调用虚函数,新建一个socket,并返回.
先来看几个相关的函数,第一个是inet_csk_reqsk_queue_unlink,它主要用来从半连接队列unlink掉一个元素.:
- static inline void inet_csk_reqsk_queue_unlink(struct sock *sk,
- struct request_sock *req,
- struct request_sock **prev)
- {
- reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);
- }
- static inline void reqsk_queue_unlink(struct request_sock_queue *queue,
- struct request_sock *req,
- struct request_sock **prev_req)
- {
- write_lock(&queue->syn_wait_lock);
- ///处理链表.
- *prev_req = req->dl_next;
- write_unlock(&queue->syn_wait_lock);
- }
第二个是inet_csk_reqsk_queue_removed,它主要用来修改对应的qlen和qlen_young的值.
- static inline void inet_csk_reqsk_queue_removed(struct sock *sk,
- struct request_sock *req)
- {
- if (reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req) == 0)
- inet_csk_delete_keepalive_timer(sk);
- }
- static inline int reqsk_queue_removed(struct request_sock_queue *queue,
- struct request_sock *req)
- {
- struct listen_sock *lopt = queue->listen_opt;
- ///如果重传数为0则说明没有重传过,因此qlen_young跟着也减一.
- if (req->retrans == 0)
- --lopt->qlen_young;
- return --lopt->qlen;
- }
最后是inet_csk_reqsk_queue_add,它用来把新的req加入到accept队列中.
- static inline void inet_csk_reqsk_queue_add(struct sock *sk,
- struct request_sock *req,
- struct sock *child)
- {
- reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);
- }
- static inline void reqsk_queue_add(struct request_sock_queue *queue,
- struct request_sock *req,
- struct sock *parent,
- struct sock *child)
- {
- req->sk = child;
- sk_acceptq_added(parent);
- ///可以看到刚好就是request_sock_queue的rskq_accept_head与rskq_accept_tail保存accept队列.
- if (queue->rskq_accept_head == NULL)
- queue->rskq_accept_head = req;
- else
- queue->rskq_accept_tail->dl_next = req;
- queue->rskq_accept_tail = req;
- req->dl_next = NULL;
- }
然后再来看tcp_check_req的实现.
- struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
- struct request_sock *req,
- struct request_sock **prev)
- {
- const struct tcphdr *th = tcp_hdr(skb);
- __be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);
- int paws_reject = 0;
- struct tcp_options_received tmp_opt;
- struct sock *child;
- tmp_opt.saw_tstamp = 0;
- ......................................
- ///如果只有rst和syn域则发送一个rst给对端.
- if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {
- TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
- goto embryonic_reset;
- }
- ///如果是重传的syn,则重新发送syn和ack分组.
- if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn &&
- flg == TCP_FLAG_SYN &&
- !paws_reject) {
- req->rsk_ops->rtx_syn_ack(sk, req);
- return NULL;
- }
- ..........................................
- ///确定有设置ack分节.
- if (!(flg & TCP_FLAG_ACK))
- return NULL;
- ///这里主要处理TCP_DEFER_ACCEPT被设置的情况,如果它被设置,则丢掉这个包.(这是因为TCP_DEFER_ACCEPT会等待数据真正发过来才处理的,而不是最后一个ack发过来就处理)
- if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&
- TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {
- inet_rsk(req)->acked = 1;
- return NULL;
- }
- ///可以创建一个新的socket了.返回一个包含新创建的socket的request结构.
- child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
- if (child == NULL)
- goto listen_overflow;
- ..................................
- #endif
- ///创建成功,则在request_sock_queue的listen_opt中unlink掉这个req.也就是从半连接队列中删除这个req.
- inet_csk_reqsk_queue_unlink(sk, req, prev);
- ///修改对应的 qlen和qlen_young的值.
- inet_csk_reqsk_queue_removed(sk, req);
- ///最后加入到accept队列中.这里注意最终是将新的socket赋值给对应的req.
- inet_csk_reqsk_queue_add(sk, req, child);
- return child;
- listen_overflow:
- if (!sysctl_tcp_abort_on_overflow) {
- inet_rsk(req)->acked = 1;
- return NULL;
- }
- embryonic_reset:
- NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
- if (!(flg & TCP_FLAG_RST))
- req->rsk_ops->send_reset(sk, skb);
- inet_csk_reqsk_queue_drop(sk, req, prev);
- return NULL;
- }
最后我们来看内核如何创建一个新的socket,tcp 协议使用tcp_v4_syn_recv_sock来实现,它做的其实很简单就是新建一个socket,并且设置状态为TCP_SYN_RECV(在 inet_csk_clone中),父socket继续处于listen状态,然后对新的socket进行一些赋值,然后对一些定时器进行初始化.这里定 时器我们全部都略过了,以后会专门来分析tcp中的定时器.
最后从tcp_v4_hnd_req中返回,判断是否与父socket相等,然后调用tcp_child_process函数:
这个函数主要是完成最终的三次握手,将子socket设置为TCP_ESTABLISHED然后根据条件唤醒被accept阻塞的主socket:
- int tcp_child_process(struct sock *parent, struct sock *child,
- struct sk_buff *skb)
- {
- int ret = 0;
- int state = child->sk_state;
- if (!sock_owned_by_user(child)) {
- ///完成最终的三次握手.
- ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),
- skb->len);
- /* Wakeup parent, send SIGIO */
- if (state == TCP_SYN_RECV && child->sk_state != state)
- ///唤醒阻塞的主socket.
- parent->sk_data_ready(parent, 0);
- } else {
- /* Alas, it is possible again, because we do lookup
- * in main socket hash table and lock on listening
- * socket does not protect us more.
- */
- sk_add_backlog(child, skb);
- }
- bh_unlock_sock(child);
- sock_put(child);
- return ret;
- }
最后来分析下在tcp_rcv_state_process中的处理当前的TCP_SYN_RECV状态,它主要是为将要到来的数据传输做一些准备,设置一些相关域.:
- case TCP_SYN_RECV:
- if (acceptable) {
- tp->copied_seq = tp->rcv_nxt;
- smp_mb();
- ///设置状态为TCP_ESTABLISHED.
- tcp_set_state(sk, TCP_ESTABLISHED);
- sk->sk_state_change(sk);
- ///这里的wake应该是针对epoll这类的
- if (sk->sk_socket)
- sk_wake_async(sk,
- SOCK_WAKE_IO, POLL_OUT);
- ///设置期望接收的isn号,也就是第一个字节的序列和窗口大小.
- tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
- tp->snd_wnd = ntohs(th->window) <<
- tp->rx_opt.snd_wscale;
- tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,
- TCP_SKB_CB(skb)->seq);
- .........................................................................
- break;