linux內核中socket的建立過程源碼分析(詳細分析)

1三個相關數據結構.node

關於socket的建立,首先須要分析socket這個結構體,這是整個的核心。linux

104 struct socket {算法

105         socket_state            state;編程

106 數組

107         kmemcheck_bitfield_begin(type);緩存

108         short                   type;安全

109         kmemcheck_bitfield_end(type);服務器

110 網絡

111         unsigned long           flags;數據結構

112 

113         struct socket_wq __rcu  *wq;

114 

115         struct file             *file;

116         struct sock             *sk;

117         const struct proto_ops  *ops;

118 }

其中,state是socket的狀態,好比CONNECTED,type是類型,好比TCP下使用的流式套接字SOCK_STREAM,flags是標誌位,負責一些特殊的設置,好比SOCK_ASYNC_NOSPACE,ops則是採用了和超級塊設備操做表同樣的邏輯,專門設置了一個數據結構來記錄其容許的操做。Sk是很是重要的,也是很是大的,負責記錄協議相關內容。這樣的設置使得socket具備很好的協議無關性,能夠通用。file是與socket相關的指針列表,wq是等待隊列。

還有兩個結構體sk_buff和tcp_sock,其中sk_buff與每個數據包相關,負責描述每個數據包的信息,而tcp_sock則是tcp相關的結構體

2.初始化並分配socket結構

Socket()本質上是一個glibc中的函數,執行其實是是調用sys_socketcall()系統調用。sys_socketcall()是幾乎全部socket相關函數的入口,便是說,bind,connect等等函數都須要sys_socketcall()做爲入口。該系統調用代碼以下:

2435 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

2436 {

2437         unsigned long a[6];

2438         unsigned long a0, a1;

2439         int err;

2440         unsigned int len;

2441 

2442         if (call < 1 || call > SYS_SENDMMSG)

2443                 return -EINVAL;

2444 

2445         len = nargs[call];

2446         if (len > sizeof(a))

2447                 return -EINVAL;

2448 

2449         /* copy_from_user should be SMP safe. */

2450         if (copy_from_user(a, args, len))

2451                 return -EFAULT;

2452 

2453         audit_socketcall(nargs[call] / sizeof(unsigned long), a);

2454 

2455         a0 = a[0];

2456         a1 = a[1];

2457 

2458         switch (call) {

2459         case SYS_SOCKET:

2460                 err = sys_socket(a0, a1, a[2]);

2461                 break;

2462         case SYS_BIND:

2463                 err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

2464                 break;

.......

2531         default:

2532                 err = -EINVAL;

2533                 break;

2534         }

2535         return err;

2536 }

省略號省略掉的是各類網絡編程用獲得的函數,所有都在這個系統調用中,再次證實前文所說,socketcall是系統全部socket相關函數打大門。

而對於建立socket,天然會在switch中調用到sys_socket()系統調用,而這個函數僅僅是調用sock_create()來建立socket和sock_map_fd()來與文件系統進行關聯。其中sock_create()調用__sock_create().接下來我盡力地來分析一下源代碼(直接在代碼中註釋):

1257 int __sock_create(struct net *net, int family, int type, int protocol,

1258                          struct socket **res, int kern)

1259 {

1260         int err;

1261         struct socket *sock;

1262         const struct net_proto_family *pf;

/*檢查傳入的協議族參數是否正確*/

1267         if (family < 0 || family >= NPROTO)

1268                 return -EAFNOSUPPORT;

/*檢查傳入的類型參數是否正確*/

1269         if (type < 0 || type >= SOCK_MAX)

1270                 return -EINVAL;

/*檢查兼容性。在使用中,family是PF仍是AF沒什麼區別,但事實上兩者PF是POSIX標準,而AF是BSD標準,按照個人理解,如今大部分使用BSD標準,所以,若是傳入參數是使用的PF,那麼在內核打印消息,並把family設置成PF*/

1277         if (family == PF_INET && type == SOCK_PACKET) {

1278                 static int warned;

1279                 if (!warned) {

1280                         warned = 1;

1281                         printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",

1282                                current->comm);

1283                 }

1284                 family = PF_PACKET;

1285         }

1286 /*這裏調用security_socket_create函數,該函數是調用security_ops->socket_create(family, type, protocol, kern),而security_ops是一個security_operations的結構體,此處再次使用了linux經常使用的手段,即提供一個結構體數據結構來描述全部的操做,該結構體異常複雜,暫時還沒能力理解,只知道該結構體是對應着linux系統下的LMS安全框架的。這裏實際上是個空函數,其實是進行一系列的安全檢查,而後若是成功則返回0*/

1287         err = security_socket_create(family, type, protocol, kern);

