Linux应用开发-12-system-V IPC 信号量
信号量:本质上是一个计数器,用于协调多进程间对共享数据对象的读取,它不以传送数据为主要目的,它主要是用来保护共享资源,信号量等待和发送信号,有两种操作PV:P 操作是申请资源,V 操作是释放资源。
核心 API 函数
semget() - (获取/创建信号量集)
用于创建一个或多个信号量(称为“信号量集”),或获取一个已存在的信号量集的ID。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
/*
key: 全局唯一的关键字
nsems: 要在“信号量集”中创建多少个信号量
semflg: 标志位,同 msgget(IPC_CREAT | 0666)。
*/
semop() - (执行 P / V 操作)
唯一一个能修改信号量值的函数,用它来实现 P 操作和 V 操作。
int semop(int semid, struct sembuf *sops, size_t nsops);
//semid:System V 信号量的标识符,用来标识一个信号量。
//nsops:表示 sops 数组的数量,如只有一个 sops 数组,nsops 就设置为 1
//struct sembuf sops : 结构体,通过填充告诉semop 你想做什么
struct sembuf {unsigned short sem_num; // 要操作信号量集中的第几个信号量 (0 是第一个)short sem_op; // 要执行的操作 short sem_flg; // 标志位 (如 SEM_UNDO)
};
/*
sem_op = -1 (P 操作):信号量当前值为 0,则阻塞等待。
sem_op = 1 (V 操作):加 1(释放资源),并唤醒一个正在等待的进程。
sem_op = 0:阻塞进程,直到信号量的值变为 0。
sem_flg = SEM_UNDO:内核会跟踪设置了 SEM_UNDO 的进程对信号量的所有修改(调整值)。一旦该进程终止,内核会自动撤销这些修改(将P操作“拿走”的资源“还回去”),从而解锁其他正在等待的进程。
*/
semctl() - (控制/初始化/删除)
“万能”控制函数,用于初始化信号量的值、获取状态或删除信号量。
int semctl(int semid, int semnum, int cmd, ...);
//semid:System V 信号量的标识符;
//semnum:表示信号量集中的第 semnum 个信号量。它的取值范围:0 ~ nsems-1 。
/*
cmd:SETVAL: 用于初始化第 semnum 个信号量的值。这是必须的步骤,因为 semget 只负责创建,不负责设置初始值。
IPC_RMID: 删除这个信号量集,释放内核资源。
IPC_STAT: 获取信号量集的状态。
*/
//第四个参数是可选的:如果使用该参数,该参数的类型为 union semun,它是多个特定命令的联合体
union semun {int val; /* 用于 SETVAL 命令: 设置单个信号量的值时,使用这个成员来传递该值。*/struct semid_ds *buf; /* 用于 IPC_STAT 和 IPC_SET 命令: IPC_STAT: 用于接收内核返回的信号量集状态信息。IPC_SET: 用于传递您想要设置的信号量集属性(如权限)。*/unsigned short *array; /* 用于 GETALL 和 SETALL 命令: GETALL: 用于接收内核返回的所有信号量的值的数组。SETALL: 用于传递一个包含所有信号量新值的数组。*/struct seminfo *__buf; /* 用于 IPC_INFO (Linux 特定) 命令: 用于获取全系统范围内的信号量限制信息。*/
};
使用示例
sem.h (头文件)
#ifndef __SEM_H__
#define __SEM_H__/** union semun 是 semctl() 函数必需的,*/
union semun {int val; /* 用于 SETVAL 命令 */struct semid_ds *buf; /* 用于 IPC_STAT, IPC_SET 命令 */unsigned short *array; /* 用于 GETALL, SETALL 命令 */struct seminfo *__buf; /* 用于 IPC_INFO */
};/*** 信号量初始化(赋值)函数* sem_id: 信号量ID* init_value: 要设置的初始值*/
int init_sem(int sem_id, int init_value);/*** 从系统中删除信号量的函数* sem_id: 信号量ID*/
int del_sem(int sem_id);/*** P 操作函数 (申请资源, 信号量值减 1)* sem_id: 信号量ID*/
int sem_p(int sem_id);/*** V 操作函数 (释放资源, 信号量值加 1)* sem_id: 信号量ID*/
int sem_v(int sem_id);#endif // __SEM_H__
sem.c (信号量封装实现)
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "sem.h"/** 信号量初始化(赋值)函数*/
int init_sem(int sem_id, int init_value)
{union semun sem_union;sem_union.val = init_value; // init_value 为初始值// semnum=0 (第0个信号量), cmd=SETVAL (设置值)if (semctl(sem_id, 0, SETVAL, sem_union) == -1){perror("Initialize semaphore");return -1;}return 0;
}/** 从系统中删除信号量的函数*/
int del_sem(int sem_id)
{union semun sem_union; // 即使不用,某些版本的semctl也需要它// cmd=IPC_RMID (删除)if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1){perror("Delete semaphore");return -1;}return 0; // del_sem 通常不需要返回值
}/** P 操作函数* (对应您文档中的 Line 42-55)*/
int sem_p(int sem_id)
{struct sembuf sops;sops.sem_num = 0; /* 单个信号量的编号为 0 */sops.sem_op = -1; /* 表示 P 操作 (减 1) */sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将自动还原信号量 */// nsops=1, 表示只操作 1 个信号量if (semop(sem_id, &sops, 1) == -1){perror("P operation");return -1;}return 0;
}/** V 操作函数* (对应您文档中的 Line 58-71)*/
int sem_v(int sem_id)
{struct sembuf sops;sops.sem_num = 0; /* 单个信号量的编号为 0 */sops.sem_op = 1; /* 表示 V 操作 (加 1) */sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将自动还原信号量 */// nsops=1, 表示只操作 1 个信号量if (semop(sem_id, &sops, 1) == -1){perror("V operation");return -1;}return 0;
}
test.c (主测试程序)
使用 sem.c 中的函数来协调父子进程
fork一个进程,初始信号量为0,父进程先进入进行P由于没有进程父进程阻塞,子进程等待3s后释放资源,此时父进程才能申请到资源,最后释放资源。
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "sem.h"#define DELAY_TIME 3int main(void)
{pid_t result;int sem_id;key_t key = (key_t)6666; // 使用一个固定的 key// 创建一个信号量集,包含1个信号量// 权限为 0666 | IPC_CREAT (创建,如果不存在)sem_id = semget(key, 1, 0666 | IPC_CREAT);if (sem_id == -1) {perror("semget");exit(1);}// 调用 init_sem 将信号量初始值设为 0// 0 = “没有资源”,P操作会立即阻塞init_sem(sem_id, 0);// 创建子进程result = fork();if (result == -1){perror("Fork");exit(1);}/* 返回值为 0 代表子进程 */else if (result == 0){// --- 子进程代码块 ---printf("子进程 (PID = %d) 将暂停 %d s...\n", getpid(), DELAY_TIME);sleep(DELAY_TIME); // 模拟耗时操作printf("释放操作的是%d进程>0父进程,0子进程\n", result);// (关键) 子进程完成工作,执行 V 操作 (释放资源)sem_v(sem_id);}else{// 父进程执行 P 操作 (申请资源)// 因为信号量初始值为 0, 父进程会在此“阻塞”// 直到子进程执行了 sem_v()sem_p(sem_id);printf("父进程获取到了资源,现在返回值的是子进程 %d 当前进程(PID = %d)\n", result, getpid());// 父进程也执行 V 操作sem_v(sem_id);printf("执行资源释放\n");// 父进程负责删除信号量del_sem(sem_id);}exit(0);
}

