进程(三)(重点):共享内存(使用最多,效率最高)+信号量(信号灯)

一、IPC进程间通信相关命令

ipcs:查看当前系统中已经使用的IPC进程间通信(1.2.3)
		ipcs -q:单独查看消息队列
		ipcs -m: 单独查看共享内存
		ipcs -s:单独查看信号灯集
		
ipcrm:删除IPC进程间通信
		ipcrm -q msqid   删除消息队列号为msqid的消息队列
		ipcrm -m shmid   删除共享内存号为shmid的共享内存
		ipcrm -s semid   删除信号灯集号为semid的信号灯集

二、键值

ftok函数原理解析

#include <sys/types.h>
#include <sys/ipc.h>
函数原型:
    key_t ftok(const char *pathname, int proj_id);

参数:
     pathname:已经存在的文件路径以及名字 
     proj_id:任意一个字符或者数字,它的低八位有效   1    'a'
返回值:    
    成功返回获取的key键值, 失败返回-1
    
使用:若两个进程间需要建立通信,需要使用相同的键值。即两个参数相同。

三、共享内存

1、共享内存简介

  • 共享内存:是所有进程间通信效率最高的一种方式,通过建立物理内存地址映射,而不是对内核操作
  • 在分配共享内存的时候,分配的共享内存大小的是 4K 的整数倍。
    在这里插入图片描述

1.1、共享内存的优缺点

  • 优点:相比于管道而言,共享内存不仅能够用于非父子进程之间的通信,而且访问数据的速度也比管道要快。
    这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。
  • 缺点:共享内存本身不支持阻塞等待操作。
    由于当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。
    (常常配合信号量(信号灯集)一起使用)

2、共享内存API

2.1、步骤

(零).获得键值-ftok
-----------------------------------------------------------------------------------------------------------------
(一).创建—shmget

(二).连接—shmat (映射共享内存到当前进程)

(三).分离—shmdt(取消映射)

(四).销毁—shmctl

2.2、详细介绍:函数shmget()、shmat()、shmdt()、shmctl()

#include <sys/ipc.h>
       #include <sys/shm.h>

函数原型:
       int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
参数:
       key : 键值
       size:创建的共享内存的大小 --4K的整数倍
       shmflg: 共享的标志位
           IPC_CREAT: 创建 | 权限(0666), 如果共享内存已经存在,就返回共享内存号
           IPC_EXCL:  如果存在直接报错返回                  
返回值:成功返回得到的共享内存号,失败返回-1

-----------------------------------------------------------------------------------------------------------------

函数原型:
       void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存到当前进程
参数:
    shmid: 共享内存号
    shmaddr: 让系统分配空间,一般填为NULL
    shmflg: 对共享内存的操作方式
        SHM_RDONLY: 只读
        0         : 读写(一般填0)
返回值:成功返回共享内存的首地址,失败返回(void *)-1 

-----------------------------------------------------------------------------------------------------------------  

函数原型         
       int shmdt(const void *shmaddr);
功能:取消映射
参数:    
    shmaddr: 指向共享内存的指针
返回值:
        成功返回0 失败-1
-----------------------------------------------------------------------------------------------------------------        

函数原型:        
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:一般用于删除共享内存
参数:
    shmid: 共享内存号
    cmd  : 操作的命令
        IPC_STAT: 获取属性
        IPC_SET : 设置属性
        IPC_RMID: 删除共享内存
    @buf: 共享内存属性的结构体  如果第二个参数为IPC_RMID  该参数直接传NULL
返回值:
    成功返回0 失败-1

3、示例:使用共享内存实现文件拷贝(共享内存)

3.1、实现注意:文件的大小为4k个字节的整数倍。

3.2、向共享文件写

#include <stdio.h>
#include <sys/ipc.h>
#include<sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>


//#define FILE_SIZE 4096	//4k个字节的整数倍

//获取文件大小(属性),并使其成为4096(4k字节)大小的整数倍
int getFileSize(char *filename){
    struct stat statbuf;
    stat(filename, &statbuf);	//获取文件的属性
    int size = statbuf.st_size;
    while((size % 4096) != 0){
        size++;
    } 
    return size;
}