1288         if (err)

1289                 return err;

/*此處開始須要再分析多個函數,再也不以註釋的方式進行分析,在源代碼的後面一一分析*/

1296         sock = sock_alloc();

1297         if (!sock) {

1298                 net_warn_ratelimited("socket: no more sockets\n");

1299                 return -ENFILE; /* Not exactly a match, but its the

1300                                    closest posix thing */

1301         }

1302 

1303         sock->type = type;

1304 

1305 #ifdef CONFIG_MODULES

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

1314 #endif

1315 

1316         rcu_read_lock();

1317         pf = rcu_dereference(net_families[family]);

1318         err = -EAFNOSUPPORT;

1319         if (!pf)

1320                 goto out_release;

1321 

1326         if (!try_module_get(pf->owner))

1327                 goto out_release;

1328 

1330         rcu_read_unlock();

1331 

1332         err = pf->create(net, sock, protocol, kern);

1333         if (err < 0)

1334                 goto out_module_put;

1335 

1340         if (!try_module_get(sock->ops->owner))

1341                 goto out_module_busy;

1342 

1347         module_put(pf->owner);

1348         err = security_socket_post_create(sock, family, type, protocol, kern);

1349         if (err)

1350                 goto out_sock_release;

1351         *res = sock;

1352 

1353         return 0;

1354 

1355 out_module_busy:

1356         err = -EAFNOSUPPORT;

1357 out_module_put:

1358         sock->ops = NULL;

1359         module_put(pf->owner);

1360 out_sock_release:

1361         sock_release(sock);

1362         return err;

1363 

1364 out_release:

1365         rcu_read_unlock();

1366         goto out_sock_release;

1367 }

如註釋中說的,接下來分析sock = sock_alloc(),首先看sock_alloc()的源代碼:

530 static struct socket *sock_alloc(void)

531 {

532         struct inode *inode;

533         struct socket *sock;

534 

535         inode = new_inode_pseudo(sock_mnt->mnt_sb);

536         if (!inode)

537                 return NULL;

538 

539         sock = SOCKET_I(inode);

540 

541         kmemcheck_annotate_bitfield(sock, type);

542         inode->i_ino = get_next_ino();

543         inode->i_mode = S_IFSOCK | S_IRWXUGO;

544         inode->i_uid = current_fsuid();

545         inode->i_gid = current_fsgid();

546         inode->i_op = &sockfs_inode_ops;

547 

548         this_cpu_add(sockets_in_use, 1);

549         return sock;

550 }

藉助LXR上的註釋咱們進行分析。該函數式分配並初始化一個socket和一個inode,兩者是捆綁在一塊兒的,若是成功則返回socket,若是inode建立出問題,則返回NULL。此處有點複雜,耦合性有點強,慢慢來分析。首先分析SOCKET_I(),該函數的目的是取得socket的結構體指針,具體分析以下:

1322 static inline struct socket *SOCKET_I(struct inode *inode)

1323 {

1324         return &container_of(inode, struct socket_alloc, vfs_inode)->socket;

1325 }

這裏使用了宏container_of,咱們繼續分析container_of宏:

718 #define container_of(ptr, type, member) ({                      \

719         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

720         (type *)( (char *)__mptr - offsetof(type,member) );})

首先分析傳入參數,ptr是指向返回對象的指針,有點繞,但前文說過,inode和socket是綁在一塊兒的,此處其實就是inode指向socket的意思。而後是type,在SOCKET_I中傳入的事socket_alloc結構體,該結構體以下:

1317 struct socket_alloc {

1318         struct socket socket;

1319         struct inode vfs_inode;

1320 };只包含了socket結構體和在虛擬文件系統中的節點結構體,無甚複雜。宏container_of中還包含了另一個宏offsetof:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)。

由此分析container_of的內容。首先是offsetof,是假設TYPE(即struct sock_alloc)的地址在0的時候其中MEMBER的地址,便是在計算MEMBER在結構體中的偏移地址,因此這個宏取名爲offsetof也不爲怪了。回過頭看container_of,這個宏分兩部分:

第一部分typeof( ((type *)0)->member ) *__mptr = (ptr),經過typeof定義一個struct sock_alloc->vfs_inode的指針__mptr,而後賦值爲ptr的值,便是說如今vfs_inode的地址就是ptr(即inode)的地址。

第二部分 (type *)( (char *)__mptr - offsetof(type,member) ),用vfs_inode的地址去減去它在結構體中的偏移地址,天然獲得結構體的首地址,又由於該結構體的第一個成員是struct socket socket,因此天然返回的是socket的地址,再經過(type *)進行強制類型轉換,因此SOCKET_I就獲得了新的socket的指針了。

