linux内核中socket的创建过程源码分析(详细分析)

1三个相关数据结构.

关于socket的创建,首先需要分析socket这个结构体,这是整个的核心。

104 struct socket {

105         socket_state            state;

106 

107         kmemcheck_bitfield_begin(type);

108         short                   type;

109         kmemcheck_bitfield_end(type);

110 

111         unsigned long           flags;

112 

113         struct socket_wq __rcu  *wq;

114 

115         struct file             *file;

116         struct sock             *sk;

117         const struct proto_ops  *ops;

118 }

其中,state是socket的状态,比如CONNECTED,type是类型,比如TCP下使用的流式套接字SOCK_STREAM,flags是标志位,负责一些特殊的设置,比如SOCK_ASYNC_NOSPACE,ops则是采用了和超级块设备操作表一样的逻辑,专门设置了一个数据结构来记录其允许的操作。Sk是非常重要的,也是非常大的,负责记录协议相关内容。这样的设置使得socket具有很好的协议无关性,可以通用。file是与socket相关的指针列表,wq是等待队列。

还有两个结构体sk_buff和tcp_sock,其中sk_buff与每一个数据包相关,负责描述每一个数据包的信息,而tcp_sock则是tcp相关的结构体

2.初始化并分配socket结构

Socket()本质上是一个glibc中的函数,执行实际上是是调用sys_socketcall()系统调用。sys_socketcall()是几乎所有socket相关函数的入口,即是说,bind,connect等等函数都需要sys_socketcall()作为入口。该系统调用代码如下:

2435 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

2436 {

2437         unsigned long a[6];

2438         unsigned long a0, a1;

2439         int err;

2440         unsigned int len;

2441 

2442         if (call < 1 || call > SYS_SENDMMSG)

2443                 return -EINVAL;

2444 

2445         len = nargs[call];

2446         if (len > sizeof(a))

2447                 return -EINVAL;

2448 

2449         /* copy_from_user should be SMP safe. */

2450         if (copy_from_user(a, args, len))

2451                 return -EFAULT;

2452 

2453         audit_socketcall(nargs[call] / sizeof(unsigned long), a);

2454 

2455         a0 = a[0];

2456         a1 = a[1];

2457 

2458         switch (call) {

2459         case SYS_SOCKET:

2460                 err = sys_socket(a0, a1, a[2]);

2461                 break;

2462         case SYS_BIND:

2463                 err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

2464                 break;

.......

2531         default:

2532                 err = -EINVAL;

2533                 break;

2534         }

2535         return err;

2536 }

省略号省略掉的是各种网络编程用得到的函数,全部都在这个系统调用中,再次证明前文所说,socketcall是系统所有socket相关函数打大门。

而对于创建socket,自然会在switch中调用到sys_socket()系统调用,而这个函数仅仅是调用sock_create()来创建socket和sock_map_fd()来与文件系统进行关联。其中sock_create()调用__sock_create().接下来我尽力地来分析一下源代码(直接在代码中注释):

1257 int __sock_create(struct net *net, int family, int type, int protocol,

1258                          struct socket **res, int kern)

1259 {

1260         int err;

1261         struct socket *sock;

1262         const struct net_proto_family *pf;

/*检查传入的协议族参数是否正确*/

1267         if (family < 0 || family >= NPROTO)

1268                 return -EAFNOSUPPORT;

/*检查传入的类型参数是否正确*/

1269         if (type < 0 || type >= SOCK_MAX)

1270                 return -EINVAL;

/*检查兼容性。在使用中,family是PF还是AF没什么区别,但事实上二者PF是POSIX标准,而AF是BSD标准,按照我的理解,现在大部分使用BSD标准,因此,如果传入参数是使用的PF,那么在内核打印消息,并把family设置成PF*/

1277         if (family == PF_INET && type == SOCK_PACKET) {

1278                 static int warned;

1279                 if (!warned) {

1280                         warned = 1;

1281                         printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",

1282                                current->comm);

1283                 }

1284                 family = PF_PACKET;

1285         }

1286 /*这里调用security_socket_create函数,该函数是调用security_ops->socket_create(family, type, protocol, kern),而security_ops是一个security_operations的结构体,此处再次使用了linux常用的手段,即提供一个结构体数据结构来描述所有的操作,该结构体异常复杂,暂时还没能力理解,只知道该结构体是对应着linux系统下的LMS安全框架的。这里其实是个空函数,实际上是进行一系列的安全检查,然后如果成功则返回0*/

1287         err = security_socket_create(family, type, protocol, kern);

