目录
1 线程同步机制:互斥量
1.1 Linux系统中多任务(进程/线程)之间的关系
- 独立:仅竞争CPU资源
- 互斥:竞争除CPU外的其他资源
- 同步:协调彼此运行的步调,保证协同运行的各个任务具有正确的执行次序
- 通信:数据共享,彼此间传递数据或信息,以协同完成某项工作
1.2 线程的数据共享
◼线程间共享的数据和资源:进程代码段、进程中的全局变量、
进程打开的文件……
◼每个线程私有的数据和资源:线程
ID
、线程上下文(一组寄存器值的集合)、线程局部变量(存储在栈中)
1.3 任务的互斥问题
◼ 任务互斥—
资源共享关系(间接相互制约关系)
- 任务本身之间不存在直接联系。一个任务正在使用某个系统资源,另外一个想用该资源的任务就必须等待,而不能同时使用
◼
全局变量存储在进程数据段中,被线
程所共享。线程对全局变量的访问,
要经历三个步骤
- 将内存单元中的数据读入寄存器
- 对寄存器中的值进行运算
- 将寄存器中的值写回内存单元
1.4 临界资源
◼临界资源
:
在一段时间内只允许一个任务(线程或
进程)访问的资源。诸任务间应采取互斥方式,实现对资源的共享
◼共享变量,打印机等属于临界资源
◼访问临界资源的那段代码被称为临界区
1.5 互斥量
◼确保同一时间里只有一个线程访问临界资源或进入临界区
◼互斥量(
mutex)本质上是一把锁
- 在访问临界资源前,对互斥量进行加锁
- 在访问完成后对互斥量解锁
- 对互斥量加锁后,任何其他试图对互斥量加锁的线程将会被阻塞,直到互斥量被解锁为止
1.5.1 互斥量的操作
◼
定义互斥量变量
- pthread_mutex_t mutex;
◼
初始化互斥量
◼
访问临界资源前对互斥量加锁
◼
访问临界资源后对互斥量解锁
◼
销毁互斥量变量
1.5.2 互斥量初始化
◼
头文件:
pthread.h
◼
静态初始化
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER
◼
动态初始化
⚫
函数原型
- int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
⚫
参数与返回值
- mutex:指向互斥量的指针
- attr:设置互斥量的属性,通常可采用默认属性,传入空指针(NULL)。
- 成功返回0,出错返回错误码
1.5.3 互斥量销毁
◼
互斥量在使用完毕后,必须要对互斥量进行销毁,以释
放资源
◼
函数原型
头文件:
pthread.h
int pthread_mutex_destroy(pthread_mutex_t *mutex);
◼
参数与返回值
- mutex:即互斥量
- 成功返回0,出错返回错误码
1.5.4 互斥量的加锁和解锁操作
◼在对临界资源访问之前和访问之后,需要对互斥量进行加锁和解锁操作
◼
函数原型
头文件:
pthread.h
int pthread_mutex_lock(pthread_mutex_t *mutex);
Int pthread_mutex_unlock(pthread_mutex_t *mutex);
◼当调用
pthread_mutex_lock
时,若互斥量已被加锁,则调用线程将被阻塞直到可以完成加锁操作为止
◼成功返回
0,出错返回错误码
1.5.5 互斥量非阻塞加锁
◼
函数原型
头文件:
pthread.h
int pthread_mutex_trylock(pthread_mutex_t *mutex);
◼
调用该函数时,若互斥量未加锁,则对该互斥量
加锁,返回
0
;若互斥量已加锁,则函数直接返回
错误码
EBUSY
(不会阻塞调用线程)
1.5.6 互斥量的操作总结
◼定义互斥量变量(
pthread_mutex_t 类型)
◼调用
pthread_mutex_init初始化互斥量变量
◼访问临界资源前,调用
pthread_mutex_lock
或者
pthread_mutex_trylock对互斥量进行加锁操作
◼访问临界资源后,调用
pthread_mutex_unlock
对互斥量解锁
◼调用
pthread_mutex_destroy销毁互斥量变量
2 条件变量
2.1 任务的同步问题
◼
任务同步
—
相互合作关系
(直接相互制约关系)
- 两个或多个任务为了合作完成同一个工作,在执行速度或某个确定的时序点上必须相互协调,即一个任务的执行必须依赖另一 个任务的执行情况
2.2 条件变量
◼程序设计中存在这样的情况:多个线程都要访问临界
资源又要相互合作(线程间同时存在互斥关系和同步关系)
◼线程A先执行某操作(例如对全局变量x的修改)后,线程B才能(根据变量x的值判断)执行另一操作(可能是对全局变量x的修改),该如何实现?
◼Linux提供了条件变量机制
- 条件变量与互斥量一起使用时,允许线程以互斥的方式阻塞等待特定条件的发生(同步)
2.3 条件变量的操作
◼
定义条件变量(
pthread_cond_t
类型
)
,
定义互
斥量变量
◼
初始化条件变量,初始化互斥量
◼
触发条件线程
x
- 互斥量加锁->XX操作->触发条件变量->互斥量解锁
◼
等待条件线程
y
- 互斥量加锁->等待条件变量->XX操作->互斥量解锁
◼
销毁条件变量,销毁互斥量变量
2.4 条件变量定义与初始化
◼
静态初始化
- pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
◼
动态初始化
⚫
函数原型
- 头文件:pthread.h
- int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
⚫
参数和返回值
- cond:条件变量
- attr:条件变量属性,若为NULL,则使用默认属性
- 成功返回0;出错返回错误码
2.5 条件变量销毁
◼
函数原型
- 头文件:pthread.h
- int pthread_cond_destroy(pthread_cond_t * cond);
◼
参数和返回值
- cond:条件变量
- 成功返回0;出错返回错误码
2.6 等待条件变量
◼
pthread_cond_wait
函数将使调用线程进入阻塞状态,
直到条件被触发
◼
函数原型
- 头文件:pthread.h
- int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
◼
参数与返回值
- cond:条件变量
- mutex:互斥量
- 成功返回0;出错返回错误码
◼
为什么条件变量需要和互斥量配合使用
- 条件变量的使用场景伴随共享资源的使用,例如全局变量
- 在调用pthread_cond_wait前,需要使互斥量处于加锁状态,这样可以通过原子操作的方式,将调用线程放到该条件变量等待线程队列(临界资源)中
◼
等待条件变量的操作
- 调用pthread_mutex_lock
- 调用pthread_cond_wait
- 调用pthread_mutex_unlock
◼
调用
pthread_cond_wait
函数后内核自动执行的
操作:
- 在线程阻塞等待条件变量之前,调用pthread_mutex_unlock
- 若条件变量被其他线程触发,在该线程被唤醒后,调用pthread_mutex_lock
2.7 触发条件变量
◼
pthread_cond_signal
和
pthread_cond_broadcast
可
以触发条件变量并唤醒等待条件变量的线程
◼
pthread_cond_signal
唤醒该条件变量等待线程队列中
的某一个线程
◼
pthread_cond_broadcast
唤醒该条件变量等待线程队
列中的所有线程,这些线程会进行竞争
◼
函数原型
- 头文件: pthread.h
- int pthread_cond_signal(pthread_cond_t *cond);
- int pthread_cond_broadcast(pthread_cond_t *cond);
◼
参数与返回值
- cond:条件变量
- 成功返回0;出错返回错误码
3 读写锁
3.1 读者-写者问题
◼
问题描述:
⚫
在对临界资源的访问中,更多的是读操作,而写操作较少,只有互
斥量机制可能会影响访问效率
⚫
期望对临界资源的访问控制粒度更精细,任一时刻允许多个线程对
临界资源进行读操作,但只允许一个线程对临界资源进行写操作
◼
互斥关系:
⚫
读操作
-
写操作互斥
⚫
写操作
-
写操作互斥
⚫
读操作
-
读操作不互斥
◼
同步关系:
⚫
缓冲区不满,才允许写操作;缓冲区不空,才允许读操作
3.2 读写锁通信机制
◼
在保证互斥的基础上,
Linux
提供了对临界资源访问控
制粒度更细的读写锁机制
◼
读写锁机制可以实现如下访问控制规则:
⚫
如果有线程对互斥资源进行读操作,则允许其它线程执行读
操作,但不允许任何线程进行写操作
⚫
如果有线程对互斥资源进行写操作,则不允许任何线程进行
读操作或写操作
3.3 读写锁的操作
◼
读写锁的操作与互斥量的操作非常类似
- 定义读写锁变量
- 初始化读写锁变量
- 访问临界资源(读操作或写操作)前对读写锁加锁
- 访问临界资源后对读写锁解锁
- 销毁读写锁变量
◼
读写锁的加锁操作在互斥量加锁的基础上扩展,
具有加读锁和加写锁两种操作
- 如果有线程已经成功对读写锁加读锁,其它线程可以继续对该读写锁加读锁,但不能再加写锁(加写锁的线程可能会被阻塞)
- 如果有线程已经成功对读写锁加写锁,则其它线程不能对该读写锁加读锁和加写锁
3.3.1 读写锁的初始化和销毁
◼
头文件:
pthread.h
◼
定义读写锁变量 pthread_rwlock_t rwlock;
◼
初始化读写锁变量
int
pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock, __const pthread_rwlockattr_t *__restrict __attr);
◼
销毁读写锁变量
int
pthread_rwlock_destroy (pthread_rwlock_t *__rwlock);
◼
返回值:成功返回
0
,否则返回错误码
3.3.2 加读锁
◼
头文件:
pthread.h
◼
阻塞加读锁
int
pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock);
◼
非阻塞加读锁
int
pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);
◼
限时加读锁
int
pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime
);
3.3.3 加写锁
◼
头文件:
pthread.h
◼
阻塞加写锁
int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock);
◼
非阻塞加写锁
int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);
◼
限时等待加写锁
int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime
);
3.3.4 读写锁解锁
◼
头文件:
pthread.h
int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock);
◼
如何确定是为读锁解锁还是为写锁解锁?
- 加锁(读锁/写锁)与解锁配对出现,为代码中距离最近的加锁操作解锁
4 Linux进程间通信机制概述
4.1 Linux进程间通信机制
4.2 管道
◼
管道是最古老、最简单的
UNIX
进程间通信机制
◼
管道是一种特殊文件
◼
管道的局限性
- 半双工:一个进程写,一个进程读
- 只能在父子进程之间使用
4.2.1 创建管道
◼
函数原型
- 头文件:unistd.h
- int pipe(int fildes[2]);
◼
参数:
- 程序通过文件描述符fildes[0]和fildes[1]来访问管道
- filedes[0]只能用于管道读操作,filedes[1]只能用于管道写操作
- 写入fildes[1]的数据可以按照先进先出的顺序从fildes[0]中读出
◼
返回值:成功返回
0
,出错返回
- 1
4.3 命名管道(FIFO)
◼管道只能在父子进程之间使用
◼FIFO
也被称为命名管道,
FIFO
是一种特殊的文件
(创建
FIFO
类似于创建文件,
FIFO
的路径名存在于文件系统中)
◼创建
FIFO
之后可以通过文件
I/O对其进行操作
◼非父子进程可以通过文件名来使用FIFO
4.3.1 创建FIFO
◼
函数原型
- 头文件:sys/types.h,sys/stat.h
- int mkfifo(const char *pathname, mode_t mode) ;
◼
参数:
- pathname:文件名(绝对路径)
- mode:文件类型、权限等
◼
返回值:成功返回
0
,出错返回
- 1
4.4 XSI IPC机制
◼
信号量集(
semaphore set
),用于实现进程之间的
同步与互斥
◼
共享内存(
shared memory
),用于在进程之间高效
地共享数据,适用于数据量大,速度要求高的场景
◼
消息队列(
message queue
),进程之间传递数据的
一种简单方法
4.5 IPC对象
4.6 IPC对象的key值和ID
◼Linux
系统中的
IPC
对象都是全局的,为每个
IPC
对象
分配唯一的ID
◼在
IPC
操作中通信各方需要通过
ID
来指示操作的
IPC
对
象,需要有机制让通信各方获取获取
IPC
对象的ID
◼创建
IPC
对象的进程通过创建
IPC
对象函数的返回值可
获取
ID值
◼
未创建
IPC
对象的进程如何获取
IPC
对象的
ID
值并使用
该对象呢?
◼IPC机制的ID值为动态分配,无法提前约定,不能跨进程传递
◼多个进程提前约定使用相同的
key
值做为参数来创建
IPC
对象或打开已经创建的
IPC对象
◼如果通信各方(进程)在创建
/
打开
IPC
对象时使用相
同的
key值:
- 首次使用该key值创建IPC对象的进程将真正创建该IPC对象,并获取其ID值
- 后续使用该key值创建IPC对象的进程都将在内核中找到该IPC对象并打开它,从而获取ID值
◼IPC
对象与
key
值一一对应,因此
key值不能重复
◼通过
ftok
函数来产生独特的
key值,避免重复
- 头文件: sys/types.h,sys/ipc.h
- key_t ftok( char * pathname, int proj_id )
◼
参数
- pathname是指定的文件名,可以是特殊文件也可以是目录文件)
- proj_id是子序号
◼如果要确保
key_t
值不变,需要确保
ftok
所指定的文件名不被删除
4.7 XSI IPC机制使用概述
5 消息队列
◼
进程之间传递数据的一种简单方法
◼
把每个消息看作一个记录,具有特定的格式
◼
消息队列就是消息的链表
◼
对消息队列有写权限的进程可以按照一定的规则
添加新消息
◼
对消息队列有读权限的进程则可以从消息队列中
读走消息
◼
消息队列能够克服管道或命名管道机制的一些缺
点,例如实时性差等
5.1 消息队列结构
5.2 消息队列操作
◼
头文件:sys/types.h,sys/ipc.h,sys/msg.h
◼
打开或创建消息队列对象
int msgget(key_t key, int msgflg)
◼
从消息队列接收消息
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long
msgtyp, int msgflg);
◼
向消息队列发送消息
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int
msgflg);
◼
消息队列控制操作
msgctl(int msqid, int cmd, struct
msqid_ds *buf);
5.2.1 创建消息队列
int msgget(key_t key, int oflag);
◼
返回值:成功返回创建或打开的消息队列对象
ID
;出错返回
-1
◼
参数:
- key:创建或打开消息队列对象时指定的key值(提前约定或通过ftok函数创建)
- Oflag:设置访问权限,取值可以为以下一个或多个值的或
#define IPC_R
000400
读权限
#define IPC_W
000200
写和修改权限
#define IPC_M
010000
改变控制方式权限
还可以附加以下参数值(按位或)
IPC_CREAT
(如果消息队列对象不存在则创建,否则打开已经存在的消息队列对象)
IPC_EXCL
(只有消息队列对象不存在的时候,才能创建新的消息队列对象,否则就产生
错误)
IPC_NOWAIT
(如果操作需要等待,则直接返回错误)
5.2.2 发送消息到消息队列
int msgsnd(int msgid, const void *ptr, size_t length, int
flag);
◼
返回值:成功返回
0
;出错返回
-1
◼
参数:
- msgid:消息队列ID
- ptr:指向msgbuf的结构体指针(其中消息类型mtype必须大于0,小于0的消息类型有特殊的指示作用)
struct msgbuf {
long mtype;
char mtext[ ];};
- length:以字节为单位指定待发送消息的长度(msgbuf结构体中消息类型mtype之后的用户自定义数据的长度),该长度可以为0
- flag:可以是0,也可以是IPC_NOWAIT(该标志可以使函数工作在非阻塞模式)
出现以下情况时:
•
指定的消息队列容量已满
•
在系统范围存在太多的消息
若设置了
IPC_NOWAIT
,则
msgsnd
立即返回(返回
EAGAIN
错误)
若未指定该标志,则
msgsnd
导致调用进程阻塞,直到可以发送成功
为止
5.2.3 从消息队列接收消息
ssize_t msgrcv(int msqid, void *ptr, size_t
length, long type, int flag);
◼
返回值:成功返回实际读取数据的字节数;出错返回-1
◼
参数:
- msgid:消息队列ID
- ptr:消息缓冲区指针,指向msgbuf的结构体指针
- length:消息缓冲区中数据部分的大小(msgbuf结构体中消息类型mtype之后的用户自定义数据的长度)
- type:指定期望从消息队列中接收什么样的消息
type
为
0
,返回队列中第一个消息(消息队列是一个
FIFO链表,所以返回的是队列中最早的消息)
type
大于
0
,返回消息队列中类型值为
type
的第一个消息
type
小于
0
,返回消息队列中类型值小于或等于
type
绝对值中类型值最小的第一个消息
- flag:当消息队列中没有期望接收的消息时会如何操作
若设置了
IPC_NOWAIT
标志,则函数立即返回
ENOMSG错误
若未设置
IPC_NOWAIT
标志,否则
msgrcv
导致调用进程阻塞直到如下某个事件发生:
1.有其他进程向消息队列中发送了所期望接收的消息
2.该消息队列被删除,此时返回
EIDRM
错误
3.进程被某个信号中断,此时返回EINTR错误
5.2.4 消息队列参考代码:消息发送者
5.2.5 消息队列参考代码:消息接收者
6 信号量集
6.1 操作系统中任务资源共享情况
◼
临界资源:在一段时间
内只允许一个任务访问
的资源。诸任务间应采
取互斥方式,实现对资
源的共享。
◼
共享资源:允许多个任
务同时访问同一种资源
的多个实例
6.2 信号量
◼
信号量一般分为三种类型:
- 互斥信号量:任务之间互斥访问临界资源
- 计数信号量:任务之间竞争访问共享资源
- 二值信号量:任务之间的同步机制
◼信号量是操作系统提供的管理资源共享的有效手段
◼信号量作为操作系统核心代码执行,其地位高于任务,任务调度不能终止其运行
6.2.1 信号量的实现
◼
信号量s一般包含以下成员:
- 整数值s.count(实现资源计数)
- 任务阻塞队列s.queue
◼
信号量操作:初始化、P操作、V操作
- 在进程初始化信号量将s.count指定为一个非负整数值,表示可用的共享资源实例总数
◼
运行中s.count可为负值(其绝对值表示当前等待访问
该共享资源的进程数)
6.2.2 互斥信号量
6.2.3 计数信号量
6.2.4 二值信号量
6.3 XSI IPC信号量集结构
6.4 XSI IPC信号量集操作
◼
头文件:
sys/types.h
,
sys/ipc.h
,
sys/sem.h
◼
创建或打开信号量集对象
int semget(key_t key, int nsems, int semflg);
◼
信号量集操作(信号量的
PV
操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);
◼
信号量集控制(信号量初始化和删除操作)
int semctl(int semid, int semnum, int cmd, union semun arg);
6.4.1 semget函数
◼
头文件:sys/sem.h
int semget(key_t key, int nsems, int semflg);
◼
返回值:成功返回创建或打开的信号量集对象(IPC对
象)ID;失败返回-1
◼
参数:
- key:用于创建或打开信号量集对象时指定的key值(约定或通过ftok函数创建)
- nsems:信号量集对象中包含的信号量数量(例如取值为1,则信号量集只包含1个信号量)
- semflg:设置访问权限,取值可以为以下某个值或多个值的或
#define IPC_R 000400 读权限
#define IPC_W 000200 写和修改权限
#define IPC_M 010000 改变控制方式权限
还可以附加以下参数值(按位或)
IPC_CREAT
(若信号量集对象不存在则创建,否则打开已经存在的信号量集对象)
IPC_EXCL
(若信号量集对象不存在则创建,否则出错)
IPC_NOWAIT
(如果本操作需要等待,则直接返回错误)
◼
semget函数示例代码
#include<stdio.h>
#include<sys/sem.h>
#define MYKEY 0x1a0a
int main()
{
int semid;
semid=semget(MYKEY,1,IPC_CREAT|IPC_R | IPC_W| IPC_M);
printf("semid=%d\n",semid);
return 0;
}
6.4.2 semop函数
◼
头文件:
sys/types.h,sys/ipc.h,sys/sem.h
int semop(int semid, struct sembuf sops[ ], size_t
nsops);
◼
返回值:成功返回
0
;失败返回
-1
◼
参数:
- semid:信号量集对象ID(semget的返回值)
- sops:指向sembuf结构数组的指针
- nsops:第二个参数中sembuf结构数组的元素个数
◼
sembuf
结构:
- unsigned short sem_num; 信号量序号,指示本次是操作信号量集中的哪个信号量(序号从0开始)
- short sem_op;信号量操作码
该值为正,信号量
V
操作,增加信号量的值(为
n
,则加
n
)
该值为负,信号量
P
操作,减小信号量的值(为
-n
,则减
n
)
该值为
0
,对信号量的当前值是否为
0
的测试
- short sem_flg;semop操作控制标志
◼
sem_flg
对
semop
操作进行控制,主要有
2
个控制标志
- IPC_NOWAIT
当指定的
PV
操作不能完成时,进程不会被阻塞,
semop
函数立即返回。返回值为-1
,
errno
置为
EAGAIN
。
例如:信号量值在
P
操作后小于
0
,如果操作控制标志没有设置IPC_NOWAIT,则将调用进程阻塞,
semop
函数将不会返回直到资源可用为止;若设置了IPC_NOWAIT
,则
semop
函数直接返回,调用进程将不会阻塞
- SEM_UNDO
进程异常退出时,执行信号量解除(
undo
)操作
例如:进程执行了
P
操作后异常退出,如果操作控制标志设置了
SEM_UNDO,则内核会对该进程执行V
操作,保证安全性
semop函数示例:实现P操作
semop函数示例:实现V操作
6.4.3 semctl函数
◼
头文件:
sys/types.h,sys/ipc.h,sys/sem.h
int semctl(int semid, int semnum, int cmd,union semun arg);
◼
返回值:成功返回值大于或等于
0
;失败返回值
-1
◼
参数:
- semid:信号量集对象的ID(semget的返回值)
- semnum:信号量集中信号量的编号(如果控制是针对整个信号量集,则将该值设置为0)
- cmd:要执行的控制命令
- arg:与控制命令配合的参数(可选)
◼
cmd
:要执行的控制命令
- 针对整个信号量集的控制命令主要包括:
IPC_RMID
,删除
IPC_SET
,设置
ipc_perm
参数
IPC_STAT
,获取
ipc_perm
参数
IPC_INFO
,获取系统信息
- 针对信号量集中某个信号量的控制命令主要包括:
SETVAL
,设置信号量的值(一般用于信号量初始化时设置初始值)
GETVAL
,获取信号量的值
GETPID
,获取信号量拥有者进程的
PID
值
◼
arg
:与控制命令配合的参数
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
◼
semctl
函数的控制命令通常为以下两种情况:
- SETVAL:用来把信号量集中的某个信号量初始化为一个给定值, 这个值通过arg参数(union semun中的val成员)来指定
- IPC_RMID:用于删除信号量集对象,此时arg参数无需赋值
7 共享内存
7.1 共享内存
◼共享内存是内核为进程间通信创建的特殊内存段
◼不同进程可以将同一段共享内存连接到自己的地址空间
◼
最快的进程间通信方式
◼本身不具有互斥访问机制
7.2 共享内存的操作
◼
头文件:
sys/types.h
,
sys/ipc.h
,
sys/shm.h
◼
打开或创建共享内存对象
int shmget(key_t key, int size, int flag);
◼
将共享内存连接到进程空间
void *shmat(int shmid, void *addr, int flag);
◼
断开进程空间和共享内存的连接
int shmdt(void *addr);
◼
共享内存控制操作
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
7.2.1 shmget函数
int shmget(key_t key, size_t size, int shmflag)
◼
成功返回创建或打开的共享内存标识符;失败返回
-1
◼
参数:
- key:创建或打开共享内存对象时指定的key值(提前约定或通过ftok函数创建)
- size:指定创建的共享内存大小(首次创建共享内存对象时通过该参数指定共享内存段的大小)
- shmflag:设置共享内存的访问权限
◼
shmflag
:取值可以为以下一个或多个值的或
#define IPC_R 000400 读权限
#define IPC_W 000200 写和修改权限
#define IPC_M 010000 改变控制方式权限
还可以附加以下参数值(按位或)
IPC_CREAT
(若共享内存对象不存在则创建,否则打开已经存在的共享内存对象)
IPC_EXCL
(若共享内存对象不存在则创建,否则出错)
IPC_NOWAIT
(如果本操作需要等待,则直接返回错误)
7.2.2 shmat函数
void *shmat(int shm_id, const void *addr, int shmflg)
◼
成功返回共享内存在进程空间中的连接地址;失败返回
-1
◼
参数:
- shm_id:共享内存对象ID
- addr:指明共享内存连接到的进程空间地址;通常指定为空指针,让Linux系统决定共享内存连接到进程空间中的哪个地址
- shmflg:可以设置以下两个标志位之一或者不设置(值为0)
– SHM_RND
(
addr
参数指定的地址应被规整到内存页面大小的整数倍)
– SHM_RDONLY
(共享内存连接到进程空间时被限制为只读)
7.2.3 shmdt函数
int shmdt(const void *shmaddr) ;
◼
成功返回
0
;失败返回
-1
◼
参数:
- shmaddr:共享内存在进程空间中的连接地址,一般为shmat函数返回的地址。
7.2.4 shmctl函数
int shmctl(int shm_id, int command, struct shmid_ds *buf) ;
◼
成功返回
0
;失败返回
-1
◼
参数:
- shm_id:共享内存对象ID
- commad:执行的控制命令
IPC_RMID
,从系统中删除该共享内存对象
IPC_STAT
,获取共享内存对象的内核结构值
IPC_SET
,设置共享内存对象的内核结构值
- buf:指向shmid_ds结构的指正,当控制命令为IPC_STAT或IPC_SET时,用于获取或设置共享内存对象的内核结构
7.3 应用实例:生产者/消费者问题
◼
问题描述:进程之间通过共享缓冲池(包含一定数量的缓冲区)交换数据。其中,“生产者”进程不断写入,而“消费者”进程不断读出;任何时刻只能有一个任务可对共享缓冲池进行操作。
◼
进程之间的共享缓冲池可以通过共享内存机制实现
◼
缓冲池结构(
5
个缓冲区)
struct BufferPool
{
char Buffer[5][100];//5
个缓冲区
int Index[5];/*
缓冲区状态:
为
0
表示对应的缓冲区未被生产者使用,可生产但不可消费
为
1
表示对应的缓冲区已被生产者使用,不可生产但可消费
*/
};
参考代码:消费者读取共享内存