接下来一节是进程信号量
我们这里面讲到的是
UNIX System V里面的信号量 system V
可以使用同步机制
来确定进程的先后执行关系
用的就是进程信号量
要讲进程信号量啊
同学们首先就必须清楚什么叫信号量
信号量是什么东西呢
信号量是一类特殊的计数器
它的值是非负的整数
用于进程或线程的同步
最主要的目的就是干这个事情
信号量有两个主要的操作
一个是等待
我们称之为P操作
有时称之为P原语
信号量的值会递减1然后返回
如果它的值为0
那么就会阻塞这个操作
就减不了 就阻塞
直到信号量的值为正了
然后它才能够继续地去减1 才能返回
它的最主要的目的就是为了控制
两个进程或线程之间的同步嘛
它就使用这个信号量的值
来去做到这一点
你可以访问这个值
你如果需要想用 你就减1
就相当于它有一个资源
这个资源上有一个标记
这个标记就是用一个数值来表示的
这个数值就是信号量
你比如说一个标准的二元信号量
就是1或0 这个概念
就表示这个资源在任意一个时刻
只能有一个进程去访问它
一旦一个进程能够访问它
原来值为1
那么一递减 它一等待它
得到了 就会变成0
那么其它的进程就不能够得到它了嘛
这个时候其它的进程就会被阻塞
一直等到第一个进程用完了那个资源
做了一次发布操作
我们称为V操作 也称V原语
这个时候信号量的值就会递增1
它用完了 这个信号量就递增了
递增1以后
原来那个等待这个信号量的一个进程呢
就会被唤醒
它就可以去做减1
就可以访问那个资源了
信号量做的就是这个事
所以你看 这里主要两个操作
一个是等待操作 一个是发布操作
进程信号量呢
它只是一类特殊的信号量
它的操作的原理依然是这个模样
只不过它等待和发布的时候
这个信号量的数值并不固定为1
同时它定义的
也不是一个信号量而是一组信号量
在Linux操作系统下边
信号量有两个实现版本
一个就是我们今天讲的进程信号量
它多用于进程同步
另外一个是POSIX标准
它主要用于线程同步
那个版本要比我们这个进程信号量
稍微简单一些
使用进程信号量的时候
是需要特别注意的
每次创建和管理的这个信号量
它不是一个 而是一个组
一个集合 一个数组
这个集合里可能包含很多个信号量
看你定义的需要
你需要一个 就定义一个
你需要两个 就定义两个
你需要几个 就定义几个
它是一个数组 不是一个单一的信号量
这是第一点要知道的
在进程里边是使用键值
来关联这个对应的进程信号量的
但进程内部操作这个进程信号量的时候
实际上并不使用这个键值
使用的是semid标识
就是这个进程信号量的ID
你可以这么理解
就相当于在进程之间进行同步的时候
semid是对内的 key是对外的
你创建和管理
想获取这个进程信号量
用的就是key
得到这个进程信号量之后
内部想用它 用的就是semid
semget()这个函数
就用于创建或获取进程信号量的集合
semget()这个函数的原型
第一个参数带着key键值
第二个参数就是信号量的元素个数
你想创建几个信号量
第三个就是信号量的创建标志
特别注意的就是第二个参数
它表示这个信号量的集合元素个数
它不是一个数组嘛 数组元素个数
不是这个信号量所对应的信号数
那个是另外一种设置的方式
如果你要获取
已经分配好了的进程信号量 那个集合
那么你就使用原先那个键值去获取它
调用这个函数
用这个查询 它就能够得到它
得到了 就会给你返回
使用一个特定的键值叫IPC_PRIVATE
就可以创建一个当前进程的
私有的信号量
其实说私有并不妥当
并不是真正私有
会创建一个新的
和其它的键值不同的进程信号量
就是创建一个新的信号量
要控制和管理进程信号量集合
就要用semctl()这个函数
传semid 传semnum 传cmd
后面还跟着一系列的可选参数
可以有 也可以没有
semid就是进程信号量的标志符
semnum就是那个信号量集的元素下标
你想控制哪一个
有的时候可能是全部都控制
那就是通过cmd那个指定操作来提供的
第四个参数arg 也许有 也许没有
有 情况也可能不一样
具体它有什么
是和cmd那个命令是有关的
你调用semctl()的时候要特别注意
进程信号量的控制和管理
它是有一个非常明确的权限要求的
如果你的进程没有这个权限
那么你是没有办法
去控制和管理进程信号量的
这一点也是需要注意的
如果你需要释放或者删除这个进程信号量集
那你就使用IPC_RMID这个命令
把cmd这个命令传这个——IPC_RMID
就可以删除那个进程信号量
这个时候它的第二个参数semnum是忽略的
你简单传0 传什么它都不管
第四个 它也不需要
当你在真正管理和控制进程信号量的时候
当你需要第四个参数的时候
那么这第四个参数的型
就是union semun这个型
这个型 你的操作系统中
可能没有给你定义
那么你就必须补充它的定义
这个定义必须是这个模样
union semun ｛ int val; struct semid_ds * buf;
unsigned short int * array; struct seminfo * __buf; ｝
固定的
union是一个非常特殊的结构
我们此前没有讲过
它和struct那个结构体的定义
是非常非常类似的
唯一的差别就是union中定义的所有的字段
共享同样的存储空间
也就是说 在任何时候
它就只有一个字段是有意义的
其它字段表达的是同样的存储区
但是那个型的解释对我们是没有意义的
所以特别注意这一点
union是共用同样存储区的一个字段的集合
它这个字段的序列和struct是不一样的
struct每一个字段是连续存储的
而union所有的字段
是存在同样一个地址区的 同一个位置
任何时候我们只能使用其中一个字段
初始化进程信号量
就要使用SETALL这个命令
第一个参数
当然传的是进程信号量集的标识符
第二参数 你设为0就完了
第三个参数 就是它的cmd要设成SETALL
它就会全部给你设定
初始化这个进程信号量
设定每一个进程信号量它的值是多少
设定它的初始值是多少
这个时候你就要传第四个参数arg
必须设定好那个union的array那个字段
标记这些元素的
每一个进程信号量的信号数是多少
然后把那个数组基地址传进去
就按照这个模式初始化进程信号量集
还有一些常用的命令我们就不解释了
你比如讲 我们看这样的一段代码
我们可以实现一个函数AcquireBinarySemaphore()
获取一个二元信号量
所谓二元信号量就是它的值
信号量的值只能是0或者1
要么是0要么是1 就叫二元信号量
我们可以获取它
传一个键值进去
它的信号量标记进去
我们调用semget()就能获取这个信号量
如果一开始没有 它就给你创建
如果有了
它就会给你返回那个信号量的ID
ReleaseBinarySemaphore()
就会释放这个二元信号量
我们会调用semctl() 传IPC_RMID
把它清除掉
初始化这个二元信号量
就必须定义这样一个短整型数组
我们这里面就1个信号量
这个数组里就1个元素
所以就传了一个1
如果有2个 你就得传2个元素
特别注意这一点
然后把union这个字段array
初始化成我们数组的基地址
这是初始化记号 就是“.array”
就只初始化union的这个array字段
其它字段我们不初始化
你这里有两个进程信号量
那么数组上就要包括两个信号量值
如果是一个 就只写一个
把这个数组基地址传进去
semun那个联合体、共用体
不就被我们定义好了吗
然后我们调用semctl()
发送SETALL命令
就会把所有的进程信号量的初始值
都给你设定好
它的信号数就会被你设定好
就使用array这里边的数据设定好
刚才不说了嘛
一个进程信号量 它是一个集合
里面肯定好多个元素
每个元素的进程信号数就会一一设定好
这就是信号量的获取、释放
包括初始化
进程信号量最重要的两个操作——等待和发布
被合并成了一个函数 semop()
第一个参数semid
进程信号量的ID
第二个参数是指向sembuf
这样结构体的指针 sops
成功的时候
就会返回这个进程信号量的标识符
失败的时候就会返回-1
设errno的值
struct sembuf 这个型
它有两个最重要的成员
一个是sem_num
你需要操作的进程信号集中的
那个信号量的元素下标 这是一个
第二个sem_op 就是你想做的操作
它就用一个简单的整数来表达
你传1就表示把信号量加1
那就是V操作
你减1就表示把信号量值减1
信号数减1
那就是一个P操作
同时你还可以减2、加2 这都可以去做
如果不满足信号量操作的要求
进程就会被阻塞 注意这一点
第三个参数就是sem_flg
调用这个函数的时候
它的那个标记
访问操作的操作标志
就是调用这个semop()这个函数的时候
它的操作标志
sem_flg一般情况下
你指定成IPC_NOWAIT
它就会不阻塞我们的进程
它就不等待了嘛
如果你指定SEM_UNDO
那就在进程退出的时候
它就会做取消操作
就把它的P操作、V操作都给取消掉
就这个意思
我们看实际的例子
我们可以写一个函数
叫WaitBinarySemaphore()
传semid进去
我们设定好它的sembuf这个结构体数组
设定好包括它的操作
我们是要减1 当然是P操作
标记是SEM_UNDO 然后调用semop()
调用这个函数去执行它 就OK了
如果是一个post操作
那么PostBinarySemaphore()这个函数的实现
具体的代码非常非常类似
就是一个 sem_op那个字段
从-1变成了1 这就是一个post
所以等待也好 发布也好
它的操作的逻辑是一样的
仅仅是一个字段的差别