I'm confused about how exactly kernel and user space are structured and which portions of memory the occupy. My current (possibly wrong) understanding is this:
A process is created and this processes' virtual memory is split up into a user-space and a kernel-space region, where as the user space region contains data, code, stack, heap etc. of the process and the kernel-space region contains things such as the page table for the process and kernel code. I'm not sure as to what the kernel code would be...driver code or similar stuff?
Also, is the system call table always mapped to the same region in the kernel-space of a process? (Is it even correct to say "kernel-space of a process"?
If I write my own driver/module and insert it, will that driver code then automatically be copied into the kernel-space of every new process that is created? If not...how exactly does this work?
Thanks in advance for any input, literature/links that can help clarify my questions are okay as well.
Cheers, Brick
You've got the general idea mostly right, but make this adjustment: there's only one "kernelspace" for the whole machine, and all processes share it.
When a process is active, it can either be running in "user mode" or "kernel mode".
In user mode, the instructions being executed by the CPU are in the userspace side of the memory map. The program is running its own code, or code from a userspace library. In user mode, a process has limited abilities. There is a flag in the CPU which tells it not to allow the use of privileged instructions, and kernel memory, although it exists in the process's memory map, is inaccessible. (You wouldn't want let any program just read and write the kernel's memory - all security would be gone.)
When a process wants to do something other than move data around in its own (userspace) virtual memory, like open a file for example, it must make a syscall. Each CPU architecture has its own unique quirky method of making syscalls, but they all boil down to this: a magic instruction is executed, the CPU turns on the "privileged mode" flag, and jumps to a special address in kernelspace, the "syscall entry point".
Now the process is running in kernel mode. Instructions being executed are located in kernel memory, and they can read and write any memory they want to. The kernel examines the request that the process just made and decides what to do with it.
In the open
example, the kernel receives 2 or 3 parameters corresponding to the arguments of int open(const char *filename, int flags[, int mode])
. The first argument provides an example of when kernelspace needs access to userspace. You said open("foo", O_RDONLY)
so the string "foo"
is part of your program in userspace. The syscall mechanism only passed a pointer, not a string, so the kernel must read the string from user memory.
To find the requested file, the kernel may consult with filesystem drivers (to figure out where the file is) and block device drivers (to load the necessary blocks from disk) or network device drivers and protocols (to load the file from a remote source). All of those things are part of the kernel, i.e. in kernelspace, regardless of whether they are built-in or were loaded as modules.
If the request can't be satisfied immediately, the kernel may put the process to sleep. That means the process will be taken off the CPU until a response is received from the disk or network. Another process may get a chance to run now. Later, when the response comes in, your process starts running again (still in kernel mode). Now that it's found the file, the open
syscall can finish up (check the permissions, create a file descriptor) and return to userspace.
Returning to userspace is a simple matter of putting the CPU back in non-privileged mode and restoring the registers to what they were before the user->kernel transition, with the instruction pointer pointing at the instruction after the magic syscall instruction.
Besides syscalls, there are other things that can cause a transition from user mode to kernel mode, including:
Loading a module is done with a syscall that asks the kernel to copy the module's code and data into kernelspace and run its initialization code in kernel mode.
This is pretty long, so I'm stopping. I hope the walk-through focusing on user-kernel transitions has provided enough examples to solidify the idea.