上一篇文章對Linux sockfs文件系統(tǒng)的注冊和掛載進行了分析,本文在上文基礎(chǔ)上進一步全面分析socket底層的相關(guān)實現(xiàn)。
一、socket與inode
socket在Linux中對應的文件系統(tǒng)叫Sockfs,每創(chuàng)建一個socket,就在sockfs中創(chuàng)建了一個特殊的文件,同時創(chuàng)建了sockfs文件系統(tǒng)中的inode,該inode唯一標識當前socket的通信。
如下圖所示,左側(cè)窗口使用nc工具創(chuàng)建一個TCP連接;右側(cè)找到該進程id(3384),通過查看該進程下的描述符,可以看到"3 ->socket:[86851]",socket表示這是一個socket類型的fd,[86851]表示這個一個inode號,能夠唯一標識當前的這個socket通信連接,進一步在該inode下查看"grep -i "86851" /proc/net/tcp”可以看到該TCP連接的所有信息(連接狀態(tài)、IP地址等),只不過是16進制顯示。
在分析socket與inode之前,先通過ext4文件系統(tǒng)舉例:
在VFS層,即抽象層,所有的文件系統(tǒng)都使用struct inode結(jié)構(gòu)體描述indoe,然而分配inode的方式都不同,如ext4文件系統(tǒng)的分配inode函數(shù)是ext4_alloc_inode,如下所示:
從函數(shù)中可以看出來,函數(shù)其實是調(diào)用kmem_cache_alloc分配了 ext4_inode_info結(jié)構(gòu)體(結(jié)構(gòu)體如下所示),然后進行了一系列的初始化,最后返回的卻是struct inode結(jié)構(gòu)體(如上面代碼的return &ei->vfs_inode)。如下結(jié)構(gòu)體ext4_inode_info(ei)所示,vfs_inode是其struct inode結(jié)構(gòu)體成員。
再看一下:ext4_inode、ext4_inode_info、inode之間的關(guān)聯(lián),
ext4_inode如下所示,是磁盤上inode的結(jié)構(gòu)
ext4_inode_info是ext4文件系統(tǒng)的inode在內(nèi)存中管理結(jié)構(gòu)體:
inode是文件系統(tǒng)抽象層:
三者的關(guān)系如下圖,struct inode是VFS抽象層的表示,ext4_inode_info是ext4文件系統(tǒng)inode在內(nèi)存中的表示,struct ext4_inode是文件系統(tǒng)inode在磁盤中的表示。
VFS采用C語言的方式實現(xiàn)了struct inode和struct ext4_inode_info繼承關(guān)系,inode與ext4_inode_info是父類與子類的關(guān)系,并且Linux內(nèi)核實現(xiàn)了inode與ext4_inode_info父子類的互相轉(zhuǎn)換,如下EXT4_I所示:
以上是以ext4為例進行了分析,下面將開始從socket與inode進行分析:
sockfs是虛擬文件系統(tǒng),所以在磁盤上不存在inode的表示,在內(nèi)核中有struct socket_alloc來表示內(nèi)存中sockfs文件系統(tǒng)inode的相關(guān)結(jié)構(gòu)體:
struct socket與struct inode的關(guān)系如下圖,正如ext4文件系統(tǒng)中struct ext4_inode_info與struct inode的關(guān)系類似,inode和socket_alloc結(jié)構(gòu)體是父類與子類的關(guān)系。
從上面分析ext4文件系統(tǒng)分配inode時,是通過ext4_alloc_inode函數(shù)分配了ext4_inode_info結(jié)構(gòu)體,并初始化結(jié)構(gòu)體成員,函數(shù)最后返回的是ext4_inode_info中的struct inode成員。sockfs文件系統(tǒng)也類似,sockfs文件系統(tǒng)分配inode時,創(chuàng)建的是socket_alloc結(jié)構(gòu)體,在函數(shù)最后返回的是struct inode。
從上篇文章中,分析了sockfs文件系統(tǒng)注冊與掛載,初始化了超級塊的函數(shù)操作集,如下所示alloc_inode是分配inode結(jié)構(gòu)體的回調(diào)函數(shù)接口。
sockfs文件系統(tǒng)的inode分配函數(shù)是sock_alloc_inode,如下所示:
sock_alloc_inode函數(shù)分配了socket_alloc結(jié)構(gòu)體,也就意味著分配了struct socket和struct inode,并最終返回了socket_alloc結(jié)構(gòu)體成員inode。
故struct socket這個字段出生的時候其實就和一個struct inode結(jié)構(gòu)體伴生出來的,它們倆共同封裝在struct socket_alloc中,由sockfs的sock_alloc_inode函數(shù)分配的,函數(shù)返回的是struct inode結(jié)構(gòu)體.和ext4文件系統(tǒng)類型類似。sockfs文件系統(tǒng)也實現(xiàn)了struct inode與struct socket的轉(zhuǎn)換:
二、socket的創(chuàng)建與初始化
首先看一下struct socket在內(nèi)核中的定義:
在內(nèi)核中還有struct sock結(jié)構(gòu)體,在struct socket中可以看到那么它們的關(guān)系是什么?
1、socket面向上層,sock面向下層的具體協(xié)議
2、socket是內(nèi)核抽象出的一個通用結(jié)構(gòu)體,主要是設置了一些跟fs相關(guān)的字段,而真正跟網(wǎng)絡通信相關(guān)的字段結(jié)構(gòu)體是struct sock
3、struct sock是套接字的核心,是對底層具體協(xié)議做的一層抽象封裝,比如TCP協(xié)議,struct sock結(jié)構(gòu)體中的成員sk_prot會賦值為tcp_prot,UDP協(xié)議會賦值為udp_prot。
(關(guān)于更多struct sock的分析將在以后的文章中分析)
創(chuàng)建socket的系統(tǒng)調(diào)用:在用戶空間創(chuàng)建了一個socket后,返回值是一個文件描述符。在SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)最后調(diào)用sock_map_fd進行關(guān)聯(lián),其中返回的就是用戶空間獲取的文件描述符fd,sock就是調(diào)用sock_create創(chuàng)建成功的socket.
socket的創(chuàng)建將調(diào)用sock_create函數(shù):
__sock_create函數(shù)調(diào)用sock_alloc函數(shù)分配socket結(jié)構(gòu)和文件節(jié)點:
socket結(jié)構(gòu)體的創(chuàng)建在sock_alloc()函數(shù)中:
new_inode_pseudo中通過繼續(xù)調(diào)用sockfs文件系統(tǒng)中的sock_alloc_inode函數(shù)完成struct socket_alloc的創(chuàng)建并返回其結(jié)構(gòu)體成員struct inode。
然后調(diào)用SOCKT_I函數(shù)返回對應的struct socket。
在_sock_create中:pf->create(net, sock, protocol, kern);
通過相應的協(xié)議族,進一步調(diào)用不同的socket創(chuàng)建函數(shù)。pf是struct net_proto_family結(jié)構(gòu)體,如下所示:
net_families[]數(shù)組里存放的是各個協(xié)議族的信息,以family字段作為下標,對應的值為net_pro_family結(jié)構(gòu)體。此處我們針對TCP協(xié)議分析,因此我們family字段是AF_INET,pf->create將調(diào)用inet_create函數(shù)繼續(xù)完成底層struct sock等創(chuàng)建和初始化。
inet_create函數(shù)完成struct socket、struct inode、struct sock的創(chuàng)建與初始化后,調(diào)用sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));完成socket與文件系統(tǒng)的關(guān)聯(lián),負責分配文件,并與socket進行綁定:
1、調(diào)用sock_alloc_file,分配一個struct file,并將私有數(shù)據(jù)指針指向socket結(jié)構(gòu)
2、fd_install 對應文件描述符和file
get_unused_fd_flags(flags)繼續(xù)調(diào)用alloc_fd完成文件描述符的分配。
sock_alloc_file(sock, flags, NULL)分配一個struct file結(jié)構(gòu)體
其中file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
&socket_file_ops);分配了file結(jié)構(gòu)體并進行初始化:
其中file->f_op = fop,將socket_file_ops傳遞給文件操作表
以上操作完成了struct socket、struct sock、struct file等的創(chuàng)建、初始化、關(guān)聯(lián),并最終返回socket描述符fd
socket描述符fd和我們平時操作文件的文件描述符相同,那么會有一個疑問,可以看到struct file_operations socket_file_ops函數(shù)表中并沒有提供write()和read()接口,只是看到read_iter,write_iter等接口,那么系統(tǒng)是如何處理的呢?
以write()為例:
sys_write()->__vfs_write()
從__vfs_write函數(shù)中可以看出來,如果socket函數(shù)表中沒有提供write接口函數(shù),則調(diào)用new_sync_write:
call_write_iter:
從以上__vfs_write()分析,如果文件函數(shù)表結(jié)構(gòu)提供了write接口函數(shù)則調(diào)用write函數(shù),如果文件函數(shù)表結(jié)構(gòu)沒有提供write接口函數(shù)(如socket操作函數(shù)表中沒有提供write接口),則調(diào)用write_iter接口,即調(diào)用socket操作函數(shù)表中的sock_write_iter。就這樣通過socket fd進行普通文件系統(tǒng)那樣通過描述符進行讀寫等。
用戶得到socket fd,可以進行地址綁定、發(fā)送以及接收數(shù)據(jù)等操作,在Linux內(nèi)核中有相關(guān)的函數(shù)完成從socket fd到struct socket、struct file的轉(zhuǎn)換:
fdget()函數(shù)從當前進程的files_struct結(jié)構(gòu)中找到網(wǎng)絡文件系統(tǒng)中的file文件指針,并封裝在struct fd結(jié)構(gòu)體中。sock_from函數(shù)通過得到的file結(jié)構(gòu)體得到對應的socket結(jié)構(gòu)指針。sock_from函數(shù)如下:
至此,socket底層來龍去脈的大體結(jié)構(gòu)大概就分析到這,最為核心的struct sock相關(guān)的聯(lián)系以及底層協(xié)議的初始化等將在以后的文章進行分析。
評論