How Tmux Starts Up: An Adventure With Linux Tools
Update! I mention below that there’s some fork calls that appear to be missing. Here’s the solution.
Julia Evans visited Hacker School yesterday, and I got to work with her on a super neat problem. In the end, the question became How does tmux start?. Specifically:
In terminal 1:
Then in terminal 2:
Huh, looks like tmux has a parent of “init” (the one with pid 9930
here). But I started tmux in a terminal, which has the bash process running. This makes sense, because if the terminal closes tmux should still run. But how does it do it?
Here’s the short, boring answer: it calls the daemon
function in unistd.h
on startup.
But here’s the more interesting part – how did we figure this out? We guessed it had to do something with forking and exiting processies, so let’s see what’s being forked!
Attempt 1: strace
Let’s see what is forking.
Problem is, strace actually doesn’t record the fork system call. I’m not sure why, but I’m guessing it’s fork in particular that doesn’t work. It does tell us when a process exits though. Oddly it never mentions the 9930
pid. Hmm.
Attempt 2: LD_PRELOAD
What about wrapping the fork function? Fork is in unistd.h
. If tmux is dynamically linking to that library, then we can inject our own code in and print to a file whenever fork is called! Let’s go!
Compile it:
Great! Now if edit the LD_PRELOAD
environment variable, all calls to fork by tmux should go to this.
The dlsym
call in our unfork.c requires the libdl
to be included for it to link correctly. As such, we include it in LD_PRELOAD
.
Lets see if it worked!
Neat! We got a bunch of pids printed to the file. But, the critical missing one is the new tmux process that is the child of init. That’s the one we care about!
Attempt 3: Running the tmux source
That was neat, but we still don’t have our answer. I got the tmux source running (I suggest using vagrant and compiling it in a virtual machine). Globally searching for fork(
I found server.c
had this function:
Ah! Printing inside of here and running tmux shows that it is indeed called. Looking more into the function I see this:
Huh.. now we are getting close. The daemon
standard library calls its own special fork, and not the standard library one. Here’s proof:
Why does daemon not call our special fork?
Here’s my guess: daemon and fork are in the same library: unistd.h
. Daemon doens’t need to externally look for the fork function through dynamic linking because it already knows where it is. I haven’t yet confirmed this though.
Wait wait. How does tmux start up?
Right! So turns out the source just tells us, and all this debugging wasn’t necessary at all :p. It forks a child in server_start
, which then forks a further child and kills itself (by calling daemon
). Now that that grandchild has no parent, init
picks it up. Nice!