创建一个BSD socket:
- socket()函数,利用sys_socket ()系统调用.(应用层)
- sys_socket ()调用sock_socket()申请一个socket数据结构并且填入相应的值。由于socket是inode数据结构的一部分,所以申请一个socket实际上是申请一个空白的inode,注册自己的文件操作集proto_ops,其包含了几乎所有的协议操作函数(release、bind、connect、accept、listen等),这组函数由下层INET协议提供。(表示层 BSD socket,进入内核)
- BSD socket层使用由下层具体协议族提供的相关的socket创建函数inet_create() 创建sock结构并且初始化下层提供的操作函数。(会话层 INET socket 层)
sock->prot = &tcp_prot;
sock->ops = &inet_stream_ops;
【在 sock 结构中的协议操作集, 是一个 proto 数据结构, 其间定义了 TCP/IP 协议层对 proto_ops 操作集的实现。INET 协议层利用这组函数实现自己的操作。TCP/IP 协议层对 proto 数据结构有三种实现: tcp_prot、udp_prot、 raw_prot。】
【TCP/IP协议层与网络设备驱动程序之间的接口是device数据结构。其中定义了网络设备驱动程序需要实现的函数集, 如open、 stop、hard_start_xmit等。各种网络设备的device结构中都要对这个函数集的实现。TCP/IP协议利用这组函数真正完成网络数据的发送和接收。】
调用流程:socket -> sys_socketcall->sys_socket–> inet_create
为一个INET BSD socket绑定一个地址:
- bind() 函数,利用sys_bind()系统调用(应用层)
- sys_bind()系统调用根据 socket 的文件描述符 fd,找到它对应的 file、inode、socket 数据结构。将 socket 地址从用户空间拷贝到内核。(表示层 BSD socket,已经进入内核)
- 在BSD socket层执行 sock->ops->bind (),因此,bind 的主要工作在 INET socket 层完成,并需要底层的 TCP 和 UDP 协议层提
供一些支持。一个已经绑定了地址的 socket 不能再用于其它通讯。(会话层 INET socket 层) - INET socket 层完成 bind 工作的函数是 inet_bind()
【UDP 和 TCP 都维护一个 hash table,用于查找进来的 IP 信息的地址,从而把它们转到正确的 socket/sock 对。UDP 维护一个已分配的 UDP 端口的 hash table:udp_table。TCP 更加复杂,因为它维护了几个 hash table 。 但是,在绑定操作中,TCP 实际上并不把绑定的 sock 数据结构加到它的 hash table 中,它只是检查请求的端口当前没有被使用。在 listen 操作中, sock 数据结构才加到 TCP 的 hash table 中。】
调用流程:bind ->sys_socketcall-> sys_bind -> inet_bind
在INET BSD socket上监听
- listen() 函数,利用sys_listen()系统调用(应用层)
- sys_listen()系统调用根据 socket 对应文件描述符 fd, 找到该 socket 对应的 file、inode、socket数据结构。调用 socket 协议操作集上的 listen 函数完成真正的 listen 工作。(表示层 BSD socket,已经进入内核)
- 在BSD socket层执行 sock->ops->listen ()。所以真正的 listen 在协议操作集中完成。 INET 提供的 listen 函数是 inet_listen。(会话层 INET socket 层)
- inet_listen()中将sock状态设置为TCP_LISTEN
【对于 UDP socket, 改变 socket 的状态已经足够, 但是 TCP 要把 socket 的 sock 数据结构加到它的两个 hash table 中, 原因是该 socket 已经激活。这两个 hash 表分别是tcp_bound_hash 和 tcp_listening_hash 表。这两个表都通过一个基于 IP 端口号的 hash函数进行索引。】
调用流程:listen -> sys_socketcall-> sys_listen -> inet_listen
Connection to an INET BSD Socket
- connect() 函数,利用sys_connect()系统调用(应用层)
- sys_connect()系统调用根据 socket 对应文件描述符 fd, 找到该 socket 对应的 file、inode、socket数据结构。将地址 uservaddr 由用户空间拷贝到内核空间。调用 socket 操作集上的 connect 函数完成真正的连接工作。(表示层 BSD socket,已经进入内核)
- 在BSD socket层执行socket操作集上的connect函数。不同的协议族对 connect 函数的实现是不同的。inet_connect () (会话层 INET socket 层)
4.调用 sock 操作集中的 tcp_connect函数。(下层传输层函数)
【在connect中可以看到三次握手时,状态的变化。客户端自动绑定端口也是发生在connect函数中(inet_connect)】
connect -> sys_socketcall -> sys_connect -> inet_connect -> tcp_connect
Accepting Connection Requests
- accept() 函数,利用sys_accept()系统调用(应用层)
- sys_accept()系统调用根据 socket 对应文件描述符 fd, 找到该 socket 对应的 file、inode、socket数据结构。申请一个新的 socket(inode)。 新 socket 的类型(type)等于老 socket 的类型。调用 socket 操作集中的 dup 函数, 复制一个 socket。 INET 中完成该操作的函数是 sock_no_dup,实际是调用协议集的 create 函数重新创建一个 socket。调用新 socket 操作集中的 accept 操作。(表示层 BSD socket,已经进入内核)
- 在BSD socket层执行socket操作集上的accept函数。INET 对该函数有两个实现:inet_accept和 sock_no_accept,前者用于 stream 类型,后者用于 dgram 类型。(会话层 INET socket 层)
4.调用 sock 操作集中的 accept函数。TCP 中完成 accept 动作的函数是 tcp_accept(下层传输层函数)
【sys_accept 在调用 inet_accpet 函数之前已经使用 inet_dup 函数创建了其对应的 sock 结构,newsock 的data字段已经指向了其对应的 sock 结构。inet_dup 调用 inet_create,创建了和原来的 sock 结构,协议,类型一致的 sock 结构。该 inet_connect 内部又将调用下层函数tcp_accept】
accept -> sys_socketcall -> sys_accpet -> inet_accpet -> tcp_accept(TCP是tcp_accept)
参考:
【1】https://blog.csdn.net/wenqian1991/article/details/46546477
【2】Linux内核分析第十一章
【3】Linux 2.0.x 源码