3.3 共享内存
(1)共享内存简介
①共享内存区域是被多个进程共享的一部分物理内存
②多个进程都可把该共享内存映射到自己的虚拟内存空间。所有用户空间的进程若要操作共享内存,都要将其映射到自己虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信。
③共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
④本身不提供同步机制,可通过信号量进行同步。
⑤提升数据处理效率,一种效率最高的IPC机制。
(2)共享内存属性结构体
(3)共享内存的使用步骤
①使用shmget函数创建共享内存
②使用shmat函数映射共享内存,将这段创建的共享内存映射到具体的进程虚拟内存空间中。
③解除映射
④删除共享内存
(4)共享内存的创建、控制、映射和解除映射
①创建共享内存
头文件 |
#include <sys/shm.h> |
函数 |
int shmget(key_t key, size_t size, int shmflag); |
参数 |
key: 用户指定的共享内存键值 size:共享内存大小 shmflag:IPC_CREAT、IPC_EXCL等权限组合 |
功能 |
创建共享内存 |
返回值 |
成功返回内核中共享内存的标识ID,出错返回-1 errno: (1)EINVAL(无效内存段大小) (2)EEXIST(内存段己经存在,无法创建) (3)EIDRM(内存段己经被删除) (4)ENOENT(内存段不存在) (5)EACCESS(权限不够) (6)ENOMEM(没有足够的内存来创建内存段) |
②共享内存的控制
头文件 |
#include <sys/shm.h> |
函数 |
int shmctl(int shmid, int cmd, struct shmid_ds buf); |
参数 |
(1)shmid: 共享内存的ID (2)buf:共享内存属性指针 (3)cmd: ①IPC_STAT: 获取共享内存段属性。 ②IPC_SET: 设置共享内存段属性 ③IPC_RMID: 删除共享内存段 ④SHM_LOCK: 锁定共享内存段页面(页面映射到物理内存不和外存进行换入和换出操作) ⑤SHM_UNLOCK:解除共享内存段而面的锁定。 |
功能 |
控制共享内存 |
返回值 |
成功返回内核中共享内存的标识ID,出错返回-1 |
③共享内存映射和解决映射
头文件 |
#include <sys/shm.h> |
函数 |
void* shmat(int shmid, char* shmaddr, int shmflag); //映射,成功返回共享内存映射到进程虚拟内存空间的地址,失败返回-1 int shmdt(char* shmaddr); //解除映射。成功返回0,失败返回-1。 |
参数 |
(1)shmid: 共享内存ID (2)shmaddr:映射到进程虚拟内存的地址。建议设置为0,由操作系统分配。 (3)shmflag:若shmaddr设置为0,则shmflag也设置为0。 ①SHM_RND:随机 ②SHM_BA: 地址为2的平方 ③SHM_RDONLY: 只读方式链接 |
备注 |
(1)errno: ①EINVAL(无效的IPC ID值或无效的地址); ②ENOMEM(没有足够的内存) ③EACCESS(存取权限不够) (2)子进程不继承父进程创建的共享内存,因为大家是共享的。子进程继承父进程映射的地址。 |
【编程实验】不同进程操作共享内存(使用管道来同步)
//tell.h
#ifndef __TELL_H__ #define __TELL_H__
//管道初始化 extern void init(); //利用管道进行等待 extern void wait_pipe(); //利用管道进行通知 extern void notify_pipe(); //销毁管道 extern void destroy_pipe(); #endif
//tell.c
#include "tell.h" #include <stdio.h> #include <stdlib.h> static int fd[2]; //保存管道的文件描述符 //管道初始化 void init() { if(pipe(fd) < 0){ perror("pipe error"); } } //利用管道进行等待 void wait_pipe() { char c; //管道读写默认是阻塞性的 if(read(fd[0], &c, 1) < 0){ perror("wait pipe error"); } } //利用管道进行通知 void notify_pipe() { char c = 'c'; if(write(fd[1], &c, 1) != 1){ perror("notify pipe error"); } } //销毁管道 void destroy_pipe() { close(fd[0]); close(fd[1]); }
//cal_shm.c
#include <unistd.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include "tell.h" int main(void) { //创建共享内存 int shmid; if((shmid = shmget(IPC_PRIVATE, 1024, //大小为1024字节 IPC_CREAT | IPC_EXCL | 0777)) < 0){ perror("shmget error"); exit(1); } pid_t pid; init(); //初始化管道 //创建子进程 if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid > 0){ //parent process //进行共享内存的映射 int* pi = (int*)shmat(shmid, 0, 0); if(pi == (int*)-1){ perror("shmat error"); exit(1); } //往共享内存中写入数据(通过地址即可操作!) *pi = 100; *(pi + 1) = 200; //操作完毕,解除映射 shmdt(pi); //通知子进程到读取共享内存中的数据 notify_pipe(); destroy_pipe(); wait(0); //删除共享内存 shmctl(shmid, IPC_RMID, NULL); }else{ //child process //子进程阻塞,等待父进程先往共享内存中写入数据 wait_pipe(); /*子进程从共享内存中读取数据*/ //子进程进行共享内存映射 int* pi = (int*)shmat(shmid, 0, 0); if(pi == (int*)-1){ perror("shmat error"); exit(1); } printf("start: %d end: %d\n", *pi, *(pi + 1)); shmdt(pi); destroy_pipe(); } }
【编程实验】共享内存实现ATM(没有互斥、是不安全!)
(1)银行帐户创建在共享内存中(而不是原来的堆)
(2)改多线程为多进程程序。
//account.h
#ifndef __ACCOUNT_H__ #define __ACCOUNT_H__ typedef struct { int code; //帐号 double balance; //余额 }Account; //取款 extern double withdraw(Account* a, double amt); //amt == amount //存款 extern double deposit(Account* a, double amt); //查看帐户余额 extern double get_balance(Account* a); #endif //__ACCOUNT_H__
//account.c
#include "account.h" #include <string.h> #include <assert.h> //取款 double withdraw(Account* a, double amt) //amt == amount { assert(a != NULL); if(amt < 0 || amt > a->balance){ return 0.0; } double balance = a->balance; //先取余额 sleep(1); //为模拟进程下可能出现的问题 balance -= amt; a->balance = balance; //更新余额。在读取余额和更新余额之间有 //故意留出“时间窗口”。 return amt; } //存款 double deposit(Account* a, double amt) { assert(a != NULL); if(amt < 0){ return 0.0; } double balance = a->balance; //先取余额 sleep(1); //为模拟多进程下可能出现的问题 balance += amt; a->balance = balance; //更新余额。 return amt; } //查看帐户余额 double get_balance(Account* a) { assert(a != NULL); double balance = a->balance; return balance; }
//account_test.c
#include "account.h" #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/shm.h> int main(void) { //在共享内存中创建银行帐户 int shmid; if((shmid = shmget(IPC_PRIVATE, sizeof(Account), IPC_CREAT | IPC_EXCL | 0777)) < 0){ perror("shmget error"); exit(1); } //进程共享内存映射(a为返回的映射地址) Account* a= (Account*)shmat(shmid, 0, 0); if(a == (Account*)-1){ perror("shmat error"); exit(1); } //银行帐户初始化 a->code = 100001; a->balance = 10000; printf("balance: %f\n", a->balance); //父子进程都进行取款 pid_t pid; if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid > 0){ //parent process //父进程进行取款操作 double amt = withdraw(a, 10000); printf("pid %d withdraw %f from code %d\n", getpid(), amt, a->code); //解除映射 shmdt(a); wait(0); //删除共享内存区 shmctl(shmid, IPC_RMID, NULL); }else{ //child process //子进程会继承父进程映射的共享内存地址 //子进程进行取款操作 double amt = withdraw(a, 10000); printf("pid %d withdraw %f from code %d\n", getpid(), amt, a->code); //解除映射 shmdt(a); } return 0; } /*输出结果: balance: 10000.000000 pid 1939 withdraw 10000.000000 from code 100001 pid 1940 withdraw 10000.000000 from code 100001 //不安全!(可用信号量解决) */