【进程间通信】信号量

这里的信号量是指内核中支持,在系统空间实现,只不过由用户进程直接使用;要与内核中信号量向区分;


(1)创建或寻找信号量在semget()完成的;sys_semget()与sys_msgget()几乎是一样的,只不过将newque换成了newary();在newary()中,此时entries指向同一个ipc_id数组,数组中p指向一个ipc_perm,此时它的数组变成了sem_array数据结构;在sem_array中有个sem_base指针,它是一个结构数组,每一个sem都是一个信号量,它的大小由nsems,与sem_array在一起分配的;sys_semget可以建立一组而不是一个信号,主要是为了防止死锁,在SEMOP操作中,要么取得所有的共享资源的使用权,或者是不占用任何共享资源地等待;

(2)信号量操作在semop()完成的;在sys_semop()中,tsops指向用户空间的一个sembuf结构数组,而nsops则是数组的大小 ;数组中每一项都规定了对一个信号的操作,而对数组中所有的规定的操作也都是原子的;在sembuf结构体中,sem_num为具体信号在通过SEMGET建立的一组信号量中的下标,sem_op则为一个小整数,可取-1,0,1,表示相应的资源操作,semflg是个标志位,若是IPC_NOWAIT,表示条件不能满足时就不要等待睡眠,立即返回,SEM_UNDO表示如果昂前进程exit()时未还申请的资源,就要由内核代为退还;先使用copy_from_user将sembuf数组拷贝过来;先使用sem_lock加锁和sem_checkid()检查一体化标号;然后是对所有的信号量操作进行一番统计;看看哪几项是要改变信号来那个的,哪几项是要SEM_UNDO的;如果有一个信号量规定了SEM_UNDO,那就要为其分配一个sem_undo数据结构,用来记录当前进程对每组信号量的债务,因此在task_struct中是有一个sem_undo结构队列的;每一个进程在记住欠谁债务的同时,还要记住有谁欠了它的债,因此在sem_array()中也有个指针undo,也是维持一个sem_undo队列;每一个sem_undo有两个指针分别用来链入到上述的两个队列中;使用的是alloc_undo来分配一个sem_undo并完成两个队列的链入;


(3)然后调用try_atomic_semop来试图将给定的所有信号量当做一个整体来完成;在操作这个for循环的时候,for正常结束,也就是对每个信号量的操作都没有使它的值semval变成负数的话,那就已经成功取得了所需的全部资源了;否则有三种情况使它break;首先,如果某个信号量的操作使其数值超过了最大的SEMVMX,本次系统调用已经支持不下去了,那就要转到out_of_range设置出错代码为ERANGE,再转到undo,通过一个while()循环将前面已经完成的操作也都抵消掉,若SEM_UNDO标志为1的话,还要将记账的资源也要还原的;第二种,是对某个信号量的操作使它变成了负数;这表示获取这个信号量代表的资源的努力受到了阻碍;一方面要根据IPC_NOWAIT标志函数的返回值;另一方面要通过undo处的while循环将已取得的资源全都退还;第三种,对某个信号量的操作sem_op的值为0,而这个信号量的当前值又是非0的,这种情况和第二种情况相同,也要使用IPC_NOWAIT来判断;返回值到sys_semop有三种情况,首先0表示所有的操作都成功了,负值表示出错了,到标号update那边处理一下即可;而返回值为1,表示某个信号量的操作失败了,需要睡眠等待;

(4)返回值大于1时,在semop()中,与报文队列类似,睡眠时要将代表着当前进程的sem_queue数据结构链入相应的sem_array数据结构中的sem_pending队列,根据alter是否改变,将其链入队列头还是尾,队列头的进程优先可以访问;如果本次信号量的值改变了,那就说明这个信号量集合发生了改变,原来因条件不满足等待的进程也许现在可以满足了,是通过update_queue()完成的;

(5)在update_queue()中,循环队列依次让每个正在睡眠中等待的进程试一下,看看能否完成其信号来那个的操作,还是通过try_atomic_semop()来完成的,最后一个参数表示只是试一下能不能成功,不执行真正的信号操作;其中有了信号量资源,q->status设置成1,或者出错了都是要将睡眠的进程唤醒的,q->status设置成出错码;由于发现q->status为1的都会跳过,找到一个可满足的进程就会返回了;从schedule()返回后,先通过sem_lock再次确认操作的对象仍是原先的信号集合,并将其锁住;若q->status为1,说明条件以满足了,但是还是要通过try_atomic_semop()再来检测;但是q->status不为1,此时可能是接收到信号而唤醒,也有可能是update_queue所设置的出错代码;

(6)对于最后的SEM_UNDO标志成1的,在exit()中有一个sem_exit(),如果exit()的进程的某个信号来那个在集合的队列中等待,将其移除队列,然后扫描该进程的semundo队列,根据每一个sem_undo数据结构的记载,依次对相应的信号量集合中的信号量数值做出调整,最后也是调用update_queue()唤醒可能在等待的进程;

(7)对于信号量的控制与管理,是在semctl()中完成的,与msgctl类似;

### Linux 进程间通信信号量的使用方法 在 Linux 系统中,信号量是一种用于进程间通信和同步的机制。它通过提供一种协调多个进程对共享资源访问的方式,确保了临界区的安全性。以下是信号量在 Linux 中进行进程间通信的具体使用方法。 #### 一、信号量的基本概念 信号量可以分为两种类型:二值信号量和计数信号量。二值信号量的值只能取 0 或 1,而计数信号量的值可以大于 1[^4]。信号量的核心操作包括 P 操作(等待)和 V 操作(释放)。P 操作会减少信号量的值,如果信号量的值小于 0,则调用该操作的进程会被阻塞;V 操作会增加信号量的值,并唤醒因信号量不足而被阻塞的进程。 #### 二、信号量接口函数 Linux 提供了一系列系统调用以支持信号量的操作: 1. **创建或获取信号量集** 使用 `semget()` 函数可以创建一个新的信号量集或获取一个已存在的信号量集。 ```c int semget(key_t key, int nsems, int semflg); ``` 参数说明: - `key`:用于标识信号量集的键值。 - `nsems`:信号量集中信号量的数量。 - `semflg`:标志位,通常设置为 `IPC_CREAT | IPC_EXCL` 来创建新信号量集[^2]。 2. **初始化信号量** 使用 `semctl()` 函数可以初始化信号量的值。 ```c int semctl(int semid, int semnum, int cmd, ... /* union semun arg */ ); ``` 参数说明: - `semid`:由 `semget()` 返回的信号量集标识符。 - `semnum`:信号量集中信号量的编号。 - `cmd`:指定操作类型,例如 `SETVAL` 用于设置信号量的初始值。 - `arg`:传递给命令的参数,通常是一个 `union semun` 类型的变量[^2]。 3. **执行 P/V 操作** 使用 `semop()` 函数可以对信号量执行 P 操作或 V 操作。 ```c int semop(int semid, struct sembuf *sops, size_t nsops); ``` 参数说明: - `semid`:信号量集标识符。 - `sops`:指向 `struct sembuf` 数组的指针,数组中的每个元素定义了一个信号量操作。 - `nsops`:信号量操作的数量。 4. **删除信号量** 使用 `semctl()` 函数可以删除信号量集。 ```c int semctl(int semid, int semnum, int cmd); ``` 将 `cmd` 设置为 `IPC_RMID` 即可删除信号量集[^2]。 #### 三、代码示例 以下是一个简单的代码示例,演示如何使用信号量实现进程间的同步: ```c #include <stdio.h> #include <stdlib.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h> union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { key_t key = ftok("semfile", 65); // 创建一个唯一的键值 int semid = semget(key, 1, IPC_CREAT | 0666); // 创建信号量集 if (semid < 0) { perror("semget"); exit(1); } union semun arg; arg.val = 1; // 初始化信号量值为 1 if (semctl(semid, 0, SETVAL, arg) < 0) { perror("semctl"); exit(1); } struct sembuf p_op = {0, -1, SEM_UNDO}; // P 操作 struct sembuf v_op = {0, 1, SEM_UNDO}; // V 操作 if (fork() == 0) { // 子进程 sleep(1); // 模拟延迟 printf("Child: Waiting for semaphore...\n"); if (semop(semid, &p_op, 1) < 0) { perror("semop"); exit(1); } printf("Child: Critical section entered.\n"); sleep(2); // 模拟临界区操作 printf("Child: Leaving critical section.\n"); if (semop(semid, &v_op, 1) < 0) { perror("semop"); exit(1); } } else { // 父进程 printf("Parent: Waiting for semaphore...\n"); if (semop(semid, &p_op, 1) < 0) { perror("semop"); exit(1); } printf("Parent: Critical section entered.\n"); sleep(2); // 模拟临界区操作 printf("Parent: Leaving critical section.\n"); if (semop(semid, &v_op, 1) < 0) { perror("semop"); exit(1); } } return 0; } ``` #### 四、注意事项 - 信号量集的键值可以通过 `ftok()` 函数生成,确保不同进程能够通过相同的键值访问同一个信号量集[^1]。 - 在多进程环境中,必须正确初始化信号量的值,否则可能导致死锁或竞争条件。 - 删除信号量集时需谨慎,确保所有进程已完成对该信号量集的使用[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值