int main(int argc, char* argv[]){
    key_t key;                            //键值
    int shmid;                            //共享内存号
    int Len;                            //文件大小
    FILE *fp = fopen(argv[1], "r+");    //文件描述符
    void *p_shmAddr;                    //将会指向共享内存的首地址

    
    //传参判断
    if(argc != 2){
        printf("format: %s <file name>\n",argv[0]);return -1;
    }
    //1. 键值
    if((key = ftok("/home/aaa", 1)) == -1){
        perror("ftok error");return -1;
    }
    
    //2. 获取文件大小
    Len = getFileSize(argv[1]);		
    
	//将文件的大小放入一个文件dataSize
	//通过创建一个中间文件,传递数值
	FILE *fp_datasize = fopen("./dataSize", "w");
	fwrite(&Len, 1, sizeof(Len), fp_datasize);
	printf("file size = %d\n", Len);
	fclose(fp_datasize);
    
    //3.创建共享内存
    if((shmid = shmget(key, Len, IPC_CREAT|0666)) == -1){
        perror("shmget:"); return -1;
    }
    
    //4. 获取共享内存的首地址(映射)
    if((p_shmAddr = shmat(shmid, NULL, 0)) == (void *)-1){
        perror("shmat: ");return -1;
    }

    //5. 从文件中读取数据放在共享内存中
    //定义一个临时指针,指向共享内存的首地址
    char *p_tmp = (char *)p_shmAddr;
    int readLen;
    while( readLen = fread(p_tmp, 1, FILE_SIZE,fp) ){
        p_tmp += readLen;
    }
    fclose(fp);
    
    shmdt(p_shmAddr);

    return 0;

}

3.3、从共享文件读出数据

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

//#define FILE_SIZE 4096

int main(int argc,char *argv[]){
    key_t key;                        //键值
    int shmid;                        //共享内存号
    void *p_shmAddr;                //将指向共享内存的首地址
    int dataLen;                    //存放文件大小
    FILE *fp = fopen("./2.jpg","w");
    
    //1. 键值
    if((key = ftok("/home/aaa", 1)) == -1){
        perror("ftok:");return -1;
    }

    //2. 获取文件大小
    FILE *fp_dataSize = fopen("./dataSize", "r");
    fread(&dataLen, 1, sizeof(dataLen), fp_dataSize);
    printf("file size = %d\n", dataLen);
    fclose(fp_dataSize);
    
    //3. 创建共享内存或者是拿到共享内存号
    if((shmid  = shmget(key, dataLen, IPC_CREAT|0666)) == -1){
        perror("shmget :");return -1;
    }
    
    //4. 获取共享内存首地址
    if((p_shmAddr = shmat(shmid, NULL, 0)) == (void *)-1){
        perror("shmat:");return -1;
    }
            
    //5. 从共享内存中读取数据存放在拷贝文件中
    char *p_tmp = (char *)p_shmAddr;                //定义临时指针 指向共享内存首地址
    int writeLen;
    while( dataLen ){
        writeLen = fwrite(p_tmp, 1, FILE_SIZE, fp);
        p_tmp += writeLen;
        dataLen -= writeLen;
    }
    fclose(fp);            //关闭文件指针
    shmdt(p_shmAddr);
       //取消映射
    shmctl(shmid, IPC_RMID, NULL);//删除共享内存
    
    return 0;
}

四、信号量

1、什么是信号量

  • 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有
  • 最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。
    1代表资源可用,0代表资源不可用,如果获取到信号灯值为0的信号灯的资源时,就会阻塞休眠等待。

1

2、信号量操作

信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,其他进程不能使用,其他进程挂起**阻塞等待**V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

五、信号量的API(semget / semctl / semop)

1、函数semget():获取信号灯集号

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

功能:得到信号灯集号(整体的编号),键值相同的通信
函数原型:
       int semget(key_t key, int nsems, int semflg);
参数:
    @key   : 键值
    @nsems : 指定需要的信号量数目( 信号灯集中信号灯的个数 )
    @semflg: 创建信号灯集的参数和权限
        IPC_CRAET | 0666	(一般使用)
        IPC_CREAT | IPC_EXCL | 0666		(EXCL:不存在创建,存在报错)
返回值:
    成功返回信号灯集号semid,失败返回-1

