linux內核中socket的創建過程源碼分析(總結性質)


     在漫長地分析完socket的創建源碼后,發現一片漿糊,所以特此總結,我的博客中同時有另外一篇詳細的源碼分析,內核版本為3.9,建議在閱讀本文后若還有興趣再去看另外一篇博文。絕對不要單獨看另外一篇。

  

一:調用鏈:

 

二:數據結構

一一看一下每個數據結構的意義:

1) socket, sock, inet_sock, tcp_sock的關系
創建完sk變量后,回到inet_create函數中:

這里是根據sk變量得到inet_sock變量的地址;這里注意區分各個不同結構體。
a. struct socket:這個是基本的BSD socket,面向用戶空間,應用程序通過系統調用開始創建的socket都是該結構體,它是基於虛擬文件系統創建出來的;
類型主要有三種,即流式、數據報、原始套接字協議;

b. struct sock:它是網絡層的socket;對應有TCPUDPRAW三種,面向內核驅動;

其狀態相比socket結構更精細:

c. struct inet_sock:它是INET域的socket表示,是對struct sock的一個擴展,提供INET域的一些屬性,如TTL,組播列表,IP地址,端口等;
d. struct raw_socket:它是RAW協議的一個socket表示,是對struct inet_sock的擴展,它要處理與ICMP相關的內容;
e. sturct udp_sock:它是UDP協議的socket表示,是對struct inet_sock的擴展;
f. struct inet_connection_sock:它是所有面向連接的socket表示,是對struct inet_sock的擴展;

g. struct tcp_sock:它是TCP協議的socket表示,是對struct inet_connection_sock的擴展,主要增加滑動窗口,擁塞控制一些TCP專用屬性;
h. struct inet_timewait_sock:它是網絡層用於超時控制的socket表示;
i. struct tcp_timewait_sock:它是TCP協議用於超時控制的socket表示;

 

三:具體過程

1、函數入口:
1) 示例代碼如下:

 

int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);  


2) 入口:
net/Socket.c:sys_socketcall(),根據子系統調用號,創建socket會執行sys_socket()函數;

2、分配socket結構:
1) 調用鏈:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->sock_alloc();

2) 在socket文件系統中創建i節點:

inode = new_inode(sock_mnt->mnt_sb);  
這里,new_inode函數是文件系統的通用函數,其作用是在相應的文件系統中創建一個inode;其主要代碼如下(fs/Inode.c):

上面有個條件判斷:if (sb->s_op->alloc_inode),意思是說如果當前文件系統的超級塊有自己分配inode的操作函數,則調用它自己的函數分配inode,否則從公用的高速緩存區中分配一塊inode;

3) 創建socket專用inode:
在“socket文件系統注冊”一文中后面提到,在安裝socket文件系統時,會初始化該文件系統的超級塊,此時會初始化超級塊的操作指針s_op為sockfs_ops結構;因此此時分配inode會調用sock_alloc_inode函數來完成:實際上分配了一個socket_alloc結構體,該結構體包含socket和inode,但最終返回的是該結構體中的inode成員;至此,socket結構和inode結構均分配完畢;分配inode后,應用程序便可以通過文件描述符對socket進行read()/write()之類的操作,這個是由虛擬文件系統(VFS)來完成的。

3、根據inode取得socket對象:
由於創建inode是文件系統的通用邏輯,因此其返回值是inode對象的指針;但這里在創建socket的inode后,需要根據inode得到socket對象;內聯函數SOCKET_I由此而來,這里使用兩個重要宏containerof和offsetof


4、使用協議族來初始化socket:

1) 注冊AF_INET協議域:

在“socket文件系統注冊”中提到系統初始化的工作,AF_INET的注冊也正是通過這個來完成的;

初始化入口net/ipv4/Af_inet.c:這里調用sock_register函數來完成注冊:

根據family將AF_INET協議域inet_family_ops注冊到內核中的net_families數組中;下面是其定義:

static struct net_proto_family inet_family_ops = {      .family = PF_INET,       .create = inet_create,      .owner  = THIS_MODULE,  };  

其中,family指定協議域的類型,create指向相應協議域的socket的創建函數;

2) 套接字類型

在相同的協議域下,可能會存在多個套接字類型;如AF_INET域下存在流套接字(SOCK_STREAM),數據報套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),在這三種類型的套接字上建立的協議分別是TCP, UDP,ICMP/IGMP等。

在Linux內核中,結構體struct proto表示域中的一個套接字類型,它提供該類型套接字上的所有操作及相關數據(在內核初始化時會分配相應的高速緩沖區,見上面提到的inet_init函數)。

AF_IENT域的這三種套接字類型定義用結構體inet_protosw(net/ipv4/Af_inet.c)來表示,如下:其中,tcp_prot(net/ipv4/Tcp_ipv4.c)、 udp_prot(net/ipv4/Udp.c)、raw_prot(net/ipv4/Raw.c)分別表示三種類型的套接字,分別表示相應套接字的 操作和相關數據;ops成員提供該協議域的全部操作集合,針對三種不同的套接字類型,有三種不同的域操作inet_stream_ops、 inet_dgram_ops、inet_sockraw_ops,其定義均位於net/ipv4/Af_inet.c下;

