操作系统-3.4 进程间通信-IPC

241 阅读5分钟

IPC:inter-process communication

三种IPC:共享内存Shared memory,信号灯semophore、消息队列message queue。 有名管道可以无亲缘关系的进程间通信,是因为用户空间有管道这种文件类型。 而这三种IPC,用户空间是没有对应的文件类型的。ftok key可以保证用户空间的二个进程对同一个IPC对象进程操作。

这三中IPC对象的实现模型:

与文件IO操作的对应关系:

文件I/OIPC
openMsg_get Shm_get Sem_get
readmsgrecv shmat semop
writemsgsnd shmat semop
closemsgctrl shmctrl semctrl

共享内存

共享内存创建后一直存在于内核中,直到被删除或系统关闭;

shmget函数

创建出的共享内存,一块缓存,类似于用户空间的数组或malloc 函数分配的空间一样。共享内存的创建shmget:

头文件: 
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
函数原型:  int shmget(key_t key, int size, int shmflg)
函数参数:  key:IPC_PRIVATE 或 ftok的返回值
          size:共享内存区大小
          shmflg:同open函数的权限位,也可以用8进制表示
返回:  成功:共享内存段标识符,ID,文件描述符, 失败:-1

查看IPC 共享内存对象命令:ipcs -m 删除IPC 共享内存对象命令:ipcrm -m id

#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
  int shmid;
  shmid=shmget(IPC_PRIVATE,128,0777);
  if(shmid <0)
  {
	printf("creat share memory failure\n");
	return -1;
  }
  printf("creat share memory sucess shmid=%d\n",shmid);
  system("ipcs -m");
 // system("ipcrm -m shmid");
  return 0;
}

ftok函数

使用shmget函数创建共享内存时,参数如果是IPC_PRIVATE,则共享内存的key值都一样为0。只能用于有亲缘关系进程之间的通信。 如果要使key非0,则需要借助函数ftok。 只要key值一样,用户空间的进程通过函数打开,就会对内核的同一个IPC对象进行操作。

ftok函数:
原型:  char ftok(const char *path, char key)
参数:  path:文件路劲和文件名
       key:一个字符
返回值:成功返回一个key值,失败-1

shmat函数

创建了共享内存后需要将内存映射到用户空间,减少内核访问,映射函数为shmat

原型:void *shmat(int shmid, const void *shmaddr, int shmflg) //类似于malloc
参数:  第一个参数,ID号
       第二个参数,映射到的地址,NULL为系统自动完成的映射
       第三个参数,shmflg, SHM_RDONLY共享内存只读, 默认是0,表示可读写
返回:  成功映射后的地址, 失败NULL
#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
  int shmid;
  int key;
  char *p;
  key=ftok("./a.c",'b');//通过a.c生成key值,a.c文件必须存在
  if(key <0 )
  {
	printf("creat key fialure\n");
	return -2;
  }
  printf("creat key sucess key=%X\n",key);
  shmid=shmget(key,128,IPC_CREAT | 0777);
  if(shmid <0)
  {
	printf("creat share memory failure\n");
	return -1;
  }
  printf("creat share memory sucess shmid=%d\n",shmid);
  system("ipcs -m");
 
  p=(char *)shmat(shmid,NULL,0);
  if(p == NULL)
  {
	printf("shmat function failure\n");
	return -3;
  }
  //write share memory
  fgets(p,128,stdin);
 
  //start read share memory
  printf("share memory data:%s",p);
 
  printf("second read share memory data:%s",p);
 // system("ipcrm -m shmid");
  return 0;
}

shmdt函数

删除用户空间的进程中的地址映射shmdt

原型:  int  shmdt(const void *shmaddr)
参数:  shmaddr共享内存映射后的地址
返回:  成功0,出错-1

shmctl函数

删除内核中的共享内存对象shmctl

原型:  int shmctl(int shmid, int cmd, struct shmid_ds * buf)
参数:  shmid,要操作的共享内存标识符
      cmd:  IPC_STAT,获取对象属性值 //实现了命令ipcs  -m
            IPC_SET,设置对象属性
            IPC_RMID,删除对象 //实现了命令ipcrm -m
