第六章 应用层(网络)--基于Linux3.10

前述文章代码由下至上分析,这次从上至下的分析,从应用层开始,看看应用层(用户空间)是如何将数据传递给内核的。在应用程序编程时,常常可以看见如下接口:

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结构体中。














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值