1288         if (err)

1289                 return err;

/*此处开始需要再分析多个函数,不再以注释的方式进行分析,在源代码的后面一一分析*/

1296         sock = sock_alloc();

1297         if (!sock) {

1298                 net_warn_ratelimited("socket: no more sockets\n");

1299                 return -ENFILE; /* Not exactly a match, but its the

1300                                    closest posix thing */

1301         }

1302 

1303         sock->type = type;

1304 

1305 #ifdef CONFIG_MODULES

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

1314 #endif

1315 

1316         rcu_read_lock();

1317         pf = rcu_dereference(net_families[family]);

1318         err = -EAFNOSUPPORT;

1319         if (!pf)

1320                 goto out_release;

1321 

1326         if (!try_module_get(pf->owner))

1327                 goto out_release;

1328 

1330         rcu_read_unlock();

1331 

1332         err = pf->create(net, sock, protocol, kern);

1333         if (err < 0)

1334                 goto out_module_put;

1335 

1340         if (!try_module_get(sock->ops->owner))

1341                 goto out_module_busy;

1342 

1347         module_put(pf->owner);

1348         err = security_socket_post_create(sock, family, type, protocol, kern);

1349         if (err)

1350                 goto out_sock_release;

1351         *res = sock;

1352 

1353         return 0;

1354 

1355 out_module_busy:

1356         err = -EAFNOSUPPORT;

1357 out_module_put:

1358         sock->ops = NULL;

1359         module_put(pf->owner);

1360 out_sock_release:

1361         sock_release(sock);

1362         return err;

1363 

1364 out_release:

1365         rcu_read_unlock();

1366         goto out_sock_release;

1367 }

如注释中说的,接下来分析sock = sock_alloc(),首先看sock_alloc()的源代码:

530 static struct socket *sock_alloc(void)

531 {

532         struct inode *inode;

533         struct socket *sock;

534 

535         inode = new_inode_pseudo(sock_mnt->mnt_sb);

536         if (!inode)

537                 return NULL;

538 

539         sock = SOCKET_I(inode);

540 

541         kmemcheck_annotate_bitfield(sock, type);

542         inode->i_ino = get_next_ino();

543         inode->i_mode = S_IFSOCK | S_IRWXUGO;

544         inode->i_uid = current_fsuid();

545         inode->i_gid = current_fsgid();

546         inode->i_op = &sockfs_inode_ops;

547 

548         this_cpu_add(sockets_in_use, 1);

549         return sock;

550 }

借助LXR上的注释我们进行分析。该函数式分配并初始化一个socket和一个inode,二者是捆绑在一起的,如果成功则返回socket,如果inode创建出问题,则返回NULL。此处有点复杂,耦合性有点强,慢慢来分析。首先分析SOCKET_I(),该函数的目的是取得socket的结构体指针,具体分析如下:

1322 static inline struct socket *SOCKET_I(struct inode *inode)

1323 {

1324         return &container_of(inode, struct socket_alloc, vfs_inode)->socket;

1325 }

这里使用了宏container_of,我们继续分析container_of宏:

718 #define container_of(ptr, type, member) ({                      \

719         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

720         (type *)( (char *)__mptr - offsetof(type,member) );})

首先分析传入参数,ptr是指向返回对象的指针,有点绕,但前文说过,inode和socket是绑在一起的,此处其实就是inode指向socket的意思。然后是type,在SOCKET_I中传入的事socket_alloc结构体,该结构体如下:

1317 struct socket_alloc {

1318         struct socket socket;

1319         struct inode vfs_inode;

1320 };只包含了socket结构体和在虚拟文件系统中的节点结构体,无甚复杂。宏container_of中还包含了另外一个宏offsetof:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)。

由此分析container_of的内容。首先是offsetof,是假设TYPE(即struct sock_alloc)的地址在0的时候其中MEMBER的地址,即是在计算MEMBER在结构体中的偏移地址,所以这个宏取名为offsetof也不为怪了。回过头看container_of,这个宏分两部分:

第一部分typeof( ((type *)0)->member ) *__mptr = (ptr),通过typeof定义一个struct sock_alloc->vfs_inode的指针__mptr,然后赋值为ptr的值,即是说现在vfs_inode的地址就是ptr(即inode)的地址。

第二部分 (type *)( (char *)__mptr - offsetof(type,member) ),用vfs_inode的地址去减去它在结构体中的偏移地址,自然得到结构体的首地址,又因为该结构体的第一个成员是struct socket socket,所以自然返回的是socket的地址,再通过(type *)进行强制类型转换,所以SOCKET_I就得到了新的socket的指针了。

