嵌入式Linux _ System V IPC进程间通信

一、System V  IPC (了解)

1、System V  IPC 机制概念 

  • System V  IPC (系统5的IPC 官方版本中引入的一大类进程间通信机制)
    1. IPC 对象包含: 共享内存 、 消息队列和信号灯集;
    2. 每个IPC对象有唯一的ID;(在创建是由系统分配的)
    3. IPC对象创建后一直存在,直到被显示地删除(或者系统关闭,IPC也会自动释放);
    4. 每个IPC对象有一个关联的KEY;一种属性;作用:通过KEY可以使得不同进程可以打开/找到 同一个IPC对象;KEY值为0 表示 私有对象;
    5. ipcs/ipcrm   两个命令
      1. ipcs :  显示当前系统5的所有IPC对象;
      2. ipcrm :删除一个对像; 可以根据key 、id 等删除;
  • System V  IPC  —— key

2、ftok(熟练)

  • 为了避免系统中的key值与新创建的key冲突 ,使用ftok;降低概率
  • 为了创建一个Key值;

          #include  <sys/types.h>

           #include  <sys/ipc.h>

  • key_t ftol(const char *path,int proj_id);
  1. 成功时返回合法的key值,失败时返回EOF;
  2. path 存在且 可访问的文件的路径;  ----  路经存在且存在
  3. key值的由来:由第一个参数系统分配的i结点的编号与第二个参数的低八位进行组合移位,形成key值;
  4. prij_id  用于生成key 的数字,不能为0;

3、System V  IPC  -- ftok  示例    

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>


int main(int argc, const char *argv[])
{
	key_t key;

	if((key = ftok(".",'a')) == -1)
	{
		perror("ftok");
		exit(-1);
	}
	printf("%d \n",(int)key);
	return 0;
}

—    每个进程必须生成相同的key,因为通过相同的Key,才能找到同一个IPC对象;

二、共享内存

1、共享内存特点(了解)

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝;
  • 共享内存 在 内存空间 创建,,不能被用户直接访问,但可被进程映射到用户空间访问,使用灵活;
  • 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用;

2、共享内存创建(熟练)

  • 共享内存使用步骤:
    1. 创建/打开共享内存;
    2. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问;
    3. 读写共享内存;
    4. 撤销共享内存映射;
    5. 删除共享内存对象;
  • 共享内存的创建  —  shmget  (可用于创建/打开一个共享内存)

        #include <sys/ipc.h>

        #include  <sys/shm.h>

  • int shmget(key_t key , int  size , int shmflg);
    1. 成功时返回共享内存的id,失败时返回EOF;
    2. key 和共享内存关联的key , IPC_PRIVATE或 ftok生成;
    3. shmflg  共享内存 标志位 IPC_CREAT|0666;

3、示例

  1. 要求:创建一个私有的共享内存,大小为512字节,权限为0666;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>


int main(int argc, const char *argv[])
{
	char* addr;
	int msgid;

	if((shmid = shmget(IPC_PRIVATE,512,0666)) < 0)
	{
		perror("shmget");
		exit(-1);
	}
	printf("%d \n",shmid);


	return 0;
}
  1.  要求:创建/打开一个和Key 关联的共享内存,大小为1024字节,权限为0666;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>


int main(int argc, const char *argv[])
{
	char* addr;
	int msgid;
	key_t key;

	if((key = ftok(".",'a')) == -1)
	{
		perror("ftok");
		exit(-1);
	}
	if((shmid = shmget(key,1024,IPC_CREAT|0666)) < 0)
	{
		perror("shmget");
		exit(-1);
	}
	printf("%d \n",shmid);

	return 0;
}
  • IPC_CREAT|0666  :系统去判断该key与对象关联 的共享内存存在了,就打开;不存在则取创建;返回ID;

4、小结

         ftok:生成key值;

         共享内存:不需要内存拷贝,效率最高;

         shmget()  :创建打开/创建;

