Socket與內核調用深度分析


1 概念

Linux的設計哲學之一就是:對不同的操作賦予不同的執行等級,就是所謂特權的概念,即與系統相關的一些特別關鍵的操作必須由最高特權的程序來完成。
Intel的X86架構的CPU提供了0到3四個特權級,數字越小,特權越高,Linux操作系統中主要采用了0和3兩個特權級,分別對應的就是內核態(Kernel Mode)用戶態(User Mode)

  • 內核態:CPU可以訪問內存所有數據,包括外圍設備(硬盤、網卡),CPU也可以將自己從一個程序切換到另一個程序;
  • 用戶態:只能受限的訪問內存,且不允許訪問外圍設備,占用CPU的能力被剝奪,CPU資源可以被其他程序獲取;

Linux中任何一個用戶進程被創建時都包含2個棧:內核棧,用戶棧,並且是進程私有的,從用戶態開始運行。內核態和用戶態分別對應內核空間與用戶空間,內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。不管是內核空間還是用戶空間,它們都處於虛擬空間中。

2 內核空間相關

  • 內核空間:存放的是內核代碼和數據,處於虛擬空間;
  • 內核態:當進程執行系統調用而進入內核代碼中執行時,稱進程處於內核態,此時CPU處於特權級最高的0級內核代碼中執行,當進程處於內核態時,執行的內核代碼會使用當前進程的內核棧,每個進程都有自己的內核棧;
  • CPU堆棧指針寄存器指向:內核棧地址;
  • 內核棧:進程處於內核態時使用的棧,存在於內核空間;
  • 處於內核態進程的權利:處於內核態的進程,當它占有CPU的時候,可以訪問內存所有數據和所有外設,比如硬盤,網卡等等;

3 用戶空間相關

  • 用戶空間:存放的是用戶程序的代碼和數據,處於虛擬空間;
  • 用戶態:當進程在執行用戶自己的代碼(非系統調用之類的函數)時,則稱其處於用戶態,CPU在特權級最低的3級用戶代碼中運行,當正在執行用戶程序而突然被中斷程序中斷時,此時用戶程序也可以象征性地稱為處於進程的內核態,因為中斷處理程序將使用當前進程的內核棧;
  • CPU堆棧指針寄存器指向:用戶堆棧地址;
  • 用戶堆棧:進程處於用戶態時使用的堆棧,存在於用戶空間;
  • 處於用戶態進程的權利:處於用戶態的進程,當它占有CPU的時候,只可以訪問有限的內存,而且不允許訪問外設,這里說的有限的內存其實就是用戶空間,使用的是用戶堆棧;

4 scoket創建過程分析

  在用戶進程中,socket(int domain, int type, int protocol) 函數用於創建socket並返回一個與socket關聯的fd,該函數實際執行的是系統調用 sys_socketcall,sys_socketcall幾乎是用戶進程socket所有操作函數的入口:

1 /** sys_socketcall (linux/syscalls.h)*/
2 asmlinkage long sys_socketcall(int call, unsigned long __user *args);

 

  sys_socketcall 實際調用的是 SYSCALL_DEFINE2:

 1 /** SYSCALL_DEFINE2 (net/socket.c)*/
 2 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
 3 {
 4     unsigned long a[AUDITSC_ARGS];
 5     unsigned long a0, a1;
 6     int err;
 7     unsigned int len;
 8     // 省略...
 9     a0 = a[0];
10     a1 = a[1];
11 
12     switch (call) {
13     case SYS_SOCKET:
14         // 與 socket(int domain, int type, int protocol) 對應,創建socket
15         err = sys_socket(a0, a1, a[2]);  
16         break;
17     case SYS_BIND:
18         err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]); 
19         break;
20     case SYS_CONNECT:
21         err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
22         break;
23 // 省略...
24 }

 

  在 SYSCALL_DEFINE2 函數中,通過判斷call指令,來統一處理 socket 相關函數的事務,對於socket(…)函數,實際處理是在 sys_socket 中,也是一個系統調用,對應的是 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol):

 1 /** SYSCALL_DEFINE3 net/socket.c*/
 2 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
 3 {
 4     int retval;
 5     struct socket *sock;
 6     int flags;
 7     // SOCK_TYPE_MASK: 0xF; SOCK_STREAM等socket類型位於type字段的低4位
 8     // 將flag設置為除socket基本類型之外的值
 9     flags = type & ~SOCK_TYPE_MASK;
10 
11     // 如果flags中有除SOCK_CLOEXEC或者SOCK_NONBLOCK之外的其他參數,則返回EINVAL
12     if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
13         return -EINVAL;
14 
15     // 取type中的后4位,即sock_type,socket基本類型定義
16     type &= SOCK_TYPE_MASK;
17 
18     // 如果設置了SOCK_NONBLOCK,則不論SOCK_NONBLOCK定義是否與O_NONBLOCK相同,
19     // 均將flags中的SOCK_NONBLOCK復位,將O_NONBLOCK置位
20     if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
21         flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
22 
23     // 創建socket結構,(重點分析)
24     retval = sock_create(family, type, protocol, &sock);
25     if (retval < 0)
26         goto out;
27 
28     if (retval == 0)
29         sockev_notify(SOCKEV_SOCKET, sock);
30 
31     // 將socket結構映射為文件描述符retval並返回,(重點分析)
32     retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
33     if (retval < 0)
34         goto out_release;
35 out:
36     return retval;
37 out_release:
38     sock_release(sock);
39     return retval;
40 }

 

  SYSCALL_DEFINE3 中主要判斷了設置的socket類型type,如果設置了除基本sock_type,SOCK_CLOEXEC和SOCK_NONBLOCK之外的其他參數,則直接返回;同時調用 sock_create 創建 socket 結構,使用 sock_map_fd 將socket 結構映射為文件描述符並返回。在分析 sock_create 之前,先看看socket結構體:

 1 /** socket結構體 (linux/net.h)*/
 2 struct socket {
 3     socket_state        state;       // 連接狀態:SS_CONNECTING, SS_CONNECTED 等
 4     short           type;            // 類型:SOCK_STREAM, SOCK_DGRAM 等
 5     unsigned long       flags;       // 標志位:SOCK_ASYNC_NOSPACE(發送隊列是否已滿)等
 6     struct socket_wq __rcu  *wq;     // 等待隊列
 7     struct file     *file;           // 該socket結構體對應VFS中的file指針
 8     struct sock     *sk;             // socket網絡層表示,真正處理網絡協議的地方
 9     const struct proto_ops  *ops;    // socket操作函數集:bind, connect, accept 等
10 };

 

  socket結構體中定義了socket的基本狀態,類型,標志,等待隊列,文件指針,操作函數集等,利用 sock 結構,將 socket 操作與真正處理網絡協議相關的事務分離。
