• 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程


     实验目的

    • 以fork和execve系统调用为例分析中断上下文的切换
    • 分析execve系统调用中断上下文的特殊之处
    • 分析fork子进程启动执行时进程上下文的特殊之处
    • 以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程

    1. 以fork为例分析中断上下文的切换

      fork系统调用fork()系统调用用于复制父进程从而创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。fork()的特殊之处在于:一次调用,两次返回。如果fork()执行出现了问题则会返回一个负数。如果fork()系统调用正常执行,会给父进程返回子进程的pid,给子进程返回0。

       fork系统调用是通过do_fork来实现的,do_fork函数中进行了两个重要的操作,分别是拷贝父进程资源、子进程加入调度队列。do_fork函数首先会通过调用copy_process来将父进程所拥有的资源拷贝给子进程,并创建子进程。子进程创建成功后,会调用wake_up_new_task来将子进程加入就绪队列,等待CPU分配资源并运行。

    代码如下

    long _do_fork(struct kernel_clone_args *args)
    {
        u64 clone_flags = args->flags;
        struct completion vfork;
        struct pid *pid;
        struct task_struct *p;
        int trace = 0;
        long nr;
     
        /*
         * Determine whether and which event to report to ptracer.  When
         * called from kernel_thread or CLONE_UNTRACED is explicitly
         * requested, no event is reported; otherwise, report if the event
         * for the type of forking is enabled.
         */
        if (!(clone_flags & CLONE_UNTRACED)) {
            if (clone_flags & CLONE_VFORK)
                trace = PTRACE_EVENT_VFORK;
            else if (args->exit_signal != SIGCHLD)
                trace = PTRACE_EVENT_CLONE;
            else
                trace = PTRACE_EVENT_FORK;
     
            if (likely(!ptrace_event_enabled(current, trace)))
                trace = 0;
        }
        p = copy_process(NULL, trace, NUMA_NO_NODE, args);
        add_latent_entropy();
     
        if (IS_ERR(p))
            return PTR_ERR(p);
     
        /*
         * Do this prior waking up the new thread - the thread pointer
         * might get invalid after that point, if the thread exits quickly.
         */
        trace_sched_process_fork(current, p);
        pid = get_task_pid(p, PIDTYPE_PID);
        nr = pid_vnr(pid);
     
        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, args->parent_tid);
        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }
        wake_up_new_task(p);
     
        /* forking complete and child started to run, tell ptracer */
        if (unlikely(trace))
            ptrace_event_pid(trace, pid);
        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
        }
     
        put_pid(pid);
        return nr;
    }

    2.以execve系统调用为例分析中断上下文的切换

      当在用户态使用execve系统调用陷入到内核态时,execve在内核中会使用do_execve处理函数来加载可执行文件,并把用该可执行文件替换掉原来的可执行文件。当使用execve加载的可执行文件执行完毕后,会返回到用户态,此时的可执行文件已经不是原来被替换掉的旧的可执行文件了,而是新的可执行文件。

      execve()系统调用的执行过程如下:

    1. 陷入内核
    2. 校验文件并加载新的可执行文件
    3. 新加载的文件根据ELF⽂件映射到进程的地址空间,覆盖原来的进程
    4. 设置EIP的值。如果可执行程序是静态链接的程序,或不需要动态链接库,则EIP设置为新的可执行文件的main函数地址,如果可执行程序需要其他的动态链接库,则入口地址是加载器ld的入口地址
    5. 返回用户态,程序从新的EIP开始继续执

    3.总结

    以正在运行的用户态进程X切换到用户态进程Y为例:

    • 正在运行的用户态进程X
    • 发生中断(包括异常、系统调用等),硬件完成以下动作:1)save cs:eip/ss:eip/eflags:当前CPU上下文压入用户态进程X的内核堆栈;2)load cs:eip/ss:esp:加载当前进程内核堆栈相关信息,跳转到中断处理程序处,即中断处理程序的起点
    • SAVE_ALL,保存现场,此时完成了中断上下文的切换,即从进程X的用户态到进程X的内核态
    • 中断处理过程中或中断返回前调用了schedule函数进行进程上下文切换。将当前用户进程X的内核堆栈切换到挑选出的next进程Y的内核堆栈,并完成进程上下文所需的EIP等寄存器的状态切换;
    • 标号1,即$1f,之后开始运行用户态进程Y
    • restore_all,恢复现场,与SAVE_ALL保存现场相对应
    • 从Y进程的内核堆栈弹出步骤2硬件完成的压栈内容,此时完成中断上下文的切换,即从进程Y的内核态返回进程Y的用户态;
    • 继续运行进程Y
  • 相关阅读:
    sleep() 和 wait() 区别是什么?
    JAVA面试中需要准备的点
    javascript 取掉空格自定义函数
    code manager tools myeclipse10 svn插件安装及使用
    javascript 常用兼容fire fox的解决办法
    javascript 获取标签内的内容
    javascript dom与字符串相互转换
    javascript window.showModalDialog不兼容goole解决方案
    javascript js获取url及url参数解析
    javascript table排序之jquery.tablesorter.js
  • 原文地址:https://www.cnblogs.com/H1K777/p/13132578.html
Copyright © 2020-2023  润新知