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

◼线程间共享的数据和资源:进程代码段、进程中的全局变量、 进程打开的文件……
◼每个线程私有的数据和资源:线程 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.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对象

 

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机制使用概述

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:信号量集对象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_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:信号量集对象的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参数无需赋值

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_STATIPC_SET时,用于获取或设置共享内存对象的内核结构
 

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

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

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值