【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天前
|
存储 Shell Linux
Linux进程概念(下)
本文详细的介绍了环境变量和进程空间的概念及其相关的知识。
8 0
Linux进程概念(下)
|
18小时前
|
监控 网络协议 关系型数据库
如何在Linux中查看正在运行的进程以及过滤特定端口和进程名称
如何在Linux中查看正在运行的进程以及过滤特定端口和进程名称
4 0
|
1天前
|
Linux
linux指令按端口查找和杀死进程
linux指令按端口查找和杀死进程
10 0
|
1天前
|
Unix Linux 调度
一篇文章讲明白linux僵死进程
一篇文章讲明白linux僵死进程
|
1天前
|
消息中间件 负载均衡 Linux
【linux】匿名管道|进程池
【linux】匿名管道|进程池
4 0
|
1天前
|
Shell Linux
【linux】进程替换的应用|shell解释器的实现
【linux】进程替换的应用|shell解释器的实现
7 0
|
安全 Linux 调度
关于linux系统如何实现fork的研究(二)【转】
转自:http://www.aichengxu.com/linux/7166015.htm 本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言   前一篇关于linux系统如何实现fork的研究(一)通过代码已经说明了从用户态怎么通过软中断实现调用系统调用clone函数,而clone函数的精华copy_process函数就在此篇文章中进行分析。
1003 0
|
Linux C语言
关于linux系统如何实现fork的研究(一)【转】
转自:http://www.aichengxu.com/linux/4157180.htm 引言 fork函数是用于在linux系统中创建进程所使用,而最近看了看一个fork()调用是怎么从应用到glibc,最后到内核中实现的,这片文章就聊聊最近对这方面研究的收获吧。
1064 0
|
1天前
|
安全 Linux Shell
Linux中SSH命令介绍
Linux中SSH命令介绍
10 2
|
1天前
|
NoSQL 关系型数据库 MySQL
linux服务器重启php,nginx,redis,mysql命令
linux服务器重启php,nginx,redis,mysql命令
6 1

热门文章

最新文章