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

10 篇文章 0 订阅
9 篇文章 0 订阅

进程间通信

一、基本概念

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

    进程间交互数据

    进程间通信的分类:

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

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

        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 <stdio.h>

                #include <unistd.h>

                #include <stdlib.h>

                #include <string.h>

                #include <sys/types.h>

                #include <sys/ipc.h>

                #include <sys/shm.h>

                #include <signal.h>

                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 <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#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 <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#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 <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <sys/ipc.h>

#include <sys/sem.h>

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));

   

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoyu1381

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值