You have a general idea that is basically correct, but do this setup: there is only one โnuclear spaceโ for the whole machine, and all processes share it.
When a process is active, it can work either in user mode or in kernel mode.
In user mode, the instructions executed by the CPU are on the user space side of the memory card. The program runs its own code or code from the user space library. In user mode, the process has limited capabilities. There is a flag in the CPU that says that it does not allow the use of privileged instructions, and the kernel memory, although it exists on the process memory card, is not available. (You would not want any program to simply read and write kernel memory - all protection would be lost.)
When a process wants to do something else than move data in its own (user) virtual memory, for example, open a file, it must do syscall. Each CPU architecture has its own unique fancy method of creating system calls, but they all boil down to the following: a magic instruction is executed, the processor turns on the privileged mode flag and jumps to a special address in kernelspace, the "syscall entry point".
Now the process runs in kernel mode. Executable instructions are in kernel memory, and they can read and write whatever memory they want. The kernel checks the request that it just made and decides what to do with it.
In the open example, the kernel receives 2 or 3 parameters corresponding to the arguments int open(const char *filename, int flags[, int mode]) . The first argument gives an example of when kernelspace needs access to user space. You said open("foo", O_RDONLY) , so the string "foo" is part of your user space program. The syscall mechanism only passed a pointer, not a string, so the kernel should read the string from user memory.
To find the requested file, the kernel can consult the file system drivers (to find out where the file is) and block device drivers (to load the necessary blocks from the disk) or network device drivers and protocols (to download the file from a remote source). All these things are part of the kernel, that is, in the nuclear space, regardless of whether they are built-in or loaded as modules.
If the request cannot be satisfied immediately, the kernel can cause the process to sleep. This means that the process will be removed from the CPU until a response is received from the disk or network. Another process may get a chance to start now. Later, when the answer arrives, your process starts again (still in kernel mode). Now that he has found the file, syscall open can finish (check permissions, create a file descriptor) and return to user space.
Returning to user space is a simple matter of returning the CPU to non-privileged mode and restoring the registers to what they were before the user โ kernel transition, with an instruction pointer pointing to the instruction after the magic syscall command.
Besides system calls, there are other things that can lead to a switch from user mode to kernel mode, including:
- page errors - if your process accesses a virtual memory address that does not have a physical address assigned to it, the CPU switches to kernel mode and switches to the page error handler. The kernel then decides whether the virtual address is valid or not, and it either creates a physical page, or resumes the process in the user space where it stopped, or sends SIGSEGV.
- interruptions - some hardware (network, disk, serial port, etc.) notifies the CPU of the need for its attention. The CPU goes into kernel mode and goes to the handler, the kernel responds to it, and then resumes the user space process that was started before the interrupt.
The module is loaded using syscall, which asks the kernel to copy the module code and data into the kernel space and run its initialization code in kernel mode.
It's quite a long time, so I stop. I hope that the end-to-end focus on core-core transitions has provided enough examples to reinforce the idea.