因为进程的独立性(各自操作的都是自己虚拟地址空间中的虚拟地址,无法访问别人的地址)导致无法直接通信。因此按照通信场景的不同操作系统提供了几种不同的进程间通信方式。
按照system V标准:管道、共享内存、消息队列、信号量。
按照posix标准:信号量。
管道
本质:内核中的一块缓冲区。
原理:让多个进程访问到相同的缓冲区来实现通信,管道实现通信使用的是系统调用的IO接口。
分类:匿名管道、命名管道。
特性:
1.半双工通信。(可选方向的单向传输)
2.生命周期:随进程结束而结束。(所有管道的操作句柄被关闭)
3.管道自带同步与互斥。(同步:保证操作的时序合理性。互斥:保证操作在同一时间的唯一性)
4.管道提供字节流服务:灵活传输;但存在粘包。(本质原因:数据没有明显的间隔);
1.匿名管道
只能用于具有亲缘的进程间通信–即子进程通过复制父进程的文件描述符表获取管道的操作句柄。
一个进程创建匿名管道,操作系统在内核重建一块缓冲区,并返回两个文件描述符作为管道的操作句柄(一个读,一个写,方向的选择权交给用户),但该缓冲区在内核中没有标识符。
读写特性:
1.若管道中没有数据,则read会阻塞,直到数据被写入。
2.若管道中数据满了,则write会阻塞,直到数据被读取。
3.若管道的所有读端被关闭,则write会触发异常,进程退出。
4.若管道所有写端被关闭,则read会返回0。
操作接口:
int pipe(int pipefd[2]);
pipifd:至少具有两个int型元素的数组;创建一个管道,通过pipefd获取系统返回的管道操作句柄,其中:
pipefd[0] :用于从管道读取数据。
pipefd[1] :用于从管道写入数据。
返回值:0 失败:-1
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main(){
//管道必须创建于子进程之前,子进程才会复制到管道的操作句柄。
int pipefd[2];
int ret=pipe(pipefd);
if(ret<0){
perror("pipe error");
return -1;
int pid = fork();
if (pid == 0) {
//child
char buf[1024] = {0};
int ret = read(pipefd[0], buf, 1023);
printf("child read buf:[%d-%s]\n", ret, buf);
exit(0);
}else if (pid > 0) {
//parent
write(pipefd[1], "hello", 5);
printf("---------\n");
}
return 0;
}
结果如下
2.命名管道
命名管道可以使用户同一主机上的任意进程间通信。且命名管道在内核中这块缓冲区是由标识的,意味着所有的进程都可以通过这个标识找到这块缓冲区实现通讯。
命名管道的标识实际是一个文件,可见于文件系统,意味着所有进程都可以通过打开文件进而访问到内核中的缓冲区。
打开特性:
1.若文件当前没有已经被已读的方式打开,则以O_WRONLY打开时会阻塞。
2.若文件当前没有已经被以写的方式打开,则以O_RDONLY打开时会阻塞。
读写特性:类似于匿名管道。
int mkfifo(const char *pathname, mode_t mode);
//pathname: 管道文件名称
//mode: 管道文件权限
//返回值:0 失败:-1
创建一个test.fifo的命名管道。
这个从里面读。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
int main()
{
char *fifo = "./test.fifo";
umask(0);
int ret = mkfifo(fifo, 0664);
if (ret < 0) {
if (errno != EEXIST) {
perror("mkfifo error");
return -1;
}
}
printf("start open -------\n");
int fd = open(fifo, O_RDONLY);
printf("end open -------\n");
if (fd < 0) {
perror("open error");
return -1;
}
printf("fifo:%s open success!!\n", fifo);
while(1) {
sleep(5);
char buf[1024] = {0};
read(fd, buf, 1023);
printf("peer say: %s\n", buf);
}
close(fd);
return 0;
}
这个往里面写。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
int main()
{
char *fifo = "./test.fifo";
umask(0);
int ret = mkfifo(fifo, 0664);
if (ret < 0) {
if (errno != EEXIST) {
perror("mkfifo error");
return -1;
}
}
printf("start open -------\n");
int fd = open(fifo, O_WRONLY);
printf("end open -------\n");
if (fd < 0) {
perror("open error");
return -1;
}
printf("fifo:%s open success!!\n", fifo);
while(1) {
char buf[1024] = {0};
printf("i say: ");
fflush(stdout);
scanf("%s", buf);
write(fd, buf, strlen(buf));
}
close(fd);
return 0;
}
演示(写入会立即读出,没有就等待)
这是写入的
这是读到的
共享内存
主要用于进程间的数据共享。
原理:申请一块物理内存,将这块物理内存隐射到进程的虚拟地址空间中;进程通过虚拟地址访问这块物理内存,多个进程映射同一个内存就可以通过这个内存实现数据共享。
本质:通过内核的缓冲区来实现通信,进程1将数据从用户态缓冲区拷贝到内核态缓冲区,进程2将数据从内核态拷贝到用户态缓冲区,期间涉及两次用户态与内核态之间的数据拷贝。
实现:
1.创建共享内存(shmget)。
int shmget(key_t key, size_t size, int shmflg);
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
2.将共享内存映射到虚拟地址空间(shmat)。
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
3.直接通过虚拟地址进行内存操作。
4.解除映射关系(shmdt)。
int shmdt(const void *shmaddr);
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
5.删除共享内存(shmctl)。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
特性:进程间通信最快的方式–直接通过虚拟地址操作数据,相较于其它方式省去了用户态到内核态之间的数据拷贝操作。
消息队列
用于多个进程间有类型的数据块传输。操作系统在内核为用户创建一个队列,其他进程可以通过访问相同的队列进行通信。且生命周期随进程
原理:内核中的一个优先级队列。
信号量
用于实现进程间的同步与互斥操作。
同步:保证多个进程之间对临界资源访问的时序合理性。
互斥:保证多个进程之间同一时间对临界资源访问的唯一性。
本质:内核中的一个计数器(资源计数:统计现在有多少资源,用于判断是否能够进行操作)
原理:进程对临界资源访问前先进行资源技术判断:
若计数>0,表示可以操作,计数减一,判断操作直接返回。
若计数<=0,表示不可操作,将进程pcb状态修改为阻塞状态,将pcb加入到等待队列中。
若其他进程创建一个资源,计数-1,唤醒等待队列上的所有进程。
ipcs / ipcrm
查看IPC
-m 查看共享内存。
-s 查看信号量。
-q 查看消息队列
删除IPC
ipcrm -m shmid