不仅用户程序, 系统内核程序执行时, 多道程序间也存在同步问题。 内核信号量我们不做研究, 我们主要学习用户态编程的同步控制机制。 由于历史的原因, Linux 系统上存在两套信号量标准: System V 信号量和POSIX 信号量。 传统的System V 信号量在大多数Linux/UNIX 上都已实现, 而POSIX 标准的也正变得越来越流行, 两者的区别总结如表5.3 所示。
表5.3 System V 信号量和POSIX 信号量比较
下面以System V 信号量集控制进程同步为例, 学习体验控制同步的作用。
1.Linux System V 信号量集操作函数
使用时需要包括的头文件有:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
(1)创建或打开信号量集函数semget。
函数原型如下。
举例:
①key: 创建或打开的信号量集的键值, 常用IPC_ PRIVATE 由系统分配。
②nsems: 新建信号量集中的信号量个数, 为1 时相当于操作单个信号量。
③semflg: 对信号量集合的打开或存取操作依赖于semflg 参数的取值。
•IPC_CREAT : 如果内核中没有新创建的信号量集合, 则创建它。
•IPC_EXCL : IPC_ EXCL 单独是没有用的, 要与IPC_CREAT 结合使用, 要么创建一个新的集合, 要么对已存在的集合返回-1。 可以保证新创建集合的打开和存取。另一种可选项是把一个八进制与掩码或, 形成信号量集合的存取权限。
该调用执行成功会返回信号量的ID, 否则返回-1。 示例语句代表新建一个只有一个信号量的私有信号量集。
(2) 信号量申请或释放操作函数semop。函数原型如下。
举例如下。
其中操作定义在一种结构体里, 如:
①semid: 信号量集的ID。
②sops: 指向对信号量集中的信号进行操作的数组, 数组类型为sembuf。
③nsops: 指示第二个操作参数数组sops 的大小, 该数组元素的数据类型定义如下。
示例代码可解释为: 利用sem_ b 结构对m 信号量集做操作, sem_ b 只有1 个长度, 所以意味着就做1 个操作。 而sem_ b 中定义的操作是对信号量集m 的索引为0 的第一个信号做wait操作, 如果程序意外退出, 为防止信号量没释放造成的死锁, 会将已做的wait 操作UNDO。
(3) 设置信号量函数semctl。
函数原型如下。
举例如下。
①semid: 信号量集的标识号。
②semnum: 要操作的信号量集中信号量的索引值, 对于集合上的第一个信号量, 该值为0。
③cmd: 表示要执行的命令, 这些命令及解释如下。(www.daowen.com)
•IPC_STAT: 读取一个信号量集的数据结构semid_ ds, 并将其存储在semun 中的buf参数中。
•IPC_SET: 设置信号量集的数据结构semid_ds 中的元素ipc_perm 值取自semun 的buf 参数。
•IPC_RMID: 将信号量集从内存中删除。
•GETALL: 用于读取信号量集中的所有信号量的值。
•GETNCNT: 返回正在等待资源的进程数目。
•GETPID: 返回最后一个执行semop 操作的进程的PID。
•GETVAL: 返回信号量集中的一个单个的信号量的值。
•GETZCNT: 返回正在等待完全空闲的资源的进程数目。
•SETALL: 设置信号量集中的所有的信号量的值。
•SETVAL: 设置信号量集中的一个单独的信号量的值
④arg: 是与cmd 搭配使用的, 类型semun 的定义在include/linux/sem.h, 对于不同的命令, 需要用到的union 里的内部变量有所不同。
该调用如果成功,返回值为一个正数;如果失败,返回值为-1,而错误号errno 有如下情况。
示例代码直接利用常数1 给信号量设置了值,严格说,第四个参数应该是semun 类型的变量,这种设置常数的方法在x86 等小端系统可以运行,但程序移植到PowerPC 等系统上或许会崩溃。
2. 进程互斥控制实例
【例5-13】 设有一个房间资源,一次只能一个进程使用,用信号量操作模拟3 个进程互斥使用房间的过程。
其执行结果如下。
试想象本代码中没有加入信号量操作语句(代码中标注有#的语句), 则fork 产生的多个进程会并发执行, 无论哪个进程获得了处理机, 它就会按代码执行到while 中的输出语句(代码中标注有∗的语句), 表示他进入房间了, 这与我们要求一次只能一个人进入不符。 而加入了信号量控制语句的程序, 每个进程要打印输出进入房间之前, semop 会根据信号量room 的取值控制进程是阻塞还是继续下一条语句。 如果进程阻塞了, 也不用担心一直执行不到, 会有先占用room 的进程在做完一次while 模拟的进房间操作后释放room信号, 也就唤醒了阻塞的进程。
从下面的输出我们可以看到, 0、 1、 2 三个进程确实是交替输出进入房间了。 可尝试sleep(rand()%秒数), 使3 个进程后续提出进房间的时间随机化, 运行看效果, 一定也能反映出只有一个人在房间里, 只是争抢成功进入的顺序不确定。
本例中房间信号量的初值如果设置为大于1 的数, 就不再是控制互斥, 而是利用信号量控制, 起到资源计数的作用, 这也是信号量相比于互斥锁的灵活特性的表现。
3. 进程有序合作控制实例
【例5-14】 设有一个盘子能放1 个水果, 儿子读书累了就拿水果吃, 没有水果就等待;父亲和母亲都不断给儿子放水果, 盘子满了就不能放。 用信号量操作模拟这3 个进程的运行过程。
其执行结果如下。
下面的输出表现出了两种同步关系。
一方面是父母间的互斥: 父母进程能否顺利执行放水果是决定于对信号量e 的申请,而e 的初值为1, 那么哪个进程先获得CPU, 抢险申请到e(将e 减1), 另一个再申请e 时会由于将e 减为负而被信号量申请操作阻塞。 所以, 父母进程之间在对盘子空的使用上是互斥的。
另一方面是父/母和孩子间的顺序合作关系: 如果孩子进程先执行了, 他会由于f 初值为0, 被阻塞在对f 信号量的申请操作上, 而无论父母谁先申请到e, 放完水果后都会释放f, 从而唤醒孩子进程向下执行吃水果。 在这种情况下父母是孩子的前趋。
如果孩子没有吃掉水果, 父母会由于没有空可用而阻塞在申请e 处, 所以儿子吃水果后, e 信号量的释放操作又触发了父或母进程在等待e 处被唤醒。 在这种情况下, 孩子又是父母的前趋。
我们在代码中加入sleep 调整他们随机的执行速度, 能测试各种执行情况的可能, 以下是一种输出。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。