這裏的兩個宏有點複雜,但實在是功能強大,值得學習。

這裏有一個值得注意的地方,以前分析了這麼久的struct sock_alloc,但咱們並無對這個結構體進行初始化。因此在new_inode_pseudo(sock_mnt->mnt_sb)中必然對這個結構體有所交代。該函數會調用alloc_inode(sb)系統調用,sb是一個超級塊結構。

該系統調用的源代碼顯示若是傳進去的超級快表操做是alloc_inode的話,則執行執行之。能夠看到,傳進去的參數是sock_mnt->mnt_sb,再查找該參數發現這個參數是在初始化sock_init中進行kern_mount時賦值的,剛好使得傳進去的參數是alloc_inode,因此,接下來執行超級快操做表中的alloc_inode,而後調用sock_alloc_inode系統調用。

該系統調用先是ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);進行slab分配,爾後的代碼也是進行inode的操做和slab的分配,到此爲止,暫時再也不繼續深刻,只是注意到通過這一系列地分配以後,socket處於未鏈接狀態,即socket.state = SS_UNCONNECTED。

至此回到__sock_create()繼續分析。接下來是一次條件編譯。#ifdef CONFIG_MODULES,這個選項是用於linux的模塊編寫的,若是在模塊編寫時用上了CONFIG_MODULES,是會在makemenuconfig中出現該模塊選項的。該處若是有CONFIG_MODULES可是卻沒有找到對應的下屬選項則會裝載一個default的模塊。

接下去是:

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

這兩句檢查對應協議族的操做表是否已經安裝,若是沒有安裝則使用request_module進行安裝。如今都是在TCP/IP協議下進行分析,因此family是AF_INET,也就是2,因此實際檢查的是全局變量net_families[2]。這個全局變量是在系統初始化時由net/ipv4/af_inet.c文件進行安裝,具體代碼是:fs_initcall(inet_init);而fs_initcall是個宏,具體實現是:

204 #define fs_initcall(fn)                 __define_initcall(fn, 5)

便是把inet_init裝載在init_call中,因此在系統啓動時天然會初始化。

1三個相關數據結構.

關於socket的建立,首先須要分析socket這個結構體,這是整個的核心。

104 struct socket {

105         socket_state            state;

106 

107         kmemcheck_bitfield_begin(type);

108         short                   type;

109         kmemcheck_bitfield_end(type);

110 

111         unsigned long           flags;

112 

113         struct socket_wq __rcu  *wq;

114 

115         struct file             *file;

116         struct sock             *sk;

117         const struct proto_ops  *ops;

118 }

其中,state是socket的狀態,好比CONNECTED,type是類型,好比TCP下使用的流式套接字SOCK_STREAM,flags是標誌位,負責一些特殊的設置,好比SOCK_ASYNC_NOSPACE,ops則是採用了和超級塊設備操做表同樣的邏輯,專門設置了一個數據結構來記錄其容許的操做。Sk是很是重要的,也是很是大的,負責記錄協議相關內容。這樣的設置使得socket具備很好的協議無關性,能夠通用。file是與socket相關的指針列表,wq是等待隊列。

還有兩個結構體sk_buff和tcp_sock,其中sk_buff與每個數據包相關,負責描述每個數據包的信息,而tcp_sock則是tcp相關的結構體

3.初始化並分配socket結構

Socket()本質上是一個glibc中的函數,執行其實是是調用sys_socketcall()系統調用。sys_socketcall()是幾乎全部socket相關函數的入口,便是說,bind,connect等等函數都須要sys_socketcall()做爲入口。該系統調用代碼以下:

2435 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

2436 {

2437         unsigned long a[6];

2438         unsigned long a0, a1;

2439         int err;

2440         unsigned int len;

2441 

2442         if (call < 1 || call > SYS_SENDMMSG)

2443                 return -EINVAL;

2444 

2445         len = nargs[call];

2446         if (len > sizeof(a))

2447                 return -EINVAL;

2448 

2449         /* copy_from_user should be SMP safe. */

2450         if (copy_from_user(a, args, len))

2451                 return -EFAULT;

2452 

2453         audit_socketcall(nargs[call] / sizeof(unsigned long), a);

2454 

2455         a0 = a[0];

2456         a1 = a[1];

2457 

2458         switch (call) {

2459         case SYS_SOCKET:

2460                 err = sys_socket(a0, a1, a[2]);

2461                 break;

2462         case SYS_BIND:

2463                 err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

2464                 break;

.......

2531         default:

2532                 err = -EINVAL;

2533                 break;

2534         }

2535         return err;

2536 }

