进程间通信方式(三)-- IPC对象(消息队列、共享内存、信号灯集)

2021/10/7 7:11:26

本文主要是介绍进程间通信方式(三)-- IPC对象(消息队列、共享内存、信号灯集),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

IPC对象

  • 1. IPC对象
  • 2. 查看IPC对象命令
  • 3. 消息队列
    • 3.1 概念
    • 3.2 相关函数
      • 3.2.1 msgget() 创建或者打开消息队列
      • 3.2.2 ftok() 获取键值
      • 3.2.3 msgctl() 控制消息队列
      • 3.2.4 msgsnd() 向消息队列写信息
      • 3.2.5 msgrcv() 从消息队列读
      • 3.3 消息队列 示例
  • 4. 共享内存
    • 4.1 概念
    • 4.2 相关函数
      • 4.2.1 shmget() 创建或者打开一个共享内存
      • 4.2.2 shmctl() 控制共享内存
      • 4.2.3 shmat() 映射共享内存
      • 4.2.4 shmdt() 解除共享内存映射
    • 4.3 共享内存示例
  • 5. 信号灯集
    • 5.1 相关概念
    • 5.2 相关函数
      • 5.2.1 semget() 打开或者创建一个信号灯集
      • 5.2.2 semctl() 控制信号灯集
      • 5.2.3 semop() 执行PV操作
    • 5.3 信号灯集 示例

1. IPC对象

IPC对象主要指三种进程间通信机制:消息队列、共享内存、信号灯集

优点:创建IPC对象后,在当前系统中所有进程都是可见的。


2. 查看IPC对象命令

使用ipcs查看当前ipc对象

ipcs 查看所有的ipc对象
       -q  查看消息队列
       -m 查看共享内存
        -s  查看信号量
ipcrm 删除ipc对象
          ipcrm –q msqid 删除指定的消息队列

3. 消息队列

3.1 概念

  • 消息队列是IPC对象的一种
  • 消息队列由消息队列ID唯一标识
  • 消息队列就是一个消息的列表。用户可以在消息队列中添加消息读取消息等。
  • 消息队列可以按照类型来发送/接收消息

3.2 相关函数

3.2.1 msgget() 创建或者打开消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
功能:
	创建或者打开一个消息队列,得到消息队列id
参数:
     key:键值,唯一的键值确定唯一的消息队列
         如何设置键值:
             自己直接定义一个键值
             使用ftok函数获取键值
    msgflg:标志位
        一般设置为 IPC_CREAT | 0777
返回值:
    成功:消息队列id
    失败:-1


3.2.2 ftok() 获取键值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:
	获取键值
参数:
    pathname:路径名
    proj_id:自定义的值,只取前八位
返回值:
    成功:键值
    失败:-1

3.2.3 msgctl() 控制消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:
	控制一个消息队列
参数:
    msqid:消息队列id
    cmd:具体控制
        IPC_STAT 获取消息队列属性信息
        IPC_SET 设置消息队列属性信息
        IPC_RMID 删除一个消息队列
    msqid_ds:消息队列属性结构体
返回值:
    成功:0
    失败:-1

3.2.4 msgsnd() 向消息队列写信息

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向一个消息队列中写入数据
参数:
    msqid:消息队列id
    msgp:要写入的数据,需要自己定义一个结构体
        struct msg_buf{
       		 //消息类型,必须有且类型也不能变
      		 // 用于读取数据时,可以指定消息的类型来读取
              long type; 
             //消息正文具体要写入的数据,大小自己定义,类型也可以自定义             
              char mtext[32]; 
        };
    msgsz:消息正文的大小,就是mtext的大小,一定不是第二个参数的大小
    msgflg:标志位
        0   阻塞
        IPC_NOWAIT  非阻塞
返回值:
    成功:0
    失败:-1

3.2.5 msgrcv() 从消息队列读

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, 
                long msgtyp, int msgflg);
功能:从消息队列中读取数据
参数:
    msqid:消息队列id
    msgp:保存读取的数据
    msgsz:消息正文大小
    msgtyp:消息类型
        0    按照顺序读取一个消息
        >0   按照顺序读取第一个消息类型为当前参数的消息
        <0   按照顺序读取第一个消息类型小于等于这个参数的绝对值的类型的消息
    msgflg:标志位
        0   阻塞
        IPC_NOWAIT  非阻塞