这里的两个宏有点复杂,但实在是功能强大,值得学习。

这里有一个值得注意的地方,之前分析了这么久的struct sock_alloc,但我们并没有对这个结构体进行初始化。所以在new_inode_pseudo(sock_mnt->mnt_sb)中必然对这个结构体有所交代。该函数会调用alloc_inode(sb)系统调用,sb是一个超级块结构。

该系统调用的源代码显示如果传进去的超级快表操作是alloc_inode的话,则执行执行之。可以看到,传进去的参数是sock_mnt->mnt_sb,再查找该参数发现这个参数是在初始化sock_init中进行kern_mount时赋值的,恰好使得传进去的参数是alloc_inode,所以,接下来执行超级快操作表中的alloc_inode,然后调用sock_alloc_inode系统调用。

该系统调用先是ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);进行slab分配,尔后的代码也是进行inode的操作和slab的分配,到此为止,暂时不再继续深入,只是注意到经过这一系列地分配之后,socket处于未连接状态,即socket.state = SS_UNCONNECTED。

至此回到__sock_create()继续分析。接下来是一次条件编译。#ifdef CONFIG_MODULES,这个选项是用于linux的模块编写的,如果在模块编写时用上了CONFIG_MODULES,是会在makemenuconfig中出现该模块选项的。该处如果有CONFIG_MODULES但是却没有找到对应的下属选项则会装载一个default的模块。

接下去是:

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

这两句检查对应协议族的操作表是否已经安装,如果没有安装则使用request_module进行安装。现在都是在TCP/IP协议下进行分析,所以family是AF_INET,也就是2,所以实际检查的是全局变量net_families[2]。这个全局变量是在系统初始化时由net/ipv4/af_inet.c文件进行安装,具体代码是:fs_initcall(inet_init);而fs_initcall是个宏,具体实现是:

204 #define fs_initcall(fn)                 __define_initcall(fn, 5)

即是把inet_init装载在init_call中,所以在系统启动时自然会初始化。

1三个相关数据结构.

关于socket的创建,首先需要分析socket这个结构体,这是整个的核心。

104 struct socket {

105         socket_state            state;

106 

107         kmemcheck_bitfield_begin(type);

108         short                   type;

109         kmemcheck_bitfield_end(type);

110 

111         unsigned long           flags;

112 

113         struct socket_wq __rcu  *wq;

114 

115         struct file             *file;

116         struct sock             *sk;

117         const struct proto_ops  *ops;

118 }

其中,state是socket的状态,比如CONNECTED,type是类型,比如TCP下使用的流式套接字SOCK_STREAM,flags是标志位,负责一些特殊的设置,比如SOCK_ASYNC_NOSPACE,ops则是采用了和超级块设备操作表一样的逻辑,专门设置了一个数据结构来记录其允许的操作。Sk是非常重要的,也是非常大的,负责记录协议相关内容。这样的设置使得socket具有很好的协议无关性,可以通用。file是与socket相关的指针列表,wq是等待队列。

还有两个结构体sk_buff和tcp_sock,其中sk_buff与每一个数据包相关,负责描述每一个数据包的信息,而tcp_sock则是tcp相关的结构体

3.初始化并分配socket结构

Socket()本质上是一个glibc中的函数,执行实际上是是调用sys_socketcall()系统调用。sys_socketcall()是几乎所有socket相关函数的入口,即是说,bind,connect等等函数都需要sys_socketcall()作为入口。该系统调用代码如下:

2435 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

2436 {

2437         unsigned long a[6];

2438         unsigned long a0, a1;

2439         int err;

2440         unsigned int len;

2441 

2442         if (call < 1 || call > SYS_SENDMMSG)

2443                 return -EINVAL;

2444 

2445         len = nargs[call];

2446         if (len > sizeof(a))

2447                 return -EINVAL;

2448 

2449         /* copy_from_user should be SMP safe. */

2450         if (copy_from_user(a, args, len))

2451                 return -EFAULT;

2452 

2453         audit_socketcall(nargs[call] / sizeof(unsigned long), a);

2454 

2455         a0 = a[0];

2456         a1 = a[1];

2457 

2458         switch (call) {

2459         case SYS_SOCKET:

2460                 err = sys_socket(a0, a1, a[2]);

2461                 break;

2462         case SYS_BIND:

2463                 err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

2464                 break;

.......

2531         default:

2532                 err = -EINVAL;

2533                 break;

2534         }

2535         return err;

2536 }

