乐于分享
好东西不私藏

【Linux驱动】字符设备驱动内核源码深度解析

【Linux驱动】字符设备驱动内核源码深度解析

Linux内核主要包括三种驱动模型:字符设备驱动块设备驱动以及网络设备驱动。其中,字符设备驱动是Linux驱动开发中最常见、最基础的驱动模型。

本文将从内核源码角度出发,拆解字符设备驱动的机制,涵盖:

  • 字符设备号管理:内核如何分配和追踪设备号

  • 字符设备对象(cdev):内核如何抽象和管理字符设备

  • kobj_map 哈希映射机制:设备号到 cdev 的快速查找

  • mknod 与 open 系统调用全链路:从用户态到内核驱动的完整路径

  • 共享内存字符设备驱动案例:简单的字符设备驱动使用方法代码

📌 提示:本文基于 Linux 内核 5.x 版本源码进行分析,主要源码文件位于

fs/char_dev.c

include/linux/cdev.h

drivers/base/map.c


一、字符设备号管理

本小节的主线是内核如何管理哪些设备号已经分配,哪些设备号可用

1.1 dev_t 设备号概述

在Linux内核中,每个字符设备都由一个32 位的设备号(dev_t)唯一标识。这个 32 位数值被划分为两部分:

  • 主设备号(Major Number):占用高 12 位(bit 12-31),用于标识设备驱动类型

  • 次设备号(Minor Number):占用低 20 位(bit 0-19),用于区分同一驱动下的不同设备实例

内核提供了三个关键宏来操作设备号:

#defineMINORBITS20#defineMINORMASK ((1U << MINORBITS) - 1)#defineMAJOR(dev) ((unsignedint) ((dev) >> MINORBITS)) /* 提取主设备号 */#defineMINOR(dev) ((unsignedint) ((dev) & MINORMASK))  /* 提取次设备号 */#defineMKDEV(ma,mi) (((ma) << MINORBITS) | (mi))        /* 组合生成设备号 */

💡 设计思想:主设备号相当于"设备类型标识",例如所有I2C设备共享一个主设备号;次设备号则用于区分具体是哪个I2C设备(如 MPU6050 传感器还是 EEPROM 存储器)。这种分层设计使得内核可以在保持设备号空间紧凑的同时,支持大量设备实例

1.2 char_device_struct 结构体

内核通过char_device_struct结构体来记录系统中已分配的设备号范围(主设备号 + 次设备号区间),形成一个资源管理表,防止设备号冲突。内核维护了chrdevs哈希表来记录设备号的使用情况:

/* 定义在 fs/char_dev.c */#define CHRDEV_MAJOR_HASH_SIZE 255

staticstructchar_device_struct{

structchar_device_struct*next;/* 将相同哈希值的节点链接成链表 */

unsignedintmajor;        /* 主设备号 */

    unsignedintbaseminor;    /* 次设备号起始值 */

    intminorct;               /* 次设备号数量 */

charname[64];             /* 设备名称 */

    structcdev*cdev;         /* 内核字符对象(已废弃) */

} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

🔑 关键设计:内核使用chrdevs维护设备号的分配信息

chrdevs数组大小仅为 255,但主设备号范围是 0~511(甚至理论上可以更大)。内核通过取模操作major % 255将主设备号映射到哈希桶。具体来说:

  • 主设备号 0、255、510... 都落在桶 0

  • 主设备号 1、256、511... 都落在桶 1

  • 桶内链表按主设备号从小到大排序

这种设计下:在不增加数组规模的前提下,理论上支持无限的主设备号范围,且查找效率不受影响。

1.3 __register_chrdev_region 函数

__register_chrdev_region是设备号分配的核心函数,负责在chrdevs哈希表中查找或插入一个空闲的设备号区间,并返回对应的char_device_struct指针

/* 定义在 fs/char_dev.c */

