字符设备特性:
1、字符读写:字符设备处理的是字节流数据
2、顺序访问:字符设备顺序访问,不支持随机读写
3、无缓冲区:字符设备通常是无缓冲区,读写一般都是实时的,也可以带缓冲,和底层实现有关
常见的字符设备:
串口,鼠标,键盘
字符设备使用
字符设备实现流程:
请参照内核源码中任意字符设备实现即好,主要分为以下三个部分
1、创建并注册字符设备,比如调用register_chrdev 函数
2、class 类创建 class_create
3、device 创建 device_create
后面两部分主要是创建sysfs 文件系统模型,以便于 mdev 机制 查询sys/class 中的dev 设备并调用mknod 自动创建dev 节点。
本文不做 mdev 机制和syfs 分析,这块留待后续进行
字符设备接口
源码实现文件路径
fs/char_dev.c
include/linux/fs.h
字符设备注册接口:
向char_device_struct 发起注册
指定设备号注册
register_chrdev_region(dev_t from, unsigned count,const char * name)
自动分配设备号
alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char name)
此两个接口底层都依赖于__register_chardev_region
向cdev_map 发起注册
cdev 结构体初始化,并绑定ops
cdev_init(struct cdev* cdev,const struct file_operationos* fops)
向cdev 注册到cdev_map 中的hash 表中
cdev_add(struct cdev*p ,dev_t dev,unsigned count)
字符设备卸载
其中 查找匹配 ,必须是主设备号 major ,baseminor ,minorct 一致
unregister_chardev_region(dev_t from,unsigned count)
unregister_chardev(unsigned int major,const char* name)
device 方式加入到cdev ,暂时未分析
cdev_device_add(struct cdev* cdev,struct device*dev)
字符设备管理结构分析
设备号含义和构成
设备号有主设备号和次设备号组成,主设备号表示某一个具体驱动,次设备号代表使用此驱动的各个设备
dev_t 表示设备号,高12bit 代表主设备号,低20 代表次设备好
问题:其中管理字符设备有两套结构进行管理,一个是char_dev_struct ,另外一个是kobj_map,其数据结构都是采用hash 结构进行管理,为什么需要两套呢,原因暂未分析,猜测可能和兼容有关
字符管理结构chardev,主要管理设备号的分配和使用,后续真实使用的都是cdev_map
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
字符设备管理结构 cdev_map,实现对cdev 的管理
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
字符设备结构cdev
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
字符设备和应用层的绑定
节点创建
1、使用mknod 创建节点,其中内部创建字符设备文件,inode 节点,完成一下内容绑定
inode->f_op = &def_chr_fops;
node->i_rdev = rdev;
用于调用vfs 虚拟文件系统层 open 时候 ,打开对应类型(字符)设备的ops,def_chr_fops 通过设备号信息(dev_t )在字符设备管理结构中找到具体设备的操作的fops
具体mknod分析流程参考一下博客,比较详细
open 函数调用流程如下
vfs_open
---- chrdev_open
----- cdev->ops->open
本文只分析 chrdev_open 函数的行为,从c 函数系统调用到 vfs_open 到 char_open 流程本次不做分析,可以以参考此博客:Linux中open命令实现原理以及源码分析-CSDN博客
chrdev_open 函数,代码如下,具体流程主要分为一下几个步骤
1、查看inode->i_cdev 节点是否挂载了cdev 设备,挂载了直接取出cdev->ops ,挂载到file->fop 上,调用 filp->f_op->open 真实字符设备打开函数,打开设备。如果inode->i_cdev 节点是null 则根据设备号i_rdev 在cdev_map 中找到对应的cdev 字符设备,并将其挂载到 i_devices 链表上,接着用cdev 中的ops 替换 filp 中的f_op ,并打开字符设备
/* * Called every time a character special file is opened */ static int chrdev_open(struct inode *inode, struct file *filp) { const struct file_operations *fops; struct cdev *p; struct cdev *new = NULL; int ret = 0; spin_lock(&cdev_lock); p = inode->i_cdev; if (!p) { struct kobject *kobj; int idx; spin_unlock(&cdev_lock); kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); if (!kobj) return -ENXIO; new = container_of(kobj, struct cdev, kobj); spin_lock(&cdev_lock); /* Check i_cdev again in case somebody beat us to it while we dropped the lock. */ p = inode->i_cdev; if (!p) { inode->i_cdev = p = new; list_add(&inode->i_devices, &p->list); new = NULL; } else if (!cdev_get(p)) ret = -ENXIO; } else if (!cdev_get(p)) ret = -ENXIO; spin_unlock(&cdev_lock); cdev_put(new); if (ret) return ret; ret = -ENXIO; fops = fops_get(p->ops); if (!fops) goto out_cdev_put; replace_fops(filp, fops); if (filp->f_op->open) { ret = filp->f_op->open(inode, filp); if (ret) goto out_cdev_put; } return 0; out_cdev_put: cdev_put(p); return ret; }
创建
参考资料:
https://zhuanlan.zhihu.com/p/577974561
Linux中mknod命令实现原理以及源码分析-CSDN博客
Linux中mknod命令实现原理以及源码分析-CSDN博客