省略号省略掉的是各种网络编程用得到的函数,全部都在这个系统调用中,再次证明前文所说,socketcall是系统所有socket相关函数打大门。

而对于创建socket,自然会在switch中调用到sys_socket()系统调用,而这个函数仅仅是调用sock_create()来创建socket和sock_map_fd()来与文件系统进行关联。其中sock_create()调用__sock_create().接下来我尽力地来分析一下源代码(直接在代码中注释):

1257 int __sock_create(struct net *net, int family, int type, int protocol,

1258                          struct socket **res, int kern)

1259 {

1260         int err;

1261         struct socket *sock;

1262         const struct net_proto_family *pf;

/*检查传入的协议族参数是否正确*/

1267         if (family < 0 || family >= NPROTO)

1268                 return -EAFNOSUPPORT;

/*检查传入的类型参数是否正确*/

1269         if (type < 0 || type >= SOCK_MAX)

1270                 return -EINVAL;

/*检查兼容性。在使用中,family是PF还是AF没什么区别,但事实上二者PF是POSIX标准,而AF是BSD标准,按照我的理解,现在大部分使用BSD标准,因此,如果传入参数是使用的PF,那么在内核打印消息,并把family设置成PF*/

1277         if (family == PF_INET && type == SOCK_PACKET) {

1278                 static int warned;

1279                 if (!warned) {

1280                         warned = 1;

1281                         printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",

1282                                current->comm);

1283                 }

1284                 family = PF_PACKET;

1285         }

1286 /*这里调用security_socket_create函数,该函数是调用security_ops->socket_create(family, type, protocol, kern),而security_ops是一个security_operations的结构体,此处再次使用了linux常用的手段,即提供一个结构体数据结构来描述所有的操作,该结构体异常复杂,暂时还没能力理解,只知道该结构体是对应着linux系统下的LMS安全框架的。这里其实是个空函数,实际上是进行一系列的安全检查,然后如果成功则返回0*/

1287         err = security_socket_create(family, type, protocol, kern);

1288         if (err)

1289                 return err;

/*此处开始需要再分析多个函数,不再以注释的方式进行分析,在源代码的后面一一分析*/

1296         sock = sock_alloc();

1297         if (!sock) {

1298                 net_warn_ratelimited("socket: no more sockets\n");

1299                 return -ENFILE; /* Not exactly a match, but its the

1300                                    closest posix thing */

1301         }

1302 

1303         sock->type = type;

1304 

1305 #ifdef CONFIG_MODULES

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

1314 #endif

1315 

1316         rcu_read_lock();

1317         pf = rcu_dereference(net_families[family]);

1318         err = -EAFNOSUPPORT;

1319         if (!pf)

1320                 goto out_release;

1321 

1326         if (!try_module_get(pf->owner))

1327                 goto out_release;

1328 

1330         rcu_read_unlock();

1331 

1332         err = pf->create(net, sock, protocol, kern);

1333         if (err < 0)

1334                 goto out_module_put;

1335 

1340         if (!try_module_get(sock->ops->owner))

1341                 goto out_module_busy;

1342 

1347         module_put(pf->owner);

1348         err = security_socket_post_create(sock, family, type, protocol, kern);

1349         if (err)

1350                 goto out_sock_release;

1351         *res = sock;

1352 

1353         return 0;

1354 

1355 out_module_busy:

1356         err = -EAFNOSUPPORT;

1357 out_module_put:

1358         sock->ops = NULL;

1359         module_put(pf->owner);

1360 out_sock_release:

1361         sock_release(sock);

1362         return err;

1363 

1364 out_release:

1365         rcu_read_unlock();

1366         goto out_sock_release;

1367 }

如注释中说的,接下来分析sock = sock_alloc(),首先看sock_alloc()的源代码:

530 static struct socket *sock_alloc(void)

531 {

532         struct inode *inode;

533         struct socket *sock;

534 

535         inode = new_inode_pseudo(sock_mnt->mnt_sb);

536         if (!inode)

537                 return NULL;

538 

539         sock = SOCKET_I(inode);

540 

541         kmemcheck_annotate_bitfield(sock, type);

542         inode->i_ino = get_next_ino();

543         inode->i_mode = S_IFSOCK | S_IRWXUGO;

544         inode->i_uid = current_fsuid();

545         inode->i_gid = current_fsgid();

546         inode->i_op = &sockfs_inode_ops;

547 

548         this_cpu_add(sockets_in_use, 1);

549         return sock;

550 }

