Linux 驱动开发入门:LCD 驱动与内核机制详解
Linux 驱动开发入门:LCD 驱动与内核机制详解
本文面向初学者,从最基础的 LCD 屏幕驱动编写 讲起,逐步带你理解 framebuffer 框架、内核空间与用户空间、内核链表、file_operations、内存分配与信号处理 等核心知识。
整理自原始学习笔记,进行了扩展和补充,力求深入浅出。
一、什么是 LCD 驱动?
1.1 硬件与驱动的关系
- LCD 控制器:硬件模块,负责向 LCD 屏幕输出信号。
- LCD 驱动:运行在内核态的代码,负责初始化控制器、分配显存、提供用户空间访问接口。
- 应用程序:运行在用户态,通过
ioctl
或write()
等方式访问驱动,从而在屏幕上显示图像。
1.2 framebuffer 框架
Linux 提供了 framebuffer (fb) 框架,用来屏蔽底层硬件差异。
驱动开发者只需要实现 fb_info 结构体,注册 framebuffer 设备,应用程序即可通过 /dev/fb0
访问。
二、LCD 驱动开发基本流程
2.1 初始化 LCD
在驱动的 init
函数中完成:
- 配置 LCD 控制器寄存器(分辨率、时序、色彩格式等)。
- 开启电源管理(例如控制 GPIO 使能背光)。
- 分配显存(frame buffer),并映射给 framebuffer 框架。
示例(简化伪代码):
static struct fb_info *my_lcd_info;static int __init lcd_init(void) {// 1. 分配 fb_infomy_lcd_info = framebuffer_alloc(0, NULL);// 2. 配置分辨率和颜色深度my_lcd_info->var.xres = 480;my_lcd_info->var.yres = 272;my_lcd_info->var.bits_per_pixel = 16;// 3. 分配显存并映射my_lcd_info->screen_base = dma_alloc_coherent(...);// 4. 注册 framebufferregister_framebuffer(my_lcd_info);printk("LCD driver loaded\n");return 0;
}
2.2 背光控制
LCD 屏幕通常需要单独控制背光。方法有:
- 通过 GPIO 设置高低电平开启/关闭背光;
- 或通过 PWM 控制亮度。
示例(GPIO 控制):
gpio_direction_output(LCD_BACKLIGHT_PIN, 1); // 打开背光
2.3 调色板设置
对于 16 色 / 256 色模式,需要设置调色板(palette)。
在 fb_info
中有 pseudo_palette
用于存储颜色映射表。
示例:
static int my_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue,unsigned transp, struct fb_info *info) {if (regno < 16) {((u32 *)info->pseudo_palette)[regno] =(red & 0xff) << 11 | (green & 0xff) << 5 | (blue & 0xff);}return 0;
}
三、内核空间与用户空间
3.1 基本概念
- 用户空间(User Space):应用程序运行区域,不能直接访问硬件。
- 内核空间(Kernel Space):驱动和内核代码运行区域,有最高权限,可以直接访问寄存器/内存。
3.2 内核态与用户态切换
- 系统调用(如
open()
、write()
)会让 CPU 从用户态切换到内核态,执行驱动的函数(file_operations)。 - 这种切换需要保存上下文,开销比普通函数调用大。
四、file_operations 与设备模型
4.1 file_operations 结构
驱动中定义 文件操作函数,映射到 /dev
节点。
常见成员:
.open
→ 打开设备时调用.release
→ 关闭设备时调用.read
→ 从设备读数据.write
→ 向设备写数据.ioctl/unlocked_ioctl
→ 设备控制命令
示例:
static struct file_operations lcd_fops = {.owner = THIS_MODULE,.open = lcd_open,.release = lcd_release,.write = lcd_write,
};
五、内存分配方式
Linux 内核中常用的内存分配函数:
- kmalloc:分配小块内存(连续物理地址)。
- vmalloc:分配较大内存(虚拟连续,但物理不一定连续)。
- dma_alloc_coherent:分配适合 DMA 的物理连续内存(常用于显存)。
LCD 驱动通常用 dma_alloc_coherent 分配 framebuffer。
六、内核链表(list_head)
Linux 内核大量使用链表管理设备/任务。
- 定义:
struct list_head
- 常见操作:
list_add()
、list_del()
、list_for_each()
示例:
struct my_device {int id;struct list_head list;
};
struct list_head device_list;INIT_LIST_HEAD(&device_list);
list_add(&dev->list, &device_list);
七、用户空间与内核空间数据交互
用户传给内核的指针不能直接使用,必须通过拷贝函数:
copy_from_user()
→ 从用户空间拷贝数据到内核空间。copy_to_user()
→ 从内核空间拷贝数据到用户空间。
示例:
if (copy_from_user(kernel_buf, user_buf, size))return -EFAULT;
八、卸载驱动
在模块卸载函数中,必须:
- 注销 framebuffer 或字符设备。
- 释放显存。
- 释放 GPIO、时钟等硬件资源。
示例:
static void __exit lcd_exit(void) {unregister_framebuffer(my_lcd_info);dma_free_coherent(...);printk("LCD driver unloaded\n");
}
九、总结
- LCD 驱动开发流程:初始化控制器 → 分配显存 → 注册 framebuffer → 控制背光。
- 内核与用户空间:应用调用
open()
/write()
→ 内核通过 file_operations 调用驱动函数。 - 内存管理:驱动开发常用 kmalloc/vmalloc/dma_alloc_coherent。
- 内核链表:统一管理内核对象,几乎无处不在。
- 数据交互:必须通过
copy_from_user
/copy_to_user
。