1. 本章小结
DRM 把 GPU 实现为设备文件。字符设备的三层角色,即驱动、设备实例与一次打开,在 DRM 中对应drm_driver、drm_device与drm_file:一个驱动管理多个设备,一个设备可被多次打开。open()经设备号找到设备与驱动,此后每次ioctl()都由drm_ioctl()在核心表与驱动私有表之间分派。
三个对象按粒度分工。drm_driver是编译期固定的只读表,记录能力位与驱动级回调;drm_device是每个设备实例一份的运行时状态,决定建立哪些节点,并让同一设备的所有文件共享一个address_space,为 CPU 映射提供偏移空间;drm_file是每次打开一份的客户端上下文,handle、同步对象与驱动私有状态都以它为单位隔离和回收。
访问控制分两层:文件权限决定能否打开节点,节点类型与drm_file上的认证、master 状态决定能调用哪些 ioctl。render 节点是无特权的渲染与计算入口,本书讨论的操作都经它进入内核。驱动接入 DRM,就是为这三个对象提供定义:描述表与操作表、内嵌drm_device的设备结构,以及以drm_dev_register()为可见边界的注册与注销路径。
设备、客户端与入口都已就位,ioctl 操作的缓冲区还是一个黑盒。下一章进入 GEM,讨论缓冲区对象的身份、引用与三类访问视图。
2. 本章依赖的其他子系统
DRM 框架层几乎完全建立在 VFS 与设备驱动模型之上。下表列出本章用到的内核子系统、关键入口及本书落点。
| 子系统 | 在本章何处用到 | 关键机制 / 入口 | 本书落点 |
|---|---|---|---|
| VFS 字符设备 | 主设备号 226 的注册、open 时替换操作表(2.2) | register_chrdev()、drm_stub_fops、replace_fops()、file->private_data | fs/char_dev.c、fs/open.c |
| VFS 文件生命周期 | drm_file何时创建与销毁,flush 与 release 的区别(2.4) | struct file引用计数、fops->flush/fops->release、fdinfo | fs/file_table.c、fs/proc/fd.c |
| 设备驱动模型 | 驱动加载、probe/remove、drm_device的分配与注册(2.2、2.6) | pci_driver、devm_drm_dev_alloc()、devres 与drmm_*托管资源 | drivers/base/、drivers/gpu/drm/drm_managed.c |
| sysfs 与 udev | 设备节点的命名、属组与权限,drmGetDevices2()聚合同一设备的节点(2.1、2.5) | drm_class、drm_devnode()生成dri/前缀、uevent | drivers/gpu/drm/drm_sysfs.c |
| 伪文件系统与 address_space | 每设备一个匿名 inode,所有文件共享其i_mapping(2.3) | simple_pin_fs()、f_mapping、unmap_mapping_range() | 附录 B.8 |
| 进程与凭据 | CAP_SYS_ADMIN决定初始认证与DRM_ROOT_ONLY,fd 经dup()/fork()/Unix socket 共享(2.4、2.5) | capable()、SCM_RIGHTS | 附录 B.1 |
| 并发与热插拔 | 设备拔出后阻止驱动继续访问硬件(2.6) | drm_dev_enter()/drm_dev_exit()基于 SRCU,drm_dev_unplug()中synchronize_srcu() | 附录 B.5 |
3. 延伸与拓展问题
下列问题沿本章用到的机制向相邻子系统延伸,每题附一个研究入口,供读者自行追踪。
3.1 VFS 与字符设备
占位操作表:DRM 以
register_chrdev()只为主设备号 226 注册了一张drm_stub_fops,真正的操作表在 open 时才由replace_fops()换入。为什么不直接为每个 minor 注册各自驱动的file_operations?replace_fops()换表时如何处理模块引用计数,换表之后f_op还能不能再变?(入口:fs/char_dev.c的chrdev_open()、include/linux/fs.h的replace_fops())一次 close 不等于一次 release:
dup()、fork()与SCM_RIGHTS传递的 fd 共享同一个struct file,只有最后一个引用消失时才会调用release,从而销毁drm_file。struct file的引用计数由谁增减?进程退出时内核如何逐个关闭它的 fd?(入口:fs/file_table.c的fput()、fs/file.c;本书附录 B.1)fdinfo:
drm_driver.show_fdinfo输出的显存与引擎统计出现在/proc/<pid>/fdinfo/<fd>中。procfs 如何把这个文件路由到具体驱动?drm-usage-stats规范约定了哪些键,gputop、nvtop一类工具如何据此统计每进程的 GPU 用量?(入口:fs/proc/fd.c、Documentation/gpu/drm-usage-stats.rst)
3.2 设备模型与用户空间设备管理
托管资源的两种生命期:驱动在 probe 中混用
devm_*与drmm_*。前者随struct device解绑释放,后者随drm_device最后一个引用释放。设备已被拔出、而某个进程仍持有打开的 fd 时,哪些资源必须用drmm_*管理才不会被提前释放?(入口:drivers/base/devres.c、drivers/gpu/drm/drm_managed.c)节点从哪里来:DRM 核心创建设备时并不设定
/dev/dri/renderD128的属组与权限位。设备节点由 devtmpfs 创建还是由 udev 创建?udev 规则如何把 render 节点授予render组,systemd-logind 又如何在会话切换时调整 card 节点的访问权限?(入口:drivers/base/devtmpfs.c、drm_devnode();systemd 的50-udev-default.rules)
3.3 内存映射
- 借来的 address_space:
drm_device的匿名 inode 从不缓存页,却借用了address_space的偏移坐标与i_mmap映射登记。unmap_mapping_range()如何经i_mmap区间树找到所有映射了某段偏移的 VMA?如果每个节点各用自己的字符设备 inode,会在哪一步失效?(入口:mm/memory.c的unmap_mapping_range();本书附录 B.8、第 3 章 CPU 视图)
3.4 并发、凭据与内核数据结构
热拔出的读侧保护:
drm_dev_enter()用 SRCU 读锁包住硬件访问,drm_dev_unplug()置位后以synchronize_srcu()等待所有读侧退出。这里为什么用 SRCU 而不用普通 RCU?读侧临界区内允许睡眠意味着什么?(入口:drivers/gpu/drm/drm_drv.c、kernel/rcu/srcutree.c;本书附录 B.5)认证的边界:
drm_file的authenticated初值取自capable(CAP_SYS_ADMIN)。在用户命名空间中,capable()与ns_capable()的判定有何不同,容器内的进程打开 primary 节点时会得到什么结果?render 节点免认证之后,隔离 GPU 访问的责任落到了哪一层?(入口:kernel/capability.c;本书附录 B.1)