【共享内存和信号量】

一.共享内存介绍

(一).什么是共享内存

共享内存本质上就是内存中的一块区域,用于进程间通信使用。该内存空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据块,同时还会创建相应结构体来记录该共享内存属性,以便于管理。

因此,共享内存不只有一份,可以根据需求申请多个。

进程之间进行通信的时候,会获取 到共享内存的地址,写端进程写入数据,读端进程通过直接访问内存完成数据读取。
(二).共享内存优点

相比于管道而言,共享内存不仅能够用于非父子进程之间的通信,而且访问数据的速度也比管道要快。这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。

在这里插入图片描述①key

shmget会根据key值创建一个共享内存,因此当创建多个共享内存时,每一个key值要独一无二。

获得key值可以使用库函数ftok专门获取一个独一无二的key_t类型值

②size

该参数用于确定共享内存大小。

一般而言是4096的整数倍,因为内存的块的大小就是4KB即4096B。因此即便我们需要的空间大小不是块大小的整数倍,操作系统实际上也还是分配块的倍数个。但在使用时,那些超过size大小的多余分配空间不能访问。

③shmflg

该参数用于确定共享内存属性。

使用上为:标志位 | 内存权限

标志位参数有两种:IPC_CREAT、IPC_EXCL

常用使用方式有两种:
方式 含义
shmget(…, IPC_CREAT | 权限) 创建失败不报错返回已有shmid
shmget(…, IPC_CREAT | IPC_EXCL | 权限) 创建失败报错返回-1

(二).连接—shmat

创建共享内存后还不能直接使用,需要找到内存地址后才能使用,即连接。
在这里插入图片描述
shmid即shmget返回值。

shmaddr用于确定将共享内存挂在进程虚拟地址哪个位置,一般填NULL即可代表让内核自己确定位置。

shmflg用于确定挂接方式,一般填0。

连接成功返回共享内存在进程中的起始地址,失败返回-1。
.读取与写入

(三)调用shmat后会返回一个地址,读端直接读取该地址数据,写端直接向该地址写入即可。

//读端, 将共享内存数据读取到文件,此处为显示器文件
char* p = (char*)shmat(...);
write(1, p, sizeof p);

//写端,将文件中数据写入共享内存,此处为键盘文件
char* p = (char*)shmat(...);
read(0, p, 4096);

信号量的概念

信号量本质是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。 

每个执行流在进入临界区之前都应该先申请信号量,申请成功就有了操作特点的临界资源的权限,当操作完毕后就应该释放信号量。

在这里插入图片描述

信号量的工作原理

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

(1)P(sv):我们将申请信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减去一,因此P操作的本质就是让计数器减一,如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

(2)V(sv):我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一,因此V操作本质就是让计数器加一,如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给他加1

与信号量有关的函数
(1)创建/获取一个信号量集合:

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       int semget(key_t key, int nsems, int semflg);

功能:创建/获取一个信号量集合
参数:

    key:可以用函数key_t ftok(const char *pathname, int proj_id);来获取。
    nsems:这个参数表示你要创建的信号量集合中的信号量的个数。sys v版本中信号量只能以集合的形式创建。
    semflg:
    同时使用IPC_CREAT和IPC_EXCL则会创建一个新的信号量集合。若已经存在的话则返回-1。
    单独使用IPC_CREAT的话会返回一个新的或者已经存在的信号量集合
在这个函数中我们可以删除信号量或初始化信号量。
#include<sys/sem.h>
int  semctl(int _semid  ,int _semnum,int _cmd  ……);

功能:控制信号量的信息。

返回值:成功返回0,失败返回-1;

参数:

semid 信号量的标志码(ID),也就是semget()函数的返回值;

_semnum, 操作信号在信号集中的编号。从0开始。

_cmd 命令,表示要进行的操作。

第四个参数:semctl()在semid标识的信号量集合上执行cmd指定的控制命令,或者在该信号量集合上第semnum个信号量上执行cmd指定的控制命令。
根据cmd不同,这个函数有三个或四个参数,当有第四个参数时,第四个参数的类型是union。
union semun{
int val;   //使用的值
struct semid_ds *buf;  //IPC_STAT、IPC_SET使用缓存区
unsigned short *array; //GETALL、SETALL使用的缓存区
struct seminfo *__buf; //IPC_INFO(linux特有)使用缓存区
};

该联合体没有定义在任何系统头⽂件中,因此得⽤户⾃⼰声明。
(Centos 是这样,但是UNIX下不需要⾃⼰定义声明)

参数cmd中可以使用的命令如下:

IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。

IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。

IPC_RMID将信号量集从内存中删除。

GETALL用于读取信号量集中的所有信号量的值。

GETNCNT返回正在等待资源的进程数目。

GETPID返回最后一个执行semop操作的进程的PID。

GETVAL返回信号量集中的一个单个的信号量的值。

GETZCNT返回这在等待完全空闲的资源的进程数目。

SETALL设置信号量集中的所有的信号量的值。

SETVAL设置信号量集中的一个单独的信号量的值。
int init_sem(int semid, int num, int val)
{
	union semun myun;
	myun.val = val;
	if(semctl(semid, num, SETVAL, myun) < 0)
	{
		perror("semctl");
		exit(1);
	}
	return 0;
}
struct   sembuf
 {
     unsigned short  sem_num;//当前操作的信号在信号集中的编号,第一个信号量为0;
     short  sem_op;//对该信号量的操作。
     short _semflg;
}; 

(1)sem_num:因为信号量是以集合的形式存在的,就相当于所有信号在一个数组里面,sem_num代表当前操作的信号在信号集中的编号; 第一个信号的编号为0;
(2)sem_op:
如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权(v操作);
如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;(p操作)
如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
(3)sem_flg:信号操作标志,它的取值有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
IPC_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

信号量结合操作

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       int semop(int semid, struct sembuf *sops, unsigned nsops);

功能:用户改变信号量的值。也就是使用资源还是释放资源 使用权。

返回值:成功返回0,失败返回-1;

参数:
_semid : 信号量的标识码。也就是semget()的返回值。
sops:是一个指向结构体数组的指针。
nsops:表示要操作信号量的个数。因为信号量是以集合的形式存在,所以第二个参数可以传一个数组,同时对一个集合中的多个信号量进行操作。 恒大于或等于1。

int sem_p(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = -1;
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0){
		perror("semop");
		exit(1);
	}

	return 0;
}
int sem_v(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = 1;
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0){
		perror("semop");
		exit(1);
	}
	return 0;
}

运用信号量与共享内存的配合使用,可以完美实现共享内存写入与读取,无需再进行延时操作。

  • 20
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值