Linux内核网络协议栈4-创建socket(2)

时间:2021-11-06 11:04:11

接上篇“创建socket” 一文;

 

5、分配sock结构:

本文中的例子会调用inet_family_ops.create方法即inet_create方法完成socket的创建工作;其调用链如下:

net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create();

inet_create()主要完成以下几个工作:

1) 设置socket的状态为SS_UNCONNECTED;

C代码  Linux内核网络协议栈4-创建socket(2)
  1. sock->state = SS_UNCONNECTED;  

 


2) 根据socket的type找到对应的套接字类型:

C代码  Linux内核网络协议栈4-创建socket(2)
  1. list_for_each_rcu(p, &inetsw[sock->type]) {  
  2.     answer = list_entry(p, struct inet_protosw, list);  
  3.   
  4.     /* Check the non-wild match. */  
  5.     if (protocol == answer->protocol) {  
  6.         if (protocol != IPPROTO_IP)  
  7.             break;  
  8.     } else {  
  9.         /* Check for the two wild cases. */  
  10.         if (IPPROTO_IP == protocol) {  
  11.             protocol = answer->protocol;  
  12.             break;  
  13.         }  
  14.         if (IPPROTO_IP == answer->protocol)  
  15.             break;  
  16.     }  
  17.     err = -EPROTONOSUPPORT;  
  18.     answer = NULL;  
  19. }  
由于同一type不同protocol的套接字保存在inetsw中的同一链表中,因此需要遍历链表来查找;在上面的例子中,会将protocol重新赋值为answer->protocol,即IPPROTO_TCP,其值为6; 

3) 使用匹配的协议族操作集初始化socket; 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. sock->ops = answer->ops;  
  2. answer_prot = answer->prot;// 供后面使用  
结合例子,sock变量的ops指向inet_stream_ops结构体变量; 

4) 分配sock结构体变量net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sk_alloc(): 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);  
其中,answer_prot指向tcp_prot结构体变量; 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. struct sock *sk_alloc(struct net *net, int family, gfp_t priority, struct proto *prot) {  
  2.     struct sock *sk;  
  3.   
  4.     sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);  
  5.     if (sk) {  
  6.         sk->sk_family = family;  
  7.   
  8.         sk->sk_prot = sk->sk_prot_creator = prot;  
  9.         sock_lock_init(sk);  
  10.         sock_net_set(sk, get_net(net));  
  11.     }  
  12.   
  13.     return sk;  
  14. }  
其中,sk_prot_alloc分配sock结构体变量;由于在inet_init中为不同的套接字分配了高速缓冲区,因此该sock结构体变量会在该缓冲区中分配空间;分配完成后,对其做一些初始化工作: 
i) 初始化sk变量的sk_prot和sk_prot_creator; 
ii) 初始化sk变量的等待队列; 
iii) 设置net空间结构,并增加引用计数; 

6、建立socket结构与sock结构的关系: 
1) socket, sock, inet_sock, tcp_sock的关系 
创建完sk变量后,回到inet_create函数中: 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. inet = inet_sk(sk);  
  2. static inline struct inet_sock *inet_sk(const struct sock *sk)  
  3. {  
  4.     return (struct inet_sock *)sk;  
  5. }  
这里是根据sk变量得到inet_sock变量的地址;细心的同学可能会问到:inet_sock是什么?之前分配的是sock变量,与inet_sock有什么关系啊? 
a. struct socket:这个是基本的BSD socket,应用程序通过系统调用开始创建的socket都是该结构体,它是基于虚拟文件系统创建出来的; 
类型主要有三种,即流式、数据报、原始套接字协议; 
其状态比较粗粒度,如下: 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. typedef enum {  
  2.     SS_FREE = 0,            /* not allocated        */  
  3.     SS_UNCONNECTED,         /* unconnected to any socket    */  
  4.     SS_CONNECTING,          /* in process of connecting */  
  5.     SS_CONNECTED,           /* connected to socket      */  
  6.     SS_DISCONNECTING        /* in process of disconnecting  */  
  7. } socket_state;  
b. struct sock:它是网络层的socket;对应有TCP、UDP、RAW三种; 
其状态相比socket结构更精细: 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. enum {  
  2.     TCP_ESTABLISHED = 1,  
  3.     TCP_SYN_SENT,  
  4.     TCP_SYN_RECV,  
  5.     TCP_FIN_WAIT1,  
  6.     TCP_FIN_WAIT2,  
  7.     TCP_TIME_WAIT,  
  8.     TCP_CLOSE,  
  9.     TCP_CLOSE_WAIT,  
  10.     TCP_LAST_ACK,  
  11.     TCP_LISTEN,  
  12.     TCP_CLOSING,    /* Now a valid state */  
  13.   
  14.     TCP_MAX_STATES  /* Leave at the end! */  
  15. };  
