C/C++ 进程间通信system V IPC对象超详细讲解(系统性学习day9)

简介: C/C++ 进程间通信system V IPC对象超详细讲解(系统性学习day9)

前言

System V IPC(Inter-Process Communication)对象是一种用于在不同进程之间进行通信的机制。它包括三种类型的对象:消息队列(Message Queue)、信号量(Semaphore)和共享内存(Shared Memory)。


一、system V IPC对象图解

1.流程图解:

2.查看linux内核中的ipc对象:

 

二、消息队列

1.消息队列的原理

 

2.消息队列相关的API

2.1 获取或创建消息队列(msgget)

头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
//参数1  ----- key :
               动态获取key:  ftok()
                    #include <sys/types.h>
                    #include <sys/ipc.h>
                    key_t ftok(const char *pathname, int proj_id);
                    //参数1  ---- 工程目录
                    //参数2  ---- 工程编号
                    //返回值 ----- 成功,返回key值,失败:-1
               静态分配: IPC_PRIVATE
//参数2 ------ msgflg:如果消息队列不存在,需要给出创建的关键字,并设置权限 IPC_CREAT | 0666
//返回值 ----- 成功:消息队列的ID,失败:-1

实例代码如下:

int main(void)
{
    key_t key;
    int msg_id;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
        perror("ftok");
        exit(1);
    }
    //创建或获取消息对象
    msg_id = msgget(key,IPC_CREAT|0666);
    if(msg_id < 0){
        perror("msgget");
        exit(1);
    }
    return 0;
}

2.2 发送消息到消息队列中

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//参数1 ----- 消息队列的id
//参数2 -----  指向struct msgbuf结构体的指针,该结构体需要自己定义,如下:
                struct msgbuf {
                   long mtype;       /* message type, must be > 0 消息类型*/
                   char mtext[1];    /* message data  消息正文*/
                };
//参数3 ---- 要发送的消息的长度 
//参数4 ---- msgflg值如下:
            IPC_NOWAIT  消息没有发送完成函数也会立即返回。
            0:直到发送完成函数才返回
//返回值 --- 成功:0,失败:-1

实例代码如下:

int main(void)
{
    key_t key;
    int msg_id;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
        perror("ftok");
        exit(1);
    }
    //创建或获取消息对象
    msg_id = msgget(key,IPC_CREAT|0666);
    if(msg_id < 0){
        perror("msgget");
        exit(1);
    }
 
//向消息队列中发送消息
    while(1){
        bzero(&buf,sizeof(buf));
        printf("请输入消息的类型:");
        scanf("%ld",&buf.mtype);
        printf("请输入消息:");
        while(getchar() != '\n');   //清空输入缓冲区
        fgets(buf.mtext,sizeof(buf.mtext),stdin);
        buf.mtext[strlen(buf.mtext)-1] = '\0';
        if(msgsnd(msg_id,&buf,strlen(buf.mtext),0) < 0){
            perror("msgsnd");
            exit(1);
        }
    }
 
    return 0;
}

2.3 从消息队列中获取消息  

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//参数1 ----- 消息队列的id
//参数2 -----  指向struct msgbuf结构体的指针,该结构体需要自己定义,如下:
                struct msgbuf {
                   long mtype;       /* message type, must be > 0 消息类型*/
                   char mtext[1];    /* message data  消息正文*/
                };
//参数3 ----  mtext的长度
//参数4 ----  msgtyp:要接收的消息类型
             msgtyp > 0 ,表示接收指定类型的消息 
             msgtyp = 0 ,按先后顺序依次接收不同类型消息
             msgtyp < 0 ,优先接收消息类型不大于|msgtyp|的最小类型的消息
//参数5 ---- msgflg值如下:
            IPC_NOWAIT  消息没有发送完成函数也会立即返回。
            0:直到发送完成函数才返回
//返回值 --- 成功:0,失败:-1

实例代码如下:

int main(void)
{
    key_t key;
    int msg_id;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
        perror("ftok");
        exit(1);
    }
    //创建或获取消息对象
    msg_id = msgget(key,IPC_CREAT|0666);
    if(msg_id < 0){
        perror("msgget");
        exit(1);
    }
 
//从消息队列中获取消息
    while(1){
        bzero(&buf,sizeof(buf));
        printf("请输入消息的类型:");
        scanf("%ld",&buf.mtype);
        if(msgrcv(msg_id,&buf,sizeof(buf.mtext),buf.mtype,0) < 0){
            perror("msgsnd");
            exit(1);
        }
        printf("msg:%s\n",buf.mtext);
    }
 
    return 0;
}

2.4 消息队列相关的命令

peter@ubuntu:~/2308/proc/day04_code$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0x0a010356 0          peter      666        61           5
peter@ubuntu:~/2308/proc/day04_code$ ipcrm -q 0
peter@ubuntu:~/2308/proc/day04_code$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

