share memory共享内存,
原理:每一个进程都有自己独立的地址空间,把同一块内存,分别映射到不同进程的地址空间的某个地址中去,这样每个进程都可以通过自己映射的地址来访问这块共享内存,优点就是,多个进程交换数据时,不用做任何的拷贝,效率高。需要注意的是,linux共享内存机制并没有提供同步机制,如果多各进程同时都往该内存写东西,就会写乱了,所以,一般我们还需要使用其他进程间的通信方法(如信号量、消息队列、管道等)来配合共享内存机制。
相关API:
1、用ftok来生成一个key,参考另一篇博文:消息队列
2、int shmget(key_t key, size_t size, int shmflg);//创建/获取共享内存
举例: int shmid = shmget(key, 1000, IPC_CREAT | 0666);
@size,要申请的内存的大小,注意,该值会被圆整为内存页大小的整数倍,内存页大小的宏定义为PAGE_SIZE,
@shmflg,shmflg可选的值可以进行或运算,可选的值有:权限值(例如0666)、参数。权限值不再赘述,只看一下参数:
IPC_CREAT 如果设置了该标志,则会创建新的共享内存,否则,会查找跟key关联的、已经存在的、且本进程有访问权的共享内存;
IPC_EXCL 该标志一般与IPC_CREAT 同时使用,作用是:若key关联的共享内存已存在,则shmget返回失败,而不是返回已存在的共享内存shmid;
SHM_HUGETLB 使用huge page来分配内存
SHM_NORESERVE 该标志的功能和mmap函数中的MAP_NORESERVE标志相同。不保留本内存段的交换空间,关于交换空间的概念,可自行查阅百科词条:交换空间 (电脑术语)。如果保留了交换空间,我们就一定能修改该内存段的数据;如果没保留交换空间,当物理内存用光时,继续写入 内存,这时本进程会收到SIGSEGV信号。
返回:成功则返回共享内存的描述符shmid,失败则返回-1,并设置errno
注意:何时会创建一个新的共享内存?①key的值为IPC_PRIVATE, 这时不论shmget的shmflg怎么设置,shmget总是创建新共享内存;②key的值不是IPC_PRIVATE,且shmget的形参shmflg包含IPC_CREAT标志,这时也会创建新的。
特性:当共享内存申请成功以后,这段内存区会被初始化为0,且记录本共享内存的信息(该信息用shmid_ds结构来描述)会被初始化,哪些成员会被初始化,初始化后的值为多少,这些信息科自行通过man shmget命令来查看。
3、void *shmat(int shmid, const void *shmaddr, int shmflg);//映射共享内存到本进程的地址空间 attach
int shmdt(const void *shmaddr);//取消映射 detach
@shmaddr,该值如果传入的实参为NULL,那么系统会自动选择一个本进程未使用到的、合适的地址,把shmid指定的共享内存映射(attach)到这个地址;
如果传入的实参不是NULL,且shmflg含SHM_RND(自动圆整)标志,那么,系统会把shmid指定的共享内存映射到shmaddr地址(shmaddr值会被自动向下圆整为SHMLBA的整数倍);如果不含SHM_RND标志,那么程序员必须保证shmaddr的实参是个页面对齐的地址。
@shmflg,可选的值有3个,SHM_RND、SHM_RDONLY、SHM_REMAP。
SHM_RND上面讲过了;
SHM_RDONLY 设置本进程对这块共享内存是只读的(当然,首先进程对这个共享内存本来就具有读权限,SHM_RDONLY 的设置才能有效,如果本来就没有读权限,即使设置了SHM_RDONLY 标志,本进程也无法读这块内存);如果不设置SHM_RDONLY 标志,那么系统会设置本进程对这块内存是读写的(当然,首先进程对这个共享内存本来就具有读写权限);另外,内核没有提供只写这种标志。
SHM_REMAP 把shmid指定的共享内存重新映射到shmaddr指定的地址(大小为size),如果shmaddr指定的地址(整个size范围内)早就被占用了,那么本函数会返回错误,这种情况下,只能把shmaddr设定为NULL。
返回值:成功则返回共享内存在本进程空间的地址,失败则返回-1并设置errno。
关联:本函数执行完之后,本共享内存的描述结构shmid_ds的三个成员会被更新:shm_atime更新为当前时间、shm_lpid更新为本进程的PID、shm_nattch加1。
fork出的子进程可以继承父进程映射好的共享内存;通过execve函数启动新程序之后,前面的映射好的共享内存全部都会被detach掉;_exit(2)函数也会把前面的映射好的共享内存全部detach掉。
shmdt函数的功能就简单了,取消掉那块共享内存到本进程地址空间的映射,形参的值就是shmat函数的返回值。本共享内存的描述结构shmid_ds的三个成员会被更新:shm_dtime更新为当前时间、shm_lpid更新为本进程的PID、shm_nattch减1,If shm_nattch becomes 0 and the segment is marked for deletion, the segment is deleted。
返回值:成功则返回0,失败则返回-1并设置errno。
4、int shmctl(int shmid, int cmd, struct shmid_ds *buf);
形参@buf的结构为:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* 本共享内存此时被多少个进程给attach了 */
...
};
The ipc_perm structure is defined as follows (uid、 gid、 mode are settable using IPC_SET):
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
@cmd,可选的值如下:
· IPC_STAT 读取共享内存的描述结构shmid_ds(前提是进程具备读权限)
· IPC_SET 修改共享内存的描述结构shmid_ds的某些成员,只有这几个成员允许被本函数修改:shm_perm.uid、shm_perm.gid、 shm_perm.mode(低9位),这时shm_ctime会被自动更新。
· IPC_RMID 删除这个共享内存,注意,并不是立即删除,只有所有attach这块共享内存的进程,全部都detach之后(这时shmid_ds结构的shm_nattch成员的值会变成0),才会被真正删除。如果该共享内存已经被标记为删除,那么shm_perm.mode将会被添加SHM_DEST标志。
· IPC_INFO,读取整个系统中,共享内存的各种限制数、参数等。由于第四参数的类型为struct shmid_ds,因此,本命令读取shminfo时,必须得转换一下类型,shminfo的结构如下:
struct shminfo {
unsigned long shmmax; /* Maximum segment size */
unsigned long shmmin; /* Minimum segment size;
always 1 */
unsigned long shmmni; /* Maximum number of segments */
unsigned long shmseg; /* Maximum number of segments
that a process can attach;
unused within kernel */
unsigned long shmall; /* Maximum number of pages of
shared memory, system-wide */
};
· SHM_INFO 读取以下内容:
struct shm_info {
int used_ids; /* # of currently existing
segments */
unsigned long shm_tot; /* Total number of shared
memory pages */
unsigned long shm_rss; /* # of resident shared
memory pages */
unsigned long shm_swp; /* # of swapped shared
memory pages */
unsigned long swap_attempts;
/* Unused since Linux 2.4 */
unsigned long swap_successes;
/* Unused since Linux 2.4 */
};
· SHM_STAT 功能与IPC_STAT.命令相同,区别在于传实参时,shmid对应的实参改为:一个内核数组索引,这个数组维护着系统中所有共享内存的信息。
· SHM_LOCK 禁止该共享内存的交换。The caller must fault in any pages that are required to be present after locking is enabled。执行此命令后,shm_perm.mode会被加入SHM_LOCKED标志。
· SHM_UNLOCK 允许共享内存交换。
返回值:失败时返回-1,并设置errno,成功时,根据cmd的不同返回值不同:IPC_INFO 、SHM_INFO的返回值为相关信息数组的最高入口。其他命令成功后返回0。
应用实例:父进程占有信号量,然后fork出子进程,然后阻塞一秒,然后往共享内存写入一个字符串,然后释放信号量。子进程等待信号量,等待成功后,读取共享内存中的数据。
如果不使用信号量进行同步的话,在父进程阻塞1秒的时间段内,子进程就会从共享内存读数据,这时父进程还没把数据写进去。
//编译: gcc -o shm_exe sharedMemory.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/shm.h>
void V(int semid) //占用资源
{
struct sembuf sops[2];
sops[0].sem_num = 0; /* 要操作的信号量的编号为0 */
sops[0].sem_op = 0; /* Wait for value to equal 0 */
sops[0].sem_flg = 0; /* 既不IPC_NOWAIT,也不IPC_UNDO */
sops[1].sem_num = 0; /* 要操作的信号量的编号为0 */
sops[1].sem_op = 1; /* >0 的作用是,把第sem_num个信号量的当前值+sem_op */
sops[1].sem_flg = SEM_UNDO; /* IPC_NOWAIT,SEM_UNDO. */
if (semop(semid, sops, 2) == -1)
{
printf("V->semop failed, infor:%s\r\n", strerror(errno) );
exit(1);
}
}
void P(int semid) //释放资源
{
struct sembuf sops[5];
sops[0].sem_num = 0; /* 要操作的信号量的编号为0 */
sops[0].sem_op = -1; /* -1 */
sops[0].sem_flg = SEM_UNDO; /* IPC_NOWAIT,SEM_UNDO. */
if (semop(semid, sops, 1) == -1)
{
printf("P->semop failed, infor:%s\r\n", strerror(errno) );
exit(1);
}
}
/*
* 功能: 创建一个二值信号量
* 形参: filePath文件路径,用于ftok函数
* 返回: 成功则返回信号量id,失败则直接令调用本函数的进程退出
* */
int creat_semphore(char* filePath)
{
key_t key = ftok(filePath, 'f');
if(-1 == key)
{
printf("ftok failed, infor:%s\r\n", strerror(errno) );
exit(EXIT_FAILURE);
}
else
{
printf("ftok ok, key = %d\r\n", key );
}
int semid = semget(key, 1, IPC_CREAT | 0666 );//灯集中只要一个灯
if(-1 == semid)
{
printf("semget failed, infor:%s\r\n", strerror(errno) );
exit(EXIT_FAILURE);
}
else
{
printf("semget ok, semid = %d\r\n", semid );
}
return semid;
}
/*
* 功能: 创建一个共享内存
* 形参: filePath文件路径,用于ftok函数; size创建的共享内存的字节数
* 返回: 成功则返回共享内存id,失败则直接令调用本函数的进程退出
* */
int creat_sharedMemory(char* filePath, size_t size)
{
key_t key = ftok(filePath, 'f');
if(-1 == key)
{
printf("ftok failed, infor:%s\r\n", strerror(errno) );
exit(EXIT_FAILURE);
}
else
{
printf("ftok ok, key = %d\r\n", key );
}
int shmid = shmget(key, size, IPC_CREAT | 0666 );//灯集中只要一个灯
if(-1 == shmid)
{
printf("shmget failed, infor:%s\r\n", strerror(errno) );
exit(EXIT_FAILURE);
}
else
{
printf("shmget ok, shmid = %d\r\n", shmid );
}
return shmid;
}
int main(int argc, char *argv[])
{
printf("exe file path: %s\r\n", argv[0] );
int semid = creat_semphore(argv[0]);
int shmid = creat_sharedMemory(argv[0], 100);
printf("father occupy semaphore!\r\n");
V(semid);//父进程占用资源,只有父进程写完了,才允许子进程读
printf("father occupied semaphore!\r\n");
pid_t pid = fork();
if(pid == 0 )//子进程
{
printf("child process start\r\n");
char *memAddr = shmat(shmid, NULL, SHM_RND);
V(semid);
printf("child occupied semaphore!\r\n");
printf("child read shared memory: %s\r\n", memAddr);
P(semid);
printf("child released semaphore!\r\n");
_exit(0);
}
else if(pid > 0)//父进程
{
char *memAddr = shmat(shmid, NULL, SHM_RND);
char str[] = "this string is from father process!";
memcpy(memAddr, str, sizeof(str));
sleep(1);
P(semid);
printf("father released semaphore!\r\n");
int stat;
if(waitpid(pid, &stat, 0) != pid)
{
printf("child process failed!\r\n");
}
else
{
printf("child process returned value = %d\r\n", stat);
}
printf("father process return\r\n");
}
else
{
printf("fork failed, infor:%s\r\n", strerror(errno) );
return -1;
}
return 0;
}