借助LXR上的注释我们进行分析。该函数式分配并初始化一个socket和一个inode,二者是捆绑在一起的,如果成功则返回socket,如果inode创建出问题,则返回NULL。此处有点复杂,耦合性有点强,慢慢来分析。首先分析SOCKET_I(),该函数的目的是取得socket的结构体指针,具体分析如下:

1322 static inline struct socket *SOCKET_I(struct inode *inode)

1323 {

1324         return &container_of(inode, struct socket_alloc, vfs_inode)->socket;

1325 }

这里使用了宏container_of,我们继续分析container_of宏:

718 #define container_of(ptr, type, member) ({                      \

719         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

720         (type *)( (char *)__mptr - offsetof(type,member) );})

首先分析传入参数,ptr是指向返回对象的指针,有点绕,但前文说过,inode和socket是绑在一起的,此处其实就是inode指向socket的意思。然后是type,在SOCKET_I中传入的事socket_alloc结构体,该结构体如下:

1317 struct socket_alloc {

1318         struct socket socket;

1319         struct inode vfs_inode;

1320 };只包含了socket结构体和在虚拟文件系统中的节点结构体,无甚复杂。宏container_of中还包含了另外一个宏offsetof:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)。

由此分析container_of的内容。首先是offsetof,是假设TYPE(即struct sock_alloc)的地址在0的时候其中MEMBER的地址,即是在计算MEMBER在结构体中的偏移地址,所以这个宏取名为offsetof也不为怪了。回过头看container_of,这个宏分两部分:

第一部分typeof( ((type *)0)->member ) *__mptr = (ptr),通过typeof定义一个struct sock_alloc->vfs_inode的指针__mptr,然后赋值为ptr的值,即是说现在vfs_inode的地址就是ptr(即inode)的地址。

第二部分 (type *)( (char *)__mptr - offsetof(type,member) ),用vfs_inode的地址去减去它在结构体中的偏移地址,自然得到结构体的首地址,又因为该结构体的第一个成员是struct socket socket,所以自然返回的是socket的地址,再通过(type *)进行强制类型转换,所以SOCKET_I就得到了新的socket的指针了。

这里的两个宏有点复杂,但实在是功能强大,值得学习。

这里有一个值得注意的地方,之前分析了这么久的struct sock_alloc,但我们并没有对这个结构体进行初始化。所以在new_inode_pseudo(sock_mnt->mnt_sb)中必然对这个结构体有所交代。该函数会调用alloc_inode(sb)系统调用,sb是一个超级块结构。

该系统调用的源代码显示如果传进去的超级快表操作是alloc_inode的话,则执行执行之。可以看到,传进去的参数是sock_mnt->mnt_sb,再查找该参数发现这个参数是在初始化sock_init中进行kern_mount时赋值的,恰好使得传进去的参数是alloc_inode,所以,接下来执行超级快操作表中的alloc_inode,然后调用sock_alloc_inode系统调用。

该系统调用先是ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);进行slab分配,尔后的代码也是进行inode的操作和slab的分配,到此为止,暂时不再继续深入,只是注意到经过这一系列地分配之后,socket处于未连接状态,即socket.state = SS_UNCONNECTED。

至此回到__sock_create()继续分析。接下来是一次条件编译。#ifdef CONFIG_MODULES,这个选项是用于linux的模块编写的,如果在模块编写时用上了CONFIG_MODULES,是会在makemenuconfig中出现该模块选项的。该处如果有CONFIG_MODULES但是却没有找到对应的下属选项则会装载一个default的模块。

接下去是:

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

这两句检查对应协议族的操作表是否已经安装,如果没有安装则使用request_module进行安装。现在都是在TCP/IP协议下进行分析,所以family是AF_INET,也就是2,所以实际检查的是全局变量net_families[2]。这个全局变量是在系统初始化时由net/ipv4/af_inet.c文件进行安装,具体代码是:fs_initcall(inet_init);而fs_initcall是个宏,具体实现是:

204 #define fs_initcall(fn)                 __define_initcall(fn, 5)

即是把inet_init装载在init_call中,所以在系统启动时自然会初始化。下面不计细节地分析inet_init:

1678 static int __init inet_init(void)

1679 {

...............

/*把各种proto注册到全局链表中去*/

1690         rc = proto_register(&tcp_prot, 1);

1691         if (rc)

1692                 goto out_free_reserved_ports;

1693 

1694         rc = proto_register(&udp_prot, 1);

1695         if (rc)

1696                 goto out_unregister_tcp_proto;

1697 

1698         rc = proto_register(&raw_prot, 1);

1699         if (rc)

1700                 goto out_unregister_udp_proto;

.......................

/*注册协议族操作表*/

1710         (void)sock_register(&inet_family_ops);

.....................

/*把各个协议对应的基础原型(base protocol)加到对应的数据结构中*/

1720         if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)

