【Linux进程】三、进程控制——fork()系统调用深度刨析

简介: 【Linux进程】三、进程控制——fork()系统调用深度刨析

🥇1. fork()、getpid()、getppid()函数介绍

🥈1.1 fork()函数介绍

fork()用于创建一个子进程,我们在shell下执行一个命令其实也是通过fork()实现的,fork()是Linux下最基本的一个系统调用。fork()最大的特点就是一次调用,两次返回,两次返回主要是区分父子进程,因为fork()之后将出现两个进程,所以有两个返回值,父进程返回子进程ID,子进程返回0。

  • 包含头文件
#include <unistd.h>
  • 函数原型
pid_t fork(void);
  • 函数功能
    fork() creates a new process by duplicating the calling process. The new process, referred to as the child, the calling process, referred to as the parent. 通过复制的方式创建一个进程,被创建的进程称为子进程,调用进程称为父进程,复制的子进程是从父进程fork()调用后面的语句开始执行的。
  • 函数参数
  • void
  • 函数返回值
  • On success, the PID of the child process is returned in the parent, and 0 is returned in the child.
  • 父进程返回子进程ID
  • 子进程返回0
  • On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately. 失败返回-1并设置errno。

🥈1.2 getpid()函数与getppid()函数介绍

  • 包含头文件
#include <sys/types.h>
#include <unistd.h>
  • 函数原型
pid_t getpid(void);
pid_t getppid(void);
  • 函数功能
  • getpid() returns the process ID of the calling process. 获得当前进程的ID。
  • getppid() returns the process ID of the parent of the calling process. 获得当前进程的父进程的ID。
  • 函数参数
    void
  • 函数返回值
  • getpid()返回当前进程ID
  • getppid()返回当前进程的父进程ID

🥇2. fork()工作机制

🥈2.1 fork()的实现机制——一次调用两次返回与进程复制

下面通过一个案例来分析fork()是如何创建进程,又是如何返回的。

/************************************************************
  >File Name  : fork_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月18日 星期三 15时59分29秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char* argv[])
{
  printf("=== process begin ===\n");
  pid_t pid = fork();
  if(pid == -1)
  {
    perror("fork err");
    return -1;  
  }
  if(pid == 0) /*子进程*/
  {
    printf("i am child: %d, may parent: %d\n", getpid(), getppid());  
        /*  test2
        while(1)
        {
            printf("fork process\n");
            sleep(1);
        }
        */
  }
  if(pid > 0)
  {
    printf("i am call: %d, child: %d, parent: %d\n", getpid(), pid, getppid()); 
        /*  test1
        sleep(1);
        */
        /*  test2
        while(1)
        {
          sleep(1);
        }
        */
  }
  printf("=== process end ===\n");
  return 0;
}

编译运行该程序,我们会发现一个很有意思的现象

首先反常的第一点,我们在程序中的打印顺序是先进入子进程(pid == 0)分支,再进入父进程(pid > 0)分支,但实际的打印顺序是先执行了父进程分支的printf()函数,后执行的子进程分支到的printf()函数;第二点是,在执行子进程的printf()函数时,竟然已经回到了shell下,可以看图中高亮标出的位置。下面对着两点详细分析;第三点,子进程打印的父进程ID和父进程自己打印的ID不同。

我们已经知道,fork()系统调用的特点是一次调用两次返回,并且子进程的创建是对父进程的复制,那么是从哪复制开始复制的呢,我们根据程序运行结果分析,程序只打印了一次begin语句,说明不是从头开始复制的,实际上它是从fork()的下一句开始复制的,从fork()开始,后面就成了两个分支。

我们看到的运行结果中红色标记的①,实际上是由父进程打印的,②是由子进程打印的,既然不是一个进程打印的,那也就没有先后顺序的问题了。而子进程打印的父进程ID是1,父进程打印的自己的ID是5270,这是因为在子进程结束前,父进程就已经结束了,新建的子进程变成了孤儿进程,所以它会被1号进程收养,所以新建子进程的父进程ID是1,这也是为什么第二个printf()语句是在shell下执行的原因,因为原来的父进程结束了,所以回到了shell进程下,此时子进程还没有结束,它被1号进程接管,继续执行后面的语句,直到结束。

