semop – 信号量操作
函数介绍
semop
系统调用用于对System V信号量集执行操作。它是信号量机制的核心操作函数,通过原子性地执行一系列信号量操作来实现进程同步。
](https://www.calcguide.tech/2025/08/18/semop%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)
函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能
对信号量集执行原子操作,用于进程间同步。
参数
int semid
: 信号量集的标识符(由semget返回)struct sembuf *sops
: 指向sembuf结构体数组的指针struct sembuf { unsigned short sem_num; // 信号量在集合中的索引 short sem_op; // 操作类型 short sem_flg; // 操作标志 };
sem_op > 0
: 释放资源(V操作),将sem_op加到信号量值上sem_op < 0
: 请求资源(P操作),从信号量值中减去sem_op的绝对值sem_op = 0
: 等待信号量值变为0
unsigned nsops
: sops数组中操作的数量
返回值
- 成功时返回0
- 失败时返回-1,并设置errno:
EAGAIN
: 操作会阻塞但设置了IPC_NOWAITEIDRM
: 信号量集已被删除EINTR
: 系统调用被信号中断EINVAL
: 参数无效EFBIG
: sem_op绝对值过大
相似函数
semtimedop()
: 带超时的信号量操作semget()
: 获取信号量集semctl()
: 控制信号量集
示例代码
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
// 信号量操作联合体
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// P操作(等待/减1)
int P(int semid, int sem_num) {
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1; // P操作
sb.sem_flg = 0;
return semop(semid, &sb, 1);
}
// V操作(发送/加1)
int V(int semid, int sem_num) {
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1; // V操作
sb.sem_flg = 0;
return semop(semid, &sb, 1);
}
// 等待信号量为0
int wait_zero(int semid, int sem_num) {
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 0;
sb.sem_flg = 0;
return semop(semid, &sb, 1);
}
int main() {
key_t key;
int semid;
union semun arg;
printf("=== Semop函数示例 ===\n");
// 创建信号量集
key = ftok(".", 'b');
if (key == -1) {
perror("ftok失败");
exit(EXIT_FAILURE);
}
semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1) {
if (errno == EEXIST) {
// 如果已存在,先删除再创建
semid = semget(key, 2, 0666);
semctl(semid, 0, IPC_RMID);
semid = semget(key, 2, 0666 | IPC_CREAT | IPC_EXCL);
} else {
perror("semget失败");
exit(EXIT_FAILURE);
}
}
if (semid == -1) {
perror("创建信号量集失败");
exit(EXIT_FAILURE);
}
printf("成功创建信号量集,标识符: %d\n", semid);
// 初始化信号量
// 信号量0:用于互斥,初始值为1(二进制信号量)
// 信号量1:用于资源计数,初始值为3(表示有3个资源)
unsigned short init_values[2] = {1, 3};
arg.array = init_values;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("初始化信号量失败");
semctl(semid, 0, IPC_RMID);
exit(EXIT_FAILURE);
}
printf("信号量初始化完成\n");
printf(" 信号量0(互斥): %d\n", semctl(semid, 0, GETVAL));
printf(" 信号量1(资源): %d\n", semctl(semid, 1, GETVAL));
// 示例1: 基本的P/V操作
printf("\n示例1: 基本的P/V操作\n");
printf("操作前 - 信号量0: %d, 信号量1: %d\n",
semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
// P操作获取互斥锁
printf("执行P操作获取互斥锁...\n");
if (P(semid, 0) == -1) {
perror("P操作失败");
} else {
printf("成功获取互斥锁\n");
printf("操作后 - 信号量0: %d, 信号量1: %d\n",
semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
// V操作释放互斥锁
printf("执行V操作释放互斥锁...\n");
if (V(semid, 0) == -1) {
perror("V操作失败");
} else {
printf("成功释放互斥锁\n");
printf("操作后 - 信号量0: %d, 信号量1: %d\n",
semctl(semid, 0, GETVAL), semctl(semid, 1, GETVAL));
}
}
// 示例2: 多进程同步演示
printf("\n示例2: 多进程同步演示\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork失败");
semctl(semid, 0, IPC_RMID);
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
printf("子进程 %d 开始执行\n", getpid());
// 模拟需要互斥访问的临界区
printf("子进程尝试获取互斥锁...\n");
if (P(semid, 0) == 0) {
printf("子进程 %d 进入临界区\n", getpid());
printf("子进程在临界区工作2秒...\n");
sleep(2);
printf("子进程 %d 离开临界区\n", getpid());
V(semid, 0); // 释放锁
}
exit(EXIT_SUCCESS);
} else {
// 父进程
sleep(1); // 让子进程先运行
printf("父进程 %d 开始执行\n", getpid());
printf("父进程尝试获取互斥锁...\n");
if (P(semid, 0) == 0) {
printf("父进程 %d 进入临界区\n", getpid());
printf("父进程在临界区工作2秒...\n");
sleep(2);
printf("父进程 %d 离开临界区\n", getpid());
V(semid, 0); // 释放锁
}
// 等待子进程结束
wait(NULL);
}
// 示例3: 资源计数信号量演示
printf("\n示例3: 资源计数信号量演示\n");
printf("当前可用资源数: %d\n", semctl(semid, 1, GETVAL));
// 模拟多个进程竞争资源
for (int i = 0; i < 4; i++) {
pid = fork();
if (pid == 0) {
// 子进程
printf("进程 %d 尝试获取资源...\n", getpid());
// P操作获取资源
if (P(semid, 1) == 0) {
printf("进程 %d 成功获取资源,剩余资源: %d\n",
getpid(), semctl(semid, 1, GETVAL));
// 使用资源
printf("进程 %d 使用资源2秒...\n", getpid());
sleep(2);
// V操作释放资源
V(semid, 1);
printf("进程 %d 释放资源,剩余资源: %d\n",
getpid(), semctl(semid, 1, GETVAL));
} else {
printf("进程 %d 获取资源失败\n", getpid());
}
exit(EXIT_SUCCESS);
}
}
// 等待所有子进程结束
for (int i = 0; i < 4; i++) {
wait(NULL);
}
// 清理资源
printf("\n清理资源...\n");
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("删除信号量集失败");
} else {
printf("成功删除信号量集\n");
}
return 0;
}