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初始化,协议对应等等部分来分析,而不是盲目地一步一步地去看。

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容