最近在移植PCIe卡驱动到linux系统上,下面是移植过程中查阅资料的记录。

  1. 驱动程序的形式,编译好之后的的文件名为 *.ko
  2. 安装驱动程序使用 insmod 命令;查看已经安装的驱动使用 lsmod 命令;卸载驱动程序使用 rmmod 命令。
  3. 在驱动中输出调试信息使用 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

printk manual

  1. 查看指定的PCI设备

使用 lspci 能够查看系统中所有的PCI设备,在开发驱动时会指定查看某个硬件id设备的详细信息,可以使用如下的命令。

lspci -d<vendor>:<device> -vvv
  1. 内核态与用户态的交互有哪些解决方案

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 函数释放占用的资源。

  1. 分两步处理的中断

与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);

(全文完)