返回:  成功0,出错-1
#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{
  int shmid;
  int key;
  char *p;
  key=ftok("./a.c",'b');
  if(key <0 )
  {
	printf("creat key fialure\n");
	return -2;
  }
  printf("creat key sucess key=%X\n",key);
  shmid=shmget(key,128,IPC_CREAT | 0777);
  if(shmid <0)
  {
	printf("creat share memory failure\n");
	return -1;
  }
  printf("creat share memory sucess shmid=%d\n",shmid);
  system("ipcs -m");
 
  p=(char *)shmat(shmid,NULL,0);
  if(p == NULL)
  {
	printf("shmat function failure\n");
	return -3;
  }
  //write share memory
  fgets(p,128,stdin);
 
  //start read share memory
  printf("share memory data:%s",p);
 
  printf("second read share memory data:%s",p);
  
  shmdt(p);
  shmctl(shmid,IPC_RMID,NULL);
  system("ipcs -m ");
 
  return 0;
}

亲缘关系进程间通信示例

a. 父进程完成内存映射后,等待读取用户输入信息;当输入完成后,通过kill向子进程发送SIGUSR1信号告知有消息可读,同时父进程进入睡眠状态;

b. 子进程完成内存映射后,就进入休眠状态;当收到信号后,进程唤醒,并从共享内存中读取信息;读取完成后向父进程发送SIGUSR2信号,告知,信息被读取,你可以继续输入了。

#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
void myfun(int signum)
{
  return ;
}
int main()
{
  int shmid;
  int key;
  char *p;
  int pid;
  shmid=shmget(IPC_PRIVATE,128,IPC_CREAT | 0777);
  if(shmid <0)
  {
	printf("creat share memory failure\n");
	return -1;
  }
  printf("creat share memory sucess shmid=%d\n",shmid);
 
  pid=fork();
  if(pid > 0)//parent process 
  { 
	signal(SIGUSR2,myfun);
    p=(char *)shmat(shmid,NULL,0);
    if(p == NULL)
    {
	   printf("parent process:shmat function failure\n");
	   return -3;
    }
    while(1)
    {
      //write share memory
	  printf("parent process start write share memory:\n");
      fgets(p,128,stdin);
	  kill(pid,SIGUSR1);// child process read data 
	  pause();// wait child process read
	}
  }
  if(pid == 0)//child process 
  {
	signal(SIGUSR1,myfun);
	p=(char *)shmat(shmid,NULL,0);
	if(p == NULL)
	{
     printf("child process shmat function failure\n");
	 return -3;
	}
    while(1)
	{
	 pause();// wait parent process write
     //start read share memory
     printf("share memory data:%s",p);
	 kill(getppid(),SIGUSR2);
	}
  }
  
  shmdt(p);
  shmctl(shmid,IPC_RMID,NULL);
  system("ipcs -m ");
 
  return 0;
}

非亲缘关系进程

server.c

#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
struct mybuf
{
    int pid;
    char buf[124];
};
void myfun(int signum)
{
    return ;
}
int main()
{
    int shmid;
    int key;
    struct mybuf  *p;
    int pid;
    key=ftok("./a.c",'a');
    if(key < 0)
    {
        printf("creat key failure\n");
        return -1;
    }
    printf("creat key sucess\n");
    shmid=shmget(key,128,IPC_CREAT | 0777);
    if(shmid <0)
    {
        printf("creat share memory failure\n");
        return -1;
    }
    printf("creat share memory sucess shmid=%d\n",shmid);


    signal(SIGUSR2,myfun);
    p=(struct mybuf *)shmat(shmid,NULL,0);
    if(p == NULL)
    {
        printf("parent process:shmat function failure\n");
        return -3;
    }
    //get client pid
    p->pid=getpid();//write server pid to share memory

    pause();//wait client read server pid;

    pid=p->pid;
    //write
    while(1)
    {
        //write share memory
        printf("parent process start write share memory:\n");
        fgets(p->buf,128,stdin);
        kill(pid,SIGUSR1);// client process read data 
        pause();// wait client process read
    }
    shmdt(p);
    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m ");

    return 0;
}

