TCP源码—系统调用

====================================================||

欢迎讨论技术的可以相互加微信:windgs (请备注csdn+xx职业)

====================================================||

目录

1、socket

2、bind

inet_bind:

inet_csk_get_port:SO_BINDTODEVICE、SO_REUSEADDR、SO_REUSEPORT-生产中使用的多

inet_get_local_port_range:

inet_is_local_reserved_port:

Q:不进行bind操作 直接listen可以嘛?重复进行bind操作可以吗?listen状态进行重新绑定?---可以参考

3、listen

inet_listen:

扫描二维码关注公众号,回复: 6755717 查看本文章

inet_csk_listen_start:

__inet_hash

4、accept

inet_csk_accept:

Q:多个进程或者线程同时accept先唤醒那个?---可以看下

Q:进程accept后进行fork,其中只有子进程进行close操作,连接会关闭嘛?---可以看下

5、connect

__inet_stream_connect:

tcp_v4_connect:

tcp_twsk_unique:

tcp_disconnect:

Q:connect的时候设置连接到0.0.0.0:0怎么处理?


1、socket

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)  

sys_socket->sock_create->__sock_create(入参有效性校验)->sock_alloc(申请分配新的inode,初始化inode->i_op=sockfs_inode_ops,创建socket)->[__sock_create]inet_create[pf->create]->[sys_socket]sock_map_fd[分配fd、创建file,并与sock之间相互关联]->sock_alloc_file(在sockfs中创建文件,设置sock->file = file、file->private_data = sock)->alloc_file(在sockfs中创建文件,设置file->f_op=socket_file_ops)->[sock_map_fd]fd_install(关联fd和file,current->files->fdt[fd]=file)

inet_create(sock类型为socket,sk为sock,从inetsw_array中查找对应的answer,sock->ops = answer->ops,创建初始化sk并与sock关联)->sk_alloc(从tcp_prot的slab中创建tcp_sock,初始化sk->sk_prot = sk->sk_prot_creator=answer->prot)->[inet_create]sock_init_data(sk->sk_socket = sock,sock->sk=sk,sk_data_ready等成员初始化工作)->[inet_create]tcp_v4_init_sock[sk->sk_prot->init](icsk->icsk_af_ops = &ipv4_specific、tcpmd5相关初始化tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific)->tcp_init_sock(初始化cwnd、sndbuf等TCP相关参数)

sock_mnt{sock_init}[sock_alloc]:sock_fs_type文件系统的superblock,文件系统相关操作 sockfs_ops  sockfs_dentry_operations

inet_family_ops{inet_init}:对应famile为PF_INET的creat操作,sys_socket系统调用最终通过函数指针调用inet_create

2、bind

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)

sys_bind->sockfd_lookup_light(查找fd对应大的socket)->[sys_bind]move_addr_to_kernel(复制用户空间结构到sockaddr_storage)->inet_bind[sock->ops->bind]->inet_csk_get_port[sk->sk_prot->get_port]->inet_csk_bind_conflict[inet_csk(sk)->icsk_af_ops->bind_conflict]

sockfd_lookup_light(根据fd查找到socket)->sock_from_file(返回file->private_data)

inet_bind:

1、会根据ip_nonlocal_bind参数设置以及IP_TRANSPARENT、IP_FREEBIND选项决定是否允许绑定非合法IP

2、如果绑定端口号低于1024,判断是否有权绑定

3、TCP_CLOSE状态并且之前没有绑定过端口(inet->inet_num为0表示之前没有绑定过),才能重新绑定

4、绑定ip地址,inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr。inet_rcv_saddr用来做hash查找,inet_saddr用来做传输,广播和多播地址设置inet_saddr=0。

5、IP_BIND_ADDRESS_NO_PORT选项设置为1的时候不允许入参端口号为0,否则入参端口号为0代表由内核自动选择

6、调用inet_csk_get_port[sk->sk_prot->get_port]进行端口绑定操作

7、如果成功设置了有效的端口号,进行如下更新

  1. if(inet->inet_rcv_saddr)
  2. sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
  3. if(snum)
  4. sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
  5. inet->inet_sport = htons(inet->inet_num);
  6. inet->inet_daddr =0;
  7. inet->inet_dport =0;

