进程的描述
进程控制块PCB -- task_struct
操作系统的三大核心功能:
1、进程管理
2、内存管理
3、文件系统
关于task_struct的具体介绍,见http://blog.csdn.net/npy_lp/article/details/7292563
它定义在linux-3.18.6/include/linux/sched.h文件中。
进程(Process)是系统进行资源分配和调度的基本单位,一个进程是一个程序的运行实例。而在Linux中,可以使用一个进程来创建另外一个进程。这样的话,Linux的进程的组织结构其实有点像Linux目录树,是个层次结构的,可以使用 pstree命令来查看。在最上面是init程序的执行进程。它是所有进程的老祖宗。Linux提供了两个函数来创建进程。
1.fork()
fork()提供了创建进程的基本操作,可以说它是Linux系统多任务的基础。该函数在/linux-3.18.6/kernel/fork.c
2.exec系列函数
如果只有fork(),肯定是不完美的,因为fork()只能参数一个父进程的副本。而exec系列函数则可以帮助我们建立一个全新的新进程。
在Linux系统中,一个进程的PCB是一个C语言的结构体task_struct来表示,而多个PCB之间是由一个双向链表组织起来的,在《Understanding the Linux Kernel》中,则是进一步描述这个链表是一个双向循环链表。
在Linux中创建一个新进程的方法是使用fork函数,fork()执行一次但有两个返回值。
在父进程中,返回值是子进程的进程号;在子进程中,返回值为0。因此可通过返回值来判断当前进程是父进程还是子进程。
使用fork函数得到的子进程是父进程的一个复制品,它从父进程处复制了整个进程的地址空间,包括进程上下文,进程堆栈,内存信息,打开的文件描述符,信 号控制设定,进程优先级,进程组号,当前工作目录,根目录,资源限制,控制终端等。而子进程所独有的只是它的进程号,资源使用和计时器等。可以看出,使用 fork函数的代价是很大的,它复制了父进程中的代码段,数据段和堆栈段里的大部分内容,使得fork函数的执行速度并不快。
创建一个进程,至少涉及的函数:
sys_clone, do_fork, dup_task_struct, copy_process, copy_thread, ret_from_fork
创建一个新进程在内核中的执行过程
- fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建
- 新的进程是从ret_from_fork处开始执行的,在ret_from_fork函数中,首先,子进程通过copy process函数和dup_task_struct函数复制父进程的状态,并通过alloc_thread_infonode将父进程的堆栈状态压入子进程的堆栈以备返回父进程时候使用,确保了内核进程的执行起点与内核堆栈保持一致,最后,跳转至sys_call exit函数,完成子进程的创建。
- Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架
- 复制一个PCB——task_struct
err = arch_dup_task_struct(tsk, orig);
- 要给新进程分配一个新的内核堆栈
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
1 *childregs = *current_pt_regs(); //复制内核堆栈
2 childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
3
4 p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5 p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
浏览进程创建过程相关的关键代码
- 系统调用内核处理函数sys_fork、sys_clone、sys_vfork
最终都是执行do_fork()
do_fork()里的复制进程的函数:
复制PCB:
打开复制PCB的具体函数:
打开alloc_thread_info():
拷贝内核堆栈数据和指定新进程的第一条指令地址。
创建的新进程是从哪里开始执行的?
(1)复制内核堆栈时
打开pt_regs:
int指令和SAVE_ALL压到内核栈的内容。
下面分析entry_32.S,也就是总控程序。
实验部分:
添加fork()到MenuOS
编译并启动MenuOS
用GDB连接,添加breakpoints,根据观察copy_process是建立新进程,
weak_up_new_task则是运行这个新进程,所以要尝试添加这样一个断点
breakpoints list:b sys_clone
b sys_clone
b do_fork
b copy_process
b dup_task_struct
b alloc_task_struct_node
b arch_dup_task_struct
b copy_thread
b ret_from_fork
b wake_up_new_task
跟踪fork执行
总结:
进程创建是linux系统调用的一种形式,进程创建可以通过fork,vfork,clone三个系统调用实现,它们都是通过do_fork函数实现进程创建的,在do_fork函数中,以ret_from_fork函数为执行起点,复制父进程的内存堆栈和数据,并修改某些参数实现子进程的定义和初始化,创建子进程的工作完成后,通过sys_call exit函数退出并pop父进程的内存堆栈,实现新进程的创建工作。
fork创建的新进程是和父进程(除了PID和PPID)一样的副本,包括真实和有效的UID和GID、进程组合会话ID、环境、资源限制、打开的文件以及共享内存段。
根据代码的分析,do_fork中,copy_process管子进程运行的准备,wake_up_new_task作为子进程forking的完成。