前言:
IPC , 进程间通讯,是指进程间通过 ipc 组件进行通讯的方式。
匿名管道 pipe
适用范围:仅限相互有关系的进程之间,比如父子进程。
Demo:
#include <stdio.h>
#include <unistd.h>
...
int p[2];
...
if (pipe(p) == -1) exit(1);
switch( fork() )
{
case 0: /* in child */
close( p[0] );
dup2( p[1], 1);
close P[1] );
exec( ... );
exit(1);
default: /* in parent */
close( p[1] );
dup2( P[0], 0 );
close( p[0] );
break;
}
...
优缺点:优点是简单,缺点是要求进程间必须在代码层面有关联,依赖于fork函数。
线程安全/进程安全:是,无需同步
命名管道 named pipe
使用范围:任何跨进程场景,或者进程内自我通讯。
Demo:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
const char* pipepath = "/home/yk/pipe1";
void workerfunc(int pipe)
{
int i = 0;
while(1){
char message[1024] = {0};
char outmessage[1024] = {0};
sprintf(message,"write to pipe , current pid %d , current cnt %d\n", getpid(), i++);
int size = write(pipe,message,strlen(message));
printf("write round %d , wrote %d \n",i,size);
int outsize = read(pipe,outmessage,1024);
printf("got %d -> %s\n",outsize,outmessage);
sleep(1);
}
}
int main(){
umask(0); //clear all temporarily
int ret = mknod(pipepath,S_IFIFO|0777,NULL); //0777 means full priviledge
if(ret==0 || (ret!=0&&errno==EEXIST)){
int fd = open(pipepath,O_RDWR|O_CREAT);
printf("ready to write and read ");
workerfunc(fd);
}else{
printf("error when creating pipepath %d",errno);
}
}
pipe 好像一个池子,不管数据的读写方是否为同一个进程,即使是同一个进程先写立刻再读,那么也能把数据读出来,所以说pipe不是单双工的,而是全双工的,只是这种全双工的行为有点奇怪,比如上面的例子里,自写自读是可以的。
优缺点:pipe存在于内核层面,效率有保障,使用起来也相对简单。
线程安全/进程安全:是,无需同步
更多参考:IPC之二:使用命名管道(FIFO)进行进程间通信的例子_whowin的博客-CSDN博客
消息队列 message queue (posix)
使用范围:任何跨进程场景,或者进程内自我通讯。
Demo:
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#define RANDOM_ORDER
#undef SINGLE_PROCESS
#ifdef RANDOM_ORDER
#define FLAG O_RDWR|O_CREAT|O_NONBLOCK
#else
#define FLAG O_RDWR|O_CREAT
#endif
#define MSG_SIZE_MAX 4096
#define Q_ITEM_MAX 10
#define HIGHEST_PRIORITY sysconf(_SC_MQ_PRIO_MAX) - 1
//must has one and only one slash at the beginning
const char* mq_name = "/testqwe";
void clear_mq(mqd_t mqd){
char rcvmsg[MSG_SIZE_MAX]={0};
while(1){
ssize_t ret = mq_receive(mqd,rcvmsg,MSG_SIZE_MAX,NULL);
if(ret == -1 && errno == EAGAIN){
printf("mq flushed\n");
break;
}
}
}
int do_send(mqd_t mqd){
static int i = 0;
char msg[1024]={0};
sprintf(msg,"send message , No.%d\n",++i);
int ret = mq_send(mqd,msg,strlen(msg),HIGHEST_PRIORITY);
if(ret==0){
printf("sent out -> %s\n",msg);
}else{
printf("send error -> %d\n",errno);
}
return ret;
}
ssize_t do_recv(mqd_t mqd){
char rcvmsg[MSG_SIZE_MAX]={0}; //must larger than or equal to MSG_SIZE_MAX
ssize_t ret = mq_receive(mqd,rcvmsg,MSG_SIZE_MAX,NULL); //NULL for receive message with all priority
if(ret!=-1){
printf("received -> %s\n",rcvmsg);
}else{
printf("received error -> %d\n",errno);
}
return ret;
}
/*
* /proc/sys/fs/mqueue description
*
* msg_default : if attribute is not set by mq_open, this value will be used as .mq_maxmsg , default is 10.
* msg_max : message count limit of the queue. default is 10, could be modified with sudo sysctl -w fs.mqueue.msg_max=1000.
* msgsize_default : if attribute is not set by mq_open, this value will be used as .mq_msgsize , default is 8192.
* msgsze_max : message count limit of the queue. default is 8192.
* queues_max : how many queues could be created on the system, default 256.
*
* NOTE : attr.mq_maxmsg * attr.mq_msgsize must less than uname -a|grep 'POSIX message queues'
*/
int main(int argc,char** argv)
{
//msg_max is 10 by default, could be consoled out by 'cat /proc/sys/fs/mqueue/msg_max',
//not modify this value to Q_ITEM_MAX with following command
system("sudo sysctl -w fs.mqueue.msg_max=1000");
//modify queue num limit
struct rlimit rlim;
memset(&rlim, 0, sizeof(rlim));
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_MSGQUEUE, &rlim);
struct mq_attr attr;
memset(&attr,0,sizeof(struct mq_attr));
attr.mq_maxmsg = Q_ITEM_MAX,
attr.mq_msgsize = MSG_SIZE_MAX,
umask(0);
mqd_t mqd = mq_open(mq_name,FLAG,0777,&attr);
if(mqd==(mqd_t)-1){
printf("mq_open error -> %d\n",errno);
}else{
clear_mq(mqd);
printf("mq_open success -> mqd = %d\n",mqd);
}
#ifdef SINGLE_PROCESS
//single process also works well
while(1){
#ifdef RANDOM_ORDER
//do recv on a empty queue will cause a block if O_NONBLOCK is not set
//if set , EAGAIN will be returned for re-recv sign.
do_recv(mqd);
do_send(mqd);
#else
do_send(mqd);
do_recv(mqd);
#endif
sleep(1);
}
#else
pid_t pid = fork();
if(pid==0){
//child process for write
while(1){
do_send(mqd);
sleep(1);
}
//should be closed both at parent and child , or ref count will not decrease, fd will leak
mq_close(mqd);
}else{
//parent process for read
while(1){
do_recv(mqd);
sleep(1);
}
//should be closed both at parent and child , or ref count will not decrease, fd will leak
mq_close(mqd);
}
#endif
}
操作系统对于消息队列的参数有如下限制:
/*
* /proc/sys/fs/mqueue description
*
* msg_default : if attribute is not set by mq_open, this value will be used as .mq_maxmsg , default is 10.
* msg_max : message count limit of the queue. default is 10, could be modified with sudo sysctl -w fs.mqueue.msg_max=1000.
* msgsize_default : if attribute is not set by mq_open, this value will be used as .mq_msgsize , default is 8192.
* msgsze_max : message count limit of the queue. default is 8192.
* queues_max : how many queues could be created on the system, default 256.
*
* NOTE : attr.mq_maxmsg * attr.mq_msgsize must less than uname -a|grep 'POSIX message queues'
*/
优缺点:内核管理,以消息为单位,相比于管道而言,更加注重数据的分割性。线程安全,进程安全。不需要额外的同步组件配合。
注意点:
1)消息队列属于内核维护,因此关闭消息队列只是关闭了进程/用户操作消息队列的通道,并不会让消息队列消失,也就是说这个队列不会被清空。所以要清空队列必须手动清空(看上面的demo),或者通过重启系统清空。
2)消息队列有诸多参数限制,在打开队列的时候可能会范围 errno = 24 的错误,这就是入参错误导致的参数不匹配。
线程安全/进程安全:是,无需同步
消息队列 message queue (system V)
todo
信号量 Sempohore (posix)
使用范围:跨进程/跨线程同步。信号量只是一种同步机制,不同于消息队列和管道是一种消息传递机制。一般情况下信号量是用来配合跨进程/跨线程的消息传递场景使用,而且这种消息传递机制往往都是线程不安全的。像posix 消息队列和posix 管道都是线程安全的,因此无需其他同步机制保护,自然也不需要 信号量配合。
Demo:
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
const char* sempname = "test";
#define FLAG O_CREAT
#define DEFAULT_SEMP_CNT 0
void dumpsemp(sem_t* sem)
{
int value,ret;
ret = sem_getvalue(sem,&value);
if(-1==ret){
printf("get value error, errno -> %d\n",errno);
}else{
printf("[%d] current sem count -> %d\n",getpid(),value);
}
}
//posix 信号量是内核管理的, 不跟随进程销毁而重置,这一点和 posix message queue一样,因此需要
//额外的clear步骤。否则信号量会残留在内核里等待下一次被使用。
void clearsemp(sem_t* sem)
{
int value=0;
do{
sem_trywait(sem);
sem_getvalue(sem,&value);
}while(value!=DEFAULT_SEMP_CNT);
}
#define myperror(S) printf("S,errno->%d\n",errno);
int main(int argc,char** argv)
{
umask(0);
sem_t* sem = sem_open(sempname,FLAG,0777,DEFAULT_SEMP_CNT);
if(SEM_FAILED==sem){
myperror("sem_open error");
return 0;
}
clearsemp(sem);
pid_t pid = fork();
if(0==pid){ //child
while(1){
int ret = sem_post(sem);
if(-1==ret){
myperror("sem_post error");
}else{
dumpsemp(sem);
}
sleep(1);
}
sem_clase(sem);
}else{ //parent
while(1){
int ret = sem_wait(sem);
if(-1==ret){
myperror("sem_wait error");
}else{
dumpsemp(sem);
}
sleep(2);
}
sem_close(sem);
}
}
优缺点:内核级别,速度快。
注意点:
1)和管道/消息队列/共享内存这些进程间通讯机制不同,信号量是同步机制,一般是用来为没有线程安全/进程安全的进程间通讯组件提供同步支持。
2)和posix 消息队列一样,信号量也由内核通过信号量name在其内部维护,如果需要清空则需要单独处理。
线程安全/进程安全:不涉及,本身就是同步机制
应用领域:最常用的领域是使用信号量完成快进程的生产者消费者模型。详见 TODO 。
信号量 Sempohore (system V)
todo
信号 Signal
文件锁 flock
todo
process mutex
todo
共享内存 shared memory (posix)
使用范围:所有需要进程间高效通讯的场景。
Demo:
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <pthread.h>
#define _OPEN_THREADS
#define _OPEN_SYS
const char* shmname = "/testshm1";
#define FLAGSHM O_CREAT|O_RDWR
#define SHM_SIZE 4096
#define SHM_ACCESS PROT_READ|PROT_WRITE
#define SHM_MMP_FLAGS MAP_SHARED
#define myperror(S) printf("S,errno->%d\n",errno);
int main(int argc,char** argv)
{
umask(0);
pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // [!] inter process usage
pthread_mutex_init(&mp,&attr);
int shmfd = shm_open(shmname,FLAGSHM,0777);
if(-1==shmfd){
myperror("shm_open error");
return 0;
}
void* startaddr = mmap(NULL,SHM_SIZE,SHM_ACCESS,MAP_SHARED,shmfd,0);
if(startaddr == MAP_FAILED){
myperror("mmap error");
return 0;
}
if(ftruncate(shmfd, SHM_SIZE) == -1) {
myperror("ftruncate");
return 0;
}
pid_t pid = fork();
if(0==pid){ //child
while(1){
pthread_mutex_lock(&mp);
static int i = 0;
char msg[1024]={0};
sprintf(msg,"msg-from-child-%d",i++);
memcpy(startaddr,msg,strlen(msg));
printf("written [%s]\n",msg);
//msync(startaddr,SHM_SIZE,MS_SYNC); needed?
pthread_mutex_unlock(&mp);
sleep(1);
}
pthread_mutex_destroy(&mp);
shm_unlink(shmfd);
}else{ //parent
while(1){
pthread_mutex_lock(&mp);
char msg[1024]={0};
memcpy(msg,startaddr,1024); // for safty, writer should note the msg len at the begnning of shared memory, here simplify it
printf("read [%s]\n",msg);
pthread_mutex_unlock(&mp);
sleep(1);
}
pthread_mutex_destroy(&mp);
shm_unlink(shmfd);
}
munmap(startaddr,SHM_SIZE);
}
优缺点:速度最快的 ipc 方式是 共享内存 (shared memory) ,需要信号量协助完成进程间同步访问共享内存区域,否则会导致竞争关系。缺点是老系统一般都不兼容。
线程安全/进程安全:否,需要进程间同步组件配合,比如信号量,进程级别mutex,文件锁,信号。其中非二元信号量无法单独使用,进程级别的mutex或二元信号量常用。
注意点:共享内存的同步控制无法通过单个信号量控制来完成,因为信号量不像 mutex 那样具备二元性,当进行 post 操作的时候无需要求信号量此时必须是0。
PS: 共享内存可以共享可执行代码段,可以通过这种方式实现跨进程函数调用,见:
c - Linux: is it possible to share code between processes? - Stack Overflowhttps://stackoverflow.com/questions/15114233/linux-is-it-possible-to-share-code-between-processes
共享内存 shared memory (system V)
todo
Android IPC - 匿名共享内存
MemoryHeapBase
MemoryBase
IMemory
Android IPC - Binder
理解 Android Binder 机制(二):C++层_慕课手记https://www.imooc.com/article/37316https://source.android.com/docs/core/architecture/hidl/binder-ipc?hl=zh-cn
https://source.android.com/docs/core/architecture/hidl/binder-ipc?hl=zh-cnhttps://github.com/gburca/BinderDemo
https://github.com/gburca/BinderDemo
参考:
Chapter 7 Interprocess Communication (System Interface Guide)