本文把Linux-2.6.11.12源码中文注释版中的注释收集出来,整理在下面。如有侵权,请告知。
==========================socket()实现====================================
struct socket sock; struct sock sk;一般sock指struct socket, sk指struct sock;
socket()->sys_socket()
sys_socket()
{
调用sock_create()创建套接口;
调用sock_map_fd()为创建的套接口分配一个文件描述符,并进行绑定;
}
sock_create()->__sock_create(family,type,protocol,**res,kern)
{
检查参数合法性;
调用security_socket_create(family,type,protocol,kern)对创建过程进行安全审计;
检查内核是否启用了此协议族;
调用sock_alloc()分配struct socket sock; //分配sock空间
sock->type = type; //SOCK_STREAM or SOCK_DGRAM
调用协议族struct net_proto_family net_families[family]->create(sock,protocol); //分配sk空间,对于IPv4来说是inet_create()
调用security_socket_post_create()进行安全审计;
}
sock_alloc()
{//创建sock的时候会在文件系统中生成相应项,同时还会插入到存储sock的表中。
struct inode *inode = new_inode(sock_mnt->mnt_sb); sock_mnt是一个全局文件系统安装点,new_inode分配sock和inode,调用的是sb->s_op->alloc_inode(),实际函数是sock_alloc_inode()
sock = SOCKET_I(inode); 通过container_of返回inode旁边的sock;
}
sock_alloc_inode()
{
struct socket_alloc *ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL);调用slab接口分配内存
初始化ei->socket.wait;等待该套接口的进程列表;
}
inet_create()
{
struct sock *sk;sk隆重登场
查找sk和sock要用的各种接口ops/prot/协议参数等等;
sk=sk_alloc();分配sk内存
配置sk->sk_prot,sk->sk_no_check,sk->sk_reuse;
初始化sk其他字段;
配置sk->sk_destruct;sk->sk_family;sk->sk_protocol;
}
至此,socket()执行完毕,sock,sk,inode均已分配内存,但是sock还没插入到内核表中。bind()/send()的时候才会插入到内核表。
============================bind()实现=======================================
struct tcp_bind_bucket tb;
bind()->sys_bind()
sys_bind(fd,umyaddr,addrlen)
{
sock = sockfd_lookup(fd,&err) //根据文件描述符查找到sock
把struct sockaddr umyaddr复制到内核;
安全审计;
调用sock->ops->bind(sock,address,addrlen),实际函数是inet_bind()
}
inet_bind()
{
确定L3地址是单播、广播还是多播;
检查L3地址合法性;
检查L4地址合法性;小于1024的要检查权限;
检查sk->state,只有CLOSE状态才能进行绑定;
调用sk->sk_prot->get_port(sk,snum)接口进行地址绑定;如tcp_v4_get_port()或udp_v4_get_port()
清除inet->daddr/dport;
}
tcp_v4_get_port(sk,snum) //UDP对应udp_v4_get_port()
{
如果snum为0:
从1024开始遍历所有端口;
根据端口号计算tcp hash桶号,对每个桶,遍历链表;
如果端口被占用了就找下一个端口,否则找到合适的端口;
如果snum不为0:
根据snum计算tcp hash桶号,遍历链表,如果端口已绑定,goto tb_found;否则,goto tb_not_found;
tb_found:
查看这个端口是否有对应的传输控制块sk,
如果有,如果sk->sk_reuse>1,或者tb->fastreuse>0且sk->sk_reuse!=0且sk->sk_state!=TCP_LISTEN,goto success;
运行到此,说明端口没有对应的sk,说明端口可以用/复用;
tb_not_found:
如果是新分配的端口,要创建绑定端口信息,并添加到TCP hash桶中;
如果没有传输控制块,且?????
success:
调用tcp_bind_hash(sk,tb,snum);tcp_bind_hash():设置传输控制块的端口,将传输控制块加入端口信息块的传输控制块链表;
}
=========================
listen()-->sys_listen()-->inet_listen(sock,backlog/*sk->sk_max_ack_backlog = backlog*/)-->tcp_listen_start():
tcp_listen_start()
{
/* 初始连接队列长度上限 */
/* 初始化传输控制块中与延时发送ACK有关的数据结构 */
/* 为管理连接请求块的散列表分配存储空间,如果失败则退出 */
/* 计算哈希表的哈希种子 */
/* 将散列块与传输控制块绑定 */
/* 设置控制块的状态为TCP_LISTEN */
/* 如果端口绑定成功 */
/* 设置网络字节序的端口号 */
/* 清除路由缓存 */
/* 将传输控制块添加到侦听散列表中 */
/* 否则 */
/* 绑定失败,设置其状态 */
/* 解除侦听连接请求块与传输控制块的绑定 */
/* 释放侦听连接请求块 返回-EADDRINUSE*/
}
tcp_listen_stop()
{
/* 停止sk_timer定时器 */
/* 设置listen_opt后,应当不会再接受新的连接请求了 */
/* 如果请求连接者数量大于0 */
/* 遍历半连接哈希表 */
/* 遍历链表中的半连接 */
/* 关闭半连接 */
/* 对于已经连接但是还没有被accept的连接 */
/* 断开已经建立连接但是还没有被accept的连接 */
}
connect()传输层调用tcp_v4_connect()与服务器建立连接:
tcp_v4_connect()
{
/* 建立与服务器连接,发送SYN段 */
/* 校验目的地址的长度及地址族的有效性 */
/* 将下一跳和目的地址都设置为源地址 */
/* 根据下一跳地址等信息查找目的路由缓存项。 */
/* 如果没有启用源路由选项,则使用路由缓存项中的目的地址 */
/* 如果未设置传输控制块中的源地址,则使用路由缓存项中的源地址 */
/* 如果传输控制块中的时间戳和目的地址已经使用过,则说明传输控制块之前已建立连接并进行通信 */
/* 允许处于TIME-WAIT状态快速迁移到CLOSE状态 */
/* 设置目的地址和目标端口 */
/* 设置IP首部选项长度 */
/* 初始化MSS上限536 */
/* 设置状态 */
/* 将传输控制添加到ehash散列表中,并动态分配端口 */
/* 如果源端口或者目的端口发生改变,则需要重新查找路由 */
/* 如果源端口或者目的端口发生改变,则需要重新查找路由 */
/* 根据双方地址、端口计算初始序号 */
/* 根据初始序号和当前时间,随机算一个初始id */
/* 发送SYN段 */
}
accept()传输层调用tcp_accept():
tcp_accept()
{
/* accept调用的传输层实现 */
/* 本调用仅仅针对侦听套口,不是此状态的套口则退出 */
/* 如果accept队列为空,说明还没有收到新连接 */
/* 如果套口是非阻塞的,goto out; */
/* 如果超时时间到,没有新连接,goto out; */
/* 运行到这里,说明有新连接到来,则等待新的传输控制块 */
/* 返回struct sock newsk (三次握手后创建的对应传输控制块)*/
}
close()传输层调用tcp_close():
tcp_close()
{ //如果socket为不同进程所共享,则tcp_close减少这个socket的引用计数
/* 获取套接口锁 */
/* 表示两个方向的上的关闭 */
/* 如果是LISTEN状态 */
/* 设置其状态为CLOSE */
/* 终止侦听 tcp_listen_stop()*/
goto adjudge_to_death;
/* 遍历接收队列中的段 */
/* 段中数据长度,如果是fin段,则减少一个字节长度,因为fin占用一个序号 */
/* 未读取的段长度 */
/* 释放段 */
/* 释放套接口占用的缓存 */
/* 如果有未读数据 */
/* 发送RST表示非正常的结束,不能发送FIN */
/* 否则如果设置了SOCK_LINGER选项,但是延时时间为0 */
/* 调用disconnect断开、删除并释放已建立连接但是未被accept的传输控制块,同时删除并释放已经接收到接收队列和失序队列上的段和发送队列上的段 */
/* 否则其他情况,包括没有设置SOCK_LINGER或者启用了SOCK_LINGER且延时时间不为0,转换当前状态到对应的状态,如果新状态需要发送FIN */
/* 发送FIN段,将发送队列上未发送的段发送出去 */
/* 在给对端发送RST或FIN段后,等待套接口的关闭,直到TCP状态为FIN_WAIT_1、CLOSING、LAST_ACK或等待超时 */
adjudge_to_death:
/* 释放锁的目的,是为了处理后备队列,新版本将其移动到后面,这里应当有一个BUG */
/* 设置套接口为DEAD状态,成为孤儿套接口,同时更新系统中孤儿套接口数 */
/* 如果当前状态为TCP_FIN_WAIT2 */
/* 如果linger2小于0,表示可以从TCP_FIN_WAIT2状态直接转换为TCP_CLOSE状态 */
/* 设置为CLOSE状态 */
/* 向对方发送RST段 */
/* 否则需要等待才进入CLOSE状态 */
/* 保持TCP_FIN_WAIT2状态的时间 */
/* 如果超过60s */
/* 通过FIN_WAIT_2定时器来处理状态转换 */
/* 小于60s,则等待,直到状态转换成功 tcp_time_wait()*/
/* 如果此时不处于CLOSE状态 */
/* 释放内存缓存 */
/* 如果孤儿套接口数量太多 或者 发送队列中的段数量大于下限且系统中总的TCP传输层缓冲区分配的内存超过缓存区大小的最高硬性限制 */
/* 这种情况下需要立即关闭套接口,设置其状态为CLOSE */
/* 向对方发送RST状态 */
/* 增加孤儿套接口数量,我觉得这里有点不妥 */
/* 如果状态为CLOSE,则可以释放传输块及其占用的资源 */
}
shutdown()传输层调用tcp_shutdown()://closesocket()则是暴力关闭。
tcp_shutdown()
{ /* 禁止在一个套接口上进行数据的接收与发送。发送FIN,从容关闭。 */
/* 如果状态是ESTABLISHED|SYN_SENT|SYN_RECV|CLOSE_WAIT */
/* 如果需要发送FIN,则发送FIN */
}
另外,还有tcp_setsockopt(),比较简单就不多说了。
TCP选项有如下:
/* TCP socket options */
#define TCP_NODELAY 1/* 禁止或者启用套接口上的Nagle算法 */
#define TCP_MAXSEG 2/* 设置应用层的MSS上限 */
#define TCP_CORK 3/* 使能此选项后,会对Nagle进行优化,200ms内发送的数据会被组合成大的报文 */
#define TCP_KEEPIDLE 4/* 设置保活探测前TCP空闲时间 1~32767*/
#define TCP_KEEPINTVL 5/* 设置保活探测间隔时间 1~32767*/
#define TCP_KEEPCNT 6/* 设置保活探测次数,超过此值,则认为连接已经断开 1~127*/
#define TCP_SYNCNT 7/* 为建立连接而重发SYN的次数 1~127*/
#define TCP_LINGER2 8/* 保持在FIN_WAIT_2状态的时间 秒*/
#define TCP_DEFER_ACCEPT 9/* 只有当真正的数据过来以后,才会accept返回新socket,否则一直处于syn recv状态,超时丢弃 */
#define TCP_WINDOW_CLAMP 10/* 设置滑动窗口上限 */
#define TCP_INFO 11/* Information about this connection. 用于tcp_getsockopt*/
#define TCP_QUICKACK 12/* 启用或者禁用快速确认模式,该标志是暂时性的。 */