省略號省略掉的是各類網絡編程用獲得的函數,所有都在這個系統調用中,再次證實前文所說,socketcall是系統全部socket相關函數打大門。

而對於建立socket,天然會在switch中調用到sys_socket()系統調用,而這個函數僅僅是調用sock_create()來建立socket和sock_map_fd()來與文件系統進行關聯。其中sock_create()調用__sock_create().接下來我盡力地來分析一下源代碼(直接在代碼中註釋):

1257 int __sock_create(struct net *net, int family, int type, int protocol,

1258                          struct socket **res, int kern)

1259 {

1260         int err;

1261         struct socket *sock;

1262         const struct net_proto_family *pf;

/*檢查傳入的協議族參數是否正確*/

1267         if (family < 0 || family >= NPROTO)

1268                 return -EAFNOSUPPORT;

/*檢查傳入的類型參數是否正確*/

1269         if (type < 0 || type >= SOCK_MAX)

1270                 return -EINVAL;

/*檢查兼容性。在使用中,family是PF仍是AF沒什麼區別,但事實上兩者PF是POSIX標準,而AF是BSD標準,按照個人理解,如今大部分使用BSD標準,所以,若是傳入參數是使用的PF,那麼在內核打印消息,並把family設置成PF*/

1277         if (family == PF_INET && type == SOCK_PACKET) {

1278                 static int warned;

1279                 if (!warned) {

1280                         warned = 1;

1281                         printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",

1282                                current->comm);

1283                 }

1284                 family = PF_PACKET;

1285         }

1286 /*這裏調用security_socket_create函數,該函數是調用security_ops->socket_create(family, type, protocol, kern),而security_ops是一個security_operations的結構體,此處再次使用了linux經常使用的手段,即提供一個結構體數據結構來描述全部的操做,該結構體異常複雜,暫時還沒能力理解,只知道該結構體是對應着linux系統下的LMS安全框架的。這裏實際上是個空函數,其實是進行一系列的安全檢查,而後若是成功則返回0*/

1287         err = security_socket_create(family, type, protocol, kern);

1288         if (err)

1289                 return err;

/*此處開始須要再分析多個函數,再也不以註釋的方式進行分析,在源代碼的後面一一分析*/

1296         sock = sock_alloc();

1297         if (!sock) {

1298                 net_warn_ratelimited("socket: no more sockets\n");

1299                 return -ENFILE; /* Not exactly a match, but its the

1300                                    closest posix thing */

1301         }

1302 

1303         sock->type = type;

1304 

1305 #ifdef CONFIG_MODULES

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

1314 #endif

1315 

1316         rcu_read_lock();

1317         pf = rcu_dereference(net_families[family]);

1318         err = -EAFNOSUPPORT;

1319         if (!pf)

1320                 goto out_release;

1321 

1326         if (!try_module_get(pf->owner))

1327                 goto out_release;

1328 

1330         rcu_read_unlock();

1331 

1332         err = pf->create(net, sock, protocol, kern);

1333         if (err < 0)

1334                 goto out_module_put;

1335 

1340         if (!try_module_get(sock->ops->owner))

1341                 goto out_module_busy;

1342 

1347         module_put(pf->owner);

1348         err = security_socket_post_create(sock, family, type, protocol, kern);

1349         if (err)

1350                 goto out_sock_release;

1351         *res = sock;

1352 

1353         return 0;

1354 

1355 out_module_busy:

1356         err = -EAFNOSUPPORT;

1357 out_module_put:

1358         sock->ops = NULL;

1359         module_put(pf->owner);

1360 out_sock_release:

1361         sock_release(sock);

1362         return err;

1363 

1364 out_release:

1365         rcu_read_unlock();

1366         goto out_sock_release;

1367 }

如註釋中說的,接下來分析sock = sock_alloc(),首先看sock_alloc()的源代碼:

530 static struct socket *sock_alloc(void)

531 {

532         struct inode *inode;

533         struct socket *sock;

534 

535         inode = new_inode_pseudo(sock_mnt->mnt_sb);

536         if (!inode)

537                 return NULL;

538 

539         sock = SOCKET_I(inode);

540 

541         kmemcheck_annotate_bitfield(sock, type);

542         inode->i_ino = get_next_ino();

543         inode->i_mode = S_IFSOCK | S_IRWXUGO;

544         inode->i_uid = current_fsuid();

545         inode->i_gid = current_fsgid();

546         inode->i_op = &sockfs_inode_ops;

547 

548         this_cpu_add(sockets_in_use, 1);

549         return sock;

550 }