staticstructchar_device_struct*__register_chrdev_region(unsignedintmajor,unsignedintbaseminor,intminorct,constchar*name) {

    structchar_device_struct*cd, **cp;

    intret=0;    inti;

    /* 1. 分配新的 char_device_struct 节点 */

    cd=kzalloc(sizeof(structchar_device_struct),GFP_KERNEL);

    if(cd==NULL)

        returnERR_PTR(-ENOMEM);

    mutex_lock(&chrdevs_lock);

    /* 2. 如果 major == 0,动态分配主设备号 */

    if(major==0) {

ret=find_dynamic_major();

            if(ret<0) {

                pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",name);

                gotoout;

            }

            major=ret

    }

    /* 3. 主设备号范围校验 */

    if(major>=CHRDEV_MAJOR_MAX{

        pr_err("CHRDEV \"%s\" major requested (%u) greater than max (%u)\n",name,major,CHRDEV_MAJOR_MAX-1);

ret= -EINVAL;

        gotoout;

    }

    cd->major=major;

    cd->baseminor=baseminor;

    cd->minorct=minorct;

    strlcpy(cd->name,name,sizeof(cd->name));

    /* 4. 计算哈希桶位置 */

    i=major_to_index(major);

    /* 5. 在链表中找到合适的插入位置(按主设备号、次设备号排序) */

    for(cp= &chrdevs[i]; *cp;cp= &(*cp)->next) {

        if((*cp)->major>major||

           ((*cp)->major==major&& 

           ((*cp)->baseminor>=baseminor|| 

           (*cp)->baseminor+ (*cp)->minorct>baseminor)))

break

        }

    /* 6. 检查次设备号是否冲突(三种重叠检测) */

    if(*cp&& (*cp)->major==major) {

        intold_min= (*cp)->baseminor;

        intold_max= (*cp)->baseminor+ (*cp)->minorct-1;        intnew_min=baseminor;        intnew_max=baseminor+minorct-1;

        /* 新范围与已有范围重叠 → 冲突 */

        if(new_max>=old_min&&new_max<=old_max) {

            ret= -EBUSY;

            gotoout;

        }

        if(new_min<=old_max&&new_min>=old_min) {

            ret= -EBUSY;

            gotoout;

        }

        /* 新范围完全覆盖已有范围 */

        if(new_min<old_min&&new_max>old_max) {

            ret= -EBUSY;

            gotoout;

        }

    }

    /* 7. 插入链表 */

    cd->next= *cp;

    *cp=cd;

    mutex_unlock(&chrdevs_lock);

    return cd;

out:

    mutex_unlock(&chrdevs_lock);

    kfree(cd);

    returnERR_PTR(ret);

}

🔑 冲突检测逻辑解析:内核在插入新设备号时,会检查三种可能的冲突场景:

  1. 新范围尾部与已有范围重叠:new_max落在[old_min, old_max]区间内

  2. 新范围头部与已有范围重叠:new_min落在[old_min, old_max]区间内

  3. 新范围完全覆盖已有范围:new_min < old_minnew_max > old_max

只有三种情况都不满足时,才认为设备号范围无冲突,可以安全注册。

1.4 find_dynamic_major 函数

当驱动调用alloc_chrdev_region(传入 major=0)请求动态分配设备号时,内核会调用find_dynamic_major从空闲范围中查找可用的主设备号:

staticintfind_dynamic_major(void){

    inti;

    structchar_device_struct*cd;

    /* 高优先级:在 234~254 范围内查找 */

    for(i=ARRAY_SIZE(chrdevs) -1;i>=CHRDEV_MAJOR_DYN_END;i--) {

        if(chrdevs[i] ==NULL)

        returni;

    }

    /* 低优先级:在 511~384 范围内查找 */

    for(i=CHRDEV_MAJOR_DYN_EXT_START;i>=CHRDEV_MAJOR_DYN_EXT_END;i--) {

        for(cd=chrdevs[major_to_index(i)];cd;cd=cd->next) {

            if(cd->major==i)

                break;

        }

        if(cd==NULL)

            returni

        }

    return-EBUSY;

}

📌 两级查找策略:

优先级查找范围说明
234 ~ 254
传统动态分配区域,"向后兼容"的保留区间
511 ~ 384
扩展动态分配区域,满足更多设备注册需求

相关宏定义:

  • CHRDEV_MAJOR_MAX 512

  • CHRDEV_MAJOR_DYN_END 234

  • CHRDEV_MAJOR_DYN_EXT_START 511

  • CHRDEV_MAJOR_DYN_EXT_END 384

1.5 设备号分配的两大对外接口

内核提供了两种设备号分配方式,对应不同的使用场景:

接口方式
register_chrdev_region()静态分配
alloc_chrdev_region()动态分配

两者最终都调用__register_chrdev_region()完成实际注册操作。在Linux系统中,可以通过cat /proc/devices查看所有已注册的设备号列表。设备号注销时,无论静态还是动态分配,统一调用unregister_chrdev_region()归还资源


二、字符设备对象(cdev)

本小节的主线是了解两个结构体的设计,分别是内核字符设备结构体cdev,以及管理字符设备的结构体kobj_map

2.1 cdev 结构体

struct cdev是内核中表示字符设备的核心数据结构。每个字符设备驱动都需要创建一个cdev实例,并将其注册到内核中:

/* 摘自 include/linux/cdev.h */

structcdev{

    structkobjectkobj;     /* 内嵌的kobject,用于设备模型管理 */

    structmodule*owner;    /* 指向所属模块,通常为THIS_MODULE */

    conststructfile_operations*ops/* 设备操作函数集 */

    structlist_headlist;   /* 用于将cdev链接到对应设备号的cdev列表 */

    dev_tdev;               /* 记录该字符设备关联的起始设备号 */

    unsignedintcount;      /* 从dev开始连续占用的次设备号数量 */

}__randomize_layout;
成员变量作用
kobj
嵌入的内核对象,使 cdev 能被 sysfs 设备模型管理
owner
指向拥有该设备的模块,用于引用计数管理
ops
指向设备操作函数集(open/read/write/ioctl 等)
list
链表节点,用于将使用该 cdev 的 inode 链接起来
dev
起始设备号(主设备号 + 次设备号)
count
该 cdev 管理的连续次设备号数量

2.2 kobj_map 结构体(哈希表)

struct kobj_map是一个内核内部结构体,定义在drivers/base/map.c中,用于建立设备号(dev_t)到 struct cdev 的快速查找映射

  • kobj_map负责将设备号范围映射到对应的struct cdev结构体。当内核通过设备号访问字符设备时(如打开设备文件时),会根据设备号在kobj_map中查找对应的struct cdev,从而获得其操作函数集

  • void *data指针中保存了cdev结构体指针

  • probes数组是一个哈希桶,每个桶是一个链表,链表节点记录了设备号范围及对应的data(通常指向struct cdev)和获取kobject的函数

  • 通过cdev_add将一个cdev添加到系统中时,实际上就是在kobj_map中插入一个节点

/* 摘自 drivers/base/map.c */

structkobj_map{

    structprobe{

        structprobe*next;    /* 链表下一个节点 */

        dev_tdev;             /* 起始设备号 */

        unsignedlongrange;   /* 次设备号范围 */

        structmodule*owner;  /* 模块所有者 */

        kobj_probe_t*get;     /* 获取kobject的函数(用于查找) */

        int(*lock)(dev_t,void*); /* 锁定函数 */

        void*data;            /* 私有数据,通常指向cdev */

    } *probes[255];            /* 255个桶的哈希表 */

    structmutex*lock;        /* 保护映射表的锁 */

};

🔑 chrdevs 和 cdev_map —— 两套哈希表的职责分工:

哈希表存储内容职责
chrdevs
已分配的设备号范围
设备号资源管理,防止冲突
cdev_map
设备号到 cdev 映射
运行时快速查找设备驱动

三、cdev 注册函数

本小节的主线是内核如何将用户定义的cdev结构体注册到内核cdev_map

3.1 cdev_init 函数

cdev_init用于初始化一个已经分配的cdev结构体,将其与文件操作集关联,并设置内嵌kobject的类型:

voidcdev_init(structcdev*cdev,conststructfile_operations*fops) {

    memset(cdev,0,sizeof*cdev); /* 清零结构体 */

    INIT_LIST_HEAD(&cdev->list);   /* 初始化链表头 */

    kobject_init(&cdev->kobj, &ktype_cdev_default); /* 初始化内嵌kobject */

    cdev->ops=fops;              /* 绑定文件操作集 */

}EXPORT_SYMBOL(cdev_init);

EXPORT_SYMBOL宏将该函数导出到内核符号表,使内核模块(.ko 文件)可以调用它。

3.2 cdev_add 函数

完成设备号,次设备数量等参数记录,并将字符设备注册到内核

intcdev_add(structcdev*p,dev_tdev,unsignedcount) {

    interror;

    p->dev=dev/* 记录起始设备号 */

    p->count=count/* 记录次设备号数量 */

    /* 调用kobj_map注册,将设备号范围映射到该cdev */

    error=kobj_map(cdev_map,dev,count,NULL,exact_match,exact_lock,p);

    if(error)

        returnerror;

    /* 增加模块引用计数,防止模块被卸载 */

    kobject_get(p->kobj.parent);

    return0;

}

EXPORT_SYMBOL(cdev_add);

3.3 kobj_map 函数

这是将 cdev 插入到全局哈希表cdev_map的核心函数,接下来才可以通过设备号查找对应的字符设备,定义在drivers/base/map.c中:

intkobj_map(structkobj_map*domain,dev_tdev,unsignedlongrange,

             structmodule*module,kobj_probe_t*probe,

             int(*lock)(dev_t,void*),void*data) {

    /* 1. 计算跨越了几个不同的主设备号 */

    unsignedn=MAJOR(dev+range-1) -MAJOR(dev) +1;

    unsigned index MAJOR(dev);    unsigned i;    struct probe *p;    if (255255;    /* 安全限制 */

    /* 2. 分配 n 个连续的 probe 结构体 */

    p=kmalloc_array(n,sizeof(structprobe),GFP_KERNEL);

    if (== NULLreturn-ENOMEM;

    /* 3. 初始化 n 个 probe(每个的 data 都指向同一个 cdev) */

    for(i=0;i<n;i++,p++) {

        p->owner=module;

        p->get=probe;

        p->lock=lock;

        p->dev=dev;

        p->range=range;

        p->data=data

    }

    /* 4. 将 probe 节点插入哈希表,按 range 排序 */

    mutex_lock(domain->lock);

    for(i=0,p-=n;i<n;i++,p++,index++) {

        structprobe**s= &domain->probes[index%255];

        /* 按 range 大小排序,range 小的在前 */

        while(*s&& (*s)->range<range)

            s= &(*s)->next;

        p->next= *s

        *s=p

    }

    mutex_unlock(domain->lock);

    return0;

}

💡 为什么按 range 排序?当查找设备号对应的 cdev 时,kobj_lookup函数会遍历链表,选择range最小且包含该设备号的 probe。这种"最佳匹配"策略确保当多个 cdev 的设备号范围有重叠时,能精确匹配到最具体的那一个。


四、字符设备号与字符设备对象的关系

  • chrdevs—— 设备号资源管理记录主设备号的使用区间,防止冲突。在驱动加载时调用register_chrdev_regionalloc_chrdev_region分配设备号时,进行冲突检测。

  • cdev_map—— 运行时查找设备根据打开设备文件的设备号,快速找到cdev结构体,获得file_operations。在cdev_add函数中调用kobj_map函数构建哈希表。


五、次设备号的解析与多实例管理

本小节用一个非常简单的例子解析为什么需要区分主次设备号,一个主设备号是如何与多个次设备号关联起来的

在实际应用中,多个功能相同的子设备可以共用同一套驱动程序

  • 不同子设备功能逻辑一样,操作函数集(openreadwrite等)完全相同

  • 没必要为每个次设备号都分配一个 cdev,只需要一个cdev就能管理多个实例

  • 用户空间看到三个独立的设备文件(/dev/device1对应次设备号0device2对应1device3对应2),但内核中都导向同一个file_operations

5.1 注册 cdev

dev_tdevno=MKDEV(major,0); /* 起始次设备号 0 */

cdev_init(&my_cdev, &fops);

cdev_add(&my_cdev,devno,3);  /* 占用次设备号 0, 1, 2 */

5.2 定义设备私有结构体和数组

structmy_device{

    void__iomem*regs;   /* 该设备的寄存器映射地址 */

    structmutexlock;    /* 互斥锁 */

    /* ... 其他私有数据 */};staticstructmy_devicedevs[3];  /* 三个实例 */

5.3 在 open 中用次设备号绑定私有数据

staticintmy_open(structinode*inode,structfile*filp) {

    intminor=iminor(inode);   /* 获取次设备号 */

    if(minor<0||minor>=3)

        return-ENODEV;

    structmy_device*dev= &devs[minor]; /* 根据次设备号选择设备 */

    filp->private_data=dev;             /* 保存到私有数据中 */

    return0;

}

📌 次设备号使用:这种"一个 cdev → 多个次设备号 → 多个设备实例"的模式是 Linux 驱动开发中的标准做法。通过iminor(inode)获取次设备号,再通过filp->private_data保存设备私有数据,后续的 read/write/ioctl 操作都能通过filp->private_data获取到正确的设备实例


六、mknod与 open系统调用的完整流程

本小节介绍从用户态打开一个字符设备驱动的完整流程,关注内核如何通过字符设备号找到对应的字符设备结构体,以及用户定义的操作函数如何被替换

6.1 系统调用的整体流程

让我们从用户态的一条命令开始

mknod/dev/mydevicec2500

这个命令触发的过程被拆解为四个关键步骤:

  1. 使用mknod创建一个字符设备 inode 节点,该节点中保存了默认 open 函数chrdev_open。节点定义在/dev/mydevice

  2. 在用户空间使用open函数,传入"/dev/mydevice"

  3. 通过一系列系统调用,最后到达do_dentry_open函数,该函数会调用inode节点对应的默认 open 函数chrdev_open

  4. chrdev_open函数在全局哈希表cdev_map中,根据设备号找到用户定义的f_ops操作函数,并替换inodefile中的操作函数。至此,用户自定义的操作函数正式被调用

6.2 mknod创建 node节点

在控制台调用mknod函数时,经过一系列系统调用,最后会调用init_special_inode函数。该函数是 VFS 层处理特殊文件的基石,根据文件类型为inode挂载合适的操作函数表,并保存必要的设备号信息。

voidinit_special_inode(structinode*inode,umode_tmode,dev_trdev) {

    inode->i_mode=mode;             /* 保存文件类型和权限 */

    if(S_ISCHR(mode)) {

        /* 字符设备文件 */

        inode->i_fop= &def_chr_fops/* 默认字符设备操作表 */

        inode->i_rdev=rdev;         /* 保存设备号 */

    }elseif(S_ISBLK(mode)) {

        /* 块设备文件 */

        inode->i_fop= &def_blk_fops;

        inode->i_rdev=rdev

    }elseif(S_ISFIFO(mode))

        /* 命名管道 */

        inode->i_fop= &pipefifo_fops;

    elseif(S_ISSOCK(mode)) ;

/* 套接字不设置操作表 */

    else

        printk(KERN_DEBUG"bogus i_mode for inode %s:%lu\n",inode->i_sb->s_id,inode->i_ino);

}

🔑 理解:此时 inode 中的i_fop指向的是def_chr_fops,一个仅包含默认 open 方法的操作表。真正的驱动专用操作函数表要等到用户调用open()时,才会通过chrdev_open函数动态替换。这是一种"延迟绑定"的设计模式

6.3 def_chr_fops —— 字符设备的默认操作函数

/* 来源: fs/char_dev.c */

conststructfile_operationsdef_chr_fops= {

    .open=chrdev_open/* 只定义了一个 open 方法 */

    .llseek=noop_llseek/* 一个什么都不做的寻址函数 */

};

6.4 chrdev_open —— 字符设备 open 默认函数

chrdev_open是整个字符设备驱动机制中最关键的桥接函数:

/* 来源: fs/char_dev.c */

staticintchrdev_open(structinode*inode,structfile*filp) {

    structcdev*p;

    struct cdev *new NULL;    int ret=0;

    /* 第一步:获取或查找 cdev */

    p=inode->i_cdev;

    if(!p) {

        structkobject*kobj;

        intidx;

        /* 根据设备号在全局哈希表 cdev_map 中查找对应的 cdev */

        kobj=kobj_lookup(cdev_map,inode->i_rdev, &idx);

        if(!kobj)

            return-ENXIO;

        /* 通过 kobject 反推出包含它的 cdev */

        new=container_of(kobj,structcdev,kobj);

        /* 并发安全:再次检查并设置 inode->i_cdev(缓存优化) */

p=inode->i_cdev;

        if(!p) {

            inode->i_cdev=p=new;

            inode->i_cindex=idx;

            list_add(&inode->i_devices, &p->list);

            new=NULL

        }

    }

    /* 第二步 ★核心★ 替换文件操作表 */

    /* 将 file 的 f_op 从 def_chr_fops 替换为驱动自己的 fops */

    filp->f_op=fops_get(p->ops);

    if(!filp->f_op)

        return-ENXIO;

    /* 第三步:调用驱动自身的 open 函数 */

    if(filp->f_op->open) {

        ret=filp->f_op->open(inode,filp);

    }

    returnret;

}

🔑 chrdev_open 三步走核心逻辑:

  1. 查找 cdev:通过kobj_lookup()在全局哈希表中查找,用container_of反推出 cdev。首次查找后缓存到inode->i_cdev

  2. 替换 f_op:filp->f_op = fops_get(p->ops)——最关键的一步,将 file 的文件操作表从def_chr_fops替换为驱动自己的file_operations

  3. 调用驱动 open:如果驱动实现了自己的 open 方法,则调用它。至此,后续对该文件的 read/write 等操作都会直接调用驱动的对应函数。

6.5 open 函数的完整调用链

6.5.1 用户态 open() 到系统调用入口

用户态的open()是 glibc 等 C 库提供的封装函数,它最终会触发系统调用__NR_open。在 x86-64 架构上,通过syscall指令陷入内核。

6.5.2 内核通用打开流程:do_sys_open → do_filp_open

do_sys_open将用户态的文件路径名拷贝到内核空间,构造open_how结构(包含打开标志和模式),然后调用do_sys_openat2

SYSCALL_DEFINE3(open,constchar__user*,filename,

                int,flags,umode_t,mode) {

    returndo_sys_open(AT_FDCWD,filename,flags,mode);

}

longdo_sys_open(intdfd,constchar__user*filename,

                 intflags,umode_tmode) {

    structopen_howhow=build_open_how(flags,mode);

    returndo_sys_openat2(dfd,filename, &how);

}

do_sys_openat2执行三个关键动作:

staticlongdo_sys_openat2(intdfd,constchar__user*filename,

                           structopen_how*how) {

    structfilename*tmp=getname(filename);     /* 拷贝路径到内核空间 */

    intfd=get_unused_fd_flags(how->flags);     /* 分配空闲fd */

    structfile*f=do_filp_open(dfd,tmp,how); /* ★路径查找并打开★ */

    d_install(fd,f);                             /* 关联 fd 与 file */

    returnfd;

}

6.5.3 路径查找与打开:path_openat → vfs_open

do_filp_open最终调path_openat(定义在 fs/namei.c),它完成路径的遍历(逐级查找目录项),找到 inode 节点,最后调用vfs_open来实际打开文件。

6.5.4 do_dentry_open —— 核心函数

do_dentry_open负责初始化struct file,并根据 inode 类型设置文件操作表,最终调用open方法。对于字符设备文件,inode->i_fop指向的是def_chr_fops。所以f->f_op->open实际上就是chrdev_open

staticintdo_dentry_open(structfile*f,structinode*inode,

                          int(*open)(structinode*,structfile*)) {

    f->f_mode=OPEN_FMODE(f->f_flags) |FMODE_LSEEK|FMODE_PREAD|FMODE_PWRITE;

    f->f_inode=inode;

    f->f_mapping=inode->i_mapping;

    /* ★获取文件操作表:优先使用 inode->i_fop★ */

    f->f_op=fops_get(inode->i_fop);

    if(!f->f_op)

        return-ENXIO;

    if(open)

        error=open(inode,f);

    /* 否则调用文件操作表中的 open 方法 */

    /* 对于字符设备 = chrdev_open! */

    if(!error&& (f->f_mode&FMODE_OPENED) &&f->f_op->open)

        error=f->f_op->open(inode,f);

    returnerror;

}

📌 完整调用链路总结:

用户态 open()sys_open()

do_sys_open()do_sys_openat2()

do_filp_open()path_openat()

vfs_open()do_dentry_open()

chrdev_open()(替换 fops)→驱动的 open()


七、字符设备驱动开发中的函数详解

7.1 container_of 宏

container_of是 Linux 内核中常用的宏之一,它通过一个结构体成员的指针,反向获取包含该成员的结构体的起始地址:

/*** container_of - 通过结构体成员指针获取父结构体指针* @ptr: 成员变量的指针* @type: 包含该成员的结构体类型* @member: 成员变量在结构体中的名称*/#definecontainer_of(ptr, type, member) ({ \    void *__mptr = (void *)(ptr); \    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \    !__same_type(*(ptr), void), \    "pointer type mismatch in container_of"); \    ((type *)(__mptr - offsetof(type, member))); })/* 简化理解版本: */#definecontainer_of(ptr, type, member) \    ((type *)((char *)(ptr) - offsetof(type, member)))

该函数根据结构体成员变量的指针,通过该变量相对于结构体的偏移,得到了该变量对应的结构体的指针。这是一个非常灵活的用法,通过保存某个结构体变量的指针,可以通过该指针反向推出该结构体的指针。

💡 container_of 的应用场景:在 Linux 内核中,这个宏无处不在。比如在chrdev_open中,内核通过kobj_lookup拿到了 cdev 内嵌的kobject的指针,然后通过container_of(kobj, struct cdev, kobj)反推出完整的cdev结构体指针。这种"内嵌 + 反推"的设计是内核面向对象编程思想的经典体现

7.2 register_chrdev_region

register_chrdev_region是 Linux 内核中用于注册字符设备编号范围的函数。该函数为驱动程序预留一段连续的设备号(主设备号 + 起始次设备号),后续将字符设备(通过cdev_add)绑定到这些设备号上:

/*** register_chrdev_region - 注册字符设备编号范围* @first: dev_t 类型,指定要注册的起始设备号(使用 MKDEV(major, minor) 宏生成)* @count: 需要注册的连续设备号数量(次设备号的范围)* @name: 设备名称,会出现在 /proc/devices 文件中* 返回值: 成功返回 0* 参数无效返回 -EINVAL* 设备号被占用返回 -EBUSY*/intregister_chrdev_region(dev_t first, unsignedint count, constchar *name);

7.3 THIS_MODULE

THIS_MODULE是一个宏,定义在<linux/module.h>头文件中。它本质上是一个指向当前模块的struct module结构体的指针:

  • 当代码被编译为可加载内核模块(.ko)时,THIS_MODULE指向该模块的struct module实例

  • 当代码被静态编译进内核(built-in)时,THIS_MODULE通常被定义为NULL或一个无实际作用的占位符

staticstructfile_operationsmy_fops= {

    .owner=THIS_MODULE,  /* 防止模块在使用中被卸载 */

    .open=my_open, .read=my_read,

    .write=my_write,

    .release=my_release,

};

structcdevmy_cdev;

cdev_init(&my_cdev, &my_fops);

my_cdev.owner=THIS_MODULE;

cdev_add(&my_cdev,devno,count);

内核通过owner字段知道哪个模块拥有这个字符设备。当用户空间程序通过系统调用(如open)打开设备文件时,内核会自动调用try_module_get(THIS_MODULE)增加模块的引用计数;当设备被关闭时,内核会调用module_put(THIS_MODULE)减少引用计数。

🔑 这样做的目的是确保模块在设备被打开期间不会被卸载。如果用户正在使用设备,而管理员执行rmmod试图卸载驱动,内核会检查模块的引用计数是否为零。若不为零(表示设备正在被使用),则卸载操作会被拒绝,防止因模块代码突然消失而导致系统崩溃。这是一个经典的"资源使用计数"保护模式。

7.4 file_operations 结构体

file_operations是把系统调用和驱动程序关联起来的关键数据结构:

函数指针对应系统调用说明
owner
——
指向拥有该结构的模块(THIS_MODULE)
llseek
lseek
修改文件读写位置
read
read
从设备读取数据
write
write
向设备写入数据
open
open
打开设备文件
release
close
关闭设备文件(引用计数归零时)
unlocked_ioctl
ioctl
设备控制操作(无 BKL 版本)
mmap
mmap
将设备内存映射到用户空间
poll
poll/select
询问设备是否可非阻塞读写

八、案例:使用字符设备驱动实现共享内存

下面是一个完整的内核模块示例,演示如何使用字符设备驱动实现共享内存。这个驱动程序创建两个字符设备(次设备号分别为 0 和 1),每个设备拥有独立的 1024 字节共享内存区域,用户空间可以通过read/write/ioctl/lseek操作这些内存。代码参考了书籍Linux设备驱动详解

#include <linux/init.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/module.h>#include <linux/ioctl.h>#include <linux/io.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/uaccess.h>#include <linux/slab.h>#define GLOBALMEM_SIZE 1024#define GLOBALMEM_MAGIC 'M'#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)
/* ===== 设备私有数据结构体 ===== */

structglobalmem_dev{

    structcdevm_cdev;                /* 内嵌的字符设备 */

    unsignedcharmem[GLOBALMEM_SIZE]; /* 共享内存区域(1KB) */

};staticint globalmem_major 266;struct globalmem_dev *globalmem_devp;/* 两个设备实例 *//* ===== open:用户打开设备文件时调用 ===== */

staticintglobalmem_open(structinode*inode,structfile*filp) {

    structglobalmem_dev*dev;

    /* 通过 inode->i_cdev (指向内嵌的 m_cdev) 反推出 globalmem_dev */

    dev=container_of(inode->i_cdev,structglobalmem_dev,m_cdev);

    /* 保存到 file->private_data,供后续 read/write 使用 */

    filp->private_data=dev;

    return0;

}

/* ===== release:用户关闭设备文件时调用 ===== */

staticintglobalmem_release(structinode*inode,structfile*filp) {

    return0;

}

/* ===== read:用户读取设备数据 ===== */

staticssize_tglobalmem_read(structfile*filp,char__user*buf,size_tcount,loff_t*ppos) {

    unsignedlongp= *ppos;

    structglobalmem_dev*dev=filp->private_data;

    if(p>=GLOBALMEM_SIZE)

        return0;

    if(count>GLOBALMEM_SIZE-p)

        count=GLOBALMEM_SIZE-p;

    /* 从内核空间拷贝数据到用户空间 */

    copy_to_user(buf, (void*)(dev->mem+p),count); *ppos=p+count;

    returncount;

}

/* ===== write:用户写入设备数据 ===== */

staticssize_tglobalmem_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*ppos) {

    unsignedlongp= *ppos;

    structglobalmem_dev*dev=filp->private_data;

    if(p>=GLOBALMEM_SIZE)

        return0;

    if(count>GLOBALMEM_SIZE-p)

        count=GLOBALMEM_SIZE-p;

    /* 从用户空间拷贝数据到内核空间 */

    copy_from_user(dev->mem+p,buf,count); *ppos=p+count;

    returncount;

}

/* ===== llseek:用户调整文件读写位置 ===== */

staticloff_tglobalmem_llseek(structfile*filp,loff_toffset,intorig) {

    loff_tret;

    switch(orig) {

    case0:

    /* SEEK_SET:从起始位置移动 */

        if(offset<0|| (unsignedint)offset>GLOBALMEM_SIZE) {

            ret= -EINVAL;

            break;

        }

        filp->f_pos= (unsignedint)offset;

        ret=filp->f_pos;

    break;

    case1:

    /* SEEK_CUR:从当前位置移动 */

    if((filp->f_pos+offset) >GLOBALMEM_SIZE|| (filp->f_pos+offset) <0) {

        ret= -EINVAL;

        break;

    }

    filp->f_pos+=offset;

    ret=filp->f_pos;

    break;

    default:

        ret= -EINVAL;

    }

    returnret;

}

/* ===== unlocked_ioctl:用户发送设备控制命令 ===== */

staticlongglobalmem_ioctl(structfile*filp,unsignedintcmd,unsignedlongarg) {

    structglobalmem_dev*dev=filp->private_data;

    switch(cmd) {

    caseMEM_CLEAR:

        memset(dev->mem,0,GLOBALMEM_SIZE);   /* 清空共享内存 */

    break;

    default:

        return-EINVAL;

    }

    return0;

}

/* ===== 文件操作函数集 ===== */

staticconststructfile_operationsglobalmem_fops= {

    .owner=THIS_MODULE,

    .open=globalmem_open,

    .release=globalmem_release

    .llseek=globalmem_llseek,

    .read=globalmem_read,

    .write=globalmem_write,

    .unlocked_ioctl=globalmem_ioctl,

};

/* ===== 模块加载函数 ===== */staticint__initglobalmem_init(void) {

    /* 1. 注册设备号:主 266,次 0~1,共 2 个 */

    register_chrdev_region(MKDEV(globalmem_major,0),2,"globalmem");

    /* 2. 为两个设备分配内存 */

    globalmem_devp=kmalloc(2*sizeof(structglobalmem_dev),GFP_KERNEL);

    memset(globalmem_devp,0,2*sizeof(structglobalmem_dev));

    /* 3. 初始化并注册次设备号 0 */

    cdev_init(&(globalmem_devp[0].m_cdev), &globalmem_fops);

    globalmem_devp[0].m_cdev.owner=THIS_MODULE;

    cdev_add(&(globalmem_devp[0].m_cdev),MKDEV(globalmem_major,0),1);

    /* 4. 初始化并注册次设备号 1 */

    dev_init(&(globalmem_devp[1].m_cdev), &globalmem_fops);

    globalmem_devp[1].m_cdev.owner=THIS_MODULE;

    cdev_add(&(globalmem_devp[1].m_cdev),MKDEV(globalmem_major,1),1);

    return0;

}

/* ===== 模块卸载函数 ===== */

staticvoid__exitglobalmem_exit(void) {

    cdev_del(&(globalmem_devp[0].m_cdev));

    cdev_del(&(globalmem_devp[1].m_cdev));

    kfree(globalmem_devp);

    dev_tdevno=MKDEV(globalmem_major,0);

    unregister_chrdev_region(devno,2);

}

MODULE_AUTHOR("punch");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("A simple shared memory character device driver");module_param(globalmem_major,int,S_IRUGO);module_init(globalmem_init);module_exit(globalmem_exit);

📌 驱动代码关键设计解析:

  1. container_of 的使用:

    globalmem_open中,通过inode->i_cdev反向推出globalmem_dev结构体

  2. private_data 传递:在 open 中将设备结构体保存到filp->private_data,这是 Linux 驱动中传递上下文的标准模式

  3. copy_to_user / copy_from_user:内核和用户空间数据交换必须通过这些安全函数进行

  4. IOCTL 命令定义:

    _IO(GLOBALMEM_MAGIC, 0)用于防止命令冲突

  5. 多实例管理:两个次设备号各自对应独立的globalmem_dev实例,各拥有独立的 1024 字节共享内存

8.1 测试驱动

编译加载驱动后,进行以下步骤来使用:

# 1. 加载驱动模块

sudo insmod globalmem.ko# 2. 查看驱动是否加载成功(应看到主设备号 266 和 "globalmem")cat /proc/devices | grep globalmem# 3. 创建设备节点sudo mknod /dev/globalmem0 c 266 0sudo mknod /dev/globalmem1 c 266 1# 4. 设置权限sudo chmod 666 /dev/globalmem0 /dev/globalmem1# 5. 测试读写echo "Hello Linux Driver!" > /dev/globalmem0cat /dev/globalmem0# 6. 卸载驱动sudo rmmod globalmem

💡 小结:本小节只是为了演示字符设备驱动的简单案例。实际开发中,建议使用alloc_chrdev_region动态分配设备号,避免冲突。同时使用class_createdevice_create可以让内核自动在/dev下创建设备节点,省去手动mknod的步骤。这些都是Linux 2.6 之后推荐的新字符设备驱动编写方式


🎯 总结

本文从内核源码角度,完整梳理了 Linux 字符设备驱动的核心机制:

  1. 设备号管理(chrdevs):通过 255 大小的哈希数组,利用取模操作管理 0~511 乃至更大的主设备号空间,配合链表按顺序组织,提供了高效的设备号冲突检测机制。

  2. cdev 对象与 kobj_map:cdev 是字符设备的抽象表示,cdev_map 是设备号到 cdev 的映射哈希表。两套哈希表各司其职——chrdevs 管理资源分配,cdev_map 支持运行时查找。

  3. 延迟绑定机制:inode 创建时只挂载默认的def_chr_fops(仅含 chrdev_open),真正驱动专用的 fops 要等到用户 open 时才通过 chrdev_open 动态替换。

  4. 完整调用链路open → sys_open → do_sys_open → do_filp_open → path_openat → do_dentry_open → chrdev_open(替换fops) → 驱动open()

  5. container_of 设计模式:通过内嵌结构体成员指针反推外围结构体,是 Linux 内核"面向对象"编程思想的集中体现。

  6. 引用计数保护:通过THIS_MODULEowner字段,防止设备在使用中被意外卸载。


文章内容为作者过往学习的笔记,接下来会按期更新,预计下一期更新内核结构体kobject解析。如果本文对你有帮助,欢迎点赞、在看、转发,让更多 Linux 驱动开发者受益 🚀

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