文章目录
-
- [#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
