linux学习---基于内存的IPC(共享内存,信号量数组,消息队列)

常用的IPC分为两个类别,一是基于文件,而是基于内存

基于文件的分别有匿名管道,有名管道,普通的文件共享,socket文件

如果要看基于文件的IPC,请参考:http://blog.csdn.net/xiaoxiaopengbo/article/details/78431018

基于内存的有普通内存共享(本文章没有介绍),共享内存,共享信号量,消息队列

本文就针对linux基于内存的IPC

目前介绍一个工具ipcs

运行一下ipcs的界面是:

主要包含共享内存段,信号量数据,消息队列

ipcs m是查看共享内存

ipcs s是查看信号量数组

ipcs q是查看消息队列

ipcrm m id删除共享内存

ipcrm s id删除信号量数组

ipcrm q id删除消息队列

下面就一一介绍下这三种方式的IPC,每一种都有特定的编程模型

一.共享内存

1、共享内存介绍

共享内存是三个IPC(Inter-Process Communication)机制中的一个。

它允许两个不相关的进程访问同一个逻辑内存。

共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式。

大多数的共享内存的实现,

都把由不同进程之间共享的内存安排为同一段物理内存

共享内存是由IPC为进程创建一个特殊的地址范围,

它将出现在该进程的地址空间中。

其他进程可以将同一段共享内存连接它们自己的地址空间中。

所有进程都可以访问共享内存中的地址,

就好像它们是由malloc分配的一样。

如果某个进程向共享内存写入了数据,

所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。

2、共享内存的同步

共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。

但是它并未提供同步机制

所以我们通常需要用其他的机制来同步对共享内存的访问。

我们通常是用共享内存来提供对大块内存区域的有效访问,

同时通过传递小消息来同步对该内存的访问。

在第一个进程结束对共享内存的写操作之前,

并无自动的机制可以阻止第二个进程开始对它进行读取。

对共享内存访问的同步控制必须由程序员来负责。 

下图显示了共享内存是如何共存的:

图中的箭头显示了每个进程的逻辑地址空间到可用物理内存的映射关系。

3、共享内存使用的函数

#include <sys/shm.h>

int shmget(key_tkey, size_t size,int shmflg);

void *shmat(int shm_id,const void *shm_addr,int shmflg);

int shmdt(const void *shm_addr);

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

1). shmget函数

该函数用来创建共享内存:

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

参数:

key : 和信号量一样,程序需要提供一个参数key,

   它有效地为共享内存段命名。

   有一个特殊的键值IPC_PRIVATE, 

   它用于创建一个只属于创建进程的共享内存,

   通常不会用到。

size: 以字节为单位指定需要共享的内存容量。

shmflag: 包含9个比特的权限标志,

     它们的作用与创建文件时使用的mode标志是一样。

     由IPC_CREAT定义的一个特殊比特必须和权限标志按位或

     才能创建一个新的共享内存段。

              常用的两种方式

              创建:IPC_CREAT|IPC_EXCL |0666;

              打开:0

NOTE:

权限标志对共享内存非常有用,

因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其它用户创建的进程只能读取共享内存。

我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,

通过将数据放共享内存并设置它的权限,

就可以避免数据被其他用户修改。

返回值:

创建成功,则返回一个非负整数,即共享内存标识;

如果失败,则返回-1.

2). shmat函数

第一次创建共享内存段时,它不能被任何进程访问。

要想启动对该内存的访问,

必须将其连接到一个进程的地址空间

这个工作由shmat函数完成:

void *shmat(int shm_id,const void *shm_addr,int shmflg);

参数:

shm_id : 由shmget返回的共享内存标识。

shm_add: 指定共享内存连接到当前进程中的地址位置。

     它通常是一个空指针, 

     表示让系统来选择共享内存出现的地址。

shmflg : 是一组标志。

     它的两个可能取值是:

     SHM_RND, 和shm_add联合使用,

         用来控制共享内存连接的地址。

     SHM_RDONLY, 它使连接的内存只读

返回值:

如果调用成功, 返回一个指向共享内存第一个字节的指针;

如果失败,返回-1.

共享内存的读写权限由它的属主(共享内存的创建者),

它的访问权限和当前进程的属主决定。

共享内存的访问权限类似于文件的访问权限。

3). shmdt

将共享内存从当前进程中分离

intshmdt(constvoid *shm_addr);

shm_addr: shmat返回的地址指针。

成功时,返回0

失败时,返回-1.

NOTE:

共享内存分离并未删除它,

只是使得该共享内存对当前进程不再可用。

4). shmctl

共享内存的控制函数

intshmctl(int shm_id,int cmd,struct shmid_ds *buf);

shmid_ds结构至少包含以下成员:

struct shmid_ds {

    struct ipc_permshm_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 */

    ...

};

参数:

shm_id : 是shmget返回的共享内存标识符。

command: 是要采取的动作,

     它可以取3个值:

IPC_STAT  把shmid_ds结构中的数据设置为共享内存的当前关联值

IPC_SET  如果进程有足够的权限,

     就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID 删除共享内存段

buf   : 是一个指针,

     包含共享内存模式和访问权限的结构。

返回值:

成功时,返回0

失败时,返回-1.

部分参考:http://blog.chinaunix.net/uid-26000296-id-3421346.html

程序源码,分别为shmA.c shmB.c,shmA.c负责每隔1s往共享内存中写数据,shmB.c负责读取数据

shmA.c源码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/ipc.h>
key_t key;
int shmid;
int *p;
    inti=0;
 
void handle(int s)
{
    //4.unload share memory->shmdt
    shmdt(p);
    //5.delete share memory->shctl
    shmctl(shmid,IPC_RMID,0);
    exit(0);
}
int main()
{
    
    signal(SIGINT,handle);
    //1.create share memory->shmget
    key=ftok(".",255);
    if(key==-1)printf("ftokerror:%m\n"),exit(-1);
    shmid=shmget(key,4,IPC_CREAT|IPC_EXCL|0666);
    if(shmid==-1)printf("shmgeterror:%m\n"),exit(-1);
    //2.attach share memory->shmat
    p=shmat(shmid,0,0);
    if(p==(int*)-1)printf("at error:%m\n"),exit(-1);
    //3.access share memory
    while(1)
    {
       *p=i;
       
       sleep(1);
       i++;
    }   
}

shmB.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/ipc.h>
 
key_t key;
int shmid;
int *p;
void handle(int s)
{
    //4.unload share memory->shmdt
    shmdt(p);
    exit(0);
}
int main()
{
    
    signal(2,handle);
    //1.create share memory->shmget
    key=ftok(".",255);
    if(key==-1)printf("ftokerror:%m\n"),exit(-1);
    shmid=shmget(key,4,0);
    if(shmid==-1)printf("shmgeterror:%m\n"),exit(-1);
    //2.attach share memory->shmat
    p=shmat(shmid,0,0);
    if(p==(int*)-1)printf("at error:%m\n"),exit(-1);
    //3.access share memory
    while(1)
    {
       sleep(1);
       printf("%d\n",*p);
    }
}

分别编译shmA.c shmB.c为A,B,执行如图

并且通过ipcs m可以查看下我们创建的共享内存段

二.信号量数组

并且通过ipcs m可以查看下我们创建的共享

信号量提供了两个进程同步的机制

函数说明

semget函数说明

int semget(key_t key,

           intnums,//信号量数组个数

           intflags);//信号量的创建标记

           //创建IPC_CREAT|IPC_EXCL|0666

           //打开0

返回: -1:失败 >=0:成功返回信号量的ID

int semop(int semid,//信号量ID

           structsembuf *op,//对信号量的操作.操作可以是数组多个

           size_tnums,//第二个参数的个数

           );

返回: -1:时失败0:成功                             

int semctl(int semid,

           intnums,//对IPC_RMID无意义

           intcmd,//SETVAL  IPC_RMID

           ...);//对IPC_RMID无意义

如果要用第四个参数,有两种选择

1)定义一个联合体,然后进行初始化

union semun {

    int val;

    structsemid_ds *buf;

    unsignedshort  *array;

    structseminfo  *__buf;

};

2)直接下参数

用到的结构提

struct sembuf

{

    intsem_num;//下标

    intsem_op;

    intsem_flg;//建议为0.

}

有两个程序分别为semA.c semB.c

semA.c源码,可独立运行

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
 
//2.1 定义一个联合体
union semun {
    int val;
    structsemid_ds *buf;
    unsignedshort  *array;
    structseminfo  *__buf;
};
 
int main()
{
    key_tkey;
    intsemid;
    intr;
    unionsemun v;//2.2 定义一个初始化的值
    structsembuf op[1];
    //1.创建信号量
    key=ftok(".",99);
    if(key==-1)printf("ftokerr:%m\n"),exit(-1);
    /*创建信号量*/
    semid=semget(key,1/*信号量数组个数*/,IPC_CREAT|IPC_EXCL|0666);
    /*得到信号量 */
    //semid=semget(key,1/*信号量数组个数*/,0);
    if(semid==-1)printf("semgeterr:%m\n"),exit(-1);
    printf("semid%d\n",semid);
    //2.初始化信号量
    //2.1定义一个联合体
    //2.2定义一个初始化的值
    v.val=2;
    r=semctl(semid,0,SETVAL,3);
    //r=semctl(semid,0,SETVAL,v);
    if(r==-1)printf("初始化失败\n"),exit(-1);
    //3.对信号量进行阻塞操作
    //3.1定义操作
    op[0].sem_num=0;//信号量的下标
    op[0].sem_op=-1;//信号量操作单位与类型
    op[0].sem_flg=0;
    while(1)
    {
       r=semop(semid,op,1);
       printf("解除阻塞!\n");
    }
    //4.删除信号量(可选)
    semctl(semid,0,IPC_RMID);
}

