1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。_
2. 本篇目标
本篇目标旨在回答如下问题:
. 如何创建一个字符类设备驱动?
. 字符类设备驱动的fops是如何绑定的?
. 字符类设备驱动接口的调用路径是怎样的?
3. 字符设备驱动
3.1 字符类设备子系统初始化
/* 建立字符类设备的kobj_map */
chrdev_init()
cdev_map = kobj_map_init(base_probe, &chrdevs_lock)
3.2 建立字符设备驱动
struct xxx_chrdev {
...
struct cdev cdev; /* Char device structure */
} xxx_cdev;
struct file_operations xxx_cdev_fops = {
.owner = THIS_MODULE,
.llseek = xxx_cdev_llseek,
.read = xxx_cdev_read,
.write = xxx_cdev_write,
.unlocked_ioctl = xxx_cdev_ioctl,
.open = xxx_cdev_open,
.release = xxx_cdev_release,
}
/* 申请字符设备设备号区间 */
alloc_chrdev_region(&dev, MAJOR, 1, "xxx");
/* 注册字符设备到 cdev_map */
cdev_init(&xxx_cdev.cdev, &xxx_cdev_fops);
xxx_cdev.cdev.owner = THIS_MODULE;
xxx_cdev.cdev.ops = &xxx_cdev_fops;
err = cdev_add (&xxx_cdev.cdev, MKDEV(MAJOR, MINOR), 1);
3.3 创建字符设备节点
这里有两种路径为字符设备驱动创建设备节点 /dev/xxx
:
. 通过 devtmpfs 来创建字符设备节点,之后通知 udevd 等用户态设备事件监听程序,处理设备节
点添加事件:修改设备节点权限或创建设备节点符号链接;
. 用户空间使用 mknod 等工具,通过 sys_mknod() 系统调用创建字符设备节点。
详情可参考博文 Linux: 设备节点创建移除过程简析 。
# insmod xxx.ko
# mknod /dev/xxx0 c $major 0
sys_mknod("/dev/xxx0", mode | S_IFCHR, deno)
sys_mknodat(AT_FDCWD, "/dev/xxx0", mode | S_IFCHR, devno)
vfs_mknod()
dir->i_op->mknod(dir, dentry, mode, dev)
shmem_mknod()
shmem_get_inode()
init_special_inode(inode, inode->i_mode, rdev)
if (S_ISCHR(mode)) { /* 字符类设备的缺省 fops 绑定 */
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
}
...
这里我们关注的重点是,字符类设备的 fops
设定为 def_chr_fops
。
3.4 操作字符设备
3.4.1 打开字符设备
/* 通过对字符设备的open调用,将字符类设备的默认fops替换为实际字符设备驱动的fops: def_chr_fops -> xxx_cdev_fops */
sys_open("/dev/xxx0", ...)
do_sys_open(AT_FDCWD, filename, flags, mode)
...
vfs_open(&nd->path, file, current_cred())
do_dentry_open(file, d_backing_inode(dentry), NULL, cred)
f->f_op->open() /* f->fop = def_chr_fops */
chrdev_open()
/* 从cdev_map找到对应的字符设备对象 */
obj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
inode->i_cdev = container_of(kobj, struct cdev, kobj);
replace_fops(filp, inode->i_cdev->ops); /* 替换字符设备的通用 fops: def_chr_fops -> 设备自定义的 fops */
filp->f_op->open(inode, filp) /* 调用字符设备自身的open()接口 */
xxx_cdev_open()
我们可以看到,通过对设备 /dev/xxx0
调用 open()
,将字符类通用操作接口 def_chr_fops
替换为了字符设备驱动自身的接口,并在最后调用了设备驱动自身的 open
接口。
3.4.2 操作字符设备
/* 字符设备的读、写、ioctl */
sys_read()/sys_write()/sys_ioctl()
...
xxx_cdev_read()/xxx_cdev_write()/xxx_cdev_ioctl()