在漫長(zhǎng)地分析完socket的創(chuàng)建源碼后,發(fā)現(xiàn)一片漿糊,所以特此總結(jié),我的博客中同時(shí)有另外一篇詳細(xì)的源碼分析,內(nèi)核版本為3.9,建議在閱讀本文后若還有興趣再去看另外一篇博文。絕對(duì)不要單獨(dú)看另外一篇。
一:調(diào)用鏈:
二:數(shù)據(jù)結(jié)構(gòu)
一一看一下每個(gè)數(shù)據(jù)結(jié)構(gòu)的意義:
1) socket, sock, inet_sock, tcp_sock的關(guān)系
創(chuàng)建完sk變量后,回到inet_create函數(shù)中:
這里是根據(jù)sk變量得到inet_sock變量的地址;這里注意區(qū)分各個(gè)不同結(jié)構(gòu)體。
a. struct socket:這個(gè)是基本的BSD socket,面向用戶空間,應(yīng)用程序通過(guò)系統(tǒng)調(diào)用開始創(chuàng)建的socket都是該結(jié)構(gòu)體,它是基于虛擬文件系統(tǒng)創(chuàng)建出來(lái)的;
類型主要有三種,即流式、數(shù)據(jù)報(bào)、原始套接字協(xié)議;
b. struct sock:它是網(wǎng)絡(luò)層的socket;對(duì)應(yīng)有TCP、UDP、RAW三種,面向內(nèi)核驅(qū)動(dòng);
其狀態(tài)相比socket結(jié)構(gòu)更精細(xì):
c. struct inet_sock:它是INET域的socket表示,是對(duì)struct sock的一個(gè)擴(kuò)展,提供INET域的一些屬性,如TTL,組播列表,IP地址,端口等;
d. struct raw_socket:它是RAW協(xié)議的一個(gè)socket表示,是對(duì)struct inet_sock的擴(kuò)展,它要處理與ICMP相關(guān)的內(nèi)容;
e. sturct udp_sock:它是UDP協(xié)議的socket表示,是對(duì)struct inet_sock的擴(kuò)展;
f. struct inet_connection_sock:它是所有面向連接的socket表示,是對(duì)struct inet_sock的擴(kuò)展;
g. struct tcp_sock:它是TCP協(xié)議的socket表示,是對(duì)struct inet_connection_sock的擴(kuò)展,主要增加滑動(dòng)窗口,擁塞控制一些TCP專用屬性;
h. struct inet_timewait_sock:它是網(wǎng)絡(luò)層用于超時(shí)控制的socket表示;
i. struct tcp_timewait_sock:它是TCP協(xié)議用于超時(shí)控制的socket表示;
三:具體過(guò)程
1、函數(shù)入口:
1) 示例代碼如下:
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
2) 入口:
net/Socket.c:sys_socketcall(),根據(jù)子系統(tǒng)調(diào)用號(hào),創(chuàng)建socket會(huì)執(zhí)行sys_socket()函數(shù);
2、分配socket結(jié)構(gòu):
1) 調(diào)用鏈:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->sock_alloc();
2) 在socket文件系統(tǒng)中創(chuàng)建i節(jié)點(diǎn):
inode = new_inode(sock_mnt->mnt_sb);
這里,new_inode函數(shù)是文件系統(tǒng)的通用函數(shù),其作用是在相應(yīng)的文件系統(tǒng)中創(chuàng)建一個(gè)inode;其主要代碼如下(fs/Inode.c):
上面有個(gè)條件判斷:if (sb->s_op->alloc_inode),意思是說(shuō)如果當(dāng)前文件系統(tǒng)的超級(jí)塊有自己分配inode的操作函數(shù),則調(diào)用它自己的函數(shù)分配inode,否則從公用的高速緩存區(qū)中分配一塊inode;
3) 創(chuàng)建socket專用inode:
在“socket文件系統(tǒng)注冊(cè)”一文中后面提到,在安裝socket文件系統(tǒng)時(shí),會(huì)初始化該文件系統(tǒng)的超級(jí)塊,此時(shí)會(huì)初始化超級(jí)塊的操作指針s_op為sockfs_ops結(jié)構(gòu);因此此時(shí)分配inode會(huì)調(diào)用sock_alloc_inode函數(shù)來(lái)完成:實(shí)際上分配了一個(gè)socket_alloc結(jié)構(gòu)體,該結(jié)構(gòu)體包含socket和inode,但最終返回的是該結(jié)構(gòu)體中的inode成員;至此,socket結(jié)構(gòu)和inode結(jié)構(gòu)均分配完畢;分配inode后,應(yīng)用程序便可以通過(guò)文件描述符對(duì)socket進(jìn)行read()/write()之類的操作,這個(gè)是由虛擬文件系統(tǒng)(VFS)來(lái)完成的。
3、根據(jù)inode取得socket對(duì)象:
由于創(chuàng)建inode是文件系統(tǒng)的通用邏輯,因此其返回值是inode對(duì)象的指針;但這里在創(chuàng)建socket的inode后,需要根據(jù)inode得到socket對(duì)象;內(nèi)聯(lián)函數(shù)SOCKET_I由此而來(lái),這里使用兩個(gè)重要宏containerof和offsetof
4、使用協(xié)議族來(lái)初始化socket:
1) 注冊(cè)AF_INET協(xié)議域:
在“socket文件系統(tǒng)注冊(cè)”中提到系統(tǒng)初始化的工作,AF_INET的注冊(cè)也正是通過(guò)這個(gè)來(lái)完成的;
初始化入口net/ipv4/Af_inet.c:這里調(diào)用sock_register函數(shù)來(lái)完成注冊(cè):
根據(jù)family將AF_INET協(xié)議域inet_family_ops注冊(cè)到內(nèi)核中的net_families數(shù)組中;下面是其定義:
static struct net_proto_family inet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE, };
其中,family指定協(xié)議域的類型,create指向相應(yīng)協(xié)議域的socket的創(chuàng)建函數(shù);
2) 套接字類型
在相同的協(xié)議域下,可能會(huì)存在多個(gè)套接字類型;如AF_INET域下存在流套接字(SOCK_STREAM),數(shù)據(jù)報(bào)套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),在這三種類型的套接字上建立的協(xié)議分別是TCP, UDP,ICMP/IGMP等。
在Linux內(nèi)核中,結(jié)構(gòu)體struct proto表示域中的一個(gè)套接字類型,它提供該類型套接字上的所有操作及相關(guān)數(shù)據(jù)(在內(nèi)核初始化時(shí)會(huì)分配相應(yīng)的高速緩沖區(qū),見上面提到的inet_init函數(shù))。
AF_IENT域的這三種套接字類型定義用結(jié)構(gòu)體inet_protosw(net/ipv4/Af_inet.c)來(lái)表示,如下:其中,tcp_prot(net/ipv4/Tcp_ipv4.c)、 udp_prot(net/ipv4/Udp.c)、raw_prot(net/ipv4/Raw.c)分別表示三種類型的套接字,分別表示相應(yīng)套接字的 操作和相關(guān)數(shù)據(jù);ops成員提供該協(xié)議域的全部操作集合,針對(duì)三種不同的套接字類型,有三種不同的域操作inet_stream_ops、 inet_dgram_ops、inet_sockraw_ops,其定義均位于net/ipv4/Af_inet.c下;
內(nèi) 核初始化時(shí),在inet_init中,會(huì)將不同的套接字存放到全局變量inetsw中統(tǒng)一管理;inetsw是一個(gè)鏈表數(shù)組,每一項(xiàng)都是一個(gè)struct inet_protosw結(jié)構(gòu)體的鏈表,總共有SOCK_MAX項(xiàng),在inet_init函數(shù)對(duì)AF_INET域進(jìn)行初始化的時(shí)候,調(diào)用函數(shù) inet_register_protosw把數(shù)組inetsw_array中定義的套接字類型全部注冊(cè)到inetsw數(shù)組中;其中相同套接字類型,不同 協(xié)議類型的套接字通過(guò)鏈表存放在到inetsw數(shù)組中,以套接字類型為索引,在系統(tǒng)實(shí)際使用的時(shí)候,只使用inetsw,而不使用 inetsw_array;
3) 使用協(xié)議域來(lái)初始化socket
了解了上面的知識(shí)后,我們?cè)倩氐絥et/Socket.c:sys_socket()->sock_create()->__sock_create()中:
pf = rcu_dereference(net_families[family]); err = pf->create(net, sock, protocol);
上面的代碼中,找到內(nèi)核初始化時(shí)注冊(cè)的協(xié)議域,然后調(diào)用其create方法;
5、分配sock結(jié)構(gòu):
sk是網(wǎng)絡(luò)層對(duì)于socket的表示,結(jié)構(gòu)體struct sock比較龐大,這里不詳細(xì)列出,只介紹一些重要的成員,sk_prot和sk_prot_creator,這兩個(gè)成員指向特定的協(xié)議處理函數(shù)集,其類型是結(jié)構(gòu)體struct proto,struct proto類型的變量在協(xié)議棧中總共也有三個(gè).其調(diào)用鏈如下:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create();
inet_create()主要完成以下幾個(gè)工作:
1) 設(shè)置socket的狀態(tài)為SS_UNCONNECTED;
sock->state = SS_UNCONNECTED;
2) 根據(jù)socket的type找到對(duì)應(yīng)的套接字類型:
由于同一type不同protocol的套接字保存在inetsw中的同一鏈表中,因此需要遍歷鏈表來(lái)查找;在上面的例子中,會(huì)將protocol重新賦值為answer->protocol,即IPPROTO_TCP,其值為6;
3) 使用匹配的協(xié)議族操作集初始化sk;
結(jié)合源碼,sock變量的ops指向inet_stream_ops結(jié)構(gòu)體變量;
4) 分配sock結(jié)構(gòu)體變量 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結(jié)構(gòu)體變量;
其中,sk_prot_alloc分配sock結(jié)構(gòu)體變量;由于在inet_init中為不同的套接字分配了高速緩沖區(qū),因此該sock結(jié)構(gòu)體變量會(huì)在該緩沖區(qū)中分配空間;分配完成后,對(duì)其做一些初始化工作:
i) 初始化sk變量的sk_prot和sk_prot_creator;
ii) 初始化sk變量的等待隊(duì)列;
iii) 設(shè)置net空間結(jié)構(gòu),并增加引用計(jì)數(shù);
6、建立socket結(jié)構(gòu)與sock結(jié)構(gòu)的關(guān)系:
inet = inet_sk(sk);
這里為什么能直接將sock結(jié)構(gòu)體變量強(qiáng)制轉(zhuǎn)化為inet_sock結(jié)構(gòu)體變量呢?只有一種可能,那就是在分配sock結(jié)構(gòu)體變量時(shí),真正分配的是inet_sock或是其他結(jié)構(gòu)體;
我們回到分配sock結(jié)構(gòu)體的那塊代碼(參考前面的5.4小節(jié):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結(jié)構(gòu)體時(shí),有兩種途徑,一是從tcp專用高速緩存中分配;二是從內(nèi)存直接分配;前者在初始化高速緩存時(shí),指定了結(jié)構(gòu)體大小為prot->obj_size;后者也有指定大小為prot->obj_size,
根據(jù)這點(diǎn),我們看下tcp_prot變量中的obj_size(net/ipv4/Tcp_ipv4.c):
.obj_size = sizeof(struct tcp_sock),
也就是說(shuō),分配的真實(shí)結(jié)構(gòu)體是tcp_sock;由于tcp_sock、inet_connection_sock、inet_sock、sock之間均為0處偏移量,因此可以直接將tcp_sock直接強(qiáng)制轉(zhuǎn)化為inet_sock。
2) 建立socket, sock的關(guān)系
創(chuàng)建完sock變量之后,便是初始化sock結(jié)構(gòu)體,并建立sock與socket之間的引用關(guān)系;調(diào)用鏈如下:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->net /ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sock_init_data():
該函數(shù)主要工作是:
a. 初始化sock結(jié)構(gòu)的緩沖區(qū)、隊(duì)列等;
b. 初始化sock結(jié)構(gòu)的狀態(tài)為TCP_CLOSE;
c. 建立socket與sock結(jié)構(gòu)的相互引用關(guān)系;
7、使用tcp協(xié)議初始化sock:
inet_create()函數(shù)最后,通過(guò)相應(yīng)的協(xié)議來(lái)初始化sock結(jié)構(gòu):這里調(diào)用的是tcp_prot的init鉤子函數(shù)net/ipv4/Tcp_ipv4.c:tcp_v4_init_sock(),它主要是對(duì)tcp_sock和inet_connection_sock進(jìn)行一些初始化;
8、socket與文件系統(tǒng)關(guān)聯(lián):
創(chuàng)建好與socket相關(guān)的結(jié)構(gòu)后,需要與文件系統(tǒng)關(guān)聯(lián),詳見sock_map_fd()函數(shù):
1) 申請(qǐng)文件描述符,并分配file結(jié)構(gòu)和目錄項(xiàng)結(jié)構(gòu);
2) 關(guān)聯(lián)socket相關(guān)的文件操作函數(shù)表和目錄項(xiàng)操作函數(shù)表;
3) 將file->private_date指向socket;
socket與文件系統(tǒng)關(guān)聯(lián)后,以后便可以通過(guò)文件系統(tǒng)read/write對(duì)socket進(jìn)行操作了;