client.c

#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
struct mybuf
{
    int pid;
    char buf[124];
};
void myfun(int signum)
{
    return ;
}
int main()
{
    int shmid;
    int key;
    struct mybuf  *p;
    int pid;
    key=ftok("./a.c",'a');
    if(key < 0)
    {
        printf("creat key failure\n");
        return -1;
    }
    printf("creat key sucess\n");
    shmid=shmget(key,128,IPC_CREAT | 0777);
    if(shmid <0)
    {
        printf("creat share memory failure\n");
        return -1;
    }
    printf("creat share memory sucess shmid=%d\n",shmid);


    signal(SIGUSR1,myfun);
    p=(struct mybuf *)shmat(shmid,NULL,0);
    if(p == NULL)
    {
        printf("parent process:shmat function failure\n");
        return -3;
    }
    //get server pid
    //read share memory 
    pid=p->pid;
    //write client pid to share memory 
    p->pid=getpid();
    //kill signal
    kill(pid,SIGUSR2);

    //client start read data from share memory
    while(1)
    {
        pause();//wait server write data to share memory;
        printf("client process receve data from share memory:%s",p->buf);//read data 
        kill(pid,SIGUSR2);//server may write share memory
    }


    shmdt(p);
    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m ");

    return 0;
}

消息队列

消息队列是链式的,消息的类型可以不一致。

8227305f040b9b98429459fc648ceabd.jpeg 消息队列中维护队列的结构体msqid_ds,其中的第一个消息指针msg_first,最后一个指针msg_last.. 每个消息中包含数据data,数据的长度length,数据的类型type。 常用消息结构msgbuf如下:

struct msgbuf{
long mtype; //消息类型
car mtext[N]; //消息正文
}

msgget函数

创建了消息队列后可以通过system("ipcs -q")查看队列信息 消息队列创建函数msgget:

头文件:  #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>
函数原型:  int msgget(key_t key, int flag);
参数:  key,和消息队列关联的key值
        flag,消息队列的访问权限
返回:  成功,消息队列ID,出错:-1

msgsnd

函数原型:  int msgsnd(int msqid, const void *msgp, size_t size, int flag)
参数:  msqid,消息队列ID
        msgp,指向消息的指针
        size,发送的消息正文字节数
        flag,IPC_NOWAIT,消息没有发送完函数也会立即返回0,直到发送完函数才返回
返回:成功0,出错-1

接收消息函数msgrcv

函数原型:  int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int flag)
参数:  msgid,消息队列号
        msgp,接收消息的缓冲区
        size,要接收的消息字节数
        msgtype,0:接收消息队列中第一个消息
                >0,接收消息队列中第一个类型为msgtype的消息
                <0,接收消息队列中类型值不大于msgtype的绝对值切类型值又最小的消息
        flag,   0:若无消息函数一直阻塞
                IPC_NOWAIT,若没消息,进程立即返回ENOMSG
返回:  成功,接收到的消息长度,出错-1

消息队列控制函数msgctl

原型:  int msgctl(int msgqid, int cmd, struct msgqid_ds *buf)
参数:  msgid,消息队列ID
        cmd,IPC_STAT,读取消息队列的属性,并将其保存在buf指向的缓冲区
            IPC_SET,设置消息队列属性,这个值取自buf参数
            IPC_RMID,从系统中删除消息队列
        buf,消息队列缓冲区
返回:  成功0,,出错-1

示例