(实际上,这里的3397进程就是我们的shell进程,shell进程是我们自己启动的进程的父进程;而1号进程则是init进程,init进程是Linux下最原始的进程,是所有进程最终的父进程。)

我们可以在父进程中加一个sleep()函数(放开上面代码中test1注释掉的代码即可),让父进程等一下子进程,并看一下效果,这次就好了。

🥈2.2 shell进程控制命令

下面我们通过shell下的进程控制命令进一步分析上面所讲的fork()实现机制,首先介绍几个命令:

  • ps aux

  • ps ajx:可以查看父进程ID,追溯进程之间的关系

  • kill 给进程发送信号,通过这个命令可以杀死进程,常用的两个参数
  • kill -l:查看所有信号;

  • kill -9 pid:给进程号为pid的进程发送9号信号,杀死进程,实际上相当于 kill -SIGKILL pid,也可以直接通过 kill pid 来杀死pid进程;

我们再做一个测试,将上面代码中的test2处的注释放开,编译并运行程序,让两个进程一直在while中执行

开始循环后,我们另起一个shell来查看进程信息,可以通过管道和grep过滤我们需要的进程信息

通过ajx追溯进程血缘关系

可以看到fork()的调用进程5721,它的父进程是3397也就是 bash shell 进程

通过kill杀死父进程,可以看到子进程被1号进程接管

1号进程就是init进程

🥇3. 进程创建的控制

🥈3.1 控制进程创建个数

我们通过一个for循环来创建进程

/************************************************************
  >File Name  : mutifork.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月18日 星期三 19时33分50秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char* argv[])
{
  int i = 0;
  pid_t pid = 0;
  for(i = 0; i < 5; i++)
  {
    pid = fork();
    if(pid == 0)
    {
      printf("i am chiled: %d, ppid: %d\n", getpid(), getppid());
      /*
      break;
      */
    } 
    if(pid > 0)
    {
      printf("i am call: %d, child:%d, ppid: %d\n", getpid(), pid, getppid());
    }
  }
  while(1)
  {
    sleep(1);
  }
  return 0;
}

编译执行,在程序我们期望的是创建5个进程,但是实际运行后出现了一大堆进程,我们可以用wc命令统计一下

shell命令统计创建的进程个数

ps aux | grep mutifork | grep -v grep | wc -l

总共有32个进程,我们在程序中只循环了5次,为什么有32个进程呢,下面看一张图

每次fork的时候,进程都会一分为二,所以5次循环相当于创建了2的5次方,也就是32个进程。要想避免这种情况,只需要根据返回值判断当前为子进程的时候就退出循环即可,也就是把上面代码中注释掉的break放开即可。

🥈3.2 进程顺序控制

使用fork()创建的进程都是一样的,在操作系统看来没有区别,先后顺序也是不确定的,我们要想控制进程的退出顺序,需要自己去实现这个逻辑。比如说我们可以依据for循环中i的值来判断哪个进程先创建的,哪个进程后创建的,按照逻辑i小的应该是先创建的,因为C语言就是顺序执行的。因为子进程创建出来就break退出for循环了,所以五个子进程对应的i是0-4,而只有最开始的父进程可以执行到i=5。

sleep(i); /*不同进程睡眠时间不同,第一个创建的进程
      i的值为0,睡眠最短,最先退出,后面的进
      程对应的i逐渐增大,睡眠时间增加,退出越晚*/
if(i < 5)
{
    printf("child: %d, parent: %d\n", getpid(), getppid());
}
else
{
    printf("parent: %d\n", getpid());
}


相关文章
|
12月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
347 16
|
11月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
257 0
|
11月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
327 0
|
11月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
205 0
|
11月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
265 0
|
8月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
921 1
二、Linux文本处理与文件操作核心命令
|
8月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
533 137
|
8月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1417 58
|
7月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
1286 2
|
8月前
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
675 16