1721                 pr_crit("%s: Cannot add ICMP protocol\n", __func__);

1722         if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)

1723                 pr_crit("%s: Cannot add UDP protocol\n", __func__);

1724         if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)

1725                 pr_crit("%s: Cannot add TCP protocol\n", __func__);

..................

1731         /* Register the socket-side information for inet_create. */

1732         for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)

1733                 INIT_LIST_HEAD(r);

/*把inetsw_array[]注册进基础原型(base protocol)的数组链表中*/

1735         for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)

1736                 inet_register_protosw(q);

..............

1802 }

至此回到__socket_create()的分析。pf = rcu_dereference(net_families[family]);是在RCU锁的保护下取得指定处的内容。if (!try_module_get(pf->owner))是模块检查。因为之后要调用->create,这个恰好有可能存在于某个可装载的模块中,所以先检查是否在模块中,不在的话继续执行下去。

然后是err = pf->create(net, sock, protocol, kern);这里调用pf中的create成员,由前面的inet_init()中的sock_register可知,把inet_family_ops装载进去,而这张表的create成员(也可以叫行为)挂入的是inet_create(),下面分析inet_create()。这个函数有些庞大,一部分一部分地看:

275 static int inet_create(struct net *net, struct socket *sock, int protocol,

276                        int kern)

277 {

278         struct sock *sk;

279         struct inet_protosw *answer;

280         struct inet_sock *inet;

281         struct proto *answer_prot;

282         unsigned char answer_flags;

283         char answer_no_check;

284         int try_loading_module = 0;

285         int err;

/*由inet_ehash_secret的值看是否有加密字符串,若没有则检查协议类型(只有TCP肯能有加密字符串),如果socket类型不是原始套接字或者数据报型的话,则创建一个加密字符串*/

287         if (unlikely(!inet_ehash_secret))

288                 if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)

289                         build_ehash_secret();

/*把socket的状态设置成未联通*/

291         sock->state = SS_UNCONNECTED;

 

 

接下来涉及到inet_protosw结构体,该结构体是用于IP协议对应socket接口,分析如下:

  struct inet_protosw {

         struct list_head list;

        unsigned short   type;     /* 对应socket的类型)*/.        unsigned short   protocol; /* IP协议编码  */

          struct proto     *prot;    /*对应的协议结构体的指针*/

            const struct proto_ops *ops;  /*对应协议的操作表*/

            char             no_check;   /* 是否计算校验和 */

            unsigned char    flags;      /* 标志位  */

 };

 

接下来继续分析inet_create():

/* 遍历寻找请求的协议类型 */

294 lookup_protocol:

295         err = -ESOCKTNOSUPPORT;

296         rcu_read_lock();

/* 遍历inetsw[]数组对应请求类型的链表元素,此处需要看一下inetsw[]的定义。这个数组由三个结构变量组成。第一种用于TCP数据流协议,标识码为IPPROTO_TCP,第二种用于UDP数据包协议,协议标识码为IPPROTO_UDP,第三种为原始套接字使用,可以是用户自己的协议,标识码IPPROTO_IP是虚拟IP协议类型。这个数组是在inet_init()中由inet_register_protosw来注册的,该函数的主要步骤为1.检查参数结构体inet_protosw是否越界2.通过type查找匹配的队列3.插入到队列中。

然后看list_for_each_rcu,这个函数在数组inetsw[]中根据sock->type(一般程序指定为SOCK_STREAM即TCP协议类型)找到协议类型所在队列并使得answer指向了TCP协议的inet_protosw结构。为后面的判断做好铺垫。

*/

297         list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

298 

299                 err = 0;

/*由于在服务器程序中一般引用listenfd = socket(AF_INET,SOCK_STREAM,0)的代码,所以此处的protocol=0,而answer->protocol由之前的list_for_each_rcu可知是TCP的,而IPPROTO_IP是属于虚拟IP类型,与原始套接字相关。所以此处301-313的代码负责选择出合适的协议*/

301                 if (protocol == answer->protocol) {

302                         if (protocol != IPPROTO_IP)

303                                 break;

304                 } else {

306                         if (IPPROTO_IP == protocol) {

307                                 protocol = answer->protocol;

308                                 break;

309                         }

310                         if (IPPROTO_IP == answer->protocol)

311                                 break;

312                 }