5、共享内存映射(熟练)

   —   首先要获得共享内存的ID后,对共享内存进行以下各种操作

   —    每一个IPC对象都有一个唯一的ID,使用该ID可以对该对象做相应的操作;

  • 共享内存映射 ----  shmat

        作用:根据共享内存的ID,把一个共享内存映射到进程的地址空间,映射完以后,我们就可以像访问一个自己分配的内存空间一样取访问共享空间;

         #include <sys/ipc.h>

         #include <sys.shm.h>

         void  *shmat(int shmid , const void *shmaddr, int shmflg);

  • 成功时返回映射后的地址,失败时返回(void*)-1;
  • shmid  : 映射空间返回的id;
  • *shmaddr : 指定映射后的地址,为NULL时,由操作系统来自动分配地址;(推荐使用NULL,认为分配 会占取不能使用的地址空间,与malloc相似)
  • shmflg  : 标志位 0表示可读写;SHM_RDONLY 表示只读;

6、共享内存读写(熟练)

  • 通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型;
  • 存放的是int ,定义int指针去读写;

  例如: 在共享内存中存放键盘输入的字符串:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(int argc, const char *argv[])
{
	char* addr;
	int shmid;
	key_t key;

	if((key = ftok(".",'a')) == -1)   ///获取 key值
	{
		perror("ftok");
		exit(-1);
	}

	if((shmid = shmget(key,1024,IPC_CREAT|0666)) < 0) //打开/创建一个 共享文件
	{
		perror("shmget");
		exit(-1);
	}
	printf("%d \n",shmid);

	if((addr = (char*)shmat(shmid,NULL,0)) == (char*)-1 )  //映射一个共享文件
	{
		perror("shmat");
		exit(-1);
	}
	fgets(addr,32,stdin);


	return 0;
}

7、共享内存撤销映射  ----  shmdt

         #include <sys/ipc.h>

         #include <sys.shm.h>

         int   shmdt (void *shmaddr);

  • 成功是返回0,失败返回EOF;
  • shmaddr : 映射空间的首地址;
  • 不使用共享内存时应撤销映射;
  • 进程结束时自动撤销共享内存的映射;

8、共享内存控制(熟练)

  • 共享内存控制  -----  shmctl

         #include <sys/ipc.h>

         #include <sys.shm.h>

         int shmctl(int shmid,int cmd , struct shmid_ds *buf);

  1. 可能会用来显式的删除一个共享内存;
  2. 成功返回0,失败时返回EOF;
  3. shmid : 要操作的共享内存的id;
  4. cmd  : 要执行的操作 IPC_STAT(获取当前共享内存的属性) IPC_SET(设置)IPC_RMID(删除一个共享内存的ID:使用该操作时不需要第三个参数,直接使用NULL即可)
  5. buf  : 保存保存或设置共享内存属性的地址;
  • 共享内存 ---- 注意事项
  1. 每块共享内存大小有限制
  2. ipcs  -l         查看共享内存的属性
  3. cat /proc/sys/kernel/shmmax   修改共享内存的大小
  4. 共享内存删除的时间点:第一个进程创建共享内存,最后一个进程删除共享内存;
  5. shmctl(shmid,IPC_RMID,NULL);标记这个共享内存将要删除
  6. nattach 变成0时真正删除;将所有进程中都删除了该共享内存时,才会真正的删除(所有进程都撤销该共享内存);

二、消息队列(熟练)

1、消息队列 的概念

  • System  V  IPC的一种,进程间通信的机制 之一;
  • 消息队列由消息队列ID来唯一标识;
  • 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等;
  • 消息队列可以按照类型来发送/接收消息;
  • 不同的进程 通过 消息队列进行通信,可在一个消息队列中定义不同的类型来辨别;

                        

  —     消息队列使用步骤:

  • 打开/创建消息队列 msgget
  • 向消息队列发送消息  msgsnd
  • 从消息队列接收消息  msgrcv
  • 控制消息队列  msgctl

2、打开/创建消息队列(熟练)

       #include  <sys/ipc.h>

      #include  <sys/msg.h>

      int msgget(key_t  key ,int msgflg);

  • 成功时返回消息队列的ID,失败时返回EOF;
  • key和消息队列关联的 key ; 如果创建进程私有消息队列使用IPC_PRIVATE , 如果多个进程使用一个消息队列,每个进程使用ftok得到key值;
  • msgflg : 标志IPC_CREAT | 0666;加上这个后,系统会检查如果消息队列不存在则创建;

—   打开消息队列的示例:

                                           

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>

