进程通信
1.进程间的通信
进程之间的内存是隔离的,如果多个进程之间需要进行信息交换,常用的方法有:
(1)Unix Domain Socker IPC
(2)管道(有名管道、无名管道)
(3)共享内存
(4)消息队列
(5)信号量
1.1SystemV IPC 和 POSIX IPC
(1)System V
System V 是一种基于UNIX的操作系统版本,引入了许多新的特性和标准,被很多UNIX系统和类UNIX系统采纳
(2)System V IPC
它是System V操作系统引入的一组进程间通信机制,包括消息队列、信号量和共享内存。这些机制允许不同的进程以一种安全且高效的方式共享数据和同步操作
1)消息队列:允许进程以消息的形式交换数据,这些消息存储在队列中,直到它们被接收。
2)信号量:主要用于进程间的同步,防止多个进程同时访问相同的资源
3)共享内存:允许多个进程访问同一块内存区域,提供了一种非常高效的数据共享方式。
System V IPC是UNIX和类UNIX中常用的,通过关键字来标识访问IPC资源
(3) POSIX IPC
POSIX IPC是 POSIX 标准的一部分,提供了更现代和标准化的进程间通信方式,同时包括消息队列、信号量和共享内存三种方式。
1)消息队列:类似于System V,但通常具有更简洁的API和更好的错误处理能力。
2)信号量:提供了更多的功能和更高的性能,支持更大范围的操作。
3)共享内存:提供了更多的控制和配置选项,以支持更复杂的应用场景。
POSIX IPC使用名字作为唯一标识符 ,这些名字通常是以"/"开头的字符串,用于唯一地识别资源,如消息队列、信号量或共享内存对象。
(4)两者的比较
System V 和POSIX IPC在功能上有所重叠,但它们在实现和API设计上有明显的区别。POSIX IPC更为现代更标准化,提供了更好的跨平台支持和更易于使用的API。而System V更早的被大量UNIX使用,因此在一些比较老的环境中仍比较重要。在选用使用哪种IPC机制时,应考虑应用程序的具体需求,目标系统的支持程度
System V和POSIX IPC各种提供了一组API,但效果类似
1.2匿名管道(pipe)
管道,半双工,仅限于父子进程通信,单向通信
这里是引用
匿名管道是位于内核的一块缓冲区,用于进程间通信,创建匿名管道的系统调用为pipe,执行man 2 pipe查看手册,其声明如下:
#include<stdio.h>
/*
在内核空间创建管道,用于父子进程或者其他相关联的进程之间通过管道进行双向的数据传输
pipefd:用于返回指向管道两端的两个文件描述符,pipefd[0]指向管道的读端,pipefd[1]指向管道的写端。
return:成功 0
不成功 -1,并且pipefd不会改变
*/
int pipe(int pipefd[2]);
样例代码:
执行文件的时候传入参数,父进程通过管道传给子进程,子进程打印出来。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <string.h>
int main(int argc,char const* argv[])
{
int pid;
//定义管道数组
int pipefd[2];
//没有参数传入
if(argc !=2)
{
fprintf(stderr,"%s:请填写需要传递的信息\n",argv[0]);
exit(EXIT_FAILURE);
}
//创建管道
if(pipe(pipefd)==-1)
{
perror("创建管道失败");
exit(EXIT_FAILURE);
}
//复制父子进程
pid=fork();
//进程创建失败
if(pid<0)
{
perror("邀请新学员失败!");
exit(EXIT_FAILURE);
}
if(pid==0)
{
//子进程
//关闭写端
close(pipefd[1]);
char str[100] ={0};
sprintf(str,"新学员%d接收的信息\n",getpid());
write(STDOUT_FILENO,str,sizeof(str));
char buf;
//从内核读
while(read(pipefd[0],&buf,1))
{
write(STDOUT_FILENO,&buf,1);
}
write(STDOUT_FILENO,"\n",1);
//关闭读端
close(pipefd[0]);
exit(EXIT_SUCCESS);
}else
{
//父进程 写入管道数据
//关闭读端
close(pipefd[0]);
printf("老学员%d对新学员传递的信息:\n",getpid());
//把传入的参数写入内核
write(pipefd[1],argv[1],strlen(argv[1]));
//关闭写端
close(pipefd[1]);
//等待子进程关闭
waitpid(pid,NULL,0);
exit(EXIT_SUCCESS);
}
return 0;
}
1.3有名管道(FIFO)
pipe是匿名管道,只能在父子进程间使用,在某些场景不能满足需求。与之相对的是有名管道,在Linux中称为FIFO,first in first out,先进先出队列
FIFO与pipe一样,提供了双向进程间的通信渠道。但不管哪一个,同一个管道只应用于单向通信,否则会出现通信混乱(可能读到自己发的数据)。
有名管道创建完成后可重复使用,不过不推荐,最好在使用完成后清除管道。
使用open()打开有名管道时,flags设置为O_WRONLY则当前进程可以用于向有名管道中写入数据,设置为O_RDONLY则当前进程用于从有名管道中读取数据。设置为O_RDWR从技术上可行的,但此时管道既读又写很可能导致一个进程读取到自己发送的数据,通信出现混乱。因此,打开有名管道时,flags只应为O_WRONLY或O_RDONLY
1)库函数
可以执行man 3 mkfifo查看文件说明
#include<sys/types.h>
#include<sys/stat.h>
/*
用于创建有名管道,该函数可以创建一个路径为pathname的FIFO专用文件,
mode指定了FIFO权限,FIFO的权限和它绑定的文件是一致的。FIFO和pipe唯一的区别
在于创建方式的差异。一旦创建了FIFO专用文件,任何进程都可以像操作文件一样
打开FIFO执行读写操作
pathname 有名管道绑定的文件路径
mode 有名管道绑定的文件权限
return int
*/
int mkfifo(const char *pathname,mode_t mode);
//pathname可以随便写,它就相当于一个中转站。并不会真的写入
!!注意:当pathname 已经存在的时候,再次创建管道就会报错,错误代码为17。可以在发送结束后用unlink()清除管道
内核为每个被进程打开的FIFO专用文件维护一个管道对象。当进程通过fifo交换数据时,内核会在内部传递所有数据,不会将其写入文件系统,因此pathname 文件大小为0
其中开头的p表示它是有名管道文件
样例代码
写入
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>
int main(int argc,char const *argv[])
{
int fd;
char*pipe_path="./myfifo";
//打印错误信息,当不是文件已存在的时候关闭进程
if(mkfifo(pipe_path,0664) !=0)
{
perror("mkfifo");
if(errno != 17)
{
exit(EXIT_FAILURE);
}
}
//对有名管道的特殊文件创建fd
fd = open(pipe_path,O_WRONLY);
if(fd==-1)
{
perror("open");
exit(EXIT_FAILURE);
}
char buf[100];
ssize_t read_num;
//读取控制台数据写入管道中
while((read_num=read(STDIN_FILENO,buf,10))>0)
{
write(fd,buf,read_num);
}
if(read_num<0)
{
perror("read");
close(fd);
exit(EXIT_FAILURE);
}
printf("发送数据完成进程终止\n");
close(fd);
//释放管道
//清除对应的特殊文件
if(unlink(pipe_path)==-1)
{
perror("unlink");
}
return 0;
}
读取
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char const *argv[])
{
int fd;
char*pipe_path="./myfifo";
/*读取管道的时候不需要再一次创建
if(mkfifo(pipe_path,0664) !=0)
{
perror("mkfifo");
exit(EXIT_FAILURE);
}
**/
//对有名管道的特殊文件创建fd
fd = open(pipe_path,O_RDONLY);
if(fd==-1)
{
perror("open");
exit(EXIT_FAILURE);
}
char buf[100];
ssize_t read_num;
//读取管道数据打印控制台中
while((read_num=read(fd,buf,10))>0)
{
write(STDOUT_FILENO,buf,read_num);
}
if(read_num<0)
{
perror("read");
close(fd);
exit(EXIT_FAILURE);
}
else
printf("接收数据完成进程终止\n");
close(fd);
return 0;
}