进程间通信(IPC)之共享存储、消息队列

1.共享存储

出于安全考虑,进程空间一般都是相互独立的,进程运行期间一般不能访问其他进程的空间。共享内存允许多个进程共享一个由内核提供的存储空间

在读取访问这块内存期间,不需要内核的接入,因此共享内存的速度更快,是效率最高的IPC。另一方面,其没有内核干预同步,因此需要使用同步互斥工具(如PV操作,后面章节会单独讲)。

使用共享内存的一般步骤:

1)shmget()获取共享内存的ID

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
    功能:用于创建或获取一段由内核提供的共享内存
    参数:
    key 键值,即ftok成功的返回值
    size 共享内存的大小,单位为字节,会自动向上取整为PAGE_SIZE的整数倍
    shmflg 创建标志与权限,创建标志为IPC_CREAT, 权限用八进制表示
                一般为 IPC_CREAT|0666
    返回:
        成功共享内存的ID
        失败返回-1,同时设置errno

2)shmat()将共享内存映射到进程的用户空间

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
       功能:映射指定的共享内存到进程的用户空间
       参数:
            shmid  要映射的共享内存的ID
            shmaddr 一般给NULL,表示由系统自动选择一块合适的用户空间进行映射 
            shmflg 映射标志,给0即可,表示使用默认的权限,即与shmget的权限一致
        返回:
            成功返回一个映射好的用户空间的内存地址,操作该地址空间,就是在操作共享内存
            失败返回(void*)-1,同时设置errno

3)memcpy()像其它普通内存一样的使用

4)shmdt()当不再使用时,解除映射

   int shmdt(const void *shmaddr);
   功能:用于解除映射的共享内存
   参数:shmaddr 待解决的内存地址
   返回:
        成功返回0
        失败返回-1,同时设置errno

5)shmctl()最后没有进程使用这块共享内存时,应删除它。

6)示例代码

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
​
int main(int argc, char const *argv[])
{
    printf("page_size = %d \n", getpagesize());
    key_t key = ftok(".", 110); //根据路径和标识符获取键值
    if (key == -1)
    {
        perror("ftok error");
        exit(1);
    }
​
    int shmid = shmget(key, 1024, IPC_CREAT | 0666);
    if (shmid == -1)
    {
        perror("shmget error");
        exit(1);
    }
    printf("key: %d, id: %d \n", key, shmid);
​
    char *addr = shmat(shmid, NULL, 0);//返回共享内存的地址
    if (addr == (void*)-1)
    {
        perror("shmat error");
        exit(1); 
    }
    printf("addr = %p\n", addr);
    const char* msg = "hello world";
    memcpy(addr, msg, strlen(msg)); //使用
    
    getchar();
    return 0;
}
//在linux中现在可以输入ipcs来查看创建好的共享内存段

2.消息队列

若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递实现进程通信。

消息队列、共享内存、信号量统称为system-V IPC ,是由同一个组织开发的,接口非常相似,这一组IPC都使用一种名为 key 的键值来唯一标识系统中的多个进程,使用这个key可以操作这些进程对象通信。

使用消息队列的一般步骤:

1)ftok()获取key值

#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:把一个存在且能访问的路径名 和 项目ID(只使用这个整数的低8位) 转换成一个IPC对象的key
返回:
    成功返回一个key, 用来创建/获取一个IPC对象
    失败返回-1,同时设置errno

2)发送者

msgget()用于创建或获取一个消息队列;

#include <sys/msg.h>
int msgget(key_t key, int msgflg);
    功能:用于创建或获取一个消息队列
    参数:
        key 用于创建或获取消息队列的key, 即ftok成功的返回值
        msgflg 创建或获取标志与权限
            IPC_CREAT  消息队列不存在,则创建,必须与权限按位或 才能一起创建一个新的消息队列
                        如果消息队列已存在,则忽略该标志
            IPC_EXCL    如果消息队列存在,则报错EEXIST
        说明: 权限只有读写,执行权限 无效,一般用8进制表示
    返回:
        成功返回消息队列的ID
        失败返回-1,同时设置errno

msgsnd()将数据打包成一个带有标识的特殊结构体,写入消息队列。