2.5 管理消息队列

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
 //参数1 ---- 消息队列ID
 //参数2 ---- 功能码:
                 IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
                IPC_SET:设置消息队列的属性。这个值取自buf参数。
                IPC_RMID:从系统中删除消息队列。    
 //参数3 ----struct msqid_ds 结构体指针
                 struct msqid_ds {
                       struct ipc_perm msg_perm;     /* Ownership and permissions */
                       time_t          msg_stime;    /* Time of last msgsnd(2) */
                       time_t          msg_rtime;    /* Time of last msgrcv(2) */
                       time_t          msg_ctime;    /* Time of last change */
                       unsigned long   __msg_cbytes; /* Current number of bytes in
                                                        queue (nonstandard) */
                       msgqnum_t       msg_qnum;     /* Current number of messages
                                                        in queue */
                       msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                        allowed in queue */
                       pid_t           msg_lspid;    /* PID of last msgsnd(2) */
                       pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
                   };

实例代码如下:

int main(int argc,char **argv)
{
    int msg_id;
 
    msg_id = atoi(argv[1]);
 
    if(msgctl(msg_id,IPC_RMID,NULL) < 0){
        perror("msgget");
        exit(1);
    }
    return 0;
}

三、共享内存

1.概念

共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝

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

进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

2.原理图解

 

3.相关的api函数

3.1 创建共享内存对象

头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//参数1 ---- 动态获取key:  ftok()
                    #include <sys/types.h>
                    #include <sys/ipc.h>
                    key_t ftok(const char *pathname, int proj_id);
                    //参数1  ---- 工程目录
                    //参数2  ---- 工程编号
                    //返回值 ----- 成功,返回key值,失败:-1
               静态分配: IPC_PRIVATE
//参数2 ---- 要创建或获取的共享内存的大小
//参数3 ---- 权限:IPC_CREAT | 0666
//返回值 ---- 成功:共享内存ID,失败:-1

实例代码如下:

int main(void)
{
    key_t key;
    int shm_id;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
        perror("ftok");
        exit(1);
    }
    //创建或获取共享内存对象
    shm_id = shmget(key,SHM_SIZE, IPC_CREAT|0666);
    if(shm_id < 0){
        perror("shmget");
        exit(1);
    }
    return 0;
}

3.2 映射共享内存  

void *shmat(int shmid, const void *shmaddr, int shmflg);
//参数1 ---- 共享内存ID
//参数2 ---- 指定进程虚拟空间的映射的起始地址,一般为NULL:让系统分配一个起始地址
//参数3 ---- 访问权限:SHM_RDONLY:共享内存只读
                    默认0:共享内存可读写
//返回值 --- 成功:映射的虚拟空间地址,失败:-1

3.3 取消映射

int shmdt(const void *shmaddr);
//参数 ----映射的虚拟空间的起始地址
//返回值 ----成功:0,失败:-1

实例代码reader.c如下:

#include "shm.h"
 
int main(void)
{
    key_t key;
    int shm_id;
    char *buf;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
  perror("ftok");
  exit(1);
    }
    //创建或获取共享内存对象
    shm_id = shmget(key,SHM_SIZE, IPC_CREAT|0666);
    if(shm_id < 0){
  perror("shmget");
  exit(1);
    }
 
    //将共享内存映射到进程的虚拟空间中
    buf = (char*)shmat(shm_id,NULL,0);
    if(buf < 0){
  perror("shmat");
  exit(1);
    }
 
 
    //打印共享内存中的数据
    while(1){
  printf("%s",buf);
  sleep(1);
    }
 
    //解除映射
    if(shmdt(buf) < 0){
  perror("shmdt");
  exit(1);
    }
    return 0;
}

实例代码writer.c如下:

#include "shm.h"
 
int main(void)
{
    key_t key;
    int shm_id;
    char *buf;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
  perror("ftok");
  exit(1);
    }
    //创建或获取共享内存对象
    shm_id = shmget(key,SHM_SIZE, IPC_CREAT|0666);
    if(shm_id < 0){
  perror("shmget");
  exit(1);
    }
 
    //将共享内存映射到进程的虚拟空间中
    buf = (char*)shmat(shm_id,NULL,0);
    if(buf < 0){
  perror("shmat");
  exit(1);
    }
 
 
    //向共享内存写数据
    while(1){
  printf("请输入字符串:");
  fgets(buf,SHM_SIZE,stdin);
    }
 
    //解除映射
    if(shmdt(buf) < 0){
  perror("shmdt");
  exit(1);
    }
    return 0;
}

实例代码shm.h 如下:

#ifndef __SHM_H__
#define __SHM_H__
 
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
#define SHM_SIZE  1024
 
#endif

3.4 管理共享内存

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//参数1 ---- 共享内存的ID
//参数2 ---- 功能码:
            IPC_STAT  (获取对象属性)
            IPC_SET (设置对象属性)
            IPC_RMID (删除对象)    
//参数3 ----struct shmid_ds 结构体指针
            struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

实例代码如下:

int main(int argc,char **argv)
{
    int shm_id;
 
    shm_id = atoi(argv[1]);
 
    if(shmctl(shm_id,IPC_RMID,NULL) < 0){
        perror("shmget");
        exit(1);
    }
    return 0;
}

四、信号灯 (信号量)

1.概念

信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。

信号灯种类:

   posix有名信号灯

   posix基于内存的信号灯(无名信号灯)

   System V信号灯(IPC对象)

1》 二值信号灯:用于表示资源是否可用

   值为0或1。与互斥锁类似,资源可用时值为1,不可用时值为0。

2》 计数信号灯:用于表示资源的数量

   值在0到n之间。用来统计资源,其值代表可用资源数

3》 等待操作,也称为P操作

   是等待信号灯的值变为大于0,然后将其减1;

4》 释放操作,也称为V操作

    用来唤醒等待资源的进程或者线程

5》System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯

 

2.信号灯相关的api函数

2.1 创建或获取信号灯对象

头文件:
 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
//参数1 ---- 动态获取key:  ftok()
                    #include <sys/types.h>
                    #include <sys/ipc.h>
                    key_t ftok(const char *pathname, int proj_id);
                    //参数1  ---- 工程目录
                    //参数2  ---- 工程编号
                    //返回值 ----- 成功,返回key值,失败:-1
               静态分配: IPC_PRIVATE
//参数2 ---- 集合中信号灯的个数
//参数3 ---- 访问权限:IPC_CREAT | 0666
//返回值 ---- 成功:信号灯对象ID,失败:-1

实例代码如下:

int main(void)
{
    key_t key;
    int sem_id;
 
    //获取key值
    key = ftok("./",0xa);
    if(key < 0){
        perror("ftok");
        exit(1);
    }
    //创建或获取共享内存对象
    sem_id = semget(key,1, IPC_CREAT|0666);
    if(sem_id < 0){
        perror("semget");
        exit(1);
    }
    return 0;
}

2.2 实现P操作和V操作  

int semop(int semid, struct sembuf *sops, size_t nsops);
//参数1 ----- 信号灯对象的ID
//参数2 ----- 结构体指针
                struct sembuf {
                   short  sem_num;  //  要操作的信号灯的编号
                   short  sem_op;   //    0 :  等待,直到信号灯的值变成0
                                               //   1  :  释放资源,V操作
                                               //   -1 :  分配资源,P操作                   
                   short  sem_flg; // 0,  IPC_NOWAIT,  SEM_UNDO
                };
//参数3 -----nops:  要操作的信号灯的个数
//返回值 ---- 成功:0,失败:-1

实例代码如下:

//1》实现P操作
  void sem_p(int sem_id,int index)
    {
        struct sembuf buf = {index,-1,0};
 
        if(semop(sem_id,&buf,1) < 0){
            perror("semop");
            exit(1);
        }
    }
//2》实现v操作
    void sem_v(int sem_id,int index)
    {
        struct sembuf buf = {index,1,0};
 
        if(semop(sem_id,&buf,1) < 0){
            perror("semop");
            exit(1);
        }
    }

2.3 管理信号灯  

int semctl(int semid, int semnum, int cmd, ...);
//参数1 ---- 信号灯对象ID
//参数2 ---- 集合中信号灯的编号
//参数3 ---- 功能码:
            IPC_STAT  ----获取信号灯对象属性
            IPC_SET   ----设置信号灯对象属性
            IPC_RMID  ----从内核中删除信号灯对象
            SETALL      ----设置集合中所有信号灯的值
            SETVAL    ----设置集合中编号为semnum的信号灯的值
//参数4 ---- 联合体变量,类型如下:
            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) */
           };
//返回值 -----成功:0,失败:-1

实例代码如下:

//初始化指定的信号灯
void sem_init(int sem_id,int semnum,int value)
{
    union semun su;
    su.val = value;
    if(semctl(sem_id,semnum,SETVAL,su) < 0){
        perror("semctl");
        exit(1);
    }
}
//初始化所有信号灯
void sem_init_all(int sem_id,unsigned short vals[])
{
    union semun su;
    su.array = vals;
    if(semctl(sem_id,0,SETALL,su) < 0){
        perror("semctl");
        exit(1);
    }
}


总结

       本篇文章针对进程间通信system V IPC对象进行详细讲解,希望能够帮到大家!

       以后还会给大家展现更多关于嵌入式和C语言的其他重要的基础知识,感谢大家支持懒大王!

相关文章
|
5月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
4月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
5月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
4月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
6月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
151 19
|
6月前
|
C++ 开发者
C++学习之继承
通过继承,C++可以实现代码重用、扩展类的功能并支持多态性。理解继承的类型、重写与重载、多重继承及其相关问题,对于掌握C++面向对象编程至关重要。希望本文能为您的C++学习和开发提供实用的指导。
112 16
|
6月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
179 13
|
5月前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
5月前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。

相关实验场景

更多