3.3 共有メモリ
(1)共有メモリプロファイル
① 共有メモリ領域は、複数のプロセスで共有される物理メモリの一部であります
② 複数のプロセスは、独自の仮想メモリ空間を共有メモリにマッピングすることができます。すべてのユーザ・プロセスが動作するメモリ空間を共有し、データ、プロセス間通信を実現するように、動作するために、共有メモリの仮想メモリアドレス空間マッピングによって、独自の仮想メモリ空間にマッピングされなければなりません。
③共有メモリは、プロセス間の共有データへの最速の方法です、プロセスは、共有メモリ領域にデータを書き込み、すべてのプロセスがこのメモリ領域を共有し、あなたはすぐにその内容を見ることができます。
④同期メカニズム自体が提供していない、セマフォで同期させることができます。
データ処理効率向上⑤ 非効率最高IPC機構。
(2)共有メモリ属性構造
(3)共有メモリを使用するステップ
①使用したshmget機能は、共有メモリを作成します
②利用にshmat機能マッピング、共有メモリ、共有メモリマッピングは、この特定のプロセスの仮想メモリ空間を作成します。
③マップされていません
④共有メモリを取り外します
(4)が、共有メモリ、制御、マッピング及びデマッピングを作成します
①共有メモリを作成します
ヘッダ |
書式#include <sysの/ shm.h> |
機能 |
INTたshmget(key_tのキー、size_tのサイズ、int型のshmflag)。 |
パラメータ |
キー:ユーザーが指定した共有メモリキー サイズ:共有メモリサイズ shmflag :IPC_CREAT、および権限の他の組み合わせIPC_EXCL |
機能 |
共有メモリを作成します。 |
返却値 |
成功したリターンカーネルの共有メモリ識別子ID、エラー-1 エラー番号: (1)EINVAL(無効メモリセグメントサイズ)(2)EEXIST(メモリ・セグメントが既に存在する、作成することはできません) (3)EIDRM(メモリ・セグメントが削除された)(4)ENOENT(メモリ・セグメントが存在しません) (5)EACCESS(不十分な権限)(6)ENOMEM(メモリ・セグメントを作成するための十分なメモリ) |
②共有メモリ制御
ヘッダ |
書式#include <sysの/ shm.h> |
機能 |
int型shmctl(int型shmidを、int型CMD、構造体する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:0に設定した場合は、shmaddr、その後また0 shmflagに設定します。 ①SHM_RND:ランダム ②SHM_BA:2の正方形のための住所 ③SHM_RDONLY:読み取り専用リンク |
リマーク |
(1)エラー番号: ①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 //不安全!(可用信号量解决) */