「Efficient Android Threading 笔记」- C1 Android Components and the Need for Multiprocessing Android RecyclerView Android Socket Programming Supporting Multiple Screens Make a Reusable UI in Android App Development 如何在 Android Studio 中包含 *.so library,并使用库中定义的方法? 使用 SpannableString 格式化字符串,实现前景色、下划线、超链接、图文混排等 如何使用 bound service 完成进程间通信? 创建自定义视图 Creating custom views 通过 Android Theme & Style 定制应用的样式 「译」Android ViewPropertyAnimator 介绍 Android Animation Interpolator - Android 动画插值器源码笔记 「译」Android Animation in Honeycomb by Chet Haase(Android 3.0系统中的动画机制) 从 Android Sample ApiDemos 中学习 android.animation API 的用法 如何学习 Android Animation? 如何实现 Android ListView「上拉加载更多」? 「译」向Big Nerd Ranch提问:为什么Fragment在Android App开发中非常重要? 分类整理我在 SegmentFault 上针对某些问题作的回答 Android Servcie 后台服务总结笔记 如何在Android设备旋转时暂存数据以保护当前的交互状态? Android Message Handler 消息处理机制总结笔记 如何获取FragmentTabHost中指定标签页的Fragment? Fragment子类必须包含一个public无参构造器 如何更新及替换ViewPager中的Fragment? 如何使用Android UI Fragment开发“列表-详情”界面? 一个Android音频文本同步的英文有声读物App的开发过程 「Android编程权威指南笔记」Android应用本地化 通过jfeinstein10/SlidingMenu实现Android侧滑菜单 为Ubuntu14.04部署Android App的Eclipse开发环境 「Android编程权威指南笔记」使用ListFragment显示列表 「Android编程权威指南笔记」SDK版本与兼容 「Android编程权威指南笔记」Android布局和组件 「Android编程权威指南笔记」UI Fragment 「Android编程权威指南笔记」Activity 第一次开发iOS App和Android的对比总结笔记 「App Training笔记」创建第一个应用 「App Training笔记」开发入门训练大纲 Android APP - 从远程FTP服务器下载文件到本地

字符设备文件 & 设备文件节点的生成及打开

2013年12月18日

设备文件节点的内核数据结构定义:struct inode

虚拟文件系统中的每个文件都关联到一个inode,用于管理文件的属性。对单个文件,可能会有多个表示文件描述符的 struct file,但它们都指向惟一的 struct inode. 用户空间打开设备文件时,内核会寻找关联的 inode, 利用其中记录的设备号等关键信息完成后续的操作。 [ LDD3-3.一些重要的数据结构.inode结构 ], [ LKA-6.3.1 ]
struct inode 包含了大量与文件有关的信息,而与设备驱动程序有关的成员:

kernel/include/linux/fs.h
struct inode {
    dev_t i_rdev;                   //真正的设备号
    struct file_operations *i_fop;  //被赋予通用的设备文件操作函数集
    union {
        struct cdev *i_cdev;        //指向特定的字符设备结构的指针
    };
}

设备文件节点的生成

在Linux系统下,设备文件是特殊的文件类型,它提供的机制使得,用户空间可以使用驱动程序定义的接口。如果驱动程序只为内核中的其它模块提供服务,则没有必要生成设备文件。
设备文件的创建依赖于“设备文件名”和“设备号”,有2种方法:

  • 设备节点的静态生成 [ ILDD-2.6 ]
    使用linux系统命令:

      mknod [-m MODE] NAME TYPE MAJOR MINOR
      # mknod /dev/demodev c 2 0
      # ls -l /dev/demodev
      crw-rw-rw- root     root       2,   0 2013-12-10 06:26 demodev
    
  • 设备节点的动态生成
    class_create(owner, name);
    kernel/drivers/base/core.c/device_create();
    调用device_create()向系统添加设备,属于“在sysfs文件系统中建立系统硬件拓扑关系结构图“四种情况中的后两种。[ ILDD-9.4-P354 ], [ ILDD-9.3.4-P342]
    device_create()产生uevent, udevd监听uevent后根据相关规则创建设备节点。 [ ELDD-5.2.1-P86], [ ELDD-4.3.1 udev ]

以上2种方法之一,会生成2个“对象”:

  • /dev目录下的设备文件 demodev;
  • struct inode 对象,用于在内核空间表示设备文件;

