应用程序在使用文件接口进行操作时,首先触发一个系统调用进入内核态,VFS层寻找具体的设备进而调用设备的相应处理函数。
图1 字符型设备层次结构
当新建一个字符型设备时,通常需要提供文件操作函数表(即常见的open,release,read,write)等等。同时需要提供一个设备号,设备号包括主设备号和从设备号。
二者都要记录到一个叫cdev的结构体,最终在注册改设备的时候,将这个结构体保存在cdev_map表中。
图2 字符型设备表
用户在使用内核注册的字符型设备前,需要创建一个字符型设备文件。这个设备通常放在/dev/目录中,当然发放在其它位置也可以工作。
mknod命令可以用来创建一个字符型设备文件。创建核心点包括两个:一是根据文件系统策略创建一个inode节点,二是将设备号填入这个inode相应字段。
mknod创建字符型设备流程
1. mknod /dev/simple_cdev c 250 0
2. sys_mknodat
3. vfs_mknod
4. shmem_mknod
5. shmem_get_inode
6. init_special_inode
注意:4、5可能因不同文件系统而是用的函数不同,但最终都会到6
在调用init_special_inode时,前期的函数已经在相应目录下创建了一个inode节点,
此时需要做的是:
1) 在inode节点中记录设备号inode->i_rdev = rdev
2) 在inode节点中记录字符型设备默认open操作函数,
所有的字符设备在被open时首先调用这个默认的chrdev_open函数,之后再调用设备自己的open函数
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->i_rdev = rdev;
}
......
}
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
注册一个字符型设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
......
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
......
}
图3 创建字符型设备文件
在使用字符型设备的时候,是借助对应的字符型设备文件完成的。
应用程序首先要open字符型设备。
字符型设备OPEN流程
1. open ---- libc
2. sys_open ---- syscall
3. do_sys_open
4. do_filp_open
5. path_openat
6. do_last
7. finish_open
8. do_dentry_open
9. chrdev_open ---- 位于def_chr_fops中定义
9.1 根据inode节点记录的设备号(i_rdev)从字符设备列表(cdev_map)查找设备。
9.2 如果找不到,则返回
9.3 如果找到,则完成两个主要任务:
9.3.1 将filp指向该设备的fops函数列表,filp是文件系统维护的一个结构,很多文件操作都要基于它
9.3.2 inode节点成员i_cdev指向该设备结构体
之后可进行读写等操作
字符型设备READ流程
1. read ---- libc
2. sys_read ---- syscall
3. vfs_read
4. filp->f_op->read 也就是 cdev->ops->read
字符型设备WRITE/CLOSE/...流程与READ类似。
图4 字符型设备的数据结构关系图
---------------------------- 测试代码 ----------------------------
/*
* a simple char device driver
*
* Copyright (C) 2014 PAN Guolin guolinp@gmail.com
*
* Licensed under GPLv2 or later.
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define SIMPLE_CDEV_MAJOR 250
static struct cdev cdev_obj;
static unsigned int data;
static int simple_cdev_open(struct inode *inode, struct file *filp)
{
data = 0;
printk(KERN_INFO "Open simple char device, data=%d\n", data);
return 0;
}
static int simple_cdev_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "Release simple char device\n");
return 0;
}
static ssize_t simple_cdev_read(struct file *filp, char __user * buf, size_t size, loff_t * ppos)
{
int count = sizeof(data);
if (copy_to_user(buf, (char *)&data, count))
return -EFAULT;
else
printk(KERN_INFO "Read %u bytes(s)\n", count);
return count;
}
static ssize_t simple_cdev_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos)
{
int count = sizeof(data);
if (copy_from_user((char *)&data, buf, count))
return -EFAULT;
else
printk(KERN_INFO "Written %u bytes(s)\n", count);
return size;
}
static const struct file_operations simple_cdev_fops = {
.owner = THIS_MODULE,
.read = simple_cdev_read,
.write = simple_cdev_write,
.open = simple_cdev_open,
.release = simple_cdev_release,
};
static int __init simple_cdev_init(void)
{
int ret;
dev_t devno = MKDEV(SIMPLE_CDEV_MAJOR, 0);
ret = register_chrdev_region(devno, 1, "simple_cdev");
if (ret < 0) {
printk(KERN_INFO "Error %d register_chrdev_region %d", ret, devno);
return ret;
}
cdev_init(&cdev_obj, &simple_cdev_fops);
cdev_obj.owner = THIS_MODULE;
ret = cdev_add(&cdev_obj, devno, 1);
if (ret)
printk(KERN_INFO "Error %d adding simple cdev%d", ret, devno);
return ret;
}
module_init(simple_cdev_init);
static void __exit simple_cdev_exit(void)
{
cdev_del(&cdev_obj);
unregister_chrdev_region(MKDEV(SIMPLE_CDEV_MAJOR, 0), 1);
}
module_exit(simple_cdev_exit);
MODULE_AUTHOR("PAN Guolin <guolinp@gmail.com>");
MODULE_LICENSE("GPL v2");
# mknod /dev/simple_cdev c 250 0
# echo hello > /dev/simple_cdev
Open simple char device, data=0
Written 4 bytes(s)
Release simple char device
# cat /dev/simple_cdev
Open simple char device, data=0
Read 4 bytes(s)
...
Release simple char device