IPC-内存映射、信号量、消息队列

内存映射

在这里插入图片描述
在这里插入图片描述

常见接口

mmap: 创建一个新映射

在这里插入图片描述
在这里插入图片描述

munmap :解除映射区域

在这里插入图片描述

实例:利用mmap打印文件的指定内容

描述

在这里插入图片描述
在这里插入图片描述

思路

  1. 获取文件大小,判断offset和lenth是否合理
  2. 根据参数offset,length,fd,实现私有文件映射mmap(页对齐)
  3. 调用write将内存中的映射文件写入stdout
  4. 解除映射区域

代码

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 定义处理出错函数
#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0) 

int main(int argc, char *argv[]) {
    char *addr;
    int fd;
    struct stat sb; 
    off_t offset, pa_offset; 
    size_t length;
    ssize_t s;

    if (argc < 3 || argc > 4) {
        fprintf(stderr, "%s file offset [length]\n", argv[0]); 
        exit(EXIT_FAILURE);
    }

    fd = open(argv[1], O_RDONLY); // 只读
    if (fd == -1)
        handle_error("open");

    if (fstat(fd, &sb) == -1)             /* To obtain file size */
        handle_error("fstat"); /* 获得文件大小也可才用fseek,ftell*/

    offset = atoi(argv[2]);
    pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1); // 位运算, 页取整,4k一页
            /* offset for mmap() must be page aligned */
    if (offset >= sb.st_size) {
        fprintf(stderr, "offset is past end of file\n");
        exit(EXIT_FAILURE);
    }

    if (argc == 4) {
        length = atoi(argv[3]);
        if (offset + length > sb.st_size)
            length = sb.st_size - offset;
                /* Can't display bytes past end of file */

    } else {    /* No length arg ==> display to end of file */
        length = sb.st_size - offset; // 缺省
    }
    /* 创建一个私有映射, 对于映射区域内容可读取,参数默认为NULL
     * 内核会将fd对应的文件选择一个合适的地址并将虚拟首地址返回
     * 给addr, 文件映射的起点是分页大小的倍数,文件映射的长度是
     * (offset - pa_offset + lenth)*/
    addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
                MAP_PRIVATE, fd, pa_offset);
    if (addr == MAP_FAILED)
        handle_error("mmap");
    /* offset - pa_offset : 分页后,偏移量相对页首的相对偏移量 */ 
    /* 调用mmap省略了read函数,减少一次内核空间和用户空间的一次传输
    ,且内核空间和用户空间共享一个缓存区*/
    s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
    if (s != length) {
        if (s == -1) handle_error("write");
        fprintf(stderr, "partial write");
        exit(EXIT_FAILURE);
    }
    // 解除映射区域
    munmap(addr, length + offset - pa_offset);
    close(fd);

    exit(EXIT_SUCCESS);
}

mmap与快速IPC

在这里插入图片描述

思考

  1. 共享内存与内存映射有什么区别和联系?
  2. mmap为什么适用于大型文件执行重复随机访问?

信号量

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

常见接口

在这里插入图片描述

semget : 创建或打开一个信号量集

在这里插入图片描述

semop : 获取或释放共享资源

在这里插入图片描述

semctl : 初始化或删除信号量集

在这里插入图片描述
在这里插入图片描述

示例: 用信号量实现PV操作

描述

洗衣房只有3个洗衣机,但是有5个人的准备使用,每个人都需要用3次,每一次用完都需要释放洗衣机的使用资源。释放资源后,进程需要竞争,抢到继续使用,没抢到则阻塞,直到所有人都使用洗衣机3次。

在这里插入图片描述

思路

  1. 父进程进行创建,初始化信号量:semget, semclt
  2. 子进程负责利用和释放资源: fork,semop
  3. 父进程负责收尸与删除信号量:semclt, wait

代码

/*************************************************************************
	> File Name: 1.sem.c
	> Mail: 1136984246@qq.com
 ************************************************************************/
