一.linux內(nèi)核網(wǎng)絡(luò)棧代碼的準備知識
1. linux內(nèi)核ipv4網(wǎng)絡(luò)部分分層結(jié)構(gòu):
BSD socket層:?這一部分處理BSD socket相關(guān)操作碧注,每個socket在內(nèi)核中以struct socket結(jié)構(gòu)體現(xiàn)搂漠。這一部分的文件
主要有:/net/socket.c /net/protocols.c etc?
INET socket層:BSD socket是個可以用于各種網(wǎng)絡(luò)協(xié)議的接口吼蚁,而當用于tcp/ip掌实,即建立了AF_INET形式的socket時,
還需要保留些額外的參數(shù)捆毫,于是就有了struct sock結(jié)構(gòu)疲扎。文件主要
有:/net/ipv4/protocol.c /net/ipv4/af_inet.c /net/core/sock.c etc?
TCP/UDP層:處理傳輸層的操作,傳輸層用struct inet_protocol和struct proto兩個結(jié)構(gòu)表示早处。文件主要
有:/net/ipv4/udp.c /net/ipv4/datagram.c /net/ipv4/tcp.c /net/ipv4/tcp_input.c /net/ipv4//tcp_output.c /net/ipv4/tcp_minisocks.c /net/ipv4/tcp_output.c /net/ipv4/tcp_timer.c
etc?
IP層:處理網(wǎng)絡(luò)層的操作湾蔓,網(wǎng)絡(luò)層用struct packet_type結(jié)構(gòu)表示。文件主要有:/net/ipv4/ip_forward.c
ip_fragment.c ip_input.c ip_output.c etc.?
數(shù)據(jù)鏈路層和驅(qū)動程序:每個網(wǎng)絡(luò)設(shè)備以struct net_device表示陕赃,通用的處理在dev.c中卵蛉,驅(qū)動程序都在/driver/net目
錄下。
2. 兩臺主機建立udp通信所走過的函數(shù)列表
^?
| sys_read fs/read_write.c?
| sock_read net/socket.c?
| sock_recvmsg net/socket.c?
| inet_recvmsg net/ipv4/af_inet.c?
| udp_recvmsg net/ipv4/udp.c?
| skb_recv_datagram net/core/datagram.c?
| -------------------------------------------?
| sock_queue_rcv_skb include/net/sock.h?
| udp_queue_rcv_skb net/ipv4/udp.c?
| udp_rcv net/ipv4/udp.c?
| ip_local_deliver_finish net/ipv4/ip_input.c?
| ip_local_deliver net/ipv4/ip_input.c?
| ip_recv net/ipv4/ip_input.c?
| net_rx_action net/dev.c?
| -------------------------------------------?
| netif_rx net/dev.c?
| el3_rx driver/net/3c309.c?
| el3_interrupt driver/net/3c309.c?
==========================?
| sys_write fs/read_write.c?
| sock_writev net/socket.c?
| sock_sendmsg net/socket.c?
| inet_sendmsg net/ipv4/af_inet.c?
| udp_sendmsg net/ipv4/udp.c?
| ip_build_xmit net/ipv4/ip_output.c?
| output_maybe_reroute net/ipv4/ip_output.c?
| ip_output net/ipv4/ip_output.c?
| ip_finish_output net/ipv4/ip_output.c?
| dev_queue_xmit net/dev.c?
| --------------------------------------------?
| el3_start_xmit driver/net/3c309.c?
V
需要C/C++ Linux服務器架構(gòu)師學習資料加群563998835(資料包括C/C++么库,Linux傻丝,golang技術(shù),Nginx诉儒,ZeroMQ葡缰,MySQL,Redis忱反,fastdfs泛释,MongoDB,ZK温算,流媒體怜校,CDN,P2P注竿,K8S茄茁,Docker,TCP/IP巩割,協(xié)程裙顽,DPDK,ffmpeg等)宣谈,免費分享
二.linux的tcp-ip棧代碼的詳細分析
1.數(shù)據(jù)結(jié)構(gòu)(msghdr,sk_buff,socket,sock,proto_ops,proto)
bsd套接字層,操作的對象是socket,數(shù)據(jù)存放在msghdr這樣的數(shù)據(jù)結(jié)構(gòu):
創(chuàng)建socket需要傳遞family,type,protocol三個參數(shù)愈犹,創(chuàng)建socket其實就是創(chuàng)建一個socket實例,然后創(chuàng)建一個文件描述符結(jié)構(gòu)闻丑,并且互相建立一些關(guān)聯(lián)漩怎,即建立互相連接的指針,并且初始化這些對文件的寫讀操作映射到socket的read嗦嗡,write函數(shù)上來扬卷。
同時初始化socket的操作函數(shù)(proto_ops結(jié)構(gòu)),如果傳入的type參數(shù)是STREAM類型,那么就初始化為SOCKET->ops為inet_stream_ops酸钦,如果是DGRAM類型怪得,則SOCKET-ops為inet_dgram_ops咱枉。對于inet_stream_ops其實是一個結(jié)構(gòu)體,包含了stream類型的socket操作的一些入口函數(shù)徒恋,在這些函數(shù)里主要做的是對socket進行相關(guān)的操作蚕断,同時通過調(diào)用下面提到的sock中的相關(guān)操作完成socket到sock層的傳遞。比如在inet_stream_ops里有個inet_release的操作入挣,這個操作除了釋放socket的類型空間操作外亿乳,還通過調(diào)用socket連接的sock的close操作,對于stream類型來說径筏,即tcp_close來關(guān)閉sock
釋放sock葛假。
創(chuàng)建socket同時還創(chuàng)建sock數(shù)據(jù)空間,初始化sock,初始化過程主要做的事情是初始化三個隊列滋恬,receive_queue(接收到的數(shù)據(jù)包sk_buff鏈表隊列),send_queue(需要發(fā)送數(shù)據(jù)包的sk_buff鏈表隊列),backlog_queue(主要用于tcp中三次握手成功的那些數(shù)據(jù)包,自己猜的),根據(jù)family聊训、type參數(shù),初始化sock的操作恢氯,比如對于family為inet類型的带斑,type為stream類型的,sock->proto初始化為tcp_prot.其中包括stream類型的協(xié)議sock操作對應的入口函數(shù)勋拟。
在一端對socket進行write的過程中勋磕,首先會把要write的字符串緩沖區(qū)整理成msghdr的數(shù)據(jù)結(jié)構(gòu)形式(參見linux內(nèi)核2.4版源代碼分析大全),然后調(diào)用sock_sendmsg把msghdr的數(shù)據(jù)傳送至inet層,對于msghdr結(jié)構(gòu)中數(shù)據(jù)區(qū)中的每個數(shù)據(jù)包敢靡,創(chuàng)建sk_buff結(jié)構(gòu)挂滓,填充數(shù)據(jù),掛至發(fā)送隊列啸胧。一層層往下層協(xié)議傳遞赶站。一下每層協(xié)議不再對數(shù)據(jù)進行拷貝。而是對sk_buff結(jié)構(gòu)進行操作吓揪。
inet套接字及以下層 數(shù)據(jù)存放在sk_buff這樣的數(shù)據(jù)結(jié)構(gòu)里:
路由:
在linux的路由系統(tǒng)主要保存了三種與路由相關(guān)的數(shù)據(jù)亲怠,第一種是在物理上和本機相連接的主機地址信息表所计,第二種是保存了在網(wǎng)絡(luò)訪問中判斷一個網(wǎng)絡(luò)地址應該走什么路由的數(shù)據(jù)表柠辞;第三種是最新使用過的查詢路由地址的緩存地址數(shù)據(jù)表。
1.neighbour結(jié)構(gòu) neighbour_table{ }是一個包含和本機所連接的所有鄰元素的信息的數(shù)據(jù)結(jié)構(gòu)主胧。該結(jié)構(gòu)中有個元素是neighbour結(jié)構(gòu)的數(shù)組叭首,數(shù)組的每一個元素都是一個對應于鄰機的neighbour結(jié)構(gòu),系統(tǒng)中由于協(xié)議的不同踪栋,會有不同的判斷鄰居的方式焙格,每種都有neighbour_table{}類型的實例,這些實例是通過neighbour_table{}中的指針next串聯(lián)起來的游沿。在neighbour結(jié)構(gòu)中烛卧,包含有與該鄰居相連的網(wǎng)絡(luò)接口設(shè)備net_device的指針,網(wǎng)絡(luò)接口的硬件地址生棍,鄰居的硬件地址冬阳,包含有neigh_ops{}指針蛤虐,這些函數(shù)指針是直接用來連接傳輸數(shù)據(jù)的,包含有queue_xmit(struct * sk_buff)函數(shù)入口地址肝陪,這個函數(shù)可能會調(diào)用硬件驅(qū)動程序的發(fā)送函數(shù)驳庭。
2.FIB結(jié)構(gòu) 在FIB中保存的是最重要的路由規(guī)則,通過對FIB數(shù)據(jù)的查找和換算,一定能夠獲得路由一個地址的方法氯窍。系統(tǒng)中路由一般采取的手段是:先到路由緩存中查找表項饲常,如果能夠找到,直接對應的一項作為路由的規(guī)則狼讨;如果不能找到贝淤,那么就到FIB中根據(jù)規(guī)則換算傳算出來,并且增加一項新的熊楼,在路由緩存中將項目添加進去霹娄。
3.route結(jié)構(gòu)(即路由緩存中的結(jié)構(gòu))
數(shù)據(jù)鏈路層:
net_device{}結(jié)構(gòu),對應于每一個網(wǎng)絡(luò)接口設(shè)備鲫骗。這個結(jié)構(gòu)中包含很多可以直接獲取網(wǎng)卡信息的函數(shù)和變量犬耻,同時包含很多對于網(wǎng)卡操作的函數(shù),這些直接指向該網(wǎng)卡驅(qū)動程序的許多函數(shù)入口执泰,包括發(fā)送接收數(shù)據(jù)幀到緩沖區(qū)等枕磁。當這些完成后,比如數(shù)據(jù)接收到緩沖區(qū)后便由netif_rx(在net/core/dev.c各種設(shè)備驅(qū)動程序的上層框架程序)把它們組成sk_buff形式掛到系統(tǒng)接收的backlog隊列然后交由上層網(wǎng)絡(luò)協(xié)議處理术吝。同樣计济,對于上層協(xié)議處理下來的那些sk_buff。便由dev_queue_xmit函數(shù)放入網(wǎng)絡(luò)緩沖區(qū)排苍,交給網(wǎng)卡驅(qū)動程序的發(fā)送程序處理沦寂。
在系統(tǒng)中存在一張鏈表dev_base將系統(tǒng)中所有的net_device{}結(jié)構(gòu)連在一起。對應于內(nèi)核初始化而言淘衙,系統(tǒng)啟動時便為每個所有可能支持的網(wǎng)絡(luò)接口設(shè)備申請了一個net_device{}空間并串連起來传藏,然后對每個接點運行檢測過程,如果檢測成功彤守,則在dev_base鏈表中保留這個接點毯侦,否則刪除。對應于模塊加載來說具垫,則是調(diào)用register_netdev()注冊net_device,在這個函數(shù)中運行檢測過程侈离,如果成功,則加到dev_base鏈表筝蚕。否則就返回檢測不到信息卦碾。刪除同理铺坞,調(diào)用
unregister_netdev。
2.啟動分析
2.1 初始化進程?:start-kernel(main.c)---->do_basic_setup(main.c)---->sock_init(/net/socket.c)---->do_initcalls(main.c)
void__initsock_init(void){inti;printk(KERN_INFO"Linux NET4.0 for Linux 2.4/n");?printk(KERN_INFO"Based upon Swansea University Computer Society NET3.039/n");/*?
? * Initialize all address (protocol) families. 每一項表示的是針對一個地址族的操作集合洲胖,例如對于ipv4來說康震,在net/ipv4/af_inet.c文件中的函數(shù)inet_proto_init()就調(diào)用sock_register()函數(shù)將inet_families_ops初始化到屬于IPV4的net_families數(shù)組中的一項。?
? */for(i =0; i < NPROTO; i++)?? net_families[i] =NULL;/*?
? * Initialize sock SLAB cache.初始化對于sock結(jié)構(gòu)預留的內(nèi)存的slab緩存宾濒。?
? */sk_init();#ifdefSLAB_SKB/*?
? * Initialize skbuff SLAB cache 初始化對于skbuff結(jié)構(gòu)的slab緩存腿短。以后對于skbuff的申請可以通過函數(shù)kmem_cache_alloc()在這個緩存中申請空間。?
? */skb_init();#endif/*?
? * Wan router layer.?
? */#ifdefCONFIG_WAN_ROUTERwanrouter_init();#endif/*?
? * Initialize the protocols module. 向系統(tǒng)登記sock文件系統(tǒng)绘梦,并且將其安裝到系統(tǒng)上來橘忱。?
? */register_filesystem(&sock_fs_type);?sock_mnt = kern_mount(&sock_fs_type);/* The real protocol initialization is performed when?
? *? do_initcalls is run.??
? *//*?
? * The netlink device handler may be needed early.?
? */#ifdefCONFIG_NETrtnetlink_init();#endif#ifdefCONFIG_NETLINK_DEVinit_netlink();#endif#ifdefCONFIG_NETFILTERnetfilter_init();#endif#ifdefCONFIG_BLUEZbluez_init();#endif/*yfhuang ipsec*/#ifdefCONFIG_IPSECpfkey_init();#endif/*yfhuang ipsec*/}
2.2 do_initcalls()?中做了其它的初始化,其中包括
協(xié)議初始化卸奉,路由初始化钝诚,網(wǎng)絡(luò)接口設(shè)備初始化
(例如inet_init函數(shù)以_init開頭表示是系統(tǒng)初始化時做,函數(shù)結(jié)束后跟module_init(inet_init),這是一個宏榄棵,在include/linux/init.c中定義凝颇,展開為_initcall(inet_init),表示這個函數(shù)在do_initcalls被調(diào)用了)
2.3 協(xié)議初始化
此處主要列舉inet協(xié)議的初始化過程。
staticint__initinet_init(void){structsk_buff*dummy_skb;structinet_protocol*p;structinet_protosw*q;structlist_head*r;printk(KERN_INFO"NET4: Linux TCP/IP 1.0 for NET4.0/n");if(sizeof(struct inet_skb_parm) >sizeof(dummy_skb->cb)) {?? printk(KERN_CRIT"inet_proto_init: panic/n");return-EINVAL;?}/*?
? * Tell SOCKET that we are alive... 注冊socket疹鳄,告訴socket inet類型的地址族已經(jīng)準備好了?
? */(void) sock_register(&inet_family_ops);/*?
? * Add all the protocols. 包括arp,ip拧略、ICMP、UPD瘪弓、tcp_v4垫蛆、tcp、igmp的初始化腺怯,主要初始化各種協(xié)議對應的inode和socket變量袱饭。
其中arp_init完成系統(tǒng)中路由部分neighbour表的初始化
ip_init完成ip協(xié)議的初始化。在這兩個函數(shù)中呛占,都通過定義一個packet_type結(jié)構(gòu)的變量將這種數(shù)據(jù)包對應的協(xié)議發(fā)送數(shù)據(jù)虑乖、允許發(fā)送設(shè)備都做初始化。
? */printk(KERN_INFO"IP Protocols: ");for(p = inet_protocol_base; p !=NULL;) {structinet_protocol*tmp= (structinet_protocol*)p->next;inet_add_protocol(p);?? printk("%s%s",p->name,tmp?", ":"/n");?? p = tmp;?}/* Register the socket-side information for inet_create. */for(r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)?? INIT_LIST_HEAD(r);for(q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)?? inet_register_protosw(q);/*?
? * Set the ARP module up??
? */arp_init();/*?
??? * Set the IP module up?
??? */ip_init();tcp_v4_init(&inet_family_ops);/* Setup TCP slab cache for open requests. */tcp_init();/*?
? * Set the ICMP layer up?
? */icmp_init(&inet_family_ops);/* I wish inet_add_protocol had no constructor hook...?
??? I had to move IPIP from net/ipv4/protocol.c :-( --ANK?
? */#ifdefCONFIG_NET_IPIPipip_init();#endif#ifdefCONFIG_NET_IPGREipgre_init();#endif/*?
? * Initialise the multicast router?
? */#ifdefined(CONFIG_IP_MROUTE)ip_mr_init();#endif/*?
? * Create all the /proc entries.?
? */#ifdefCONFIG_PROC_FSproc_net_create ("raw",0, raw_get_info);?proc_net_create ("netstat",0, netstat_get_info);?proc_net_create ("snmp",0, snmp_get_info);?proc_net_create ("sockstat",0, afinet_get_info);?proc_net_create ("tcp",0, tcp_get_info);?proc_net_create ("udp",0, udp_get_info);#endif/* CONFIG_PROC_FS */ipfrag_init();return0;?}??module_init(inet_init);
2.4 路由初始化(包括neighbour表晾虑、FIB表疹味、和路由緩存表的初始化工作)
2.4.1 rtcache表 ip_rt_init()函數(shù) 在net/ipv4/ip_output中調(diào)用,net/ipv4/route.c中定義
2.4.2 FIB初始化 在ip_rt_init()中調(diào)用 在net/ipv4/fib_front.c中定義
2.4.3 neigbour表初始化 arp_init()函數(shù)中定義
2.5 網(wǎng)絡(luò)接口設(shè)備初始化
在系統(tǒng)中網(wǎng)絡(luò)接口都是由一個dev_base鏈表進行管理的走贪。通過內(nèi)核的啟動方式也是通過這個鏈表進行操作的佛猛。在系統(tǒng)啟動之初惑芭,將所有內(nèi)核能夠支持的網(wǎng)絡(luò)接口都初始化成這個鏈表中的一個節(jié)點坠狡,并且每個節(jié)點都需要初始化出init函數(shù)指針,用來檢測網(wǎng)絡(luò)接口設(shè)備遂跟。然后逃沿,系統(tǒng)遍歷整個dev_base鏈表婴渡,對每個節(jié)點分別調(diào)用init函數(shù)指針,如果成功凯亮,證明網(wǎng)絡(luò)接口設(shè)備可用边臼,那么這個節(jié)點就可以進一步初始化,如果返回失敗假消,那么證明該網(wǎng)絡(luò)設(shè)備不存在或是不可用柠并,只能將該節(jié)點刪除。啟動結(jié)束之后富拗,在dev_base中剩下的都是可以用的網(wǎng)絡(luò)接口設(shè)備臼予。
2.5.1 do_initcalls---->net_dev_init()(net/core/dev.c)------>ethif_probe()(drivers/net/Space.c,在netdevice{}結(jié)構(gòu)的init中調(diào)用,這邊ethif_probe是以太網(wǎng)卡針對的調(diào)用)
3.網(wǎng)絡(luò)設(shè)備驅(qū)動程序(略)
4.網(wǎng)絡(luò)連接
4.1 連接的建立和關(guān)閉
tcp連接建立的代碼如下:
server=gethostbyname(SERVER_NAME);sockfd=socket(AF_INET,SOCK_STREAM,0);address.sin_family=AF_INET啃沪;address.sin_port=htons(PORT_NUM);memcpy(&address.sin_addr,server->h_addr,server->h_length);connect(sockfd,&address,sizeof(address));
連接的初始化與建立期間主要發(fā)生的事情如下:
1)sys_socket調(diào)用:調(diào)用socket_creat(),創(chuàng)建出一個滿足傳入?yún)?shù)family粘拾、type、和protocol的socket,調(diào)用sock_map_fd()獲取一個未被使用的文件描述符创千,并且申請并初始化對應的file{}結(jié)構(gòu)缰雇。
2)sock_creat():創(chuàng)建socket結(jié)構(gòu),針對每種不同的family的socket結(jié)構(gòu)的初始化追驴,就需要調(diào)用不同的create函數(shù)來完成械哟。對應于inet類型的地址來說,在網(wǎng)絡(luò)協(xié)議初始化時調(diào)用sock_register()函數(shù)中完成注冊的定義如下:
structnet_proto_familyinet_family_ops={PF_INET;??????????????? inet_create??????? };
所以inet協(xié)議最后會調(diào)用inet_create函數(shù)殿雪。
3)inet_create: 初始化sock的狀態(tài)設(shè)置為SS_UNCONNECTED,申請一個新的sock結(jié)構(gòu)戒良,并且初始化socket的成員ops初始化為inet_stream_ops,而sock的成員prot初始化為tcp_prot。然后調(diào)用sock_init_data,將該socket結(jié)構(gòu)的變量sock和sock類型的變量關(guān)聯(lián)起來冠摄。
4)在系統(tǒng)初始化完畢后便是進行connect的工作糯崎,系統(tǒng)調(diào)用connect將一個和socket結(jié)構(gòu)關(guān)聯(lián)的文件描述符和一個sockaddr{}結(jié)構(gòu)的地址對應的遠程機器相關(guān)聯(lián),并且調(diào)用各個協(xié)議自己對應的connect連接函數(shù)河泳。對應于tcp類型沃呢,則sock->ops->connect便為inet_stream_connect。
5)inet_stream_connect: 得到sk,sk=sock->sk,鎖定sk拆挥,對自動獲取sk的端口號存放在sk->num中薄霜,并且用htons()函數(shù)轉(zhuǎn)換存放在sk->sport中。然后調(diào)用sk->prot->connect()函數(shù)指針纸兔,對tcp協(xié)議來說就是tcp_v4_connect()函數(shù)惰瓜。然后將sock->state狀態(tài)字設(shè)置為SS_CONNECTING,等待后面一系列的處理完成之后,就將狀態(tài)改成SS_CONNECTTED汉矿。
6) tcp_v4_connect():調(diào)用函數(shù)ip_route_connect()崎坊,尋找合適的路由存放在rt中。ip_route_connect找兩次洲拇,第一次找到下一跳的ip地址奈揍,在路由緩存或fib中找到曲尸,然后第二次找到下一跳的具體鄰居,到neigh_table中找到男翰。然后申請出tcp頭的空間存放在buff中另患。將sk中相關(guān)地址數(shù)據(jù)做一些針對路由的變動,并且初始化一個tcp連接的序列號蛾绎,調(diào)用函數(shù)tcp_connect()昆箕,初始化tcp頭,并設(shè)置tcp處理需要的定時器租冠。一次connect()建立的過程就結(jié)束了为严。
連接的關(guān)閉主要如下:
1)close: 一個socket文件描述符對應的file{}結(jié)構(gòu)中,有一個file_operations{}結(jié)構(gòu)的成員f_ops肺稀,它的初始化關(guān)閉函數(shù)為sock_close函數(shù)第股。
2)sock_close:調(diào)用函數(shù)sock_release(),參數(shù)為一個socket{}結(jié)構(gòu)的指針。
3)sock_release:調(diào)用inet_release话原,并釋放socket的指針和文件空間
4)inet_release: 調(diào)用和該socket對應協(xié)議的關(guān)閉函數(shù)inet_release,如果是tcp協(xié)議夕吻,那么調(diào)用的是tcp_close;最后釋放sk繁仁。
4.2 數(shù)據(jù)發(fā)送流程圖
各層主要函數(shù)以及位置功能說明:
1)sock_write:初始化msghdr{}結(jié)構(gòu) net/socket.c
2)sock_sendmsg:net/socket.c
3)inet_sendmsg:net/ipv4/af_net.c
4)tcp_sendmsg:申請sk_buff{}結(jié)構(gòu)的空間涉馅,把msghdr{}結(jié)構(gòu)中的數(shù)據(jù)填入sk_buff空間。net/ipv4/tcp.c
5)tcp_send_skb:net/ipv4/tcp_output.c
6)tcp_transmit_skb:net/ipv4/tcp_output.c
7)ip_queue_xmit:net/ipv4/ip_output.c
8)ip_queue_xmit2:net/ipv4/ip_output.c
9)ip_output:net/ipv4/ip_output.c
10)ip_finish_output:net/ipv4/ip_output.c
11)ip_finish_output2:net/ipv4/ip_output.c
12)neigh_resolve_output:net/core/neighbour.c
13)dev_queue_xmit:net/core/dev.c
4.3 數(shù)據(jù)接收流程圖
各層主要函數(shù)以及位置功能說明:
1)sock_read:初始化msghdr{}的結(jié)構(gòu)類型變量msg黄虱,并且將需要接收的數(shù)據(jù)存放的地址傳給msg.msg_iov->iov_base. net/socket.c
2)sock_recvmsg: 調(diào)用函數(shù)指針sock->ops->recvmsg()完成在INET Socket層的數(shù)據(jù)接收過程.其中sock->ops被初始化為inet_stream_ops,其成員recvmsg對應的函數(shù)實現(xiàn)為inet_recvmsg()函數(shù). net/socket.c
3)sys_recv()/sys_recvfrom():分別對應著面向連接和面向無連接的協(xié)議兩種情況. net/socket.c
4)inet_recvmsg:調(diào)用sk->prot->recvmsg函數(shù)完成數(shù)據(jù)接收,這個函數(shù)對于tcp協(xié)議便是tcp_recvmsg net/ipv4/af_net.c
5)tcp_recvmsg:從網(wǎng)絡(luò)協(xié)議棧接收數(shù)據(jù)的動作,自上而下的觸發(fā)動作一直到這個函數(shù)為止,出現(xiàn)了一次等待的過程.函數(shù)tcp_recvmsg可能會被動地等待在sk的接收數(shù)據(jù)隊列上,也就是說,系統(tǒng)中肯定有其他地方會去修改這個隊列使得tcp_recvmsg可以進行下去.入口參數(shù)sk是這個網(wǎng)絡(luò)連接對應的sock{}指針,msg用于存放接收到的數(shù)據(jù).接收數(shù)據(jù)的時候會去遍歷接收隊列中的數(shù)據(jù),找到序列號合適的.
但讀取隊列為空時tcp_recvmsg就會調(diào)用tcp_v4_do_rcv使用backlog隊列填充接收隊列.
6)tcp_v4_rcv:tcp_v4_rcv被ip_local_deliver函數(shù)調(diào)用,是從IP層協(xié)議向INET Socket層提交的"數(shù)據(jù)到"請求,入口參數(shù)skb存放接收到的數(shù)據(jù),len是接收的數(shù)據(jù)的長度,這個函數(shù)首先移動skb->data指針,讓它指向tcp頭,然后更新tcp層的一些數(shù)據(jù)統(tǒng)計,然后進行tcp的一些值的校驗.再從INET Socket層中已經(jīng)建立的sock{}結(jié)構(gòu)變量中查找正在等待當前到達數(shù)據(jù)的哪一項.可能這個sock{}結(jié)構(gòu)已經(jīng)建立,或者還處于監(jiān)聽端口稚矿、等待數(shù)據(jù)連接的狀態(tài)。返回的sock結(jié)構(gòu)指針存放在sk中捻浦。然后根據(jù)其他進程對sk的操作情況,將skb發(fā)送到合適的位置.調(diào)用如下:
TCP包接收器(tcp_v4_rcv)將TCP包投遞到目的套接字進行接收處理. 當套接字正被用戶鎖定,TCP包將暫時排入該套接字的后備隊列(sk_add_backlog).這時如果某一用戶線程企圖鎖定該套接字(lock_sock),該線程被排入套接字的后備處理等待隊列(sk->lock.wq).當用戶釋放上鎖的套接字時(release_sock,在tcp_recvmsg中調(diào)用),后備隊列中的TCP包被立即注入TCP包處理器(tcp_v4_do_rcv)進行處理,然后喚醒等待隊列中最先的一個用戶來獲得其鎖定權(quán). 如果套接字未被上鎖,當用戶正在讀取該套接字時, TCP包將被排入套接字的預備隊列(tcp_prequeue),將其傳遞到該用戶線程上下文中進行處理.如果添加到sk->prequeue不成功,便可以添加到 sk->receive_queue隊列中(用戶線程可以登記到預備隊列,當預備隊列中出現(xiàn)第一個包時就喚醒等待線程.) /net/tcp_ipv4.c
7)ip_rcv晤揣、ip_rcv_finish:從以太網(wǎng)接收數(shù)據(jù),放到skb里朱灿,作ip層的一些數(shù)據(jù)及選項檢查昧识,調(diào)用ip_route_input()做路由處理,判斷是進行ip轉(zhuǎn)發(fā)還是將數(shù)據(jù)傳遞到高一層的協(xié)議.調(diào)用skb->dst->input函數(shù)指針,這個指針的實現(xiàn)可能有多種情況,如果路由得到的結(jié)果說明這個數(shù)據(jù)包應該轉(zhuǎn)發(fā)到其他主機,這里的input便是ip_forward;如果數(shù)據(jù)包是給本機的,那么input指針初始化為ip_local_deliver函數(shù)./net/ipv4/ip_input.c
8)ip_local_deliver、ip_local_deliver_finish:入口參數(shù)skb存放需要傳送到上層協(xié)議的數(shù)據(jù),從ip頭中獲取是否已經(jīng)分拆的信息,如果已經(jīng)分拆,則調(diào)用函數(shù)ip_defrag將數(shù)據(jù)包重組盗扒。然后通過調(diào)用ip_prot->handler指針調(diào)用tcp_v4_rcv(tcp)跪楞。ip_prot是inet_protocol結(jié)構(gòu)指針,是用來ip層登記協(xié)議的,比如由udp,tcp,icmp等協(xié)議侣灶。 /net/ipv4/ip_input.c
Linux通過同時對多種通信協(xié)議的支持來提供通用的底層基礎(chǔ)服務甸祭。它的第一個網(wǎng)絡(luò)模型的版本是4.3 BSD,也稱為Net/1褥影,今天的Linux已經(jīng)使用Net/4 (Linux 2.2)池户,其中大多數(shù)代碼已經(jīng)完全和BSD的版本不同,但是它依然支持UINX平臺之間程序的移植。
Linux網(wǎng)絡(luò)套接字實現(xiàn)的模式是UNIX下的普遍標準煞檩。同時,Net/4的網(wǎng)絡(luò)層是完全另起爐灶重寫的栅贴。首先斟湃,新的網(wǎng)絡(luò)層盡可能地實行并行處理, 因此其伸縮性比起以前的版本檐薯,不可同日而語凝赛。其次,它包括了許多的優(yōu)化坛缕,以便繞過不少流行操作系統(tǒng)網(wǎng)絡(luò)實現(xiàn)中的不合理處(例如Windows)墓猎。到目前為止,Linux 是唯一與IPv4和IPv6協(xié)議標準完全保持兼容的操作系統(tǒng)赚楚,而Linux2.4的IPv4伸縮性又大有提高毙沾。
Linux支持的六種不同通信協(xié)議族:
1) TCP/IP (使用TCP/IP的Internet 協(xié)議族),本文討論的重點宠页。
2) UNIX域協(xié)議 (一種進程間通信的協(xié)議)
3) X25協(xié)議
4) AX25協(xié)議 (業(yè)余無線X25)
5)IPX協(xié)議 (Novell IPX)
6) APPLETALK協(xié)議 (AppleTalk DDP)
1.1 內(nèi)核源代碼的組織
表1是本文要使用的Linux Net/4網(wǎng)絡(luò)源代碼的,其中大部分位于目錄/usr/src/linux-2.2.x/net,列表如下左胞,
插口層?
BSD Socket?
/net/socket.c?
/net/protocols.c?
INET Socket?
/ipv4/protocol.c?
/ipv4/af_inet.c?
/net/ipv4/core/sock.c?
協(xié)議層?
TCP/UDP?
/net/ipv4/udp.c?
/net/ipv4/datagram.c?
/net/ipv4/tcp_input.c?
/net/ipv4//tcp_output.c?
/net/ipv4/tcp.c?
/net/ipv4/tcp_minisocks.c?
/net/ipv4/tcp_timer.c etc...?
IP?
/net/ipv4/ip_forward.c?
/net/ipv4/ip_fragment.c?
/net/ipv4/ip_input.c?
/net/ipv4/ip_output.c?
接口層?
Ethernet?
......
1.2 Linux中TCP/IP網(wǎng)絡(luò)層次結(jié)構(gòu)與實現(xiàn)?
Linux通過一組相鄰的軟件層實現(xiàn)了TCP/IP模型,它由BSD Socket層举户、INET
Socket層烤宙、傳輸層、網(wǎng)絡(luò)層俭嘁,和鏈路層構(gòu)成躺枕。應用程序使用系統(tǒng)調(diào)用向內(nèi)核函數(shù)傳遞參數(shù)和數(shù)據(jù)從而進入內(nèi)核空間,由內(nèi)核中注冊的內(nèi)核函數(shù)對相應的數(shù)據(jù)結(jié)構(gòu)進行處理供填。Linux的TCP/IP層次結(jié)構(gòu)和實現(xiàn)方式如圖所示拐云。