#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能: 发送消息到消息队列
参数: 
    msqid 消息队列的ID,即msgget成功的返回值,代表要操作的消息队列
    msgp 要发送的消息结构的首地址
    msgsz 要发送消息的大小,单位为字节(不包括消息类型的大小)
    msgflg 标志,要么为0表示阻塞,要么为IPC_NOWAIT,表示不阻塞
返回:
    成功返回0
    失败返回-1,同时设置errno
​
说明:
    消息结构如下:(要理解这种封装消息的做法)
    struct msgbuf {
        long mstype; // 必须大于0,可自定义,表示某种含义,主要用于区分不同的消息及作为从消息队列中挑选消息的依据
        char data[1]; // 这是要发送的数据缓冲区,大小可自定义。一般是数组,也可以是其它结构。
    };

发送者示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>
​
struct msgbuf
{
    long mtype;
    char data[1024];
};
​
int main(int argc, char const *argv[])
{
    // ftok的第一个参数为路径名,要求存在且能访问,此处使用的是家目录
    // ftok的第二个参数称为项目ID,一般取值范围1-255,可自己定,但不能为0
    key_t key = ftok(".", 100);
    if (key == -1)
    {
        perror("ftok error");
        exit(1);
    }
    printf("key = %d\n", key);
​
    int msgid = msgget(key, IPC_CREAT|0666);
    if (msgid == -1)
    {
        perror("msgget error");
        exit(1);
    }
    printf("msgid = %d \n", msgid);
​
    struct msgbuf buf;
    buf.mtype = 100;
    strcpy(buf.data, "hello msg queue");
    int r = msgsnd(msgid, &buf, strlen(buf.data), 0);
    printf("msg snd r: %d \n", r);
​
    return 0;
}

3)接收者

msgget()获取消息队列的ID;

msgrcv()从消息队列中读取指定标识的消息。

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能: 从消息队列接收消息
参数: 
    msqid 消息队列的ID,即msgget成功的返回值,代表要操作的消息队列
    msgp  内存缓冲区首地址,用于保存接收到的消息,即一个结构体变量的地址
    msgsz 待接收消息的大小,单位为字节(不包括消息类型的大小)
    msgtyp 消息类型,有以下情况:
        = 0 表示读消息队列中的第一条消息
        > 0 表示读消息队列中的第一条类型为msgtyp的消息
        < 0 表示读消息队列中消息类型小于 msgtyp 的绝对值的 第一条消息
    msgflg 标志,要么为0表示阻塞,要么为IPC_NOWAIT,表示不阻塞
​
返回值
    成功返回实际读取的消息大小
    失败返回-1,同时设置errno

接收者示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <sys/ipc.h>
​
struct msgbuf
{
    long mtype;
    char data[1024];
};
​
int main(int argc, char const *argv[])
{
    // ftok的第一个参数为路径名,要求存在且能访问,此处使用的是家目录
    // ftok的第二个参数称为项目ID,一般取值范围1-255,可自己定,但不能为0
    key_t key = ftok(".", 100);
    if (key == -1)
    {
        perror("ftok error");
        exit(1);
    }
    printf("key = %d\n", key);
​
    int msgid = msgget(key, IPC_CREAT|0666);
    if (msgid == -1)
    {
        perror("msgget error");
        exit(1);
    }
    printf("msgid = %d \n", msgid);
​
    struct msgbuf buf;
    int r = msgrcv(msgid, &buf, sizeof(buf.data), 0, 0);
    printf("msg recv bytes: %d \n", r);
    printf("recv buf: %s \n", buf.data);
    
    // bzero(buf.data, sizeof(buf.data));
    // memset(buf.data, 0, sizeof(buf.data));
    // buf.data[r] = '\0';
​
    msgctl(msgid, IPC_RMID, NULL);
    
    return 0;
}

执行结果

当发送者与接收者都不再使用消息队列时,应msgctl() 删除以释放系统资源。

 #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    功能:用控制消息队列,如读取信息、设置属性、删除等
    参数: 
        msqid 要操作的消息队列的ID
        cmd  控制指令
                IPC_RMID  表示删除
                ...
        buf 结构体指针,如果指令为删除,则不需要此参数,写NULL即可。
    返回:
        成功返回0
        失败返回-1
​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值