int main(int argc, const char *argv[])
{
	char* addr;
	int msgid;
	key_t key;

	if((key = ftok(".",'a')) == -1)
	{
		perror("ftok");
		exit(-1);
	}
	if((msgid = msgget(key,IPC_CREAT|0666))<0)
	{
		perror("msgid");
		exit(-1);
	}
	return 0;
}
  • 多个进程使用ftok获得key时,“.”标识在当前目录,如果多进程都是使用该参数的话,要保证进程在同一个目录中执行;

3、发送消息(熟练)

       #include  <sys/ipc.h>

       #include  <sys/msg.h>

       int msgsnd(int msgif, const void* msgp , size_t  size);

  1. 成功时返回0,失败时返回-1
  2. msgid  消息队列id
  3. msgp   消息缓冲区地址;
  4. size      消息正文长度;
  5. msgflg    标志位 0 或 IPC_NOWAIT;
  • 标志位中: 0 的含义是,进程成功收到消息后返回(当消息队列满时,进程被阻塞,直到成功收到消息后返回0);IPC_NOWAIT 的含义是,消息发送后,成功返回0,消息队列满了也不阻塞,直接返回-1后其他信号;
  • 消息格式

     —  通信双方首先定义好统一的消息格式;

     —  用户根据应用需求定义结构体类型;

     —  首成员类型为long ,代表消息类型(正整数);

     —  其他成员都属于消息正文;                   

typedef struct 
{
	long mtype;     // 
	char mtext[64];
}MSG;
  • 通信的双方:通信格式必须是一致的;

4、小结

  • 打开/创建 消息队列 ----  msgget
  • 定义消息格式 --- 用户自定义结构体;
  • 消息发送  -----  msgsnd   向用户发送指定的数据;

5、接收消息队列(熟练)

       #include  <sys/ipc.h>

       #include  <sys/msg.h>

         int  msgrcv (int  msgid , void *msgp , size_t size , long msgtype , );

  1. 成功时返回收到的消息长度,失败时返回-1;
  2. msgid   消息队列 id;
  3. msgp    消息缓冲区地址;
  4. size      指定接收的消息长度;
  5. msgtype   指定接收的消息类型;
  6. msgflg      标志位   0 或 IPC_NOWAIT;
  • 如果指定长度 比传过来的消息的长度要 小,则只会接收指定的长度的消息;
  • msgtype 可指定为0;接收最早的 消息;也可指定为负数,按照优先级接收消息;
  • msgflg  : 0 意味则 如果没有指定的消息,阻塞等待,直到有消息为止;IPC_NOWAIT:有消息则接收,没有消息则返回失败;

6、控制消息队列(熟练)

       #include  <sys/ipc.h>

       #include  <sys/msg.h>

         int  msgctl (int  msgid , int  cmd , struct  msqid_ds *buf );

  1. 成功时返回0,失败时返回-1;
  2. msgid   消息队列 id;
  3. cmd      要执行的操作   IPC_STAT/IPC_SET/IPC_RMID;
  4. buf      存放消息队列属性的地址;
  • 使用方法与shmctl大体相同,不同为共享内存的删除需要所有进程都删除了才会彻底删除,而消息队列一旦使用该函数,将会彻底删除,如果有进程正在发送或者接收消息,也将立马出错;

7、消息队列示例(掌握)

  • 消息队列 示例 :

第一个进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef struct   //消息队列的格式
{
	long mtype;       //消息类型
	char mtext[64];    //消息正文
}MSG;

#define LEN  (sizeof(MSG) - sizeof(long))  //正文长度
#define TypeA  100
#define TypeB  200


int main(int argc, const char *argv[])
{
	key_t key;
	int msgid;
	MSG buf;

	if((key = ftok(".",'q')) == -1)   //得到key值
	{
		perror("ftok");
		exit(-1);
	}
	if((msgid = msgget(key , IPC_CREAT|0666)) < 0)  //打开/创建一个消息队列
	{
		perror("msgget");
		exit(-1);
	}
	while(1)
	{
		buf.mtype = TypeB;
		printf("intput : \n");
		fgets(buf.mtext,64,stdin);   //从键盘输入字符到消息队列中
		msgsnd(msgid,&buf,LEN,0);      //发送消息
		if(strcmp(buf.mtext,"quit\n") == 0)break;
		if(msgrcv(msgid,&buf,LEN,TypeA,0) < 0)    //接收消息
		{
			perror("msgrcv");
			exit(-1);
		}
		printf("recv from clientB : %s \n",buf.mtext);
		if(strcmp(buf.mtext,"quit\n") == 0)
		{
			msgctl(msgid,IPC_RMID,0);//删除消息队列
			exit(-1);
		}
	}
	return 0;
}