回到 sock_create 繼續看socket創建過程,sock_create 實際調用的是 __sock_create:

 1 /** __sock_create (net/socket.c)*/
 2 int __sock_create(struct net *net, int family, int type, int protocol,
 3              struct socket **res, int kern)
 4 {
 5     int err;
 6     struct socket *sock;
 7     const struct net_proto_family *pf;
 8 
 9     // 檢查是否是支持的地址族,即檢查協議
10     if (family < 0 || family >= NPROTO)
11         return -EAFNOSUPPORT;
12     // 檢查是否是支持的socket類型
13     if (type < 0 || type >= SOCK_MAX)
14         return -EINVAL;
15 
16     // 省略...
17 
18     // 檢查權限,並考慮協議集、類型、協議,以及 socket 是在內核中創建還是在用戶空間中創建
19     // 可以參考:https://www.ibm.com/developerworks/cn/linux/l-selinux/
20     err = security_socket_create(family, type, protocol, kern);
21     if (err)
22         return err;
23 
24     // 分配socket結構,這其中創建了socket和關聯的inode (重點分析)
25     sock = sock_alloc();
26     if (!sock) {
27         net_warn_ratelimited("socket: no more sockets\n");
28         return -ENFILE; /* Not exactly a match, but its the
29                    closest posix thing */
30     }
31     sock->type = type;
32     // 省略...
33 }

 

  __socket_create 檢查了地址族協議和socket類型,同時,調用 security_socket_create 檢查創建socket的權限(如:創建不同類型不同地址族socket的SELinux權限也會不同)。接着,來看看 sock_alloc:

  

 1 /** sock_alloc (net/socket.c)*/
 2 static struct socket *sock_alloc(void)
 3 {
 4     struct inode *inode;
 5     struct socket *sock;
 6 
 7     // 在已掛載的sockfs文件系統的super_block上分配一個inode
 8     inode = new_inode_pseudo(sock_mnt->mnt_sb);
 9     if (!inode)
10         return NULL;
11 
12     // 獲取inode對應socket_alloc中的socket結構指針
13     sock = SOCKET_I(inode);
14 
15     inode->i_ino = get_next_ino();
16     inode->i_mode = S_IFSOCK | S_IRWXUGO;
17     inode->i_uid = current_fsuid();
18     inode->i_gid = current_fsgid();
19 
20     // 將inode的操作函數指針指向 sockfs_inode_ops 函數地址
21     inode->i_op = &sockfs_inode_ops;
22 
23     this_cpu_add(sockets_in_use, 1);
24     return sock;
25 }

 

  new_inode_pseudo 函數實際調用的是 alloc_inode(struct super_block *sb) 函數:

  
 1 /** alloc_inode (fs/inode.c)*/
 2 static struct inode *alloc_inode(struct super_block *sb)
 3 {
 4     struct inode *inode;
 5 
 6     // 如果文件系統的超級塊已經指定了alloc_inode的函數,則調用已經定義的函數去分配inode
 7     // 對於sockfs,已經將alloc_inode指向sock_alloc_inode函數指針
 8     if (sb->s_op->alloc_inode)
 9         inode = sb->s_op->alloc_inode(sb);
10     else
11         // 否則在公用的 inode_cache slab緩存上分配inode
12         inode = kmem_cache_alloc(inode_cachep, GFP_KERNEL);
13 
14     if (!inode)
15         return NULL;
16 
17     // 編譯優化,提高執行效率,inode_init_always正常返回0
18     if (unlikely(inode_init_always(sb, inode))) {
19         if (inode->i_sb->s_op->destroy_inode)
20             inode->i_sb->s_op->destroy_inode(inode);
21         else
22             kmem_cache_free(inode_cachep, inode);
23         return NULL;
24     }
25 
26     return inode;
27 }

 

  從前文 “socket文件系統注冊” 提到的:”.alloc_inode = sock_alloc_inode” 可知,alloc_inode 實際將使用 sock_alloc_inode 函數去分配 inode:

  
 1 /** sock_alloc_inode (net/socket.c)*/
 2 static struct inode *sock_alloc_inode(struct super_block *sb)
 3 {
 4     // socket_alloc 結構體包含一個socket和一個inode,將兩者聯系到一起
 5     struct socket_alloc *ei;
 6     struct socket_wq *wq;
 7 
 8     // 在sock_inode_cachep緩存上分配一個socket_alloc
 9     // sock_inode_cachep: 前文"socket文件系統注冊"中已經提到,專用於分配socket_alloc結構
10     ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
11     if (!ei)
12         return NULL;
13     // 分配socket等待隊列結構
14     wq = kmalloc(sizeof(*wq), GFP_KERNEL);
15     if (!wq) {
16         kmem_cache_free(sock_inode_cachep, ei);
17         return NULL;
18     }
19     // 初始化等待隊列
20     init_waitqueue_head(&wq->wait);
21     wq->fasync_list = NULL;
22     wq->flags = 0;
23     // 將socket_alloc中socket的等待隊列指向wq
24     RCU_INIT_POINTER(ei->socket.wq, wq);
25 
26     // 初始化socket的狀態,標志,操作集等
27     ei->socket.state = SS_UNCONNECTED;
28     ei->socket.flags = 0;
29     ei->socket.ops = NULL;
30     ei->socket.sk = NULL;
31     ei->socket.file = NULL;
32 
33     // 返回socket_alloc中的inode
34     return &ei->vfs_inode;
35 }

 

5 內核跟蹤

  我們首先在 sys_socketcall 處建立斷點
