Handle threading after a fork() (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 7 Jun 2018 18:59:28 +0000 (14:59 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 7 Jun 2018 19:22:15 +0000 (15:22 -0400)
commitf85af48f30bd4d60828daf209f852fda1c5f47ab
tree9a974d0c78fb2a6a9bb3dc6d19e66a6f9f111411
parent76036be4841e8b5d2f8634333ca83d274271ad8c
Handle threading after a fork() (XCC)

Threading and fork() do not go together well, especially user-level
threads.  The kernel won't let you fork() an MCP.  However, you can have a
threaded SCP - dropbear is one such example.  It uses the pthread 2LS, but
just happens to be on one core.

Dropbear uses threads internally in its implementation of tty.c.  There are
two threads: one for pushing inbound traffic and another for outbound
traffic between the network and the child's pipes.

Recall another pain in the ass with fork: duplicated state between the
child and parent.  We have a bunch of things implemented in userspace, such
as select() and an alarm service.  Those are FDs that get carried over to
the child.  We have the "fork callbacks" to clean that stuff up.

Now, what happens when you fork a threaded process?  Its forked thread
returns in the child, and the child also has all of the memory structures
for the other threads.  If you exec() immediately, that all gets blown
away.  However, if that thread happens to block on a syscall, then the 2LS
takes over and it can run those other threads.

In dropbear's case, it returns from the fork, it closes the tty threads'
FDs (in one of its functions, not in a fork-cb), does a few other things,
etc.  One of those things was chdir(), which happens to block.  The reason
for that is chdir() calls synchronize_rcu(), which can block until the GP
ktask runs, at least under the current settings.

Once the thread (thread0) blocks in chdir, the other threads run.  However,
their FDs were closed, so they'll return an error.  Depending on whether or
not you have the DB bug fix, it'll either segfault under some circumstances
or just error out with a data_flow error.

To fix this, we now require all 2LSs that want to support forking to have
explicit support.  The thread0 sched trivially supports this.  The pthread
2LS will allow you to fork only from thread0, and it will suspend threading
for the duration of the fork in both the parent and the child.  During that
time, only thread0 will run.  After the fork(), the parent will resume
threading.  In the child, we'll never thread.  Those other threads will sit
on the ready_queue forever and will never run.  In DB's case, we don't want
them to run, since their FDs are closed.

Also, note that this is only a problem for the child, not the parent.
Further, SYS_fork will never appear to block for the child.  The syscall is
marked 'finished' before the child is RUNNABLE_S.

In general, let's just try to stop using fork().

Rebuild glibc, your shells, and dropbear.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/Versions
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/fork.c
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/fork_cb.c
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/sys/fork_cb.h
user/parlib/thread0_sched.c
user/pthread/pthread.c