#include "head.h"
#define VALUE 3 // 定义资源数

/* caller must define this union*/
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) */
};

int create_sem() {
    key_t key = ftok(".", 2021); /*创建键key,用于用于标识IPC对象*/
    int sem_id;
    /*根据key创建或关联一个信号量数目为1的集合,权限是666, P操作需要写权限,V操作需要读权限*/
    if ((sem_id = semget(key, 1, IPC_CREAT | 0666)) < 0) {
        perror("semget");
        return -1;
    }
    return sem_id; // 与semget的返回值保持一致
}

 /*初始化过程只需要一个进程完成,否则会出现竞争*/
int init_sem(int sem_id) {
    union semun arg; 
    arg.val = VALUE; /* 有VALUE个共享资源*/ 
    return semctl(sem_id, 0, SETVAL, arg); // 函数原型中...随着cmd
    /*将sem_id指定的第0个信号量初始化为arg.val*/
}

int sem_P(int sem_id) {
    struct sembuf sbuff;
    sbuff.sem_num = 0; /*集合中的第0个信号量执行操作*/
    sbuff.sem_op = -1; /*利用资源,减少信号量*/
    sbuff.sem_flg = SEM_UNDO; /*进程终止时撤销该进程的调整总和*/
    /**/
    if (semop(sem_id, &sbuff, 1) == -1) {
        perror("shmop");
        return -1;
    }
    return semctl(sem_id, 0, GETVAL); /*返回P后的信号量值semval*/
}

int sem_V(int sem_id) {
    struct sembuf sbuff;
    sbuff.sem_num = 0;
    sbuff.sem_op = 1; /*释放资源,增加信号量*/
    sbuff.sem_flg = SEM_UNDO; 
    if (semop(sem_id, &sbuff, 1) == -1) {
        perror("shmop");
        return -1;
    }
    return semctl(sem_id, 0, GETVAL);
}

int main(int argc, char **argv) {
    int sem_id;
    if ((sem_id = create_sem()) < 0) {
        perror("create_sem()");
        exit(1);
    }
    
    if (init_sem(sem_id) < 0) {
        perror("init_sem");
        exit(1);
    }
    
    int cnt = 0;
    unsigned short semval;
    int child_num;
    pid_t pid;
    for (int i = 1; i <= 5; i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
            exit(1);
        }
        if (pid == 0) {
            child_num = i;
            break;
        }
    }

    if (pid == 0) {
        while (1) {
            semval = sem_P(sem_id);// P:使用资源,信号量减1
            // printf("<P> : semval = %d\n", semval);
            // 输出信号量的值
            cnt++; 
            printf("<Child[%d]> : cnt = %d\n", child_num, cnt);
            sleep(2);
            semval = sem_V(sem_id); // V: 释放资源,信号量加1 
            // printf("<V> : semval = %d\n", semval);
            if (cnt == VALUE) break;
        }
        exit(0);
    } else {
        for (int i = 0; i < 5; i++) {
            wait(NULL);
        }
    }
    // 删除信号量只能由一个进程完成
    if (semctl(sem_id, 0, GETVAL) == VALUE) {
        if (semctl(sem_id, 0, IPC_RMID) == -1) {
            perror("semctl");
            exit(1);
        }
    }
    return 0;
}

思考与拓展

哲学家进餐问题和生产者消费者模式
CompareAndSet(CAS)
如何用pv信号量确保共享内存的互斥和交替访问?

在这里插入图片描述

消息队列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

常见接口

msgget : 创建或打开一个队列

在这里插入图片描述

msgclt : 对消息队列进行删除,修改,初始化

在这里插入图片描述

msgsnd : 把新消息加入队列尾部

在这里插入图片描述

msgrcv : 从队列中取消息

在这里插入图片描述

用消息队列实现C/S的IPC

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

实例:利用消息队列收发信息

描述

在这里插入图片描述

