进程的创建

简介: 进程的创建

fork() 函数用于在已有的进程中再创建一个新的进程。这个被创建的新的进程被视为子进程,而调用进程成为其父进程

#include <unistd.h>
pid_t fork(void);点击复制复制失败已复制


Linux 官方手册解释了 fork() 函数创建新进程的方式,子进程被创建是通过对父进程进行复制得来的,即子进程是父进程的复制品。进程都会有属于自己的虚拟地址空间,用以保存进程的各种信息,详见进程的内存布局笔记。因此,子进程对父进程的复制,实质上来讲,是复制父进程的整个地址空间。其中包括了进程的上下文、代码段、进程堆栈、内存信息、文件描述符、信号处理函数等;而子进程所独有的只有它的进程号、资源使用、计时器等。


注意

子进程虽然复制了父进程所使用的地址空间,但子进程创建成功之后,子进程访问的虚拟地址空间一定是属于自己的,而非与父进程共享空间。其本质是父子进程映射到不同物理地址空间。


因为子进程是对父进程的精准复制,所以父子进程的程序代码段是一样的。因此,需要某一种方式来区分父子进程。否则,父子进程执行的代码是一致的,创建子进程没有任何意义。


那么区分父子进程,保证父子进程执行的代码段不相同,是通过 fork() 函数返回值来判定的。 Linux 官方手册对函数的返回值解释为:子进程通过对父进程的精准复制产生, fork() 函数如果执行失败,父进程得到返回值为 -1 ;如果执行成功,在子进程中得到一个值为 0 ,在父进程中得到一个值为子进程的 ID (一定大于 0 的整数)。一定注意的是, fork() 函数不是在父进程中得到两个返回值,而是父子进程分别得到 fork() 函数返回的一个值。


关于父子进程的区分,可以通过一个简单的示例进行解释,如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, const char *argv[]) {
  pid_t pid;
  pid = fork();
  if (pid < 0) {
    perror("fork error");
    return -1;
  } else if (pid == 0) {
    /* child子进程执行代码区 */
    printf("The child process\n");
  } else {
    /* parent父进程执行代码区 */
    printf("The parent process\n");
  }
  return 0;
}点击复制复制失败已复制


上述代码是典型的创建子进程的框架代码,编译运行,结果如下:

$ gcc main.c && ./a.out 
The parent process
The child process点击复制复制失败已复制


根据运行结果可以看出,两个输出打印函数 printf() 均有打印。注意一个函数不可能在一个进程中返回两个值,因此两句输入打印不是出自于一个进程。根据之前的描述,创建子进程成功,则子进程获得一个返回值为 0 ,父进程获得返回值为子进程的 ID (大于 0 )。因此上述代码应该被这样解读:


  1. 程序在执行fork()函数时,开始创建子进程,子进程开始对父进程进行复制。
  2. 在变量pid接收fork()函数的返回值之前,子进程创建结束。
  3. 由于子进程几乎复制了父进程的所有地址空间(包括栈区),因此父子进程都有局部变量pid,子进程的pid接收的返回值为0,而在父进程的pid接收的返回值是子进程的ID
  4. 由于父子进程的变量pid接收的值不同,因此,根据代码的分支判断,可以看出父子进程虽然代码相同,但是执行的内容却不同。
  5. 父进程执行的代码是判断pid大于0的部分,以及fork()函数。fork()函数之前所有的执行代码,称为父进程的执行代码区。子进程执行的代码是判断pid等于0的部分,称为子进程的执行代码区。注意:子进程不执行fork()函数以及fork()函数以上所有的执行代码,否则子进程会被无限创建。


为了更加明显可以理解代码的框架,可以输出进程的 ID 来查看。获得进程的 ID 使用 getpid() 函数和 getppid() 函数。

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);点击复制复制失败已复制


getpid() 函数用于获得调用(自身)进程的 ID


getppid() 函数用于获得调用进程的父进程ID


对上例进行修改,如下所示:父子进程最后都执行 while 死循环不退出,父进程打印自己的 ID ,子进程打印自己的 ID 及父进程的 ID

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, const char *argv[]) {
  pid_t pid;
  pid = fork();
  if (pid < 0) {
    perror("fork error");
    return -1;
  } else if (pid == 0) {
    /* child子进程执行代码区 */
    printf("The child process, id = %d parent id = %d\n", getpid(), getppid());
    while(1);
  } else {
    /* parent父进程执行代码区 */
    printf("The parent process, id = %d\n", getpid());
    while(1);
  }
  return 0;
}点击复制复制失败已复制


编译运行,结果如下:

$ gcc main.c && ./a.out 
The parent process, id = 12207
The child process, id = 12208 parent id = 12207点击复制复制失败已复制


由此可以看出,在子进程的执行代码区中打印出的父进程 ID12207 ,与父进程的执行代码区打印自身的 ID 是一样的,同为 12207


通过终端输入 $ ps axj 也可查看当前系统中的进程信息:

$ ps axj
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  11284   12207   12207   11284 pts/0      12207 R+    1000   0:11 ./a.out
  12207   12208   12207   11284 pts/0      12207 R+    1000   0:11 ./a.out点击复制复制失败已复制


提示

  • PPID (process parent ID):父进程ID
  • PID (process ID):进程ID
  • PGID (process group ID):进程组ID
  • SID (session ID):会话组ID
目录
相关文章
|
8月前
|
Linux
一个进程最多可以创建多少个线程基本分析
一个进程最多可以创建多少个线程基本分析
478 1
|
程序员 C++ Windows
Windows C++ 启动子进程并绑定子进程,主进程结束关闭后自动结束关闭子进程
在Windows平台上主进程启动子进程,并使主进程结束关闭后自动结束关闭子进程
231 0
|
6月前
进程的创建
进程的创建
|
8月前
|
缓存 监控 安全
进程的介绍及相关命令
进程的介绍及相关命令
|
8月前
|
Linux Shell
【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】
【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】
|
8月前
|
调度
进程有哪几种状态
进程有哪几种状态
|
消息中间件 Linux 调度
Linux进程管理:深入探索进程的创建、终止与调度
在Linux操作系统中,进程管理是一个重要的主题。进程是程序的执行实例,负责执行应用程序的代码,并拥有自己的内存空间和资源。本文将深入探讨Linux进程管理的相关知识,包括进程的创建、终止与调度,以帮助读者更好地理解Linux操作系统中的进程运行机制。
372 0
|
Unix Linux 调度
Linux之创建进程、查看进程、进程的状态以及进程的优先级
Linux之创建进程、查看进程、进程的状态以及进程的优先级
244 0
C#开发:执行进程等待
C#开发:执行进程等待
270 0