上一篇文章對Linux sockfs文件系統(tǒng)的注冊和掛載進行了分析,本文在上文基礎上進一步全面分析socket底層的相關實現。
一、socket與inode
socket在Linux中對應的文件系統(tǒng)叫Sockfs,每創(chuàng)建一個socket,就在sockfs中創(chuàng)建了一個特殊的文件,同時創(chuàng)建了sockfs文件系統(tǒng)中的inode,該inode唯一標識當前socket的通信。
如下圖所示,左側窗口使用nc工具創(chuàng)建一個TCP連接;右側找到該進程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結構體描述indoe,然而分配inode的方式都不同,如ext4文件系統(tǒng)的分配inode函數是ext4_alloc_inode,如下所示:

從函數中可以看出來,函數其實是調用kmem_cache_alloc分配了 ext4_inode_info結構體(結構體如下所示),然后進行了一系列的初始化,最后返回的卻是struct inode結構體(如上面代碼的return &ei->vfs_inode)。如下結構體ext4_inode_info(ei)所示,vfs_inode是其struct inode結構體成員。


再看一下:ext4_inode、ext4_inode_info、inode之間的關聯(lián),
ext4_inode如下所示,是磁盤上inode的結構

ext4_inode_info是ext4文件系統(tǒng)的inode在內存中管理結構體:

inode是文件系統(tǒng)抽象層:



三者的關系如下圖,struct inode是VFS抽象層的表示,ext4_inode_info是ext4文件系統(tǒng)inode在內存中的表示,struct ext4_inode是文件系統(tǒng)inode在磁盤中的表示。

VFS采用C語言的方式實現了struct inode和struct ext4_inode_info繼承關系,inode與ext4_inode_info是父類與子類的關系,并且Linux內核實現了inode與ext4_inode_info父子類的互相轉換,如下EXT4_I所示:

以上是以ext4為例進行了分析,下面將開始從socket與inode進行分析:
sockfs是虛擬文件系統(tǒng),所以在磁盤上不存在inode的表示,在內核中有struct socket_alloc來表示內存中sockfs文件系統(tǒng)inode的相關結構體:

struct socket與struct inode的關系如下圖,正如ext4文件系統(tǒng)中struct ext4_inode_info與struct inode的關系類似,inode和socket_alloc結構體是父類與子類的關系。

從上面分析ext4文件系統(tǒng)分配inode時,是通過ext4_alloc_inode函數分配了ext4_inode_info結構體,并初始化結構體成員,函數最后返回的是ext4_inode_info中的struct inode成員。sockfs文件系統(tǒng)也類似,sockfs文件系統(tǒng)分配inode時,創(chuàng)建的是socket_alloc結構體,在函數最后返回的是struct inode。
從上篇文章中,分析了sockfs文件系統(tǒng)注冊與掛載,初始化了超級塊的函數操作集,如下所示alloc_inode是分配inode結構體的回調函數接口。

sockfs文件系統(tǒng)的inode分配函數是sock_alloc_inode,如下所示:

sock_alloc_inode函數分配了socket_alloc結構體,也就意味著分配了struct socket和struct inode,并最終返回了socket_alloc結構體成員inode。
故struct socket這個字段出生的時候其實就和一個struct inode結構體伴生出來的,它們倆共同封裝在struct socket_alloc中,由sockfs的sock_alloc_inode函數分配的,函數返回的是struct inode結構體.和ext4文件系統(tǒng)類型類似。sockfs文件系統(tǒng)也實現了struct inode與struct socket的轉換:

二、socket的創(chuàng)建與初始化
首先看一下struct socket在內核中的定義:

