[common] IPC

前言:

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

见:[Linux C] signal 的使用-CSDN博客文章浏览阅读99次。信号的创建有两套api,一个是signal,一个是sigaction,signal缺陷很多,比如没有提供触发后自动再次设置处理信号处理策略,这会导致连续触发的两个信号,一个进入了期待的信号处理流程,另外一个则进入了默认的信号处理流程。但是信号的递送可能会出现阻塞,这个阻塞发生在信号发送者把信号送入内核的信号队列中(需要从代码层面验证)。signal 是一种通信机制,可以跨进程发送,可以同进程跨线程发送,可以不同进程向指定线程发送。https://blog.csdn.net/ykun089/article/details/134112690?spm=1001.2014.3001.5502




文件锁 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。

参考:Synchronization With Semaphores (Multithreaded Programming Guide)icon-default.png?t=N7T8https://docs.oracle.com/cd/E19120-01/open.solaris/816-5137/sync-11157/index.html

PS: 共享内存可以共享可执行代码段,可以通过这种方式实现跨进程函数调用,见:
c - Linux: is it possible to share code between processes? - Stack Overflowicon-default.png?t=N7T8https://stackoverflow.com/questions/15114233/linux-is-it-possible-to-share-code-between-processes




共享内存 shared memory (system V)

todo




Android IPC - 匿名共享内存

MemoryHeapBase

MemoryBase

IMemory

android匿名共享内存原理浅读_android 匿名共享内存_小道安全的博客-CSDN博客理论基础android系统在应用程序框架层中提供了两个C++类MemoryHeapBase和MemoryBase来创建和管理匿名共享内存。如果一个进程需要与其他进程共享一块完整的匿名共享内存,那么就可以通过使用MemoryHeapBase类类创建这块匿名共享内存。如果一个进程创建一块匿名共享内存后,只希望与其他进程共享其中的一部分,那么就可以通过MemoryBase类来创建这块匿名共享内存。IMemory.h:定义内存相关类的接口,表示堆内存的类IMemoryHeap和BnMemoryHeap,表示一_android 匿名共享内存https://blog.csdn.net/c_kongfei/article/details/119790367




Android IPC - Binder

理解 Android Binder 机制(二):C++层_慕课手记icon-default.png?t=N7T8https://www.imooc.com/article/37316https://source.android.com/docs/core/architecture/hidl/binder-ipc?hl=zh-cnicon-default.png?t=N7T8https://source.android.com/docs/core/architecture/hidl/binder-ipc?hl=zh-cnhttps://github.com/gburca/BinderDemoicon-default.png?t=N7T8https://github.com/gburca/BinderDemo




参考:

Chapter 7 Interprocess Communication (System Interface Guide)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值