当前位置: 首页 > news >正文

【嵌入式linux学习】01_1应用层open怎么到硬件控制

【嵌入式linux学习】:从应用层的open到led闪烁

这个blog讲讲为什么我在应用层写一句简单的open("/dev/led", O_RDWR),电路板上的 LED 灯就能亮起来?

整个过程可以看作是一次跨越三个“世界”的通信:

  1. 用户空间 (User Space):APP 只认识文件路径(字符串,如"/dev/led")。
  2. 内核空间 (Kernel Space):内核只认识数据结构inode,cdev,file)。
  3. 硬件世界 (Hardware):硬件只认识物理电信号(高低电平)。

我们的目标,就是要把“字符串”变成“电信号”。

总结一下

1️⃣通过open,产生系统调用,到达内核层_>通过路径查找(Path Walk)机制,逐层解析目录,找到相应结构体inode——>识别到是一个字符设备——>内核根据inode->i_rdev(设备号),在内核的cdev_map散列表中查找对应的struct cdev对象 ——>当chrdev_open找到cdev后,它做了一个赋值操作:filp->f_op = cdev->ops;(这里的cdev->ops就是你写的my_fops

在这一行代码执行之前,这个文件只是一个普通的、冷冰冰的设备节点,内核只知道它有个设备号。

在这一行代码执行之后,这个struct file就“活”了。它绑定到了你的驱动上。

关键点:不仅是open,用户层对这个文件描述符 (fd) 调用的readwriteioctl,全都会绕过通用逻辑,直接跳转到你写的my_readmy_write函数里去。

2️⃣找到设备后,通过物理地址和虚拟地址映射,控制寄存器,从而对LED进行控制

文章目录

    • 【嵌入式linux学习】:从应用层的open到led闪烁
    • 第一阶段: (VFS 层)
      • 第一步:从字符串到 Inode (VFS 的工作)
      • 第二步:识别“我是个字符设备”
      • 第三步:默认处理函数 `chrdev_open`
      • 第四步:偷天换日 (最关键的一步)
      • 具体代码
    • 第二阶段:驱动层
      • 1. 次设备号 (Minor Number) 的作用
      • 2. 代码实现逻辑
    • 第三阶段:硬件层
      • 1. 物理地址与虚拟地址
      • 2. 寄存器操作
    • 总结

第一阶段: (VFS 层)

当 APP 调用open时,系统调用触发中断,CPU 陷入内核态,执行sys_open。内核的第一件事是:根据路径找到“负责这件事的人”

第一步:从字符串到 Inode (VFS 的工作)

APP 传入的是一个字符串路径(如/dev/hello)。内核中的sys_open无法直接操作字符串,它必须把这个路径解析成内核能理解的对象。

  • 内核通过路径查找(Path Walk)机制,逐层解析目录。
  • 最终找到/dev/hello对应的目录项结构体dentry
  • 每个dentry都指向一个inode(索引节点)

关键点:inode是文件(或设备节点)在文件系统中的静态表示。无论被打开多少次,inode只有一个。它里面存储了最关键的信息:设备号(Major/Minor)

第二步:识别“我是个字符设备”

找到inode后,内核检查inode->i_mode

  • 如果是普通文件,按文件系统逻辑处理。
  • 如果是字符设备文件 (S_IFCHR),内核会意识到:“这不是普通文件,我需要找到对应的驱动。”

此时,inode结构体中的i_rdev字段(包含主/次设备号)成为了连接驱动的钥匙。

第三步:默认处理函数chrdev_open

inode被初始化时(通常是mknoddevice_create时),字符设备的i_fop默认被指向了一个通用的内核函数:def_chr_fops

  • 当 VFS 尝试打开这个文件时,它首先调用的是这个默认的def_chr_fops->open,也就是chrdev_open

第四步:偷天换日 (最关键的一步)

chrdev_open函数内部,发生了一次“移花接木”:

  1. 内核根据inode->i_rdev(设备号),在内核的cdev_map散列表中查找对应的struct cdev对象。
  2. 找到cdev后,内核将cdev中保存的file_operations(也就是你写的驱动代码my_fops)拿出来。
  3. 替换:将当前文件对象 (struct file) 的f_op指针,修改为你写的驱动fops
  4. 调用:最后,手动调用你写的.open函数,并将inodefile传给你。

具体代码

// 伪代码:内核层处理 open 系统调用的简化逻辑intsys_open(constchar*filename,intflags){// 1. 路径解析:通过 filename 找到 inodestructinode*inode=path_lookup(filename);// 2. 创建 file 结构体(代表本次会话)structfile*filp=alloc_file();filp->f_op=inode->i_fop;// 此时还是默认的 def_chr_fops// 3. 调用 open// 如果是字符设备,这里调用的是 chrdev_openif(filp->f_op->open){returnfilp->f_op->open(inode,filp);}}// 字符设备通用的 open 函数intchrdev_open(structinode*inode,structfile*filp){// 1. 根据 inode->i_rdev (设备号) 找到 cdevstructcdev*p=inode->i_cdev;if(!p){kobj=kobj_lookup(cdev_map,inode->i_rdev,...);p=container_of(kobj,structcdev,kobj);}// 2. 关键:将 file 的操作集替换为驱动定义的 fopsfilp->f_op=fops_get(p->ops);// 3. 真正调用你自己写的驱动 open 函数if(filp->f_op->open){returnfilp->f_op->open(inode,filp);}}

第二阶段:驱动层

现在,执行流终于进入了你写的驱动代码中的.open函数。

intmy_driver_open(structinode*inode,structfile*file);

这个时候,最关键的问题来了:驱动怎么知道你要打开具体的哪一个硬件?

假设你的驱动管理了 4 个 LED 灯,APP 打开/dev/led0,驱动怎么保证不去操作 LED1?

1. 次设备号 (Minor Number) 的作用

答案就在inode参数里。

  • 主设备号:决定了用哪个驱动程序(找对类)。
  • 次设备号:决定了用该驱动下的哪个具体设备(找对人)。

2. 代码实现逻辑

在驱动的open函数中,通常有以下标准操作:

structmy_led_dev{void__iomem*reg_base;// 寄存器基地址intgpio_pin;// 具体引脚号};// 假设我们有多个 LED 设备对象structmy_led_devled_devs[4];intmy_driver_open(structinode*inode,structfile*file){// 1. 获取次设备号intminor=iminor(inode);// 2. 根据次设备号,找到对应的硬件描述结构体structmy_led_dev*dev=&led_devs[minor];// 3. 【最佳实践】将私有数据挂在 file 结构体上// 这样以后 read/write 函数直接从 file->private_data 取,不用再看 inodefile->private_data=dev;return0;// 成功}

第三阶段:硬件层

找到了代表 LED0 的结构体led_devs[0],最后一步就是让硬件动起来。

1. 物理地址与虚拟地址

CPU 不能直接访问物理地址(如0x10008000),Linux 内核运行在虚拟地址空间。 在驱动初始化阶段(module_initprobe),我们必须做一次映射:

// 将物理地址映射为内核可操作的虚拟地址dev->reg_base=ioremap(PHYS_ADDR_LED_CTRL,SIZE);

2. 寄存器操作

回到open函数,当我们拿到了dev结构体,其实就拿到了那个映射好的reg_base指针。 虽然open通常只做初始化(如上电),但为了演示,假设我们在 open 时点亮它:

// 读-改-写 操作u32 val=ioread32(dev->reg_base);// 读取硬件寄存器当前状态val|=(1<<dev->gpio_pin);// 修改位iowrite32(val,dev->reg_base);// 写回硬件

iowrite32执行的那一瞬间,CPU 通过总线向物理内存地址发送了电信号,硬件控制器接收到信号,驱动了 MOS 管,电流导通,LED 灯亮了!

总结

用户态调用open("/dev/xxx")后触发系统调用,内核通过路径查找定位到对应的 inode, 发现是字符设备后,根据inode->i_rdevcdev_map中找到对应的struct cdev, 并将其中的file_operations绑定到当前文件对象, 随后执行驱动实现的open函数。

实际的硬件操作通常在驱动的probe阶段完成寄存器映射,并在read/write/ioctl等文件操作中通过访问寄存器来控制硬件

如果把这个过程比作一次“寄快递”:

  1. APP (open):你在快递单上填了一个地址"/dev/led0"路径)。
  2. VFS (内核):快递公司根据地址查库,发现这是寄给“老王公司”(驱动程序)下的“0号员工”(次设备号)的。
  3. Driver (驱动):老王公司收到件,派单给 0 号员工。0 号员工查阅手册,按下了他办公桌上的开关(ioremap 后的虚拟地址)。
  4. Hardware (硬件):开关连接的电缆通电,终点的灯亮起。

http://www.gsyq.cn/news/1580140.html

相关文章:

  • 如何彻底解决Jupyter Notebook 7+ Tab键缩进失效:5个专业修复方案
  • Safety-DB实战:识别和修复10个常见Python包安全漏洞
  • 3步掌握biliTickerBuy:终极B站会员购智能抢票工具完整指南
  • ai编程的prompt
  • biliTickerBuy:从B站会员购抢票小白到高手的智能助手
  • Speedlify终极指南:如何高效构建持续性能监控系统?
  • 5分钟开启智慧物业新时代:e家宜业开源平台完整部署指南
  • 如何用4GB显存流畅运行SDXL模型:Fooocus低配置优化实战指南
  • 3分钟构建你的离线语音识别系统:Whisper.cpp终极指南
  • Scaffold-ETH 2:5分钟高效构建专业级以太坊应用的全栈开发框架
  • charset_normalizer:如何高效解决Python字符编码检测问题的完整方案
  • 如何在10分钟内构建完整回合制RPG游戏?Godot Open RPG终极指南
  • Anycubic i3 MEGA系列3D打印机固件升级终极指南
  • 华为OD机试真题精讲:石头剪刀布游戏(Python/Java/C++多语言实现)
  • LinkClump:浏览器批量操作链接的终极解决方案
  • biliTickerBuy终极指南:免费开源的B站会员购自动化抢票解决方案
  • PhysicsLayout最佳实践:在商业应用中优雅使用物理动画
  • SSD目标检测模型:从零到一掌握实时物体识别核心技术 [特殊字符]
  • 如何在64位Windows上运行16位程序:winevdm终极指南 [特殊字符]
  • Vim终极武器:YouCompleteMe智能代码补全完全实战指南
  • 生成word文档的腾讯元宝:AI导出鸭技术架构深度测评
  • 5分钟快速上手ML4W OS:打造现代化Hyprland桌面环境的终极指南
  • LeetcodeHot100(6)三数之和
  • 链表知识点以及习题
  • 2025_NIPS_Learning from Visual Observation via Offline Pretrained State-to-Go Transformer
  • AI 串联软件测试流水线
  • AI剧本杀局内玩法规范与设计
  • 前端手记(一):项目启动与前端任务拆分
  • 08 - 组织生命体:AI时代组织管理深度诊断试卷
  • 协作机器人选型的 6 个技术维度:重复定位精度、轴数、负载与防爆一文讲透