一、什么是进程间通信
进程间通信是指在不同进程之间传播或交换信息;每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信
二、linux进程通信的目的
进程间通信有如下的目的:
1、数据传输,一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M之间;
2、共享数据,多个进程想要操作共享数据,一个进程对数据的修改,其他进程应该立刻看到;
3、通知事件,一个进程需要向另一个或一组进程发送消息,通知它们发生了某件事情;
4、资源共享,多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制;
5、进程控制,有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
三、进程间通信的几种方式
- 管道(匿名管道、命名管道)
- 共享内存
- 消息队列
- 信号量
1、管道
本质:在内核中开辟一块缓冲区;若多个进程拿到同一个管道(缓冲区)的操作句柄,就可以访问同一个缓冲区,就可以进行通信
读写特性:
若管道中没有数据,则调用read读取数据会阻塞
若管道中数据满了(约64k),则调用write写入数据会阻塞
若管道的所有读端pipefd[0]被关闭,则继续调用write会产生异常导致进程退出 ----父子进程都调用close(pipefd[0])
若管道的所有写端pipefd[1]被关闭,则继续调用read,read读完管道中的数据后不再阻塞,而返回0 ----父子进程都调用close(pipefd[1])
2、共享内存
特性:
共享内存是最快的进程间通信方式
生命周期不随进程,随内核(没有人为情况下)
共享内存没有自带同步与互斥,多个进程进行访问的时候存在安全问题
3、队列
内核中的一个优先级队列,多个进程通过访问同一个队列,进行添加结点或者获取结点实现通信
msgget 创建 msgsnd 获取 msgctl 删除
4、信号量
信号量是用于实现进程间的同步与互斥(如共享内存不提供同步与互斥,存在安全隐患,可以使用信号量搭配共享内存使用)
四、共享内存
操作流程
- 获取共享内存对象的ID
- 将共享内存映射至本进程虚拟空间的某个区域
- 不同进程通过对这块共享内存进行读写、传输数据
- 当进程不再使用这块共享内存时,解除映射关系
- 当没有进程再需要这块共享内存时,删除它
使用共享内存
相关API
- 获取共享内存对象的ID:int shmget(key_t key, size_t size, int shmflg);
- 映射共享内存:void *shmat(int shmid, const void *shmaddr, int shmflg);
- 解除内存映射:int shmdt(const void *shmaddr);
- 设置内存对象:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 查看IPC对象信息:ipcs -m
相关API
获取共享内存对象的ID
shmget函数负责创建或打开共享内存段
- 函数原型:int shmget(key_t key, size_t size, int shmflg);
- 函数功能:创建或打开一个共享内存对象
- 函数参数:
- key:IPC对象的键值,一般为IPC_PRIVATE或ftok返回的key值
- Size:共享内存大小,一般为内存物理页的整数倍
- shmflg:
- IPC_CREAT:如果不存在与指定的key对应的段,那么就创建一个新段
- IPC_EXCL:若key指定的内存存在且指定了IPC_CREAT,返回EEXIST错误
- SHM_HUGETLB:使用巨页(huge page)
- 返回值:共享内存的标识符ID
attach共享内存——使用共享内存
shmget函数,不过是在茫茫内存中创建了或找到了一块共享内存区域,但是这块内存和进程尚没有任何关系。要
想使用该共享内存,必须先把共享内存引入进程的地址空间,这就是attach操作。
- 函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
- 函数功能:将shmid标识的共享内存引入到当前进程的虚拟地址空间
- 函数参数
- shmid:共享内存的IPC对象ID
- shmaddr:
- 若为NULL:共享内存会被attach到一个合适的虚拟地址空间,建议使用NULL
- 不为NULL:系统会根据参数及地址边界对齐等分配一个合适的地址
- shmflg:
- IPC_RDONLY:附加只读权限,不指定的话默认是读写权限
- IPC_REMAP: 替换位于shmaddr处的任意既有映射:共享内存段或内存映射
- SHM_RND : 将shmaddr四舍五入为SHMMLBA字节的倍数
第二个参数是用来指定将共享内存放到虚拟地址空间的什么位置的。
大部分的普通青年都会将第二个参数设置为NULL,表示用户并不在意,一切交由内核做主。
- 返回值:共享内存段的地址
shmat如果调用成功,则返回进程虚拟地址空间内的一个地址。如果失败,就会返回(void*)-1
detach共享内存——分离共享内存
shmdt函数仅仅是使进程和共享内存脱离关系,并未删除共享内存。shmdt函数的作用是将共享内存的引用计数减1。如前所述,只有共享内存的引用计数为0时,调用shmctl函数的IPC_RMID命令才会真正地删除共享内存。
- 函数原型:int shmdt(const void *shmaddr);
- 函数功能:解除内存映射,将共享内存分离出当前进程的地址空间
- 函数参数:
- shmaddr:共享内存地址
TIPS: 通过fork创建的子进程会继承父进程所附加的共享内存段,父子进程可以通过共享内存进行IPC通信。
在exec系统调用中,所有附加的共享内存段都会被分离
函数shmdt仅仅是使进程和共享内存脱离关系,将共享内存的引用计数减1,并未删除共享内存。
当共享内存的引用计数为0时,调用shmctl的IPC_RMID命令才会删除共享内存
设置共享内存属性
- 函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 函数功能:获取/设置共享内存对象属性
- 当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数
- 函数参数:
- shmid:共享内存对象ID
- cmd:
- 当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数
- IPC_STAT: 用于获取shmid对应的共享内存的信息。
- IPC_SET:IPC_SET也只能修改shm_perm中的uid、gid及mode
- IPC_RMID:可以通过如下方式删除共享内存段:如果共享内存的引用计数shm_nattch等于0,则可以立即删除共享内存。
- buf:
- 将该内存对象关联的shmid_ds数据结构拷贝到参数buf中
编程方式
- 创建
- 映射
- 读写
- 解除映射
- 销毁
共享内存share memory写端
int main(int argc, char *argv[])
{
key_t key = ftok(".", 588);
int shm_id = shmget(key, 4096, IPC_CREAT|0666 );
printf("shm_id:%d\n", shm_id);
/* Automatic allocation */
char *shm_p = shmat(shm_id, NULL, 0);
memset(shm_p, 0, sizeof(shm_p));
fgets(shm_p, 4096, stdin);
sleep(30);
shmctl(shm_id, IPC_RMID, NULL);
return 0;
}
共享内存share memory读端
int main(int argc, char *argv[])
{
key_t key = ftok(".", 588);
int shm_id= shmget(key, 4096, 0666);
char *shm_p = shmat(shm_id, NULL, 0);
printf("from share memory:%s\n", shm_p);
shmdt(shm_p);
return 0;
}
共享内存的通信限制
- SHMMNI:系统所能创建的共享内存的最大个数,IPCMIN:32768
- SHMMIN:一个共享内存段的最小字节数4096
- SHMMAX:一个共享内存段的最大字节数33554432
- SHMALL:系统中共享内存的分页总数2097152
- SHMSEG:一个进程允许attch的共享内存段的大小个数
共享内存通信特点
- 共享内存抛弃了“内核代理人”角色,提升了系统性能
- 需要进程本身维护共享内存的各种问题:同步、互斥…
- 一般需要信号量、互斥锁、文件锁等配合使用,在各个进程之间在高效通信的同时,防止发送数据的践踏,破坏