1. 程式人生 > >linux核心中socket的建立過程原始碼分析(總結性質)

linux核心中socket的建立過程原始碼分析(總結性質)

http://www.jianshu.com/p/5d82a685b5b6

在漫長地分析完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;對應有TCP、UDP、RAW三種,面向核心驅動;

其狀態相比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進行操作了;



文/ice_camel(簡書作者)
原文連結:http://www.jianshu.com/p/5d82a685b5b6
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。