How does kernel block read and write sys calls?

Viewed 275

from my previous question Does close() on socket on one end, closes on the other end as well? I have learnt when one end sends FIN, the kernel will stop blocking the read (because after FIN the kernel does not "expects" any more data so it stops blocking). But the question is specific to sockets. Since the read() and write() could be use to any file descriptor (of any device type, e.g. I can read from pipe, socket, char devices etc.). And by all of these device types the kernel does some sort of blocking. E.g. by pipe, the kernel blocks read() until the write end of pipe is close()ed (until which it does not "expects" no more data to come in). But the question is, if the kernel does "some sort of blocking" by the type of device, and decides on its own when to "expect" some data and when not,

  1. how is even the "blocking" part implemented? That is, how can process stops its execution at some point and "waits" for inputs? (the is the behaviour of "blocking"). How is it implemented at kernel level? (I suspect some sort of buffering but would really like to know exec implementation)

  2. If the kernel could blocks and stop process from its execution, can I do it also in user-level? That is can I somehow programatically stop a execution of process, create random buffer somewhere and just wait for input from other process? That's why I ask about the kernel implementation, because I would like to mimic the same behaviour in user space.

if possible even provide some explanatory resources (e.g. good books) about the kernel part (I guess about IO devices implementation in kernel).

1 Answers

This is too broad a topic to discuss thoroughly, but here are some basics.

Primarily, two things can stop a process: an interrupt from outside the process and certain instructions, which I will call trap instructions, from inside the process. In either case, the behavior of the computer processor is largely similar:

  • Whenever a process is executing, the processor is continually reading instructions from memory, executing those instructions, and maintaining information about the process, including what the address of the next instruction to execute is and a variety of registers containing data for the process.
  • When either an interrupt or one of the trap instructions occurs, the processor saves the address of the next instruction and possibly some other data for the process, sets some internal processor state to be in a privileged mode, and transfers control to (starts executing instructions at) a special address.

That address may be different for interrupts and traps, and it may be different for different interrupts and different traps. Whatever it is, the operating system has placed special software at that address. It is software that “handles” the interrupt or trap (or that jumps to software that does it).

That software saves the data that is in the processor for the process that was interrupted or that trapped. It stores it in some data structures the operating system maintains for each process.

Now the process is not executing any more. The operating system is executing, and the process will not execute again unless the operating system restores the data for that process from the data structures to the processor registers and then transfers control back to the process’ instructions. This is done via some special return-from-interrupt or return-from-trap instruction that does that last bit of restoring processor state for the process, so that it can resume execution where it left off.

So, the way the operating systems blocks a process is simply not to resume execution of it. To the operating system, processes are largely data that it manipulates, and it chooses what to do with that data.

When a process attempts to read from a socket, it calls a routine that includes a trap instruction. That routine sets up some data saying that the process would like to read from a socket, and then it executes the trap instruction. The trap causes the process to stop executing and the kernel to start executing. The trap handler in the kernel looks at the data passed by the routine and sees it is a request to read a socket. It calls some other software for that. If that software sees there is no data available for the socket, it will simply leave the process suspended, and it will return to other software in the kernel.

Then the kernel generally will see if it has any other processes ready to run. If it does, it will choose one of them and start running it. If it does not, it will put the processor in a low-power mode to wait for an interrupt.

At some time in the future, some data may arrive for the socket. A network device might generate an interrupt to the processor, or some other process might call a routine to send data. In whatever way the system gets the data, when the software that manages the socket sees it has new data, it will check whether it has a process waiting for data from the socket. If it does, it will tell the kernel that process no longer needs to wait. If nothing else is blocking the process, it will be ready to run. When the kernel decides it is that process’ turn to use the processor again (or a processor, if several are available), it will resume the process as described above.

You cannot generally implement these procedures yourself in user code. They require privileged access to the hardware. Operating systems typically provide ways for processes to communicate, and you could implement things similar to this using system calls that do that communication.

Related