Socket与系统调用深度分析
Posted lambdoor
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket与系统调用深度分析相关的知识,希望对你有一定的参考价值。
在Linux里面,可通过创建Socket,使得进程之间进行网络通信,可通过TCP或者UDP的方式进行交互。
scoket系统调用主要完成socket的创建,必要字段的初始化,关联传输控制块,绑定文件等任务,完成返回socket绑定的文件描述符;
/** * socket函数调用关系 * sys_socket * |-->sock_create * | |-->__sock_create * | |-->inet_create * |-->sock_map_fd */
一:首先调用到sock_create
sock_create(family, type, protocol, &sock)
在这个第一层函数sock_create里, 比起socket系统调用来多出来第四个参数&sock,
这个参数是一个结构体, 定义如下:
struct socket { socket_state state; kmemcheck_bitfield_begin(type); short type; kmemcheck_bitfield_end(type); unsigned long flags; struct socket_wq __rcu *wq; struct file *file; struct sock *sk; const struct proto_ops *ops; };
其中:
- state为枚举类型: SS_FREE, SS_UNCONNECTED, SS_CONNECTING, SS_CONNECTED, SS_DISCONNECTED. ( 比较好理解 );
- type为诸如SOCK_STREAM之类的;
- flags是标记;
- wq是一个wait queue, 具体怎么用后面会提到;
- file是供gc使用的文件描述符;
- sk是比较重要的数据结构, 指向代表下层协议(network layer)数据的sock结构
1. __sock_create
sock_create向下调到第二层方法:
__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
这一层函数引入了一个新的参数struct net, 后面再做分析。
在__sock_create这一层:
1.1 调用secutiry子系统的方法secutiry_ops->socket_create去security子系统折腾了一圈;
1.2 sock_alloc: 分配struct socket
1.2.1 通过sockfs中分配一个inode ( sockfs的初始化详见net/socket.c的sock_init方法 )
1.2.2 基于inode结构取到socket结构 ( 怎么取到的? 这里使用的是著名的container_of宏 )
通过调用container_of宏, 使用inode结构取到了socket结构, 返回.
1.3 从全局net_families数组中根据下标family取到对应的struct net_proto_family结构pf;
全局net_families数组是一个维护着系统全局所有网络簇信息的一个数组结构.
net_families数组通过sock_register/sock_unregister添加/删除元素. ( 最多40个元素, 可以理解为下层协议的数据结构, 例如ipv4, ipv6, netlink, appletalk, bluetooth等, 本文后面部分采用ipv4作为示例. )
sock_register/sock_unregister通常在net/xxx/Af_xxx.c中调用.
例如对于INET, 在net/ipv4/Af_inet.c中将INET的struct net_proto_family结构添加到全局net_families数组中.
INET的struct net_proto_family定义如下:
static const struct net_proto_family inet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE, };
1.4 关键的一步调用到pf->create, 按照上面的描述, 调用到了net/ipv4/Af_inet.c的inet_create方法.
2 根据struct socket的type(SOCK_STREAM之类), 遍历inetsw[type], 找到对应到protocol的结构体;
注意:
inetsw是一个链表数组, key为SOCK_STREAM, SOCK_DGRAM, SOCK_RAW等等.
inetsw的初始化在net/ipv4/Af_inet.c的inet_init方法中:
-
先初始化inesw为SOCK_STREAM/SOCK_DGRAM/SOCK_RAW等作为key的list_head数组;
-
遍历inetsw_array, 将其挂入inetsw中.
inetsw_array的元素封装了TCP, UDP, PING, RAW等协议, 即为上文中描述的”对应到protocol的结构体”.
inetsw_array的元素结构如下:
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, } ... }
3 将”对应到protocol的结构体”的ops赋给struct socket结构的ops.
例如如果type是SOCK_STREAM, protocol是TCP, 将&inet_stream_ops赋给struct socket结构的ops.
各个数据结构关系如下图:
4 调用sock_init_data(struct socket, struct sock)
从函数原型上可以看出, 是借助struct socket来初始化网络层核心数据结构struct sock:
- sk->sk_receive_queue, sk_write_queue, sk_error_queue ( 如果配了NET_DMA, 还有sk_async_wait_queue )
- sk->sk_send_head
- sk->sk_timer
- sk->sk_rcvbuf ( 这里指的是rmem )
- sk->sk_sndbuf ( 这里指的是wmem )
- sk->sk_state ( 初始值为TCP_CLOSE )
- 接管struct socket的wq
- sk->sk_dst_lock
- sk->sk_callback_lock
- sk->sk_rcvlowat ( SO_RCVLOWAT )
- sk->sk_rcvtimeo ( SO_RCVTIMEO )
- sk->sk_sndtimeo ( SO_SNDTIMEO )
- sk->sk_stamp ( 上一个packet收到的timestamp )
5 sk->sk_backlog_rcv ( 收到backlog的回调函数 ) 初始化为sk->sk_prot->backlog_rcv
例如对于TCP, backlog_rcv指向net/ipv4/Tcp_ipv4.c的全局结构体struct proto tcp_prot中的tcp_v4_do_rcv
6 inet->ut_ttl ( Unicast的TTL )为1
7 inet->mc_loop ( Multicast的loop ) 为1
8 inet->mc_ttl ( Multicast的TTL ), mc_all为1
9 inet->mc_index ( Multicast的device index )为0
10 如果inet->inet_num ( local port? )不为空,
意味着该protocol允许并且已经在socket创建时指定local Port, 于是调用sk->sk_prot->hash(sk).
例如对于TCP, hash()指向net/ipv4/Tcp_ipv4.c的全局结构体struct proto tcp_prot中的inet_hash:
该方法按local Port计算hash key, 在&tcp_hashinfo->listening_hash按hash key, 将struct sock插入tcp的listening_hash链表.
但是似乎这个hashtable元素只有32个, 为什么这么小? (待看)
11 调用sk->sk_prot->init, 例如对于TCP, 指向net/ipv4/Tcp_ipv4.c的全局结构体struct proto tcp_port中的tcp_v4_init_sock, 此方法完成该socket在内核网络子系统TCP层的初始化:
注意: TCP层有自己的数据结构struct tcp_sock, 从struct sock强转而来(下图图示中”tcp_sock specified”部分为struct tcp_sock的专有数据, 内存布局于common字段的后面, 故可以使用强制类型转换于struct sock互转)
- out_of_order_queue初始化 ( 该queue用于存放Out of order的segment )
- 初始化三个timer: retransmit_timer <-> net/ipv4/tcp_timer.c的tcp_write_timer delack <-> net/ipv4/tcp_timer.c的tcp_delack_timer keepalive <-> net/ipv4/tcp_timer.c的tcp_keepalive_timer
- “Data for direct copy to user” ( 称为prequeue )的init ( 机制待细看 )
- RTO ( Retransmit Time Out ) 初始化为1*HZ
- mdev 初始化为1*HZ ( mdev是什么待细看 )
- snd_cwnd初始化为10 ( Sending Congestion Window )
- snd_ssthresh初始化为0x7fffffff ( 慢启动size threshold )
- snd_cwnd_clamp初始化为~0 ( snd_cwnd的上限 )
- mss_cache初始化为536U
- reordering初始化为sysctl_tcp_reordering ( packet reording metric )
- icsk->icsk_ca_ops ( pluggable congestion control hook 可插拔的拥塞控制hook )
- sk->sk_state设为TCP_CLOSE …
至此pf->create调用结束, 也就是inet_create方法调用结束.
图示如下:
12 调用secutiry子系统的方法secutiry_ops->socket_post_create去security子系统折腾了一圈;
至此__sock_create调用结束.
至此socket系统调用中sock_create调用结束.
二:调用sock_map_fd将struct socket挂接到fd供进程使用.
1. 调用sock_alloc_file, 传入struct socket, struct file和flag
将struct socket的file设为struct file;
将struct file的private_data设为struct socket;
这样struct socket和struct file便互相关联起来了.
2. 调用fd_install将file descriptor加入fd array
至此, 整个socket系统调用结束.
以上是关于Socket与系统调用深度分析的主要内容,如果未能解决你的问题,请参考以下文章