当前位置:首页>Linux>搞懂 Linux 设备驱动模型,才算真正吃透 Linux 内核底层

搞懂 Linux 设备驱动模型,才算真正吃透 Linux 内核底层

  • 2026-07-01 19:57:01
搞懂 Linux 设备驱动模型,才算真正吃透 Linux 内核底层

大家好,我是蟹老板~

前几天后台有个的兄弟留言说:“哥,Linux驱动到底是在写啥?不就是readwriteioctl那一套吗?”

兄弟,你那是应用层接口,那是皮,Linux设备驱动模型的精髓,在于皮囊底下的骨架和经络,你如果只盯着file_operations看,那你永远只能写写简单的字符设备,一旦碰到复杂的总线、热插拔、电源管理,立马凉凉。

Linux驱动模型其实就干了一件事:让设备和驱动能够自动找到彼此。

一、 驱动模型底层基石:内核核心对象体系

Linux内核是用C语言写的,但面向对象的思想却无处不在。为了把系统里的各种总线、设备、驱动给统一管起来,内核整出了三个最底层的核心对象:kobject、kset 和 ktype。

1.1 kobject:内核最小基础对象

内核代码里有个说法:一切皆 kobject,任何一个内核对象要能被统一管理,都需要一个身份证——这个身份证就是struct kobject

struct kobject {    const char *name;                 // 名字,对应sysfs下的一个目录struct list_head entry;           // 链表节点,用于组成双向链表struct kobject *parent;           // 父对象指针,体现出层级关系struct kset *kset;                // 当前kobject所属的集合struct kobj_type *ktype;          // 当前kobject的类型信息struct kernfs_node *sd;           // sysfs文件系统的目录项struct kref kref;                 // 引用计数    unsigned int state_initialized:1;    unsigned int state_in_sysfs:1;    unsigned int state_add_uevent_sent:1;    unsigned int state_remove_uevent_sent:1;    unsigned int uevent_suppress:1;};

别看这一堆字段,重点就三个:name(名字)、parent(父子关系)、kref(引用计数)、ktype(类型)。

kref就是内核里的"智能指针"原型,说白了就是一个计数器——有人用,计数就加1;用完就减1,减到0的时候调用release方法释放对象。内核用这个机制管理生命周期,防止用着用着对象被人删了。

parent指针构建起了 kobject 之间的父子层级关系,这种层级关系在内核中形成了一种树状结构。USB控制器下面挂USB设备,USB设备下面又挂USB接口的功能单元,sysfs里就是一层一层目录套目录。

但是这里有个坑:kobject本身并不单独使用

Greg Kroah-Hartman在他那篇经典文章里说过这么一句话:**内核代码很少创建独立的kobject。相反,kobject被嵌入到其他结构体中,用于控制对更大的、特定领域对象的访问。**用面向对象的说法就是——kobject是所有设备对象的"基类",你写一个具体的设备驱动,定义的结构体里第一件事就是塞一个kobject进去,比如UIO驱动的struct uio_map

struct uio_map {struct kobject kobj;struct uio_mem *mem;};

那给定一个指向kobject的指针,怎么拿到外层的uio_map?总不能让每个人都去算偏移量吧。

内核提供了container_of宏来解决这个问题:

struct uio_map *u_map = container_of(kp, struct uio_map, kobj);

这个宏在驱动开发的代码里到处可见,是C语言实现"继承"和"多态"的核心。

1.2 kset:对象集合容器

既然有了 kobject 这种单个的对象,那如果有一堆同类型的对象凑在一起,总得有个容器来装吧?那谁管一堆对象的集合?

这时候 kset 就粉墨登场了。

struct kset {struct list_head list;        // 包含在kset内的所有kobject构成的双向链表    spinlock_t list_lock;         // 链表锁struct kobject kobj;          // kset自己的kobject,所以kset本身也是一个kobject    conststruct kset_uevent_ops *uevent_ops;  // 热插拔事件操作};

kset是个容器,里面有个双向链表list,把所有属于它的kobject串起来。同时kset自己还嵌了一个kobject,所以kset本身在sysfs里也表现为一个目录。

比如说,你在/sys/bus/下看到的所有目录——i2cspipciplatform——每个目录对应一个kset。每个总线是一个kset,总线下面挂的设备和驱动各自又是kobject,全都挂在总线kset的链表里。

sysfs的目录结构很大程度上就是根据kset来组织的。

1.3 ktype:对象属性操作模板

刚学驱动那会儿,我经常把 kset 和 ktype 搞混,心想这俩货不都是分类的意思吗?

其实差别大了去了,kset 关注的是组织结构,管的是“把哪些对象放在一起”;而 ktype 关注的是行为和属性,管的是“这群对象应该怎么放屁、怎么拉屎”。

ktype长这样:

struct kobj_type {    void (*release)(struct kobject *kobj);          // 释放函数    conststruct sysfs_ops *sysfs_ops;              // sysfs操作函数struct attribute **default_attrs;               // 默认属性列表};

每个嵌入了kobject的结构体都需要一个对应的ktype。ktype控制着kobject在创建和销毁时应该做什么——怎么释放、sysfs里的属性文件该怎么读写。

说白了,kobject定义了"有什么",ktype定义了"怎么操作"。

1.4 三大对象的层级关系

总结一下吧:kobject+kset+ktype=整个Linux驱动模型的底层骨架

