系统编程 – 共享内存
一、共享内存的简介
共享内存与消息队列一样,都是IPC对象,所以在使用时必须要先获取key值。
共享内存是进程间通信数据传输效率最高的一种通信方式。
二、共享内存原理
在Linux系统下,程序被加载到内存成为进程时,系统会给这个进程分配它执行所需要的内存空间,但是对于软件应用层的进程来说,是不允许直接操作物理内存的,只能操作”虚拟内存”,虚拟内存与物理内存之间存在MMU(内存管理单元),实现虚拟内存与物理内存之间的映射关系。
三、共享内存的缺陷
共享内存的机制是直接从物理内存中申请一片空间作为两个进程共用的一片内存空间,进程在使用共享内存进行通信时,这片空间**完全脱离了Linux内核的管理**,此时进程间不好掌控这片空间的同步关系,共享内存在通信时,一般需要使用信号量来规范进程的同步。
四、相关API
1、 Shmget() //获取共享内存操作ID
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);
==》key:共享内存的key值
==》size:共享内存的大小(页大小的整数倍 PAGE_SIZE : 4096)
==》shmflg:标志 (如果不存在则创建 IPC_CREAT | 0777)
==》返回值:成功返回共享内存ID,失败返回 -1
2、shmat() //将共享内存映射到进程空间
SYNOPSIS
#include <sys/types.h>
#incude <sys/shm.h>
void *shmat(int shmid,const void *shmaddr,int shmflg); //映射空间
void shmdt(const void *shmaddr); //解除映射
==》shmid:共享内存操作ID
==》shmaddr:映射空间的首地址 NULL(系统自动寻找一片合适的空间)
==》shmflg:映射标志 0
==》返回值:成功返回映射空间的地址 ,失败返回 (void *)-1
例子1:
进程A和进程B使用共享内存进行通信,进程A往共享内存写入自己的PID,进程B读取共享内存数据,给目标进程发送信号,然后自己退出。
进程A
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <signal.h>
#include <unistd.h>
#define SHM_PATH "." //地址
#define PROJ_ID 5 //用户定义的ID号
#define SHM_SIZE 4096*2 //共享内存的大小
/*进程A*/
int main(int argc, char *argv[])
{
//1, 获取key值
key_t key = ftok(SHM_PATH, PROJ_ID);
if(-1 == key)
{
perror("ftok failed");
return -1;
}
//2, 获取共享内存的ID
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0777);
if(-1 == shmid)
{
perror("shmget failed");
return -1;
}
//3, 映射共享内存到进程空间
char *shmaddr = shmat(shmid, NULL, 0);
if(shmaddr == (void *)-1)
{
perror("shmat failed");
return -1;
}
//4, 写入自己的PID到共享内存中
printf("pid : %d\n", getpid());
*(pid_t *)shmaddr = getpid(); //把自己的PID赋值到共享内存开头
//5, 等待信号(退出信号)
pause();
//6, 解除映射
shmdt(shmaddr);
return 0;
}
进程B
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <signal.h>
#define SHM_PATH "." //地址
#define PROJ_ID 5
#define SHM_SIZE 4096*2
/*进程B*/
int main(int argc, char *argv[])
{
//1, 获取key值
key_t key = ftok(SHM_PATH, PROJ_ID);
if(-1 == key)
{
perror("ftok failed");
return -1;
}
//2, 获取共享内存的ID
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0777);
if(-1 == shmid)
{
perror("shmget failed");
return -1;
}
//3, 映射共享内存到进程空间
char *shmaddr = shmat(shmid, NULL, 0);
if(shmaddr == (void *)-1)
{
perror("shmat failed");
return -1;
}
//4, 获取共享内存中的数据
printf("pid : %d\n", *(pid_t *)shmaddr);
//5, 发送信号
kill(*(pid_t *)shmaddr, SIGINT);
//6, 解除映射
shmdt(shmaddr);
return 0;
}
进程间通信 – 信号量
一、信号量简介
信号量是一个IPC对象,使用时需要获取key值。信号量在进程间通信时,是专门用来规范进程对共享内存使用的,一般会结合共享内存使用。相当于使用共享内存时的”信号灯”。
信号量的本质是 P V操作
P操作 : 信号量的值减一 (当信号量的值为0,此时P操作会阻塞)
V操作 : 信号量的值加一
二、信号量与共享内存使用
三、相关API
1、semget() //获取信号量的操作ID
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
==》key: IPC对象的数组
==》nsems: 信号量的个数 --》信号量数组
==》semflg: IPC_CREAT | 0777 --》信号量不存在则创建
==》返回值: 成功返回信号量操作ID,失败返回-1
2、semop() //信号量 P/V 操作
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semmid,struct sembuf *sops,size_t nsops);
==》semid:信号量的操作ID
==》sops:信号量操作结构体指针
==》nsops:操作信号量数组中元素个数 -->1
struct sembuf{
unsigned short sem_num; /* semaphore number /
short sem_op; / semaphore operation /
short sem_flg; / operation flags */
};
==》sem_num:信号量值的下标(从0开始)
==》sem_op : 信号量操作数 p: -1; v:1
==》sem_flg : 操作标志位 0
3、semctl() //信号量初始值设置
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd,...)
==》 semid : 信号量操作ID
==》 semnum : 信号量下标
==》 cmd : 命令 SETVAL : 设置信号量的值
==》第四个参数: 如果第三个选择 SETVAL,那么第四个就是设置的信号量的值
例子2:
使用信号量与共享内存实现一个简单的通信功能。
A --> 发送一句话 B --> 读取, 发送一句话 A–> 读取,发送一句话
进程A
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#define SHM_PATH "." //地址
#define SEM_PATH "." //地址
#define ProJ_ID_SHM 10 //用户自定义ID
#define ProJ_ID_SEM 20 //用户自定义ID
#define SHM_SIZE 4096 //共享内存大小
/*共享内存与信号量 -- A*/
int main(int argc, char *argv[])
{
//1,获取共享内存和信号量的key值
key_t shm_key, sem_key;
shm_key = ftok(SHM_PATH, ProJ_ID_SHM);
sem_key = ftok(SEM_PATH, ProJ_ID_SEM);
if(-1 == shm_key || -1 == sem_key)
{
perror("ftok failed");
return -1;
}
//2,获取共享内存和信号量的操作ID
int shmid, semid;
shmid = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0777);
semid = semget(sem_key, 2, IPC_CREAT | 0777);
if(-1 == shmid || -1 == semid)
{
perror("get id failed");
return -1;
}
//3,映射共享内存到进程空间
char * shmaddr = shmat(shmid, NULL, 0);
if(shmaddr == (void *)-1)
{
perror("shmat failed");
return -1;
}
//4,设置信号量初始值 --> 0号信号量设置为1, 1号信号量设置为0
semctl(semid, 0, SETVAL, 1); //0号信号量设置为1
semctl(semid, 1, SETVAL, 0); //1号信号量设置为0
//5,进行通信
struct sembuf p_sops_0, v_sops_1;
p_sops_0.sem_num = 0;
p_sops_0.sem_op = -1;
p_sops_0.sem_flg = 0; //对0号信号量进行P操作结构体
v_sops_1.sem_num = 1;
v_sops_1.sem_op = 1;
v_sops_1.sem_flg = 0; //对1号信号量进行V操作结构体
while(1)
{
//1) 对信号量0进行p操作 (第2轮循环时在此阻塞)
semop(semid, &p_sops_0, 1);
//2) 输出共享内存内容 --> printf
//printf("[%d]readbuf:%s\n",getpid(), shmaddr);
//3) 清空共享内存
//memset(shmaddr, 0, SHM_SIZE);
//4) 从键盘获取数据写入共享内存
fgets(shmaddr, SHM_SIZE, stdin);
//scanf("%s", shmaddr);
//5) 对信号量1进行v操作 (把进程B的阻塞解除)
semop(semid, &v_sops_1, 1);
if(strcmp("EXIT\n", shmaddr) == 0)
break;
}
//6,解除共享内存映射, 删除信号量,删除共享内存
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL); //删除共享内存
semctl(semid, 0, IPC_RMID); //删除信号量
return 0;
}
进程B
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#define SHM_PATH "."
#define SEM_PATH "."
#define ProJ_ID_SHM 10
#define ProJ_ID_SEM 20
#define SHM_SIZE 4096
/*共享内存与信号量 -- B*/
int main(int argc, char *argv[])
{
//1,获取共享内存和信号量的key值
key_t shm_key, sem_key;
shm_key = ftok(SHM_PATH, ProJ_ID_SHM);
sem_key = ftok(SEM_PATH, ProJ_ID_SEM);
if(-1 == shm_key || -1 == sem_key)
{
perror("ftok failed");
return -1;
}
//2,获取共享内存和信号量的操作ID
int shmid, semid;
shmid = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0777);
semid = semget(sem_key, 2, IPC_CREAT | 0777);
if(-1 == shmid || -1 == semid)
{
perror("get id failed");
return -1;
}
//3,映射共享内存到进程空间
char * shmaddr = shmat(shmid, NULL, 0);
if(shmaddr == (void *)-1)
{
perror("shmat failed");
return -1;
}
//4,设置信号量初始值 --> 0号信号量设置为1, 1号信号量设置为0
semctl(semid, 0, SETVAL, 1); //0号信号量设置为1
semctl(semid, 1, SETVAL, 0); //1号信号量设置为0
//5,进行通信
struct sembuf p_sops_1, v_sops_0;
p_sops_1.sem_num = 1;
p_sops_1.sem_op = -1;
p_sops_1.sem_flg = 0; //对1号信号量进行P操作结构体
v_sops_0.sem_num = 0;
v_sops_0.sem_op = 1;
v_sops_0.sem_flg = 0; //对0号信号量进行V操作结构体
while(1)
{
//1) 对信号量1进行p操作 (第1轮循环时在此阻塞)
semop(semid, &p_sops_1, 1);
//2) 输出共享内存内容 --> printf
printf("[%d]readbuf:%s",getpid(), shmaddr);
if(strcmp("EXIT\n", shmaddr) == 0)
break;
//3) 清空共享内存
memset(shmaddr, 0, SHM_SIZE);
//4) 从键盘获取数据写入共享内存
//fgets(shmaddr, SHM_SIZE, stdin);
//scanf("%s", shmaddr);
//5) 对信号量0进行v操作 (把进程A的阻塞解除)
semop(semid, &v_sops_0, 1);
}
shmdt(shmaddr);
return 0;
}
例子3:
通过共享内存传输文件
进程A:文件接收端
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h> // /usr/include/sys/sem.h
#include <sys/ipc.h> // #include "stdio.h"
#include <signal.h>
#include <unistd.h>
#include <string.h>
#define SHM_PATH "/home/gec"
#define SEM_PATH "/home/gec"
#define SHM_ID 10
#define SEM_ID 20
#define SHM_SIZE 4096
/*文件接收端*/
int main(int argc, char *argv[])
{
//1,获取共享内存,信号量key值 ==> 文件名
key_t shm_key, sem_key;
shm_key = ftok(SHM_PATH, SHM_ID);
sem_key = ftok(SEM_PATH, SEM_ID);
if(-1 == shm_key || -1 == sem_key)
{
perror("ftok failed");
return -1;
}
//2,获取共享内存,信号量操作ID ==> 文件描述符
int shmid, semid;
shmid = shmget(shm_key, SHM_SIZE, IPC_CREAT|0777);
semid = semget(sem_key, 2, IPC_CREAT|0777);
if(-1 == shmid || -1 == semid)
{
perror("get id failed");
return -1;
}
//3,共享内存映射到进程空间
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if(shmaddr == (void *)-1)
{
perror("shmat failed");
return -1;
}
//4, 获取文件大小信息和文件名信息
long int filesize, recvsize = 0;
char filename[64] = {0};
struct sembuf p_sops_1, v_sops_0;
v_sops_0.sem_num = 0;
v_sops_0.sem_op = 1;
v_sops_0.sem_flg = 0; //0号信号量V操作
p_sops_1.sem_num = 1;
p_sops_1.sem_op = -1;
p_sops_1.sem_flg = 0; //1号信号量p操作
// P(D); //数据资源量 -1
semop(semid, &p_sops_1, 1);
sscanf(shmaddr, "%ld:%s", &filesize, filename);
printf("recv filesize:%ld, filename:%s\n", filesize, filename);
// V(S); //空间资源量 +1
semop(semid, &v_sops_0, 1);
//5, 创建一个同名文件
FILE *fp = fopen(filename, "w");
if(NULL == fp)
{
perror("fopen failed");
return -1;
}
//6, 读取文件数据填入文件
while(1)
{
//P (D);
semop(semid, &p_sops_1, 1);
//读取文件数据
if(filesize - recvsize < SHM_SIZE)
{
fwrite(shmaddr, 1, filesize - recvsize, fp);
printf("recv file finish!\n");
break;
}
else{
size_t ret = fwrite(shmaddr, 1, SHM_SIZE, fp);
recvsize += ret; //已经接收的文件字节数
if(recvsize >= filesize) //已经接收完了
{
break;
}
}
//V (S);
semop(semid, &v_sops_0, 1);
}
fclose(fp);
//8,解除映射,删除共享内存,删除信号量
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL); //删除共享内存
semctl(semid, 0, IPC_RMID); //删除信号量
return 0;
}
进程B:文件发送端
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h> // /usr/include/sys/sem.h
#include <sys/ipc.h> // #include "stdio.h"
#include <signal.h>
#include <unistd.h>
#include <string.h>
#define SHM_PATH "/home/gec"
#define SEM_PATH "/home/gec"
#define SHM_ID 10
#define SEM_ID 20
#define SHM_SIZE 4096
/*文件发送端*/
int main(int argc, char *argv[])
{
//0,规定程序执行格式 ./test2_A filename
if(argc != 2)
{
printf("请按照格式执行:%s <filename>\n", argv[0]);
return -1;
}
//1,获取共享内存,信号量key值 ==> 文件名
key_t shm_key, sem_key;
shm_key = ftok(SHM_PATH, SHM_ID);
sem_key = ftok(SEM_PATH, SEM_ID);
if(-1 == shm_key || -1 == sem_key)
{
perror("ftok failed");
return -1;
}
//2,获取共享内存,信号量操作ID ==> 文件描述符
int shmid, semid;
shmid = shmget(shm_key, SHM_SIZE, IPC_CREAT|0777);
semid = semget(sem_key, 2, IPC_CREAT|0777);
if(-1 == shmid || -1 == semid)
{
perror("get id failed");
return -1;
}
//3,共享内存映射到进程空间
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if(shmaddr == (void *)-1)
{
perror("shmat failed");
return -1;
}
//4,信号量初始化 空间资源信号量0 S=1, 数据资源信号量1 D=0
semctl(semid, 0, SETVAL, 1); //S 信号量
semctl(semid, 1, SETVAL, 0); //D 信号量
struct sembuf p_sops_0, v_sops_1;
p_sops_0.sem_num = 0;
p_sops_0.sem_op = -1;
p_sops_0.sem_flg = 0; //0号信号量p操作
v_sops_1.sem_num = 1;
v_sops_1.sem_op = 1;
v_sops_1.sem_flg = 0; //1号信号量v操作
//5,获取文件信息 --> stat : 获取文件大小
struct stat buf;
stat(argv[1], &buf);
FILE *fp = fopen(argv[1], "r");
if(NULL == fp)
{
perror("fopen failed");
return -1;
}
printf("filename:%s, filesize:%ld\n", argv[1], buf.st_size);
//6, 发送文件大小信息和文件名信息
semop(semid, &p_sops_0, 1); //P (S);
memset(shmaddr, 0, SHM_SIZE);
sprintf(shmaddr, "%ld:%s", buf.st_size, argv[1]);//发送文件信息
//V (D);
semop(semid, &v_sops_1, 1);
//7, 循环发送文件内容
while(1)
{
semop(semid, &p_sops_0, 1); //P (S);
memset(shmaddr, 0, SHM_SIZE);
size_t ret = fread(shmaddr, SHM_SIZE, 1, fp);
if(0 == ret)
{
printf("send file finish\n");
semop(semid, &v_sops_1, 1);
if(feof(fp))
break;
}
//V (D);
semop(semid, &v_sops_1, 1);
}
fclose(fp);
//8,解除映射
shmdt(shmaddr);
return 0;
}