Linux学习-4线程同步和进程

目录

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 线程的数据共享

Linux学习-4线程同步和进程
◼线程间共享的数据和资源:进程代码段、进程中的全局变量、进程打开的文件……
◼每个线程私有的数据和资源:线程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 任务的同步问题

任务同步相互合作关系(直接相互制约关系)
  • 两个或多个任务为了合作完成同一个工作,在执行速度或某个确定的时序点上必须相互协调,即一个任务的执行必须依赖另一 个任务的执行情况
Linux学习-4线程同步和进程

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_signalpthread_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;出错返回错误码
Linux学习-4线程同步和进程
Linux学习-4线程同步和进程

3 读写锁

3.1 读者-写者问题

问题描述:
在对临界资源的访问中,更多的是读操作,而写操作较少,只有互
斥量机制可能会影响访问效率
期望对临界资源的访问控制粒度更精细,任一时刻允许多个线程对
临界资源进行读操作,但只允许一个线程对临界资源进行写操作
互斥关系:
读操作-写操作互斥
写操作-写操作互斥
读操作-读操作不互斥
同步关系:
缓冲区不满,才允许写操作;缓冲区不空,才允许读操作

3.2 读写锁通信机制

在保证互斥的基础上,Linux提供了对临界资源访问控
制粒度更细的读写锁机制
读写锁机制可以实现如下访问控制规则:
如果有线程对互斥资源进行读操作,则允许其它线程执行读
操作,但不允许任何线程进行写操作
如果有线程对互斥资源进行写操作,则不允许任何线程进行
读操作或写操作
 

3.3 读写锁的操作

读写锁的操作与互斥量的操作非常类似
  • 定义读写锁变量
  • 初始化读写锁变量
  • 访问临界资源(读操作或写操作)前对读写锁加锁
  • 访问临界资源后对读写锁解锁
  • 销毁读写锁变量
读写锁的加锁操作在互斥量加锁的基础上扩展,具有加读锁和加写锁两种操作
  • 如果有线程已经成功对读写锁加读锁,其它线程可以继续对该读写锁加读锁,但不能再加写锁(加写锁的线程可能会被阻塞)
  • 如果有线程已经成功对读写锁加写锁,则其它线程不能对该读写锁加读锁和加写锁
Linux学习-4线程同步和进程

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进程间通信机制

Linux学习-4线程同步和进程

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.hsys/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对象

Linux学习-4线程同步和进程
 

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.hsys/ipc.h
  • key_t ftok( char * pathname, int proj_id )
参数
  •   pathname是指定的文件名,可以是特殊文件也可以是目录文件)
  •  proj_id是子序号  
◼如果要确保key_t值不变,需要确保ftok所指定的文件名不被删除

4.7 XSI IPC机制使用概述

Linux学习-4线程同步和进程

5 消息队列

进程之间传递数据的一种简单方法
把每个消息看作一个记录,具有特定的格式
消息队列就是消息的链表
对消息队列有写权限的进程可以按照一定的规则
添加新消息
对消息队列有读权限的进程则可以从消息队列中
读走消息
消息队列能够克服管道或命名管道机制的一些缺
点,例如实时性差等
 

5.1 消息队列结构

Linux学习-4线程同步和进程
 

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:指定期望从消息队列中接收什么样的消息
                  type0,返回队列中第一个消息(消息队列是一个FIFO链表,所以返回的是队列中最早的消息)
                  type大于0,返回消息队列中类型值为type的第一个消息
                  type小于0,返回消息队列中类型值小于或等于type绝对值中类型值最小的第一个消息
 
  • flag:当消息队列中没有期望接收的消息时会如何操作
                  若设置了IPC_NOWAIT标志,则函数立即返回ENOMSG错误
                  若未设置IPC_NOWAIT标志,否则msgrcv导致调用进程阻塞直到如下某个事件发生:
                                       1.有其他进程向消息队列中发送了所期望接收的消息
                                       2.该消息队列被删除,此时返回EIDRM错误
                                       3.进程被某个信号中断,此时返回EINTR错误
 

5.2.4 消息队列参考代码:消息发送者

Linux学习-4线程同步和进程

