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

一 实验目的

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

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

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

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

 

二 实验过程

1.fork函数调用

fork系统调用用于创建一个新进程,称为子进程,它与父进程并发运行。创建新的子进程后,两个进程都将执行fork()系统调用之后的下一条指令。子进程使用相同的PC,相同的CPU寄存器,相同的打开文件,这些文件在父进程中使用。fork()有着不同的返回值,负值:创建子进程失败,零:返回到新创建的子进程,正值:返回给父亲或调用者。该值包含新创建子进程的进程ID。
Linux下用于创建进程的API有三个fork,vfork和clone,这三个函数分别是通过系统调用sys_fork,sys_vfork以及sys_clone实现的。而且这三个系统调用,都是通过do_fork来实现的,只是传入了不同的参数。
以下是do_fork函数的原型
long do_fork(unsigned long clone_flags,
    unsigned long stack_start,
    unsigned long stack_size,
    int __user *parent_tidptr,
    int __user *child_tidptr)
{
    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 ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;
        else
            trace = PTRACE_EVENT_FORK;
    
        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }
    
    p = copy_process(clone_flags, stack_start, stack_size,
        child_tidptr, NULL, trace);
    /*
    * Do this prior waking up the new thread - the thread pointer
    * might get invalid after that point, if the thread exits quickly.
    */
    if (!IS_ERR(p)) {
        struct completion vfork;
        struct pid *pid;
    
        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, parent_tidptr);
    
        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);
    } else {
        nr = PTR_ERR(p);
    }
    return nr;

}

  父进程通过函数中的copy_process函数来实现对子进程的创建。

 

2.execve函数调用

execve和其他系统调⽤不同之处是加载完新的可执⾏程序之后已经覆盖了原来⽗进程的上下⽂环境。execve系统调用的作用是运行另外一个指定的程序。它会把新程序加载到当前进程的内存空间内,当前的进程会被丢弃,它的堆、栈和所有的段数据都会被新进程相应的部分代替,然后会从新程序的初始化代码和 main 函数开始运行。同时,进程的 ID 将保持不变。

execve的调用逻辑是

sys_execve() -> do_execve() -> do_execveat_common() -> do_execve_file -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()

 

3.linux系统的一般执行过程

3.1一般情况

正在运行的用户态进程X切换到运行用户态进程Y的过程

    (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

3.2特殊情况

    (1)通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;

    (2)内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;

    (3)创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;

    (4)加载一个新的可执行程序后返回到用户态的情况,如execve;

 

三 总结

1、Linux进程调度是基于分时和优先级的。

2、Linux中,内核线程是只有内核态没有用户态的特殊进程。

3、内核可以看作各种中断处理过程和内核线程的集合。

4、Linux系统的一般执行过程 可以抽象成正在运行的用户态进程X切换到运行用户态进程Y的过程。

5、Linux中,内核线程可以主动调度,主动调度时不需要中断上下文的切换。

6、Linux内核调用schedule()函数进行调度,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换。

 





posted @ 2020-06-14 22:19  wozaizai  阅读(185)  评论(0编辑  收藏  举报