文章目录
- 引言
- 🧑🏫 **Linux 中的 System V 信号量:基础与实战**
- 🌟 **System V 信号量简介**
- 🔑 **关键概念**
- 🛠️ **System V 信号量的相关函数**
- 📜 **函数原型**
- 1. `semget()` — 创建或获取信号量集
- 2. `semop()` — 执行信号量操作
- 3. `semctl()` — 控制信号量集
- 4. `semctl()` — 删除信号量集
- 💡 **示例:基本的信号量操作**
- 🔍 **解析**
- 🧐 **进阶实例:生产者-消费者问题**
- 📝 **总结:**
切勿好高骛远,任何大事,都是一砖一瓦垒起来的 ——家驹(StrangeHead)
引言
上一篇文章我们知道了什么是信号,以及如何使用POSIX信号量,什么是P操作,什么是V操作,了解过Linux进化史的小伙伴就知道,POSIX是Unix的一个标准规范(Unix及其衍生系统遵循的一系列标准的集合),而System V也只不过是别家公司基于Linux写的另一套信号量罢了。回顾一下上一节的知识,接下来这一节对你来说简直是轻松驾驭!
🧑🏫 Linux 中的 System V 信号量:基础与实战
在并发编程中,尤其是在多进程环境下,资源共享是一个常见问题。为了避免多个进程同时访问某个共享资源,导致数据不一致或系统崩溃,System V 信号量应运而生!信号量是一种原始的同步机制,可以在多个进程间进行通信和控制资源访问。
🌟 System V 信号量简介
System V 信号量(sem_t)是 Unix/Linux 系统中一种进程间同步和互斥的机制。它们通常用于解决 互斥(保证一次只有一个进程访问资源)和 同步(控制多个进程的执行顺序)问题。
🔑 关键概念
- 信号量集:每个信号量都是一个整数,表示某一资源的可用数量。多个信号量可以组合在一起形成一个信号量集。
- P 操作(等待):用于请求资源,减少信号量。如果信号量为 0,进程会阻塞,直到资源可用。
- V 操作(释放):用于释放资源,增加信号量,并唤醒一个被阻塞的进程。
🛠️ System V 信号量的相关函数
系统提供了几个关键的函数来操作信号量。它们分别是:
semget()
– 获取信号量集的标识符。semctl()
– 控制信号量集的属性。semop()
– 执行信号量操作(P 操作和 V 操作)。semctl()
– 删除信号量集。
📜 函数原型
1. semget()
— 创建或获取信号量集
int semget(key_t key, int nsems, int semflg);
示例:
int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
这将创建一个包含 1 个信号量的信号量集,并将其标识符保存在 sem_id 中。信号量集权限为 0666,表示所有用户均可读写。
- key: 信号量集的标识符,通常用
IPC_PRIVATE
表示创建一个新的信号量集,或者指定一个特定的键值。 - nsems: 信号量集的数量,即信号量的个数。
- semflg: 权限标志,通常使用
IPC_CREAT
来创建一个新的信号量集,或者使用IPC_EXCL
保证不会与已有的信号量集冲突。
IPC_CREAT:创建一个新的信号量集。
IPC_EXCL:如果信号量集已经存在,返回错误。
0666:信号量集的权限,类似文件权限。
- 返回值:返回一个信号量集的标识符。
- 失败时,返回 -1,并设置 errno。
2. semop()
— 执行信号量操作
int semop(int semid, struct sembuf *sops, size_t nsops);
示例:
struct sembuf sops;
sops.sem_num = 0; // 第一个信号量
sops.sem_op = -1; // P 操作(减 1,表示请求资源)
sops.sem_flg = 0;if (semop(sem_id, &sops, 1) == -1) {perror("semop");exit(1);
}
该代码会让当前进程等待,直到第一个信号量的值大于 0。
-
semid: 信号量集的标识符。
-
sops: 指向一个
struct sembuf
数组的指针,表示一系列信号量操作。每个sops
包含:-
sem_num:操作的信号量编号,表示要操作的信号量。
-
sem_op:操作类型:
- -1:P 操作,减少信号量(等待资源)。
- 1:V 操作,增加信号量(释放资源)。
-
sem_flg:通常为 0,表示没有额外标志。
-
-
nsops: 操作的数量。
-
返回值:成功时返回 0,失败时返回 -1。
-
失败时,返回 -1,并设置 errno。
3. semctl()
— 控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
示例:
// 设置信号量的初始值
if (semctl(sem_id, 0, SETVAL, 1) == -1) {perror("semctl");exit(1);
}// 获取信号量的值
int val = semctl(sem_id, 0, GETVAL);
if (val == -1) {perror("semctl");exit(1);
}
printf("信号量值为:%d\n", val);
- semid: 信号量集标识符。
- semnum: 信号量的编号。如果是对整个信号量集进行操作,可以传入 0。
- cmd: 控制命令,常用命令有:
IPC_RMID
:删除信号量集。GETVAL
:获取信号量当前的值。SETVAL
:设置信号量的值。
返回值:根据命令不同,返回不同的值。
- 根据命令不同,返回不同的值。例如,
SETVAL
返回旧的信号量值,GETVAL
返回信号量的当前值。
4. semctl()
— 删除信号量集
通过 semctl()
以 IPC_RMID
命令删除信号量集。
💡 示例:基本的信号量操作
我们通过一个简单的程序来演示如何创建信号量、执行 P 操作、V 操作,以及如何删除信号量集。
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/types.h>int main() {// 创建一个信号量集,包含 1 个信号量int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);if (sem_id == -1) {perror("semget");exit(1);}// 初始化信号量为 1if (semctl(sem_id, 0, SETVAL, 1) == -1) {perror("semctl");exit(1);}struct sembuf sops;sops.sem_num = 0; // 使用第一个信号量sops.sem_flg = 0; // 不使用其他标志位// P 操作:请求资源,信号量减 1sops.sem_op = -1; // 这里是 P 操作(信号量减 1)if (semop(sem_id, &sops, 1) == -1) {perror("semop");exit(1);}printf("进程 %d 获取资源,开始工作...\n", getpid());sleep(2); // 模拟处理资源的时间// V 操作:释放资源,信号量加 1sops.sem_op = 1; // 这里是 V 操作(信号量加 1)if (semop(sem_id, &sops, 1) == -1) {perror("semop");exit(1);}printf("进程 %d 释放资源,结束工作!\n", getpid());// 删除信号量集if (semctl(sem_id, 0, IPC_RMID) == -1) {perror("semctl");exit(1);}return 0;
}
🔍 解析
semget()
:创建一个信号量集,信号量的数量为 1(即一个资源)。semctl()
:初始化信号量的值为 1,表示资源初始状态为可用。semop()
:执行 P 操作,使得进程占用资源。若信号量为 0,进程将被阻塞。sleep(2)
:模拟进程使用资源的时间。semop()
:执行 V 操作,释放资源,将信号量值加 1,唤醒其他等待的进程。semctl()
:删除信号量集,释放资源。
🧐 进阶实例:生产者-消费者问题
在这个经典问题中,生产者不断生产资源,消费者不断消费资源,我们使用信号量来控制生产者和消费者的同步。
下一节我们使用System V来实现这个进阶实例
📝 总结:
System V 信号量是 Linux 系统中提供的一种强大的并发控制工具。通过 semget()
创建信号量集,利用 semop()
执行操作,使用 semctl()
控制信号量的值,进程间可以高效地同步和互斥。对于初学者来说,理解信号量的基本操作和应用场景(如生产者-消费者问题)是学习多进程编程的重要一步。