进程通信
进程通信:进程间的信息交换
1、进程通信类型

1.1 共享储存器
通过共享某些数据结构 或存储区来实现进程的通信。
- 共享数据结构。例如:生产者-消费者问题中的有界缓冲区。这种方式属于低级进程通信。
- 共享存储区 。在内存中划分出一块共享存储区 ,各个进程均可直接读写,通信前进程只需要申请一个分区。这个属于高级进程通信 ,也是最快的进程通信。
低级进程通信:效率低,通信不透明,共享数据结构的设置,进程的同步互斥等问题均需要程序员设置。
高级进程通信:提供高级通信命令(原语),过程透明,高效传输大量数据。
1.2 管道通信
管道(pipe)指进程间的通信共享文件(pipe文件)。一个管道机制必须有以下特征:
- 互斥
- 同步
- 确定对方是否存在
管道首创UNIX系统
1.3 消息传递
以格式化消息为单位,数据在消息中,利用OS提供通信命令(原语),在进程间进行消息传递。这也属于高级进程通信。
实现方式分为直接通信 和间接通信两种。
1.4 客户机-服务器系统
主要实现方式:套接字 、远程过程调用 和远程方法调用。
套接字
套接字是通信标志 类型的数据结构,包含通信目标地址 、通信使用的端口号,通信网络的**传输层协议。**一共分为两个类型:
-
基于文件:类似于通道通信
-
基于网络 :采用非对称方式通信。
非对称:发送者提供接收者名称。
远程过程调用和远程方法调用
远程过程调用(RPC)是一个通信协议,允许一台主机的进程调用另一台主机的进程。
如果软件采用面向对象编程 ,远程过程调用也可叫做远程方法调用。
2、 消息传递通信的实现方式
2.1 直接通信
直接通信原语
- 对称寻址 ,发送和接受进程均需要 以显式方式提供标识符。
- 非对称寻址 ,发送者提供接收者名称,而接收者 只需要填写表示源线程的参数(完成通信后的返回值)。
消息格式
共有定长 和变长两种消息格式,
定长可以减少消息的处理和存储开销。
变长适合发送长信息
通信链路
单向通信链路只允许发送进程向接收进程发送
双向通信链路,进程可以互相发送消息
2.2 间接通信|信箱通信
进程间的通信通过建立在随机存储器 的共享缓冲区 上的中间实体 来暂存消息。每个信箱都有唯一的标识符 。可以实现实时通信 和非实时通信。

3、通信实例
3.1 管道
3.1.1 匿名管道|无名管道
数据只能单向流动 ,主要用于父子进程 间的通信,即存在血缘关系。
C
int main() {
int file_descriptors[2];
pid_t pid; // 子进程号
char buf[256] = {0}; // 初始化缓冲区
int returned_count;
if (pipe(file_descriptors) == -1) {
perror("pipe");
exit(1);
}
if ((pid = fork()) == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
printf("in the spawned (child) process ...\n");
// 子进程写数据,关闭通道读端
close(file_descriptors[INPUT]);
if (write(file_descriptors[OUTPUT], "test data", strlen("test data")) == -1) {
perror("write");
exit(1);
}
close(file_descriptors[OUTPUT]);
exit(0);
} else {
printf("in the spawned (parent) process ...\n");
// 父进程读数据,关闭写端
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
if (returned_count == -1) {
perror("read");
exit(1);
}
printf("%d bytes of data received from spawned process: %.*s\n", returned_count, returned_count, buf);
close(file_descriptors[INPUT]);
}
return 0;
}