结果为:

打印三次接触阻塞后程序便进入阻塞

总结:因为我们是采用第二种方法r=semctl(semid,0,SETVAL,3);来做semctl

sem_op:前提条件信号量是unsigned shortint;

不能<0.

-:够减,则semop马上返回,不够减,则阻塞.

+:执行+操作

0:判定信号量>0,则阻塞,直到为0

因为我们初始化为3,每次进行-1操作,发现3次是够减的,所以返回三次,然后程序进入阻塞状态

如果还是不懂,那么我们贴上去semB.c的操作,在程序进入阻塞后,我们用semB.c的程序对信号量每隔1S进行+操作,预期效果是A程序每隔1s返回一次

SemB.c源码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
 
//2.1 定义一个联合体
union semun {
    int val;
    structsemid_ds *buf;
    unsignedshort  *array;
    structseminfo  *__buf;
};
 
int main()
{
    key_tkey;
    intsemid;
    intr;
    unionsemun v;//2.2 定义一个初始化的值
    structsembuf op[1];
    //1.创建信号量
    key=ftok(".",99);
    if(key==-1)printf("ftokerr:%m\n"),exit(-1);
    /*创建信号量*/
    //semid=semget(key,1/*信号量数组个数*/,IPC_CREAT|IPC_EXCL|0666);
    /*得到信号量 */
    semid=semget(key,1/*信号量数组个数*/,0);
    if(semid==-1)printf("semgeterr:%m\n"),exit(-1);
    printf("semid%d\n",semid);
    
    //3.对信号量进行阻塞操作
    //3.1定义操作
    op[0].sem_num=0;//信号量的下标
    op[0].sem_op=1;//信号量操作单位与类型
    op[0].sem_flg=0;
    while(1)
    {
       r=semop(semid,op,1);
       sleep(1);
    }
    //4.删除信号量(可选)
}

运行下,达到了我们预期的效果

同时来查看下ipcs的信号量数据看下

发现就是我们创建爱你的一个,并且semid是B程序打印的

三.消息队列

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。  每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。

Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

函数说明

int msgget(key_t, key, int msgflg);

第一个参数为消息队列的key,第二个参数建议

//创建IPC_CREAT|IPC_EXCL|0666

//打开

它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.

int msgsnd(int id,//消息队列ID

           constvoid *msg,//要发送消息

           size_tlen,//消息的长度

           intflags//发送消息的方式,建议为0

           );

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

第二个参数的消息有固定的格式

       4字节:表示消息的类型

       若干字节:消息内容。

第三个参数:

       消息的大小,注意不包含类型的4个字节

int msgrcv(int msgid, void *msg_ptr,size_t msg_st, long int msgtype, int msgflg);

int msgctl(int msgid, int command, struct msgid_ds *buf); 

分别有msgA.c msgB.c程序

#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
    long type;
    char data[32];
};
int main()
{
    key_t key;
    int msgid;
    int i;
    // 1.创建消息队列
    key=ftok(".",254);
    if(key==-1)printf("ftokerror:%m\n"),exit(-1);
    msgid=msgget(key,IPC_CREAT|IPC_EXCL|0666);
    if(msgid==-1)printf("get err:%m\n"),exit(-1);
    
    // 2.构建消息
    struct msgbuf msg;
    
    // 3.发送消息
    for(i=1;i<=10;i++)
    {
       msg.type=1;
       sprintf(msg.data,"message:%d",i);
       msgsnd(msgid,&msg,sizeof(msg.data),0);
    }
    // 4.删除队列
    //msgctl(msgid,IPC_RMID,0);
}

msgB.c源码

#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
    long type;
    char data[32];
};
int main()
{
    key_t key;
    int msgid;
    int i;
    // 1.得到消息队列
    key=ftok(".",254);
    if(key==-1)printf("ftokerror:%m\n"),exit(-1);
    msgid=msgget(key,0);
    if(msgid==-1)printf("geterr:%m\n"),exit(-1);
    
    // 2.构建消息
    struct msgbufmsg;
    
    // 3.接受消息
    while(1)
    {
       bzero(&msg,sizeof(msg));
       msg.type=1;
       msgrcv(msgid,&msg,sizeof(msg.data),1,0);
       printf("%s\n",msg.data);
    }
    
}

先运行A,发现消息队列已经有了,并且没有取出

此时运行B

然后查看下消息队列


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wireless_Link

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

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

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

打赏作者

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

抵扣说明:

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

余额充值