I would like to take some time in this post to talk a little bit about the differences between userspace and kernel space. We have thrown the term “userspace” a lot over the past few blog posts and it is time to clarify on what these terms mean as well as what they imply. To understand the difference, we need to understand a few fundamental things about the ARM core on which we are running our code.
2.0 Processor Execution Modes
Userspace is a separate virtual memory space in which all the user level applications run. Code located in this virtual space, when executed by the CPU core, runs in an unprivileged CPU mode specifically in the CPU’s user mode. Here access is granted to only a limited number of instructions and CPU registers. The current CPU mode, on ARM cores, is always identified in the machine by the last four bits of the CPSR – Current Program Status Register.
ARM supports a total of seven execution modes: All modes other than User, are considered privileged modes.
1. User mode – All user level applications run in this mode.
2. FIQ Fast Interrupt Mode – Used to handle interrupts classified as “fast”, get in and get out type of interrupts.
3. IRQ Mode – Used to handle device interrupts and non FIQ interrupts.
4. Supervisor mode (default CPU boot mode). Used by the OS and software interrupts (SWIs).
5. Abort – Used to handle violations caused by erroneous memory accesses
6. Undefined – Used to handle undefined instructions
7. System – A privileged mode with the same register set as usermode, used to run exceptions handlers.
3.0 A Userspace Execution Example
A userspace application, can attempt to execute a piece of code from the kernel address space even though we are in the incorrect processor mode. We have all done it, at one time or another, we forgot to initialize a pointer and we de-referenced it. In turn the OS rewarded us at runtime with a very nasty segmentation fault error message, and terminated our program.
So what happened in that unfortunate incident? Well, in the C language, uninitialized stack variables can have any value. In our case, our uninitialized pointer had a garbage value from which we took for a valid address and tried to follow by de-referencing the pointer. We will assume that the garbage address which our pointer has, by chance, maps to a piece of kernel memory.
Now this is a contrived example but it does exemplify how the inner architecture works to some degree.
When we de-reference any pointer, that translates to an assembly LOAD instruction from the memory address indicated by the pointer.
That LOAD instruction goes down the CPU pipeline, and at the EXECUTE pipeline stage the CPU will forward our garbage address to the Memory Management Unit (MMU) for the MMU to fetch data from the address indicated by our bad pointer. The MMU will follow the bad pointer value to whatever page table holds the physical to virtual mapping for that pointer. It will then attempt to translate our pointer’s virtual address to a physical one.
At this point however, the MMU also checks whether the current CPU mode is allowed to access the page.
Since this page was originally marked by the OS during boot as privileged mode accessible only and our program is running in usermode, the MMU will generate an exception to the CPU.
The CPU will switch processor mode to Abort ( a privileged processor mode) and execute the data abort exception handler which handles the unauthorized memory access. This handler will check the address of our offending load instruction, and dump from memory our entire program, thus terminating our execution. The OS will then print a nasty message for us to see and feel inadequate about.
4.0 Kernel Space
So now that we discussed userspace, we can approach Kernel space. Kernel space is the virtual memory space that the OS kernel runs in. The kernel code is always executed in privileged mode and as such has access over the entirety of all virtual space including userspace.
So what exactly are the advantages of kernel space? Well for our purposes the main advantage is that we have direct access to memory mapped hardware peripherals and as such, we can control devices without asking the kernel to do it for us, as we would have to form userspace. This is also a reason as to why most device drivers run in kernel space since now, we do not have to communicate with the kenel from userspace through software interrupts (SWI). As such when we implement a controller which is more time sensitive, we will create a kernel space drive.
However, this sort of power does not come without its risks. De-referencing a bad pointer in the kernel, will oftentimes crash the entire system, usually requiring a reboot to get back to an operational state.
In future posts, we will investigate creating a Linux device driver in Kernel space and interacting with hardware at a much more intimate level.
- I2C Communication from Linux Userspace – Part II
- UART Communication from Linux Userspace