linux 驱动开发笔记
最近在移植PCIe卡驱动到linux系统上,下面是移植过程中查阅资料的记录。
- 驱动程序的形式,编译好之后的的文件名为
*.ko
。
- 安装驱动程序使用
insmod
命令;查看已经安装的驱动使用lsmod
命令;卸载驱动程序使用rmmod
命令。
- 在驱动中输出调试信息使用
printk
接口。查看驱动的调试信息使用dmesg
命令。
printk
支持信息等级,使用下面的指令可以把所有的调试信息都输出出来。
echo 8 > /proc/sys/kernel/printk
对于 KERN_DEBUG
等级的调试信息(等同 pr_debug
接口),需要在编译时增加 DEBUG
宏,打开动态调试 CONFIG_DYNAMIC_DEBUG
开关,才能在 dmesg
中看到调试信息。
使用下面的命令开启整个驱动(module)的 debug 等级调试信息。
echo 'module usbcore +p' > /sys/kernel/debug/dynamic_debug/control
- 查看指定的PCI设备
使用 lspci 能够查看系统中所有的PCI设备,在开发驱动时会指定查看某个硬件id设备的详细信息,可以使用如下的命令。
lspci -d<vendor>:<device> -vvv
- 内核态与用户态的交互有哪些解决方案
a. 系统调用(同步):
设备文件使用 read()
/ poll()
/ select()
设备专用的指令 ioctl
用户态查询变化 sysfs
/ procfs
b. 信号signal(异步):
内核态可以向指定的用户态进程发送信号
优点:触发迅速
缺点:能够携带的信息有限,用户态在处理信号时稍复杂(考虑与既有线程的的并发问题)
c. netlink socket:
优点:支持双向信息传递
缺点:使用复杂
d. eventfd:
优点:轻量级,支持与 poll
/ select
一起使用
缺点:信息结果只能为计数器数值(每次写入是加法操作,每次读取是获取当前值并清零)
e. sysfs 通知:
优点:基于文件系统,使用标准接口
缺点:需要轮询
f. uevent(设备变化相关):
优点:标准的设备通知机制
缺点:主要用于设备变化
然而最终我并没有采用上面的任何一种方案,而是把 ioctl
交互过程改为同步交互模式,这样就不存在异步通信的需求了。
我尝试过的 eventfd 方案记录:
用户态程序调用 ioctl
将创建好的 eventfd 文件描述符传递到内核态的驱动程序中。驱动程序调用 eventfd_ctx_fdget
函数将用户态的文件描述符转换为内核态的 eventfd_ctx*
指针,调用 eventfd_signal
触发eventfd通知(这样用户态程序就能通过eventfd文件),在使用结束后调用 eventfd_ctx_fdput
函数释放占用的资源。
- 分两步处理的中断
与Windows系统的驱动程序处理中断的思路类似,先在中断处理函数中记录收到的中断信息(避免长时间阻塞中断),再在后续的流程中处理后续的中断信息。
在Linux系统中,创建了一个 kthread
来处理收到的中断信息,类似于用户态中的线程。通过 struct completion
实现中断处理函数唤醒中断处理线程。
用到的内核态函数接口如下:
// 创建线程
struct task_struct *kthread_run(int (*threadfn)(void *data), void *data, const char namefmt[], ...);
// 停止线程
int kthread_stop(struct task_struct *k);
// 在线程循环中检查是否需要停止
bool kthread_should_stop();
// 创建条件变量
void init_completion(struct completion *x);
// 重置条件变量
void reinit_completion(struct completion *x);
// 唤醒条件变量
void complete(struct completion *x);
// 等待条件变量
unsigned long wait_for_completion_timeout(
struct completion *x,
unsigned long timeout
);
// jiffies时间转毫秒(等待条件变量的时候需要提供jiffy时间)
unsigned long msecs_to_jiffies(const unsigned int msecs);
(全文完)