进程间通信就是在不同进程之间传播或交换信息。但是,我们知道进程是具有独立性的,因此,进程之间不可能直接进行通信。人们之间交流需要空气作为介质,进程之间“交流”也需要一个媒介,那么进程之间存在着什么双方都可以访问的介质呢?
- 进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。
- 系统空间是“公共场所”,各进程均可以访问,所以内核也可以提供这样的条件。
- 还有双方都可以访问的外设。两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。
进程间通信官方的说法:
进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。
简单来讲程间通信就是不同进程之间进行数据的交换。
进程间通信主要包括管道, 系统IPC(包括消息队列,信号,共享存储), 套接字(SOCKET).
管道分为匿名管道和命名管道,匿名管道只能用于具有亲缘关系(父子、兄弟)进程之间的通信,而命名管道则可用于任意进程之间。
系统IPC的三种方式类同,都是使用了内核里的标识符来识别。
管道与文件描述符,文件指针的关系?
其实管道的使用方法与文件类似,都能使用read,write,open等普通IO函数. 管道描述符来类似于文件描述符. 事实上, 管道使用的描述符,文件指针和文件描述符最终都会转化成系统中SOCKET描述符. 都受到系统内核中SOCKET描述符的限制. 本质上LINUX内核源码中管道是通过空文件来实现.
-
IPC目的
1)数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
2)共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
4)资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。
5)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程通过与内核及其它进程之间的互相通信来协调它们的行为。Linux支持多种进程间通信(IPC)机制,信号和管道是其中的两种。除此之外,Linux还支持System V 的IPC机制(用首次出现的Unix版本命名)。
从IPC的角度看,管道提供了从一个进程向另一个进程传输数据的有效方法。但是,管道有一些固有的局限性:
因为读数据的同时也将数据从管道移去,因此,管道不能用来对多个接收者广播数据。
管道中的数据被当作字节流,因此无法识别信息的边界。
如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中那一个发送的数据。
-
管道特性
- 半双工单向通信
- 读写特性
- 管道自带同步与互斥保护操作
- 提供字节流服务:数据传输比较灵活,但有可能会造成数据粘连
- 生命周期随进程
- 管道简单理解:内核的一块缓冲区用于在进程间传输数据资源,操作系统为了接口统一,对于管道这块缓冲区的操作,和io操作使用同一套接口。
匿名管道基本使用CODE:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
int main()
{
//int pipe(int pipefd[2])
//pipefd 用于获取管道的操作描述符
//pipefd[0] 用于从管道读取数据
//pipefd[1] 用于向管道写入数据
//返回值: 成功:0 失败:-1
int pipefd[2];
//创建匿名管道
int ret=pipe(pipefd);
if(ret<0)
{
perror("pipe error!");
return -1;
}
//创建子进程
int pid=fork();
if(pid<0)
{
perror("fork error!");
return -1;
}
else if(pid==0)
{
//子进程用于从管道中读取数据
//因为子进程读取在父进程写入之前,这时管道中没有数据,此时子进程挂起等待数据写入
close(pipefd[1]);
char buff[1024]={0};
read(pipefd[0],buff,1023);
printf("%s\n",buff);
}
else
{
//父进程用于向管道中写入数据
close(pipefd[0]);
char *ptr="leagu of lengend!";
write(pipefd[1],ptr,strlen(ptr));
}
waitpid(pid,NULL,0);
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
命名管道基本使用CODE:
//命名管道写入
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int ret =mkfifo("./test.fifo",0664);
if(ret<0)
{
if(errno!=EEXIST)
{
perror("mkfifo error!");
return -1;
}
}
int fd =open("./test.fifo",O_WRONLY);
if(fd<0)
{
perror("open error!");
return -1;
}
printf("open fifo success!\n");
while(1)
{
char buff[1024]={0};
printf("please input:");
fflush(stdout);
scanf("%s",buff);
write(fd,buff,strlen(buff));
}
close(fd);
return 0;
}
//命名管道读取
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
int main()
{
int ret =mkfifo("./test.fifo",0664);
if(ret<0)
{
if(errno!=EEXIST)
{
perror("mkfifo error!");
return -1;
}
}
int fd =open("./test.fifo",O_RDONLY);
if(fd<0)
{
perror("open error!");
return -1;
}
printf("open fifo success!\n");
while(1)
{
char buff[1024]={0};
read(fd,buff,1023);
printf("read buff:%s\n",buff);
}
close(fd);
return 0;
}
Message Queues(消息队列)
消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。Linux维护了一个消息队列向量表:msgque,来表示系统中所有的消息队列。其定义如下:
struct msqid_ds *msgque[MSGMNI];
该向量表中的每一个元素都是一个指向msqid_ds数据结构的指针,而一个msqid_ds数据结构完整地描述了一个消息队列。
MSGMNI的值是128,就是说,系统中同时最多可以有128个消息队列。
当创建消息队列时,一个新的msqid_ds数据结构被从系统内存中分配出来,并被插入到msgque 向量表中。
Linux提供了四个消息队列操作。
1. 创建或获得消息队列(MSGGET)
2. 发送消息
3. 接收消息
4. 消息控制
共享内存
通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:
对于共享内存,linux本身无法对其做同步,需要程序自己来对共享的内存做出同步计算,而这种同步很多时候就是用信号量实现。
信号量
本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。信号量,分为互斥信号量,和条件信号量。一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;
(2)若此信号量的值为正,则允许进行使用该资源,进程将信号量减去所需的资源数;
(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转至步骤(1);
(4)当进程不再使用一个信号量控制的资源时,信号量值加其所占的资源数,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。