Kernel

05/06/2025

05/06/2025

Here are part of my notes of today’s kernel studies. I had to read them to understand more how libuv works. The kernel, however, is not part of that library; knowing how it works in this tiny aspect makes a difference when we want to understand blocking.

Read, write and accept are blocking. This is the default behaviour. Timeouts only limit how long the call blocks. write blocks when the destination buffer is full. read will block when there is no data and accept will block when no connection is pending.

Blocking means, for the user, that the thread is stuck in some operation like the ones mentioned before (networking, file I/O, etc.). One approach to solve it is to ask the kernel to notify us when a file descriptor is ready. This is how epoll and kqueue work. This approach is called readiness notification. Another, completion-based approach is to submit a job to the kernel and get a completion event. IOCP and iouring work this way; the kernel performs the read and then reports completion._

Interesting, epoll does work with files, but we need a completion approach for true asynchronous file I/O. Regular files are always reported ready, so epoll does not provide asynchronicity here—the kernel immediately says “ready.”

select was the first one. Basically, it is a bunch of file descriptors passed to the kernel. All fds are checked once per select(). If anything is ready, we do the job and pass it to a buffer. Kernel scans the whole set each time—this is costly.

We register the interest list only once. Then we can use epollctl to add or remove from the list. Then we do epollwait to see what is ready and execute it.

Syscalls in epoll copy data back and forth between kernel and user, but only tiny event structures—the actual payload is not copied until we call read.

Link to Udemy course