一、socketnode
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相關參數)socket
sock_mnt{sock_init}[sock_alloc]:sock_fs_type文件系統的superblock,文件系統相關操做 sockfs_ops sockfs_dentry_operationstcp
inet_family_ops{inet_init}:對應famile爲PF_INET的creat操做,sys_socket系統調用最終經過函數指針調用inet_create函數
二、bindui
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)spa
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:
一、會根據ip_nonlocal_bind參數設置以及IP_TRANSPARENT、IP_FREEBIND選項決定是否容許綁定非合法IP
二、若是綁定端口號低於1024,判斷是否有權綁定
三、TCP_CLOSE狀態而且以前沒有綁定過端口(inet->inet_num爲0表示以前沒有綁定過),才能從新綁定
四、綁定ip地址,inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr。inet_rcv_saddr用來作hash查找,inet_saddr用來作傳輸,廣播和多播地址設置inet_saddr=0。
五、IP_BIND_ADDRESS_NO_PORT選項設置爲1的時候不容許入參端口號爲0,不然入參端口號爲0表明由內核自動選擇
六、調用inet_csk_get_port[sk->sk_prot->get_port]進行端口綁定操做
七、若是成功設置了有效的端口號,進行以下更新
if(inet->inet_rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
if(snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
inet->inet_sport = htons(inet->inet_num);
inet->inet_daddr =0;
inet->inet_dport =0;
inet_csk_get_port:
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操做。
三、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:
socket狀態只有爲SS_UNCONNECTED且類型爲SOCK_STREAM才能進行listen操做
TCP狀態只能是CLOSE或者LISTEN才能進行listen操做,LISTEN狀態下進行listen操做的時候從新更新backlog,sk->sk_max_ack_backlog = backlog,fastopen隊列的maxlen在listen後不能更新。
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)。
四、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鏈接纔會關閉。
五、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中鏈接衝突,
一、若是在ehsah中找到了源地址、目標地址、源端口、目標端口、接口、net命名空間都徹底同樣的sk2,
sk2若是爲timewait狀態且tcp_twsk_unique返回1,則把sk插入到ehash並從ehash中刪除tw(sk2),並根據入參可能會從bhash中刪除tw
其餘狀況則不能使用該端口,即不能重複鏈接
二、若是沒有在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