一、基础知识
- 编译代码的三个环境变量,
ARCH
、CROSS_COMPILE
、PATH
分别表示架构、工具链、路径
- 驱动模块传参,
module_param
,module_param_array
、module_param_string
传递基本数据类型、数组和字符串
- 内核模块导出
EXPORT_SYMBOL
可以导出变量、函数
- 驱动模块编译进内核,当前驱动源码下的Makefile和Kconfig,前者使用
obj-x += x.o
指定编译方法,后者编写config选项在menu中添加配置选项,上级目录的Kconfig通过source包含子目录的Kconfig
- 字符设备注册流程
alloc_chrdev_region(*dev, baseminor, count, *name); // 动态分配设备号,分配的第一个设备号为dev,分配count个设备号,主设备号相同,次设备号从baseminor开始,name表示名称
cdev_init(*cdev, *fops); // 绑定cdev和fops
cdev_add(*cdev, devt, count); // 注册一个cdev结构体,对应设备号devt,关联数量count,一般每个设备分配一个cdev即count=1
class_create(THIS_MODULE, *name); // 创建一个名称为name的类
device_create(*class, NULL, devt, NULL, *name); // 在class类下创建一个device,没有父设备,设备号是devt,节点名称name,udev机制会自动识别并创建设备节点
- fops如何传递数据?file结构体中有成员private_data,可以通过它传递
- MISC设备,主设备号10,注册方法
misc_register
,miscdevice结构体中有成员fops,在驱动入口中注册MISC设备,并在fops中实现IO操作
- 错误处理,
IS_ERR
判断函数返回的指针是不是错误指针,如果是错误指针通过PTR_ERR
返回错误码,goto
语法实现了不同阶段的不同错误处理
二、高级字符设备
- IO模型的分类:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO
- 阻塞IO
wait_queue_head_t head;
init_waitqueue_head(&head); // 初始化等待队列头
wait_event_interrupt(head, cond); // 条件不满足进入可中断的阻塞等待
wait_up_interrupt(&head); // 唤醒等待队列上等待的休眠进程
- 非阻塞IO:读写时如果资源无效则直接返回-EAGAIN,并且非阻塞访问时需要带有flag,O_NONBLOCK
- IO多路复用,实现驱动中的poll函数,驱动poll函数中对可能引起状态变化的等待队列调用
poll_wait
,之后返回是否能对设备进行无阻塞读写访问的掩码
- 信号驱动IO,实现驱动中的fasync函数,直接调用
fasync_helper
函数,当资源准备好的时候使用kill_fasync
发送指定的信号
- 异步IO,实现
aio
函数,略
- 定时器的使用
struct timer_list my_timer;
void my_timer_callback(struct timer_list *t);
timer_setup(&my_timer, my_timer_callback, 0);
mod_timer(&my_timer, jifiies+msecs_to_jiffies(1000));
del_timer(&my_timer);
- Linux内核打印
dmesg | grep "something"
cat /proc/kmsg // 开一个终端长时间读信息
echo 7 4 1 7 > /proc/sys/kernel/printk
- lseek,实现对三个参数的处理,
SEEK_SET
、SEEK_CUR
、SEEK_END
- ioctl
_IO(type, nr); // type为一个ASCII码、nr表示一个cmd的索引
_IOR(type, nr, size); //size为一个结构体/变量
_IOW(type, nr, size);
_IOWR(type, nr, size);
_IOC_TYPE(cmd); // 根据cmd返回type
_IOC_NR(cmd); // 根据cmd返回nr
_IOC_DIR(cmd); // 根据CMD返回方向,与_IOC_READ/_IOC_WRITE相与
access_ok(addr, size); // 检测用户空间的内存块是否可用
- 分支预测优化,
likely
表示表达式为真的可能性更大、unlikely
表示表达式为真的可能性更小
- 驱动的其他调试方式
dump_stack(); // 打印内核调用堆栈、打印函数调用关系
WARN_ON(cond); // cond为真时,打印内核调用堆栈,打印函数调用关系
BUGON(cond); // cond为真时,抛出oops,打印函数调用堆栈和错误信息
panic(fmt); // 系统死机,打印韩式调用堆栈和寄存器值
三、中断
- 中断子系统
用户层:用户驱动,使用通用的中断API注册中断和中断服务函数
核心层:向上提供通用API
硬件相关层:包含特定处理器相关的代码、以及中断控制器的驱动代码
硬件层:包含中断控制器和CPU
- 函数使用
request_irq(irq, handler, flags, *name, *dev); // 请求一个中断号并关联对应的中断处理函数
gpio_to_irq(gpio); // 将GPIO的引脚编号转化为中断请求号
free_irq(irq, *dev_id); // 释放中断号、中断处理程序和设备标识
irq_handler的返回值,IRQ_NONE中断函数未处理该中断、IRQ_HANDLED中断函数处理了该中断、IRQ_WAKE_THREAD中断函数处理了该中断并请求唤醒一个内核线程进行后续处理
- 中断关键结构体
struct irq_desc{ // 描述中断handle_irq; // 中断处理函数*action; // 中断action*kstat_irqs; // 中断统计信息irq_data; // 中断相关数据irq_common_data; // 中断通用数据
};
struct irqaction{handler; // 中断处理函数*dev_id; // 传递的设备相关信息*next; // irq行为的链表下一项flags; // 中断标志、触发方式、共享标志
};
- tasklet软中断(中断下半部分的一种实现,运行在软中断的上下文中)
struct tasklet_struct my_tasklet;
void my_tasklet_func(unsigned long data);
tasklet_init(&my_tasklet, my_tasklet_func, 0);
tasklet_enable(&my_tasklet);
tasklet_disable(&my_tasklet);
tasklet_schedule(&my_taslket); // 放在中断处理函数返回前执行,当内核准备返回用户空间时,检测到有调度的tasklet,执行对应的tasklet_func
- 软中断(不建议直接使用软中断、因为已经被内核用了、网络数据收发、块设备、定时器等)
在TASKLET_SOFTIRQ对应的枚举中加入自定义的软中断或直接使用现成软中断
open_softirq(int nr, void (*action)(struct softirq_action *)); // 注册软中断和对应的处理函数
void raise_softirq(unsigned int nr); // 触发软中断
- Tasklet如何使用软中断,在
softirq
初始化的时候,注册软中断和对应的处理函数,该处理函数遍历一个tasklet的链表指向对应的中断处理后半部分,当调用tasklet_schedule
时,将tasklet加入链表并触发软中断,严格来说是挂起,真实执行需要等到中断处理函数完成,程序即将从内核返回用户空间。
- 工作队列,工作队列运行在内核进程上下文,可以休眠,由工作队列线程管理执行工作队列的工作队列项
// 使用共享工作队列
struct work_struct workqueue_work;
void work(struct work_struct *work);
INIT_WORK(&workqueue_work, work);
schedule_work(&workqueue_work); // 提交工作项到内核共享工作队列中
// 使用自定义工作队列
struct workqueue_struct* workqueue = create_workqueue("test"); // 创建自定义工作队列
void work(struct work_struct *work);
struct work_struct workqueue_work;
INIT_WORK(&workqueue_work, work);
queue_work(workqueue, &workqueue_work);
// 延时执行的工作队列项
INIT_DELAYED_WORK(&workqueue_work, work); // 初始化延时的工作项
queue_delayed_work(workqueue, &workqueue_work, 3*HZ); // 提交延时工作项到自定义工作队列
// 如何使用工作队列传参
// 工作队列work_struct结构体放在一个结构体中,使用container_of函数根据work_struct结构体寻找到传参结构体
// 并发管理的工作队列
struct workqueue_struct* workqueue = alloc_workqueue("test", WQ_UNBOUND, 0);
- 中断线程化,将中断下半部分交给一个内核线程执行,通过函数
request_threded_irq(irq, handler, thread_fn, irqflags, *devname, *dev_id);
请求并注册一个线程化的中断处理函数,中断上半部分返回IRQ_WAKE_THREAD
将中断处理推迟到下半部分