很久以来我都是只闻其名,未见其形.终于在这次系统的学习linux编程中接触到了共享内存.果然很牛.
上一篇文章中我们讲的信号量,个人感觉,严格的说,信号量只是进城通信的辅助.而共享内存才真正实现了进程通信.
共享内存机制允许两个不想关的进程访问同一段物理内存,当然得是一台主机.
头文件<sys/shm.h> 和信号量的情况一样,也需要包含sys/types.h 和 sys/ipc.h .当然有可能已经包含在了sys/shm.h中了.
共享内存函数如下:
int shmget(key_t key, size_t size, int shm_flg);
void * shmat(int shm_id, void * addr, int shmflg);
int shmdt(const void * addr);
int shmctl(int shm_id, int command, struct shmid_ds *buf);
其实IPC系列的函数很多地方都类似.
shmget 通过指定一个唯一的key来创建一个唯一的共享内存的标记.size指定了共享内存的大小.shm_flg指定了权限和动作.低九位是权限,其它位可取IPC_CREAT IPC_EXCL.当然key同样可以是IPC_PRIVATE,也就是说可以指定为进程内部的共享.如果单纯指定IPC_CREAT,那么如果共享内存已经存在则获取,如果另外指定了IPC_EXCL,则当共享内存存在时会出错.
shmget的返回值,当成功的时候返回共享内存的标记,失败返回-1
shmat 为共享内存分配一段真实可用的物理内存,并且返回一个指向这段内存的指针.第一个参数shm_id 即为shmget的返回值,第二个参数addr, 指定物理内存的地址.如果为空,则表示由系统分配.第三个参数指定进程对这段物理内存的权限.常用的权限包括SHM_RND,这个参数一般在第二个参数为用户指定的时候会使用这个参数.SHM_RDONLY,当前进程对物理内存仅有读权限.如果想要对共享内存可写,只要指定这个参数为0即可.也就是说,不是只读即可写.
shmat的返回值,成功的时候返回指向的物理内存,如果失败返回NULL.
shmdt 前面的at我们可以理解成是attach,即绑定.这个函数执行与shmat相反的操作---解绑定,即dettach.如果进程调用了这个函数,则通过使用共享内存标记将无法再对物理内存进行访问.
shmdt的返回值,当成功是返回0, 失败返回-1.
shmctl 每个IPC都有这样一个函数,它用来了解实时的IPC状态,设置IPC值和清理IPC.首先第一个参数还是共享内存的标记.第二个参数指定对共享内存采取的动作.第三个参数根据第二个参数的动作来决定填哪些字段.一般而言,使用这个函数最多的就是删除共享内存.
shmctl返回值, 成功是返回0, 失败时返回-1.
介绍完这四个函数,我们来设计一个小场景.比如一个老人临死前留下了100块钱的家产,不允许三兄弟评分,但是当三兄弟需要用钱的时候,可以过来拿.当然三兄弟之间不能冲突.不然可能会把钱弄丢了.我们来试着实现一下这个场景.
好吧,同样是信号量时候类似的问题.我又出错了.总结一下自己写遇到的问题.
1. 我过早的调用了shmctl,以至于其它的进程获取到的内容已经不是我存放在共享内存里面的内容了.也就是说,shmctl一定要在所有进程都使用完共享内存才可以.我自己的乌龙.
2. 一般而言,不需要使用IPC_EXCL这个标记,因为如果存在就使用,如果不存在就创建.
3. 同样是对等的用户,如果root创建了共享内存,而用普通用户去试着修改,肯定会报权限问题.
代码如下:
father.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/shm.h> #define MY_LEGACY 100 #define MAXLEN 10 int main(int argc, char ** argv) { int ret; int shmid; int *legacy = NULL; shmid = shmget((key_t)123456, 4, 0666 | IPC_CREAT); if (shmid < 0) { perror("oh, i am not die\n"); exit(EXIT_FAILURE); } legacy = (int *)shmat(shmid, NULL, 0); if (NULL == legacy) { perror("\n"); exit(EXIT_FAILURE); } *legacy = MY_LEGACY; printf("my legacy %d is up to you\n", *legacy); shmdt(legacy); // shmctl(shmid, IPC_RMID, NULL); return 0; }老子进程做的事情就是把钱存起来.供儿子挥霍.
son.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #include <sys/shm.h> #include <sys/sem.h> union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; int main(int argc, char ** argv) { if (argc < 2) { printf("usage : which son of the poor old man you are\n"); return 0; } int ret; int cost = 1; int shmid; int *legacy = NULL; int semid; struct sembuf sem_p; struct sembuf sem_v; union semun sem_un; semid = semget((key_t)123456789, 1, 0666 | IPC_CREAT); if (semid < 0) { perror("\n"); exit(EXIT_FAILURE); } sem_un.val = 1; ret = semctl(semid, 0, SETVAL, sem_un); sem_p.sem_num = 0; sem_p.sem_op = -1; sem_p.sem_flg = SEM_UNDO; sem_v.sem_num = 0; sem_v.sem_op = 1; sem_v.sem_flg = SEM_UNDO; shmid = shmget((key_t)123456, 4, 0666|IPC_CREAT); if (shmid < 0) { perror("\n"); exit(EXIT_FAILURE); } srand(time(NULL)); legacy = (int *)shmat(shmid, NULL, 0); while (cost) { semop(semid, &sem_p, 1); if (*legacy <= 0) { break; } else { int spend = rand() % 10; *legacy = *legacy - spend; printf("son %s have spend %d, left %d \n", argv[1], spend, *legacy); } sleep(1); semop(semid, &sem_v, 1); } shmdt((void*)legacy); shmctl(shmid, IPC_RMID, NULL); return 0; }儿子果然很不争气的争着抢着去花老子的钱.银行怕出问题,所以用信号量给他们维护秩序.
看下运行结果:
首先是老子存钱:
然后是儿子花钱:
唉,这个有点问题,这没出息的儿子透支了.主要是使用了随机数,所以如果判断当前还有五块钱,这是大于0的,但是如果随机数生成了10块,自然就会透支5块.我就不修改了.同学们在学习的过程中还是应该实地的去敲一下代码加深印象,如果能够自己去设想一个场景然后实现,相信效果会更好.本文都是一些入门的基础,希望对大家有所帮助.