Linux进程的创建与管理
我们先简单的回顾一下进程的概念:进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。但和程序不同的是,在这个进程中,系统可能需要再启动一个或多个进程来完成独立的多个任务。
在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程叫做根进程。根进程是Linux中所有进程的祖宗,其余进程都是根进程的子孙。具有同一个父进程的进程叫做兄弟进程。
Linux进程创建的过程示意图如下所示:
子进程的创建
在Linux中,父进程以分裂的方式来创建子进程,创建一个子进程的系统调用叫做fork()。
- 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值。
fork:
fork - create a child process-创建一个子进程
头文件:
#include <unistd.h>
语句实现:
pid_t fork(void);
系统调用fork()
在系统中表示一个进程的实体是进程控制块,创建新进程的主要工作就是要创建一个新控制块,而创建一个新控制块最简单的方法就是复制。
- 当然,这里的复制并不是完全复制,因为父进程控制块中某些项的内容必须按照子进程的特性来修改,例如进程的标识、状态等。另外,子进程控制块还必须要有表示自己父进程的域和私有空间,例如数据空间、用户堆栈等。下面的两张图就表示父进程和相对应的子进程的内存映射:
进程地址空间: - 进程的数据区也就是未初始化的数据(.bss)、已初始化的数据(.data);进程的栈区也就是进程的用户栈和堆;进程程序代码就是进程的程序文件(.text);除此之外还有进程的系统堆栈区。
函数用法:
-
对于父进程, fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。
-
未成功创建子进程,返回-1;
在父进程中调用fork()之后会产生两种结果:一种为分裂子进程失败,另一种就是分裂子进程成功。如果fork()失败,则返回-1,;否则会出现父进程和子进程两个进程,在子进程中fork()返回0,在父进程中fork()返回子进程的ID。系统调用fork()工作流程示意图如下:
也就是说,在fork()函数之前需要确认内核中有足够的资源来完成。如果资源满足要求,则内核slab分配器在相应的缓冲区中构造子进程的进程控制块,并将父进程控制块中的全部成员都复制到子进程的控制块,然后再把子进程控制块必须的私有数据改成子进程的数据。当fork()返回到用户空间之前,向子进程的内核栈中压入返回值0,而向父进程内核堆栈压入子进程的pid。最后进行一次进程调度,决定是运行子进程还是父进程。
- 在代码中获得当前进程pid的函数为:getpid();
- 在代码中获得当前进程父进程pid的函数为:getppid()。
- 这里需要注明一点:父子进程的调度的顺序是由调度器决定的,与进程的创建顺序无关。
代码实现:
执行结果:
程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的信息。如果程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。
关于子进程的时间片
Linux在大多数情况下是按时间片来进行进程调度的。当某一个进程所拥有的时间片用完之后,内核会立即剥夺它的运行权,停止它的执行。那么当子进程被创建出来之后,这个子进程的初始时间片应该是多大呢?
Linux规定,如果父进程只是如之前那样简单地创建了一个子进程,那么系统会将父进程剩余的时间片分成两份,一份留给父进程,另一份作为子进程的时间片。因为之前的这种简单方式下,父子进程使用的是同一个代码,还没有成为两个真正的各自独立的进程,所以没有资格享用两份时间片。
循环创建N个子进程:
代码示例:
gdb调试:
gcc main.c -g
生成a.out文件
gdb a.out //进入调试界面
输入start开始逐语句调试,注意,在没有特殊标记之前,程序默认跟踪父进程
那怎么追踪子进程呢:
set follow-fork-mode parent//追踪父进程
set follow-fork-mode child//追踪子进程
大家觉得有兴趣可以自己去试试;
fork有两个典型的用法:
-
一个进程创建一个自身的拷贝,这样每个拷贝都可以在另一个拷贝执行其他任务的同时处理各自的某个操作。这是网络服务器的典型用法。
-
一个进程想要执行另一个程序。既然创建新进程的唯一方法为调用fork,该进程于是首先调用fork创建一个自身的拷贝,然后其中一个拷贝(通常为子进程)调用exec把自身替换成新的程序。这是诸如shell之类程序的典型用法。
-
读时共享,写时拷贝 --提高效率
父子进程在fork之后的区别:
- 相同点:全局变量(互相独立,不共享),date(互相独立),text,环境变量、栈、堆、用户id,宿主目录,进程工作目录,信号处理方式…
- 不同点:进程id,fork返回值,父进程id,父子进程运行时间…