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

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

  

一:調用鏈:數組

 

二:數據結構緩存

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

1) socket, sock, inet_sock, tcp_sock的關係
建立完sk變量後,回到inet_create函數中:數據結構

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

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

其狀態相比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表示;spa

 

三:具體過程

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

 

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


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

二、分配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)來完成的。

三、根據inode取得socket對象:
因爲建立inode是文件系統的通用邏輯,所以其返回值是inode對象的指針;但這裏在建立socket的inode後,須要根據inode獲得socket對象;內聯函數SOCKET_I由此而來,這裏使用兩個重要宏containerof和offsetof


四、使用協議族來初始化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方法;

五、分配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空間結構,並增長引用計數;

六、創建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結構的相互引用關係;


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

八、socket與文件系統關聯:

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

1) 申請文件描述符,並分配file結構和目錄項結構;2) 關聯socket相關的文件操做函數表和目錄項操做函數表;3) 將file->private_date指向socket;socket與文件系統關聯後,之後即可以經過文件系統read/write對socket進行操做了;

相關文章
相關標籤/搜索