Reputation: 104
Consider this code:
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);
Now, if "new" cannot be decremented immediately (because it is already at 0), will there be a time interval in which the "old" semaphore has been increased and the "new" one has not? I mean, can this always happen atomically?
Upvotes: 1
Views: 429
Reputation: 5211
Here are some implementation details in Linux 5.4. The source code managing the sysV semaphores is in ipc/sem.c.
The semop() system call entry point calls the common internal do_semtimedop() function with the timeout parameter set to NULL:
SYSCALL_DEFINE3(semop, int, semid, struct sembuf __user *, tsops,
unsigned, nsops)
{
return do_semtimedop(semid, tsops, nsops, NULL);
}
After some checks on the parameters and permissions, loosely speaking, do_semtimedop() locks the whole semaphore set and calls another internal routine named perform_atomic_semop() which makes the requested "sem operations". This function scans the semaphore set twice:
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;
}
Back in do_semtimedop(), the return from perform_atomic_semop() is checked:
So, to answer the question, there is no intermediate time period where one semaphore operation is done while the other is pending on the same set. There are all done at once or the calling task is put to sleep until it can do it.
Upvotes: 2
Reputation: 140990
will there be a time interval in which the "old" semaphore has been increased and the "new" one has not?
This is an implementation detail. Implementation may check if the operation is possible, then do the operations, or do operations one after another and if one operation cannot be done revert the operations already done. Or implementation may not do anything at all, up until the next sem*
operation and query the operations then, etc. The bottom part is that either way only the observable state is what matters, and such state where one has increased and the other not cannot be observed by any process, as the operation is done "atomically".
can this always happen atomically?
Yes, where "atomically" means that no process can observe the state in the middle of operation. It doesn't mean that such state "doesn't exists", as the kernel has to query one operation after another. It means that such state can't be observed from a process that uses POSIX api.
Upvotes: 2