Adventure with ucontext on Linux and Mac
Having been playing with ucontext
lately, and I would like share my experience
on it. The spec I have for it is the one from open group,
makecontext,
and the Linux manual page, setcontext,
which probably doesn’t apply for Mac.
§Minimal stack size
Firstly, let try the example from open group. The relevant change I did is to replace the stack size with one macro so that we don’t need to change two places if we want a different stack size.
1 | static void |
After reasoning, we expect the output to be:
start f2
start f1
finish f2
finish f1
Fortunately, that’s exactly I obtained on Linux. However, it hangs on Mac, which
is due to the small stack size. The minimal size on Mac is defined in
signal.h
as MINSIGSTKSZ
, so let just do that in the beginning.
#define SSIZE MINSIGSTKSZ // minimal 32K for Mac; 2K for Linux
After this, it works like a charm on both platforms. One SO question was asked due to this stack size problem.
§Does uc_link
work or not
§uc_link
with makecontext
I shall just quote the relevant paragraph from open group:
The uc_link member is used to determine the context that shall be resumed when
the context being modified by makecontext() returns.
The first example we saw has proved that when it’s used with makecontext
, it indicates the resuming context. Then,
does uc_link
also work with contexts that are not modified by makecontext
? If we understand the spec correctly, the
answer should be no.
1 | static void |
The output for Linux:
1 | ctx3 |
and the output for Mac:
1 | ctx3 |
The output is different, but they remain the same independent of whether we set uc_link
or not. Therefore, we could
say uc_link
only works with contexts modified by makecontext
. The different output from two platforms is caused by
the fact that it’s undefined behavior for contexts that created by setcontext
or swapcontext
reaches the end.
§Could I change uc_link
after makecontext
1 | static void |
The output for Linux:
1 | ctx3 |
and the output for Mac:
1 | ctx3 |
Therefore, Mac is more flexible on this topic. The doc doesn’t say anything about this, so I had to go to the source
code to understand the behavior.
makecontext
for Linux uses uc_link
info when the makecontext
is called; it’s stored in the stack already. While, in Mac,
makecontext registers callback to
use uc_link
when context is finished, so we could change uc_link
after the makecontext
call.
§Reusing saved context multiple times
1 | static void |
ctx[0]
is entered twice, first, the swapcontext
inside test_reusing
, second, the setcontext
in main.
The output for Linux:
1 | start test_setcontext |
and the output for Mac:
1 | start test_setcontext |
The Linux output is the one I am expecting. I was so surprised to see ‘over’ on my stdout.
The source of swapcontext looks
quite innocent, and I was stuck here for a very long time. Having no idea where the problem lies, I decided to
re-implement swapcontext
after the source code from Apple.
1 | static int myswapcontext(ucontext_t *old, ucontext_t *new) |
This time the output for both platforms “shifted” a bit; for Linux:
1 | start test_setcontext |
for Mac:
1 | start test_setcontext |
Apparently, this naive implement of swapcontext
is wrong. I kindly of understood why swapcontext
on Linux is
implemented like
this.
(I don’t know assembly; hope I guessed it right.)
§What I am trying to do
I just want some basic cooperative scheduling for coroutine. The original problem could be simplified as the following snippets:
1 | static void |
For Linux the output is the following, with exit status 23
.
1 | start test_setcontext |
For Mac, due to the buggy implementation of swapcontext
, it actually works:
1 | start test_setcontext |
If you understand everything so far, it’s quite obvious that the solution is just to add one explicit context switch in
the end of test_coroutine
, uncommenting the commented statement.