藉助LXR上的註釋咱們進行分析。該函數式分配並初始化一個socket和一個inode,兩者是捆綁在一塊兒的,若是成功則返回socket,若是inode建立出問題,則返回NULL。此處有點複雜,耦合性有點強,慢慢來分析。首先分析SOCKET_I(),該函數的目的是取得socket的結構體指針,具體分析以下:

1322 static inline struct socket *SOCKET_I(struct inode *inode)

1323 {

1324         return &container_of(inode, struct socket_alloc, vfs_inode)->socket;

1325 }

這裏使用了宏container_of,咱們繼續分析container_of宏:

718 #define container_of(ptr, type, member) ({                      \

719         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \

720         (type *)( (char *)__mptr - offsetof(type,member) );})

首先分析傳入參數,ptr是指向返回對象的指針,有點繞,但前文說過,inode和socket是綁在一塊兒的,此處其實就是inode指向socket的意思。而後是type,在SOCKET_I中傳入的事socket_alloc結構體,該結構體以下:

1317 struct socket_alloc {

1318         struct socket socket;

1319         struct inode vfs_inode;

1320 };只包含了socket結構體和在虛擬文件系統中的節點結構體,無甚複雜。宏container_of中還包含了另一個宏offsetof:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)。

由此分析container_of的內容。首先是offsetof,是假設TYPE(即struct sock_alloc)的地址在0的時候其中MEMBER的地址,便是在計算MEMBER在結構體中的偏移地址,因此這個宏取名爲offsetof也不爲怪了。回過頭看container_of,這個宏分兩部分:

第一部分typeof( ((type *)0)->member ) *__mptr = (ptr),經過typeof定義一個struct sock_alloc->vfs_inode的指針__mptr,而後賦值爲ptr的值,便是說如今vfs_inode的地址就是ptr(即inode)的地址。

第二部分 (type *)( (char *)__mptr - offsetof(type,member) ),用vfs_inode的地址去減去它在結構體中的偏移地址,天然獲得結構體的首地址,又由於該結構體的第一個成員是struct socket socket,因此天然返回的是socket的地址,再經過(type *)進行強制類型轉換,因此SOCKET_I就獲得了新的socket的指針了。

這裏的兩個宏有點複雜,但實在是功能強大,值得學習。

這裏有一個值得注意的地方,以前分析了這麼久的struct sock_alloc,但咱們並無對這個結構體進行初始化。因此在new_inode_pseudo(sock_mnt->mnt_sb)中必然對這個結構體有所交代。該函數會調用alloc_inode(sb)系統調用,sb是一個超級塊結構。

該系統調用的源代碼顯示若是傳進去的超級快表操做是alloc_inode的話,則執行執行之。能夠看到,傳進去的參數是sock_mnt->mnt_sb,再查找該參數發現這個參數是在初始化sock_init中進行kern_mount時賦值的,剛好使得傳進去的參數是alloc_inode,因此,接下來執行超級快操做表中的alloc_inode,而後調用sock_alloc_inode系統調用。

該系統調用先是ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);進行slab分配,爾後的代碼也是進行inode的操做和slab的分配,到此爲止,暫時再也不繼續深刻,只是注意到通過這一系列地分配以後,socket處於未鏈接狀態,即socket.state = SS_UNCONNECTED。

至此回到__sock_create()繼續分析。接下來是一次條件編譯。#ifdef CONFIG_MODULES,這個選項是用於linux的模塊編寫的,若是在模塊編寫時用上了CONFIG_MODULES,是會在makemenuconfig中出現該模塊選項的。該處若是有CONFIG_MODULES可是卻沒有找到對應的下屬選項則會裝載一個default的模塊。

接下去是:

1312         if (rcu_access_pointer(net_families[family]) == NULL)

1313                 request_module("net-pf-%d", family);

這兩句檢查對應協議族的操做表是否已經安裝,若是沒有安裝則使用request_module進行安裝。如今都是在TCP/IP協議下進行分析,因此family是AF_INET,也就是2,因此實際檢查的是全局變量net_families[2]。這個全局變量是在系統初始化時由net/ipv4/af_inet.c文件進行安裝,具體代碼是:fs_initcall(inet_init);而fs_initcall是個宏,具體實現是:

204 #define fs_initcall(fn)                 __define_initcall(fn, 5)

便是把inet_init裝載在init_call中,因此在系統啓動時天然會初始化。下面不計細節地分析inet_init:

1678 static int __init inet_init(void)

1679 {

...............

/*把各類proto註冊到全局鏈表中去*/

1690         rc = proto_register(&tcp_prot, 1);

1691         if (rc)

1692                 goto out_free_reserved_ports;

1693 

1694         rc = proto_register(&udp_prot, 1);

1695         if (rc)

1696                 goto out_unregister_tcp_proto;

1697 

1698         rc = proto_register(&raw_prot, 1);

1699         if (rc)

1700                 goto out_unregister_udp_proto;

.......................

/*註冊協議族操做表*/

1710         (void)sock_register(&inet_family_ops);

.....................

/*把各個協議對應的基礎原型(base protocol)加到對應的數據結構中*/

1720         if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)

