进程通信
进程通信( InterProcess Communication,IPC)就是指进程之间的信息交换。实际上,进程的同步与互斥本质上也是一种进程通信(这也就是待会我们会在进程通信机制中看见信号量和 PV 操作的原因了),只不过它传输的仅仅是信号量,通过修改信号量,使得进程之间建立联系,相互协调和协同工作,但是它缺乏传递数据的能力。
虽然存在某些情况,进程之间交换的信息量很少,比如仅仅交换某个状态信息,这样进程的同步与互斥机制完全可以胜任这项工作。但是大多数情况下,进程之间需要交换大批数据,比如传送一批信息或整个文件,这就需要通过一种新的通信机制来完成,也就是所谓的进程通信。
再来从操作系统层面直观的看一些进程通信:我们知道,为了保证安全,每个进程的用户地址空间都是独立的,一般而言一个进程不能直接访问另一个进程的地址空间,不过内核空间是每个进程都共享的,所以进程之间想要进行信息交换就必须通过内核。
Linux 内核提供的常见的进程通信机制:
- 管道(也称作共享文件)
- 消息队列(也称作消息传递)
- 共享内存(也称作共享存储)
- 信号量和 PV 操作
- 信号
- 套接字(Socket)
一、 管道
1. 匿名管道
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。如果想实现相互通信(全双工通信),我们需要创建两个管道才行。另外,通过管道符 | 创建的管道是匿名管道,用完了就会被自动销毁。并且,匿名管道只能在具有亲缘关系(父子进程)的进程间使用。也就是说,匿名管道只能用于父子进程之间的通信。
在 Linux 的实际编码中,是通过 pipe 函数来创建匿名管道的,若创建成功则返回 0,创建失败就返回 -1。fork()调用后,操作系统会复制父进程的地址空间到子进程,包括代码段、数据段、堆栈等。这意味着子进程拥有父进程的内存空间的一个副本。fork()返回两次,一次在父进程中,返回子进程的PID;一次在子进程中,返回0。如果fork()调用失败,它会在父进程中返回-1,并且设置errno来指示错误:
如果pid1 == 0,则当前代码运行在子进程中。
如果pid1 > 0,则当前代码运行在父进程中。:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
pid_t pid1;
int fields[2];
char buffer[80];
char s[100];
char ss[100];
if (pipe(fields) != 0)
{
fprintf(stderr, "Createpipe error:%s\n\a", strerror(errno));
exit(1);
}
if ((pid1 = fork()) < 0)
printf("fork child error!\n");
/* 子进程写入数据 */
if (pid1 == 0)
{
printf("fork child,pid is %d, child is sending a message !\n", pid1);
char s[] = "hello!\n";
write(fields[1], s, sizeof(s));
exit(0);
}
/* 父进程读取数据 */
else
{
printf("parent read start, pid is %d!\n", pid1);
read(fields[0], buffer, 80);
printf("parent receive the message:%s", buffer);
}
exit(0);
}
2.命名管道
命名管道fifo解决了pipe只能有关系的进程才能通信的问题,实现一个有名管道实际上就是实现一个FIFO文件,有名管道一旦建立,之后它的读,以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但仅是一个节点而已,文件的数据还是存在内核缓冲页面上,和普通管道相同。
使用 Linux 命令 mkfifo 来创建有名管道:
mkfifo myPipe
myPipe 就是这个管道的名称,接下来,我们往 myPipe 这个有名管道中写入数据:
echo "hello" > myPipe
执行这行命令后,你会发现它就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。于是,我们执行另外一个命令来读取这个有名管道里的数据:
cat < myPipe
读进程,定义一个结束消息,保证在写进程先退出时读进程不会发生疯读现象:
// 读进程
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define FIFO_PATH "myfifofile"
#define END_MESSAGE "END" // 定义结束消息
int main()
{
int fd;
char cont_r[256]; // 增加1字节来存储结束字符
// 创建命名管道
if (mkfifo(FIFO_PATH, 0666) < 0 && errno != EEXIST)
{
perror("create fifo failed");
return -1;
}
else
{
printf("create fifo success\n");
// 打开文件进行读操作
fd = open(FIFO_PATH, O_RDONLY, 0666);
if (fd > 0)
{
while (1)
{
// 读取数据
ssize_t bytes_read = read(fd, cont_r, sizeof(cont_r) - 1);
if (bytes_read <= 0)
{
// 如果没有数据可读,退出循环
break;
}
// 确保字符串以空字符结尾
cont_r[bytes_read] = '\0';
// 打印读取到的数据
printf("read: %s\n", cont_r);
// 检查是否接收到结束消息
if (strcmp(cont_r, END_MESSAGE) == 0)
{
printf("Received end message, exiting...\n");
break;
}
}
close(fd);
}
else
perror("open failed");
}
return 0;
}
写进程:
// 写进程
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define FIFO_PATH "myfifofile"
int main()
{
int fd;
char cont_w[] = "hello sundy";
const char *end_message = "END"; // 结束消息
if (mkfifo(FIFO_PATH, 0666) < 0 && errno != EEXIST)
{
perror("create fifo failed");
return -1;
}
else
{
p