学信号量的目的
学习信号量主要是为后面学习多线程做一个铺垫,所以信号量大家简单理解一下概念就可以,具体的代码实现可以不用过多关注(本文不提供)
而且信号量现在好像用的不多,因为有更好的替代品,后面将网络的时候会说
基础概念
- 临界资源:任何时刻都只允许一个进程访问的公共资源
- 临界区:访问临界资源的代码
- 原子性:对象被操作的时候只有两种状态,还未开始和已经结束。比如int a
举个例子来理解概念
我们举个例子来理解信号量:看电影。
- 所有的座位对应一整块公共资源,每个座位对应一小块资源
- 一个人对应一个进程
- 买票相当于访问这块资源
那么信号量的理论有三点
- 买完票,在电影播放的这段时间里这个座位就是我的--->申请成功后,进程独享这块资源
- 有多少座位卖多少张票---> 永远不会发生并发访问
- 只有一个座位,就只卖一张票--->这块公共资源作为一个整体被使用
并发访问
所有的座位对应一整块公共资源,每个座位对应一小块资源
这句话的意思是:虽然是一整块公共资源,但我们将它进行划分,使得可以有多个进程去访问其内部的资源, 这就是并发访问
问题与信号量
但有个问题:怎么控制进入的进程数呢?如果有10个座位,如何控制最多只有十个人买票?
我们需要进行计数:信号量
信号量就是一个计数器,描述临界资源数量 保护临界资源
进程申请资源的过程
先申请信号量,未成功就一直等待,信号量--
访问完资源,释放信号量,信号量++
问题:信号量怎么创建--->进程间通信
用全局变量可以吗,比如 int count。这不可以
因为:
- 进程拥有自己的代码区,修改一个进程的全局变量(数据段中),另一个进程不会改变
- count++/count--不是原子的,在汇编中他们是多条语句
解决方法
让各个进程看到同一份资源!!!,这就涉及到进程间通信了
从这里也可以看出信号量也是公共资源,以及他的申请(P操作)和释放(V操作)也是原子的
补充:
系统允许进程一次申请多个信号量
信号量的接口:semget、semctl与semop
semget:创建
int semget(key_t key, int num_sems, int semflg);
他的semflg和返回值和共享内存很像,不同的是第二个参数num_sems,表示申请num_sems个信号量
需要注意的是:写代码的时候,不用管key,他的作用是在内核中标明信号量的唯一性
semctl
int semctl(int semid, int semnum, int cmd, ...);
semid表示哪个信号集,semget的返回值
semnum表示要对信号量集中的第semnum-1个信号量进行操作
PV操作
int semop(int semid, struct sembuf *sops, size_t nsops);
semid (int):
信号量集合的标识符,通常由 semget 返回。它标识了要操作的信号量集合。
sops (struct sembuf *):
指向一个或多个 sembuf 结构体数组的指针。每个 sembuf 结构体定义了对信号量的操作。这个数组包含多个操作,可以一次性对多个信号量进行操作。
nsops (size_t):
该参数指定了要操作的信号量数目。它通常是 sops 数组的大小。
sembuf必须包含以下成员, 通常由自己定义
struct sembuf {
unsigned short sem_num; // 信号量在集合中的索引
short sem_op; // 对信号量的操作,正值表示增加,负值表示减少,0 表示等待
short sem_flg; // 操作标志(如阻塞、非阻塞等)
};
IPC资源
ipcs指的是当前系统中所有进程间通信的资源(消息队列、共享内存、信号量集)
使用方法是:
内核中,管理ipc资源的方式
所有管理ipc资源的结构体(消息队列.....),他们的第一个成员都是kern_ipc_perm。kern_ipc_perm是一个指向ipc的指针,他存放在ipc_id_array数组中。(但这也是他被替换的原因之一:不符合一切皆文件了
那么对ipc的管理,实际上就是对ipc_id_array的管理(增删查改), 创建新的ipc就将指针存入ipc_id_array。
访问ipc资源就是找到ipc_id_array数组,利用其中的指针转换成对应的结构体指针之后就可以操作资源了
至于ipc具体的类型,结构体中还有一个成员是mode,记录着类型
比如
(sem_array*)ipc_id_array[0]->sem_base[0].semval--;
小结
本来想继续写信号的,但好像这篇有点长了,就再写一篇