November 14, 2022

BSD TCP/IP for Kyu - sleep and wakeup

These are the fundamental synchronization primitives in BSD unix. There is nothing like them in Kyu; we will use semaphores.

BSD has processes with a proc structure. When a process makes a system call that can block (such as accept), it will call sleep (or tsleep). When an event happens that will allow the process to unblock, the kernel calls "wakeup" to set it going. When blocked it is on some sleep queue, when unblocked it is on some run queue. The details of the run queue is of no importance here. A call to sleep looks like this:

sleep(iorq, PRIBIO);
err = tsleep(&so->so_timeo, PSOCK | PCATCH, netcon, 0)
wakeup(iorq);
The code that implements these is in kern_synch.c

The call to sleep never returns an error.
The first argument is the "ident" or sleep address,
the second address is a priority.

The call to tsleep is more commonly used (or so it seems). The first two arguments are the same as sleep, the third is a string argument that shows (in 2 characters) what sort of sleep is going on. The last argument is a time in counts of HZ. A timeout of 0 is no timeout (just like a sleep call, but with the option to specify the string). If the tsleep is awakened by wakeup, the return is 0. If the tsleep times out, the return is EWOULDBLOCK in most cases, but perhaps other things when signals are involved.

How does this get used in the TCP code?

Both accept and connect sleep on &so->so_timeo.

soclose() sleeps on this channel also.

Wakeups on this channel happen in:

soisconnected(so)
soisdisconnecting(so)
soisdisconnected(so)
sonewconn1(head, connstatus)
tcp_notify(inp, error) in tcp_subr.c
Each call to tsleep is wrapped in a test loop. Note however that the wait channel includes the specific socket structure address, so the wakeup will only affect the process waiting on that specific socket.

For connect, The call is:

while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0)
    error = tsleep((caddr_t)&so->so_timeo, PSOCK | PCATCH, netcon, 0);
For accept, it is:
while (so->so_qlen == 0 && so->so_error == 0)
    error = tsleep((caddr_t)&so->so_timeo, PSOCK | PCATCH, netcon, 0);
Note that in both these cases the timeout argument is 0 (so it sleeps forever). The loop is typical and essential. In unix there may be a number of processes sleeping on the same event. The wakeup will wake them all up and they must check the condition and go back to sleep if it is still pending.

wakeup((caddr_t) &so->so_timeo); gets called by notify when errors occur.


Have any comments? Questions? Drop me a line!

Kyu / [email protected]