This content originally appeared on DEV Community and was authored by Ahmet Can Gulmez
Let’s talk about a bit Linux device drivers.
Any device driver is basically a program that controls a real hardware. In Linux world, drivers are separated into three categories: character, block, network. Each category handles the data with different ways. A character device controls the data as character like keyword, mouse or speaker. A block device processes the data as chunk of data. We can give the SSD, hard disk, eMMC as examples for this category. Network devices just are special for network hardwares like wifi card. These handle data as packets.
In this post, I will explain the character device and give a example project link.
The libraries that we use when writing a device driver is the Linux kernel headers not GLIBC ones. Because the device drivers are loaded when booting up the system before the user-space programs don’t run. So any device driver cannot use any GLIBC headers.
When writing a device driver, firstly we should add some information about the driver like license, author, description. These are defined with MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION macros.
There also are some structures that specifically used in character device drivers. These are:
struct cdev
struct file_operations
struct class
struct device
In normal programs, we define the main() as entry-point. So the execution of the program starts from main. But in device drivers, this logic is a bit different. We should define the two functions with two important macros. First is the init function with __init macro and second is exit function with __exit macro. These two macros are just defined as:
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline
#define __exit __section(".exit.text") __exitused __cold notrace
These two function are the init and exit point in the driver. We mostly define the registration of the driver in init function and
deallocate all the allocated stuffs in exit function.
In despite of normal program, we can call the init and exit functions that we want. But we have to use module_init() and module_exit() to explicitly set the init and exit functions.
Any character driver have to implement some user-space functions. These functions are the GLIBC ones like open(), close(), read(), lseek or etc. After defined and registered these, we can use the our character device in any user-space program to handle file operations onto any character device.
In init function, we generally do these steps:
Dynamically allocate a device number
Initialize the cdev structures with implemented file operations
Register the cdev structure with VFS (Virtual File System)
Create device class under /sys/class
Populate the sysfs with device information
In exit function, we undo all the operation in init function from tail to head.
Also when compiling the driver implementation, we have to use the kernel build system. We cannot use just compiler. We need the kernel tools.
Project link: https://github.com/CanGulmez/Linux-Drivers/tree/main/pseudo
This content originally appeared on DEV Community and was authored by Ahmet Can Gulmez