第二个进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef struct 
{
	long mtype;
	char mtext[64];
}MSG;

#define LEN  (sizeof(MSG) - sizeof(long))
#define TypeA  100
#define TypeB  200

int main(int argc, const char *argv[])
{
	key_t key;
	int msgid;
	MSG buf;

	if((key = ftok(".",'q')) == -1)
	{
		perror("ftok");
		exit(-1);
	}
	if((msgid = msgget(key , IPC_CREAT|0666)) < 0)
	{
		perror("msgget");
		exit(-1);
	}
	while(1)
	{
		if(msgrcv(msgid,&buf,LEN,TypeB,0) < 0)
		{
			perror("msgrcv");
			exit(-1);
		}
		if(strcmp(buf.mtext,"quit\n") == 0)
		{
			msgctl(msgid,IPC_RMID,0);//删除消息队列
			exit(-1);
		}
		printf("recb from cleintA : %s \n",buf.mtext);
		buf.mtype = TypeA;
		printf("intput : \n");
		fgets(buf.mtext,64,stdin);
		msgsnd(msgid,&buf,LEN,0);
		if(strcmp(buf.mtext,"quit\n") == 0)break;
	}
	return 0;
}

 

 

 

 

  • 一个进程中使用 多进程、多线程 实现信息说法;
  • 使用消息队列跟方便的搭建一个 CS架构,实现多客户端之间的通信;

三、信号灯(集)机制

1、System  V 信号灯

  • 信号灯的含义: 计数信号灯; 
  • Posix无名信号灯在线程专题中有所提及;
  • 本讲主要讲解 System V 信号灯;

2、System  V 信号灯特点

  • Posix信号灯 不论是P 还是V操作,只能操作一个信号灯;
  • 而在System V 信号灯 可以操作集合中的多个信号灯;

  —  两个进程 P1  P2分别申请资源A  B 锁死的情况:

                 

   —  两个进程 P1  P2分别申请资源A  B 使用System V 信号灯避免 锁死的情况:

                

  • 将P(A) P(B)申请资源 整合成一个集合set,只有同时满足时 ,另一个才能使用资源;

—  使用System V 信号扽 使用步骤:

  •   打开/创建 信号灯     semget
  •   信号灯 初始化           semctl
  •   P/V   操作                  semop
  •   删除信号灯                 semctl

3、打开/创建信号灯(熟练)

           #include   <sys/ipc .h>

           #include   <sys/sem.h> 

           int  semget (key_t  key , int  nsems  , int  semflg);

  1. 成功时返回信号灯 的id , 失败返回-1;
  2. key   和消息队列光联的key  IPC_PRIVATE 或 ftok;
  3. nsems    集合 中包含的计数信号灯个数;
  4. semflg     标志位  IPC_CREAT|0666    IPC_EXCL;
  • nsems  : 初始化时要明确 程序中有几类资源,每类资源需要多少个信号灯;
  • semflg  : IPC_CREAT|0666 信号灯不存在则创建;如果加上IPC_EXCL,起检测的作用,如果不存在则创建,如果存在则出错;一般在第一个进程创建信号灯时使用,判断某个信号灯是否创建过;

4、信号灯初始化(熟练)

1、信号灯初始化

           #include   <sys/ipc .h>

           #include   <sys/sem.h> 

           int  semctl (int semid , int  semnum  , int cmd , .......);

  1. 成功时返回0 , 失败返回-1;
  2. semif   要操作的信号灯集 ID;
  3. semnum  要操作的集合中的信号灯编号;
  4. cmd  执行的操作  SETVAL    IPC_RMID;
  5. union  semun   取决于cmd;
  • 信号灯集合中,信号灯的编号从0开始;
  • SETVAL : 设置 信号灯; IPC_RMID: 删除信号灯集合;
  • 第四个参数:union semun  取决于cmd  ; SETVAL时需要,IPC_RMID 时不需要;
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) */
           };

