在漫長地分析完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進行操作了;