/************************************************************************************
*本文为个人学习记录,如有错误,欢迎指正。
* https://www.cnblogs.com/embedded-tzp/p/4507240.html
* http://www.169it.com/tech-qa-linux/article-5682294992603241339.html
* https://blog.csdn.net/zhoujiaxq/article/details/7646013
* http://www.cnblogs.com/xiaojiang1025/p/6196198.html
************************************************************************************/
Linux内核对设备的管理是基于kobject来进行的,详见Linux设备管理:kobject, kset, ktype分析。Linux对字符设备的管理框架依赖于struct kobj_map、struct cdev、dev_t dev、struct file_operations等数据结构。如下图所示。
2. 字符设备数据结构
Linux内核中关于字符设备的操作函数存放在 "/kernel/fs/char_dev.c" 文件中。
2.1 dev_t dev
一个字符设备或块设备都有一个主设备号(major)和一个次设备号(minor)。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
Linux内核中,使用dev_t来描述设备号。
typedef u_long dev_t; // 在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。
Linux内核中提供以下几个宏来操作dev_t。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 从设备号中提取主设备号 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) // 从设备号中提取次设备号 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) // 将主、次设备号拼凑为设备号
2.2 struct cdev
Linux内核中,使用struct cdev结构体来描述一个字符设备。
<include/linux/cdev.h> struct cdev { struct kobject kobj; //内嵌的内核对象 struct module *owner; //该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE,主要用于模块计数 const struct file_operations *ops;//该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构体 struct list_head list; //用来将已经向内核注册的所有字符设备形成链表 dev_t dev; //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一个) unsigned int count; //隶属于同一主设备号的次设备号的个数 };
2.3 struct file_operations
Linux内核中,使用file_operations结构来管理设备驱动程序的函数,这个结构的每一个成员的名字都对应着一个函数调用。
用户进程利用在对设备文件进行操作时(read/write等),系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取其file_operations结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。
struct file_operations { struct module *owner; /* 模块拥有者,一般为 THIS——MODULE */ ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); /* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/ int (*mmap) (struct file *, struct vm_area_struct *); /* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */ long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg); /* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */ int (*open) (struct inode *, struct file *); /* 打开设备 */ int (*release) (struct inode *, struct file *); /* 关闭设备 */ int (*flush) (struct file *, fl_owner_t id); /* 刷新设备 */ loff_t (*llseek) (struct file *, loff_t, int); /* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */ int (*fasync) (int, struct file *, int); /* 通知设备 FASYNC 标志发生变化 */ unsigned int (*poll) (struct file *, struct poll_table_struct *); /* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */ };
2.4 struct kobj_map
Linux内核中,所有的字符设备都会记录在一个cdev_map 变量中。cdev_map是一个struct kobj_map类型的指针,其中包含着一个struct probe*类型、大小为255的数组,数组的每个元素指向的一个probe结构封装了一个设备号和相应的设备对象(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; //指向struct cdev对象 } *probes[255]; struct mutex *lock; }