/dev/demodev 与新生成的 inode 之间的关联由文件系统管理;设备文件节点的生成涉及VFS和特定文件系统的技术细节,从驱动程序开发的角度,只需关注与驱动程序相关联的部分。
inode对象被创建后,会被初始化: [ ILDD-2.6 ], [ LKA-6.3.2 ]

kernel/fs/inode.c
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
    inode->i_mode = mode;
    if (S_ISCHR(mode)) {
        inode->i_fop = &def_chr_fops; //字符设备的inode被赋予字符设备通用操作
        inode->i_rdev = rdev;
}

至此,字符设备文件的 struct inode 已经具有如下数据:

  • 设备号;
  • 指向通用的字符设备文件操作函数集struct file_operations def_chr_fops (kernel/fs/char_dev.c).
    def_chr_fops主要定义了 .open = chrdev_open;当用户空间调用open(“/dev/demodev”, …)时,chrdev_open() 会被调用,它根据inode中记录的设备号查找 struct cdev(由对应的字符设备驱动程序向内核注册),然后用户空间就可以调用cdev中的file_operations,从而控制设备。详见下述“字符设备文件的打开“。

字符设备文件的打开

用户空间调用open后,引发的系统调用所做的事情:
步骤[ ILDD-2.7 ]:

  1. 通过系统调用 sys_open() 进入内核空间;
    1. 在内核空间中主要由 kernel/fs/open.c/do_sys_open 发起设备文件的打开操作,首先获取设备文件 /dev/demodev 的 inode (根据设备文件名查找节点,如同设备文件节点的生成,也涉及文件系统的技术细节,暂且不考);
    2. 然后调用 inode 的open函数 inode->i_fop->open(),对于字符设备而言,实际执行的是 chrdev_open(),(参考“设备文件节点的生成”一节中对字符设备 inode 的分析);
    3. 每次打开都会创建一个未使用的文件描述符fd,一个新的struct file对象 filp,用于跟踪这次操作;内核进程会维护一个文件描述符表,fd 作为索引值,把 filp 赋值给该表,用于关联 filp 和 fd;

      struct file 中与设备驱动相关的成员:
       struct file_operations    *f_op; 文件操作函数集接口;
       atomic_long_t f_count; file的使用计数;
       unsigned int f_flags; 设备文件的打开模式;
       void *private_data; 记录设备驱动程序的自定义数据,方便数据传递;
      
  2. chrdev_open() 通过 inode中记录的设备号(inode->i_rdev)在字符设备数据库(cdev_map)中查找对应的字符设备 cdev(参考“字符设备的注册”);
  3. 查找成功后,把 inode的 i_cdev 成员指向该cdev,这样再次打开该设备文件节点时,可以通过成员i_cdev 直接使用该cdev,而不必查找 cdev_map(有些书中称之为“缓存”);
  4. 把 cdev 的 file_operations 与 filp 关联(filp->f_op = inode->i_cdev->ops),并执行 filp->f_op->open(), 即 cdev 的 open 函数;
  5. 将 fd 返回到用户空间。

至此,用户的 open 终于调用了驱动的 open,并且后续的 read、write、ioctl 等函数的调用,可以通过 fd 获取设备文件对应的 filp,进而调用 f_op 中实现的设备驱动函数。
结合关键数据结构,观察大体的执行流程图 [ ILDD-2.7-图2-10 ], [ LKA-6.4.2-图6-7 ]
《ILDD 图2-10》非常经典地展示了设备文件打开的代码执行流程、内核数据结构之间的关系。

ILDD-2.7-Figure2.10- Flowchart of opening character device node

LKA-6.4.2-Figure6.7- Relations between data structures for the representation character devices

chrdev_open()是用于字符设备的标准(或通用的或基本的)打开操作,在调用顺序上,它处于用户open和特定设备驱动open之间。这种“分层”应该是考虑了字符设备的多样性,每个特定的设备都需要一组独立、自定义的操作函数。正是 chrdev_open() 为用户空间提供了打开字符设备的一致方式。 [ LKA-6.3.3 ], [ LKA-6.4.2 ]

字符设备文件的关闭

用户空间的 close 最终会执行 file->f_op->release(inode, file), 即 cdev 的 release 函数。[ ILDD-2.7-P83]

编辑历史

  • 2013-12-18 初稿 by li2 上海闸北
  • 2014-01-07 使用markdown编辑格式

知识共享许可协议
li2的博客WeiYi.Li 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议进行许可。
© 2011-2022. All rights reserved by WeiYi.Li. Powerd by Jekyll & LinAnYa's Theme