5、信号灯集初始化  ----  示例

 

                            

  • 一般程序中有多少个信号灯 就需要初始化几次;
  • 信号灯不能被多次初始化; 在初始化前最好 判断一下是否存在;

6、信号灯 — P/V操作(熟练)

   —  信号灯P/V操作   ----  semop

           #include   <sys/ipc .h>

           #include   <sys/sem.h> 

           int  semop (int semid ,  struct  sembuf* sops  , unsigned nsops);

  1. 成功时返回0 , 失败返回-1;
  2. semif   要操作的信号灯集 ID;
  3. sops    描述对信号灯操作的结构体(数组);
  4. nsops   要操作的信号灯的个数;
  • sops : 一个 struct sembuf* 类型 的数组,要操作的信号灯操作一一sembuf  对应;
strct  sembuf
{
    short semnum;
    short sem_op;
    short sem_flg;
};
  • semnum    信号灯编号;
  • sem_op     -1 :p操作   1 : v 操作
  • sem_flg      0  /  IPC_NOWAIT 
  • P操作 : 获取/释放资源  V操作: 使用资源

7、信号灯 共享内存(熟练)-----  实际的例程

  • 使用信号灯,实现两个进程间的通信;
  • 创建一个 可写的信号灯,一个可读的信号灯,共两个信号灯组成信号灯集
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#define N  64
#define READ 0
#define WRITE 1

union semun   // 信号灯的设置 格式
{
	int val;
	struct semid_ds *df;
	unsigned short *array;
	struct seminfo *buf;
};

void init_sem(int semid , int s[] ,int n)  // 信号灯的初始化
{
	int i;
	union semun myun;
	for(i = 0;i<n;i++)
	{
		myun.val = s[i];
		semctl(semid,i,SETVAL,myun); //初始化
	}
}

void pv(int semid , int num, int op)
{
	struct sembuf buf;

	buf.sem_num = num; //信号灯编号
	buf.sem_op = op;    // -1 p操作  1 v 操作
	buf.sem_flg = 0;    //0/IPC_NOWAIT
	semop(semid,&buf,1);  //一个一个的操作信号灯
}

int main(int argc, const char *argv[])
{
	int shmid,semid,s[] = {0,1};
	pid_t pid;
	key_t key;
	char *shmaddr;

	if((key = ftok(".",'q')) == -1)   //得到key值
	{
		perror("ftok");
		exit(-1);
	}

	if((shmid = shmget(key,2,IPC_CREAT|0666)) < 0)  // 打开/创建 共享内存
	{
		perror("shmget");
		exit(-1);
	}
	if((semid = semget(key,2,IPC_CREAT|0666)) < 0)   //打开/创建 信号灯
	{
		perror("semget");
		goto _error1;
	}
	init_sem(semid,s,2);  // 初始化  2 个信号灯
	if((shmaddr = (char*)shmat(shmid,NULL,0)) == (char*)-1) // 映射共享内存
	{
		perror("shmat");
		goto _error2;
	}
	if((pid = fork()) < 0)  // 创建子进程
	{
		perror("fork");
		goto _error2;
	}else if(pid == 0)  //子进程
	{
		char *p,*q;
		while(1)
		{
			pv(semid,READ,-1);  //对编号0的信号灯  P操作
			p = q = shmaddr;
			while(*q)
			{
				if(*q != ' ')
				{
					*p++ =*q;
				}
				q++;
			}
			*p = '\0';
			printf("%s",shmaddr);
			pv(semid,WRITE,1);//对编号1的信号灯  V操作
		}
	}else  // 父进程
	{
		while(1)
		{
			pv(semid,WRITE,-1);//对编号1的信号灯  P操作
			printf("intput > \n");
			fgets(shmaddr,N,stdin);
			if(strcmp(shmaddr,"quit\n") == 0)break;
			pv(semid,READ,1);//对编号0的信号灯  V操作
		}
		kill(pid,SIGUSR1); //向子进程发送 SIGUSR1信号
	}
_error2:
	semctl(semid,0,IPC_RMID);  //删除信号灯
_error1:
	shmctl(semid,IPC_RMID,NULL); // 删除共享内存
	return 0;
}
  • semop:
  • 信号灯集 +  共享内存  : 实现内存的有序访问;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值