目录
1 线程同步机制:互斥量
1.1 Linux系统中多任务(进程/线程)之间的关系
1.2 线程的数据共享
1.3 任务的互斥问题
1.4 临界资源
1.5 互斥量
1.5.1 互斥量的操作
1.5.2 互斥量初始化
1.5.3 互斥量销毁
1.5.4 互斥量的加锁和解锁操作
1.5.5 互斥量非阻塞加锁
1.5.6 互斥量的操作总结
2 条件变量
2.1 任务的同步问题
2.2 条件变量
2.3 条件变量的操作
2.4 条件变量定义与初始化
2.5 条件变量销毁
2.6 等待条件变量
2.7 触发条件变量
3 读写锁
3.1 读者-写者问题
3.2 读写锁通信机制
3.3 读写锁的操作
3.3.1 读写锁的初始化和销毁
3.3.2 加读锁
3.3.3 加写锁
3.3.4 读写锁解锁
4 Linux进程间通信机制概述
4.1 Linux进程间通信机制
4.2 管道
4.2.1 创建管道
4.3 命名管道(FIFO)
4.3.1 创建FIFO
4.4 XSI IPC机制
4.5 IPC对象
4.6 IPC对象的key值和ID
4.7 XSI IPC机制使用概述
5 消息队列
5.1 消息队列结构
5.2 消息队列操作
5.2.1 创建消息队列
5.2.2 发送消息到消息队列
5.2.3 从消息队列接收消息
5.2.4 消息队列参考代码:消息发送者
5.2.5 消息队列参考代码:消息接收者
6 信号量集
6.1 操作系统中任务资源共享情况
6.2 信号量
6.2.1 信号量的实现
6.2.2 互斥信号量
6.2.3 计数信号量
6.2.4 二值信号量
6.3 XSI IPC信号量集结构
6.4 XSI IPC信号量集操作
6.4.1 semget函数
6.4.2 semop函数
6.4.3 semctl函数
7 共享内存
7.1 共享内存
7.2 共享内存的操作
7.2.1 shmget函数
7.2.2 shmat函数
7.2.3 shmdt函数
7.2.4 shmctl函数
7.3 应用实例:生产者/消费者问题
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.hint pthread_mutex_destroy(pthread_mutex_t *mutex);◼参数与返回值
- mutex:即互斥量
- 成功返回0,出错返回错误码
1.5.4 互斥量的加锁和解锁操作
◼在对临界资源访问之前和访问之后,需要对互斥量进行加锁和解锁操作 ◼函数原型 头文件:pthread.hint 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.hint 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.hint 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 0x1a0aint 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.hint 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.hint 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表示对应的缓冲区已被生产者使用,不可生产但可消费*/}; 参考代码:消费者读取共享内存