2、函数semctl():(设置、获取)信号灯的(值、属性)、删除信号灯集

功能: 
		设置、获取 信号灯的值
		设置得到信号灯的属性(不常用)
		删除信号灯集
函数原型:
       int semctl(int semid, int semnum, int cmd, ...);
功能:控制信号灯       
参数:
    @semid : 信号灯集号(semget的返回值/、整体的编号)
    @semnum: 信号灯的编号(可以有很多个)
    @cmd   : 命令
        SETVAL: 设置信号灯的值--> 第四个参数填写数值***
      	GETVAL: 得到信号灯的值--> 第四个参数不填***
        IPC_STAT: 得到属性
        IPC_SET: 设置属性
       IPC_RMID: 删除信号灯集***
    @...  : 类型为联合体
        union semun {
           int              val;    /* SETVAL */***
           struct semid_ds *buf;    /* IPC_STAT, IPC_SET */

       };
返回值:
    失败返回-1,成功的话要根据第三个参数确定返回值
    如果cmd为GETVAL: 成功返回得到的信号灯的值
    如果cmd为其他:   成功返回0
    
例子:如果要设置0号信号灯的值为1的话:
    semctl(semid, 0, SETVAL, 1);    

3、函数:semop():操作信号灯:p(-1)v(+1)操作,是否阻塞(在编写p或v操作函数时使用)

函数原型:
    int semop(int semid, struct sembuf *sops, size_t nsops);
功能:信号灯集中的信号灯的操作函数
参数:
    @semid : 信号灯集号
    @sops  : 结构体sembuf的指针变量,(灯的编号,pv操作,是否阻塞)
           unsigned short sem_num;  /* 信号灯的编号*/
           short          sem_op;   /* 信号量操作数P/V (P:-1 申请资源  V:+1 释放资源) */
           short          sem_flg;  /* 操作权限0:阻塞     IPC_NOWAIT: 非阻塞等待 */
    @nsops : 要操作的计数器数量,对几个信号灯进行操作
返回值:
    成功返回0 失败返回-1

六、示例(共享内存+信号量):进程间通信:实现数据发送与接收

sem.h文件

#ifndef __SEM_H__
#define __SEM_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>

/* 创建以及初始化信号量 */
int init_sem(int nsems);

/*P操作申请资源*/
int P(int semid, int wihch);

/*V操作释放资源*/
int V(int semid, int wihch);

/* 删除信号量*/
int semdt(int semid);

#endif

sem.c文件

#include "sem.h"
union semnu{
    int val;
    struct semid_ds *buf;
};

/* 
功  能:初始化信号灯值
参  数:
	semid:信号集号
	which:哪一个信号灯(进程或线程的编号)
	value:设置信号灯的值
调   用:内部调用 */
int init_sem_value(int semid, int wihch, int value){
    union semnu buf = {
        .val = value,
    };
    if(semctl(semid, wihch, SETVAL, buf) == -1){		//SETVAL可以直接写value,而不调用联合体(共用体)
        perror("semctl");return -1;
    }
    return 0;
}

/* 
功  能:创建以及初始化信号量
参  数:
	nsems: 一共几个信号灯(几个进程或是线程)
	
返回值:成功返回semid 失败返回-1
调  用: 外部调用
*/
int init_sem(int nsems){
    //创建键值
    key_t key_sem;
    if((key_sem = ftok("/home/aaa", 1)) == -1){
        perror("ftok");return -1;
    }
    
    //获取或者初始化信号灯集
    int semid;
    if((semid = semget(key_sem, nsems, IPC_CREAT | IPC_EXCL | 0666)) == -1){
        if(errno == EEXIST){
            semid = semget(key_sem, nsems, IPC_CREAT | 0666);
        }else{
            perror("semget :");
            return -1;
        }
    }else{
        //如果上面的if没有报错 说明是第一次创建信号量
        //初始化信号量
        init_sem_value(semid, 0, 1);
        for(int i = 1; i< nsems; i++){
        init_sem_value(semid, i, 0);
        }
    }
    return semid;
}

