semop(2) - Linux 手册页

名称

semop, semtimedop - 信号量操作

概要

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

int semtimedop(int semid, struct sembuf *sops, unsigned nsops,
               struct timespec *timeout);
glibc 的功能测试宏要求(参见 feature_test_macros(7))
semtimedop(): _GNU_SOURCE

描述

信号量集中的每个信号量都具有以下相关联的值

unsigned short  semval;   /* semaphore value */
unsigned short  semzcnt;  /* # waiting for zero */
unsigned short  semncnt;  /* # waiting for increase */
pid_t           sempid;   /* ID of process that did last op */
semop() 对 semid 指定的信号量集中的选定信号量执行操作。sops 指向的数组中 nsops 个元素中的每一个都指定了要在单个信号量上执行的操作。此结构的元素类型为 struct sembuf,包含以下成员
unsigned short sem_num;  /* semaphore number */
short          sem_op;   /* semaphore operation */
short          sem_flg;  /* operation flags */
sem_flg 中可识别的标志是 IPC_NOWAITSEM_UNDO。如果操作指定了 SEM_UNDO,则当进程终止时,该操作将自动撤销。
包含在 sops 中的操作集是按 数组顺序 执行的,并且是 原子 地执行,即这些操作要么作为一个完整的单元执行,要么完全不执行。如果并非所有操作都能立即执行,系统调用的行为取决于各 sem_flg 字段中是否存在 IPC_NOWAIT 标志,如下所述。

每个操作都在信号量集的第 sem_num 个信号量上执行,其中集合的第一个信号量编号为 0。操作有三种类型,由 sem_op 的值区分。

如果 sem_op 是正整数,则该操作将此值加到信号量值(semval)中。此外,如果为此操作指定了 SEM_UNDO,系统会更新此信号量的撤销计数(semadj)。此操作总是可以进行——它从不强制线程等待。调用进程必须对信号量集具有更改(alter)权限。

如果 sem_op 为零,则进程必须对信号量集具有读取权限。这是一种“等待零”操作:如果 semval 为零,则操作可以立即继续。否则,如果在 sem_flg 中指定了 IPC_NOWAIT,则 semop() 失败并将 errno 设置为 EAGAIN(且不执行 sops 中的任何操作)。否则,semzcnt(等待此信号量值变为零的线程计数)加一,并且线程进入睡眠状态,直到发生以下情况之一

semval 变为 0,此时 semzcnt 的值减一。

• 信号量集被移除:semop() 失败,并将 errno 设置为 EIDRM

• 调用线程捕获到信号:semzcnt 的值减一,并且 semop() 失败,并将 errno 设置为 EINTR

semtimedop() 调用中 timeout 指定的时间限制过期:semop() 失败,并将 errno 设置为 EAGAIN

如果 sem_op 小于零,则进程必须对信号量集具有更改权限。如果 semval 大于或等于 sem_op 的绝对值,则操作可以立即进行:从 semval 中减去 sem_op 的绝对值,并且如果为此操作指定了 SEM_UNDO,系统会更新此信号量的撤销计数(semadj)。如果 sem_op 的绝对值大于 semval,且在 sem_flg 中指定了 IPC_NOWAIT,则 semop() 失败,并将 errno 设置为 EAGAIN(且不执行 sops 中的任何操作)。否则,semncnt(等待此信号量值增加的线程计数)加一,线程进入睡眠状态,直到发生以下情况之一
semval 变得大于或等于 sem_op 的绝对值,此时 semncnt 的值减一,从 semval 中减去 sem_op 的绝对值,且如果为此操作指定了 SEM_UNDO,系统会更新此信号量的撤销计数(semadj)。

• 信号量集从系统中移除:semop() 失败,并将 errno 设置为 EIDRM

• 调用线程捕获到信号:semncnt 的值减一,并且 semop() 失败,并将 errno 设置为 EINTR

semtimedop() 调用中 timeout 指定的时间限制过期:系统调用失败,并将 errno 设置为 EAGAIN

成功完成后,数组 sops 指向的每个信号量的 sempid 值会被设置为调用者的进程 ID。此外,sem_otime 会被设置为当前时间。

semtimedop() 的行为与 semop() 相同,只是在调用线程需要睡眠的情况下,睡眠持续时间受限于 timeout 参数所指向的 timespec 结构体中所指定的流逝时间。(此睡眠间隔将向上取整至系统时钟粒度,由于内核调度延迟,间隔可能会超出少量时间。)如果达到指定的时间限制,semtimedop() 会失败并将 errno 设置为 EAGAIN(且不执行 sops 中的任何操作)。如果 timeout 参数为 NULL,则 semtimedop() 的行为与 semop() 完全相同。

返回值

如果成功,semop() 和 semtimedop() 返回 0;否则返回 -1,并将 errno 设置为指示错误的值。

错误

失败时,errno 被设置为以下值之一

E2BIG

参数 nsops 大于 SEMOPM(每个系统调用允许的最大操作数)。

EACCES

调用进程没有执行指定信号量操作所需的权限,且没有 CAP_IPC_OWNER 能力。

EAGAIN

操作无法立即进行,并且在 sem_flg 中指定了 IPC_NOWAIT,或者 timeout 中指定的时间限制已过期。

EFAULT

sopstimeout 参数中指定的地址不可访问。

EFBIG

对于某些操作,sem_num 的值小于 0 或大于等于该集合中的信号量数量。

EIDRM

信号量集已被移除。

EINTR

在此系统调用中阻塞时,线程捕获到了信号;参见 signal(7)。

EINVAL

信号量集不存在,或者 semid 小于零,或者 nsops 具有非正值。

ENOMEM

某操作的 sem_flg 指定了 SEM_UNDO,但系统没有足够的内存来分配撤销结构。

ERANGE

对于某些操作,sem_op+semval 大于 SEMVMX(实现定义的 semval 最大值)。

版本

semtimedop() 首先出现在 Linux 2.5.52 中,随后被移植回 2.4.22 内核。Glibc 对 semtimedop() 的支持始于 2.3.3 版本。

符合

SVr4, POSIX.1-2001。

说明

Linux 或任何 POSIX 版本不需要包含 <sys/types.h><sys/ipc.h>。然而,一些旧的实现要求包含这些头文件,SVID 也记录了它们的包含。打算移植到此类旧系统的应用程序可能需要包含这些头文件。

进程的 sem_undo 结构不会被 fork(2) 产生的子进程继承,但它们会被 execve(2) 系统调用所继承。

无论建立信号处理程序时 SA_RESTART 标志的设置如何,semop() 在被信号处理程序中断后永远不会自动重启。

semadj 是一个进程内整数,它仅仅是指定了 SEM_UNDO 标志的所有信号量操作的(负)计数。当使用 semctl(2) 的 SETVALSETALL 请求直接设置信号量值时,所有进程中相应的 semadj 值都会被清除。

信号量的 semvalsempidsemzcntsemnct 值都可以通过适当的 semctl(2) 调用来获取。

以下信号量集资源限制会影响 semop() 调用

SEMOPM

一次 semop() 调用允许的最大操作数 (32)(在 Linux 上,此限制可以通过 /proc/sys/kernel/sem 的第三个字段读取和修改)。

SEMVMX

semval 的最大允许值:实现定义 (32767)。

该实现在退出时最大调整值(SEMAEM)、系统范围内的最大撤销结构数(SEMMNU)和每个进程的最大撤销条目数系统参数方面没有固有限制。

错误

当进程终止时,其关联的 semadj 结构集用于撤销其所有使用 SEM_UNDO 标志执行的信号量操作的影响。这引发了一个难题:如果一个(或多个)信号量调整导致试图将信号量的值降低到零以下,实现应该怎么做?一种可能的方法是阻塞,直到可以执行所有信号量调整。然而,这是不可取的,因为它可能迫使进程终止阻塞任意长的时间。另一种可能是完全忽略此类信号量调整(类似于为信号量操作指定 IPC_NOWAIT 时失败)。Linux 采取了第三种方法:尽可能多地降低信号量值(即降至零),并允许进程立即终止。

在内核 2.6.x (x <= 10) 中,存在一个错误,在某些情况下会阻止等待信号量值变为零的线程在值确实变为零时被唤醒。此错误已在内核 2.6.11 中修复。

示例

以下代码段使用 semop() 原子地等待信号量 0 的值变为零,然后将信号量的值加一。

 struct sembuf sops[2];
 int semid;

/* Code to set semid omitted */

sops[0].sem_num = 0;        /* Operate on semaphore 0 */
 sops[0].sem_op = 0;         /* Wait for value to equal 0 */
 sops[0].sem_flg = 0;

sops[1].sem_num = 0;        /* Operate on semaphore 0 */
 sops[1].sem_op = 1;         /* Increment value by one */
 sops[1].sem_flg = 0;

if (semop(semid, sops, 2) == -1) {
     perror("semop");
     exit(EXIT_FAILURE);
 }

参见

clone(2), semctl(2), semget(2), sigaction(2), capabilities(7), sem_overview(7), svipc(7), time(7)

引用自

ipc(2), proc(5), pthreads(7)