• 深入理解系统调用


    一、实验要求

    • 找一个系统调用,系统调用号为学号最后2位相同的系统调用
    • 通过汇编指令触发该系统调用
    • 通过gdb跟踪该系统调用的内核处理过程
    • 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

    二、查找系统调用

      我的学号后两位是56,对应系统调用为clone

    查阅资料可知,系统调用clone是用来创建轻量级进程(即线程)的,主要用于线程库的实现。它的函数原型如下

    #define _GNU_SOURCE
    #include <sched.h>
    int clone(int (*func)(void*),void *child_stack,int flags,void *func_arg,....
               /*pid_t *ptid,struct user_desc *tls,pid_t *ctid*/);
                      
                                         Return process ID of child on success,or -1 on error

    新线程被创建后,就会运行参数func指向的函数,该函数的参数则由参数func_arg指定。因为clone产生的子进程共享父进程内存,所以它不能使用父进程的栈。相反,调用者必须分配一块大小适中的内存空间供子进程的栈使用,同时将这块内存的指针置于参数child_stack中。参数flags服务于双重目的。首先,其低字节中存放着子进程的终止信号,子进程退出时其父进程将收到这一信号。(如果克隆产生的子进程因信号而终止,父进程依然会收到SIGCHLD信号)该字节也可能为0,这时将不会产生任何信号。
    clone()函数中的flags参数是各位掩码的组合。其参数如下:

    • 共享文件描述符:CLONE_FILES

      如果指定了该标志,父子进程会共享同一个打开文件描述符表。也就是说,无论哪个进程对文件描述符的分配与释放都会影响另一个进程。

    • 共享与文件系统相关的信息:CLONE_FS

      如果指定了该标志,那么父子进程将共享与文件系统相关的信息:权限掩码、根目录以及当前工作目录。也就是说无论在哪个进程中调用umask()、chdir()或者chroot(),都将影响到另一个进程。

    • 共享对信号的处置设置:CLONE_SIGHAND

      如果设置了该标志,那么父子进程将共享同一信号处置表。无论在哪个进程中调用sigaction()或者signal()来改变对信号处置的设置,都会影响其他进程对信号的处置。

    • 共享父进程的虚拟内存:CLONE_VM

      如果设置了该标志,父子进程将会共享同一份虚拟内存页。无论哪一个进程更新了内存,或是调用了mmap()、munmap(),另一进程同样会观察到变化。

    • 线程组:CLONE_THREAD

      若设置了该标志,则会将子进程置于父进程的线程组中。如果未设置该标志,那么会将子进程置于新的线程组中。

    • 线程库支持:CLONE_PARENT_SETTID、CLONE_CHILD_SETTID和CLONE_CHILD_CLEARTID

      为实现POSIX线程,Linux2.6提供了对CLONE_PARENT_SETTID、CLONE_CHILD_SETTID和CLONE_CHILD_CLEARTID的支持。这些标志将会影响clone()对参数ptid、ctid的处理。如果设置了CLONE_PARENT_SETTID,内核会将子进程的线程ID写入ptid所指向的位置。如果设置了CLONE_CHILD_SETTID,那么clone()会将子线程的线程ID写入指针ctid所指向的位置。如果设置了CLONE_CHILD_CLEARTID,则会在子进程终止时将ctid所指向的内存清零。

    • 线程本地存储:CLONE_SETTLS

    如果设置了该标志,那么参数tls所指向的user_desc结构会对线程所使用的线程本地存储缓冲区加以描述。

    • 共享systemV信号量的撤销值:CLONE_SYSVSEM

      如果设置了该标志,父子进程会将共享同一个SystemV信号量撤销值列表。

    • 每进程挂载命名空间:CLONE_NEWNS
    • 将子进程的父进程置为调用者的父进程:CLONE_PARENT

      默认情况下,当调用clone()创建新进程时,新进程的父进程就用clone()进程。如果设置该标志,那么调用者的父进程就成为子进程的父进程。

    • 进程跟踪:CLONE_PTRACE和CLONE_UNTRACED

      如果设置了CLONE_PTRACE且正在跟踪子进程,那么也会对子进程进行跟踪。从Linux2.6起,即可设置CLONE_UNTRACED标志,这也意味着跟踪进程不能强制其子进程设置为CLONE_PTRACE

    • 挂起父进程直至子进程退出或者调用exec():CLONE_VFORK

      如果设置了该标识,父进程将一直挂起,直至子进程调用exec()或者_exit()来释放虚拟内存资源为止。

    二、触发系统调用

    编写以下程序,使用clone来触发系统调用

    #include <stdio.h>
    #include <malloc.h>
    
    #include <sched.h>
    #include <signal.h>
    
    #include <sys/types.h>
    #include <unistd.h>
    
    
    #define FIBER_STACK 8192
    int a;
    void * stack;
    
    int do_something()
    {
        printf("This is son, the pid is:%d, the a is: %d
    ", getpid(), ++a);
        free(stack); 
        exit(1);
    }
    
    int main()
    {
        void * stack;
        a = 1;
        stack = malloc(FIBER_STACK);//为子进程申请系统堆栈
    
        if(!stack)
        {
            printf("The stack failed
    ");
            exit(0);
        }
        printf("creating son thread!!!
    ");
    
        clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);//创建子线程
    
        printf("This is father, my pid is: %d, the a is: %d
    ", getpid(), a);
        exit(1);
    }

    编译内核,制作根文件系统步骤略过,已经有很多同学写的很详细了。

    在编译完上述程序后,将其放入根文件系统的home目录下,启动qemu,运行该程序可以获得以下结果。可以看到子进程被成功创建,并运行了指定的do_something函数,由于指定了CLONE_VM标志,使得子进程能够和父进程共享内存,表现为他们两个打印出的变量a值相同

    三、通过gdb跟踪该系统调用的内核处理过程

    通过如下命令重新启动qemu

    qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S –s

    另开一个终端,设置gdb连接qemu的端口(很不幸。。。qemu启动刚好也会触发这个系统调用,然后卡死,gdb此时使用bt一直提示Selected thread is running.实在不知道如何解决这个问题)

    cd linux-5.4.34/
    gdb vmlinux
    (gdb) target remote:1234
    (gdb) b __x64_sys_clone

     不过通过查阅资料可以知道系统调用的进入点是entry_SYSCALL_64,对应的汇编代码如下

    ENTRY(entry_SYSCALL_64)
        UNWIND_HINT_EMPTY
        /*
         * Interrupts are off on entry.
         * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
         * it is too small to ever cause noticeable irq latency.
         */
    
        swapgs
        /* tss.sp2 is scratch space. */
        movq    %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
        SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
        movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp
    
        /* Construct struct pt_regs on stack */
        pushq    $__USER_DS                /* pt_regs->ss */
        pushq    PER_CPU_VAR(cpu_tss_rw + TSS_sp2)    /* pt_regs->sp */
        pushq    %r11                    /* pt_regs->flags */
        pushq    $__USER_CS                /* pt_regs->cs */
        pushq    %rcx                    /* pt_regs->ip */
    GLOBAL(entry_SYSCALL_64_after_hwframe)
        pushq    %rax                    /* pt_regs->orig_ax */
    
        PUSH_AND_CLEAR_REGS rax=$-ENOSYS
    
        TRACE_IRQS_OFF
    
        /* IRQs are off. */
        movq    %rax, %rdi
        movq    %rsp, %rsi
        call    do_syscall_64        /* returns with IRQs disabled */

    在执行系统调用前,entry_SYSCALL_64主要做了这些事情:①切换gs寄存器从用户态到内核态,通过swapgs指令实现 ②保存中断上下文 ③初始化内核堆栈,然后执行do_syscall_64真正处理系统调用

    继续查看执行do_syscall_64的源代码

    #ifdef CONFIG_X86_64
    __visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
    {
        struct thread_info *ti;
    
        enter_from_user_mode();
        local_irq_enable();
        ti = current_thread_info();
        if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
            nr = syscall_trace_enter(regs);
    
        if (likely(nr < NR_syscalls)) {
            nr = array_index_nospec(nr, NR_syscalls);
            regs->ax = sys_call_table[nr](regs);
    #ifdef CONFIG_X86_X32_ABI
        } else if (likely((nr & __X32_SYSCALL_BIT) &&
                  (nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
            nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
                        X32_NR_syscalls);
            regs->ax = x32_sys_call_table[nr](regs);
    #endif
        }
    
        syscall_return_slowpath(regs);
    }
    #endif

    这个函数所做的内容就比较明确了,它首先查阅了系统调用表,然后调用了对应的系统调用,执行完以后就要由内核态返回用户态了

    整个系统调用的流程如下图所示

  • 相关阅读:
    Django测试开发-4-django3.0.3报错:/mysql ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3
    Django测试开发-3-新建一个Django工程
    Django测试开发-2-创建虚拟环境
    Django测试开发-1-MVC/MVT的概念
    sqlserver中复合索引和include索引到底有多大区别?
    PLSQL开发笔记和小结(转载)
    PL/sql语法单元
    jquery 获取下拉框值与select text
    jquery 事件
    jquery dom操作
  • 原文地址:https://www.cnblogs.com/cccc2019fzs/p/12952156.html
Copyright © 2020-2023  润新知