返回值:
    成功:接收到的数据的大小
    失败:-1

3.3 消息队列 示例

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

typedef struct{
    long type;
    int id;
    char name[32];
    int score;
}MSG;

#define DATASIZE (sizeof(MSG) - sizeof(long))

int main(int argc, char const *argv[])
{
    //使用ftok函数获取键值
    key_t key;
    if((key = ftok(".", 1000)) == -1)
    {
        perror("ftok error");
        exit(1);
    }
    //ftok函数获取的健值就是
    //这个函数的第二个参数的后8位 ↓
    //和 第一个参数对应文件的设备号的后8位 ↓
    //以及这个文件inode号的后16位 ↓
    //共同组成键值
    
    // printf("key = %#x\n", key);
    // struct stat mystat;
    // stat(".", &mystat);
    // printf("dev_num = %x\n", mystat.st_dev);
    // printf("inode = %x\n", mystat.st_ino);
    // printf("1000 = %#x\n", 1000);
  
    //创建一个消息队列
    int msgid;
    if((msgid = msgget(key, IPC_CREAT | 0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }
    
    puts("--------------------------------------------");
    system("ipcs -q");
    puts("--------------------------------------------");

    MSG msg1 = {1, 1001, "zhangsan", 90};
    MSG msg2 = {2, 1002, "lisi", 100};
    MSG msg3 = {3, 1003, "wangwu", 92};
    MSG msg4 = {4, 1004, "zhaoliu", 88};
    msgsnd(msgid, &msg1, DATASIZE, 0);
    msgsnd(msgid, &msg2, DATASIZE, 0);
    msgsnd(msgid, &msg3, DATASIZE, 0);
    msgsnd(msgid, &msg4, DATASIZE, 0);

    puts("--------------------------------------------");
    system("ipcs -q");
    puts("--------------------------------------------");

	MSG msg;
    ssize_t bytes;
    if((bytes = msgrcv(msgid, &msg, DATASIZE, -3, 0)) == -1)
    {
        perror("msgrcv error");
        exit(1);
    }

    printf("bytes = %ld, id:%d,name:%s,score:%d\n", bytes, msg.id, msg.name, msg.score);

    //删除消息队列
    if(msgctl(msgid, IPC_RMID, NULL) == -1)
    {
        perror("msgctl error");
        exit(1);
    }
    puts("--------------------------------------------");
    system("ipcs -q");
    puts("--------------------------------------------");

	return 0;
}


4. 共享内存

4.1 概念

共享内存是一种最高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据在这里插入代码片的拷贝

为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间

  • 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
  • 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

4.2 相关函数

struct shmid_ds
{
   struct ipc_perm  shm_perm;        //操作许可,里面包含共享内存的用户ID、组ID等信息
   int  shm_segsz;                   //共享内存段的大小,单位为字节
   __kernel_time_t  shm_atime;       //最后一个进程访问共享内存的时间
   __kernel_time_t  shm_dtime;       //最后一个进程离开共享内存的时间
   __kernel_time_t  shm_ctime;       //最后一次修改共享内存的时间
   __kernel_ipc_pid_t  shm_cpid;     //创建共享内存的进程ID
   __kernel_ipc_pid_t  shm_lpid;     //最后操作共享内存的进程ID
   ushort  shm_nattch;               //当前使用该共享内存段的进程数量
   ushort  shm_unused;
   void  *shm_unused2;
   void  *shm_unused3;
};

参考IPC进程间通信—共享内存


4.2.1 shmget() 创建或者打开一个共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:
	创建或者打开一个共享内存
参数:
    key:键值,唯一的键值确定唯一的共享内存id
    size:共享内存的大小
    shmflg:标志位,一般设置为IPC_CREAT | 0777
返回值:
    成功:共享内存id
    失败:-1


4.2.2 shmctl() 控制共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制一个共享内存
参数:
    shmid:共享内存id
    cmd:具体操作
        IPC_STAT  获取属性
        IPC_SET   设置属性
        IPC_RMID  删除共享内存
    buf:共享内存属性结构体
返回值:
    成功:0
    失败:-1


4.2.3 shmat() 映射共享内存

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存
参数:
    shmid:共享内存id
    shmaddr:映射的地址确定
        NULL 系统自定义
    shmflg:标志位
        0  可读可写
        SHM_RDONLY 只读
返回值:
    成功:映射的地址
    失败:-1

4.2.4 shmdt() 解除共享内存映射

int shmdt(const void *shmaddr);
功能:
	解除共享内存映射
参数:
    shmaddr:映射的共享内存的地址,shmat的返回值
返回值:
    成功:0
    失败:-1

4.3 共享内存示例

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

typedef struct{
	int a;
	char b;
    float c;
}MSG;

int main(int argc, char const *argv[])
{
    //使用ftok函数获取健值
    key_t key;
    if((key = ftok(".", 10)) == -1)
    {
        perror("ftok error");
        exit(1);
    }

    //创建共享内存
    int shmid;
    if((shmid = shmget(key, 666, IPC_CREAT | 0777)) == -1)
    {
        perror("shmget error");
        exit(1);
    }
	puts("----------------------------------");
    system("ipcs -m");
	puts("----------------------------------");

	MSG *num;
    //映射共享内存
    num = shmat(shmid, NULL, 0);

    //当映射完地址之后,直接对地址操作即可
    //*num = 100;
    num->a = 100;
    num->b = 'w';
    num->c = 245.234532;
    
	//解除共享内存映射
    //shmdt(num);
    puts("----------------------------------");
    system("ipcs -m");
	puts("----------------------------------");
    
    MSG *num2;
    //映射共享内存
    num2 = shmat(shmid, NULL, 0);

    //当映射完地址之后,直接对地址操作即可
    //printf("*num = %d\n", *num);
    printf("a = %d, b = %c, c = %.2f\n", num2->a, num2->b, num2->c);

    //解除共享内存映射
    shmdt(num2);

	puts("----------------------------------");
    system("ipcs -m");
	puts("----------------------------------");

    //删除共享内存
    if(shmctl(shmid, IPC_RMID, NULL) == -1)
    {
        perror("shmctl error");
        exit(1);
    }

	puts("----------------------------------");
    system("ipcs -m");
	puts("----------------------------------");
    return 0;
}

在这里插入图片描述
共享内存表项含义


为什么会出现“dest”这个状态

Linux下删除任何内容,都会先检查一下这个内容的引用计数(就是文件的使用数,n个进程使用,引用计数为n)。若引用计数为0,就会真正的删除该内容(这里就是删除共享内存)。不为0,表示仍有进程使用,则正在使用的进程可以正常使用,直至引用计数降为0后,系统才会将该内容真正意义上的删除掉。
对这里用共享内存来说同理,显示“dest”是表示该共享内存已经被删除但有进程还在使用它。这时操作系统将共享内存的mode标记为SHM_DEST,key标记为0x00000000,并对外显示status为“dest”。当用户调用shmctl的IPC_RMID时,系统会先查看这个共享内存的引用计数,如果引用计数为0,就会销毁这段共享内存,否者设置这段内存的mod的mode位为SHM_DEST,如果所有进程都不用则删除这段共享内存。

参考关于linux 共享内存


5. 信号灯集

5.1 相关概念

信号灯集用于在进程间解决同步问题,本质是一个计数器,主要执行PV操作,P操作是减操作,V操作是加操作,当信号量的值为0是,P操作会阻塞

注意:IPC中的信号灯集PV操作每次加减的值可以是大于1的


5.2 相关函数

5.2.1 semget() 打开或者创建一个信号灯集

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:
	打开或者创建一个信号灯集
参数:
	key:键值,唯一的键值确定唯一的信号灯集
	nsems:信号量的个数,至少1个
	semflg:标志位
          一般设置为 IPC_CREAT | 0777
返回值:
			成功:信号灯集的id
			失败:-1

5.2.2 semctl() 控制信号灯集

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
功能:
	控制信号灯集
参数:
	semid:信号灯集的id
	semnum:要操作的信号的编号,从0开始
	cmd:执行命令
        IPC_RMID 删除信号灯集
        SETVAL 设置信号量的初始值
             需要通过第四个参数来设置,
             需要自己定义共用体
              union semun{
                  int val;
              }
返回值:
	成功:0
	失败:-1

5.2.3 semop() 执行PV操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:
	执行PV操作
参数:
	semid:信号灯集的id
	sops:结构体指针数组,实现具体PV操作
          struct sembuf
          {
              unsigned short sem_num;  要操作的信号量的编号,从0开始
               short sem_op;  PV操作
                   >0 V操作
                   <0 P操作
               short sem_flg;  标志位
                   0 阻塞
                   IPC_NOWAIT 非阻塞
          }
	nsops:要操作的信号量的个数
返回值:
		成功:0
		失败:-1

5.3 信号灯集 示例

创建 / 删除

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

int main(int argc, char const *argv[])
{
    //获取健值
    key_t key;
    if((key = ftok(".", 10)) == -1)
    {
        perror("ftok error");
        exit(1);
    }

    //创建信号灯集
    int semid;
    if((semid = semget(key, 5, IPC_CREAT | 0777)) == -1)
    {
        perror("semget error");
        exit(1);
    }
	puts("------------------------------------");
    system("ipcs -s");
	puts("------------------------------------");

    //删除信号灯集
    if(semctl(semid, 0, IPC_RMID) == -1)
    {
        perror("semctl error");
        exit(1);
    }

	puts("------------------------------------");
    system("ipcs -s");
	puts("------------------------------------");
	
    return 0;
}

通过信号灯集解决共享内存的同步问题
write.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>

union semun{
    int val;
};
int main(int argc, char const *argv[])
{
    //使用ftok函数获取健值
    key_t key;
    if((key = ftok(".", 10)) == -1)
    {
        perror("ftok error");
        exit(1);
    }

    //***************共享内存*******************
    //创建共享内存
    int shmid;
    if((shmid = shmget(key, 666, IPC_CREAT | 0777)) == -1)
    {
        perror("shmget error");
        exit(1);
    }

    //映射共享内存
    char *buf;
    buf = shmat(shmid, NULL, 0);

    //*****************信号灯集******************
    //创建信号灯集
    int semid;
    if((semid = semget(key, 1, IPC_CREAT | 0777)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    //设置信号灯集中信号量的初始值
    union semun myun;
    myun.val = 0;
    if(semctl(semid, 0, SETVAL, myun) == -1)
    {
        perror("semctl error");
        exit(1);
    }

    char str[32] = {0};
    struct sembuf sem_buf = {0, 1, 0};
    while(1)
    {
        fgets(str, 32, stdin);
        str[strlen(str) - 1] = '\0';

        strcpy(buf, str);

        //执行V操作
        if(semop(semid, &sem_buf, 1) == -1)
        {
            perror("semop error");
            exit(1);
        }
    }

    return 0;
}

read.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>

union semun{
    int val;
};

int main(int argc, char const *argv[])
{
    //使用ftok函数获取健值
    key_t key;
    if((key = ftok(".", 10)) == -1)
    {
        perror("ftok error");
        exit(1);
    }

    //***************共享内存*******************
    //创建共享内存
    int shmid;
    if((shmid = shmget(key, 666, IPC_CREAT | 0777)) == -1)
    {
        perror("shmget error");
        exit(1);
    }

    //映射共享内存
    char *buf;
    buf = shmat(shmid, NULL, 0);

    //*****************信号灯集******************
    //创建信号灯集
    int semid;
    if((semid = semget(key, 1, IPC_CREAT | 0777)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    //设置信号灯集中信号量的初始值
    union semun myun;
    myun.val = 0;
    if(semctl(semid, 0, SETVAL, myun) == -1)
    {
        perror("semctl error");
        exit(1);
    }

    struct sembuf sem_buf = {0, -1, 0};
    while(1)
    {
        //执行P操作
        if(semop(semid, &sem_buf,1) == -1)
        {
            perror("semop error");
            exit(1);
        }

        printf("buf = %s\n", buf);
    }

    return 0;
}


这篇关于进程间通信方式(三)-- IPC对象(消息队列、共享内存、信号灯集)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程