inet_csk_get_port:SO_BINDTODEVICE、SO_REUSEADDR、SO_REUSEPORT-生产中使用的多

SO_BINDTODEVICE选项可以设置绑定接口,即sk->sk_bound_dev_if,通过这个选项设置不同的接口后则可以绑定到相同的端口和ip地址上

SO_REUSEADDR:只要不为listen状态就可以重复绑定相同的地址端口。但是即使设置该选项,自动选择端口的时候仍然会尽量避免选定重复的IP和端口

SO_REUSEPORT:只要用户uid相同或已绑定的sk处于TW状态就可以抢占。  设置SO_REUSEPORT选项后,同一个用户两个套接字可以同时listen相同的端口和地址,而SO_REUSEADDR则不行

inet_get_local_port_range:

通过顺序锁保护获取ip_local_port_range参数范围

inet_is_local_reserved_port:

ip_local_reserved_ports参数的设置保存在Bit arrays数据结构中,判断一个端口是否为预留端口(需要编译宏CONFIG_SYSCTL生效)

inet_bind_hash[inet_csk_get_port]:

更新inet_sk(sk)->inet_num = snum,并把sk添加到对应tb的owners队列中

Q:不进行bind操作 直接listen可以嘛?重复进行bind操作可以吗?listen状态进行重新绑定?---可以参考

A:不进行bind直接进行listen的时候会自动选择端口不能重复进行bind操作只能在closed状态下进行bind操作

3、listen

SYSCALL_DEFINE2(listen, int, fd, int, backlog)

sys_listen(会在入参backlog和somaxconn之间取小向后传递)->sockfd_lookup_light->[sys_listen]inet_listen[sock->ops->listen]->inet_csk_listen_start->reqsk_queue_alloc(初始化icsk_accept_queue中的参数,包括TFO队列)->[inet_listen]inet_csk_get_port[sk->sk_prot->get_port]->[inet_listen]inet_hash[sk->sk_prot->hash]->__inet_hash

sock_net(sock->sk)->core.sysctl_somaxconn:/proc/sys/net/core/somaxconn  默认为128

inet_listen:

  1. socket状态只有为SS_UNCONNECTED且类型为SOCK_STREAM才能进行listen操作

  2. TCP状态只能是CLOSE或者LISTEN才能进行listen操作,LISTEN状态下进行listen操作的时候重新更新backlog,sk->sk_max_ack_backlog = backlog,fastopen队列的maxlen在listen后不能更新

  3. tcp_fastopen设置:如果TFO_SERVER_ENABLE(2)有设置,且没有通过socket选项初始化fastopen队列长度的时候

    如果TFO_SERVER_WO_SOCKOPT1(0x400)设置,则更新fastopenq.max_qlen=min(backlog, somaxconn)

    如果TFO_SERVER_WO_SOCKOPT2(0x800)设置,则更新fastopenq.max_qlen=min(backlog, tcp_fastopen)

inet_csk_listen_start:

初始化accept队列、逻辑TFO队列、TFO RST队列、逻辑半连接队列、delay ACK等

通过inet_csk_get_port[sk->sk_prot->get_port]获取port成功的时候,通过inet_hash把sock添加到listen队列

__inet_hash

根据绑定的本地端口和net空间散列到hash桶中,sk->sk_prot->h.hashinfo->listening_hash[inet_sk_listen_hashfn(sk)],listening_hash哈希表大小为固定的INET_LHTABLE_SIZE(32)。

4、accept

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen) ->sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0)

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen, int, flags)

sys_accept4->sockfd_lookup_light->[sys_accept4]sock_alloc(分配分配inode,创建新的child socket)->[sys_accept4]get_unused_fd_flags(分配fd)->[sys_accept4]sock_alloc_file(在文件系统创建文件)->[sys_accept4]inet_accept[sock->ops->accept](最后设置newsock状态为SS_CONNECTED)->inet_csk_accept[sk1->sk_prot->accept]->inet_csk_wait_for_connect(根据超时时间等待连接)->reqsk_queue_remove(从accept队列取出head)->[inet_accept](关联newsock和sk2)

