• 操作系统——进程间通信——共享内存、消息队列、信号量


    进程间通信

    一、基本概念

        什么是进程间通信:是指两个或多个进程之间交互数据的过程,因为进程之间是相互独立的,为了进程间协同工作就必须实现

        进程间交互数据

        进程间通信的分类:

            简单的进程间通信:信号、普通文件、环境变量表、命令行参数

            传统的进程间通信:管道文件

            XSI的进程间通信:共享内存、消息队列、信号量

            网络进程间的通信:Socket套接字

    二、传统的进程间通信-管道文件

        管道是UNIX系统中最古老的进程通信技术,古老意味着所有系统都支持,早期的管道都是半双工通信,现有的系统管道是全双工通信

        管道就是一种特殊的文件,数据在文件中是流动的,读取之后就自动消失,如果文件中没有数据则会阻塞

        有名管道:基于有文件名的管道文件的通信

            编程模型

                进程A       进程B

                创建管道

                打开管道    打开管道

                写数据      读数据

                关闭管道    关闭管道

                删除管道  

           

            创建有名管道文件的方式:

            1、命令 mkfifo

            2、函数

            int mkfifo(const char *pathname, mode_t mode);

            功能:创建有名管道

            pathname:管道文件的权限

            mode:管道文件权限 0664

            int fd = open("fifo",O_WRONLY);

          if(0 > fd)

          {

              perror("open");

              unlink("fifo");

              return -1;

          }

          char buf[256] = {};

          for(;;)

          {

            read(fd,buf,strlen(buf)+1);

            if(strcmp(buf,"quit"))

            {

                printf("%s",buf);

                break;

            }

          }

          close(fd);

       


     

        匿名管道:

            只适合通过fork创建父子进程之间使用

            int pipe(int pipefd[2]);

            功能:创建一个匿名管道文件

            通过pipefd返回该匿名管道文件的读权限fd和写权限的fd

            pipefd[0] 用于读匿名管道

            pipefd[1] 用于写匿名管道

            编程模型:

                父进程      子进程

                获取一对fd  

                创建子进程  拷贝/共享一对fd

                关闭读      关闭写

                写数据      读数据

                关闭写      关闭读

    三、XSI进程间通信

        X/open公司制定的用于进程间通信的系统(S)接口(I)

        XSI进程间通信都需要借助系统内核完成,需要创建内核对象,内核对象会以整数的形式返回给用户态,类似于文件描述符,

        也叫做IPC标识符

        文件的创建打开需要借助文件名,IPC内核对象需要借助IPC键值(整数),必须要确保IPC键值是独一无二的

        key_t ftok(const char *pathname, int proj_id);

        功能:计算出一个独一无二的IPC键值

        pathname:项目路径

        proj_id:项目标号

        返回值:计算出来的IPC键值

        注意:项目路径必须真实存在,否则计算出来的key永远相同

       

        共享内存:

        基础特点:

            两个或多个进程之间共享一块由内核负责统一管理内存,该内存可以与多个进程的虚拟内存进行映射

            优点:不需要复制信息,直接读写内存,是最快的一种IPC机制

            缺点:需要考虑同步访问问题,一般使用信号

            int shmget(key_t key,size_t size,int shmflg);

            功能:创建\获取一块共享内存

            key:IPC键值

            size:共享内存的大小,获取共享内存时此参数无意义,一般给0

            shmflg:

                IPC_CREAT   创建共享内存,如已存在直接获取

                IPC_EXCL    共享内存已存在,返回失败

                获取时直接给0

                注意:如果是创建共享内存还需要额外提供共享内存的权限  例如:IPC_CREAT|0664

            返回值:IPC标识符,失败-1

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

            功能:让虚拟内存与共享内存进行映射

            shmid:IPC标识符 shmget的返回值

            shmaddr:想要映射的虚拟内存首地址,为NULL时系统会自动分配地址

            shmflg:

                SHM_RDONLY 以只读方式映射共享内存

                SHM_RND:只有shmaddr参数不为NULL时才有有效,表示从shmaddr开始向下以整数页方式映射

           

            返回值:与共享内存映射成功后的虚拟内存首地址,失败返回(void *) -1

            int shmdt(const void *shmaddr);

            功能:取消映射

            shmaddr:映射成功后的虚拟内存首地址

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

            功能:删除/控制共享内存

            shmid:IPC标识符

            cmd:

                IPC_STAT   获取共享内存属性 buf输出型参数

                IPC_SET    设置共享内存属性 buf输入型参数

                IPC_RMID   删除共享内存     NULL

            buf

        编程模型:

            进程A               进程B

            创建共享内存        获取共享内存

            映射共享内存        映射共享内存

            写数据并通知其他进程 收到通知并读数据

            收到通知并读数据    写数据并通知其他进程

            取消映射            取消映射

            删除共享内存        

                    #include

                    #include

                    #include

                    #include

                    #include

                    #include

                    #include

                    #include

                    int shmid;

                    char* shm;

                    //  接收到通知 读数据

                    void sigread(int num)

                    {

                        printf("read:%s\n",shm);

                        if(0 == strcmp(shm,"quit"))

                        {

                            printf("通信结束!\n");

                            //  取消映射

                            if(shmdt(shm))

                            {

                                perror("shmdt");    

                            }

                            //  删除共享内存

                            if(shmctl(shmid,IPC_RMID,NULL))

                            {

                                perror("shmctl");

                            }

                            exit(0);

                        }

                    }

                    int main(int argc,const char* argv[])

                    {

                        signal(34,sigread);

                        //  创建共享内存

                        shmid = shmget(ftok(".",110),4096,IPC_CREAT|0644);

                        if(0 > shmid)

                        {

                            perror("shmget");

                            return -1;

                        }

                        pid_t pid = 0;

                        printf("我是%u,请输入与我通信的进程ID:",getpid());

                        scanf("%u",&pid);

                        //  映射共享内存

                        shm = shmat(shmid,NULL,0);

                        if((void*)-1 == shm)

                        {

                            perror("shmat");

                            shmctl(shmid,IPC_RMID,NULL);

                            return -1;

                        }

                        //  写数据并通知其他进程

                        for(;;)

                        {

                            printf(">>>");

                            scanf("%s",shm);

                            kill(pid,34);

                            if(0 == strcmp(shm,"quit"))

                            {

                                printf("通信结束!\n");

                                break;

                            }

                        }

                        //  取消映射

                        if(shmdt(shm))

                        {

                            perror("shmdt");    

                        }

                        //  删除共享内存

                        if(shmctl(shmid,IPC_RMID,NULL))

                        {

                            perror("shmctl");

                        }

                        exit(0);

                    }

    消息队列:

        基本特点:是由内核负责维护管理的链式数据队列,不是根据先后顺序出队,而是根据消息类型进行收发数据  

            int msgget(key_t key, int msgflg);

            功能:创建\获取消息队列

            key:IPC键值

            msgflg:

                IPC_CREAT  消息队列已存在则获取,否则创建

                IPC_EXCL   消息队列已存在则返回错误

                注意:如果创建需要提供权限

            返回值:IPC标识符,失败-1

            int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

            功能:向消息队列发送消息包

            msqid:IPC标识符

            msgp:要发送的消息包的首地址

                struct msgbuf {

                   long mtype;      //  消息类型

                   char mtext[n];   //  数据

               };

            msgsz:数据的字节数,不包含消息类型

            msgflg:

                阻塞发送一般给0

                IPC_NOWAIT 当消息队列满,不等待立即返回

            返回值:成功0,失败-1

            ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

            功能:从消息队列中接收对应消息包的数据

            msqid:IPC标识符

            msgp:存储消息包的内存首地址

            msgsz:存储数据的内存字节数(尽量大些)

            msgtyp:消息类型(按照类型获取,不按照顺序)

                >0 读取消息类型=msgtyp的消息

                =0 读取消息队列中第一条消息

                <0 读取消息类型小于abs(msgtyp)的消息,如果有多个则读值最小的

            msgflg

                IPC_NOWAIT 消息队列都不符合时不阻塞,立即返回

                MSG_EXCEPT 如果msgtyp>0,则读取第一条不等于msgtyp的消息

                MSG_NOERROR 如果不包含此标志,如果实际发送过来的数据字节数>接收的字节数,则返回失败,

                如果包含此标志,那么就只读取接收的字节数,一定会成功

            返回值:成功读取到数据的字节数

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

        功能:获取\修改消息队列的属性、删除队列

        msqid:IPC标识符

        cmd:

            IPC_STAT   获取消息队列属性 buf输出型参数

            IPC_SET    设置消息队列属性 buf输入型参数

            IPC_RMID   删除消息队列     NULL

        buf:消息队列管理结构体

        编程模型:

            进程A                   进程B

          创建消息队列             获取消息队列

          发送消息                  获取消息

          获取消息                  发送消息

          删除消息队列

    message.h

    #ifndef MESSAGE_H

    #define MESSAGE_H

    #define MSG_SIZE 256

    typedef struct Msg

    {

        long type;

        char data[MSG_SIZE];

    }Msg;

    #endif//MESSAGE_H

    msgA.c

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include "message.h"

    int main(int argc,const char* argv[])

    {

        //  创建消息队列

        int msqid = msgget(ftok(".",120),IPC_CREAT|0644);

        if(0 > msqid)

        {

            perror("msgget");

            return -1;

        }

       

        Msg msg = {};

        for(;;)

        {

            //  发送消息

            msg.type = 5;

            printf(">>>");

            scanf("%s",msg.data);

            if(msgsnd(msqid,&msg,strlen(msg.data)+1,0))

            {

                perror("msgsnd");

                break;

            }

            if(0 == strcmp(msg.data,"quit")) break;

            //  接收消息

            if(0 >= msgrcv(msqid,&msg,MSG_SIZE,6,0))

            {

                perror("msgrcv");  

                break;

            }

            printf("recv:%s\n",msg.data);

            if(0 == strcmp(msg.data,"quit")) break;

        }

        printf("通信结束!\n");

       

        usleep(1000);

        //  删除消息队列

        if(msgctl(msqid,IPC_RMID,NULL))

        {

            perror("msgctl");

            return -1;

        }

        return 0;

    }

    msgB.c

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include "message.h"

    int main(int argc,const char* argv[])

    {

        //  获取消息队列

        int msqid = msgget(ftok(".",120),0);

        if(0 > msqid)

        {

            perror("msgget");

            return -1;

        }

       

        Msg msg = {};

        for(;;)

        {

            //  接收消息

            if(0 >= msgrcv(msqid,&msg,MSG_SIZE,5,0))

            {

                perror("msgrcv");  

                break;

            }

            printf("recv:%s\n",msg.data);

            if(0 == strcmp(msg.data,"quit")) break;

            //  发送消息

            msg.type = 6;

            printf(">>>");

            scanf("%s",msg.data);

            if(msgsnd(msqid,&msg,strlen(msg.data)+1,0))

            {

                perror("msgsnd");

                break;

            }

            if(0 == strcmp(msg.data,"quit")) break;

        }

        printf("通信结束!\n");

        return 0;

    }

    信号量:    

        基本特点:由内核管理的一个"全局变量",用于记录共享资源的数量,限制进程对共享资源的访问使用

        信号量是一种数据操作锁,本身是不具备数据交互功能,而是通过控制其他的通信资源从而配合实现进程间通信

        1、如果信号量的值大于0,说明可以使用资源,使用时需要信号量-1,然后再使用

        2、如果信号量的值等于0,说明没有资源可使用,此时进程进入休眠,直到信号量的值大于0,进程会被唤醒,执行步骤1

        3、当资源使用完毕,把信号量的值+1,正在休眠的进程就会被唤醒

       

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

        功能:创建\获取信号量

        key:IPC键值

        nsems:信号量的数量 一般写1

        semflg:IPC_CREAT  信号量已存在则获取,否则创建

                IPC_EXCL   信号量已存在则返回错误

                注意:如果创建需要提供权限

        返回值:IPC标识符 失败-1

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

        功能:删除、控制信号量

        semid:IPC标识符

        semnum:要操作的第几个信号量,从0开始,下标

        cmd:

            IPC_STAT   获取信号量属性 buf输出型参数

            IPC_SET    设置信号量属性 buf输入型参数

            IPC_RMID   删除信号量     NULL

            SETVAL     设置某个信号量的值

            SETALL     设置所有信号量的值

            GETVAL      获取某个信号量的值

            GETALL      获取所有信号量的值

            GETNCNT     获取等待拿资源的进程数量

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

        功能:对信号量进行加减操作

        semid:IPC标识符

        sembuf{

            unsigned short sem_num;  // 信号量的下标

            short          sem_op;   //

                    1 信号量+1

                    -1 信号量-1 如果不能减,则默认阻塞

            short          sem_flg;  //

                    IPC_NOWAIT 不阻塞

                    SEM_UNDO 如果进程终止没有手动还资源,系统会自动还

        }

        nsops:表示指向结构体的数量,一般写1

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    int main(int argc,const char* argv[])

    {

        //  创建信号量

        int semid = semget(ftok(".",119),1,IPC_CREAT|0644);

        if(0 > semid)

        {

            perror("semget");

            return -1;

        }

        //  设置信号量的值

        //  0下标  5资源数

        if(semctl(semid,0,SETVAL,5))

        {

            perror("semctl");

            semctl(semid,0,IPC_RMID);

            return -1;

        }

        printf("我是父进程%u,我有%d头小毛驴\n",

            getpid(),semctl(semid,0,GETVAL));

        for(int i=0; i<10; i++)

        {

            pid_t pid = fork();

            if(0 == pid)

            {

                //  尝试获取共享资源

                srand(i);

                struct sembuf buf = {0,-1,0};

                semop(semid,&buf,1);

       

                //  工作

                printf("我是子进程%u,我骑了一头小毛驴!还剩%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));

                sleep(rand()%8+3);

                //  还共享资源

                buf.sem_op = 1;

                semop(semid,&buf,1);

                printf("我是子进程%u,我还了一头小毛驴!还剩%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));

                return 0;

            }

        }

        while(-1 != wait(NULL));

        printf("我是父进程%u,我有%d头小毛驴\n",

            getpid(),semctl(semid,0,GETVAL));

       

    }

  • 相关阅读:
    7、Linux驱动开发:设备-自动创建设备节点
    【SA8295P 源码分析】126 - 摄像头 POC (Power over Coax) 同轴电缆供电技术原理分析
    Tomcat的类加载器
    什么是代理服务器
    最新ChatGPT程序源码+AI系统+详细图文部署教程/支持GPT4.0/支持Midjourney绘画/Prompt知识库
    如何为linux kernel贡献代码
    Nginx+Lua+OpenResty(详解及使用)
    Java 基础三:使用Velocity模板生成 xml
    北京汽车美容元宇宙,未来已来
    MySQL Installer is running in Community mode
  • 原文地址:https://blog.csdn.net/xiaoyu1381/article/details/126663571