AI智能
改变未来

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.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_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;出错返回错误码

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_flgsemop操作进行控制,主要有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表示对应的缓冲区已被生产者使用,不可生产但可消费*/}; 参考代码:消费者读取共享内存

 

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Linux学习-4线程同步和进程