1. 程式人生 > >Socket與系統呼叫深度分析

Socket與系統呼叫深度分析

學習一下對Socket與系統呼叫的分析分析

一、介紹

我們都知道高階語言的網路程式設計最終的實現都是呼叫了系統的Socket API程式設計介面,在作業系統提供的socket系統介面之上可以建立不同埠之間的網路連線,從而使我們可以編寫各基於不同網路協議的應用程式。而使用者程式一般都是執行在使用者態,依靠的Socket介面也是在在使用者態,我們都知道socket介面是通過系統呼叫機制進入核心,從而從核心的層面提高服務。本次實驗主要需要分析出socketAPI函式是如何進行系統呼叫的。

首先我們再明確一下系統呼叫和socketAPI之間的關係:也就是說系統呼叫是使得API能獲得核心支援的途徑。

 

 

 

 二、實驗步驟

首先從參考了大佬的部落格之後獲悉,在linux系統中,與socket API有關的系統呼叫包括所有的與socketapi都使用的sys_socketcall系統呼叫和每一個socketapi都對應特定的系統呼叫,因此我主要通過這兩類來進行分析。

1、首先使用gdb連線menu系統,這個系統是上次實驗製作的簡易系統

注意:這裡需要先用下面的命令啟動tcp server,否則會顯示連線超時

 

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

 

然後新開一個終端,開啟gdb,並且使用target連線上menu系統

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

連線以後就可以打斷點進行除錯了。

 

2、對特殊的系統呼叫進行測試:給sys_bind和sys_listen分析

 

 

 接著按c即繼續執行程式,此時menu系統繼續啟動。

 

 

 

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

說明並沒有停在斷點,也就是使用replyhi的時候並沒有呼叫sys_listen和sys_bind這兩個核心函式。

 

 

 3、接下來對所有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子函式,這些函式進行的系統呼叫分別是socket、bind、accept、recv、send、close。然後在核心的sockct函式中,通過傳入的系統呼叫,使用switch語句呼叫不同的核心處理函式,完成網路通訊。

三、總結

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

實驗過程參考同學部落格:https://www.cnblogs.com/hhssqq9999/p/12048964.html

 

 

&nb