/************************************************************************************html
*本文为我的学习记录,若有错误,欢迎指正。node
* http://www.cnblogs.com/xiaojiang1025/p/6367061.html数据结构
* http://www.cnblogs.com/xiaojiang1025/p/6367910.html框架
* http://www.cnblogs.com/xiaojiang1025/p/6369065.html函数
* http://www.javashuo.com/article/p-dnlpbddz-bh.html学习
* http://www.javashuo.com/article/p-rzrvxiho-by.htmlspa
************************************************************************************/.net
字符设备是指在I/O传输过程当中以字节为单位进行传输的设备。其特色是,不能随机读取字符设备的某一数据,只能按照字节的前后顺序读取数据。常见的字符设备有串口、键盘、鼠标等。指针
字符设备相关数据结构详见Linux设备管理(二):内核中字符设备的管理。
(1)动态申请cdev内存
/* *所在文件:/kernel/fs/char_dev.c *参数:无 *返回值: * 成功: cdev 对象首地址 * 失败:NULL */ struct cdev *cdev_alloc(void);
(2)初始化cdev
初始化cdev,创建cdev与file_operations之间的关联。
/* *所在文件:/kernel/fs/char_dev.c *参数: * struct cdev *p: 被初始化的cdev对象 * const struct file_operations *p:字符设备操做方法集 *返回值:无 */ void cdev_init(struct cdev *p, const struct file_operations *p);
(3)申请设备号
1)指定设备号
/* *所在文件:/kernel/fs/char_dev.c *参数: * dev_t from: 起始设备号 * unsigned count: 需注册的次设备个数 * const char *name: 设备名称 *返回值: * 0: 注册成功 * 负数: 注册失败 */ int register_chrdev_region(dev_t from, unsigned count, const char *name);
2)系统自动分配设备号
/* *所在文件:/kernel/fs/char_dev.c *参数: * dev_t *dev: 输出型参数,系统自动分配的设备号 * unsigned baseminor: 次设备的起始编号 * unsigned count: 次设备的个数 * const char *name: 设备名称 *返回值: * 0: 注册成功 * 负数: 注册失败 */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
(4)注册cdev设备对象
将cdev添加至系统字符设备链表中。
/* *所在文件:/kernel/fs/char_dev.c *参数: * struct cdev *p:被初始化的cdev对象 * dev_t dev: 起始设备号 * unsigned count:该设备连续的次设备号的个数 *返回值: * 成功:返回0 * 失败:返回负数 */ int cdev_add(struct cdev *p, dev_t dev, unsigned count);
建立设备文件的细节详见Linux设备文件的建立。
(1)在/sys中导出设备类信息
/* *所在文件:/kernel/include/linux/device.h *参数: * owner:类所属模块,通常为THIS_MODULE * name: 类名 *返回值: * struct class *:指向所建立的类 */ #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
(2)建立设备文件
/* *所在文件:/kernel/driver/base/core.c *参数: * struct class *class: 被初始化的cdev对象 * struct device *parent:父设备 * dev_t dev: 设备号 * void *drvdata: 设备的回调数据 * const char *fmt: 设备名 *返回值: * struct device *: 指向所建立的设备 */ struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
根据须要实现file_operations中的函数,经常使用的函数以下:
(1)read、write
Linux下各个进程都有本身独立的进程空间,即便是将内核的数据映射到用户进程,该数据的PID也会自动转变为该用户进程的PID,因为这种机制的存在,咱们不能直接将数据从内核空间和用户空间进行拷贝,而须要专门的拷贝数据函数/宏:
long copy_from_user(void *to, const void __user * from, unsigned long n) long copy_to_user(void __user *to, const void *from, unsigned long n)
有了这两个函数,内核中的read,write就能够实现内核空间和用户空间的数据拷贝。
(2)ioctl
虽然在文件操做结构体"structfile_operations"中有不少对应的设备操做函数,可是有些命令是实在找不到对应的操做函数。如CD-ROM的驱动,想要一个弹出光驱的操做,这种操做并非全部的字符设备都须要的,因此文件操做结构体也不会有对应的函数操做。
出于这样的缘由,ioctl就有它的用处了——一些没办法归类的函数就统一放在ioctl这个函数操做中,经过指定的命令来实现对应的操做。因此,ioctl函数里面都实现了多个的对硬件的操做,经过应用层传入的命令来调用相应的操做。
ioctl函数使用的详细方法,详见Linux驱动程序中ioctl函数的用法。
/* *参数: * struct inode *inode和struct file *filp: ioctl的操做有多是要修改文件的属性,或者访问硬件。要修改文件属性的话,就要用到这两个结构体了,因此这里传来了它们的指针。 * unsigned int cmd: 命令 * unsigned long arg:参数 *返回值: 无 */ int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
(1)注销cdev
将cdev对象从系统字符设备链表中剔除,并清理cdev所占的内存资源。
/* *所在文件:/kernel/fs/char_dev.c *参数: * struct cdev *p:被注销的cdev对象 *返回值: 无 */ void cdev_del(struct cdev *p);
(2)释放设备号
/* *所在文件:/kernel/fs/char_dev.c *参数: * dev_t from:被注销设备的起始设备号 * unsigned count:该设备连续的次设备号个数 *返回值: 无 */ void unregister_chrdev_region(dev_t from, unsigned count);
(3)删除设备类
/* *所在文件:/kernel/driver/base/class.c *参数: * struct class *cls:设备类 *返回值: 无 */ void class_destroy(struct class *cls);
(4)删除设备文件
/* *所在文件:/kernel/driver/base/core.c *参数: * struct class *class:设备类 * dev_t devt: 设备号 *返回值: 无 */ void device_destroy(struct class *class, dev_t devt);