文章目录
一、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的信号灯集
二、键值
#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的信号灯的资源时,就会阻塞休眠等待。
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;
}