进程间通信(一)
进程间通信
- 一、介绍
- 进程间通信目的
- 基本概念
- 二、进程间通信方式
- 信号
- 信号的处理
- 信号的复位
- 管道
- 用c创建、使用管道
- 有名管道
- 有名管道io使用
- System V IPC
- ipcs命令
- ipcrm命令

一、介绍
进程间通信是指两个或多个进程之间进行数据交换和信息传递的机制。其目的是使不同进程能够相互协作、共享数据和完成任务。进程间通信通常基于操作系统的内核机制,因为进程的用户空间是私有的,而内核空间是共享的。
进程间通信目的
- 数据传输:一个进程需要将他的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
- 进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
基本概念
- 进程阻塞:当一个进程在执行某些操作的条件得不到满足时,就自动放弃 CPU 资源而进入休眠状态,以等待条件的满足。当操作条件满足时,系统就将控制权返还给该进程继续进行未完的操作
- 共享资源:因为计算机的内存、存储器等资源是有限的,无法为每一个进程都分配一份单独的资源。所以系统将这些资源在各个进程间协调使用,称为共享资源
- 锁定:当某个进程在使用共享资源使用,可能需要防止别的进程对该资源的使用。比如,一个进程在对某个文件进行读操作时,如果别的进程也在此时向文件中写入了内容,就可能导致进程读入错误的数据。为此,Linux 提供一些方法来保证共享资源在被某个进程使用时,别的进程无法使用。这就叫做共享资源的锁定。
二、进程间通信方式
信号
信号是UNIX系统所使用的进程通信方式,最古老的一种。系统用它来通知一个或多个进程异步事件的发生,比如键盘上某个按键被按下,或者计时器达到某个特定的事件。信号不但能从内核发往一个进程,也能从一个进程发往另一个进程。
信号的处理
UNIX的系统调用signal()用于接收一个指定类型的信号。声明如下
int signal(int sig,__sighandler_t handler);
sig表示处理的信号类型,除了SIGKILL和SIGSTOP外的任何一种信号
通过KILL -l 查看信号类型
kill -l
示例:
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
int catch(int sig);
int main(void)
{signal(SIGINT,catch); /* 将 SIGINT 信号与 catch 函数关联 */printf("xixi\n");sleep(10);printf("end\n");return;
}int catch(int sig)
{printf("Catch succeed!\n");return 1;
}
当程序运行时,我们按下中断键(Ctrl+C),进程被中断,函数catch就被执行。他执行完毕后,进程回到中断点继续执行
信号的复位
在Linux中,当一个信号的信号处理函数执行时,如果该进程又收到了该信号,该信号会自动被存储而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。
管道
管道就是将一个程序的输出和另外一个程序的输入连接起来的单向通道。它是UNIX/Linux 系统的各种进程通信方法中,最古老而应用最为广泛的一种(特别是在 shell中)。
ls -l|more
示意图:
![[Pasted image 20251110174904.png]]
用c创建、使用管道
在 C 程序中,我们使用系统函数 pipe()来建立管道。它只有一个参数:一个有两个成
员的整型数组,用于存放 pipe()函数新建立的管道句柄。
数组中的第一个元素(fd[0])是从管道中读出数据的句柄,第二个元素(fd[1])是向
管道写入数据的句柄。也即是说,fd[1]的写入由 fd[0]读出.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main(void)
{int fd[2], nbytes;pid_t childpid;char string[] = “Hello, world!\n”;char readbuffer[80];pipe(fd);if((childpid = fork()) == -1){perror(“fork”);exit(1);}if(childpid == 0){
/* 子进程关闭管道的读句柄 */close(fd[0]);/* 通过写句柄向管道写入信息 */write(fd[1], string, strlen(string));_exit(0);}else{
/* 父进程关闭管道的写句柄 */close(fd[1]);/* 通过读句柄从管道读出信息 */nbytes = read(fd[0], readbuffer, sizeof(readbuffer));printf(“Received string: %s”, readbuffer);}return(0);
}
有名管道
为了解决管道不能提供非父/子关系进程间通信的缺陷,在管道的基础上发展了有名管
道(FIFOs)的概念。
管道在Linux系统内部都是以文件节点(inode)的形式存在的,但是由于其对外的不可见性,无法创建新的句柄对其进行访问。而有名管道在 Linux 系统中以一种特殊的设备文件的形式存在于文件系统中。这样它不仅具有了管道的通信功能,也具有了普通文件的优点(可以同时被多个进程共享,可以长期存在等等),有效的解决了管道通信的缺点。
有名管道io使用
server
#include<iostream>
#include<stdlib.h>
#include<sys/stat.h>
#include<unistd.h>
#include<linux/stat.h>#define FIFO_FILE "sample FIFO"
int main()
{FILE* fp;char readbuf[80];umask(0);//c创建有名管道mknod(FIFO_FILE,S_IFIFO|0666,0);while(1){//打开有名管道道fp=fopen(FIFO_FILE,"r");//读取数据fgets(readbuf,80,fp);printf("recived string:%s\n",readbuf);//关闭fclose(fp);}return 0;}
因为有名管道自动支持进程阻塞,所以我们可以让这个 server 在后台运行:
#include<stdio.h>
#include<stdlib.h>
#define FIFO_FIFE "sampleFIFO"
int main(int argc,char* argv[])
{FILE* fp;if(argc!=2){printf("USAGE:fifoclient [string]\n");exit(1);}//打开有名管道if((fp=fopen(FIFO_FIFE,"w"))==NULL){perror("fopen");exit(1);}//写入数据fputs(argv[1],fp);fclose(fp);return 0;}
由于有名管道的自动阻塞特性,当上面的 server 打开一个有名管道准备读入时,server
进程就会被阻塞以等待其他进程(在这里是我们的 client 进程)在有名管道中写入数据。
反之亦然。不过,如果需要,我们也可以在打开一个有名管道时使用 O_NONBLOCK标志来关闭它的自动阻塞特性
System V IPC
AT&T在 UNIX System V中引入几种新的进程通信方式,即消息队列,信号量和共享内存,统称为System V IPC.
System V IPC的一个显著特定,是他的具体实例在内核中是以对象的形式出现的,我们称之为IPC对象。每个IPC对象在系统内核中都有一个唯一的标识符。通过标识符可以正确的引用指定的IPC对象。需要注意的是,标识符的唯一性只在每一类的IPC对象内成立。比如说,一个消息队列和一个信号量的标识符可能是相同的,但绝不会出现两个由相同标识符的消息队列。
标识符只在内核中使用,IPC 对象在程序中是通过关键字(key)来访问的。和 IPC 对
象标识符一样,关键字也必须是唯一的。而且,要访问同一个 IPC 对象,Server 和 Client必须使用同一个关键字
通常使用ftok()函数来生成关键字
key_t ftok(char* pathname,char proj)
ftok()函数通过混合pathname所指文件的inode和minor 和device值以及proj的值来产生关键字。这样并不能完全保证关键字的唯一性,不过程序可以检测关键字的冲突并通过更换 pathname 和 proj 的组合来产生新的关键字。下面是一段使用 ftok 的代码:
key_t mykey;
mykey=ftok("/tmp/myapp",'a');
这段代码中,ftok函数混合文件和字符a来产生关键字mykey。
ipcs命令
ipcs 在命令行终端显示系统内核的ipc对象状况
ipcs -q //只显示消息队列
ipcs -m //显示共享内存
ipcs -s //只显示信号量
ipcrm命令
使用这个命令强制系统删除已存在的IPC对象
命令如下:
ipcrm <msg|sem|shm> <IPC ID>
ipcrm后面的参数是指定要删除的IPC对象类型,分别是消息队列、信号量和共享内存。然后需要的是要删除对象的标识符。标识符可以通过ipcs命令获取。