5.2.5 消息队列参考代码:消息接收者

Linux学习-4线程同步和进程
 

6 信号量集

6.1 操作系统中任务资源共享情况

临界资源:在一段时间内只允许一个任务访问的资源。诸任务间应采取互斥方式,实现对资源的共享。
共享资源:允许多个任务同时访问同一种资源的多个实例
Linux学习-4线程同步和进程
 

6.2 信号量

信号量一般分为三种类型:
  • 互斥信号量:任务之间互斥访问临界资源
  • 计数信号量:任务之间竞争访问共享资源
  • 二值信号量:任务之间的同步机制
 ◼信号量是操作系统提供的管理资源共享的有效手段
◼信号量作为操作系统核心代码执行,其地位高于任务,任务调度不能终止其运行  
 

6.2.1 信号量的实现

信号量s一般包含以下成员:
  • 整数值s.count(实现资源计数)
  • 任务阻塞队列s.queue
信号量操作:初始化、P操作、V操作
  • 在进程初始化信号量将s.count指定为一个非负整数值,表示可用的共享资源实例总数
运行中s.count可为负值(其绝对值表示当前等待访问该共享资源的进程数)
Linux学习-4线程同步和进程
 

6.2.2 互斥信号量

Linux学习-4线程同步和进程
 

6.2.3 计数信号量

Linux学习-4线程同步和进程

6.2.4 二值信号量

Linux学习-4线程同步和进程
 

6.3 XSI IPC信号量集结构

Linux学习-4线程同步和进程
Linux学习-4线程同步和进程

6.4 XSI IPC信号量集操作

头文件:sys/types.hsys/ipc.hsys/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;
}
Linux学习-4线程同步和进程

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:信号量集对象IDsemget的返回值)
  • 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_flgsemop操作进行控制,主要有2个控制标志
  • IPC_NOWAIT
           当指定的PV操作不能完成时,进程不会被阻塞,semop函数立即返回。返回值为-1errno置为EAGAIN
           例如:信号量值在P操作后小于0,如果操作控制标志没有设置IPC_NOWAIT,则将调用进程阻塞,semop函数将不会返回直到资源可用为止;若设置了IPC_NOWAIT,则semop函数直接返回,调用进程将不会阻塞
  • SEM_UNDO
           进程异常退出时,执行信号量解除(undo)操作
           例如:进程执行了P操作后异常退出,如果操作控制标志设置了SEM_UNDO,则内核会对该进程执行V操作,保证安全性
 
semop函数示例:实现P操作
Linux学习-4线程同步和进程
 
semop函数示例:实现V操作
Linux学习-4线程同步和进程
 

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:信号量集对象的IDsemget的返回值)
  • 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参数无需赋值

Linux学习-4线程同步和进程

Linux学习-4线程同步和进程

Linux学习-4线程同步和进程

7 共享内存

7.1 共享内存

 ◼共享内存是内核为进程间通信创建的特殊内存段  
◼不同进程可以将同一段共享内存连接到自己的地址空间
最快的进程间通信方式
◼本身不具有互斥访问机制  
Linux学习-4线程同步和进程
 

7.2 共享内存的操作

头文件:sys/types.hsys/ipc.hsys/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_RNDaddr参数指定的地址应被规整到内存页面大小的整数倍)
                 – 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_STATIPC_SET时,用于获取或设置共享内存对象的内核结构
 

7.3 应用实例:生产者/消费者问题

问题描述:进程之间通过共享缓冲池(包含一定数量的缓冲区)交换数据。其中,“生产者”进程不断写入,而“消费者”进程不断读出;任何时刻只能有一个任务可对共享缓冲池进行操作。
进程之间的共享缓冲池可以通过共享内存机制实现
缓冲池结构(5个缓冲区)
struct BufferPool
{
     char Buffer[5][100];//5个缓冲区
     int Index[5];/*缓冲区状态:
                                              为0表示对应的缓冲区未被生产者使用,可生产但不可消费
                                              为1表示对应的缓冲区已被生产者使用,不可生产但可消费*/
};
 
参考代码:消费者读取共享内存

Linux学习-4线程同步和进程

Linux学习-4线程同步和进程