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通信流程完全相同,至此,整個追蹤過程結束。