9
votes

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:

  1. 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?

  2. 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"?

  3. 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

2

2 Answers

32
votes

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:

  1. page faults - if your process accesses a virtual memory address that doesn't have a physical address assigned to it, the CPU enters kernel mode and jumps to the page fault handler. The kernel then decides whether the virtual address is valid or not, and it either creates a physical page and resumes the process in userspace where it left off, or sends a SIGSEGV.
  2. interrupts - some hardware (network, disk, serial port, etc.) notifies the CPU that it requires attention. The CPU enters kernel mode and jumps to a handler, the kernel responds to it and then resumes the userspace process that was running before the interrupt.

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.

3
votes

There is no kernel-space region in the process's virtual memory map. The virtual memory map has: text, bss, data, heap, stack of the loaded program and shared libs. On Linux, you can check /proc/$PID/maps of any user-space process for an example.

When a user-space process accesses some kernel-domain code through a system call, the kernel code is executed on-behalf of the process in its stack. Obviously, after returning from the system call, all kernel/driver code will be out of the stack. To clarify more, if at one moment, some part of the kernel code is not used by any process, it will not be part of any process's virtual memory map.

If you're using Linux, I'd recommend Robert Love's "Linux kernel development" book.