Socket與系統調用深度分析

學習一下對Socket與系統調用的分析分析html

1、介紹linux

咱們都知道高級語言的網絡編程最終的實現都是調用了系統的Socket API編程接口,在操做系統提供的socket系統接口之上能夠創建不一樣端口之間的網絡鏈接,從而使咱們能夠編寫各基於不一樣網絡協議的應用程序。而用戶程序通常都是運行在用戶態,依靠的Socket接口也是在在用戶態,咱們都知道socket接口是經過系統調用機制進入內核,從而從內核的層面提升服務。本次實驗主要須要分析出socketAPI函數是如何進行系統調用的。編程

首先咱們再明確一下系統調用和socketAPI之間的關係:也就是說系統調用是使得API能得到內核支持的途徑。api

 

 

 

 2、實驗步驟服務器

首先從參考了大佬的博客以後獲悉,在linux系統中,與socket API有關的系統調用包括全部的與socketapi都使用的sys_socketcall系統調用和每個socketapi都對應特定的系統調用,所以我主要經過這兩類來進行分析。網絡

一、首先使用gdb鏈接menu系統,這個系統是上次實驗製做的簡易系統app

注意:這裏須要先用下面的命令啓動tcp server,不然會顯示鏈接超時socket

 

執行 qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s -S

 

而後新開一個終端,打開gdb,而且使用target鏈接上menu系統tcp

gdb file ~/net/linux-5.0.1/vmlinux target remote:1234

鏈接之後就能夠打斷點進行調試了。函數

 

二、對特殊的系統調用進行測試:給sys_bind和sys_listen分析

 

 

 接着按c即繼續運行程序,此時menu系統繼續啓動。

 

 

 

根據提示,輸入replyhi之後,發現gdb的終端以下所示:

說明並無停在斷點,也就是使用replyhi的時候並無調用sys_listen和sys_bind這兩個內核函數。

 

 

 三、接下來對全部socketapi都使用的sys_socketcall系統調用進行測試,使用 b sys_socketcall打斷點,接着按c繼續執行

 

 

 而後在mune系統進行測試,輸入replyhi之後,發現gdb終端有反饋了,以下所示:

說明停在了SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args),也就是此時使用了這個內核函數

 

查看源碼以下:

 

 下面咱們簡單分析一下這個函數:發現這段c語言很大一部分都是switch語句,說明這個函數的主要做用是根據不一樣的輸入來選擇不一樣的操做,調用不一樣的內核處理函數,傳入是SYS_BIND則調用__sys_bind,傳入SYS_LISTEN則調用 __sys_listen等內核函數進行相應的處理。

SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) { unsigned long a[AUDITSC_ARGS]; unsigned long a0, a1; int err; unsigned int len; if (call < 1 || call > SYS_SENDMMSG) return -EINVAL; call = array_index_nospec(call, SYS_SENDMMSG + 1); len = nargs[call]; if (len > sizeof(a)) return -EINVAL; /* copy_from_user should be SMP safe. */
    if (copy_from_user(a, args, len)) return -EFAULT; err = audit_socketcall(nargs[call] / sizeof(unsigned long), a); if (err) return err; a0 = a[0]; a1 = a[1]; switch (call) { case SYS_SOCKET: err = __sys_socket(a0, a1, a[2]); break; case SYS_BIND: err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]); break; case SYS_CONNECT: err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]); break; case SYS_LISTEN: err = __sys_listen(a0, a1); break; case SYS_ACCEPT: err = __sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], 0); break; case SYS_GETSOCKNAME: err = __sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_GETPEERNAME: err = __sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_SOCKETPAIR: err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]); break; case SYS_SEND: err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], NULL, 0); break; case SYS_SENDTO: err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], (struct sockaddr __user *)a[4], a[5]); break; case SYS_RECV: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], NULL, NULL); break; case SYS_RECVFROM: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], (struct sockaddr __user *)a[4], (int __user *)a[5]); break; case SYS_SHUTDOWN: err = __sys_shutdown(a0, a1); break; case SYS_SETSOCKOPT: err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]); break; case SYS_GETSOCKOPT: err = __sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]); break; case SYS_SENDMSG: err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1, a[2], true); break; case SYS_SENDMMSG: err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], true); break; case SYS_RECVMSG: err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1, a[2], true); break; case SYS_RECVMMSG: if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME)) err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], (struct __kernel_timespec __user *)a[4], NULL); else err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], NULL, (struct old_timespec32 __user *)a[4]); break; case SYS_ACCEPT4: err = __sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], a[3]); break; default: err = -EINVAL; break; } return err; }

此時咱們查看一下replyhi的源碼,看看到底是如何調用的。

從下面的程序能夠看到,replyhi調用其餘函數的順序InitializeService,ServiceStart,RecvMsg,SendMsg、ServiceStop。

 

 

 接下來咱們須要作的是找出InitializeService,ServiceStart,RecvMsg,SendMsg和ServiceStop,看看這些函數是如何使用內核處理函數的。

(1)首先是InitializeService,能夠看到這個函數的主要就是調用了 PrepareSocket(IP_ADDR,PORT)和 InitServer()

 

 

 因此只能繼續追蹤,首先看 PrepareSocket(IP_ADDR,PORT),能夠看到這個函數的主要做用是分配內存空間,並調用socket函數,建立套接字

 

 

 接着是InitServer()函數,從程序中看到這個函數的主要做用調用bind函數,服務器使用bind來指明熟知的端口號,而後等待鏈接
並進行監聽

 

 因此InitializeService函數主要做用在於創建socket,綁定socket並進行端口的監聽。

(2)ServiceStart函數,顯然,這個函數的做用只是調用accept,獲取傳入鏈接請求。

 

 (3) SendMsg,顧名思義,這個函數主要就是進行信息的發送,依靠的內核函數是send函數

 

 (4)RecvMsg 這個函數主要就是進行信息的接受,依靠的內核函數是recv函數

 

 

 

(5)ServiceStop,這個函數很簡單,就是調用close函數進行撤銷套接字,結束當前的鏈接。

 

 至此,咱們終於分析完了replyhi這個函數是如何依靠socketAPI以及內核服務程序進行網絡通訊的。

回顧總結一下:

reply函數經過調用InitializeService,ServiceStart,RecvMsg,SendMsg、ServiceStop子函數,這些函數進行的系統調用分別是socketbind、acceptrecv、send、close。而後在內核的sockct函數中,經過傳入的系統調用,使用switch語句調用不一樣的內核處理函數,完成網絡通訊。

3、總結

此次的實驗確實挺難的,作了好久,也只是作出了點皮毛,可是經過這個實現,進一步瞭解了linux內核網絡通訊部分的機制,對寫內核的人更加欽佩,只能說,大佬太強了!!!

實驗過程參考同窗博客:https://www.cnblogs.com/hhssqq9999/p/12048964.html

 

 

 

原文出處:https://www.cnblogs.com/iyuanyuan/p/12069677.html

相關文章
相關標籤/搜索