  • • kobject给每个内核对象一个"身份证"
  • • kset把这些"身份证"按类别放到不同的"抽屉"里
  • • ktype规定了每个抽屉里的文件的读写规则

这个底层骨架就是sysfs的"活水源"。sysfs下的每个目录对应一个kobject,每个属性文件通过ktype里的sysfs_ops来读写。

device、driver、bus、class,都建立在这套体系之上。

二、驱动模型三大核心支柱:总线、设备、驱动

驱动模型的灵魂就一句话:总线负责连接,设备挂在总线,驱动匹配设备。

把这个逻辑刻在脑子里。它解释了驱动的整个生命周期:从注册开始,经过匹配,执行probe,最后正确运行。

当年我第一次听到这个概念的时候,心里这不脱裤子放屁吗?我直接写个驱动把硬件操作写死,不也能跑得飞起?

直到后来我们组做了一款产品,同一个视频解码芯片,今天要挂在 I2C 总线上读写配置,明天因为方案变动,要改挂到 SPI 总线上。要是按照老一套的写法,那几天我别想睡觉了,得把整个驱动重构一遍。但如果用了这套模型,我只需要改一下设备端的总线归属,驱动层的核心逻辑连一个字都不用动!

2.1 总线:设备与驱动的桥梁

在内核里,总线是用 struct bus_type 结构体来表示的,官方文档里是这么说的:总线是处理器和一个或多个设备之间的通道。

站在设备模型的角度,所有设备都通过总线连接,即使是内部的、虚拟的platform总线也是如此

bus_type结构体里有些关键字段,我们来看一眼:

struct bus_type {    const char *name;                     // 总线名称,如"platform"、"i2c"    const char *dev_name;struct device *dev_root;    conststruct attribute_group **bus_groups;    conststruct attribute_group **dev_groups;    conststruct attribute_group **drv_groups;    int (*match)(struct device *dev, struct device_driver *drv);    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);    int (*probe)(struct device *dev);    int (*remove)(struct device *dev);    // ... 还有PM电源管理相关函数};
};struct subsys_private *p;                // 驱动核心私有数据struct lock_class_key lock_key;

重点说说这个match函数指针——每当总线上有新设备或新驱动加入,总线的match函数就会被调用,判断驱动和是否支持该设备。匹配成功,则事情还没完,后续还会调用probe完成真正的初始化。

内核里预定义了很多总线类型:platform_bus_typei2c_bus_typespi_bus_typepci_bus_type……

我刚开始做嵌入式驱动的时候,一直不理解为什么GPIO控制器、UART控制器都要注册成platform设备,明明这些硬件不是真的通过物理总线连接的。后来才明白——platform总线是Linux内核定义的一条虚拟总线,专门用来管理那些没办法归类到I2C、SPI、USB等实体总线的设备。

ARM SoC里的片内外设几乎全挂在这条虚拟总线上。

2.2 设备:硬件实体的内核抽象

接下来看设备,设备就是硬件资源的内核抽象,告诉系统"我是谁,我有什么"

不管你的硬件是一个高大上的PCIe显卡,还是一个几毛钱的LED小灯,在内核眼里,它都被抽象成了 struct device

struct device这个结构体定义特别大,我挑几个核心的说:

