In the Linux kernel (and by extension, Android), hardware devices are abstracted as special files in the /dev directory. This allows user-space applications to interact with complex hardware using standard file operations.
The most common type of device driver in Android is the Character Driver (char driver).
What is a Character Driver?
A character driver manages a device that can be accessed as a continuous stream of bytes. You read from or write to it sequentially, like a serial port, a keyboard, or an audio stream.
Unlike block drivers, character drivers do not support random access natively. You generally cannot say, "Read exactly byte number 4,096" without reading the previous 4,095 bytes first.
Major and Minor Numbers
When you list devices in /dev, you will see two numbers instead of a file size. These are the Major and Minor numbers.
- Major Number: Identifies the specific driver associated with the device (e.g., driver
10is for miscellaneous char devices). - Minor Number: Identifies the specific hardware instance the driver is managing (e.g., if you have two identical cameras, they might be minor
0and minor1).
# List character devices in an Android shell
# Notice the 'c' at the start of the line indicating a char device
adb shell ls -l /dev | grep "^c"
# Output example: crw-rw-rw- 1 root root 10, 105 2024-01-01 12:00 binder
The file_operations Struct
When writing a character driver in C, the core task is implementing the file_operations structure. This structure maps standard user-space system calls to your custom kernel functions.
// Example: A simplified file_operations struct for a custom Android sensor
static const struct file_operations my_sensor_fops = {
.owner = THIS_MODULE,
.open = my_sensor_open,
.release = my_sensor_release,
.read = my_sensor_read,
.unlocked_ioctl = my_sensor_ioctl,
.mmap = my_sensor_mmap,
};
The Power of ioctl
While read() and write() are great for transferring raw data, configuring hardware requires out-of-band commands (e.g., "Change the camera resolution to 1080p"). This is handled by the ioctl() (Input/Output Control) system call.
ioctl takes a command ID and an optional data argument. It is heavily used in Android.
Android-Specific Examples
Almost all of Android's most critical custom kernel mechanisms are implemented as character drivers.
- The Binder Driver (
/dev/binder): The absolute backbone of Android IPC. User-space processes open this character device and useioctlto pass messages to other processes. It also heavily utilizesmmapto share memory between the kernel and user-space. - Ashmem (
/dev/ashmem): The Anonymous Shared Memory system. Processes open this char device to request shared memory regions, then useioctlcommands likeASHMEM_SET_NAMEandASHMEM_SET_SIZEto configure them.
Understanding character drivers is essential because they form the lowest-level bridge between the Android C++ HAL layer and the actual hardware.