313                 err = -EPROTONOSUPPORT;

314         }

/*此处根据err的值和已经加载了的模块数量动态的安装本次请求需要的协议类型的结构,但看到了unlikely,再分析前面的代码发现err很大可能(最起码在我们一般性的TCP/IP编程中)此时还是等于0的,即是说,由于TCP协议的结构已经安装,不必再安装,直接进入下一步*/

316         if (unlikely(err)) {

317                 if (try_loading_module < 2) {

318                         rcu_read_unlock();

321 *(net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)

323                         if (++try_loading_module == 1)

324                                  request_module("net-pf-%d-proto-%d-type-%d",

325                                                PF_INET, protocol, sock->type);

/*否则就使用通用名称*/

330                         else

331                                 request_module("net-pf-%d-proto-%d",

332                                                PF_INET, protocol);

333                         goto lookup_protocol;

334                 } else

335                         goto out_rcu_unlock;

336         }

337 

338         err = -EPERM;

/*检查通用性。只有root才有权限使用原始套接字。*/

339         if (sock->type == SOCK_RAW && !kern &&

340             !ns_capable(net->user_ns, CAP_NET_RAW))

341                 goto out_rcu_unlock;

/*对socket的操作集合进行了挂钩,指向了answer->ops,由于在inet_protosw的设置中已经设置成了TCP协议相关,所以此处sock->ops设置为inet_stream_ops,而answer_prot设置为tcp_prot结构。该结构式一个struct proto结构,负责传输层使用。*/

343         sock->ops = answer->ops;

344         answer_prot = answer->prot;

345         answer_no_check = answer->no_check;

346         answer_flags = answer->flags;

347         rcu_read_unlock();

348 

349         WARN_ON(answer_prot->slab == NULL);

350 

351         err = -ENOBUFS;

/*此处调用sk_alloc分配一个struct sock,该结构体庞大,其作用是网络层对socket的表示,意思就是IP协议下有很多东西比如IP地址,网卡接口,端口等等信息需要再socket层中有所体现从而使编程者方便使用,然后就利用指针等形式把内容进行一定程度上的映射。sk_alloc首先对sock->proto和sock_creator进行设置,设置成当前协议对应的proto调用sk_prot_alloc()根据是否提供了slab缓存而判断是使用slab缓存还是通用缓存。只要分配成功,则调用sock_lock_init()对缓存进行初始化,主要是对sock锁、等待队列以及进程数据结构中的网络空间结构进行分配。初始化完了后调用sock_net_set()函数对网络空间结构进行记录,然后最后增加一个net计数器。至此回到inet_create,判断是否成功分配*/

352         sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);

353         if (sk == NULL)

354                 goto out;

355 

356         err = 0;

/*可复用性检查,不懂*/

357         sk->sk_no_check = answer_no_check;

358         if (INET_PROTOSW_REUSE & answer_flags)

359                 sk->sk_reuse = SK_CAN_REUSE;

/*返回一个struct inet_sock的指针给inet*/

361         inet = inet_sk(sk);

/*判断是不是面向连通(目前只有SOCK_STREAM是面向连通的,不可以理解成TCP,因为ICMP也是面向连通的)*/

362         inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

363 

364         inet->nodefrag = 0;

/*判断是否是原始套接字,如果是,新建IP头部*/

366         if (SOCK_RAW == sock->type) {

367                 inet->inet_num = protocol;

368                 if (IPPROTO_RAW == protocol)

369                         inet->hdrincl = 1;

370         }

/*判断是否采用路径MTU发现算法*/

372         if (ipv4_config.no_pmtu_disc)

373                 inet->pmtudisc = IP_PMTUDISC_DONT;

374         else

375                 inet->pmtudisc = IP_PMTUDISC_WANT;

376 

377         inet->inet_id = 0;

/*进一步初始化sk结构(struct sock),此处分析一下sock_init_data:初始化接收(读)队列,初始化写队列,初始化错误信息队列,注意这三个队列都是双向链表,属于sk_buff_head结构体,其中会把sk_buff结构体串联起来。初始化数据包发送定时器,变量(主要是函数指针即钩子函数)*/

379         sock_init_data(sock, sk);

380 

381         sk->sk_destruct    = inet_sock_destruct;

382         sk->sk_protocol    = protocol;

383         sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

384 

385         inet->uc_ttl    = -1;

386         inet->mc_loop   = 1;

387         inet->mc_ttl    = 1;

388         inet->mc_all    = 1;

389         inet->mc_index  = 0;

390         inet->mc_list   = NULL;

