实验:从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换

学号后三位<126>

原创作品转载请注明出处https://github.com/mengning/linuxkernel/

 

一、什么是进程

“进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。”    --百度百科

 

“In computing, a process is the instance of a computer program that is being executed. It contains the program code and its activity. Depending on the operating system (OS), a process may be made up of multiple threads of execution that execute instructions concurrently.”    --Wikipedia

 

二、进程的创建过程

在Linux中主要提供了fork、vfork、clone三个进程创建方法。 
在linux源码中这三个调用的执行过程是执行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;

    
    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);
    
    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);

        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;
}

do_fork进行了下列操作:

调用copy_process函数,将当前进程复制出来一分作为子进程,并且问子进程设置相应的信息。

如果是cfork调用,就进行vfork的初始化。

如果是vfork调用,那么子进程会执行exec,并且暂停父进程。

调用wake_up_new_task函数,将子进程放入调度器的队列,使其随着调度进程选中进行运行,将其唤醒。

 

故创建新进程时是通过调用do_fork来实现创建,先给新进程分配一个新的堆栈,然后通过copy_process函数来对父进程的信息进行复制,dup_task_struct函数为子进程分配好了内核栈,然后copy_thread完成内核栈信息的初始化。

 

三、使用gdb跟踪分析进程的创建

gdb vmlinux
target remote:1234
 b sys_clone
 b dup_task_struct
 b do_fork
 b copy_process
 b copy_thread
 b ret_from_for

断点效果如图

 

四、关于编译链接的过程和ELF可执行文件格式

 

ELF文件格式包括三种主要的类型:可执行文件、可重定向文件、共享库。
1.可执行文件(应用程序)可执行文件包含了代码和数据,是可以直接运行的程序。
2.可重定向文件(.o)可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。
.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些.o文件的活动可以反映出不同的需要。
Linux下,我们可以用gcc -c编译源文件时可将其编译成.o格式。
3.共享文件(*.so)也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为ld.so.1,libc.so.1或者 ld-linux.so.1。
在linux下输入“man elf”即可查看其详细的格式定义。

 

我们可以很方便的使用gcc来进行编译和链接的工作

 

五、对Linux系统的执行过程的理解

在调度时机方面,内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度。
schedule()函数实现进程调度,context_ switch完成进程上下文切换,switch_ to完成寄存器的切换。
用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度

 

posted @ 2019-03-26 12:10  Prinny  阅读(204)  评论(0编辑  收藏  举报