子进程执行
write
函数时将字符串传递给操作系统内核。随后被写入管道缓冲区。父进程执行
read
函数 时,将数据从内核缓冲区拷贝到buf
中。
3.1.2 命名管道|有名管道
文件系统中创建特殊文件 ,用于在不相关的进程间通信。
有两种命令方式可以创建有名管道:函数mkfifo
和系统调用mknod
。
bash
mkfifo("[name]","rw");
mknod [name] p;
写入数据
c
int main() {
int fd;
char message[] = "Hello, name pipe!";
// 打开有名管道进行写入
fd = open(FIFO_PATH, O_WRONLY);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
// 向管道中写入数据
if (write(fd, message, strlen(message)) < 0) {
perror("write");
close(fd);
exit(EXIT_FAILURE);
}
printf("Message sent: %s\n", message);
// 关闭文件描述符
close(fd);
return 0;
}
读入数据
c
int main() {
int fd;
char buffer[256];
ssize_t bytes_read;
// 打开有名管道进行读取
fd = open(FIFO_PATH, O_RDONLY);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
// 从管道中读取数据
bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read < 0) {
perror("read");
close(fd);
exit(EXIT_FAILURE);
}
buffer[bytes_read] = '\0';
printf("Message received: %s\n", buffer);
// 关闭文件描述符
close(fd);
return 0;
}

3.2 信号
信号用于通知接收进程有某事发生。信号除了可以用于进程间通信 ,还可以被进程发送给自身 。这是一种异步通信模式。
标准信号常量
宏 | 信号 |
---|---|
SIGABRT | (Signal Abort) 程序异常终止。 |
SIGFPE | (Signal Floating-Point Exception) 算术运算出错,如除数为 0 或溢出(不一定是浮点运算)。 |
SIGILL | (Signal Illegal Instruction) 非法函数映象,如非法指令,通常是由于代码中的某个变体或者尝试执行数据导致的。 |
SIGINT | (Signal Interrupt) 中断信号,如 ctrl-C,通常由用户生成。 |
SIGSEGV | (Signal Segmentation Violation) 非法访问存储器,如访问不存在的内存单元。 |
SIGTERM | (Signal Terminate) 发送给本程序的终止请求信号。 |
c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义信号处理函数
void sigint_handler(int signum) {
printf("Received SIGINT signal. Exiting...\n");
_exit(0);
}
int main() {
// 设置信号处理函数
signal(SIGINT, sigint_handler);
printf("Running... Press Ctrl + C to terminate.\n");
while (1) {
sleep(1);
}
return 0;
}
3.3 消息队列(System V)
system V标准是通过共享内存来实现通信的技术,两个进程的地址空间通过页表的映射指向同一块内存的物理地址,一个读、一个写,以此进行通信 。因为system V是内存级的通信,导致其只能进行本地通信,由于网络通信的发展,这种通信方式已经被逐渐淘汰。共享内存不随进程的结束而自动释放,生命周期随内核(文件生命周期随进程)。
3.4 共享内存
内存中划分出一块共享存储区 ,各个进程均可直接读写。存在映射/dev/mem设备和内存映像文件两种方法,但前一种直接控制物理内存,并不常用。
共享内存的特点:
-
共享内存不提供任何保护共享内存的机制,因为数据不一致问题导致。但在使用时,可以通过代码限制手动提供保护。
-
共享内存是所有进程IPC速度最快的,因为共享内存大大减少了数据的拷贝次数
3.5 信号量
信号量又叫信号灯,是一种保护临界资源的数据。信号量把共享内存不整体使用,而是分块来使用。信号量的本质是一个计数器,申请信号量就是对公共资源的一种预定机制。如果对共享资源整体使用,其实就是资源只有一个,这时信号量称为二元信号量。
所以进程通过信号量访问共享内存的方式为:
申请信号量------访问共享内存------释放信号量
信号量不是全局变量,因为全局变量不能被所有进程看到,全局变量++--操作也不是原子的(在汇编代码上看不是一条语句)。所以信号量本身也是必须是共享资源。 信号量的申请和释放都必须是安全的,申请和释放信号量称为PV操作,保证安全的方法是使操作(++--)是原子性的。
3.6 套接字
套接字(Socket)是一种网络编程 接口,用于实现不同主机之间的进程通信。它为网络通信提供了一种抽象机制,使得开发者可以方便地在不同的网络协议和传输层协议上进行数据传输。适用于异地计算机进程间通信和本地进程间通信。