前面介绍了进程通信的几种方式,信号量,管道,消息队列,今天主要总结下共享内存的知识点。
什么是共享内存
我们一张图来解释什么叫共享内存。
我们知道,每个进程都有一个叫PCB(Linux下一般为task _ struct)的数据结构,用于保存进程的相关信息。我们可以通过PCB找到进程地址空间,进程地址空间一般包括栈,堆,数据段,代码段等等,需要明确的一点就是用户访问的都是虚拟内存,操作系统是通过页表+MMU机制来将虚拟内存映射为物理内存。
所以,如上图所示,两个进程1,2各自有一个PCB,他们的进程地址空间是独立的,因此映射的物理内存也是不同的(黑笔所画的)。
为了实现进程通信,我们需要让他们共享一段内存,这样子如果需要通信,只需要将信息写入这段共享内存中就可以了。
因此,如红笔所示,我们在进程1,2的地址空间选取一段,只需要通过页表和MMU机制将他们映射到同一段物理内存中,因此他们就完成了对内存的共享。
共享内存就是允许两个不相关的进程访问同一个虚拟内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
一些注意点
- 共享内存,存在于每个进程的进程地址空间中,通过页表+MMU机制映射为同一块物理内存,因此,它属于每个进程,由于它并不需要系统调用干预和数据复制,它的效率是非常高的,它比我们所学的几种IPC机制(信号量,管道,消息队列)都要快。
- 既然它性能最好,为什么还需要有其他的IPC机制?直接用它不就好了吗?它虽然很快,但是它不提供同步互斥机制,这样子一来就需要我们程序员来提供,带来了编程的难度。
Linux下相关函数
1、shmget函数
该函数用来创建共享内存,它的原型为:
int shmget(key_ t key, size_t size, int shmflg);
第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
第二个参数,size以字节为单位指定需要共享的内存容量
第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
2、shmat函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数,shm_id是由shmget函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
3、shmdt函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
int shmdt(const void *shmaddr);
参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.
4、shmctl函数
与信号量的semctl函数一样,用来控制共享内存,它的原型如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数,shm_id是shmget函数返回的共享内存标识符。
第二个参数,command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
实例
由于共享内存不提供同步机制,因此,我们自己编写代码完成其同步机制,利用我们之前所学的信号量即可。
这个程序大体思路为如下:
- 创建一个共享内存
- 创建父子进程,其中子进程负责写,父进程负责读。
- 为了体现测试和体现互斥机制,我们先让父进程挂起0.5秒,保证子进程先运行。子进程写01234这四个字符,我们让子进程每次写一个字符就休眠0.5秒,这样子如果没有提供互斥机制,那么在这休眠0.5秒时候,父进程便会读取已经写的字符。
- 如果加入信号量,子进程先运行,会执行P操作,锁住资源,这样子就算子进程挂起,父进程依旧访问不了共享内存。
结果如下:
共享内存没有提供信号量父进程打印的结果 & 共享内存提供信号量父进程打印的结果 对比:
代码拆分为以下几个文件:
makefile semaphore.c semaphore.h shm.c shm.h test test.c
其中makefile用于构建工程。
semaphore.c semaphore.h用于实现信号量机制
shm.c shm.h用于实现共享内存
test.c用于测试信号量和共享内存,test为可执行文件。
在Linux下输入./test执行即可看到结果。
以下为各自代码:
makefile
test:test.c semaphore.c shm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f test
semaphore.h
#ifndef __SEM_H__
#define __SEM_H__
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#define PATHNAME "./"
#define PROJ_ID 0x666
union semun
{
int val; //单个信号的值
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int create_sem_set(int nsem);
int get_sem_set(int nsem);
int init_sem_set(int sem_set_id, int which_sem);
int sem_p(int sem_set_id, int which_sem);
int sem_v(int sem_set_id, int which_sem);
int destroy_sem_set(int sem_set_id);
#endif
semaphore.c
/*************************************************************************
> File Name: semaphore.c
> Author: xuyang
> Mail: [email protected]
> Created Time: 2017-05-19 18:07:52
************************************************************************/
#include "semaphore.h"
static int sem_set(int nsem, int semflag)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key < 0)
{
perror("ftok failure");
exit(-1);
}
int sem_set_id = semget(key, nsem, semflag);
if (sem_set_id < 0)
{
perror("semget failure");
exit(-1);
}
return sem_set_id;
}
int create_sem_set(int nsem)
{
if (nsem <= 0)
{
perror("nsem <= 0");
exit(-1);
}
return sem_set(nsem, IPC_CREAT | IPC_EXCL | 0666);
}
int get_sem_set(int nsem)
{
if (nsem <= 0)
{
perror("nsem <= 0");
exit(-1);
}
return sem_set(nsem, IPC_CREAT);
}
int init_sem_set(int sem_set_id, int which_sem)
{
union semun my_semun;
my_semun.val = 1;//semun中val的值去设置信号集(sem_set)中单个sem的值
int ret = semctl(sem_set_id, which_sem, SETVAL, my_semun);
if (ret == -1)
{
perror("semctl error");
exit(-1);
}
return 0;
}
static int sem_operatons(int sem_set_id, int which_sem, int op)
{
struct sembuf sb;
//sembuf is as follows:
//unsigned short sem_num
//short sem_op
//short sem_flg
sb.sem_num = which_sem;
sb.sem_op = op;
sb.sem_flg = SEM_UNDO;
return semop(sem_set_id, &sb, 1);
}
int sem_p(int sem_set_id, int which_sem)
{
return sem_operatons(sem_set_id, which_sem, -1);
}
int sem_v(int sem_set_id, int which_sem)
{
return sem_operatons(sem_set_id, which_sem, +1);
}
int destroy_sem_set(int sem_set_id)
{
int ret = semctl(sem_set_id, 0, IPC_RMID, NULL);
if (ret == -1)
{
perror("semctl failure");
exit(-1);
}
return ret;
}
shm.h
#ifndef __SHM__
#define __SHM__
#include<stdlib.h>
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#define PATHNAME "./"
#define PROJ_ID 0x666
int create_shm(int sz);
int get_shm(int sz);
void* malloc_addr(int shm_id);
int free_addr(const void* addr);
int destroy_shm(int shm_id);
#endif
shm.c
/*************************************************************************
> File Name: shm.c
> Author: xuyang
> Mail: [email protected]
> Created Time: 2017-05-20 11:05:02
************************************************************************/
#include"shm.h"
static int shm(int sz, int flag)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key == -1)
{
perror("ftok failure");
exit(-1);
}
int shm_id = shmget(key, sz, flag);
if (shm_id < 0)
{
perror("shmget failure");
exit(-1);
}
return shm_id;
}
int create_shm(int sz)
{
return shm(sz, IPC_CREAT | IPC_EXCL | 0666);
}
int get_shm(int sz)
{
return shm(sz, IPC_CREAT);
}
void* malloc_addr(int shm_id)
{
return shmat(shm_id, NULL, 0);
}
int free_addr(const void* addr)
{
return shmdt(addr);
}
int destroy_shm(int shm_id)
{
return shmctl(shm_id, IPC_RMID, NULL);
}
test.c
/*************************************************************************
> File Name: test.c
> Author: xuyang
> Mail: [email protected]
> Created Time: 2017-05-19 21:16:49
************************************************************************/
#include"semaphore.h"
#include"shm.h"
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#define SHM_SIZE 4096
int main()
{
//创建信号量并初始化
int sem_set_id = create_sem_set(1);
init_sem_set(sem_set_id, 0);
//创建共享内存
int shm_id = create_shm(SHM_SIZE);
//创建子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork failure");
exit(-1);
}
else if (pid == 0) //子进程
{
char* buf = (char*)malloc_addr(shm_id);
int count = 0;
sem_p(sem_set_id, 0);
while (count < 5)
{
buf[count] = '0' + count;
count++;
usleep(500000);
}
buf[count] = '\0';
sem_v(sem_set_id, 0);
free_addr(buf);
}
else //父进程
{
char* buf = (char*)malloc_addr(shm_id);
usleep(500000);//让子进程先P操作
sem_p(sem_set_id, 0);
int times = 5;
while (times--)
{
usleep(500000);
printf("%s\n", buf);
}
sem_v(sem_set_id, 0);
free_addr(buf);
destroy_shm(shm_id);
destroy_sem_set(sem_set_id);
}
return 0;
}