Linux內(nèi)核角度分析服務(wù)器Listen細節(jié)
Listen功能簡述
編寫服務(wù)器程序時,在Linux中需要調(diào)用Listen系統(tǒng)調(diào)用,如下所示,Listen系統(tǒng)調(diào)用的主要功能就是根據(jù)傳入的backlog參數(shù)創(chuàng)建連接隊列,并將套接字的狀態(tài)遷移至LISTEN狀態(tài),最后將監(jiān)聽sock注冊到TCP全局的監(jiān)聽套接字哈希表。
Listen系統(tǒng)調(diào)用-函數(shù)執(zhí)行流程
系統(tǒng)調(diào)用調(diào)用的函數(shù)執(zhí)行如下所示:
其中sockfd_lookup_light函數(shù)根據(jù)fd描述符得到struct socket結(jié)構(gòu)體,并找到當(dāng)前系統(tǒng)設(shè)定的最大可監(jiān)聽連接數(shù)somaxconn ,PROC文件系統(tǒng)中somaxconn默認為128,意味著單個套接口隊列的長度,可最大監(jiān)聽128個連接 ,如下所示:
somaxconn與Listen系統(tǒng)調(diào)用傳入的參數(shù)backlog進行比較,若當(dāng)前傳入的參數(shù)backlog大于somaxconn則使用somaxconn,即backlog最大值不能超過somaxconn。該系統(tǒng)調(diào)用核心是執(zhí)行:sock->ops->listen(sock,backlog) ;也就是說找到服務(wù)器的socket后,通過它的協(xié)議操作表結(jié)構(gòu)struct proto_ops執(zhí)行其listen鉤子函數(shù),proto_ops協(xié)議操作表結(jié)構(gòu)的掛入是在socket創(chuàng)建過程根據(jù)協(xié)議類型進行設(shè)置的,TCP實際掛入的是inet_stream_ops操作表結(jié)構(gòu),listen在inet_stream_ops表中的賦值如下所示:
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ? ?


故sock->ops->listen針對于TCP而言,繼續(xù)調(diào)用inet_listen函數(shù):
inet_listen函數(shù)首先是對套接字類型、狀態(tài)進行檢查,類型必須是流式套接字且狀態(tài)必須是close或者listen狀態(tài):
inet_listen函數(shù)核心的繼續(xù)調(diào)用inet_csk_start_listen函數(shù):
inet_csk_listen_start函數(shù)通過reqsk_queue_alloc創(chuàng)建連接隊列,隊列結(jié)構(gòu)體如下,隊列的最大長度是sk_max_ack_backlog,也就是用戶傳入的backlog參數(shù)值,隊列的長度計數(shù)是sk_ack_backlog。
其中request_sock結(jié)構(gòu)體是請求隊列的節(jié)點如下所示,*dl_next將所有的accept請求串起來。
struct request_sock_queue和struct request_sock的關(guān)系如下:

inet_csk_listen_start調(diào)用的分配并初始化連接隊列的函數(shù)reqsk_queue_alloc如下所示,其中可以看到queue->rskq_accept_head初始化為NULL
inet_csk_listen_start函數(shù)中另一個核心內(nèi)容就是調(diào)用哈希函數(shù):
sk->sk_prot->hash(sk)將監(jiān)聽sock注冊到TCP全局的監(jiān)聽套接字哈希表,對于TCP對應(yīng)的協(xié)議棧,hash函數(shù)是inet_hash:
繼續(xù)調(diào)用__inet_hash:
關(guān)于:
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
通過圖解,如下所示:

最終得到struct inet_hashinfo:
內(nèi)核將監(jiān)聽隊列分為32個哈希桶bucket:listening_hash[INET_LHTABLE_SIZE],保存處于監(jiān)聽狀態(tài)的TCP套接口哈希鏈表,每個哈希桶由獨立的保護鎖和鏈表,此結(jié)構(gòu)通過減小鎖的粒度,增加并行處理的可能,每個哈希桶如下所示:
哈希桶的選擇由函數(shù)inet_sk_listen_hashfn的返回值決定
inet_sk_listen_hash函數(shù)使用數(shù)據(jù)包的目的端口號(本地監(jiān)聽端口號)計算的hash值為索引得到具體的哈希桶,如下所示:
內(nèi)核為處于LISTEN狀態(tài)的socket分配了大小為32的哈希桶,監(jiān)聽的端口號經(jīng)過哈希算法運算打散到這些哈希桶中(如果開啟了IPV6,并且啟用了端口重用,將此套接口添加在監(jiān)聽套接口桶的鏈表末尾;否則,添加到鏈表頭部,如下代碼所示)
如下圖所示,哈希鏈表的組織方式:

當(dāng)收到客戶端的 SYN 握手報文以后,會根據(jù)目標(biāo)端口號的哈希值計算出哈希沖突鏈表,然后遍歷這條哈希鏈表得到對應(yīng)的socket。
