码迷,mamicode.com
首页 > 其他好文 > 详细

Socket与系统调用深度分析

时间:2019-12-19 22:57:36      阅读:102      评论:0      收藏:0      [点我收藏+]

标签:系统   之一   protoc   检验   get   计算机   long   from   调用   

Socket与系统调用深度分析

 1.系统调用:在系统中真正被所有进程都使用的内核通信方式是系统调用。例如当进程请求内核服务时,就使用的是系统调用。一般情况下,进程是不能够存取系统内核的。它不能存取内核使用的内存段,也不能调用内核函数,CPU的硬件结构保证了这一点。只有系统调用是一个例外。进程使用寄存器中适当的值跳转到内核中事先定义好的代码中执行。在Intel结构的计算机中,这是由中断0x80实现的。

    进程可以跳转到的内核中的位置叫做system_call。在此位置的过程检查系统调用号,它将告诉内核进程请求的服务是什么。然后,它再查找系统调用表sys_call_table,找到希望调用的内核函数的地址,并调用此函数,最后返回。

    所以,如果希望改变一个系统调用的函数,需要做的是编写一个自己的函数,然后改变sys_call_table中的指针指向该函数,最后再使用cleanup_module将系统调用表恢复到原来的状态

 

2.系统调用入口函数:

entry.s:

ENTRY(system_call)

        pushl %eax                      # save orig_eax
        SAVE_ALL
#ifdef __SMP__
        ENTER_KERNEL
#endif
        movl $-ENOSYS,EAX(%esp)
        cmpl $(NR_syscalls),%eax
        jae ret_from_sys_call
        movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
        testl %eax,%eax
        je ret_from_sys_call
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%edx)
        movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
        movl SYMBOL_NAME(current_set),%ebx
#endif
        andl $~CF_MASK,EFLAGS(%esp)
        movl %db6,%edx
        movl %edx,dbgreg6(%ebx)
        testb $0x20,flags(%ebx)
        jne 1f
        call *%eax
        movl %eax,EAX(%esp)
        jmp ret_from_sys_call
    这段代码现保存所有的寄存器值,然后检查调用号(__NR_name)是否合法(在系统调用表中查找),找到正确的函数指针后,就调用该函数(即你真正希望内核帮你运行的函数)。运行返回后,将调用ret_from_sys_call,这里就是著名的进程调度时机之一。
    当在程序代码中用到系统调用时,编译器会将上面提到的宏展开,展开后的代码实际上是将系统调用号放入ax后移用int 0x80使处理器转向系统调用入口,然后查找系统调用表,进而由内核调用真正的功能函数。
    自己添加过系统调用的人可能知道,要在程序中使用自己的系统调用,必须显示地应用宏_syscallN。
    而对于linux预定义的系统调用,编译器在预处理时自动加入宏_syscall3(int,ioctl,arg1,arg2,arg3)并将其展开。所以,并不是ioctl本身是宏替换符,而是编译器自动用宏声明了ioctl这个函数。

 

 

3.LINUX内部是如何分别为各种系统调用服务的:当进程需要进行系统调用时,必须以C语言函数的形式写一句系统调用命令。当进程执行到用户程序的系统调用命令时,实际上执行了由宏命令_syscallN()展开的函数。系统调用的参数由各通用寄存器传递。然后执行INT 0X80,以核心态进入入口地址system_call。
ENTRY(system_call)
        pushl %eax                        # save orig_eax
        SAVE_ALL
#ifdef __SMP__
        ENTER_KERNEL
#endif
        movl $-ENOSYS,EAX(%esp)
        cmpl $(NR_syscalls),%eax
        jae ret_from_sys_call
        movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
        testl %eax,%eax
        je ret_from_sys_call
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%edx)
        movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
        movl SYMBOL_NAME(current_set),%ebx
#endif
        andl $~CF_MASK,EFLAGS(%esp)        # clear carry - assume no errors
        movl %db6,%edx
        movl %edx,dbgreg6(%ebx)  # save current hardware debugging status
        testb $0x20,flags(%ebx)                # PF_TRACESYS
        jne 1f
        call *%eax
        movl %eax,EAX(%esp)                # save the return value
        jmp ret_from_sys_call

    从system_call入口的汇编程序的主要功能是:
    ·保存寄存器当前值(SAVE_ALL);
    ·检验是否为合法的系统调用;
    ·根据系统调用表_sys_call_table和EAX持有的系统调用号找出并转入系统调用响应函数;
    ·从该响应函数返回后,让EAX寄存器保存函数返回值,跳转至ret_from_sys_call(arch/i386/kernel/entry.S)。
    ·最后,在执行位于用户程序中系统调用命令后面余下的指令之前,若INT 0X80的返回值非负,则直接按类型type返回;否则,将INT 0X80的返回值取绝对值,保留在errno变量中,返回-1。

 

4.结合socketAPI分析系统调用

socket系统调用的定义在net/socket.c中,如下:

 

int __sys_socket(int family, int type, int protocol)
{
    int retval;
    struct socket *sock;
    int flags;

    /* Check the SOCK_* constants for consistency.  */
    BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
    BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
    BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
    BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

    flags = type & ~SOCK_TYPE_MASK;
    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;
    type &= SOCK_TYPE_MASK;

    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        return retval;

    return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    return __sys_socket(family, type, protocol);
}

  

 

4、Linux Socket api对应的系统调用如何被内核处理的

  我们通过gdb调试来查看具体程序中socket api对应的系统调用如何被处理的,我们先打开一个menuos界面,然后重新打开一个窗口打开gdb调试。并设断点:

gdb
file linux-5.0.1/vmlinux
target remote:1234
b __sys_socket

技术图片

然后对socket api系统调用函数设置断点,查看replyhi和hello函数中对其的调用技术图片

可以看到,replyhi不止一次调用了sys_socketcall

sys_socketcall对应的系统调用服务例程是SYSCALL_DEFINE2。Linux系统调用的定义部分都被命名为SYSCALL_DEFINEx,末尾数字代表系统调用的参数(sys_socketcall有两个参数,刚好对应)

在net/socket.c中可以找到SYS_DEFINE2的定义

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;
}

  

可以看到实现的核心是一个switch语句,通过call参数来处理sys_socket、sys_bind等系统调用,每个case都和系统调用表中的项对应

Socket与系统调用深度分析

标签:系统   之一   protoc   检验   get   计算机   long   from   调用   

原文地址:https://www.cnblogs.com/lcw12356/p/12070437.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!