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


     

    目录

    前言

    一、system V IPC对象图解

    1.流程图解:

    ​编辑

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

     二、消息队列

    1.消息队列的原理

    2.消息队列相关的API

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

     实例代码如下:

    2.2 发送消息到消息队列中

     实例代码如下:

    2.3 从消息队列中获取消息  

    实例代码如下: 

     2.4 消息队列相关的命令

     2.5 管理消息队列

     实例代码如下:

    三、共享内存

    1.概念

    2.原理图解

    3.相关的api函数

    3.1 创建共享内存对象

     实例代码如下:

    3.2 映射共享内存  

     3.3 取消映射

     实例代码reader.c如下:

     实例代码writer.c如下:

    实例代码shm.h 如下:

     3.4 管理共享内存

    实例代码如下: 

    四、信号灯 (信号量)

    1.概念

    2.信号灯相关的api函数

    2.1 创建或获取信号灯对象

    实例代码如下:

    2.2 实现P操作和V操作  

    实例代码如下:

    2.3 管理信号灯  

    实例代码如下: 

    总结


    前言

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


    一、system V IPC对象图解

    1.流程图解:

    e3b7f73d700f4fe085171956b7ac8851.png

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

    fbaf8bbd33cc44b59fc87277a69541ee.png

     

     二、消息队列

    1.消息队列的原理

    d8f9942acfe846f0a214a7fade16240d.png

     

    2.消息队列相关的API

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

    头文件:

    #include
    #include
    #include

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

     实例代码如下:

    1. int main(void)
    2. {
    3. key_t key;
    4. int msg_id;
    5. //获取key
    6. key = ftok("./",0xa);
    7. if(key < 0){
    8. perror("ftok");
    9. exit(1);
    10. }
    11. //创建或获取消息对象
    12. msg_id = msgget(key,IPC_CREAT|0666);
    13. if(msg_id < 0){
    14. perror("msgget");
    15. exit(1);
    16. }
    17. return 0;
    18. }

    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

     实例代码如下:

    1. int main(void)
    2. {
    3. key_t key;
    4. int msg_id;
    5. //获取key
    6. key = ftok("./",0xa);
    7. if(key < 0){
    8. perror("ftok");
    9. exit(1);
    10. }
    11. //创建或获取消息对象
    12. msg_id = msgget(key,IPC_CREAT|0666);
    13. if(msg_id < 0){
    14. perror("msgget");
    15. exit(1);
    16. }
    17. //向消息队列中发送消息
    18. while(1){
    19. bzero(&buf,sizeof(buf));
    20. printf("请输入消息的类型:");
    21. scanf("%ld",&buf.mtype);
    22. printf("请输入消息:");
    23. while(getchar() != '\n'); //清空输入缓冲区
    24. fgets(buf.mtext,sizeof(buf.mtext),stdin);
    25. buf.mtext[strlen(buf.mtext)-1] = '\0';
    26. if(msgsnd(msg_id,&buf,strlen(buf.mtext),0) < 0){
    27. perror("msgsnd");
    28. exit(1);
    29. }
    30. }
    31. return 0;
    32. }

    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

    实例代码如下: 

    1. int main(void)
    2. {
    3. key_t key;
    4. int msg_id;
    5. //获取key
    6. key = ftok("./",0xa);
    7. if(key < 0){
    8. perror("ftok");
    9. exit(1);
    10. }
    11. //创建或获取消息对象
    12. msg_id = msgget(key,IPC_CREAT|0666);
    13. if(msg_id < 0){
    14. perror("msgget");
    15. exit(1);
    16. }
    17. //从消息队列中获取消息
    18. while(1){
    19. bzero(&buf,sizeof(buf));
    20. printf("请输入消息的类型:");
    21. scanf("%ld",&buf.mtype);
    22. if(msgrcv(msg_id,&buf,sizeof(buf.mtext),buf.mtype,0) < 0){
    23. perror("msgsnd");
    24. exit(1);
    25. }
    26. printf("msg:%s\n",buf.mtext);
    27. }
    28. return 0;
    29. }

     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) */
                       };

     实例代码如下:

    1. int main(int argc,char **argv)
    2. {
    3. int msg_id;
    4. msg_id = atoi(argv[1]);
    5. if(msgctl(msg_id,IPC_RMID,NULL) < 0){
    6. perror("msgget");
    7. exit(1);
    8. }
    9. return 0;
    10. }

    三、共享内存

    1.概念

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

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

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

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

    2.原理图解

     d3ea3585b84f4dd1a960bd7045694f9f.png

     

    3.相关的api函数

    3.1 创建共享内存对象

    头文件:

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

     实例代码如下:

    1. int main(void)
    2. {
    3. key_t key;
    4. int shm_id;
    5. //获取key
    6. key = ftok("./",0xa);
    7. if(key < 0){
    8. perror("ftok");
    9. exit(1);
    10. }
    11. //创建或获取共享内存对象
    12. shm_id = shmget(key,SHM_SIZE, IPC_CREAT|0666);
    13. if(shm_id < 0){
    14. perror("shmget");
    15. exit(1);
    16. }
    17. return 0;
    18. }

    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如下:

    1. #include "shm.h"
    2. int main(void)
    3. {
    4. key_t key;
    5. int shm_id;
    6. char *buf;
    7. //获取key
    8. key = ftok("./",0xa);
    9. if(key < 0){
    10. perror("ftok");
    11. exit(1);
    12. }
    13. //创建或获取共享内存对象
    14. shm_id = shmget(key,SHM_SIZE, IPC_CREAT|0666);
    15. if(shm_id < 0){
    16. perror("shmget");
    17. exit(1);
    18. }
    19. //将共享内存映射到进程的虚拟空间中
    20. buf = (char*)shmat(shm_id,NULL,0);
    21. if(buf < 0){
    22. perror("shmat");
    23. exit(1);
    24. }
    25. //打印共享内存中的数据
    26. while(1){
    27. printf("%s",buf);
    28. sleep(1);
    29. }
    30. //解除映射
    31. if(shmdt(buf) < 0){
    32. perror("shmdt");
    33. exit(1);
    34. }
    35. return 0;
    36. }

     实例代码writer.c如下:

    1. #include "shm.h"
    2. int main(void)
    3. {
    4. key_t key;
    5. int shm_id;
    6. char *buf;
    7. //获取key
    8. key = ftok("./",0xa);
    9. if(key < 0){
    10. perror("ftok");
    11. exit(1);
    12. }
    13. //创建或获取共享内存对象
    14. shm_id = shmget(key,SHM_SIZE, IPC_CREAT|0666);
    15. if(shm_id < 0){
    16. perror("shmget");
    17. exit(1);
    18. }
    19. //将共享内存映射到进程的虚拟空间中
    20. buf = (char*)shmat(shm_id,NULL,0);
    21. if(buf < 0){
    22. perror("shmat");
    23. exit(1);
    24. }
    25. //向共享内存写数据
    26. while(1){
    27. printf("请输入字符串:");
    28. fgets(buf,SHM_SIZE,stdin);
    29. }
    30. //解除映射
    31. if(shmdt(buf) < 0){
    32. perror("shmdt");
    33. exit(1);
    34. }
    35. return 0;
    36. }

    实例代码shm.h 如下:

    1. #ifndef __SHM_H__
    2. #define __SHM_H__
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define SHM_SIZE 1024
    11. #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 */
                   ...
               };

    实例代码如下: 

    1. int main(int argc,char **argv)
    2. {
    3. int shm_id;
    4. shm_id = atoi(argv[1]);
    5. if(shmctl(shm_id,IPC_RMID,NULL) < 0){
    6. perror("shmget");
    7. exit(1);
    8. }
    9. return 0;
    10. }

    四、信号灯 (信号量)

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

    实例代码如下:

    1. int main(void)
    2. {
    3. key_t key;
    4. int sem_id;
    5. //获取key
    6. key = ftok("./",0xa);
    7. if(key < 0){
    8. perror("ftok");
    9. exit(1);
    10. }
    11. //创建或获取共享内存对象
    12. sem_id = semget(key,1, IPC_CREAT|0666);
    13. if(sem_id < 0){
    14. perror("semget");
    15. exit(1);
    16. }
    17. return 0;
    18. }

    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. //1》实现P操作
    2. void sem_p(int sem_id,int index)
    3. {
    4. struct sembuf buf = {index,-1,0};
    5. if(semop(sem_id,&buf,1) < 0){
    6. perror("semop");
    7. exit(1);
    8. }
    9. }
    10. //2》实现v操作
    11. void sem_v(int sem_id,int index)
    12. {
    13. struct sembuf buf = {index,1,0};
    14. if(semop(sem_id,&buf,1) < 0){
    15. perror("semop");
    16. exit(1);
    17. }
    18. }

    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

    实例代码如下: 

    1. //初始化指定的信号灯
    2. void sem_init(int sem_id,int semnum,int value)
    3. {
    4. union semun su;
    5. su.val = value;
    6. if(semctl(sem_id,semnum,SETVAL,su) < 0){
    7. perror("semctl");
    8. exit(1);
    9. }
    10. }
    11. //初始化所有信号灯
    12. void sem_init_all(int sem_id,unsigned short vals[])
    13. {
    14. union semun su;
    15. su.array = vals;
    16. if(semctl(sem_id,0,SETALL,su) < 0){
    17. perror("semctl");
    18. exit(1);
    19. }
    20. }

     


    总结

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

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

           希望这篇博客能给各位朋友们带来帮助,最后懒大王请来过的朋友们留下你们宝贵的三连以及关注,感谢你们!

     

     

  • 相关阅读:
    QT中表格控件使用
    Hexagon_V65_Programmers_Reference_Manual(32)
    @MapperScan注解原理解析 --> 1.3版本和2.0版本的区别
    pgsql执行脚本并传参
    HTML5-iframe内联框架、表单元素、功能组件
    初始Redis(入门篇)
    猫罐头哪个好?宠物店精选5款口碑好的猫罐头推荐!
    一个开发文档模板
    ubuntu安装后配置
    物联网行业案例 - Splashtop 助力成都谷帝科技有限公司远程维护安卓设备
  • 原文地址:https://blog.csdn.net/weixin_58070962/article/details/133686170