1721                 pr_crit("%s: Cannot add ICMP protocol\n", __func__);

1722         if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)

1723                 pr_crit("%s: Cannot add UDP protocol\n", __func__);

1724         if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)

1725                 pr_crit("%s: Cannot add TCP protocol\n", __func__);

..................

1731         /* Register the socket-side information for inet_create. */

1732         for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)

1733                 INIT_LIST_HEAD(r);

/*把inetsw_array[]註冊進基礎原型(base protocol)的數組鏈表中*/

1735         for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)

1736                 inet_register_protosw(q);

..............

1802 }

至此回到__socket_create()的分析。pf = rcu_dereference(net_families[family]);是在RCU鎖的保護下取得指定處的內容。if (!try_module_get(pf->owner))是模塊檢查。由於以後要調用->create,這個剛好有可能存在於某個可裝載的模塊中,因此先檢查是否在模塊中,不在的話繼續執行下去。

而後是err = pf->create(net, sock, protocol, kern);這裏調用pf中的create成員,由前面的inet_init()中的sock_register可知,把inet_family_ops裝載進去,而這張表的create成員(也能夠叫行爲)掛入的是inet_create(),下面分析inet_create()。這個函數有些龐大,一部分一部分地看:

275 static int inet_create(struct net *net, struct socket *sock, int protocol,

276                        int kern)

277 {

278         struct sock *sk;

279         struct inet_protosw *answer;

280         struct inet_sock *inet;

281         struct proto *answer_prot;

282         unsigned char answer_flags;

283         char answer_no_check;

284         int try_loading_module = 0;

285         int err;

/*由inet_ehash_secret的值看是否有加密字符串,若沒有則檢查協議類型(只有TCP肯能有加密字符串),若是socket類型不是原始套接字或者數據報型的話,則建立一個加密字符串*/

287         if (unlikely(!inet_ehash_secret))

288                 if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)

289                         build_ehash_secret();

/*把socket的狀態設置成未聯通*/

291         sock->state = SS_UNCONNECTED;

 

 

接下來涉及到inet_protosw結構體,該結構體是用於IP協議對應socket接口,分析以下:

  struct inet_protosw {

         struct list_head list;

        unsigned short   type;     /* 對應socket的類型)*/.        unsigned short   protocol; /* IP協議編碼  */

          struct proto     *prot;    /*對應的協議結構體的指針*/

            const struct proto_ops *ops;  /*對應協議的操做表*/

            char             no_check;   /* 是否計算校驗和 */

            unsigned char    flags;      /* 標誌位  */

 };

 

接下來繼續分析inet_create():

/* 遍歷尋找請求的協議類型 */

294 lookup_protocol:

295         err = -ESOCKTNOSUPPORT;

296         rcu_read_lock();

/* 遍歷inetsw[]數組對應請求類型的鏈表元素,此處須要看一下inetsw[]的定義。這個數組由三個結構變量組成。第一種用於TCP數據流協議,標識碼爲IPPROTO_TCP,第二種用於UDP數據包協議,協議標識碼爲IPPROTO_UDP,第三種爲原始套接字使用,能夠是用戶本身的協議,標識碼IPPROTO_IP是虛擬IP協議類型。這個數組是在inet_init()中由inet_register_protosw來註冊的,該函數的主要步驟爲1.檢查參數結構體inet_protosw是否越界2.經過type查找匹配的隊列3.插入到隊列中。

而後看list_for_each_rcu,這個函數在數組inetsw[]中根據sock->type(通常程序指定爲SOCK_STREAM即TCP協議類型)找到協議類型所在隊列並使得answer指向了TCP協議的inet_protosw結構。爲後面的判斷作好鋪墊。

*/

297         list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

298 

299                 err = 0;

/*因爲在服務器程序中通常引用listenfd = socket(AF_INET,SOCK_STREAM,0)的代碼,因此此處的protocol=0,而answer->protocol由以前的list_for_each_rcu可知是TCP的,而IPPROTO_IP是屬於虛擬IP類型,與原始套接字相關。因此此處301-313的代碼負責選擇出合適的協議*/

301                 if (protocol == answer->protocol) {

302                         if (protocol != IPPROTO_IP)

303                                 break;

304                 } else {

306                         if (IPPROTO_IP == protocol) {

307                                 protocol = answer->protocol;

308                                 break;

309                         }

310                         if (IPPROTO_IP == answer->protocol)

311                                 break;

312                 }