/*
功  能:P操作申请资源
参  数:
	semid: 信号灯集号
	which: 对哪一个信号灯进行P操作(进程或线程的编号)
	
返回值:0 / -1
调  用;外部调用
*/
int P(int semid, int wihch){
    struct sembuf buf = {
        .sem_num = wihch,  /* 信号量序号(信号灯的编号) */
        .sem_op  = -1,   /* 信号量操作数P:-1 申请资源 */
        .sem_flg = 0,  /* 操作权限0:阻塞             IPC_NOWAIT: 非阻塞等待 */
    };
    if(semop(semid, &buf, 1) == -1){
        perror("semop P");
        return -1;
    }
    return 0;
}

/*
功  能:V操作释放资源
参  数:
		semid: 信号灯集号
		which: 对哪一个信号灯进行P操作(进程或线程的编号)
		
返回值:0 / -1
调  用:外部调用
*/
int V(int semid, int wihch){
    struct sembuf buf = {
        .sem_num = wihch,  /* 信号量序号(信号灯的编号) */
        .sem_op  = 1,   /* 信号量操作数V:+1 释放资源 */
        .sem_flg = 0,  /* 操作权限0:阻塞             IPC_NOWAIT: 非阻塞等待 */
    };
    if(semop(semid, &buf, 1) == -1){
        perror("semop V");
        return -1;
    }
    return 0;
}

/* 
    功  能:删除信号量
    参  数:semid: 要进行删除的信号灯集号
    返回值:0 / -1
	调  用:外部调用
*/
int semdt(int semid){
    semctl(semid, 0, IPC_RMID);
    return 0;
}

write.c文件

#include "sem.h"
#define SHMSIZE 4096

int main(){
    key_t key_shm;        //键值
    int shmid;            //共享内存号  标识符
    char *addr;            //将指向共享内存的首地址
    int semid;            //信号量标识符(信号灯集号)
    
    //0. 创建信号量并初始化
    semid = init_sem(2);
    
    //1. 创建键值
    if((key_shm = ftok("/home/aaa", 1)) == -1){
        perror("ftok");return -1;
    }
    
    //2. 创建共享内存
    if((shmid = shmget(key_shm, SHMSIZE, IPC_CREAT | 0666)) == -1){
        perror("shmget");return -1;
    }

    //3. 将共享内存映射到当前进程空间 拿到共享内存首地址
    if((addr = shmat(shmid, NULL, 0)) == (void *)-1){
        perror("shmat ");return -1;
    }
    
    //4. 操作共享内存
    while(1){
        P(semid, 1);
        printf("input >  ");
        fgets(addr, SHMSIZE, stdin);
        addr[strlen(addr)-1] = '\0';
        V(semid, 0);
        
        if(strncmp(addr, "quit", 4) == 0)
            break;
    }
    
    //5. 取消映射
    if((shmdt(addr)) == -1){
        perror("shmdt");return -1;
    }
    
    //6. 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    
    //7. 删除信号量
    semdt(semid);
    
    return 0;
}

read.c文件

#include "sem.h"
#define SHMSIZE 4096

int main(){
    key_t key_shm;        //键值
    int shmid;            //共享内存号  标识符
    char *addr;            //将指向共享内存的首地址
    int semid;            //信号量标识符(信号灯集号)
    
    //0. 创建信号量并初始化
    semid = init_sem(2);
    
    //1. 创建键值
    if((key_shm = ftok("/home/aaa", 1)) == -1){
        perror("ftok");return -1;
    }
    
    //2. 创建共享内存
    if((shmid = shmget(key_shm, SHMSIZE, IPC_CREAT | 0666)) == -1){
        perror("shmget");return -1;
    }

    //3. 将共享内存映射到当前进程空间 拿到共享内存首地址
    if((addr = shmat(shmid, NULL, 0)) == (void *)-1){
        perror("shmat ");return -1;
    }
    
    //4. 操作共享内存
    while(1){
        P(semid, 0);
        printf("data = %s\n", addr);
        V(semid, 1);

    
        if(strncmp(addr, "quit", 4) == 0)
            break;
    }
    
    //5. 取消映射
    if((shmdt(addr)) == -1){
        perror("shmdt");return -1;
    }
    
    //6. 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    
    //7. 删除信号量
    semdt(semid);
    
    return 0;    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

好好睡觉好好吃饭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值