【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());
}


相关文章
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
74 1
|
9天前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
62 20
|
29天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
98 13
|
1月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
1月前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
7月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
150 13
|
6月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
6月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
207 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
5月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
6月前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
189 1

热门文章

最新文章