zoukankan      html  css  js  c++  java
  • 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程

    一、实验目的

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

    二、fork系统调用分析

      fork()系统调用用于复制父进程从而创建子进程。fork()的特殊之处在于:一次调用,两次返回。如果fork()执行出现了问题则会返回一个负数。如果fork()系统调用正常执行,会给父进程返回子进程的pid,给子进程返回0。实际上出fork、vfork和clone这3个系统调⽤,以及do_fork和kernel_thread内核函数都可以实现相同的功能,他们最终都是通过调用_do_fork()函数来实现创建子进程的功能。下面我们来分析_do_fork()函数。

    _do_fork代码,具体功能见注释

    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;
        }<br>  //copy_process主要通过dup_task_struct()函数进行进程描述符的复制,将父进程的结构体task_struct变量的值复制给子进程<br>  //通过copy_thread()函数进行子进程内核栈的初始化
        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);//将已经准备好进程上下文的子进程加入就绪队列<br>  //该函数对task_struct结构体中的thread_struct结构体进行了初始化。<br>  //将子进程的ax值设置为0,即fork()系统调用最终给子进程的返回值<br>  //设置子进程的sp值,即子进程的内核堆栈<br>  //将子进程的ip值设置为ret_from_fork,即子进程返回后开始执行的地方
      /* 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;
    }

     

      

    三、execve系统调用分析

    execve()用来加载可执行程序,linux系统提供了execl、execlp、execle、execv、execvp和execve等6个库函数,这些库函数统称为exec函数,exec函数都是通过execve系统调⽤进⼊内核,对应的系统调⽤内核处理函数为__x64_sys_execve,最终通过调⽤do_execve来具体执⾏加载可执⾏⽂件的⼯作。

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

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

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

    1. 正在运行的用户态进程X
    2. 发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
    3. SAVE_ALL //保存现场
    4. 中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
    5. 标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
    6. restore_all //恢复现场
    7. iret - pop cs:eip/ss:esp/eflags from kernel stack
    8. 继续运行用户态进程Y
  • 相关阅读:
    button 垂直分布
    GitHub上值得关注的iOS开源项目
    电脑连接网络(网络正常),但不能上网,登录网页提示dns_probe_finished_no_internet
    android 模拟应用因内存不足被后台杀死命令
    android 屏幕划分
    android 没有root的手机导出数据库
    移动硬盘不能识别,设备管理器中显示黄色感叹号
    低功耗蓝牙开发(BLE)
    音视频学习笔记
    Java中为什么要使用线程池?如何使用?
  • 原文地址:https://www.cnblogs.com/smjsoftware/p/13137768.html
Copyright © 2011-2022 走看看