內 核初始化時,在inet_init中,會將不同的套接字存放到全局變量inetsw中統一管理;inetsw是一個鏈表數組,每一項都是一個struct inet_protosw結構體的鏈表,總共有SOCK_MAX項,在inet_init函數對AF_INET域進行初始化的時候,調用函數 inet_register_protosw把數組inetsw_array中定義的套接字類型全部注冊到inetsw數組中;其中相同套接字類型,不同 協議類型的套接字通過鏈表存放在到inetsw數組中,以套接字類型為索引,在系統實際使用的時候,只使用inetsw,而不使用 inetsw_array;

 

3) 使用協議域來初始化socket

 

了解了上面的知識后,我們再回到net/Socket.c:sys_socket()->sock_create()->__sock_create()中:

pf = rcu_dereference(net_families[family]);  err = pf->create(net, sock, protocol);  

上面的代碼中,找到內核初始化時注冊的協議域,然后調用其create方法;

5、分配sock結構:

  sk是網絡層對於socket的表示,結構體struct sock比較龐大,這里不詳細列出,只介紹一些重要的成員,sk_prot和sk_prot_creator,這兩個成員指向特定的協議處理函數集,其類型是結構體struct proto,struct proto類型的變量在協議棧中總共也有三個.其調用鏈如下:

net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create();

inet_create()主要完成以下幾個工作:

1) 設置socket的狀態為SS_UNCONNECTED;

 

sock->state = SS_UNCONNECTED;  

 

 

2) 根據socket的type找到對應的套接字類型:

由於同一type不同protocol的套接字保存在inetsw中的同一鏈表中,因此需要遍歷鏈表來查找;在上面的例子中,會將protocol重新賦值為answer->protocol,即IPPROTO_TCP,其值為6;

3) 使用匹配的協議族操作集初始化sk;

結合源碼,sock變量的ops指向inet_stream_ops結構體變量;

4) 分配sock結構體變量 net/Socket.c:sys_socket()->sock_create()->__sock_create()->net /ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sk_alloc():

其中,answer_prot指向tcp_prot結構體變量;

其中,sk_prot_alloc分配sock結構體變量;由於在inet_init中為不同的套接字分配了高速緩沖區,因此該sock結構體變量會在該緩沖區中分配空間;分配完成后,對其做一些初始化工作:

i) 初始化sk變量的sk_prot和sk_prot_creator;
ii) 初始化sk變量的等待隊列;
iii) 設置net空間結構,並增加引用計數;

6、建立socket結構與sock結構的關系:

inet = inet_sk(sk);  

這里為什么能直接將sock結構體變量強制轉化為inet_sock結構體變量呢?只有一種可能,那就是在分配sock結構體變量時,真正分配的是inet_sock或是其他結構體;

我們回到分配sock結構體的那塊代碼(參考前面的5.4小節:net/core/Sock.c):

 

static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority, int family) {      struct sock *sk;      struct kmem_cache *slab;        slab = prot->slab;      if (slab != NULL)          sk = kmem_cache_alloc(slab, priority);      else          sk = kmalloc(prot->obj_size, priority);        return sk;  }  

上面的代碼在分配sock結構體時,有兩種途徑,一是從tcp專用高速緩存中分配;二是從內存直接分配;前者在初始化高速緩存時,指定了結構體大小為prot->obj_size;后者也有指定大小為prot->obj_size,

根據這點,我們看下tcp_prot變量中的obj_size(net/ipv4/Tcp_ipv4.c):

 

.obj_size       = sizeof(struct tcp_sock),  

也就是說,分配的真實結構體是tcp_sock;由於tcp_sock、inet_connection_sock、inet_sock、sock之間均為0處偏移量,因此可以直接將tcp_sock直接強制轉化為inet_sock。


2) 建立socket, sock的關系
創建完sock變量之后,便是初始化sock結構體,並建立sock與socket之間的引用關系;調用鏈如下:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->net /ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sock_init_data():
該函數主要工作是:
a. 初始化sock結構的緩沖區、隊列等;
b. 初始化sock結構的狀態為TCP_CLOSE;
c. 建立socket與sock結構的相互引用關系;


7、使用tcp協議初始化sock:
inet_create()函數最后,通過相應的協議來初始化sock結構:這里調用的是tcp_prot的init鈎子函數net/ipv4/Tcp_ipv4.c:tcp_v4_init_sock(),它主要是對tcp_sock和inet_connection_sock進行一些初始化;

8、socket與文件系統關聯:

創建好與socket相關的結構后,需要與文件系統關聯,詳見sock_map_fd()函數:

1) 申請文件描述符,並分配file結構和目錄項結構;
2) 關聯socket相關的文件操作函數表和目錄項操作函數表;
3) 將file->private_date指向socket;

socket與文件系統關聯后,以后便可以通過文件系統read/write對socket進行操作了;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM