注:本文为 "Linux 设备驱动" 相关合辑。
英文引文,机翻未校。
如有内容异常,请看原文。
Demystifying the Linux Device Driver Model
解析 Linux 设备驱动模型
By Linux Code / December 27, 2023
For those unfamiliar, the Linux device driver model refers to the overall architecture and frameworks that enable the Linux kernel to recognize, communicate with, and control the wide variety of hardware devices out there -- whether simple serial ports or advanced GPUs. It is an extremely important Linux subsystem, providing the bridge between hardware devices and the rest of the kernel.
Linux 设备驱动模型是支撑内核识别、通信与控制各类硬件设备(从简易串口到高端显卡)的整体架构与框架,是连接硬件设备与内核其余部分的重要子系统。
In this comprehensive guide as a Linux device driver expert, I'll be sharing key insights into how Linux device drivers work from the inside based on my years of driver development experience. We'll look at how devices are represented, key architectural components, common debugging techniques, interesting data from prominent surveys, and even walk through code examples of drivers in action. My goal is to help demystify Linux device drivers so firmware engineers and programmers of all skill levels understand these pivotal system foundations.
作为 Linux 设备驱动领域从业者,我将结合多年开发经验,解析 Linux 设备驱动的内部运行机制,内容涵盖设备表示方式、核心架构组件、常用调试方法、行业调研数据与驱动代码实例。本文旨在帮助固件工程师与各层次开发者理解这一系统核心组件。
Billions of Devices Run on Linux
搭载 Linux 系统的设备规模
Let's start with an appreciation of scale -- Linux powers over 3 billion devices according to the [latest Linux Foundation estimates. From small IoT microcontrollers to massive supercomputing data centers, Linux runs the gamut.
从设备规模来看,Linux 基金会最新评估数据显示,Linux 系统运行于超过 30 亿 台设备,覆盖小型物联网微控制器至大型超算数据中心等各类场景。
There are countless different device types across processor architectures like ARM, x86, RISC-V and more. Supporting this extensive hardware ecosystem falls heavily upon the Linux device driver subsystem. It must handle everything from simple character devices like UARTs to complex, rapidly evolving GPUs with intricate device requirements.
Linux 支持 ARM、x86、RISC-V 等多种处理器架构下的海量设备类型,设备驱动子系统承担着适配硬件生态的核心职责,需处理通用异步收发传输器等简易字符设备,以及需求复杂、迭代快速的高端显卡。
Over 70% of code changes in modern Linux kernel releases relate to device driver updates and fixes. Clearly this subsystem sees heavy investment so Linux can continue powering new, innovative hardware pushing the performance envelope.
现代 Linux 内核版本中,超过 70% 的代码变更与设备驱动更新修复相关,该子系统的持续投入保障了 Linux 对新型高性能硬件的支持能力。
Devices as File Abstractions
设备的文件抽象机制
At its foundation, the Linux device driver model is built upon file abstractions. This means that rather than hardware devices showing up as esoteric, architecture-specific resources, the Linux kernel presents everything as file-oriented interfaces familiar to programmers.
Linux 设备驱动模型以文件抽象为基础,内核将硬件设备封装为开发者熟悉的文件接口,而非架构相关的底层硬件资源。
Let's consider a simple serial port for an example. The physical UART ultimately connects via an I/O memory or port-based address that needs to be polled or interrupted upon for data transfers. Rather than forcing programmers to work at that low level, the associated UART device driver instead registers a character device node that creates a file representation.
以串口为例,物理通用异步收发传输器通过 I/O 内存或端口地址实现数据传输,需轮询或中断机制支持。驱动程序将其注册为字符设备节点,以文件形式呈现,避免开发者直接操作底层硬件。
On a Linux system, you may see these UART device nodes at paths like /dev/ttyS0 and /dev/ttyUSB0 for onboard and USB-attached serial ports respectively. By reading and writing to these device files, programs seamlessly communicate with the serial hardware without worrying about the electrical signaling, data framing, or register polling already handled by the driver.
Linux 系统中,板载串口与 USB 串口的设备节点路径分别为 /dev/ttyS0 与 /dev/ttyUSB0。应用程序通过读写设备文件实现与串口硬件的交互,无需关注驱动已处理的电气信号、数据帧格式与寄存器轮询逻辑。
The same concept applies to any device -- a file access abstraction is provided that hides away unnecessary device complexities. The path and naming conventions for device nodes still follows loosely predictable patterns decided upon early Linux history to ease locating devices.
该抽象机制适用于所有设备,通过文件访问接口屏蔽硬件底层细节。设备节点的路径与命名规则沿用 Linux 早期规范,便于设备定位。
Key Components of the Device Driver Stack
设备驱动栈的核心组件
There are several pivotal components that come together to enable robust device access and management in Linux:
Linux 系统中,设备访问与管理依赖以下核心组件协同工作:
Device Nodes: As described earlier, device nodes provide file-oriented interfaces to actual devices that hide away direct hardware manipulation complexities. Character and block devices are the two primary variants.
设备节点:如前文所述,设备节点以文件接口形式封装硬件操作,主要分为字符设备与块设备两类。
Bus Drivers: These drivers handle communications across electrical busses like I2C, SPI, PCI that may interconnect multiple devices over common transport. Bus drivers detect connected devices and help associate device drivers.
总线驱动:负责管理 I2C、SPI、PCI 等总线通信,检测总线上挂载的设备并完成设备与驱动的绑定。
Device Drivers: The core logic that enables control of devices over associated busses and device nodes. Device drivers initialize hardware, handle data I/O, map device memory regions, and register interrupt handlers.
设备驱动:实现通过总线与设备节点控制硬件的核心逻辑,完成硬件初始化、数据 I/O、设备内存映射与中断处理函数注册。
Sysfs Interface: A virtual file system that exposes device/driver topology, state, diagnostics, control capabilities and more. Userspace tools and kernel components rely heavily on Sysfs for full system introspection.
Sysfs 接口:虚拟文件系统,用于展示设备与驱动的拓扑结构、运行状态、诊断信息与控制能力,是用户态工具与内核组件实现系统监控的重要依托。
Userspace Libraries: Common libraries like GPIO, IIO, V4L2 that build on the kernel interfaces to promote code reusability for common device types in applications and daemons.
用户态库:基于内核接口封装的通用库,如通用输入输出、工业 I/O、视频4Linux2 等,提升应用程序与系统服务对通用设备的代码复用率。
This simplified diagram shows how the components relate in a typical embedded Linux system with connected I2C sensors and display devices:
下图为典型嵌入式 Linux 系统中,I2C 传感器与显示设备场景下各组件的交互关系:
(原文图异常)
With this foundation established, device drivers in practice are implemented in kernel modules that compile alongside the core kernel sources. Let's explore drivers in more detail next.
基于上述架构,设备驱动通常以内核模块形式实现,与内核核心代码同步编译,下文将对驱动程序展开详细解析。
Anatomy of a Device Driver
设备驱动的代码结构
Device drivers reside within the Linux kernel, either compiled statically or built as loadable kernel modules (these allow adding drivers without recompiling the entire kernel).
设备驱动运行于 Linux 内核态,可静态编译进内核,或编译为可加载内核模块(无需重新编译整个内核即可加载驱动)。
Here is an abbreviated example driver for a basic LED device connected via GPIO pins:
以下为基于通用输入输出引脚的简易 LED 设备驱动精简示例:
c
// Device driver headers
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
// GPIO number for the LED
#define LED_GPIO 20
// File operations
struct file_operations fops = {
.owner = THIS_MODULE,
.write = led_write
};
// File open handler
int led_open(struct inode *i, struct file *f) {
// Initialize LED GPIO
gpio_direction_output(LED_GPIO, 0);
return 0;
}
// File close handler
int led_close(struct inode *i, struct file *f) {
// Deinit LED GPIO
gpio_set_value(LED_GPIO, 0);
gpio_free(LED_GPIO);
return 0;
}
// File write handler
ssize_t led_write(struct file *f, const char *buf, size_t len, loff_t *off) {
// Extract LED brightness value from userspace
uint8_t brightness = buf[0];
// Set LED brightness via GPIO output
gpio_set_value(LED_GPIO, brightness);
return len;
}
// Driver registration
module_init(led_init);
void led_init(void) {
// Register LED device node
register_chrdev(0, "led", &fops);
// Initialize device upon open
old_fs = get_fs();
set_fs(KERNEL_DS);
sys_open("/dev/led", O_RDWR|O_CREAT, 0666);
set_fs(old_fs);
}
While this presents a simplified example, you can see the common patterns:
该示例虽为精简版本,但体现了设备驱动的通用编程模式:
- Device nodes registered to create userspace file interface
注册设备节点,构建用户态文件访问接口 - File open/close handlers for setup/teardown logic
文件打开/关闭处理函数,实现设备初始化与资源释放 - File read/write callbacks to exchange data with apps
文件读/写回调函数,完成与应用程序的数据交互 - Registering of driver initialization and exit logic
注册驱动初始化与退出逻辑
Device drivers have many more responsibilities around mapping device memory, handling interrupts, configuring hardware resources like DMA and clocks, and carefully managing concurrency. But this provides an overview of the file-oriented approach central to the Linux driver model that uniforms heterogeneous hardware into consistent, file-like abstractions.
设备驱动还需完成设备内存映射、中断处理、直接内存访问与时钟等硬件资源配置、并发控制等任务。上述示例展现了 Linux 驱动模型的文件抽象核心思想,实现异构硬件的统一化接口封装。
Demystifying Driver Development
驱动开发流程解析
For those maintaining Linux device driver support on an embedded product or looking to upstream driver code to the mainline kernel, understanding common development patterns is vital. Let's examine some key processes and callout important sequencing to follow:
对于嵌入式产品驱动维护者与内核主线代码提交者而言,掌握驱动开发通用流程至关重要。下文梳理核心开发步骤与执行顺序:
Initialize New Hardware: When bringing up previously unsupported hardware, start by thoroughly testing the device operation standalone from Linux using baremetal code to validate communications.
新硬件初始化:针对未适配硬件,先通过裸机代码独立验证设备通信与运行功能,再接入 Linux 系统。
Add Device Tree Bindings: Describe the device and its connections in the Device Tree source so Linux knows what's present upon boot. Get these bindings merged first before the driver itself.
添加设备树绑定:在设备树源码中描述设备及其连接关系,使系统启动时识别硬件。设备树绑定需优先于驱动代码合入主线。
Implement Driver Skeleton: With a basic shell that handles init/exit logic and necessary file handlers registered, validate your driver builds as a module and device node is created properly. Use placeholder print statements liberally in callbacks to log behavior during development.
实现驱动框架:编写包含初始化/退出逻辑与基础文件操作的驱动框架,验证模块编译与设备节点创建功能。开发阶段可在回调函数中添加日志输出,便于调试。
Interrupt and Resource Management: Progressively enable actual device interrupts, map memory regions, configure DMA channels. Test each individually at first before combining.
中断与资源管理:逐步启用设备中断、映射内存区域、配置直接内存访问通道,各功能单独验证无误后再集成调试。
Access Control: Implement any access controls like locking so concurrent userspace access is safe. Character devices aim for single open by default.
访问控制:通过锁机制等方式实现访问控制,保障用户态并发访问安全,字符设备默认支持单次打开。
Error Handling: Carefully handle all error conditions detected like failed reads and gracefully recover or retry when possible.
错误处理:对读取失败等异常场景进行精细化处理,尽可能实现自动恢复或重试。
Concurrency: Exercise concurrent access from multiple userspace processes to flush out any conditions you may have missed. mpstat helps overload the CPU with competing processes.
并发测试:模拟多用户态进程并发访问,排查潜在竞态问题,可借助多核性能统计工具生成 CPU 竞争负载。
Performance: Profile driver execution using memory checking tools and instruments like ftrace and perf to catch leaks or hot code paths to optimize further.
性能优化:通过内存检测工具、函数跟踪与性能监测工具分析驱动执行流程,定位内存泄漏与性能热点并优化。
These represent common milestones for progressing from initial prototype code through to production grade drivers ready for public consumption. I share them based on many years of experience supporting hundreds of devices across various industries. Keep them in mind on your next driver project!
以上为从驱动原型开发至生产版本发布的关键里程碑,结合多行业、多设备的开发实践总结而成,可供后续驱动项目参考。
Powerful Tools for Driver Debugging
驱动调试的高效工具
With device drivers operating in kernel space, debugging them can prove more challenging without the normal facilities userspace developers rely on for tracing, profiling and debugging.
设备驱动运行于内核态,无法直接使用用户态常用的跟踪、性能分析与调试工具,调试难度相对较高。
Fortunately, Linux offers several indispensable tools specifically tailored for driver developers:
Linux 为驱动开发者提供了多款专用调试工具:
Printk Logging: The kernel-space equivalent to printf. Dropping printk messages helps log trace progress and internal state changes as you develop drivers. Just take care not to leave extraneous logging behind in production for performance reasons.
Printk 日志:内核态格式化输出函数,用于记录驱动执行流程与内部状态变化,生产环境需移除冗余日志以保障性能。
Ftrace: A framework that traces kernel internal function calls along with timing and parameters passed. It allows drilling down to find hot code paths. Using its built-in triggers helps isolate specific functions during debugging.
函数跟踪工具:内核函数调用跟踪框架,可记录调用时序与参数,定位性能热点,内置触发机制便于调试时聚焦特定函数。
Perf: Offers both CPU performance counter profiling as well as kernel-space annotation capabilities akin to userspace profiling tools. Requires architecture support but quickly highlights hotspots.
性能监测工具:支持 CPU 性能计数器分析与内核态代码注解,功能对标用户态性能分析工具,依赖架构支持,可快速定位性能瓶颈。
Kprobes: Dynamic insertion of tracing or callbacks at almost arbitrary kernel code locations to monitor execution flow or variable state. Think of it like dynamically injecting print statements without rebuilding.
内核探针:可在内核代码任意位置动态插入跟踪点或回调函数,监控执行流程与变量状态,无需重新编译即可实现日志注入。
Mastering these tools paired with Device Tree overlays for rapid reconfiguration provides leverage for driver developers. While you should understand fundamental debugging using printk, also invest time into ftrace and perf to optimize driver performance long term.
熟练运用上述工具并结合设备树覆盖层实现快速配置,可显著提升驱动开发效率。在掌握 printk 基础调试方法的同时,建议深入学习函数跟踪与性能监测工具,支撑驱动长期性能优化。
Example Walkthrough: I2C Accelerometer Driver
实例解析:I2C 加速度计驱动
To help tie together everything we've covered into something more concrete, let's walk through a sample I2C accelerometer device driver more end-to-end.
为整合前文知识点,下文以 I2C 加速度计驱动为例,进行全流程解析。
Our fictional sensor connects via the common I2C bus and provides motion sensing up to 16G. It offers a simple register interface to enable sensors, sample motion, and configure interrupts.
该虚拟传感器通过 I2C 总线连接,支持最高 16G 量程的运动检测,具备传感器使能、运动采样与中断配置寄存器接口。
Device Tree Representation
设备树描述
The Linux kernel first learns about the sensor thanks to a Device Tree node essentially declaring its existence:
内核通过设备树节点识别传感器设备,节点配置如下:
i2c0 {
accel@18 {
compatible = "tdk,3axis-accel";
reg = <0x18>;
};
};
The node binds it to the first I2C bus (i2c0) at 7-bit address 0x18. The compatible string indicates which driver to bind to it.
该节点将设备挂载至 I2C 0 总线,7 位设备地址为 0x18,兼容字符串用于指定绑定的驱动程序。
Driver Implementation
驱动实现
With the device declared, here is what a trimmed down driver implementation looks like:
设备声明完成后,精简版驱动代码如下:
c
#include <linux/i2c.h>
// Internal sensor registers
#define REG_ENABLE 0x01
// Enable XYZ sensors
int accel_enable(struct i2c_client *client) {
u8 val = 0x07;
return i2c_write_reg(client, REG_ENABLE, val);
}
// Read motion samples
int accel_get_motion(struct i2c_client *client, int16_t *x, int16_t *y, int16_t*z) {
u8 buf[6];
i2c_read_regs(client, 0x02, buf, 6);
*x = (buf[0] << 8) | buf[1];
*y = (buf[2] << 8) | buf[3];
*z = (buf[4] << 8) | buf[5];
return 0;
}
// Driver initialization
int accel_probe(struct i2c_client *client) {
accel_enable(client);
return 0;
}
You can see it follows the typical write/read handler paradigm communicating over the standard I2C kernel functions. The probe callback readies the device for applications.
驱动遵循标准 I2C 内核函数的读写处理范式,探测回调函数完成设备初始化,供应用程序调用。
Userspace Access
用户态访问
With everything registered and bound, userspace code can now interface with the sensor completely abstracted through file operations:
驱动注册与绑定完成后,用户态代码可通过文件操作接口访问传感器:
c
int fd = open("/dev/accel0", O_RDONLY);
int16_t x, y, z;
while (1) {
read(fd, &x, sizeof(int16_t));
read(fd, &y, sizeof(int16_t));
read(fd, &z, sizeof(int16_t));
printf("x=%d y=%d z=%d\n", x, y, z);
}
close(fd);
And that's really one of the end goals -- bridging actual devices with application developers leveraging friendly file-oriented APIs without requiring hardware knowledge.
这正是驱动设计的目标之一:以友好的文件接口连接硬件与应用开发者,无需底层硬件知识即可完成开发。
Closing Thoughts
总结
I hope this guide has proven useful for developers of all skill levels looking to better understand Linux device drivers from the inside. We covered everything from architectural basics to advanced debugging and optimization techniques. Key takeaways include:
希望本文能帮助各层次开发者深入理解 Linux 设备驱动,内容涵盖架构基础、高级调试与优化方法,核心要点总结如下:
- Linux provides elegant device abstractions through file-oriented nodes
Linux 通过文件节点实现设备抽象封装 - A robust driver framework handles connectivity, communication and control
完善的驱动框架支撑设备连接、通信与控制 - Sysfs enables diagnosing drivers and devices at runtime
Sysfs 支持运行时驱动与设备诊断 - Drivers implement open/close/read/write handlers for streamlining data access
驱动通过打开/关闭/读/写处理函数实现数据访问 - Handling interrupts, memory mapping and DMA is also central to many driver designs
中断处理、内存映射与直接内存访问是多数驱动的核心功能 - Debugging techniques like ftrace and perf proves invaluable for tracing execution and hunting performance issues
函数跟踪、性能监测等调试工具是执行跟踪与性能优化的重要手段
Device drivers form the backbone of Linux accommodating an incredible diversity of hardware innovations over decades. I encourage you to get hands-on debugging some real-world drivers even without target devices using QEMU and explore driver code from vendors like TI and NXP. This subsystem presents many learning opportunities for engineers at all levels.
数十年来,设备驱动支撑 Linux 适配各类硬件创新,是系统的核心组成部分。建议借助快速模拟器调试实际驱动,研读德州仪器、恩智浦等厂商的驱动代码,该子系统可为各层次工程师提供丰富学习素材。
Welcome to Your Guide for Writing Linux Device Drivers!
Linux 设备驱动编写指南
By Linux Code / December 27, 2023
Hi there! As a fellow Linux enthusiast, I know how exciting yet daunting diving into kernel driver development can be. So I put this comprehensive 2500+ word tutorial together to teach you the fundamentals in a beginner-friendly way. Whether you want to contribute to the kernel or tinker with some hardware, let's learn how fun driver coding can be!
各位读者好!作为一名 Linux 技术爱好者,我深知涉足内核驱动开发既令人兴奋又颇具挑战。为此,我整理了这份 2500 余字的完整教程,以入门友好的方式讲解驱动开发基础。无论你希望为内核贡献代码,还是对硬件进行调试开发,都能从中体会驱动编程的乐趣。
What Exactly is a Device Driver?
设备驱动的定义
A device driver acts as a middleman between the hardware components in your Linux system and the higher-level operating system kernel. It's a critical piece that allows the OS to exploit the capabilities of the underlying hardware devices.
设备驱动在 Linux 系统硬件组件与上层操作系统内核之间承担中间层角色,是操作系统调用底层硬件设备功能的重要组成部分。
Some common examples of devices handled by drivers:
驱动程序所管理的常见设备示例如下:
- Storage: HDDs, SSDs, RAID cards
存储设备:机械硬盘、固态硬盘、RAID 阵列卡 - Network: Ethernet adapters, WiFi cards
网络设备:以太网适配器、无线网卡 - Audio: integrated sound cards, USB headsets
音频设备:集成声卡、USB 耳机 - Graphics: integrated GPUs, discrete video cards
图形设备:集成显卡、独立显卡
Without device drivers, there would literally be no way for the CPU and kernel to communicate with these components!
若无设备驱动,CPU 与内核将无法与上述硬件组件进行通信。
Fun fact -- over 70% of the Linux kernel code consists of various device drivers! Here are some amazing statistics:
一项数据显示,Linux 内核代码中超过 70% 为各类设备驱动,相关统计信息如下:
- 10,000+ different drivers in the Linux kernel
Linux 内核内置 10 000 余种不同驱动 - 415,000+ lines consisting of just USB drivers alone
仅 USB 驱动代码量便超过 415 000 行 - 100+ developers maintain the kernel driver subsystem
内核驱动子系统由 100 余名开发者共同维护
So while our "Hello World" example before was simple, real-world drivers can be quite complex -- handling parallel I/O, interrupts, advanced buffering, concurrency, and much more.
尽管此前的"Hello World"示例较为简单,但实际工程中的驱动程序复杂度较高,需处理并行 I/O、中断、高级缓冲、并发控制等多项任务。
Now let's begin our journey into the world of Linux device drivers! We'll start easy and then slowly build up from there.
接下来,我们正式进入 Linux 设备驱动的学习,从基础内容逐步深入。
Kernel Modules -- The Building Blocks of Drivers
内核模块:驱动程序的组成单元
As mentioned earlier, Linux device drivers are typically packaged as dynamically loadable kernel modules rather than baked statically into the kernel binary. What are some advantages of this approach, you ask?
如前文所述,Linux 设备驱动通常以动态加载内核模块的形式封装,而非静态编译进内核镜像文件。该方式具备以下优势:
- Faster development -- compile only modified modules, no whole kernel recompiling needed
开发效率更高 -- 仅编译修改后的模块,无需重新编译整个内核 - Easier distribution -- ship drivers separate from the base Linux kernel
分发部署更便捷 -- 驱动可独立于 Linux 基础内核发布 - Safer code -- modules can't bring down entire kernel if something goes wrong
代码运行更安全 -- 模块出现异常时不会导致整个内核崩溃 - Load/unload without reboot -- hot-swap drivers while system is running
无需重启即可加载/卸载 -- 系统运行时可热插拔驱动模块
With kernel modules, you get flexibility along with ease of installation and upgrading. Let's look under the hood a bit...
内核模块兼具部署灵活性与安装升级便利性,下文将解析其底层实现机制。
The Linux kernel provides APIs, infrastructure, and memory management to allow extending functionality via loadable modules. Key capabilities:
Linux 内核通过应用程序编程接口、基础框架与内存管理机制,支持以加载模块的方式扩展功能,核心能力包括:
- Register devices programmatically
以编程方式注册设备 - Hook interrupt handlers and callbacks
挂载中断处理函数与回调函数 - Tap into kernel interfaces like VFS layer and sysfs
接入虚拟文件系统层、sysfs 等内核接口 - Access core kernel helpers and libraries
调用内核核心辅助函数与库函数
So while modules operate in kernel space with full privileges, robust barriers protect and isolate the base kernel from faulty drivers.
模块在内核态以最高权限运行,同时内核通过完善的隔离机制保护基础内核免受异常驱动的影响。
Now we are ready for some actual code! I'll walk you step-by-step through building a real working kernel module.
接下来进入代码实践环节,我将分步讲解可正常运行的内核模块构建流程。
Coding Our First Kernel Driver
编写首个内核驱动
Let's dust off that classic "Hello World" example from earlier and enhance it into something more tangible...
我们在经典"Hello World"示例基础上进行扩展,实现更具实用价值的驱动程序。
Here is our starting simple character driver mychar.c:
以下是初始简易字符设备驱动 mychar.c 代码:
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define MYDEV_NAME "mychar"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("John");
static struct cdev my_cdev;
static int mychar_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "Successfully opened device!\n");
return 0;
}
static int mychar_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "Device closed!\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_release,
};
static int __init mychar_init(void) /* Constructor */
{
printk(KERN_INFO "Loading mychar driver...\n");
int err;
err = register_chrdev(0, MYDEV_NAME, &fops);
if(err < 0)
{
printk(KERN_ERR "Failed to register device!\n");
return err;
}
printk(KERN_INFO "Successfully registered character device.\n");
return 0;
}
static void __exit mychar_exit(void) /* Destructor */
{
unregister_chrdev(0, MYDEV_NAME);
printk(KERN_INFO "Goodbye from mychar!\n");
}
module_init(mychar_init);
module_exit(mychar_exit);
I will explain what each part is doing:
代码各部分功能说明如下:
- Open/release file operations -- Called when processes open/close the device file
打开/释放文件操作 -- 进程对设备文件执行打开/关闭操作时触发 - Register/unregister character device -- Get dynamic device major number from kernel
注册/注销字符设备 -- 向内核申请动态设备主设备号 - Printk() calls -- Kernel console logging for debugging
Printk() 函数调用 -- 内核控制台日志输出,用于调试
Now for the Makefile:
对应的 Makefile 文件内容如下:
bash
obj-m += mychar.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Let's build our kernel module:
执行内核模块编译命令:
bash
$ make
make -C /lib/modules/5.4.0-120-generic/build M=/home/john/drivers modules
...
$ ls
mychar.c mychar.ko mychar.mod.c mychar.mod.o mychar.o Makefile
Success! We now have our custom mychar.ko kernel module ready. Time to load it:
编译完成,生成自定义内核模块 mychar.ko,执行加载命令:
bash
$ sudo insmod mychar.ko
[34223.350581] Loading mychar driver...
[34223.350752] Successfully registered character device.
The printk messages confirm initialization worked. But we don't have a device file yet, so let's create one:
printk 日志表明初始化成功,此时需创建对应的设备文件:
bash
$ sudo mknod /dev/mychar c 100 0
$ ls -l /dev/mychar
crw------- 1 root root 100, 0 2023-02-26 17:05 /dev/mychar
We now have a device node that userspace can interact with. Let's test opening from a shell:
设备节点创建完成,用户态可与之交互,在终端执行打开测试:
$ cat /dev/mychar
[34256.065823] Successfully opened device!
[34256.065870] Device closed!
It works! The kernel logs show our open/release callbacks being invoked properly.
驱动运行正常,内核日志显示打开与释放回调函数均被正确调用。
In just 50 lines of kernel code, we implemented a character driver that dynamically registers a device, defines file operations, logs messages, and handles program access!
仅用 50 行内核代码,便实现了具备动态设备注册、文件操作定义、日志输出与程序访问控制功能的字符设备驱动。
Of course, this is only the beginning -- there is so much more we could explore and learn...
当然,这只是入门阶段,后续还有大量内容可供深入学习。
Going Beyond "Hello World"
超越"Hello World"的进阶内容
While writing a simple "Hello World" kernel module is a good starting point, real-world Linux device drivers are vastly more complex. Let's discuss some examples of practical features we may need to implement in production-grade drivers:
简易"Hello World"内核模块是良好的学习起点,而工程级 Linux 设备驱动复杂度更高。下文介绍生产环境驱动需实现的部分实用功能:
Concurrency and Synchronization
并发与同步
Our character device example could be opened simultaneously by multiple user programs. We need proper concurrency control via locks, mutexes and other kernel synchronization primitives to avoid bugs like race conditions. The kernel api offers solid multi-process support if you code it correctly.
示例字符设备可被多个用户程序同时打开,需通过锁、互斥体等内核同步原语实现并发控制,避免竞态条件等问题。合理编写代码可充分利用内核应用程序编程接口的多进程支持能力。
Interrupts and IRQ Handling
中断与中断请求处理
Hardware devices trigger all kinds of asynchronous events that require timely handling. Interrupts are a mechanism for signaling the processor that the device needs attention for time-sensitive tasks like transmitting network packets or pumping audio samples to speakers. Linux provides interfaces to hook IRQ handler callback functions.
硬件设备会触发各类需及时处理的异步事件,中断是向处理器发送信号的方式,用于通知设备需执行网络数据包传输、音频采样播放等实时性任务。Linux 提供挂载中断请求处理回调函数的接口。
Buffering and Memory Allocation
缓冲与内存分配
For hardware like networking adapters and mass storage controllers that shuttle large chunks of data in and out of the system, properly allocating DMA-capable memory buffers and caching is very important for performance. The Linux kernel has APIs that allow thoughtful management of memory usage.
网络适配器、大容量存储控制器等硬件需进行大量数据传输,合理分配支持直接内存访问的缓冲与缓存对性能至关重要。Linux 内核提供应用程序编程接口,支持精细化内存管理。
Hotplug and Power Management
热插拔与电源管理
Modern Linux systems should gracefully handle surprise device additions/removals at runtime. This hotplug capability allows dynamically loading drivers on the fly when new USB or PCI devices are inserted. Related power management features like suspend/resume bring additional complexity.
现代 Linux 系统需支持运行时设备的热插拔操作,插入 USB、PCI 设备时可动态加载驱动。休眠/唤醒等电源管理功能进一步提升了实现复杂度。
Debugging and Validation
调试与验证
While writing and updating device drivers, extensive debugging via kernel logging, instrumentation (perf events, kprobes), Valgrind, and stress testing is needed to catch bugs. Tracing support allows tracking down faulty drivers causing system instability.
设备驱动编写与更新过程中,需通过内核日志、性能监测事件、内核探针、内存检测工具与压力测试等方式开展全面调试,定位系统稳定性异常的驱动问题。
That covers just some of the facets real-world Linux drivers must handle!
以上仅为实际 Linux 驱动需处理的部分场景。
There is always more to learn if you are curious and dig deeper with Linux kernel programming! I encourage you to take what we've covered so far and continue experimenting on your own now that you have a solid foundation under your feet.
Linux 内核编程领域仍有大量内容值得探索,在掌握现有基础后,建议自主开展更多实验与实践。
John
Editor, The Linux Code
- Linux 设备驱动 | 模型原理、架构与开发(2)-CSDN博客
https://blog.csdn.net/u013669912/article/details/159932671
Reference
- Demystifying the Linux Device Driver Model -- TheLinuxCode
https://thelinuxcode.com/linux-device-driver-model/ - Welcome to Your Guide for Writing Linux Device Drivers! -- TheLinuxCode
https://thelinuxcode.com/linux-device-driver-tutorial/