请教:多进程什么是共享内存存问题

关于在 Linux 下多个不相干的进程互斥访问同一片共享内存的问题,记录锁,好文章
关于在 Linux 下多个不相干的进程互斥访问同一片共享内存的问题,记录锁,好文章
[摘要:/a/0435 http://blog.csdn.net/luansxx/article/details/7736618 那里的没有干系,界说为: 那几个过程出有女子干系,也出有 Server/Client 干系 那一片同享内存一最先没有存正在,第]
http://blog.csdn.net/luansxx/article/details/7736618
这里的&不相干&,定义为:
这几个进程没有父子关系,也没有 Server/Client 关系
这一片共享内存一开始不存在,第一个要访问它的进程负责新建
也没有额外的 daemon 进程能管理这事情
看上去这是一个很简单的问题,实际上不简单。有两大问题:
如果用传统 IPC 的 semget 那套接口,是没法解决的。实测发现,down 了以后进程退出,信号量的数值依然保持不变。
用 pthread (2013年的)新特性可以解决。在创建 pthread mutex 的时候,指定为 ROBUST 模式。
pthread_mutexattr_t
pthread_mutexattr_init(&ma);
pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(&c-&lock, &ma);
注意,pthread 是可以用于多进程的。指定 PTHREAD_PROCESS_SHARED 即可。
关于 ROBUST,官方解释在:
http://pubs.opengroup.org/onlinepubs//functions/pthread_mutexattr_setrobust.html
需要注意的地方是:
如果持有 mutex 的线程退出,另外一个线程在 pthread_mutex_lock 的时候会返回 EOWNERDEAD。这时候你需要调用 pthread_mutex_consistent 函数来清除这种状态,否则后果自负。
写成代码就是这样子:
int r = pthread_mutex_lock(lock);
if (r == EOWNERDEAD)
pthread_mutex_consistent(lock);
所以要使用这个新特新的话,需要比较新的 GCC ,要 2013 年以后的版本。
好了第一个问题解决了。我们可以在初始化共享内存的时候,新建一个这样的 pthread mutex。但是问题又来了:
这个问题看上去简单至极,不过如果用这样子的代码:
void *p = get_shared_mem();
if (p == NULL)
p = create_shared_mem_and_init_mutex();
lock_shared_mem(p);
是不严谨的。如果共享内存初始化成全 0,那可能碰巧还可以。但我们的 mutex 也是放到共享内存里面的,是需要 init 的。
想象一下四个进程同时执行这段代码,很可能某两个进程发现共享内存不存在,然后同时新建并初始化信号量。某一个 lock 了 mutex,然后另外一个又 init mutex,就乱了。
可见,在 init mutex 之前,我们就已经需要 mutex 了。问题是,哪来这样的 mutex?前面已经说了传统 IPC 没法解决第一个问题,所以也不能用它。
其实,Linux 的文件系统本身就有这样的功能。
首先 shm_open 那一系列的函数是和文件系统关联上的。
~ ll /dev/shm/
其实 /dev/shm 是一个 mount 了的文件系统。这里面放的就是一堆通过 shm_open 新建的共享内存。都是以文件的形式展现出来。可以 rm,rename,link 各种文件操作。
其实 link 函数,也就是硬链接。是完成&原子操作&的关键所在。
搞过汇编的可能知道 CMPXCHG 这类(两个数比较,符合条件则交换)指令,是原子操作内存的最底层指令,最底层的信号量是通过它实现的。
而 link 系统调用,类似的,是系统调用级,原子操作文件的最底层指令。处于 link 操作中的进程即便被 kill 掉,在内核中也会完成最后一次这次系统调用,对文件不会有影响,不存在 &link 了一半& 这种状态,它是&原子&的。
伪代码如下:
shm_open("ourshm_tmp", ...);
首先新建初始化一份副本。然后用 link 函数。
最后无论如何都要 unlink 掉副本。
这两种方法,貌似在各类经典书籍中都没提及,因为是 2013 年新出的,也是因为 Unix 鼓励用管道进行这类通信的原因。
在同类开源项目中。D-Bus 用的是另外的 daemon 进程去管理 socket。Android 的 IPC 则用了另外的内核模块(netlink 接口)来完成。
总之,都是用了额外的接口。
因此我开发了不需要额外 daemon 的轻量级 IPC 通信框架&kbz-event。
欢迎各种围观!
===============================================================
进程间共享数据的保护,需要进程互斥锁。与线程锁不同,进程锁并没有直接的C库支持,但是在Linux平台,要实现进程之间互斥锁,方法有很多,大家不妨回忆一下你所了解的。下面就是标准C库提供的一系列方案。
1、实现方案
不出意外的话,大家首先想到的应该是信号量(Semaphores)。对信号量的操作函数有两套,一套是Posix标准,另一套是System V标准。
Posix信号量
[cpp]&view plaincopy
sem_t&*sem_open(const&char&*name,&int&oflag);&&
sem_t&*sem_open(const&char&*name,&int&oflag,&mode_t&mode,&unsigned&int&value);&&
int&sem_init(sem_t&*sem,&int&pshared,&unsigned&int&value);&&
int&sem_wait(sem_t&*sem);&&
int&sem_trywait(sem_t&*sem);&&
int&sem_timedwait(sem_t&*sem,&const&struct&timespec&*abs_timeout);&&
int&sem_close(sem_t&*sem);&&
int&sem_destroy(sem_t&*sem);&&
int&sem_unlink(const&char&*name);&&
System V信号量
[cpp]&view plaincopy
int&semget(key_t&key,&int&nsems,&int&semflg);&&
int&semctl(int&semid,&int&semnum,&int&cmd,&...);&&
int&semop(int&semid,&struct&sembuf&*sops,&unsigned&nsops);&&
int&semtimedop(int&semid,&struct&sembuf&*sops,&unsigned&nsops,&struct&timespec&*timeout);&&
线程锁共享
其实还有另外一个方案:线程锁共享。这是什么呢,我估计了解它的人不多,如果你知道的话,那可以称为Linux开发牛人了。
线程锁就是pthread那一套C函数了:
[html]&view plaincopy
int&pthread_mutex_init&(pthread_mutex_t&*mutex,&const&pthread_mutexattr_t&*mutexattr);&&
int&pthread_mutex_destroy&(pthread_mutex_t&*mutex);&&
int&pthread_mutex_trylock&(pthread_mutex_t&*mutex);&&
int&pthread_mutex_lock&(pthread_mutex_t&*mutex);&&
int&pthread_mutex_timedlock&(pthread_mutex_t&*restrict&mutex,&const&struct&timespec&*restrict&abstime);&&
int&pthread_mutex_unlock&(pthread_mutex_t&*mutex);&&
但是这只能用在一个进程内的多个线程实现互斥,怎么应用到多进程场合呢,被多个进程共享呢?
很简单,首先需要设置互斥锁的进程间共享属性:
[html]&view plaincopy
int&pthread_mutexattr_setpshared(pthread_mutexattr_t&*mattr,&int&pshared);&&&
pthread_mutexattr_t&&&&
pthread_mutexattr_init(&mattr);&&&
pthread_mutexattr_setpshared(&mattr,&PTHREAD_PROCESS_SHARED);&&&
其次,为了达到多进程共享的需要,互斥锁对象需要创建在共享内存中。
最后,需要注意的是,并不是所有Linux系统都支持这个特性,程序里需要检查是否定义了_POSIX_SHARED_MEMORY_OBJECTS宏,只有定义了,才能用这种方式实现进程间互斥锁。
2、平台兼容性
我们来看看这三套方案的平台移植性。
绝大部分嵌入式Linux系统,glibc或者uclibc,不支持_POSIX_SHARED_MEMORY_OBJECTS;
绝大部分嵌入式Linux系统,不支持Posix标准信号量;
部分平台,不支持System V标准信号量,比如Android。
3、匿名锁与命名锁
当两个(或者多个)进程没有特殊关系(比如父子进程共享)时,我们只能通过约定好的名字来访问同一个锁,这就是命名锁。然而,如果我们有其他途径定位一个锁,那么匿名锁是更好的选择。这三套方案是否都支持匿名锁与命名锁呢?
Posix信号量
通过sem_open创建命名锁,通过sem_init创建匿名锁,其实sem_init也支持进程内部锁。
System V信号量
semget中的key参数可以看成是名字,所以支持命名锁。该方案不支持匿名锁。
线程锁共享
不支持命名锁,支持匿名锁。
在匿名锁与命名锁的支持上,一些方案是有不足的,但这还是小问题,更严重的问题是异常状况下的死锁问题。
与多线程环境不一样的是,在多进程环境中,一个进程的异常退出不会影响其他进程,但是如果使用了进程互斥锁呢?假如一个进程获取了互斥锁,但是在访问互斥资源的代码中crash了,或者遇到信号退出了,那么其他等待同一个锁的进程(内部某个线程)就挂死了。在多线程环境中,程序异常整个进程退出,不需要考虑异常时锁的释放,多进程环境则是一个实实在在的问题。
System V信号量通过UNDO方式可以解决该问题。但是如果考虑到平台兼容性等问题,这三个方案仍不能满足需求,我会接着介绍一种更好的方案。
========================================================================
http://blog.csdn.net/luansxx/article/details/7737899
在《进程互斥锁》中,我们看到了实现进程互斥锁的几个常见方案:Posix信号量、System V信号量以及线程锁共享,并且分析了他们的平台兼容性以及严重缺陷。这里要介绍
一种安全且平台兼容的进程互斥锁,它是基于文件记录锁实现的。
1、文件记录锁
UNIX编程的&圣经&《Unix环境高级编程》中有对文件记录锁的详细描述。
下载链接:/club/computer/Unix环境高级编程.rar
记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。对于UNIX,&记录&这个定语也是误用,因为UNIX内核根本没有使用文件记录这种概念。一个更适合的术语可能是&区域锁&,因为它锁定的只是文件的一个区域(也可能是整个文件)。
2、平台兼容性
各种UNIX系统支持的记录锁形式:&
4.3BSDReno
可以看成,记录锁在各个平台得到广泛支持。特别的,在接口上,可以统一于fcntl。
建议性锁和强制性锁之间的区别,是指其他文件操作函数(如open,read、write)是否受记录锁影响,如果是,那就是强制性的记录锁,大部分平台只是建议性的。不过,对实现进程互斥锁而言,这个影响不大。
3、接口描述
[cpp]&view plaincopy
#include&&sys/types.h&&&
#include&&unistd.h&&&
#include&&fcnt1.h&&&
int&fcnt1(int&filedes,&int&cmd,&...
对于记录锁,cmd是F_GETLK、F_SETLK或F_SETLKW。第三个参数(称其为flockptr)是一个指向flock结构的指针。
[cpp]&view plaincopy
struct&flock&{&&
&&&&short&l_&&&&
&&&&short&l_&&
&&&&off_t&l_&&&
&&&&off_t&l_&&&&&
&&&&pid_t&l_&&&&&/*&PID&of&process&blocking&our&lock&&
以下说明fcntl函数的三种命令:
F_GETLK决定由flockptr所描述的锁是否被另外一把锁所排斥(阻塞)。如果存在一把锁,它阻止创建由flockptr所描述的锁,则这把现存的锁的信息写到flockptr指向的结构中。如果不存在这种情况,则除了将ltype设置为F_UNLCK之外,flockptr所指向结构中的其他信息保持不变。
F_SETLK设置由flockptr所描述的锁。如果试图建立一把按上述兼容性规则并不允许的锁,则fcntl立即出错返回,此时errno设置为EACCES或EAGAIN。
F_SETLKW这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。如果由于存在其他锁,那么按兼容性规则由flockptr所要求的锁不能被创建,则调用进程睡眠。如果捕捉到信号则睡眠中断。
4、实现方案
如何&基于记录锁实现进程互斥锁?
1、需要一个定义全局文件名,这个文件名只能有相关进程使用。这需要在整个系统做一个规划。
2、规定同一个进程互斥锁对应着该文件的一个字节,字节位置称为锁的编号,这样可以用一个文件实现很多互斥锁。
3、编号要有分配逻辑,文件中要记录已经分配的编号,这个逻辑也要保护,所以分配0号锁为系统锁。
4、为了实现命名锁,文件中要记录名称与编号对应关系,这个对应关系的维护也需要系统锁保护。
这些逻辑都实现在一个FileLocks类中:
[cpp]&view plaincopy
class&FileLocks&&
&&&&FileLocks();&&
&&&&~FileLocks();&&
&&&&size_t&alloc_lock();&&
&&&&size_t&alloc_lock(std::string&const&&&keyname);&&
&&&&void&lock(size_t&pos);&&
&&&&bool&try_lock(size_t&pos);&&
&&&&void&unlock(size_t&pos);&&
&&&&void&free_lock(size_t&pos);&&
&&&&void&free_lock(std::string&const&&&keyname);&&
private:&&
&&&&int&&&&&&&&&&&&&m_fd_;&&
inline&FileLocks&&&global_file_lock()&&
&&&&static&FileLocks&g_fileblocks(&"process.filelock"&);&&
&&&&return&g_&&
这里用了一个FileLocks全局单例对象,它对应的文件名是&/tmp/filelock&,在FileLocks中,分别用alloc()和alloc(keyname)分配匿名锁和命名锁,用free_lock删除锁。free_lock(pos)删除匿名锁,free_lock(keyname)删除命名锁。
对锁的使用通过lock、try_lock、unlock实现,他们都带有一个pos参数,代表锁的编号。
有了FileLocks类作为基础,要实现匿名锁和命名锁就很简单了。
4.1、匿名锁
[cpp]&view plaincopy
class&FileMutex&&
&&&&FileMutex()&&
&&&&&&&&:&m_lockbyte_(global_file_lock().alloc_lock())&&
&&&&~FileMutex()&&
&&&&&&&&global_file_lock().free_lock(m_lockbyte_);&&
&&&&void&lock()&&
&&&&&&&&global_file_lock().lock(m_lockbyte_);&&
&&&&bool&try_lock()&&
&&&&&&&&return&global_file_lock().try_lock(m_lockbyte_);&&
&&&&void&unlock()&&
&&&&&&&&global_file_lock().unlock(m_lockbyte_);&&
protected:&&
&&&&size_t&m_lockbyte_;&&
需要注意的是,进程匿名互斥锁需要创建在共享内存上。只需要也只能某一个进程(比如创建共享内存的进程)调用构造函数,其他进程直接使用,同样析构函数也只能调用一次。
4.2、命名锁
&命名锁只需要构造函数不同,可以直接继承匿名锁实现
[cpp]&view plaincopy
class&NamedFileMutex&&&
&&&&:&public&FileMutex&&
&&&&NamedFileMutex(std::string&const&&&key)&&
&&&&&&&&:&m_lockbyte_(global_file_lock().alloc_lock(key))&&
&&&&~NamedFileMutex()&&
&&&&&&&&m_lockbyte_&=&0;&&
需要注意,命名锁不住析构时删除,因为可能多个对象共享该锁。
5、线程安全性
感谢关注 Ithao123精品文库频道,是专门为互联网人打造的学习交流平台,全面满足互联网人工作与学习需求,更多互联网资讯尽在 IThao123!
Laravel是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来;它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁、富于表达力。
Hadoop是一个由Apache基金会所开发的分布式系统基础架构。
用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。
Hadoop实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS。HDFS有高容错性的特点,并且设计用来部署在低廉的(low-cost)硬件上;而且它提供高吞吐量(high throughput)来访问应用程序的数据,适合那些有着超大数据集(large data set)的应用程序。HDFS放宽了(relax)POSIX的要求,可以以流的形式访问(streaming access)文件系统中的数据。
Hadoop的框架最核心的设计就是:HDFS和MapReduce。HDFS为海量的数据提供了存储,则MapReduce为海量的数据提供了计算。
产品设计是互联网产品经理的核心能力,一个好的产品经理一定在产品设计方面有扎实的功底,本专题将从互联网产品设计的几个方面谈谈产品设计
随着国内互联网的发展,产品经理岗位需求大幅增加,在国内,从事产品工作的大部分岗位为产品经理,其实现实中,很多从事产品工作的岗位是不能称为产品经理,主要原因是对产品经理的职责不明确,那产品经理的职责有哪些,本专题将详细介绍产品经理的主要职责
IThao123周刊具体用处呢,有很多,比如多进程浏览器共享Cookie啦,多个进程传送点数据啦.
共享内存封装.
封装成了MemoryStream的形式.
ms : TShareMemS
ms := TShareMemStream.Create('Global\test', FILE_MAP_ALL_ACCESS, 4096);
if (ms.Memory && nil)(*and(ms.AlreadyExists)*) then
//如果创建失败Memory指针是空指针
//AlreadyExists表示已经存在了,也就是之前被别人(也许是别的进程)创建过了.
//获取锁,多个进程线程访问安全访问
if ms.GetLock(INFINITE) then
ms.read(...);
ms.write(...);
ms.ReleaseLock();
unit ShareMemoryS
SysUtils, Classes, Syncobjs,
TShareMemStream = class(TCustomMemoryStream)
FSize: Int64;
FEvent: TE
FAlreadyExists: B
property Event: TEvent read FE
constructor Create(const ShareName:
ACCESS: DWORD = FILE_MAP_ALL_ACCESS; ASize: Int64 = 16 * 1024 * 1024);
destructor D
function Write(const B Count: Integer): L
function GetLock(ATimeOut: DWORD = INFINITE): B
procedure ReleaseLock();
property AlreadyExists: Boolean read FAlreadyE
implementation
procedure InitSecAttr(var sa: TSecurityA var sd: TSecurityDescriptor);
sa.nLength := sizeOf(sa);
sa.lpSecurityDescriptor := @
sa.bInheritHandle :=
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@sd, true, nil, false);
{ TShareMem }
constructor TShareMemStream.Create(const ShareName: ACCESS: DWORD;
ASize: Int64);
sa: TSecurityA
sd: TSecurityD
lprotect: DWORD;
FEvent := TEvent.Create(nil, false, true, ShareName +
'_TShareMemStream_Event');
FSize := AS
InitSecAttr(sa, sd);
ACCESS := ACCESS and (not SECTION_MAP_EXECUTE);
if (ACCESS and FILE_MAP_WRITE) = FILE_MAP_WRITE then
lprotect := PAGE_READWRITE
else if (ACCESS and FILE_MAP_READ) = FILE_MAP_READ then
lprotect := PAGE_READONLY;
FFile := CreateFileMapping(INVALID_HANDLE_VALUE, @sa, lprotect,
Int64Rec(FSize).Hi, Int64Rec(FSize).Lo, PChar(ShareName));
e := GetLastE
if FFile = 0 then
FAlreadyExists := e = ERROR_ALREADY_EXISTS;
SetPointer(MapViewOfFile(FFile, ACCESS, 0, 0, Int64Rec(FSize).Lo),
Int64Rec(FSize).Lo);
destructor TShareMemStream.D
if Memory && nil then
UnmapViewOfFile(Memory);
SetPointer(nil, 0);
Position := 0;
if FFile && 0 then
CloseHandle(FFile);
FFile := 0;
inherited D
function TShareMemStream.GetLock(ATimeOut: DWORD): B
wr : TWaitR
wr := FEvent.WaitFor(ATimeOut);
Result := wr = wrS
procedure TShareMemStream.ReleaseL
FEvent.SetE
function TShareMemStream.Write(const B Count: Integer): L
Result := 0;
if (Size - Position) &= Count then
System.Move(Buffer, PByte(Memory)[Position], Count);
Position := Position + C
Result := C
此条目发表在分类目录,贴了, , 标签。将加入收藏夹。7745人阅读
时隔一年再次用到 cout 的时候,哥潸然泪下,这是一种久别重逢的感动,虽然基本忘光了。趁着有大把时间,再把生产者消费者问题巩固一下,用纯C吧。珍惜能写代码的幸福时光。
生产者和消费者问题是多个相互合作的进程之间的一种抽象。生产者和消费者之间的关系:
1. &对缓冲区的访问是互斥的。由于两者都会修改缓冲区,因此,一方修改缓冲区时,另一方不能修改,这就是互斥。
2. &一方的行为影响另一方。缓冲区不空,才能消费,何时不空?生产了就不空;缓冲区满,就不能生产,何时不满?消费了就不满。这是同步关系。
为了描述这种关系,一方面,使用共享内存代表缓冲区;另一方面,使用 互斥信号量 控制对缓冲区的访问,使用同步信号量描述两者的依赖关系。
三. 共享存储
共享存储是进程间通信的一种手段,通常,使用信号量同步或互斥访问共享存储。共享存储的原理是将进程的地址空间映射到一个共享存储段。在LINUX下,通过使用 shmget 函数创建或者获取共享内存。
1)不指定 KEY
// IPC_PRIVATE指出需要创建内存;&
//SHM_SIZE 指出字节大小;&
//SHM_MODE 指出访问权限字如 0600表示,用户可以读写该内存
int shmget(key_t&IPC_PRIVATE,size_t SHM_SIZE,int SHM_MODE);
2)指定KEY
//如果SHM_KEY指向的共享存储已经存在,则返回共享存储的ID;&
//否则,创建共享存储并返回其ID
int &shmget(key_t SHM_KEY,size_t SHM_SIZE,int SHM_MODE);
只需要共享存储的 ID 就可以通过 &shmat &函数获得共享存储所占用的实际地址。因此,可以在父进程的栈中用变量存放指向共享存储的指针,那么 fork 之后,子进程就可以很方便地通过这个指针访问共享存储了。
如果进程之间并没有父子关系,但是协商好了共享存储的 KEY , 那么在每个进程中,就可以通过 KEY 以及 shmget 函数获得共享存储的 I D , 进而通过 shmat 函数获得共享存储的实际地址,最后访问。
在我的实现中,我把生产者实现为父进程,消费者实现为子进程,并通过方法一实现进程之间共享内存。
四. 信号量集
信号量有两种原语 P 和 V ,P 锁定资源,V 释放资源。LINUX 下的使用信号量集合的接口特别复杂。我所用到的函数如下:
1. 创建或者获取信号量集合
// IPC_PRIVATE 表示创建信号量集, NUM_OF_SEM表示该集合中有多少信号量; FLAGS复杂不追究
semget(IPC_PRIVATE, NUM_OF_SEM, FLAGS );
// SEM_KEY 是 key_t 类型
//如果 SEM_KEY 代表的信号量集存在,则返回信号量集的ID
//如果不存在,则创建信号量集并返回ID
semget(SEM_KEY, NUM_OF_SEM,FLAGS);
2. 初始化信号量
创建的过程并未指定信号量的初始值,需要使用 semctl 函数指定。
semctl(int semSetId , int semIdx , int cmd, union semun su);
其中 semSetId 是指信号量集的 ID , semIdx 指信号量集中某个信号量的索引(从零开始), 如果是要设置信号量的值, 填 SETVAL 即可, 为了设置信号量的值,可以指定su.val为索要设置的值。
我在 UBUNTU 下使用 union semun 编译时总报错:
invalid use of undefined type ‘union semun’
据说是 Linux 下删除了 semun 的定义。可以通过自定义 semun 解决:
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
&sys/sem.h&
union semun{
struct semid_ds *
unsigned short *
五. 代码分解
#include &stdio.h&
//支持 printf
#include &sys/shm.h& //支持 shmget shmat 等
#include &sys/sem.h& //支持 semget
#include &stdlib.h&
//支持 exit
共需要三个信号量:
第一个信号量用于限制生产者必须在缓冲区不满时才能生产,是同步信号量
第二个信号量用于限制消费者必须在缓冲区有产品时才消费,是同步信号量
第三个信号量用于限制生产者和消费者在访问缓冲区时必须互斥,是互斥信号量
创建信号量集合,semget
if((semSetId = semget(IPC_PRIVATE,3,SEM_MODE)) & 0)
perror(&create semaphore failed&);
初始化三个信号量,semctl,需要用到 union semun&
//信号量初始化,其中 su 表示 union semun
su.val = N_BUFFER;//当前库房还可以接收多少产品
if(semctl(semSetId,0,SETVAL, su) & 0){
perror(&semctl failed&);
su.val = 0;//当前没有产品
if(semctl(semSetId,1,SETVAL,su) & 0){
perror(&semctl failed&);
su.val = 1;//为1时可以进入缓冲区
if(semctl(semSetId,2,SETVAL,su) & 0){
perror(&semctl failed&);
封装对信号量集中的某个信号量的值的+1或者-1操作
//semSetId 表示信号量集合的 id
//semNum 表示要处理的信号量在信号量集合中的索引
void waitSem(int semSetId,int semNum)
sb.sem_num = semN
sb.sem_op = -1;//表示要把信号量减一
sb.sem_flg = SEM_UNDO;//
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if(semop(semSetId,&sb,1) & 0){
perror(&waitSem failed&);
void sigSem(int semSetId,int semNum)
sb.sem_num = semN
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if(semop(semSetId,&sb,1) & 0){
perror(&waitSem failed&);
3. 使用共享内存
//把共享存储区域中的内容用结构体封装起来
struct ShM{
//缓冲区分配以及初始化
if((shmId = shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE)) & 0)
perror(&create shared memory failed&);
//shmat返回void*指针需要强制转化类型
pSM = (struct ShM *)shmat(shmId,0,0);
//初始化工作
pSM-&start = 0;
pSM-&end = 0;
4. 生产过程
waitSem(semSetId,0);//获取一个空间用于存放产品
waitSem(semSetId,2);//占有产品缓冲区
produce();
sigSem(semSetId,2);//释放产品缓冲区
sleep(1);//每两秒生产一个
sigSem(semSetId,1);//告知消费者有产品了
5. 消费过程
waitSem(semSetId,1);//必须有产品才能消费
waitSem(semSetId,2);//锁定缓冲区
consume();//获得产品,需要修改缓冲区
sigSem(semSetId,2);//释放缓冲区
sigSem(semSetId,0);//告知生产者,有空间了
sleep(2);//消费频率
六. 代码全文
#include &stdio.h&
#include &sys/shm.h&
#include &sys/sem.h&
#include &stdlib.h&
#define SHM_SIZE ()
#define SHM_MODE 0600
#define SEM_MODE 0600
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
&sys/sem.h&
union semun{
struct semid_ds *
unsigned short *
struct ShM{
const int N_CONSUMER = 3;//消费者数量
const int N_BUFFER = 5;//缓冲区容量
int shmId = -1,semSetId=-1;
//sem union,用于初始化信号量
//semSetId 表示信号量集合的 id
//semNum 表示要处理的信号量在信号量集合中的索引
void waitSem(int semSetId,int semNum)
sb.sem_num = semN
sb.sem_op = -1;//表示要把信号量减一
sb.sem_flg = SEM_UNDO;//
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if(semop(semSetId,&sb,1) & 0){
perror(&waitSem failed&);
void sigSem(int semSetId,int semNum)
sb.sem_num = semN
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if(semop(semSetId,&sb,1) & 0){
perror(&waitSem failed&);
//必须在保证互斥以及缓冲区不满的情况下调用
void produce()
int last = pSM-&
pSM-&end = (pSM-&end+1) % N_BUFFER;
printf(&生产 %d\n&,last);
//必须在保证互斥以及缓冲区不空的情况下调用
void consume()
int last = pSM-&
pSM-&start = (pSM-&start + 1)%N_BUFFER;
printf(&消耗 %d\n&,last);
void init()
//缓冲区分配以及初始化
if((shmId = shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE)) & 0)
perror(&create shared memory failed&);
pSM = (struct ShM *)shmat(shmId,0,0);
pSM-&start = 0;
pSM-&end = 0;
//信号量创建
//第一个:同步信号量,表示先后顺序,必须有空间才能生产
//第二个:同步信号量,表示先后顺序,必须有产品才能消费
//第三个:互斥信号量,生产者和每个消费者不能同时进入缓冲区
if((semSetId = semget(IPC_PRIVATE,3,SEM_MODE)) & 0)
perror(&create semaphore failed&);
//信号量初始化,其中 su 表示 union semun
su.val = N_BUFFER;//当前库房还可以接收多少产品
if(semctl(semSetId,0,SETVAL, su) & 0){
perror(&semctl failed&);
su.val = 0;//当前没有产品
if(semctl(semSetId,1,SETVAL,su) & 0){
perror(&semctl failed&);
su.val = 1;//为1时可以进入缓冲区
if(semctl(semSetId,2,SETVAL,su) & 0){
perror(&semctl failed&);
int main()
int i = 0,child = -1;
//创建 多个(N_CONSUMER)消费者子进程
for(i = 0; i & N_CONSUMER; i++)
if((child = fork()) & 0)//调用fork失败
perror(&the fork failed&);
else if(child == 0)//子进程
printf(&我是第 %d 个消费者子进程,PID = %d\n&,i,getpid());
waitSem(semSetId,1);//必须有产品才能消费
waitSem(semSetId,2);//锁定缓冲区
consume();//获得产品,需要修改缓冲区
sigSem(semSetId,2);//释放缓冲区
sigSem(semSetId,0);//告知生产者,有空间了
sleep(2);//消费频率
//父进程开始生产
if(child & 0)
waitSem(semSetId,0);//获取一个空间用于存放产品
waitSem(semSetId,2);//占有产品缓冲区
produce();
sigSem(semSetId,2);//释放产品缓冲区
sleep(1);//每两秒生产一个
sigSem(semSetId,1);//告知消费者有产品了
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:61045次
排名:千里之外
原创:30篇
(1)(1)(2)(10)(8)(1)(1)(5)(1)(2)(1)(1)

我要回帖

更多关于 什么是共享内存 的文章

 

随机推荐