391         inet->rcv_tos   = 0;

392 

393         sk_refcnt_debug_inc(sk);

394   

395         if (inet->inet_num) {

396                 /* It assumes that any protocol which allows

397                  * the user to assign a number at socket

398                  * creation time automatically

399                  * shares.

400                  */

401                 inet->inet_sport = htons(inet->inet_num);

402                 /* Add to protocol hash chains. */

403                 sk->sk_prot->hash(sk);

404         }

/*查阅前面的sock_alloc会发现,这里的sk_prot被设置为answer_prot(即answer->prot),而answer_prot又被设置成了tcp_prot,所以此处调用tcp_prot中的init,查阅tcp_prot即可得到init这个函数指针指向的是tcp_v4_init_sock(),接下来分析tcp_v4_init_sock(见源代码后面)*/

406         if (sk->sk_prot->init) {

407                 err = sk->sk_prot->init(sk);

408                 if (err)

409                         sk_common_release(sk);

410         }

411 out:

412         return err;

413 out_rcu_unlock:

414         rcu_read_unlock();

415         goto out;

416 }

这里分析tcp_v4_init_sock,这个函数负责初始化TCP的IPV4的部分:

2150 static int tcp_v4_init_sock(struct sock *sk)

2151 {

2152         struct inet_connection_sock *icsk = inet_csk(sk);

2153 

2154         tcp_init_sock(sk);

2155 

2156         icsk->icsk_af_ops = &ipv4_specific;

2157 

2158 #ifdef CONFIG_TCP_MD5SIG

2159         tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;

2160 #endif

2161 

2162         return 0;

2163 }

这里涉及两个新的数据结构tcp_sock与inet_connection_sock,其实还有一个数据结构inet_sock。之前提过struct socket是面向用户态的,而struct sock是面向内核驱动的,但socket与内核协议栈的交互实际上是通过xxx_sock这个数据机构,此处是TCP协议,自然是tcp_sock 数据结构,保存所有tcp相关信息。而inet_connection_sock实际上是inet_sock的扩展,之前说过inet_sock是INET域在socket的表现,而inet_connection_sock就是在inet_sock的基础上保存连接信息。

tcp_v4_init_sock中的tcp_init_sock(sk)是具体的初始化举措,尤其重要的是对SIN和AFK这两个SYN帧的初始化。

至此inet_create函数分析完毕,同时sock_create函数也分析完毕,接下来分析sock_map_fd():

386 static int sock_map_fd(struct socket *sock, int flags)

387 {

388         struct file *newfile;

389         int fd = get_unused_fd_flags(flags);

390         if (unlikely(fd < 0))

391                 return fd;

392 

393         newfile = sock_alloc_file(sock, flags, NULL);

394         if (likely(!IS_ERR(newfile))) {

395                 fd_install(fd, newfile);

396                 return fd;

397         }

398 

399         put_unused_fd(fd);

400         return PTR_ERR(newfile);

401 }

首先明白在用户空间中操作socket是完全当成文件在操作,但之前只分配了相应的inode和网络空间结构,所以本函数的目的是与文件系统形成关联。

本函数在3.9中与2.6内核区别较大,一句一句地分析。get_unused_fd_flags会调用__alloc_fd(),该函数负责分配一个文件描述符并设置成busy状态。然后执行sock_alloc_file,该函数负责得到第一个没被使用的文件,并对其path,状态等等进行设置,然后再把文件操作表socket_file_ops装载好。然后执行fd_install(fd, newfile),把文件描述符和文件进行关联。最后执行put_unused_fd。

至此整个socket的创建完成。

总结一下这次分析的过程。对分析源码的步骤有了新的想法,首先应该想到要完成这一步从理论上需要完成哪些东西,然后再递归地去分析这些东西又需要完成那些步骤。然后根据每个步骤去看由哪些代码执行,比如在socket的创建中,我就应该从文件系统初始化,通用socket初始化,TCP初始化,INET初始化,sock初始化,协议对应等等部分来分析,而不是盲目地一步一步地去看。

其次,不能纠缠于细节,个别有重大意义的细节可以仔细分析,但更多的时候应该一块一块地去看代码看这些代码完成了什么功能,而不是纠结于每一句想完成什么。

最后也是最重要的,在分析源码之前需要先分析整个过程涉及的数据结构以及他们存在的目的,他们之间的关系,只有在数据结构清晰明了了之后才可能对过程有较为清晰容易地理解。

linux内核中socket的创建过程源码分析(详细分析)

上一篇:夹缝


下一篇:【sinatra】修改默认ip绑定