#include "sys/types.h"
#include "sys/msg.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
struct msgbuf
{
  long type;
  char voltage[124];
  char ID[4];
};
int main()
{
  int msgid;
  int readret;
  struct msgbuf sendbuf,recvbuf;
  msgid=msgget(IPC_PRIVATE,0777);
  if(msgid <0)
  {
	printf("creat message queue failure\n");
	return -1;
  }
  printf("creat message queue  sucess msgid=%d\n",msgid);
  system("ipcs -q");
  //init sendbuf
  sendbuf.type=100;
  printf("please input message:\n");
  fgets(sendbuf.voltage,124,stdin);
  //start write message to message queue
  msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.voltage),0);
 
  //start read message from message queue
  memset(recvbuf.voltage,0,124);
  readret=msgrcv(msgid,(void *)&recvbuf,124,100,0);
  printf("recv:%s",recvbuf.voltage);
  printf("readret=%d\n",readret);
  //second read message queue
  msgrcv(msgid,(void *)&recvbuf,124,100,0);
  printf("second read after\n");
  //delete message queue
  msgctl(msgid,IPC_RMID,NULL);
  system("ipcs -q");
  return 0;
}

信号灯

信号灯的IPC对象是一个信号灯集合,包含了多个信号量,多有的函数都是对一个集合的操作。

e58fbd0fc3264f7a38e21426c27b1409.jpeg

POSIX 信号量 与 IPC 信号灯之间比较

功能POSIX信号量IPC信号灯
定义sem_t sem1semget
初始化sem_initsemctl
P操作sem_waitsemop
V操作sem_postsemop

semget函数

头文件:
  #include <sys/types.h>
  #include <sys/ipc.h>
  #include <sys/sem.h>
函数原型:int semget(key_t key, int nsems, int semflg)
参数:
  key:和信号灯关联的key值
  nsems:信号灯集合包含的信号灯数量
  semflg:信号灯集合访问权限
返回:成功:信号灯集ID,出错-1           

semop函数

函数原型:int semop(int semid, struct sembuf *opsptr, size_t nops)
函数参数:
  semid:信号灯集ID
  struct sembuf{
    short sem_num;//要操作的信号灯编号
    short sem_op;//0,,等待,直到信号灯的值变成0. //1:释放资源,V操作。 //-1:分配资源,P操作
    short sem_flg;// 0,IPC_NOWAIT, SEM_UNDO
  };
   nops:要操作的信号灯个数
  返回:成功0,出错-1

semctl函数

原型:int semctl(int semid, int semnum, int cmd, ...union semun arg)
参数:semid 信号灯集合ID
    semnum:要修改的信号灯编号
    cmd:GETVAL获取信号灯的值
        SETVAL设置信号灯的值
        IPC_RMID从系统中删除信号灯集合
返回:成功0,出错-1

示例

线程使用信号灯同步

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
//#include "semaphore.h"
#include "sys/ipc.h"
#include "sys/sem.h"
//sem_t sem;
union semun 
{	                
	int  val;  	/* Value for SETVAL*/
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
								                  
	unsigned short  *array;  /* Array for GETALL, SETALL */
												                 
	struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};
 
int semid;
union semun mysemun;
struct sembuf mysembuf;
void *fun(void *var)//child thread code
{
  int j;
  //p  wait
 // sem_wait(&sem);  //sleep
  mysembuf.sem_op=-1;
  semop(semid,&mysembuf,1);
  for(j=0;j<10;j++)//second
  {
    usleep(100);
	printf("this is fun j=%d\n",j);
  }
 
}
int main()//main thread code
{
  int i;
  char str[]="hello linux\n";
  pthread_t tid;
  int ret;
  semid=semget(IPC_PRIVATE,3,0777);
  if(semid < 0)
  {
	printf("creat semaphore failure\n");
	return -1;
  }
  printf("creat semaphore sucess,semid=%d\n",semid);
  system("ipcs -s");
  mysemun.val=0;
  semctl(semid,0,SETVAL,mysemun);
  //sem_init(&sem,0,0);//sem=0
  mysembuf.sem_num=0;
  mysembuf.sem_flg=0;
  ret=pthread_create(&tid,NULL,fun,(void *)str);
  if(ret<0)
  {
	printf("creat thread failure\n");
	return -1;
  }
  for(i=0;i<10;i++)//first 
  {
	usleep(100);
	printf("this is main fun i=%d\n",i);
  }
  //v
 // sem_post(&sem);
  mysembuf.sem_op=1;
  semop(semid,&mysembuf,1);
  while(1);
  return 0;
}