码迷,mamicode.com
首页 > 系统相关 > 详细

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

时间:2020-06-15 20:48:38      阅读:47      评论:0      收藏:0      [点我收藏+]

标签:from   binary   sch   进程启动   node   cer   thread   vfork   pre   

一、实验要求

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

  • 以fork和execve系统调用为例分析中断上下文的切换

  • 分析execve系统调用中断上下文的特殊之处

  • 分析fork子进程启动执行时进程上下文的特殊之处

  • 以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程

二、execve系统调用

技术图片

 

 

 

Linux系统一般会提供了execl、execlp、execle、execv、execvp和execve等6个用以加载执行一个可执行文件的库函数,这些库函数统称为exec函数,差异在于对命令行参数和环境变量参数的传递方式不同。exec函数都是通过execve系统调用进入内核,对应的系统调用内核处理函数为sys_execve或__x64_sys_execve,它们都是通过调用do_execve来具体执行加载可执行文件的工作。

exec函数族就实现了在一个进程中启动另外一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。

整体的调用关系为sys_execve()或__x64_sys_execve -> do_execve() –>do_execveat_common() -> __do_execve_file -> exec_binprm()-> search_binary_handler() ->oad_elf_binary() -> start_thread()。

 

exec_binprm()函数具体如下:

static int exec_binprm(struct linux_binprm *bprm)
{
    pid_t old_pid, old_vpid;   
    int ret;
    old_pid = current->pid;
    rcu_read_lock();
    old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
    rcu_read_unlock();
    <strong>ret = search_binary_handler(bprm);</strong>
    if (ret >= 0) {
        audit_bprm(bprm);
        trace_sched_process_exec(current, old_pid, bprm);
        ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
        proc_exec_connector(current);
    }
    return ret;
}   

 

三、fork系统调用过程

技术图片

 

 

 

fork系统调用在陷?内核态之后有两次返回:

  1. 第?次返回到原来的?进程的位置继续向下执?
  2. 在?进程中fork也返回了?次,会返回到ret_from_fork,再经过系统调?返回到?户态

技术图片

 

 

 

在fork系统调用中,最重要的的就是_do_fork函数:do_fork函数主要完成了调?copy_process()复制?进程、获得pid、调?wake_up_new_task将?进程加?就绪队列等待调度执?等。

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;
    }
    // 拷贝父进程task_struct以及其中的一些资源,返回创建的task_struct的指针
    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);
    // 取出task结构体内的pid
    pid = get_task_pid(p, PIDTYPE_PID);
    nr = pid_vnr(pid);

    if (clone_flags & CLONE_PARENT_SETTID)
        put_user(nr, args->parent_tid);
        // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行
    if (clone_flags & CLONE_VFORK) {
        p->vfork_done = &vfork;
        init_completion(&vfork);
        get_task_struct(p);
    }
    // 将子进程添加到调度器的队列,使得子进程有机会获得CPU
    wake_up_new_task(p);

    /* forking complete and child started to run, tell ptracer */
    if (unlikely(trace))
        ptrace_event_pid(trace, pid);
    // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间,保证子进程优先于父进程运行
    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;
}

 

其中copy_process() 主要做了如下工作:

   a. 调用 dup_task_struct 复制一份task_struct结构体,作为子进程的进程描述符;

   b. 初始化与调度有关的数据结构,调用了sched_fork,这里将子进程的state设置为TASK_RUNNING;

   c. 复制所有的进程信息,包括fs、信号处理函数、信号、内存空间(包括写时复制)等;

        d.调用copy_thread,设置子进程的堆栈信息;  

   e. 为子进程分配一个pid。

 

四、Linux系统的一般执行过程

  1. 正在运?的?户态进程X。
  2. 发?中断(包括异常、系统调?等),CPU完成load cs:rip(entry of a speci?c ISR),即跳转到中断处理程序??。
  3. 中断上下?切换,具体包括如下?点:
  • swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了?个快照。
  • rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。快速系统调?是由系统调???处的汇编代码实现?户堆栈和内核堆栈的切换。
  • save cs:rip/ss:rsp/r?ags:将当前CPU关键上下?压?进程X的内核堆栈,快速系统调?是由系统调???处的汇编代码实现的。

此时完成了中断上下?切换,即从进程X的?户态到进程X的内核态。

  1. 中断处理过程中或中断返回前调?了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下?切换等。
  2. switch_to调?了__switch_to_asm汇编代码做了关键的进程上下?切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆栈,并完成了进程上下?所需的指令指针寄存器状态切换。之后开始运?进程Y(这?进程Y曾经通过以上步骤被切换出去,因此可以从switch_to下??代码继续执?)。
  3. 中断上下?恢复,与(3)中断上下?切换相对应。注意这?是进程Y的中断处理过程中,?(3)中断上下?切换是在进程X的中断处理过程中,因为内核堆栈从进程X 切换到进程Y了。
  4. 为了对应起?,中断上下?恢复的最后?步单独拿出来(6的最后?步即是7)iret - pop cs:rip/ss:rsp/r?ags,从Y进程的内核堆栈中弹出(3)中对应的压栈内容。此时完 成了中断上下?的切换,即从进程Y的内核态返回到进程Y的?户态。注意快速系统调?返回sysret与iret的处理略有不同。
  5. 继续运??户态进程Y。

 

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

标签:from   binary   sch   进程启动   node   cer   thread   vfork   pre   

原文地址:https://www.cnblogs.com/snowyaa/p/13133866.html

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