1. 什么需要进程间通信?
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2.进程通信的分类
2.1 管道
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
2.1.1 匿名管道
//头文件
#include<unistd.h>
//创建管道
int pipe(int fd[2]);
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码。
如下demo我们可以体会下匿名管道的操作特性
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int pipefd[2] = {0};
int ret = pipe(pipefd);
int pid = fork();
if(ret < 0){
perror("pipe error");
return -1;
}
if(pid < 0){
perror("fork error");
return -1;
}else if(pid == 0){
char buff[1024] = {0};
int ret = read(pipefd[0],buff,1023);
printf("buff:[%s] - [%d]\n",buff,ret);
}else{
char *ptr = "hello friend ~~";
write(pipefd[1],ptr,strlen(ptr));
sleep(1);
}
while(1){
printf("-----------%d\n",getpid());
sleep(1);
}
return 0;
}
我们看到父进程通过管道写端将信息传给子进程,子进程通过读端拿到消息,那么管道的读写规则是什么样的呢?
管道读写规则:
- 当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。 - 当管道满时
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据。
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。 - 如果所有管道写端对应的文件描述符被关闭,则read返回0
- 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出
2.1.2 命名管道
//头文件
#include<unistd.h>
//创建命名管道
int mkfifo(const char *filename,mode_t mode);
//filename文件名,mode对该文件的权限
我们通过命名管道实现一个简单的通信来体会命名管道的操作特性
//write
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/sdt.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{
char *file = "./test.txt";
int ret = mkfifo(file,0664);
if(ret < 0 && errno != EEXIST){
perror("mkfifo error");
return -1;
}
int fd = open(file,O_WRONLY);
if(fd < 0){
perror("open error");
return -1;
}
char buff[1024];
while(1){
buff[0] = 0;
fflush(stdout);
ssize_t s = read(0,buff,sizeof(buff) - 1);
if(s > 0){
buff[s] = 0;
write(fd,buff,strlen(buff));
}else if(s <= 0){
perror("read error");
}
}
close(fd);
return 0;
}
读端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
umask(0);
char *file = "./test.txt";
if(mkfifo(file,0664) < 0){
perror("mkfifo error");
}
int fd = open(file,O_RDONLY);
if(fd < 0){
perror("open fifo");
}
char buff[1024];
while(1){
buff[0] = 0;
printf("-----\n");
ssize_t s = read(fd,buff,sizeof(buff) - 1);
if(s > 0){
buff[s - 1] = 0;
printf("it say--- %s\n",buff);
}else if(s == 0){
printf("exit\n");
break;
}else{
perror("read error");
}
}
close(fd);
return 0;
}
来看效果:
通过上面的demo演示我们其实可以看出匿名管道与命名管道的区别
- 匿名管道直接用pipe创建打开
- 命名管道用mkfifo创建后,还必须用open打开
- 匿名管道使用进程有“血缘关系”,而命名管道不必。
2.2 共享内存
共享内存区是最快的IPC形式。
共享内存相关内存函数:
1.创建
int shmget(key_t key, size_t size, int shmflg);
参数:key --- 这个内存段名字
size --- 共享内存的大小
shmflg --- 对于内存的操作权限
返回值: :成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
- 连接
将共享内存段连接到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:shmid --- 共享内存标识
shmaddr --- 指定连接的地址
shmflg --- 它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节,失败返回-1
- 脱离
将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);
参数:shmaddr --- 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
- 控制
用于控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:shmid --- 由shmget返回的共享内存标识码
cmd --- 将要采取的动作(有三个可取值)
buf --- 指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0,失败返回-1。
2.3 消息队列
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
- PC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核
2.4 信号量
内核中的计数器:
若技术 > 0则表示有资源,可以获取; <= 0表示没有资源,则进入等待状态。