inet_csk_accept:

如果sock状态不为LISTEN,返回错误

如果accept队列为空,根据O_NONBLOCK选择超时等待或者立即返回。超时时间sk->sk_rcvtimeo默认为MAX_SCHEDULE_TIMEOUT(有符号的longmax),可以通过SO_RCVTIMEO选项来设置,设置值会向上取整到HZ精度。

accept后,普通连接直接释放req,TFO下如果连接还没完成三次握手则设置req->sk = NULL,否则同样释放req。

Q:多个进程或者线程同时accept先唤醒那个?---可以看下

A:个程序运行产生的进程accept的时候,会根据随机数进行随机选择,参考__inet_lookup_listener

多个线程accept的时候,多个线程实际对应一个sk,accept的时候会把自己的wait描述符添加到等待队列的尾部。唤醒的时候则是从head先唤醒,因此多线程accept的时候,先accept的线程先被唤醒

通过fork产生的多个进程的accept实际对应一个sk,处理与多线程accept相同

Q:进程accept后进行fork,其中只有子进程进行close操作,连接会关闭嘛?---可以看下

A:不会,fork后父进程和子进程实际引用一个文件描述符,需要父进程和子进程都close后,TCP连接才会关闭。

5、connect

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)

sys_connect->sockfd_lookup_light->[sys_connect]inet_stream_connect[sock->ops->connect]->__inet_stream_connect->tcp_v4_connect[sk->sk_prot->connect]->[__inet_stream_connect]inet_wait_for_connect

__inet_stream_connect:

连接目标地址不能为AF_UNSPEC

socket状态为SS_UNCONNECTED,TCP状态为CLOSED才能进行连接

根据O_NONBLOCK计算超时时间

连接成功后更新socket状态为SS_CONNECTED

tcp_v4_connect:

如过有ip选项,则初始化下一跳地址

获取到下一跳的路由,如果获取失败或者路由是多播或者广播,则返回错误 ,如果没有指定源地址根据查找到的路由初始化源地址

__inet_check_established->twsk_unique->tcp_twsk_unique[sk->sk_prot->twsk_prot->twsk_unique]

__inet_check_established:

判断选定的端口是否和ehash中连接冲突,

1、如果在ehsah中找到了源地址、目标地址、源端口、目标端口、接口、net命名空间都完全一样的sk2,

sk2如果为timewait状态且tcp_twsk_unique返回1,则把sk插入到ehash并从ehash中删除tw(sk2),并根据入参可能会从bhash中删除tw

其他情况则不能使用该端口,即不能重复连接

2、如果没有在ehash中找到相符的sk2,则同样端口分配成功,sk加入ehash。

tcp_twsk_unique:

如果tcptw有记录时间戳信息,并且记录时间距离当前超过1s,tcp_tw_reuse有效的时候,则把tcptw中记录的时间戳信息记录到sk中,并返回1

tcp_connect[tcp_v4_connect]:

初始化连接相关参数tcp_connect_init

分配SKB、初始化SKB、添加到写队列、获取连接的ECN信息

发送SKB,TFO下使用tcp_send_syn_data,普通连接使用tcp_transmit_skb

启动重传定时器ICSK_TIME_RETRANS

tcp_disconnect:

Q:connect的时候设置连接到0.0.0.0:0怎么处理?

A:目的端口为0,目的地址和源地址会根据选出来的路由entry的目的地址和源地址进行设置,最终都会设置为127.0.0.1,源端口则会根据inet_hash_connect来选定,选定的时候会根据源地址、目的地址和目的端口先生成一个随机的offset,然后根据这个offset来随机选择源端口

Q:__inet_hash_connect中端口bhash冲突的场景下,怎么添加到ehash链表中的?

如果这个sk是第一个绑定这个端口的sk,那么直接在__inet_hash_connect中插入ehash,否则通过__inet_check_established插入到ehash

到这里,对于第一个问题的close调用自然有了结论:单线程(进程)中使用close与多线程中是一致的,但这两者与多进程的行为并不一致,多进程中共享的同一个socket必须都调用了close才会真正的关闭连接。

猜你喜欢

转载自blog.csdn.net/Windgs_YF/article/details/94739220