313                 err = -EPROTONOSUPPORT;

314         }

/*此處根據err的值和已經加載了的模塊數量動態的安裝本次請求須要的協議類型的結構,但看到了unlikely,再分析前面的代碼發現err很大可能(最起碼在咱們通常性的TCP/IP編程中)此時仍是等於0的,便是說,因爲TCP協議的結構已經安裝,沒必要再安裝,直接進入下一步*/

316         if (unlikely(err)) {

317                 if (try_loading_module < 2) {

318                         rcu_read_unlock();

321 *(net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)

323                         if (++try_loading_module == 1)

324                                  request_module("net-pf-%d-proto-%d-type-%d",

325                                                PF_INET, protocol, sock->type);

/*不然就使用通用名稱*/

330                         else

331                                 request_module("net-pf-%d-proto-%d",

332                                                PF_INET, protocol);

333                         goto lookup_protocol;

334                 } else

335                         goto out_rcu_unlock;

336         }

337 

338         err = -EPERM;

/*檢查通用性。只有root纔有權限使用原始套接字。*/

339         if (sock->type == SOCK_RAW && !kern &&

340             !ns_capable(net->user_ns, CAP_NET_RAW))

341                 goto out_rcu_unlock;

/*對socket的操做集合進行了掛鉤,指向了answer->ops,因爲在inet_protosw的設置中已經設置成了TCP協議相關,因此此處sock->ops設置爲inet_stream_ops,而answer_prot設置爲tcp_prot結構。該結構式一個struct proto結構,負責傳輸層使用。*/

343         sock->ops = answer->ops;

344         answer_prot = answer->prot;

345         answer_no_check = answer->no_check;

346         answer_flags = answer->flags;

347         rcu_read_unlock();

348 

349         WARN_ON(answer_prot->slab == NULL);

350 

351         err = -ENOBUFS;

/*此處調用sk_alloc分配一個struct sock,該結構體龐大,其做用是網絡層對socket的表示,意思就是IP協議下有不少東西好比IP地址,網卡接口,端口等等信息須要再socket層中有所體現從而使編程者方便使用,而後就利用指針等形式把內容進行必定程度上的映射。sk_alloc首先對sock->proto和sock_creator進行設置,設置成當前協議對應的proto調用sk_prot_alloc()根據是否提供了slab緩存而判斷是使用slab緩存仍是通用緩存。只要分配成功,則調用sock_lock_init()對緩存進行初始化,主要是對sock鎖、等待隊列以及進程數據結構中的網絡空間結構進行分配。初始化完了後調用sock_net_set()函數對網絡空間結構進行記錄,而後最後增長一個net計數器。至此回到inet_create,判斷是否成功分配*/

352         sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);

353         if (sk == NULL)

354                 goto out;

355 

356         err = 0;

/*可複用性檢查,不懂*/

357         sk->sk_no_check = answer_no_check;

358         if (INET_PROTOSW_REUSE & answer_flags)

359                 sk->sk_reuse = SK_CAN_REUSE;

/*返回一個struct inet_sock的指針給inet*/

361         inet = inet_sk(sk);

/*判斷是否是面向連通(目前只有SOCK_STREAM是面向連通的,不能夠理解成TCP,由於ICMP也是面向連通的)*/

362         inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

363 

364         inet->nodefrag = 0;

/*判斷是不是原始套接字,若是是,新建IP頭部*/

366         if (SOCK_RAW == sock->type) {

367                 inet->inet_num = protocol;

368                 if (IPPROTO_RAW == protocol)

369                         inet->hdrincl = 1;

370         }

/*判斷是否採用路徑MTU發現算法*/

372         if (ipv4_config.no_pmtu_disc)

373                 inet->pmtudisc = IP_PMTUDISC_DONT;

374         else

375                 inet->pmtudisc = IP_PMTUDISC_WANT;

376 

377         inet->inet_id = 0;

/*進一步初始化sk結構(struct sock),此處分析一下sock_init_data:初始化接收(讀)隊列,初始化寫隊列,初始化錯誤信息隊列,注意這三個隊列都是雙向鏈表,屬於sk_buff_head結構體,其中會把sk_buff結構體串聯起來。初始化數據包發送定時器,變量(主要是函數指針即鉤子函數)*/

379         sock_init_data(sock, sk);

380 

381         sk->sk_destruct    = inet_sock_destruct;

382         sk->sk_protocol    = protocol;

383         sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

384 

385         inet->uc_ttl    = -1;

386         inet->mc_loop   = 1;

387         inet->mc_ttl    = 1;

