Comment 2 for bug 161795

Revision history for this message
Scott James Remnant (Canonical) (canonical-scott) wrote :

I've did some printk debugging of this while on the plane home. You'll probably want a copy of kernel/exit.c open as I describe the cause of the bug; line numbers refer to git head as I write this message.

The spin occurs inside sys_waitid (1696..1729) when called with the WNOWAIT option and a signal from a ptraced process to be waited upon; the syscall never exits, so depending on the kernel state, the effect can be anything from a process spinning to a full system hang.

sys_waitid is largely just a wrapper around do_wait (1534..1694), it's within this function that the infinite loop actually occurs.

The function encloses a loop so that we iterate through every thread of the process (1553..1651), and within that a loop to iterate through the children of each thread (1557..1637). In some cases, a wait entry may vanish during execution, so there is a repeat label (1543) to which we can goto if such a condition is detected.

The case of a traced child being on top of the queue is handled (1569..1581) by checking for a race condition and otherwise falling through to the same handling as a stopped child (1582..1599).

For stopped and traced children, we call wait_task_stopped and check its return value (1591..1598); a zero return value means that we found something to return, and -EAGAIN means that the entry has already been waited upon by another thread, so the function should be begun again since the data has changed.

wait_task_stopped (1355..1464) handles the specific cases, and is passed a "noreap" value set to non-zero of WNOWAIT was specified in the waitid options. Other than the initial checks, noreap is handled completely separately (1383..1395): it obtains the information about the process, checks it, and then calls out to wait_noreap_copyout() to pass the data back to userspace before returning.

It has a sanity check on lines 1389..1391; the first part of this is to make sure that the exit_code of the process is non-zero; if it's become zero, it has been waited upon by another thread, so it jumps to the bail_ref label (1422..1432) which unlocks the tasklist and returns -EAGAIN (thus causing the do_wait() function to be restarted).

This sanity check also checks that the process isn't in the TASK_TRACED state (1390), if it is, it will also return -EAGAIN.

And thus we have our infinite loop.

If the process is in TASK_TRACED, and we specify WNOWAIT, we will forever repeat inside do_wait() since wait_task_stopped will always return -EAGAIN.