在这里插入图片描述

思路

  1. usage函数: -s, -r, -t, -k : getopt, mode
  2. 创建消息队列
  3. 发送消息与接收消息

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

// mtext的大小
struct msgbuf {
    long mtype; /*根据mtype的值选择发送和接受的字段*/
    char mtext[80]; /*用于存储消息内容,自定义大小,可为零*/
};

// usage
static void usage(char *prog_name, char *msg) {
    if (msg != NULL)
        fputs(msg, stderr);

    fprintf(stderr, "Usage: %s [options]\n", prog_name);
    fprintf(stderr, "Options are:\n");
    fprintf(stderr, "-s        send message using msgsnd()\n");
    fprintf(stderr, "-r        read message using msgrcv()\n");
    fprintf(stderr, "-t        message type (default is 1)\n"); // 消息类型
    fprintf(stderr, "-k        message queue key (default is 1234)\n"); // 如何查看已有的key值?
    exit(EXIT_FAILURE);
}

static void send_msg(int qid, int msgtype) { // static:函数的定义域只在本文件
    struct msgbuf msg;
    time_t t;

    msg.mtype = msgtype; 

    time(&t); // 当前时间
    snprintf(msg.mtext, sizeof(msg.mtext), "a message at %s",
            ctime(&t)); 
    /*向消息队列中写入信息,当消息队列满时,直接返回EAGAIN,而不是阻塞到队列非满*/
    if (msgsnd(qid, (void *) &msg, sizeof(msg.mtext), IPC_NOWAIT) == -1) {
        /*msgsnd 放回值是0, 而不是发送的字节数write*/
        /*sizeof(msg.mtext) 而不是 sizeof(msg)*/
        perror("msgsnd error");
        exit(EXIT_FAILURE);
    }
    printf("sent: %s\n", msg.mtext);
}

static void get_msg(int qid, int msgtype) {
    struct msgbuf msg;
    /*从消息队列msgtype中删除一条信息并且将内容复制到msg指向的缓存区*/
    if (msgrcv(qid, (void *) &msg, sizeof(msg.mtext), msgtype,
               MSG_NOERROR | IPC_NOWAIT) == -1) {
            /* MSG_NOERROR: mtext超过可用空间进行截取而不是报错*/
        /*除了ENOMSG之外的错误*/ 
        if (errno != ENOMSG) {
            perror("msgrcv");
            exit(EXIT_FAILURE);
        }
        /*队列中某一个msgtype无消息且调用IPC_NOWAIT报错*/
        printf("No message available for msgrcv()\n");
    } else
        printf("message received: %s\n", msg.mtext);
}

int main(int argc, char *argv[]) {
    int qid, opt;
    int mode = 0;               /* 1 = send, 2 = receive */
    int msgtype = 1;
    int msgkey = 1234; // ftok
    // getopt
    while ((opt = getopt(argc, argv, "srt:k:")) != -1) {
        switch (opt) {
        case 's':
            mode = 1;
            break;
        case 'r':
            mode = 2;
            break;
        case 't':
            msgtype = atoi(optarg);
            if (msgtype <= 0)
                usage(argv[0], "-t option must be greater than 0\n");
            break;
            break;
        case 'k':
            msgkey = atoi(optarg);
            break;
        default:
            usage(argv[0], "Unrecognized option\n");
        }
    }
    
    if (mode == 0)
        usage(argv[0], "must use either -s or -r option\n");
    /*创建一个新的消息队列并返回标识符,不是文件描述符*/ 
    qid = msgget(msgkey, IPC_CREAT | 0666); 

    if (qid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    if (mode == 2)
        get_msg(qid, msgtype);
    else
        send_msg(qid, msgtype);

    exit(EXIT_SUCCESS);
}

思考

  1. 信号量用key, 消息队列和共享内存段都用key,key和fd的区别是什么?为什么说用消息队列不能用select, epoll等,反而需要两套IO操作?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值