接上篇“创建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;
- sock->state = SS_UNCONNECTED;
2) 根据socket的type找到对应的套接字类型:
- list_for_each_rcu(p, &inetsw[sock->type]) {
- answer = list_entry(p, struct inet_protosw, list);
- /* Check the non-wild match. */
- if (protocol == answer->protocol) {
- if (protocol != IPPROTO_IP)
- break;
- } else {
- /* Check for the two wild cases. */
- if (IPPROTO_IP == protocol) {
- protocol = answer->protocol;
- break;
- }
- if (IPPROTO_IP == answer->protocol)
- break;
- }
- err = -EPROTONOSUPPORT;
- answer = NULL;
- }
3) 使用匹配的协议族操作集初始化socket;
- sock->ops = answer->ops;
- answer_prot = answer->prot;// 供后面使用
4) 分配sock结构体变量net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sk_alloc():
- sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
- struct sock *sk_alloc(struct net *net, int family, gfp_t priority, struct proto *prot) {
- struct sock *sk;
- sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
- if (sk) {
- sk->sk_family = family;
- sk->sk_prot = sk->sk_prot_creator = prot;
- sock_lock_init(sk);
- sock_net_set(sk, get_net(net));
- }
- return sk;
- }
i) 初始化sk变量的sk_prot和sk_prot_creator;
ii) 初始化sk变量的等待队列;
iii) 设置net空间结构,并增加引用计数;
6、建立socket结构与sock结构的关系:
1) socket, sock, inet_sock, tcp_sock的关系
创建完sk变量后,回到inet_create函数中:
- inet = inet_sk(sk);
- static inline struct inet_sock *inet_sk(const struct sock *sk)
- {
- return (struct inet_sock *)sk;
- }
a. struct socket:这个是基本的BSD socket,应用程序通过系统调用开始创建的socket都是该结构体,它是基于虚拟文件系统创建出来的;
类型主要有三种,即流式、数据报、原始套接字协议;
其状态比较粗粒度,如下:
- typedef enum {
- SS_FREE = 0, /* not allocated */
- SS_UNCONNECTED, /* unconnected to any socket */
- SS_CONNECTING, /* in process of connecting */
- SS_CONNECTED, /* connected to socket */
- SS_DISCONNECTING /* in process of disconnecting */
- } socket_state;
其状态相比socket结构更精细:
- enum {
- TCP_ESTABLISHED = 1,
- TCP_SYN_SENT,
- TCP_SYN_RECV,
- TCP_FIN_WAIT1,
- TCP_FIN_WAIT2,
- TCP_TIME_WAIT,
- TCP_CLOSE,
- TCP_CLOSE_WAIT,
- TCP_LAST_ACK,
- TCP_LISTEN,
- TCP_CLOSING, /* Now a valid state */
- TCP_MAX_STATES /* Leave at the end! */
- };
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函数中:
- inet = inet_sk(sk);
我们回到分配sock结构体的那块代码(参考前面的5.4小节:net/core/Sock.c):
- static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority, int family) {
- struct sock *sk;
- struct kmem_cache *slab;
- slab = prot->slab;
- if (slab != NULL)
- sk = kmem_cache_alloc(slab, priority);
- else
- sk = kmalloc(prot->obj_size, priority);
- return sk;
- }
根据这点,我们看下tcp_prot变量中的obj_size(net/ipv4/Tcp_ipv4.c):
- .obj_size = sizeof(struct tcp_sock),
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结构:
- if (sk->sk_prot->init) {
- err = sk->sk_prot->init(sk);
- if (err)
- sk_common_release(sk);
- }
8、socket与文件系统关联:
回到net/Socket.c:sys_socket()函数:
- asmlinkage long sys_socket(int family, int type, int protocol)
- {
- int retval;
- struct socket *sock;
- retval = sock_create(family, type, protocol, &sock);
- if (retval < 0)
- goto out;
- retval = sock_map_fd(sock);
- if (retval < 0)
- goto out_release;
- out:
- /* It may be already another descriptor 8) Not kernel problem. */
- return retval;
- out_release:
- sock_release(sock);
- return retval;
- }
1) 申请文件描述符,并分配file结构和目录项结构;
2) 关联socket相关的文件操作函数表和目录项操作函数表;
3) 将file->private_date指向socket;
socket与文件系统关联后,以后便可以通过文件系统read/write对socket进行操作了;
小结:
1、socket库函数通过内核创建socket,并初始化其状态为TCP_CLOSE;
2、创建完后,与文件系统关联,其文件一般位于/proc/$pid/fd/目录下;
3、应用程序可以通过文件对socket进行操作;