前述文章代码由下至上分析,这次从上至下的分析,从应用层开始,看看应用层(用户空间)是如何将数据传递给内核的。在应用程序编程时,常常可以看见如下接口:
sockfd = socket(AF_INET, SOCK_STREAM, 0)
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)
recv(sockfd, recv_buffer, BUFFER_SIZE, 0)
bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)
listen(sockfd, 5) == -1
accept(sockfd, (struct sockaddr *)&client_addr, (socklen_t *)&sin_size)
send(sock_fd, snd_buff, BUFFER_SIZE, 0)
这些同接口通常用于客户端和服务器端的程序中,这是应用程序调用的接口经由C库会调用内核里的实现函数,这里的调用通常称为系统调用。
//include/linux/syscall.h
asmlinkage long sys_socketcall(int call, unsigned long __user *args);
该函数的定义由下面的2460行的SYSCALL_DEFINE2定义,上面的recv等和sys_socketcall函数定义于同一文件中,这里就只列出sys_socketcall函数了。
//net/socket.c
2460 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
2461 {
2462 unsigned long a[AUDITSC_ARGS];
2463 unsigned long a0, a1;
2474 /* copy_from_user should be SMP safe. */
2475 if (copy_from_user(a, args, len))
...
2482 a0 = a[0];
2483 a1 = a[1];
2484
2485 switch (call) {
2486 case SYS_SOCKET:
2487 err = sys_socket(a0, a1, a[2]);
2488 break;
2489 case SYS_BIND:
2490 err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
2491 break;
2492 case SYS_CONNECT:
2493 err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
2494 break;
2495 case SYS_LISTEN:
2496 err = sys_listen(a0, a1);
...
2502 case SYS_GETSOCKNAME:
2503 err =
2504 sys_getsockname(a0, (struct sockaddr __user *)a1,
2505 (int __user *)a[2]);
2506 break;
2507 case SYS_GETPEERNAME:
2508 err =
2509 sys_getpeername(a0, (struct sockaddr __user *)a1,
2510 (int __user *)a[2]);
2511 break;
2512 case SYS_SOCKETPAIR:
2513 err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
2514 break;
2515 case SYS_SEND:
2516 err = sys_send(a0, (void __user *)a1, a[2], a[3]);
2517 break;
2518 case SYS_SENDTO:
2519 err = sys_sendto(a0, (void __user *)a1, a[2], a[3],
2520 (struct sockaddr __user *)a[4], a[5]);
2521 break;
2522 case SYS_RECV:
2523 err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
2524 break;
2525 case SYS_RECVFROM:
2526 err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
2527 (struct sockaddr __user *)a[4],
2528 (int __user *)a[5]);
...
2563 }
C库已经对网络方面的套接字函数均调用的是sys_socketcall函数。该函数的若干case语句表明其只充当了分配器的角色,该函数会调用对应的系统调用,由于它们的套路都一样,这里只看2487行的socket的创建函数 err= sys_socket(a0, a1, a[2]),
//net/socket.c
1361 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
1362 {
1381 retval = sock_create(family, type, protocol, &sock);
1382 if (retval < 0)
1383 goto out;
1396 }
1381行各参数的意义根据创建的套接字类型不同而有所差异,在最开始给出的创建套接字的原型如下:
sockfd = socket(AF_INET, SOCK_STREAM, 0)
这里family对应第一参数,即协议族,这关系到后面数据所经过的协议栈,第二个参数是套接字类型,SOCK_STREAM是面向连接的流式套接字,常用于TCP服务,最后的协议参数是协议,为0表示由程序选择合适的协议类型。sock_create函数的定义如下:
1349 int sock_create(int family, int type, int protocol, struct socket **res)
1350 {
1351 return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
1352 }
该函数调用__sock_create完成实际的创建工作,它的两个下划线表示它是内核中比较重要的函数,对其修改需要慎重。第一个参数current->nsproxy->net_ns是一个命名空间相关的内容,命名空间和container实现一起用于实现轻量级虚拟化技术LXC。current是描述当前进程的结构体,其类型是task_struct,该结构体是内核最庞大的结构体之一,相关的字段涉及内核的其它子系统,__sock_create就是在当前进程的网络命名空间中创建一个应用程序指定的套接字,不同命名空间中看到的网络拓扑可以是不一样的,这样可以带来隔离话的好处。这里来看__sock_create,命名空间的内容就不在这里涉及了。
1236 int __sock_create(struct net *net, int family, int type, int protocol,
1237 struct socket **res, int kern)
1238 {
1275 sock = sock_alloc();
1296 pf = rcu_dereference(net_families[family]);
1311 err = pf->create(net, sock, protocol, kern);
1346 }
1275行是创建一个inode和socket对象,inode是文件系统的索引节点,其和超级块一起用于管理文件系统,VFS层会使用这些信息以实现不同的文件系统使用相对统一的接口管理,1296行是根据相应的协议族找到对应的创建函数。tcp/ip协议族注册,在《网络子系统初始化过程》文末略有提及,这里使用的socket创建函数是net/ipv4/af_inet.c函数。这个socket创建由具体的协议族完成。
static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
到这里已经能够理清socket的创建过程是如何从应用程序到内核的网络协议栈的,这时我们可以看socket接收和发送数据的接口了,这里给出它们的流程图,具体代码参看内核的实现。
图6.1 流式套接字创建流程
接下来看看应用层的发送和接收是如何同传输层进行通信的,由上面的发送和接收图,可以看出,对于发送则sock_sendmsg和inet_sendmsg之间的调用,对于接收是sock_recvmsg和inet_recvmsg之间的交互。
对于发送,在sock_sendmsg和inet_sendmsg之间__sock_sendmsg_nosec会被调用,该函数的626行的根据对应的协议栈的发送函数进行发送数据。
616 static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
617 struct msghdr *msg, size_t size)
618 {
619 struct sock_iocb *si = kiocb_to_siocb(iocb);
620
621 si->sock = sock;
622 si->scm = NULL;
623 si->msg = msg;
624 si->size = size;
625
626 return sock->ops->sendmsg(iocb, sock, msg, size);
627 }
在流式套接字中的sendmsg和recvmsg这两个函数会被调用到。
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
};
inet_recvmsg和inet_sendmsg于inet_stream_ops都定义于net/ipv4/af_inet.c函数,其又调用具体的协议进行处理,这里是tcp_prot协议中的tcp_recvmsg和tcp_sendmsg函数。
int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
flags & ~MSG_DONTWAIT, &addr_len);
}
int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
size_t size)
{
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
tcp_prot协议的成员如下,这些成员的初始化是在网络子系统初始化时完成的,在《网络子系统初始化过程》。
struct proto tcp_prot = {
.name = "TCP",
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
};
到这里可以有个困惑,那就是为什么sock->ops->sendmsg(iocb, sock, msg, size)会调用inet_sendmsg,又为什么sk->sk_prot->sendmsg(iocb, sk, msg, size)对应tcp_sendmsg;这就又要回到上面的套接字创建函数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;
297 list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
298
300 /* Check the non-wild match. */
301 if (protocol == answer->protocol) {
302 if (protocol != IPPROTO_IP)
303 break;
304 } else {
305 /* Check for the two wild cases. */
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 }
343 sock->ops = answer->ops;
344 answer_prot = answer->prot;
345 answer_no_check = answer->no_check;
346 answer_flags = answer->flags;
352 sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
...
}
第297行的inetsw是一个双链表数组,这些数组的下标对应于用户空间的参数,如STREAM,DGRAM,RAW等,根据这个链表可以找到structinet_protosw类型的answer成员,这个链表在tcp/ip协议栈初始化时就完成了,该链表的形式如下,流式套接字对应于第一个成员。第343的ops对于与 inetsw_array[]的第一成员的inet_stream_ops,至此,找到了inet_sendmsg的来历了。
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.no_check = 0,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
},
...
};
inet_create函数的352 sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot),其中answer_prot在344行被赋值成了 &tcp_prot成员。
//net/core/sock.c
1363 struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
1364 struct proto *prot)
1365 {
1366 struct sock *sk;
1367
1368 sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
1370 sk->sk_family = family; //协议族信息保留
1375 sk->sk_prot = sk->sk_prot_creator = prot;
1383
1384 return sk;
1385 }
该函数首先调用sk_prot_alloc创建一个sock对象,然后在1375行将该对象的sk_prot成员赋值成prot,也就是tcp_prot,该函数返回sock类型的指针以做进一步的处理工作,比如调用tcp_prot的初始化函数tcp_v4_init_sock。该结构体的指针即是传输层的,这样就切换到tcp协议处理了。
接收的过程一样,函数tcp层函数的指针同发送指针一样位于tcp_prot结构体中。