在內核中還有struct sock結構體,在struct socket中可以看到那么它們的關系是什么?
1、socket面向上層,sock面向下層的具體協(xié)議
2、socket是內核抽象出的一個通用結構體,主要是設置了一些跟fs相關的字段,而真正跟網絡通信相關的字段結構體是struct sock
3、struct sock是套接字的核心,是對底層具體協(xié)議做的一層抽象封裝,比如TCP協(xié)議,struct sock結構體中的成員sk_prot會賦值為tcp_prot,UDP協(xié)議會賦值為udp_prot。
(關于更多struct sock的分析將在以后的文章中分析)
創(chuàng)建socket的系統(tǒng)調用:在用戶空間創(chuàng)建了一個socket后,返回值是一個文件描述符。在SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)最后調用sock_map_fd進行關聯(lián),其中返回的就是用戶空間獲取的文件描述符fd,sock就是調用sock_create創(chuàng)建成功的socket.

socket的創(chuàng)建將調用sock_create函數:

__sock_create函數調用sock_alloc函數分配socket結構和文件節(jié)點:


socket結構體的創(chuàng)建在sock_alloc()函數中:

new_inode_pseudo中通過繼續(xù)調用sockfs文件系統(tǒng)中的sock_alloc_inode函數完成struct socket_alloc的創(chuàng)建并返回其結構體成員struct inode。
然后調用SOCKT_I函數返回對應的struct socket。
在_sock_create中:pf->create(net, sock, protocol, kern);
通過相應的協(xié)議族,進一步調用不同的socket創(chuàng)建函數。pf是struct net_proto_family結構體,如下所示:

net_families[]數組里存放的是各個協(xié)議族的信息,以family字段作為下標,對應的值為net_pro_family結構體。此處我們針對TCP協(xié)議分析,因此我們family字段是AF_INET,pf->create將調用inet_create函數繼續(xù)完成底層struct sock等創(chuàng)建和初始化。
inet_create函數完成struct socket、struct inode、struct sock的創(chuàng)建與初始化后,調用sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));完成socket與文件系統(tǒng)的關聯(lián),負責分配文件,并與socket進行綁定:
1、調用sock_alloc_file,分配一個struct file,并將私有數據指針指向socket結構
2、fd_install 對應文件描述符和file

get_unused_fd_flags(flags)繼續(xù)調用alloc_fd完成文件描述符的分配。
sock_alloc_file(sock, flags, NULL)分配一個struct file結構體

其中file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
&socket_file_ops);分配了file結構體并進行初始化:

其中file->f_op = fop,將socket_file_ops傳遞給文件操作表

以上操作完成了struct socket、struct sock、struct file等的創(chuàng)建、初始化、關聯(lián),并最終返回socket描述符fd

socket描述符fd和我們平時操作文件的文件描述符相同,那么會有一個疑問,可以看到struct file_operations socket_file_ops函數表中并沒有提供write()和read()接口,只是看到read_iter,write_iter等接口,那么系統(tǒng)是如何處理的呢?
以write()為例:
sys_write()->__vfs_write()

從__vfs_write函數中可以看出來,如果socket函數表中沒有提供write接口函數,則調用new_sync_write:

call_write_iter:

從以上__vfs_write()分析,如果文件函數表結構提供了write接口函數則調用write函數,如果文件函數表結構沒有提供write接口函數(如socket操作函數表中沒有提供write接口),則調用write_iter接口,即調用socket操作函數表中的sock_write_iter。就這樣通過socket fd進行普通文件系統(tǒng)那樣通過描述符進行讀寫等。
用戶得到socket fd,可以進行地址綁定、發(fā)送以及接收數據等操作,在Linux內核中有相關的函數完成從socket fd到struct socket、struct file的轉換:

fdget()函數從當前進程的files_struct結構中找到網絡文件系統(tǒng)中的file文件指針,并封裝在struct fd結構體中。sock_from函數通過得到的file結構體得到對應的socket結構指針。sock_from函數如下:

至此,socket底層來龍去脈的大體結構大概就分析到這,最為核心的struct sock相關的聯(lián)系以及底層協(xié)議的初始化等將在以后的文章進行分析。
電子發(fā)燒友App





















評論