实验:从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析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完成寄存器的切换。
用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度