1. Introduction
UNIX systems expose an interface to most devices via files. Since everything is a file in Linux, working with a device in this manner can be a convenient and familiar process. However, sometimes we might need to consider the way computer components function and get recognized by the kernel.
In this tutorial, we look at the address space files in Linux. First, we explore device files and the address space in general. Next, we go over a way to directly access ports in the system. After that, we explain the partially-deprecated kernel structure access file. Finally, we delve into a way to access all of main memory.
We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.
2. Device Files and Address Space
Linux provides many device files for different parts of the system:
- storage
- central processing unit (CPU)
- terminals
- peripherals like a mouse and keyboard
- port-specific components like USB devices
In most computer systems, everything can be represented by address spaces. If we know how the addresses are mapped and have access to them, we can read from and write to the memory of storage devices, but also write commands and read back status data from their controllers’ memory. The same is valid for peripherals like printers.
For example, depending on our device, we might be able to print a line of text via the simple echo command and stream redirection:
$ echo Line. > /dev/lp0
In fact, there are even three files in Linux which deal with the actual address space itself. Let’s explore them.
3. /dev/port
The /dev/port character device file exposes an interface to the hardware ports on the system.
Since, as we already saw, ports are in the system address space, we effectively get a partial view of main memory:
- storage mediums
- network interface cards
- serial ports
- any available port that the kernel sees
To read from or write to these ports, we can read or send data to the /dev/port file. Specifically, to target port 0, we first lseek() to position 0. Increasing the position increases the port number.
One step further, we have access to all of the kernel’s memory.
4. /dev/kmem
Conveniently, the /dev/kmem file enables access to the internal kernel structures.
To make use of that, first, we usually read the kernel symbol table. Next, we use the table as an index to the addresses of regions we want to read or write.
Since this file can be a security hole, kernels after version 2.6.26 only have it when CONFIG_DEVKMEM is enabled in the configuration. Further, the main uses of /dev/kmem have now been mostly taken over by pseudo-filesystems like /proc, /sys, and others.
There are even proposals to entirely remove /dev/kmem from the kernel. This follows the restrictions placed on another, even more exposing address space file.
5. /dev/mem
Indeed, main memory, i.e., random access memory (RAM), was once available in its entirety via the /dev/mem file.
While Linux still supports this file, it usually only provides access to input and output devices like /dev/port. Unlike /dev/port, which directly refers to ports, /dev/mem exposes memory-mapped devices in the physical RAM.
What part of the main memory is available through /dev/mem depends on the CONFIG_STRICT_DEVMEM kernel configuration option. Allowing full access to RAM is a critical security and stability risk.
6. Summary
In this article, we looked at address space files available in Linux.
In conclusion, while /dev/port, /dev/kmem, and /dev/mem are similar, they provide access to different parts of a system.