388         inet->mc_all    = 1;

389         inet->mc_index  = 0;

390         inet->mc_list   = NULL;

391         inet->rcv_tos   = 0;

392 

393         sk_refcnt_debug_inc(sk);

394   

395         if (inet->inet_num) {

396                 /* It assumes that any protocol which allows

397                  * the user to assign a number at socket

398                  * creation time automatically

399                  * shares.

400                  */

401                 inet->inet_sport = htons(inet->inet_num);

402                 /* Add to protocol hash chains. */

403                 sk->sk_prot->hash(sk);

404         }

/*查閱前面的sock_alloc會發現,這裏的sk_prot被設置爲answer_prot(即answer->prot),而answer_prot又被設置成了tcp_prot,因此此處調用tcp_prot中的init,查閱tcp_prot便可獲得init這個函數指針指向的是tcp_v4_init_sock(),接下來分析tcp_v4_init_sock(見源代碼後面)*/

406         if (sk->sk_prot->init) {

407                 err = sk->sk_prot->init(sk);

408                 if (err)

409                         sk_common_release(sk);

410         }

411 out:

412         return err;

413 out_rcu_unlock:

414         rcu_read_unlock();

415         goto out;

416 }

這裏分析tcp_v4_init_sock,這個函數負責初始化TCP的IPV4的部分:

2150 static int tcp_v4_init_sock(struct sock *sk)

2151 {

2152         struct inet_connection_sock *icsk = inet_csk(sk);

2153 

2154         tcp_init_sock(sk);

2155 

2156         icsk->icsk_af_ops = &ipv4_specific;

2157 

2158 #ifdef CONFIG_TCP_MD5SIG

2159         tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;

2160 #endif

2161 

2162         return 0;

2163 }

這裏涉及兩個新的數據結構tcp_sock與inet_connection_sock,其實還有一個數據結構inet_sock。以前提過struct socket是面向用戶態的,而struct sock是面向內核驅動的,但socket與內核協議棧的交互其實是經過xxx_sock這個數據機構,此處是TCP協議,天然是tcp_sock 數據結構,保存全部tcp相關信息。而inet_connection_sock其實是inet_sock的擴展,以前說過inet_sock是INET域在socket的表現,而inet_connection_sock就是在inet_sock的基礎上保存鏈接信息。

tcp_v4_init_sock中的tcp_init_sock(sk)是具體的初始化舉措,尤爲重要的是對SIN和AFK這兩個SYN幀的初始化。

至此inet_create函數分析完畢,同時sock_create函數也分析完畢,接下來分析sock_map_fd():

386 static int sock_map_fd(struct socket *sock, int flags)

387 {

388         struct file *newfile;

389         int fd = get_unused_fd_flags(flags);

390         if (unlikely(fd < 0))

391                 return fd;

392 

393         newfile = sock_alloc_file(sock, flags, NULL);

394         if (likely(!IS_ERR(newfile))) {

395                 fd_install(fd, newfile);

396                 return fd;

397         }

398 

399         put_unused_fd(fd);

400         return PTR_ERR(newfile);

401 }

首先明白在用戶空間中操做socket是徹底當成文件在操做,但以前只分配了相應的inode和網絡空間結構,因此本函數的目的是與文件系統造成關聯。

本函數在3.9中與2.6內核區別較大,一句一句地分析。get_unused_fd_flags會調用__alloc_fd(),該函數負責分配一個文件描述符並設置成busy狀態。而後執行sock_alloc_file,該函數負責獲得第一個沒被使用的文件,並對其path,狀態等等進行設置,而後再把文件操做表socket_file_ops裝載好。而後執行fd_install(fd, newfile),把文件描述符和文件進行關聯。最後執行put_unused_fd。

至此整個socket的建立完成。

總結一下此次分析的過程。對分析源碼的步驟有了新的想法,首先應該想到要完成這一步從理論上須要完成哪些東西,而後再遞歸地去分析這些東西又須要完成那些步驟。而後根據每一個步驟去看由哪些代碼執行,好比在socket的建立中,我就應該從文件系統初始化,通用socket初始化,TCP初始化,INET初始化,sock初始化,協議對應等等部分來分析,而不是盲目地一步一步地去看。

其次,不能糾纏於細節,個別有重大意義的細節能夠仔細分析,但更多的時候應該一塊一塊地去看代碼看這些代碼完成了什麼功能,而不是糾結於每一句想完成什麼。

最後也是最重要的,在分析源碼以前須要先分析整個過程涉及的數據結構以及他們存在的目的,他們之間的關係,只有在數據結構清晰明瞭了以後纔可能對過程有較爲清晰容易地理解。

相關文章
相關標籤/搜索