c. struct inet_sock:它是INET域的socket表示,是对struct sock的一个扩展,提供INET域的一些属性,如TTL,组播列表,IP地址,端口等; 
d. struct raw_socket:它是RAW协议的一个socket表示,是对struct inet_sock的扩展,它要处理与ICMP相关的内容; 
e. sturct udp_sock:它是UDP协议的socket表示,是对struct inet_sock的扩展; 
f. struct inet_connection_sock:它是所有面向连接的socket表示,是对struct inet_sock的扩展; 
g. struct tcp_sock:它是TCP协议的socket表示,是对struct inet_connection_sock的扩展,主要增加滑动窗口,拥塞控制一些TCP专用属性; 
h. struct inet_timewait_sock:它是网络层用于超时控制的socket表示; 
i. struct tcp_timewait_sock:它是TCP协议用于超时控制的socket表示; 

上面简单介绍了一下内核中不同的socket相关的结构体的作用;回到inet_create函数中: 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. inet = inet_sk(sk);  
这里为什么能直接将sock结构体变量强制转化为inet_sock结构体变量呢?只有一种可能,那就是在分配sock结构体变量时,真正分配的是inet_sock或是其他结构体; 

我们回到分配sock结构体的那块代码(参考前面的5.4小节:net/core/Sock.c): 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority, int family) {  
  2.     struct sock *sk;  
  3.     struct kmem_cache *slab;  
  4.   
  5.     slab = prot->slab;  
  6.     if (slab != NULL)  
  7.         sk = kmem_cache_alloc(slab, priority);  
  8.     else  
  9.         sk = kmalloc(prot->obj_size, priority);  
  10.   
  11.     return sk;  
  12. }  
上面的代码在分配sock结构体时,有两种途径,一是从tcp专用高速缓存中分配;二是从内存直接分配;前者在初始化高速缓存时,指定了结构体大小为prot->obj_size;后者也有指定大小为prot->obj_size, 
根据这点,我们看下tcp_prot变量中的obj_size(net/ipv4/Tcp_ipv4.c): 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. .obj_size       = sizeof(struct tcp_sock),  
也就是说,分配的真实结构体是tcp_sock;由于tcp_sock、inet_connection_sock、inet_sock、sock之间均为0处偏移量,因此可以直接将tcp_sock直接强制转化为inet_sock;这几个结构体间的关系如下: 
Linux内核网络协议栈4-创建socket(2)
2) 建立socket, sock的关系 
创建完sock变量之后,便是初始化sock结构体,并建立sock与socket之间的引用关系;调用链如下: 
net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sock_init_data(): 
该函数主要工作是: 
a. 初始化sock结构的缓冲区、队列等; 
b. 初始化sock结构的状态为TCP_CLOSE; 
c. 建立socket与sock结构的相互引用关系; 

7、使用tcp协议初始化sock: 
inet_create()函数最后,通过相应的协议来初始化sock结构: 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. if (sk->sk_prot->init) {  
  2.     err = sk->sk_prot->init(sk);  
  3.     if (err)  
  4.         sk_common_release(sk);  
  5. }  
例子中,这里调用的是tcp_prot的init钩子函数net/ipv4/Tcp_ipv4.c:tcp_v4_init_sock(),它主要是对tcp_sock和inet_connection_sock进行一些初始化; 

8、socket与文件系统关联: 
回到net/Socket.c:sys_socket()函数: 
C代码  Linux内核网络协议栈4-创建socket(2)
  1. asmlinkage long sys_socket(int family, int type, int protocol)  
  2. {  
  3.     int retval;  
  4.     struct socket *sock;  
  5.   
  6.     retval = sock_create(family, type, protocol, &sock);  
  7.     if (retval < 0)  
  8.         goto out;  
  9.   
  10.     retval = sock_map_fd(sock);  
  11.     if (retval < 0)  
  12.         goto out_release;  
  13.   
  14. out:  
  15.     /* It may be already another descriptor 8) Not kernel problem. */  
  16.     return retval;  
  17.   
  18. out_release:  
  19.     sock_release(sock);  
  20.     return retval;  
  21. }  
创建好与socket相关的结构后,需要与文件系统关联,详见sock_map_fd()函数: 
1) 申请文件描述符,并分配file结构和目录项结构; 
2) 关联socket相关的文件操作函数表和目录项操作函数表; 
3) 将file->private_date指向socket; 

socket与文件系统关联后,以后便可以通过文件系统read/write对socket进行操作了; 

小结: 
1、socket库函数通过内核创建socket,并初始化其状态为TCP_CLOSE; 
2、创建完后,与文件系统关联,其文件一般位于/proc/$pid/fd/目录下; 
3、应用程序可以通过文件对socket进行操作;