在這里插入圖片描述
  捕獲到sys_socketcall,對應的內核處理函數為SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
  在net/socket.c中查看SYSCALL_DEFINE2相關的源代碼如下:

  1 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
  2 {
  3     unsigned long a[AUDITSC_ARGS];
  4     unsigned long a0, a1;
  5     int err;
  6     unsigned int len;
  7 
  8     if (call < 1 || call > SYS_SENDMMSG)
  9         return -EINVAL;
 10     call = array_index_nospec(call, SYS_SENDMMSG + 1);
 11 
 12     len = nargs[call];
 13     if (len > sizeof(a))
 14         return -EINVAL;
 15 
 16     /* copy_from_user should be SMP safe. */
 17     if (copy_from_user(a, args, len))
 18         return -EFAULT;
 19 
 20     err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
 21     if (err)
 22         return err;
 23 
 24     a0 = a[0];
 25     a1 = a[1];
 26 
 27     switch (call) {
 28     case SYS_SOCKET:
 29         err = __sys_socket(a0, a1, a[2]);
 30         break;
 31     case SYS_BIND:
 32         err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
 33         break;
 34     case SYS_CONNECT:
 35         err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
 36         break;
 37     case SYS_LISTEN:
 38         err = __sys_listen(a0, a1);
 39         break;
 40     case SYS_ACCEPT:
 41         err = __sys_accept4(a0, (struct sockaddr __user *)a1,
 42                     (int __user *)a[2], 0);
 43         break;
 44     case SYS_GETSOCKNAME:
 45         err =
 46             __sys_getsockname(a0, (struct sockaddr __user *)a1,
 47                       (int __user *)a[2]);
 48         break;
 49     case SYS_GETPEERNAME:
 50         err =
 51             __sys_getpeername(a0, (struct sockaddr __user *)a1,
 52                       (int __user *)a[2]);
 53         break;
 54     case SYS_SOCKETPAIR:
 55         err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
 56         break;
 57     case SYS_SEND:
 58         err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],
 59                    NULL, 0);
 60         break;
 61     case SYS_SENDTO:
 62         err = __sys_sendto(a0, (void __user *)a1, a[2], a[3],
 63                    (struct sockaddr __user *)a[4], a[5]);
 64         break;
 65     case SYS_RECV:
 66         err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
 67                      NULL, NULL);
 68         break;
 69     case SYS_RECVFROM:
 70         err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
 71                      (struct sockaddr __user *)a[4],
 72                      (int __user *)a[5]);
 73         break;
 74     case SYS_SHUTDOWN:
 75         err = __sys_shutdown(a0, a1);
 76         break;
 77     case SYS_SETSOCKOPT:
 78         err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3],
 79                        a[4]);
 80         break;
 81     case SYS_GETSOCKOPT:
 82         err =
 83             __sys_getsockopt(a0, a1, a[2], (char __user *)a[3],
 84                      (int __user *)a[4]);
 85         break;
 86     case SYS_SENDMSG:
 87         err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1,
 88                     a[2], true);
 89         break;
 90     case SYS_SENDMMSG:
 91         err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2],
 92                      a[3], true);
 93         break;
 94     case SYS_RECVMSG:
 95         err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1,
 96                     a[2], true);
 97         break;
 98     case SYS_RECVMMSG:
 99         if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME))
100             err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,
101                          a[2], a[3],
102                          (struct __kernel_timespec __user *)a[4],
103                          NULL);
104         else
105             err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1,
106                          a[2], a[3], NULL,
107                          (struct old_timespec32 __user *)a[4]);
108         break;
109     case SYS_ACCEPT4:
110         err = __sys_accept4(a0, (struct sockaddr __user *)a1,
111                     (int __user *)a[2], a[3]);
112         break;
113     default:
114         err = -EINVAL;
115         break;
116     }
117     return err;
118 }

 

  SYSCALL_DEFINE2根據不同的call來進入不同的分支,從而調用不同的內核處理函數,如__sys_bind, __sys_listen等等內核處理函數。

  在SYSCALL_DEFINE2調用的與socket相關的函數們都打上斷點,再進行調試
在這里插入圖片描述
在這里插入圖片描述
  在Qemu中輸入replyhi,發現gdb捕獲到以上斷點,一直到sys_accept4函數停止。說明此時服務器處於阻塞狀態,一直在等待客戶端連接,在Qemu中輸入hello,同樣捕獲斷點,可以看到客戶端發起連接,發送接收數據,整個過程與TCP socket通信流程完全相同,至此,整個追蹤過程結束。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM