当两个信号量同时变化并且两者之一不能立即递减时会发生什么?

What happens when two semaphores change simultaneously and one of the two cannot decrement immediately?

考虑这段代码:

struct sembuf s_op[2];

s_op[0].sem_num = old;
s_op[0].sem_op = 1;
s_op[0].sem_flg = 0;
s_op[1].sem_num = new;
s_op[1].sem_op = -1;
s_op[1].sem_flg = 0;
semop(semid, s_op, 2);

现在,如果“新”不能立即递减(因为它已经为0),是否会有一个时间间隔“旧”信号量增加而“新”信号量没有增加? 我的意思是,这是否总是自动发生?

will there be a time interval in which the "old" semaphore has been increased and the "new" one has not?

这是一个实现细节。实现可能会检查操作是否可行,然后执行操作,或者一个接一个地执行操作,如果一个操作无法完成,则恢复已经完成的操作。或者实现可能根本不做任何事情,直到下一个 sem* 操作并查询操作,等等。底部是无论哪种方式,只有 observable 状态是什么很重要,并且任何进程都无法观察到这种状态,其中一个增加而另一个不增加,因为该操作是“原子地”完成的。

can this always happen atomically?

是的,这里的“原子”是指没有进程可以观察到运行中的状态。这并不意味着这种状态“不存在”,因为内核必须一个接一个地查询操作。这意味着无法从使用 POSIX api.

的进程中观察到这种状态

以下是 Linux 5.4 中的一些实施细节。管理 sysV 信号量的源代码在 ipc/sem.c.

semop()系统调用入口点调用公共内部do_semtimedop()函数timeout 参数设置为 NULL:

SYSCALL_DEFINE3(semop, int, semid, struct sembuf __user *, tsops,
        unsigned, nsops)
{
    return do_semtimedop(semid, tsops, nsops, NULL);
}

在对参数和权限进行一些检查后,粗略地说,do_semtimedop() 锁定整个信号量集并调用另一个名为 的内部例程perform_atomic_semop() 进行请求的“sem 操作”。 该函数扫描信号量集两次:

  1. 第一个扫描循环 确定是否有任何操作导致集合中信号量的负值。如果是,则 returns -EAGAIN 如果 IPC_NOWAIT 在标志中设置,如果未设置则为 1 .它也可能 return -ERANGE 如果其中一个操作导致的值大于最大值(定义为 SEMVMX = 32767 include/uapi/linux/sem.h).
  2. 第二次扫描循环 将请求的操作作为第一个循环确定这不会导致信号量的负值。 0 是 returned.
static int perform_atomic_semop(struct sem_array *sma, struct sem_queue *q)
[...]
    /*
     * We scan the semaphore set twice, first to ensure that the entire
     * operation can succeed, therefore avoiding any pointless writes
     * to shared memory and having to undo such changes in order to block
     * until the operations can go through.
     */
    for (sop = sops; sop < sops + nsops; sop++) {
        int idx = array_index_nospec(sop->sem_num, sma->sem_nsems);

        curr = &sma->sems[idx];
        sem_op = sop->sem_op;
        result = curr->semval;

        if (!sem_op && result)
            goto would_block; /* wait-for-zero */

        result += sem_op;
        if (result < 0)
            goto would_block;

        if (result > SEMVMX)
            return -ERANGE;

        if (sop->sem_flg & SEM_UNDO) {
            int undo = un->semadj[sop->sem_num] - sem_op;

            /* Exceeding the undo range is an error. */
            if (undo < (-SEMAEM - 1) || undo > SEMAEM)
                return -ERANGE;
        }
    }

    for (sop = sops; sop < sops + nsops; sop++) {
        curr = &sma->sems[sop->sem_num];
        sem_op = sop->sem_op;
        result = curr->semval;

        if (sop->sem_flg & SEM_UNDO) {
            int undo = un->semadj[sop->sem_num] - sem_op;

            un->semadj[sop->sem_num] = undo;
        }
        curr->semval += sem_op;
        ipc_update_pid(&curr->sempid, q->pid);
    }

    return 0;

would_block:
    q->blocking = sop;
    return sop->sem_flg & IPC_NOWAIT ? -EAGAIN : 1;
}

回到 do_semtimedop()perform_atomic_semop() 的 return 是检查:

  • 如果 returned 值为 0,信号量集上的任何等待任务都会被唤醒,直到 运行 它们在被唤醒之前的未决操作(即 do_semtimedop() 为每个等待任务调用)。释放全局锁,0被returned
  • 如果 return 值为负(-EAGAIN 或任何其他检测到的错误,如 -ERANGE),则释放全局锁并且错误号为 returned
  • 如果return值 > 0(实际上是 1),则调用任务进入睡眠状态:它被添加到信号量集的等待队列中。调用被阻塞,直到任务被唤醒。

因此,要回答这个问题,不存在一个信号量操作完成而另一个信号量操作在同一组上挂起的中间时间段。一次全部完成,或者调用任务进入休眠状态,直到它可以完成。