  • • name:设备名称,用于匹配驱动
  • • bus:指向该设备所属的总线(必须明确)
  • • parent:父设备指针,体现了硬件拓扑关系
  • • platform_data:设备相关的硬件特定数据(寄存器地址、中断号、DMA通道等)
  • • of_node:如果使用了设备树,这个字段指向对应的设备树节点

注册设备的调用流程是这样的:

设备注册调用device_add()  -> bus_add_device()   // 把设备添加到总线的设备列表里  -> bus_probe_device() // 检查已有驱动能否匹配  -> 如果已经注册的同总线驱动,match如果成功,触发probe

设备和驱动的匹配逻辑

设备注册时,它的所属总线b会遍历drivers链表,对每个驱动调用bus.match;如果先前注册了驱动,逻辑同样,设备注册时也会尝试已经注册的驱动。

2.3 驱动:硬件操作逻辑载体

驱动是啥?驱动就是**"负责操作和控制具体硬件的代码模板"**,告诉系统"我能操作哪些设备,怎么操作"。

struct device_driver 就是那个负责干苦力的工人,核心字段:

  • • name:驱动名称
  • • bus:这个驱动属于哪个总线
  • • probe/remove/shutdown/suspend/resume:核心回调函数
  • • of_match_table:设备树匹配表(驱动开发中太重要了)
  • • owner:所属内核模块

注册驱动的流程和注册设备互为镜像:

驱动注册调用driver_register()  -> bus_add_driver()   // 把驱动添加到总线的驱动链表  -> driver_attach()    // 遍历总线上所有设备,调用bus.match进行匹配  -> 匹配成功就调用driver.probe(对应platform_driver里的probe)

平台总线上的驱动注册尤其要注意设置drv->driver.bus = &platform_bus_type,内核代码里__platform_driver_register强制把驱动的总线类型指向platform_bus_type。这意味着所有platform驱动都被自动归到platform总线的麾下。

2.4 三者联动核心关系

很多面试官喜欢问驱动模型核心思想是什么?

直接回答:总线管理设备和驱动,匹配成功后触发probe完成初始化,基本不会错。

三、核心工作机制:设备与驱动的匹配全过程

这里说驱动开发最核心的逻辑:设备和驱动怎么自动绑定?谁调用probe?

很多刚接触驱动的小伙伴天天都在纳闷:我代码里明明就写了一个 probe 函数,我也没在别的地方调它啊,它怎么自己就莫名其妙地跑起来了?这就涉及到了内核里最核心的匹配绑定机制。

3.1 内核匹配的两大核心场景

在实际开发中,设备和驱动谁先注册,完全取决于系统的加载顺序。内核必须做到无论谁先来,最后都能正确相遇。

场景一:先注册设备,后注册驱动

这种情况在现代Linux系统里挺常见的。系统启动的时候,内核首先去解析设备树或者板级文件,把硬件设备一个接一个地注册到对应的总线上。这时候,由于对应的驱动模块还没加载(可能还在外设的 ko 文件里躺着呢),这些设备就只能静静地在总线的设备链表里当单身狗。过了会儿,用户态通过 insmod 或者 modprobe 把驱动模块给加载进来了。驱动一注册,内核就会带着这个驱动去遍历总线上的那条设备链表,挨个比对,最后把属于它的设备给领走。

场景二:先注册驱动,后注册设备

这种场景经常发生在热插拔设备上。比如你写了一个USB摄像头的驱动,系统一开机就把驱动给加载好了。这时候由于你还没插摄像头,总线的设备链表里啥也没有。等到哪天你突然把摄像头往USB接口上一怼,USB总线控制器捕获到了电信号的变化,立马在内核里动态创建了一个 struct device 并注册。新设备一进来,内核就拿着它去遍历已经存在的驱动链表,正好发现了那个嗷嗷待哺的摄像头驱动,当场一拍即合。

3.2 四大匹配规则优先级(面试高频,我能证明 )

如果你去面试Linux驱动岗位,十个面试官有九个会问:platform总线如何完成设备和驱动的匹配? 标准答案就是这四大规则:

1. 设备树匹配(最高优先级)

当内核开启设备树支持时,总线会调用of_driver_match_device(),对比设备节点的compatible属性和驱动of_match_table中定义的兼容性列表。

static conststruct of_device_id my_of_match[] = {    { .compatible = "lckfb,mychardev" },    { /* sentinel */ }};MODULE_DEVICE_TABLE(of, my_of_match);staticstruct platform_driver my_driver = {    .driver = {        .name = "mychardev",        .of_match_table = my_of_match,    },    .probe = my_probe,};

设备树里这么写:

mychardev@0 {    compatible = "lckfb,mychardev";    reg = <0x12340000 0x1000>;    interrupts = <56>;};

只要compatible对上了,内核就知道这个驱动支持这个设备节点。

2. 设备ID表匹配

非设备树的场景(比如ACPI系统或者老式板级文件),使用platform_device_id表进行匹配。

static conststruct platform_device_id my_id_table[] = {    { "mychardev-v1", (kernel_ulong_t) &my_device_data_v1 },    { "mychardev-v2", (kernel_ulong_t) &my_device_data_v2 },    { }};MODULE_DEVICE_TABLE(platform, my_id_table);

3. 名称字符串匹配

这是最原始、最直接的一种匹配方式。如果设备树匹配和ID表匹配都没命中,内核会退而求其次,比较设备的name字段和驱动的driver.name字段是否相同。

驱动里设置:

staticstruct platform_driver my_driver = {    .driver = {        .name = "mychardev",   // 驱动名称    },};

设备的名称也要是mychardev才能匹配上。

不过这种匹配方式太死板,灵活性差,平台驱动开发中我基本不推荐用,除非有特殊限制。

4. 自定义match匹配(最低优先级)

总线可以注册自己的match函数,完全自定义匹配逻辑。比如说,pci总线就有自己复杂的匹配规则,不按常理出牌。不过一般业务驱动开发不会用到这一层,总线框架自己玩的东西。

3.3 probe函数触发完整流程

一旦匹配成功,内核接下来要干的事情就顺理成章了,总线会调用内核的统一接口,顺藤摸瓜一路回调到你写在驱动里的 probe 函数。

拿platform总线来举例。前面提到,__platform_driver_register会把驱动的probe回调绑定到总线的probe层,最终调用链是这样的:

注册时调用driver_register() → bus_add_driver() → driver_attach() → __driver_attach() → 调用driver_match_device()进行匹配判断 → 如果匹配,调用driver_probe_device() → 最终调用really_probe() → 调用总线或驱动的probe函数。

针对platform总线,platform_drv_probe里做了几件重要的事情:

  1. 1. 将通用struct device指针转换为platform专有的struct platform_driverstruct platform_device
  2. 2. 设置设备节点的默认时钟属性
  3. 3. 将设备附加到电源域
  4. 4. 最后调用开发者实现的drv->probe(dev)

所以,你写在platform_driver结构体里的probe函数,确实是在匹配成功后由内核自动调用的。你不用自己调用,系统会帮你调用,条件是把设备和驱动注册到同一条总线上,match返回成功

probe里一般要做什么?

  • • 从platform_device中提取硬件资源(寄存器地址、IRQ号等)
  • • 申请字符设备号和注册cdev(字符设备驱动场景下)
  • • ioremap映射寄存器地址
  • • 申请中断并注册中断处理函数
  • • 初始化硬件状态
  • • 最后通常是创建设备类class和自动设备节点,这样用户空间才能通过设备节点访问硬件

3.4 remove函数解绑机制

有probe就必须有remove,不然驱动卸载时内存就泄露得不成样子。remove函数在驱动卸载时被内核调用,职责是回收probe里占用的所有资源:

  • • 释放内存映射(iounmap)
  • • 释放中断(free_irq)
  • • 注销cdev
  • • 释放设备号
  • • 删除自动创建设备节点

这个流程和 probe 刚好完全相反。在 probe 里你拿了多少好处,在 remove 里你就得一五一十全部吐出来。

对于platform驱动,新版本的Linux内核推荐使用remove_new(返回void),因为原来的remove返回int但返回值基本被忽略,新版本语义更清晰。

四、进阶核心:设备类、sysfs与udev用户态交互机制

Linux设备模型除了搞定内核内部的驱动匹配,还要解决一个问题:用户空间的应用程序怎么访问硬件?

答案藏在/sys和/dev这两个目录里。

4.1 设备类:设备分类管理体系

struct class是一个更高层次的抽象,它按功能对设备进行"分类"。注意,class和设备映射到具体总线的逻辑是独立的,这是Linux设备模型的一大特点——分类和物理连接是解耦的。

这个结构体的主要作用就是在/sys/class下创建设备的分类视图。

在字符设备驱动里,我们经常会这么写:

staticstruct class *my_class;my_class = class_create(THIS_MODULE, "my_device_class");device_create(my_class, NULL, devno, NULL, "mydevice");

这样做的结果是:

  • • 在/sys/class/my_device_class下面会生成一个软链接(或子目录)指向实际的设备路径
  • • 同时,/dev/mydevice会自动生成(配合udev机制)

class_create和device_create把设备模型的底层逻辑和用户空间连接起来了。

4.2 sysfs文件系统:内核设备可视化

sysfs是一个虚拟文件系统,默认挂载在/sys目录。它把内核的数据结构以文件和目录的形式暴露给用户空间,让你像操作普通文件一样读取和修改内核参数。

sysfs的目录结构有固定的三个核心入口:/sys/bus/sys/devices/sys/class

  • • /sys/devices是设备树的"原始副本",按照物理连接层次展示所有设备(一个很大的树结构)
  • • /sys/bus以总线的维度展示设备和驱动:每个总线目录下包含devicesdrivers两个子目录,分别存放注册到该总线的设备和驱动
  • • /sys/class按设备功能分类,方便用户态程序按用途查找设备

sysfs里那些属性文件是怎么来的呢?每个kobject有自己的ktype,ktype里定义了sysfs_ops和一组默认属性。当你调用device_add注册设备时,内核会通过sysfs_create_dir为kobject创建sysfs目录,并调用sysfs_create_file等函数创建属性文件。

通过sysfs调试设备参数实在是太方便了。举个例子,我要看一个platform设备的中断号是多少,直接cat /sys/devices/platform/xxx/uevent就能看到硬件信息(虽然最终用户态udev也能看到)。更常用的做法是:echo 0 > /sys/module/xxx/parameters/debug 来开关驱动debug日志。没有sysfs,这种运行时配置就难搞多了。

4.3 udev机制:自动创建设备节点

很多刚入门的开发者觉得很神奇——为什么驱动模块加载后,/dev目录下会自动出现我想要的设备节点?这背后是udev在发力。

系统启动阶段,内核通过devtmpfs在/dev下生成基础设备文件。然后udevd守护进程(用户空间)监听内核通过netlink发送的uevent事件,解析内核传递的环境变量,匹配/etc/udev/rules.d/下的规则,决定如何创建/删除设备节点、如何设置权限和属主、甚至加载/卸载驱动模块。

具体工作流程分两步:

  1. 1. 驱动程序通过class_create和device_create在/sys/class下创建分类标识和设备信息
  2. 2. udevd捕获到内核的uevent事件后,在/dev下动态生成对应的设备节点

这对用户空间应用程序来说,设备节点是确定的稳定入口,无需关心设备什么时候插拔。我U盘插上去瞬间/dev/sdb1就出现了,这是udev干的活。

做个冷热插拔的小实验

当你注册一个设备节点时,驱动代码里调用了device_create,内核会通过kobject_uevent函数向用户空间发送"add"事件,udevd抓到事件后运行处理程序创建设备节点。

反过来,移除驱动时发送"remove"事件,udev删除节点。

在嵌入式设备开发中,很多人用mdev(一个轻量化的udev实现,常见于Busybox系统)来处理设备节点的动态创建。它在资源受限的环境中非常实用,功能虽比完整udev少,但核心设备节点自动创建的功能一点也不含糊。

五、实战:模型如何驱动真实硬件

5.1 案例1:platform总线 + 字符设备驱动开发流程

结合前面的理论,我们从头撸一个完整的platform字符设备驱动代码框架。

这个例子比较经典,可能和你平时写的有些出入,但核心逻辑是一样的:platform_driver负责匹配设备,cdev负责字符设备操作,sysfs和udev负责与用户空间的交互

驱动部分框架如下:

#include <linux/module.h>#include <linux/kernel.h>#include <linux/platform_device.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/device.h>#define DEV_NAME "mychardev"static int major;staticstruct class *my_class;static int my_open(struct inode *inode, struct file *file){    printk(KERN_INFO "my device opened\n");    return 0;}static ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off){    printk(KERN_INFO "my device read\n");    return 0;}static ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *off){    printk(KERN_INFO "my device written\n");    return len;}static conststruct file_operations my_fops = {    .owner = THIS_MODULE,    .open = my_open,    .read = my_read,    .write = my_write,};static int my_probe(struct platform_device *pdev){    printk(KERN_INFO "my_probe called!\n");    // 动态申请设备号并注册cdev    major = register_chrdev(0, DEV_NAME, &my_fops);    if (major < 0) {        printk(KERN_ERR "register_chrdev failed\n");        return major;    }    // 自动创建设备节点需要class    my_class = class_create(THIS_MODULE, DEV_NAME);    if (IS_ERR(my_class)) {        unregister_chrdev(major, DEV_NAME);        return PTR_ERR(my_class);    }    device_create(my_class, NULL, MKDEV(major, 0), NULL, DEV_NAME);    return 0;}static int my_remove(struct platform_device *pdev){    printk(KERN_INFO "my_remove called!\n");    device_destroy(my_class, MKDEV(major, 0));    class_destroy(my_class);    unregister_chrdev(major, DEV_NAME);    return 0;}static conststruct of_device_id my_of_match[] = {    { .compatible = "lckfb,mychardev" },    { /* sentinel */ }};MODULE_DEVICE_TABLE(of, my_of_match);staticstruct platform_driver my_driver = {    .probe = my_probe,    .remove_new = my_remove,    .driver = {        .name = "mychardev",        .of_match_table = my_of_match,    },};module_platform_driver(my_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("Platform Char Device Demo");

编译加载后:

insmod mydriver.kocat /proc/devices            # 看到mychardev对应主设备号ls /dev/mychardev            # 设备节点已生成echo "hello" > /dev/mychardev    # 触发write回调cat /dev/mychardev           # 触发read回调rmmod mydriver               # 触发remove,设备节点被删除

5.2 案例2:设备树(Device Tree)的应用

随着设备树的普及,我建议不要再把硬件信息写到platform_device结构体里初始化。维护一个c文件描述I2C/SPI/GPIO的寄存器地址和中断号,会导致内核代码和硬件信息紧耦合,换一块板子就得重新编译内核。设备树就是来救场的——把板级硬件信息从内核代码抽离出来,放到dts文件中

设备树匹配驱动的核心就是compatible属性:驱动中的of_match_table和设备树节点的compatible能对应上,内核就会匹配并调用probe

写DTS文件大致分成两步。

首先,编写dts文件(或dtsi片段):

/ {    my_device: mychardev@0 {        compatible = "lckfb,mychardev";        reg = <0x12340000 0x1000>;        interrupts = <56>;        status = "okay";    };};

然后驱动端声明of_match_table,告诉内核我的驱动支持哪些compatible字符串。

内核在启动过程中处理DTB(设备树二进制文件),把它解析成device_node树,然后为每个节点创建platform_devicei2c_client等设备实例,资源(内存地址、IRQ号)也随之传递。驱动中的probe被调用时,从platform_device中提取硬件资源并初始化硬件。

5.3 调试与工具

做底层驱动开发,写代码往往只占两成时间,剩下八成时间全在调试 Bug。

内核是个小黑盒,要是出了问题,我们怎么知道设备模型里到底哪儿掉链子了?

(1)sysfs节点监控

  • • ls /sys/bus/platform/devices/:查看所有平台设备
  • • ls /sys/bus/platform/drivers/:查看所有平台驱动
  • • cat /sys/devices/platform/xxx/uevent:查看设备的硬件资源信息
  • • echo 0 > /sys/module/xxx/parameters/debug:动态开关驱动调试(前提是模块有可调参数)
  • • tree /sys/class/xxx:查看设备分类视图,快速定位某个设备是否注册成功

(2)uevent事件的捕获与分析

直接运行udevadm monitor --property,可以看到内核发出的完整uevent事件(包括action、devpath、major/minor等关键信息),这对调试设备节点没自动生成的问题非常非常有用。

(3)内核日志与调试技巧

  • • dmesg | tail:实时查看probe/remove日志
  • • echo 8 > /proc/sys/kernel/printk:提高内核日志输出级别
  • • 动态调试:在内核中配置CONFIG_DYNAMIC_DEBUG,然后echo "file mydriver.c +p" > /sys/kernel/debug/dynamic_debug/control来动态控制打印
  • • insmod时加上dyndbg=+p参数可以启用模块的调试打印

就拿uevent监控来说,曾经有一个项目里deamon进程总是拿不到设备节点,我通过udevadm monitor发现内核根本没往用户空间发add事件。最后排查到驱动中的class_create调晚了一步,device_add先完成,所以sysfs下设备类目录没生成,udev自然没法工作——这能看出内核和用户空间的交互时机有多敏感。

驱动模型的核心概念:

调试驱动的时候,这三个命令保你平安:
udevadm monitor --property   # 看内核发什么事件tree /sys/class/              # 看设备类是否注册好dmesg -w                      # 看probe报什么错误

如果一个设备匹配了但probe没跑起来,多半是中断号、内存地址、DMA通道那部分资源解析出问题了,去/sys/devices/platform/<设备>/uevent里cat一下看资源是不是空的。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 09:42:32 HTTP/2.0 GET : https://f.mffb.com.cn/a/497249.html
  2. 运行时间 : 0.166296s [ 吞吐率:6.01req/s ] 内存消耗:4,544.96kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=e209e6fda811360bee9cc802640d5fe0
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000522s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000569s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000304s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000284s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000483s ]
  6. SELECT * FROM `set` [ RunTime:0.000197s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000516s ]
  8. SELECT * FROM `article` WHERE `id` = 497249 LIMIT 1 [ RunTime:0.035325s ]
  9. UPDATE `article` SET `lasttime` = 1783042952 WHERE `id` = 497249 [ RunTime:0.005925s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000299s ]
  11. SELECT * FROM `article` WHERE `id` < 497249 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000582s ]
  12. SELECT * FROM `article` WHERE `id` > 497249 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.021938s ]
  13. SELECT * FROM `article` WHERE `id` < 497249 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.011768s ]
  14. SELECT * FROM `article` WHERE `id` < 497249 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.004130s ]
  15. SELECT * FROM `article` WHERE `id` < 497249 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.013938s ]
0.167850s