版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/83342221
--------参考文献 W.Richard Stevens, Stephen A.Rago.UNIX环境高级编程[M].北京:人民邮电出版社,2014.6:459-462.
目录
一、 共享内存
1.1 定义
共享存储允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制。所以这是一种最快的IPC。在多个进程之间同步访问一个给定的存储区。若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应该去读取这些数据。通常,信号量用于同步共享存储访问。
扫描二维码关注公众号,回复:
3772794 查看本文章
1.2 方案设计
多个进程将同一文件映射到它们的地址空间。内核为每一个共享存储段维护着一个结构:
struct shmid_ds{
struct ipc_perm shm_perm; //权限控制。
size_t shm_segsz; //size of segement in bytes.字节数。
pid_t shm_lpid; //pid of last shmop(). 最后执行shmop()的进程ID。
pid_t shm_cpid; //pid of creator.创建该共享内存的进程ID。
shmatt_t shm_nattch; //number of current attaches.现有链接数。
time_t shm_time; //last_attach time. 最后被链接的时间。
time_t shm_dtime; //last-detach time. 最后被删除链接的时间。
time_t shm_ctime; //last-change time. 最后修改时间。
.
.
.
}
1.3 相关知识
shmget 获得一个共享内存标识符。是创建一个新的共享存储段(关键字key没有对应的标识符ID)还是引用一个现有的共享存储段(关键字key有对应的标识符ID)。
#include<sys/shm.h>
int shmget(key_t key, size_t size, int flag);
//返回值:若成功,返回共享存储ID;若出错,返回-1。
- key 关键字。
- size 共享存储段的长度,以字节为单位。实现通常将其向上取整为系统页长的整数倍。如应用指定的size 值并非系统页长的整数倍,那么最后一页的余下部分是不可使用的(浪费)。如果在创建一个新段(通常在服务器进程中),则必须指定其size 。如果引用一个现存的段(一个客户进程),则将size指定为0。当创建一个新段时,段内的内容初始化为0。
- flag (幻数),进行一些操作。
semctl 函数包含了多种信号量操作。
#include<sys/shm.h>
int semctl(int shmid, int cmd, struct shmid_ds *buf);
//返回值:若成功,返回0;若出错,返回-1。
- shmid 共享存储ID。
- cmd (幻数)IPC_STAT、IPC_SET、IPC_RMID、SHM_LOCK、SHM_UNLOCK。
- buf 指针。
shmat 函数将shmid连接到它的地址空间中。
#include<sys/shm.h>
void* shmat(int shmid, const void* addr, int flag);
//返回值:若成功,返回指向共享存储段的指针;若出错,返回-1。返回-1很令人费解,看看2.2写进程就明白了。
- shmid 共享存储ID。
- addr 不展开说,推荐使用NULL(连接到内核选择第一个可用地址上)。
- flag (幻数),进行一些操作。
二、 代码实现
2.1 信号量部分
//semaphore.h 头文件
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include<sys/sem.h> //semget() semctl() semop()
typedef union semun
{
int val; //for SETVAL
struct semid_ds *buf; //for IPC_STAT and IPC_SET
unsigned short *array; //for GETALL and SETALL
}semun;
int sem_init(int key); //初始化信号量集
void sem_p(int semid, int semnum); //p操作,申请临界资源
void sem_v(int semid, int semnum); //v操作,归还临界资源
void sem_del(int semid, int semnum); //删除信号量
#endif
//semaphore.c 源文件
#include"semaphore.h"
int sem_init(int key) //初始化信号量集
{
int semid = semget((key_t)key, 0, 0666);
if(-1 == semid)
{
semid = semget((key_t)key, 1, 0666|IPC_CREAT);
semun un;
un.val = 1; //初始资源数目为1,因为临界资源是一块共享内存区域
semctl(semid, 0, SETVAL, un); //un.val
}
return semid;
}
void sem_p(int semid, int semnum) //p操作,申请临界资源
{
struct sembuf buf;
buf.sem_num = semnum;
buf.sem_op = -1; //资源总数减1
buf.sem_flg = SEM_UNDO; //进程结束时会自动将申请的资源归还。
semop(semid, &buf, 1);
}
void sem_v(int semid, int semnum) //v操作,归还临界资源
{
struct sembuf buf;
buf.sem_num = semnum;
buf.sem_op = 1; //资源总数加1
semop(semid, &buf, 1);
}
void sem_del(int semid, int semnum)
{
semctl(semid, 0, IPC_RMID); //删除信号量集合
}
2.2 写进程(留心)
自主实现的sem_p函数体内,到底是否指定SEM_UNDO(幻数),是需要一个小心处理的问题。经过分析,我们发现确实需要指定SEM_UNDO标志。读进程和写进程实现时——使用sem_p和sem_v将临界区域保护起来,如果读进程执行了p操作,突然崩了,临界资源(共享内存)将得不到释放,写进程就会永远处于阻塞的状态。
信号量本质就是放在内核中的计数器,只起到标志作用。这就好比有人在图书馆用书占了一个座位,但是一天到晚都没有去过(不文明的行为)。如果有同学想要坐那个位子,发现有人;实际相当于没人。信号量有时也会如此,刚将资源分配给一个进程,做了标记,进程崩了,来不及归还资源;这时候信号量应该自己自动处理。
void sem_p(int semid, int semnum) //p操作,申请临界资源
{
struct sembuf buf;
buf.sem_num = semnum;
buf.sem_op = -1; //资源总数减1
buf.sem_flg = SEM_UNDO; //进程结束时会自动将申请的资源归还。
semop(semid, &buf, 1);
}
#include<stdio.h> //printf()
#include<string.h> //memset()
#include<sys/shm.h> //shmid() shmat()
#include<assert.h> //assert()
#include"semaphore.h" //引入稍前提到的头文件
int main()
{
int shmid = shmget((key_t)1996, 512, 0666|IPC_CREAT); //将标识符转换成共享内存内核ID
assert(-1 != shmid);
char* ptr = (char*)shmat(shmid, 0, 0); //申请共享内存
assert((char*)-1 != ptr);
memset(ptr, 0, 512);
int semid = sem_init(100); //创建信号量集或者引用现有信号量集
while(1)
{
sem_p(semid, 0); /*申请资源,因为资源总数只有1,故而只能一个进程独享*/
printf("please input: ");
fflush(stdout); //将提示语句从缓冲区中刷出来
memset(ptr, 0, 512);
fgets(ptr, 128, stdin);
ptr[strlen(ptr) - 1] = '\0'; /*一次读入的有效字符只有127个,因为结束符的关系*/
sem_v(semid, 0); //归还资源
if(0 == strcmp(ptr, "end")) //输出end字符串时,循环结束
break;
}
shmdt(ptr); //释放该块共享内存区域
sem_del(semid); //删除信号量集
return 0;
}
2.3 读进程
#include<sys/shm.h> //shmget() shmdt() shmat()
#include<string.h> //memset()
#include<assert.h> //assert()
#include<stdio.h> //printf()
#include"semaphore.h"
int main()
{
int shmid = shmget((key_t)1996, 512, 0666|IPC_CREAT); /*将标识符转换成共享内存内核ID。*/
assert(-1 != shmid);
char* ptr = (char*)shmat(shmid,0 , 0); //申请共享内存
assert((char*)-1 != ptr);
memset(ptr, 0, 512);
int semid = sem_init(100); //申请信号量集或者引用现有信号量集
while(1)
{
sem_p(semid, 0); /*申请资源(共享内存),因为只有1个,故特定时刻只能1个进程独占。*/
if(0 != strcmp(ptr, "end")) //打印输内存中不是end的字符串
printf("%s\n", ptr);
sem_v(semid, 0); //归还资源(共享内存)。
if(0 == strcmp(ptr, "end")) //如果内存中的字符串是end,循环结束。
break;
}
shmdt(ptr); //释放申请的共享内存区域。
sem_del(semid); //删除信号量集。
return 0;
}