linux 设备初始化

文章目录

    • [#pic_left =65%x)](#pic_left =65%x))
    • 初始化整体流程、pci_probe
      • 1、pci_probe之前PCI总线会做些什么东西?
      • [2、pci_probe: pci_register_driver() 注册匹配成功后,才会触发pri_probe](#2、pci_probe: pci_register_driver() 注册匹配成功后,才会触发pri_probe)
      • [3、核心职责:probe 函数要做什么?](#3、核心职责:probe 函数要做什么?)
      • [4、"一个 PCI 驱动通过 pci_register_driver() 向内核注册时" 这里的一个PCI驱动是指什么 一个ko文件插入吗?](#4、“一个 PCI 驱动通过 pci_register_driver() 向内核注册时” 这里的一个PCI驱动是指什么 一个ko文件插入吗?)
    • [struct pci_driver:负责启动、结束流程](#struct pci_driver:负责启动、结束流程)
    • [struct class](#struct class)
    • [struct device](#struct device)
    • [struct pci_dev](#struct pci_dev)
      • 0、结构体
      • [1、提问:struct xx_device 的设备信息为什么要挂在pdev中?](#1、提问:struct xx_device 的设备信息为什么要挂在pdev中?)
      • [2、提问:struct xx_device 的设备信息怎么挂在pdev中?](#2、提问:struct xx_device 的设备信息怎么挂在pdev中?)
      • [3、pci_dev 中为什么要包含 pci_driver?](#3、pci_dev 中为什么要包含 pci_driver?)
    • [struct cdev](#struct cdev)
      • 0、结构体
      • [1、struct cdev 与 struct pci_dev的区别,pci_dev 不就表示这个设备了吗,为什么还要一个cdev?](#1、struct cdev 与 struct pci_dev的区别,pci_dev 不就表示这个设备了吗,为什么还要一个cdev?)
      • [2、一个 pci_dev 可以对应多个 cdev](#2、一个 pci_dev 可以对应多个 cdev)
      • [3、pci_dev 内嵌了 struct device,而 cdev 与之无关](#3、pci_dev 内嵌了 struct device,而 cdev 与之无关)
    • [struct file_operations :函数指针的集合](#struct file_operations :函数指针的集合)
      • 0、结构体
      • [1、完整调用流程(PCI 驱动的 probe 中)](#1、完整调用流程(PCI 驱动的 probe 中))
      • [3、file_operations 中 用户调用的函数 内核怎么通过注册的表 知道是要调用哪个函数的](#3、file_operations 中 用户调用的函数 内核怎么通过注册的表 知道是要调用哪个函数的)
      • 4、THIS_MODULE

#pic_left =65%x)


初始化整体流程、pci_probe

Todolist:

1、深度分析下面各个函数的具体作用

-- 先从重要的函数开始入手

c 复制代码
用户 insmod mydriver.ko
        │
        ▼
sys_init_module → do_init_module()
        │
        ▼
my_init()   // module_init 声明的函数
        │
        └── pci_register_driver(&my_pci_driver)
                │
                ▼
        __pci_register_driver()
        ├── driver_register() → 将 pci_driver 加入 PCI 总线驱动链表
        └── 触发 PCI 总线匹配 (遍历所有已存在的 pci_dev)
                │
                ▼
        pci_bus_match() → 比较驱动的 id_table 与 pci_dev 的 vendor/device
                │ 匹配成功
                ▼
        driver_probe_device() → really_probe()
                │
                ▼
        pci_device_probe()  // PCI 总线的 probe 回调
                │
                ▼
        __pci_device_probe() → pci_call_probe() → local_pci_probe()
                │
                ▼
        my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
        {
            // ========== 第一阶段:PCI 设备硬件初始化 ==========
            1. pci_enable_device(pdev);           // 唤醒设备,使能 I/O、MEM
            2. pci_request_regions(pdev, "mydev"); // 申请 BAR 资源(或单独申请每个 BAR)
            3. pci_set_dma_mask(pdev, DMA_BIT_MASK(64));  // 设置 DMA 寻址能力(64位)
               pci_set_consistent_dma_mask(...);         // 设置一致 DMA 掩码(可选)
            4. pci_set_master(pdev);               // 启用总线主控(允许设备发起 DMA)
            5. ioremap(pci_resource_start(pdev, bar), ...); // 映射 BAR 到内核虚拟地址
            
            // ========== 第二阶段:驱动私有数据分配 ==========
            6. priv = kzalloc(sizeof(*priv), GFP_KERNEL);
               priv->pdev = pdev;
               priv->regs = ioremap_base;   // 保存映射后的地址
               priv->irq  = pdev->irq;
            
            // ========== 第三阶段:中断注册 ==========
            7. request_irq(pdev->irq, my_isr, IRQF_SHARED, "mydev", priv);
            
            // ========== 第四阶段:字符设备接口注册 ==========
            8. alloc_chrdev_region(&dev_num, 0, 1, "mychardev");  // 动态分配设备号
            9. cdev_init(&priv->cdev, &my_fops);   // 绑定 file_operations
               priv->cdev.owner = THIS_MODULE;
           10. cdev_add(&priv->cdev, dev_num, 1);  // 注册到内核 cdev_map
            
            // ========== 第五阶段:创建设备节点(自动生成 /dev/ 文件)==========
           11. priv->class = class_create(THIS_MODULE, "myclass");
           12. device_create(priv->class, NULL, dev_num, NULL, "mydevice%d", instance);
            
            // ========== 第六阶段:挂载私有数据并返回成功 ==========
           13. pci_set_drvdata(pdev, priv);        // 将 priv 挂到 pdev 上
           14. return 0;   // probe 成功
        }
        │
        ▼
    probe 返回 0 → 驱动绑定完成,设备可用

1、pci_probe之前PCI总线会做些什么东西?

提问:"PCI 核心会将该驱动的 id_table(支持的设备ID列表)与系统中所有已存在的 PCI 设备进行匹配" 这里的系统中所有已存在的PCI设备,这里的设备是从哪来的?

-- 硬件上电,BIOS固件先扫;内核接力,通过扫描总线创建 struct pci_dev 对象,建立花名册(都建立在初始化阶段)

-- 驱动后到(insmod ko文件),拿着名单来匹配。

struct pci_dev 对象是内核框架创建的,bios-》内核框架,struct pci_dev 列表

1、固件扫描:固件在启动时扫描硬件并初始化。

2、内核接管:内核启动后,解析固件信息,通过扫描总线创建 struct pci_dev 对象。这个结构体是所有后续操作的基础,它包含了设备的所有信息(如供应商ID、设备ID、内存地址、中断号等)。

3、驱动注册:随后,PCI驱动调用 pci_register_driver() 进行注册,提交自己的 id_table

4、匹配Probe:PCI核心拿着驱动的id_table,与系统中已存在的所有 struct pci_dev 进行匹配。一旦发现匹配,就会调用驱动中的probe函数,并将对应的struct pci_dev指针作为参数传递给它。


2、pci_probe: pci_register_driver() 注册匹配成功后,才会触发pri_probe


3、核心职责:probe 函数要做什么?

c 复制代码
使能设备:调用 pci_enable_device() 来激活 PCI 设备,使其准备好响应 I/O 和内存访问。

获取资源:通过 pci_resource_start()、pci_resource_len() 等函数获取设备的内存、I/O 端口基地址和中断号(IRQ),
这些信息由 BIOS 或内核 PCI 子系统分配好并存储在 struct pci_dev 中。

设置 DMA 掩码:如果设备支持 DMA,需调用 pci_set_dma_mask() 等函数告知内核设备寻址能力。

设置总线控制:通常调用 pci_set_master() 来启用设备的 Bus Master 能力,使其能够主动发起 DMA 传输。

申请资源:调用 request_region() 或 request_mem_region() 来声明对 I/O 或内存地址区的占用,防止其他驱动冲突。

注册设备接口:根据设备类型,将其注册到相应的内核子系统。例如,一个网卡驱动会在 probe 中调用 register_netdev(),
一个字符设备驱动会调用 cdev_add(),使设备能在 /dev 或 /sys 下被用户空间访问。

设置私有数据:通过 pci_set_drvdata() 将驱动自定义的设备结构体与 struct pci_dev 关联起来,
便于在其他回调函数(如 remove、中断处理)中获取设备状态。

错误处理与清理:上述任何一步失败都应执行相应的清理动作,并返回一个负的错误码。
只有当所有初始化均成功时,probe 函数才返回 0。

以下是这些操作的代码示意,帮助你建立一个整体的概念:
static int example_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct example_device *my_dev;
    int ret;

    // 1. 使能设备
    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    // 2. 设置DMA掩码
    ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
    if (ret) {
        ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
        if (ret)
            goto err_disable;
    }

    // 3. 启用总线主控
    pci_set_master(pdev);

    // 4. 为设备私有结构体分配内存
    my_dev = kzalloc(sizeof(*my_dev), GFP_KERNEL);
    if (!my_dev) {
        ret = -ENOMEM;
        goto err_disable;
    }

    // 5. 获取并保存设备资源:内存基地址、中断号等
    my_dev->mmio_base = pci_resource_start(pdev, 0);
    my_dev->mmio_len = pci_resource_len(pdev, 0);
    my_dev->irq = pdev->irq;

    // 6. 申请I/O内存区域
    ret = request_mem_region(my_dev->mmio_base, my_dev->mmio_len, "example_driver");
    if (!ret) {
        ret = -EBUSY;
        goto err_free_priv;
    }

    // 7. 将私有数据指针挂接到pci_dev上
    pci_set_drvdata(pdev, my_dev);

    // 8. ... 其他特定于设备的初始化(映射BAR、注册中断、创建设备文件等)

    return 0; // 成功

err_free_priv:
    kfree(my_dev);
err_disable:
    pci_disable_device(pdev);
    return ret;
}

4、"一个 PCI 驱动通过 pci_register_driver() 向内核注册时" 这里的一个PCI驱动是指什么 一个ko文件插入吗?


struct pci_driver:负责启动、结束流程

struct pci_driver属于driver层,驱动register使用

--去查找表单,算是先行,pci_driver先去匹配白名单,触发 PCI 总线匹配

0、结构体:包含初始化probe、注销remove等功能点

struct pci_driver 是Linux内核中所有PCI设备驱动必须实现的核心结构体。

struct pci_driver是 PCI 子系统的特定设备结构体,定义在 include/linux/pci.h 中,它内嵌了一个 struct device,并添加了 PCI 特有的字段;

c 复制代码
struct pci_driver {
    struct list_head node;
    const char *name;
    const struct pci_device_id *id_table;   // 设备ID匹配表(关键字段)
    int  (*probe)(struct pci_dev *dev, const struct pci_device_id *id); // 探测函数(关键字段)
    void (*remove)(struct pci_dev *dev);    // 移除函数(关键字段)
    int  (*suspend)(struct pci_dev *dev, pm_message_t state);
    int  (*resume)(struct pci_dev *dev);
    void (*shutdown)(struct pci_dev *dev);
    int (*sriov_configure)(struct pci_dev *dev, int num_vfs);
    const struct pci_error_handlers *err_handler;
    struct device_driver driver;
    struct pci_dynids dynids;
    // ... 其他字段 ...
};

上面信息不满意,让其重新生成:

c 复制代码
struct pci_driver {
    /* --- 链表与基本标识 --- */
    struct list_head node;              /* 用于驱动全局链表 */
    const char *name;                   /* 驱动名称,会在 /sys/bus/pci/drivers/ 下创建同名目录 */

    /* --- 设备匹配表(核心!)--- */
    // id_table 是 probe 函数被调用的前提,它用于匹配内核中的所有 pci_dev 结构
    const struct pci_device_id *id_table;   /* 驱动支持的设备ID列表,必须非空,probe才能被调用 */

    /* --- 生命周期与热插拔回调 --- */
    int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
    void (*remove)(struct pci_dev *dev);                /* 设备移除时的回调 */
    void (*shutdown)(struct pci_dev *dev);              /* 系统关机/重启时的回调 */

    /* --- 电源管理回调 --- */
    int (*suspend)(struct pci_dev *dev, pm_message_t state);
    int (*suspend_late)(struct pci_dev *dev, pm_message_t state);
    int (*resume_early)(struct pci_dev *dev);
    int (*resume)(struct pci_dev *dev);                 /* 设备唤醒时的回调 */

    /* --- 高级功能支持 --- */
    int (*sriov_configure)(struct pci_dev *dev, int num_vfs);  /* 配置SRIOV虚拟功能 */
    const struct pci_error_handlers *err_handler;              /* 高级错误处理 */

    /* --- 设备模型集成 --- */
    struct device_driver driver;        /* 内嵌的通用驱动结构体 */
    struct pci_dynids dynids;           /* 动态添加的设备ID列表,用于驱动在运行时绑定新设备 */
};

-- todo:对应函数作用待深入

c 复制代码
// 1. 定义驱动支持的设备ID表
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_MYDEV, PCI_DEVICE_ID_MYDEV) },
    { 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids); // 导出到用户空间,以便modprobe自动加载

// 2. 定义pci_driver结构体变量,指定关键回调函数
static struct pci_driver my_pci_driver = {
    .name     = "my_pci_driver",         // 设置驱动名称
    .id_table = my_pci_ids,              // 绑定ID表
    .probe    = my_pci_probe,            // 设置probe函数
    .remove   = my_pci_remove,           // 设置remove函数
};

// 3. 在模块初始化函数中注册驱动
static int __init my_module_init(void)
{
    return pci_register_driver(&my_pci_driver);
}
module_init(my_module_init);

// 4. 在模块退出函数中注销驱动
static void __exit my_module_exit(void)
{
    pci_unregister_driver(&my_pci_driver);
}
module_exit(my_module_exit);

1、pci_register_driver(&xx_pci_driver)

pci_register_driver()


struct class

struct class 在 Linux 内核中,struct class 是设备模型的核心结构体之一,用于表示一类设备(如 "gpio"、"input"、"net" 等)


struct device

struct device 是内核设备模型的通用部分 ,定义在 include/linux/device.h 中,包含所有设备共有的属性:

设备名称、父设备、总线类型

驱动私有数据指针 (driver_data)

设备号 (dev_t)

电源管理信息

引用计数 (通过 kobject)

释放回调 (release)


struct pci_dev

struct pci_dev 是 PCI 子系统的特定设备结构体,定义在 include/linux/pci.h 中,它内嵌了一个 struct device,并添加了 PCI 特有的字段:

Vendor ID / Device ID / Class、BAR 资源 (resource\[\])、中断号 (irq)、PCIe 能力标志、总线拓扑信息 (bus, devfn)

0、结构体

c 复制代码
Linux 内核(基于 6.x 版本)中 struct pci_dev 和 struct pci_driver 的完整代码定义:

struct pci_dev {
    /* --- 链接与总线拓扑 --- */
    struct list_head bus_list;          /* 在所属总线设备列表中的节点 */
    struct pci_bus *bus;                /* 指向设备所在的PCI总线 */
    struct pci_bus *subordinate;        /* 若为桥设备,指向其下级总线 */

    /* --- 设备标识(来自配置空间) --- */
    unsigned int devfn;                 /* 设备功能号(高5位设备号,低3位功能号) */
    unsigned short vendor;              /* 厂商ID */
    unsigned short device;              /* 设备ID */
    unsigned short subsystem_vendor;    /* 子系统厂商ID */
    unsigned short subsystem_device;    /* 子系统设备ID */
    unsigned int class;                 /* 设备类别(基类/子类/接口) */
    u8 revision;                        /* 硬件修订版本号 */
    u8 hdr_type;                        /* PCI配置空间头部类型 */
    u8 pcie_cap;                        /* PCIe能力结构偏移 */
    u8 pcie_type:4;                     /* PCIe设备/端口类型 */
    u8 pcie_mpss:3;                     /* 支持的PCIe最大有效负载大小 */

    /* --- 中断资源 --- */
    unsigned int irq;                   /* 分配的中断号 */
    u8 pin;                                 /* 使用的中断引脚(INTA# ~ INTD#) */

    /* --- 内存与I/O资源(BAR)--- */
    struct resource resource[DEVICE_COUNT_RESOURCE];   /* I/O和内存区域 + 扩展ROM */
    u8 rom_base_reg;                    /* ROM基地址寄存器在配置空间中的偏移 */

    /* --- 电源管理 --- */
    pci_power_t current_state;          /* 当前电源状态(D0/D1/D2/D3) */
    int pm_cap;                         /* 电源管理能力结构偏移 */
    unsigned int pme_support:5;         /* PME#支持的电源状态掩码 */
    unsigned int pme_interrupt:1;       /* PME#中断标志 */
    unsigned int d1_support:1;          /* 是否支持D1电源状态 */
    unsigned int d2_support:1;          /* 是否支持D2电源状态 */
    unsigned int d3cold_allowed:1;      /* 是否允许进入D3cold状态 */
    unsigned int d3_delay;              /* D3→D0转换延迟(毫秒) */
    unsigned int d3cold_delay;          /* D3cold→D0转换延迟(毫秒) */

    /* --- 驱动绑定 --- */
    struct pci_driver *driver;          /* 指向当前绑定的驱动 */

    /* --- DMA 配置 --- */
    u64 dma_mask;                       /* DMA可寻址的地址掩码 */
    struct device_dma_parameters dma_parms;   /* DMA参数(对齐、边界等) */

    /* --- 错误处理与状态 --- */
    pci_channel_state_t error_state;    /* 当前连接状态(正常/丢失/需要复位) */

    /* --- 设备模型集成 --- */
    struct device dev;                  /* 内嵌的通用设备结构体,用于sysfs/电源管理/引用计数 */
    int cfg_size;                           /* 配置空间的大小(以字节为单位) */
    /* 注:此内嵌device即包含了 driver_data 字段,通过 pci_set_drvdata/get 访问 */

#ifdef CONFIG_PCIEASPM
    struct pcie_link_state *link_state; /* PCIe链路状态(ASPM) */
#endif
};
c 复制代码
struct pci_dev = 通用设备 (struct device) + PCI 额外信息

struct pci_dev {
    // PCI 特有字段开始
    unsigned int devfn;
    unsigned short vendor;
    unsigned short device;
    unsigned int irq;
    struct resource resource[DEVICE_COUNT_RESOURCE];
    // ...

    struct device dev;   // 内嵌的通用设备结构体    
    // ... 更多 PCI 特有字段
};

1、提问:struct xx_device 的设备信息为什么要挂在pdev中?


2、提问:struct xx_device 的设备信息怎么挂在pdev中?

struct pci_dev *pdev 是 pci_set_drvdata(pdev, struct ev_device *ev_dev)

-- struct xx_device 是挂到pdev中结构体!struct xx_device是驱动开发者自定义的私有数据结构体,用于保存与具体设备实例相关的所有信息和资源。

-- 它通常被分配在 probe 函数中,并通过 pci_set_drvdata 挂载到 struct pci_dev 上。

-- dev_set_drvdata(&pdev->dev, data)

-- 挂表的时候:pci_dev - xx_device 一一对应;


3、pci_dev 中为什么要包含 pci_driver?


struct cdev

0、结构体

在内核代码中,struct cdev 的定义十分简洁,但其内容却是驱动开发者必须透彻理解的核心。以下是它在 include/linux/cdev.h 中的完整定义:

c 复制代码
struct cdev {
    struct kobject kobj;                /* 内嵌的内核对象,用于设备模型管理 */
    struct module *owner;               /* 指向实现驱动的模块,通常是 THIS_MODULE */
    const struct file_operations *ops;  /* 指向设备操作函数表,这是驱动的核心 */
    struct list_head list;              /* 用于维护设备号与inode的链表 */
    dev_t dev;                          /* 设备号,由主设备号和次设备号构成 */
    unsigned int count;                 /* 该字符设备所支持的次设备号数量 */
} __randomize_layout;                  /* 一个编译器指令,随机化结构体布局以增强安全性 */
c 复制代码
内核提供了一组专用函数来创建和管理 cdev 结构体。

cdev_init(cdev, fops)
-- 初始化一个已分配的 cdev 结构体,将它与 file_operations 函数表绑定,并为 list 和 kobj 成员做好准备工作。

cdev_alloc(void)
-- 动态地在堆上分配一个 cdev 结构体的内存并进行初始化,与 cdev_init 功能类似,但在堆上而非栈上。

cdev_add(cdev, dev, count)
-- 注册设备到内核,使系统知道cdev已就绪。它会将dev和count填入结构体,并在全局的cdev_map中建立索引,之后应用层就能找到该设备。

cdev_del(cdev)
-- 从系统中注销设备,并释放 cdev 结构体占用的内存。

关于 cdev_init 与 cdev_alloc 的选择:
核心区别在于内存分配:cdev_alloc 负责分配内存,cdev_init 负责初始化。
cdev_alloc():动态分配内存,适用于需要在运行时动态创建设备的场景。
cdev_init():对已分配的内存进行初始化。如果你已经在栈上定义了 struct cdev my_cdev,则应使用此函数。
重要注意事项:cdev_alloc() 内部已包含了必要的初始化工作,因此不应再对同一个指针调用 cdev_init(),否则可能会导致内存泄漏或行为异常.

1、struct cdev 与 struct pci_dev的区别,pci_dev 不就表示这个设备了吗,为什么还要一个cdev?

struct pci_dev 描述的是"硬件设备本身",而 struct cdev 描述的是 "该硬件设备向用户空间提供的操作接口"。

struct pci_dev 是硬件对象,struct cdev是软件对象

pci_dev 是内核在扫描 PCI 总线时为 每一个硬件设备建立的数据结构,它包含了该设备的 配置空间信息(厂商 ID、设备 ID)、BAR 资源(I/O 端口、内存地址)、中断号 等硬件相关的信息。

cdev 是一个纯粹的软件对象 ,它只关心 如何将硬件操作暴露给用户程序 。它绑定了 file_operations(open/read/write 等函数),并关联一个设备号(dev_t)

例子:一个 PCI 接口的 GPIO 卡。驱动需要做两件事:

-- 通过 pci_dev 找到卡的 BAR 地址,映射寄存器,使能设备,申请中断。

-- 创建 cdev,绑定 open/read/write 函数,让用户程序可以读写 GPIO 引脚。

-- 缺少 pci_dev 就无法操作硬件;缺少 cdev 用户程序就无法访问。两者缺一不可。


2、一个 pci_dev 可以对应多个 cdev

有些 PCI 设备提供多种功能,例如一个多串口 PCI 卡(比如 4 个串口)。驱动可以:

一个 pci_dev 结构体代表这块 PCI 卡本身。

创建 4 个 cdev,分别对应 /dev/ttyS0、/dev/ttyS1、/dev/ttyS2、/dev/ttyS3。

每个 cdev 有自己的 file_operations,但都通过私有数据指向同一个 pci_dev,以便访问共享的硬件资源(如全局中断状态寄存器)。

此时,pci_dev 是硬件底座,cdev 是接口插槽。

即使没有 cdev,pci_dev 依然存在(例如一个网卡驱动可能完全不用字符设备,而是注册 net_device)。


3、pci_dev 内嵌了 struct device,而 cdev 与之无关

你可能会注意到,struct pci_dev 内部包含了一个 struct device dev,而 cdev 却没有包含 pci_dev。这是因为:

-- device 是设备模型的通用"基类",所有总线设备都必须继承它,以便纳入统一的设备树和电源管理。

-- cdev 是纯软件接口 ,它可以通过 container_of 技巧 嵌入到驱动的私有数据结构 中,而这个私有数据结构可以通过 dev_set_drvdata 挂载到 pci_dev->dev 上。因此,从 cdev 可以反向找到 pci_dev,但反过来不行(一个 pci_dev 可能有多个 cdev,也可能没有)。

典型的数据关系:

c 复制代码
// cdev 是纯软件接口,它可以通过 container_of 技巧嵌入到驱动的私有数据结构中
// 从 cdev 可以反向找到 pci_dev,但反过来不行(一个 pci_dev 可能有多个 cdev,也可能没有)。
struct my_priv {   // 私有的数据结构,肯定要包含pci_dev、cdev的信息
    struct pci_dev *pdev;     // 反向指针
    struct cdev cdev;         // 嵌入 cdev
    ...
};

// struct pci_dev 是硬件对象,struct cdev是软件对象
probe() {
    struct my_priv *priv;
    cdev_init(&priv->cdev, &fops);
    cdev_add(&priv->cdev, dev_num, 1);
    dev_set_drvdata(&pdev->dev, priv);   // 将 priv 挂到 pdev->dev 上
}

// 当用户打开 /dev/mydev 时,open 函数通过 container_of(inode->i_cdev, struct my_priv, cdev) 获得 priv,
// 进而通过 priv->pdev 访问硬件。

struct file_operations :函数指针的集合

0、结构体

struct file_operations 是 Linux 内核中的一个核心结构体,它在用户空间的系统调用和驱动程序的硬件操作之间,建立起了一座关键的桥梁。

这个结构体本质上是一个函数指针的集合 ,被定义在 <linux/fs.h> 头文件中。

它为一个打开的文件定义了一套"方法"或"操作",每个成员都对应一个具体的系统调用。

当你在应用程序中调用 read、write 时,内核最终会通过这个结构体找到并执行驱动中对应的函数。

下面是一个典型的结构体定义,展示了其核心成员:

c 复制代码
struct file_operations {
    struct module *owner;          // 拥有该结构的模块指针,防止模块在使用时被卸载
    loff_t (*llseek) (struct file *, loff_t, int);        // 修改文件读写位置
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);       // 从设备读取数据
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); // 向设备写入数据
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); // 异步读
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); // 异步写
    int (*iterate) (struct file *, struct dir_context *);     // 读取目录(非设备用)
    unsigned int (*poll) (struct file *, struct poll_table_struct *); // 查询设备状态(select/poll)
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // 设备控制命令(无大内核锁版本)
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);   // 兼容32位应用的ioctl
    int (*mmap) (struct file *, struct vm_area_struct *);     // 将设备内存映射到用户空间
    int (*open) (struct inode *, struct file *);             // 打开设备
    int (*release) (struct inode *, struct file *);          // 关闭设备
    int (*fsync) (struct file *, loff_t, loff_t, int datasync); // 刷新设备缓冲区(同步)
    int (*fasync) (int, struct file *, int);                 // 异步通知
    int (*lock) (struct file *, int, struct file_lock *);    // 文件锁
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    // ... 一些更现代或非标准的内核扩展可能包含更多成员
};

-- 深入分析:deepseek - file_operations 的结构体内容

c 复制代码
static const struct file_operations my_fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .read    = my_read,
    .write   = my_write,
    .release = my_release,
    // 不需要的成员可以不写,默认为 NULL
};

struct my_priv {
    struct cdev cdev;          // 嵌入 cdev
    // 其他设备资源...
};

1、完整调用流程(PCI 驱动的 probe 中)

c 复制代码
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct my_priv *priv;
    dev_t dev_num;
    int ret;

    // 1. 分配私有结构体(包含 cdev)
    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    priv->pdev = pdev;

    // 2. 使能 PCI 设备、申请资源、映射寄存器等...
    // ...

    // 3. 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "mydev");
    if (ret)
        goto err_free;

    // 4. 初始化 cdev 并绑定 file_operations
    cdev_init(&priv->cdev, &my_fops);
    priv->cdev.owner = THIS_MODULE;

    // 5. 注册 cdev
    ret = cdev_add(&priv->cdev, dev_num, 1);
    if (ret)
        goto err_unregister_region;

    // 6. 可选:创建设备类及 /dev 节点
    // ...

    // 7. 保存私有数据到 pdev
    pci_set_drvdata(pdev, priv);
    return 0;

err_unregister_region:
    unregister_chrdev_region(dev_num, 1);
err_free:
    kfree(priv);
    return ret;
}

初始化顺序:实现 fops → 分配 cdev → cdev_init → 分配设备号 → cdev_add → 用户空间可访问。

清理顺序:cdev_del → unregister_chrdev_region


3、file_operations 中 用户调用的函数 内核怎么通过注册的表 知道是要调用哪个函数的

-- 待解锁


4、THIS_MODULE

相关推荐
遇印记1 小时前
软考知识点(局域网基础)
运维·服务器·局域网
lulu12165440781 小时前
Codex Computer Use 深度分析:AI桌面自动化的技术突破与行业影响
java·运维·人工智能·自动化·ai编程
難釋懷1 小时前
Nginx-CA 签名
服务器·nginx·ssl
雪霁清寒2 小时前
麒麟V10用MobaXterm远程连接SSH偶尔卡顿的问题
linux·ssh
ylscode2 小时前
Linux CIFSwitch 内核新漏洞允许攻击者获得 root 权限
linux·运维·服务器
Plastic garden2 小时前
Docker compose ruoyi示例
运维·docker·容器
qq_452396232 小时前
第十四篇:《Docker Swarm 生产实践:堆栈部署与配置管理》
运维·docker·容器
qq_452396232 小时前
第十三篇:《Docker Swarm 集群基础》
运维·docker·容器
诸葛务农2 小时前
共沸脱水技术及其在光刻胶用PGMEA纯化中的应用(中)
linux·数据库·人工智能