"coroutine" means encapsulating the asynchronous IO mechanism of Linux epoll through long jmp to form a "continuous" process that is considered by users.
All asynchronous IOs of operating systems are divided into startup function and callback function .
Take Linux as an example. start function is responsible for adding read and write events to epoll framework.
After the event triggers , the callback function is used to process the lower half of . The entire event processing process of
is similar to interrupt processing of in the Linux kernel.
A complete IO process requires several callbacks, and when reading the code, search everywhere for callback function where is set in .
and even, some programmers will modify the pointer of the callback function in the middle [covering face]
C function pointer is more "flexible" than C++ virtual function . The C++ virtual function table table fixed when is compiled, but C function pointer can modify when is run (it is a normal variable).
Then, someone really modified it halfway, causing the readability of the code to drop sharply.
Then, coroutine appears, which looks like at least synchronizes .
program flow jumps in a function, which is the ordinary goto statement .
program flow jumps in half of of functions, that is, jumps (long jmp). The principle of of
coroutine is as follows:
1. When a certain file descriptor needs IO to wait for , use to jump to return to the main framework function of epoll, so that other IO of can run.
2. When the IO of the file descriptor is ready for again, it jumps back from the main frame function through a long jump, and then the last position of continues to run:
This position is the position where the function gave up running . It is a certain point in in the function.
can still come back after giving up CPU in the half of the function, so you need to save the function's running context: stack information, register information. Where to save
?
can only be saved to heap , because stack and register will both continuously overwrite as the code runs, and only the heap is controlled by the user.
User Test Code
The picture above is "user code". Although the two functions __async_connectt() and __async_write() are executed asynchronously by , they are all in one function _async_test(), and the entire process looks like synchronously .
__async_connect() function, the upper half of
__async_connect() is divided into the upper half and the lower half, and __asm_co_task_yield() is used as the separation point. The upper half of
calls the asynchronous connectt(), and the lower half calls getsockoptt() to read the result.
In order to avoid blocking thread , it is necessary to give up CPU after asynchronous connect(), so that main framework function can do other .
This function __asm_co_task_yield() that gives up the CPU is the key to "coroutine library".
After it gives up the CPU, after the event triggers , resumes run again: At this time, function __asm_co_task_yield() will return , and then run the code in the figure below.
__async_connect() function, the second half of
When the asynchronous connect() succeeds, the error code err obtained by getsockopt() is 0.
__async_write() function
__async_write() function flow is similar to __async_connect(). It also gives up the CPU when the file descriptor becomes unwritable and waits for the next time it is writeable before restarting.
epoll main framework function
epoll main framework function is a while loop: use the epoll_wait() system call to monitor the trigger of the event.
It will handle both IO events and timer .
timer precision is limited by waiting time of epoll_wait().
epoll main framework function
__scf_co_task_run() can make "coroutine task" run for the first time, or restore to run again.
_scf_co_task_run() function
This function just calls __asm_co_task_run(). The specific long jump is implemented in assembly .
Because long jump involves 's meticulous memory control , it can only be implemented using assembly.
run result:
should be used on the machine to use the command nc -vv -l 2000 as the server. The log printed by
is the change in the stack information during long jump . The approximate functions of the two assembly functions
are as follows: I won’t talk about the details of
. This kind of code has been around for a long time and even the author can’t understand it [covering his face]