Linux中,根据设备的类型能够分为三类:字符设备、块设备和网络设备。html
字符设备:应用程序按字节/字符来读写数据,一般不支持随机存取。咱们经常使用的键盘、串口都是字符设备。node
块设备:应用程序能够随机访问设备数据。典型的块设备有硬盘、SD卡、闪存等,应用程序 能够寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块的倍数进行。linux
网络设备是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。网络
0x01 前置知识
函数
用户使用open函数打开设备文件,作了些什么工做:
spa
(引自https://embed-linux-tutorial.readthedocs.io/zh_CN/latest/linux_driver/character_device.html)指针
设备文件一般在开机启动时自动建立的,不过,咱们仍然可使用命令mknod来建立一个新的设备文件,命令的基本语法以下:code
mknod 设备名 设备类型 主设备号 次设备号
当咱们使用上述命令,建立了一个字符设备文件时,实际上就是建立了一个设备节点inode结构体,而且将该设备的设备编号记录在成员i_rdev,将成员f_op指针指向了def_chr_fops结构体。这就是mknod负责 的工做内容。htm
(之间的过程有点多,详细地可照着图看源码了解)总的来讲用户调用open函数时,最终会调用file结构体中的f_op,即def_chr_fops。
blog
在Linux内核中,使用结构体cdev来描述一个字符设备。函数chrdev_open最终将该文件结构体file的成员f_op替换成了cdev对应的ops成员,并执行ops结构体中的open函数。
咱们使用对该文件描述符fd调用read、write函数,最终都会调用file结构体对应的函数,实际上也就是调用cdev结构体中ops结构体内的相关函数。
总结一下整个过程,当咱们使用open函数,打开设备文件时,会根据该设备的文件的设备号找到相应的设备结构体,从而获得了操做该设备的方法。也就是说若是咱们要添加一个新设备的话,咱们须要提供一个设备号,一个设备结构体以及操做该设备的方法(file_operations结构体)。接下来,咱们将介绍以上的三个内容。
0x02 设备驱动程序的编写
(引自哪我也不记得了)
1)定义cdev设备
//第一种方式 static struct cdev chrdev; //第二种方式 struct cdev *cdev_alloc(void);
2)分配/注销设备号
Linux的各类设备都以文件的形式存放在/dev目录下,为了管理这些设备,系统为各个设备进行编号,每一个设备号又分为主设备号和次设备号。主设备号用来区分不一样种类的设备,如USB,tty等,次设备号用来区分同一类型的多个设备,如tty0,tty1……下图 列出了部分tty设备,他们的主设备号都是4,而不一样的次设备号分别对应一个tty设备。
内核提供了一种数据类型:dev_t,用于记录设备编号,该数据类型其实是一个无符号32位整型,其中的12位用于表示主设备号,剩余的20位则用于表示次设备号。
静态地为一个字符设备申请一个或多个设备编号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:
动态分配设备编号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数说明以下:
void unregister_chrdev_region(dev_t from, unsigned count)
内核还提供了register_chrdev函数用于分配设备号。该函数是一个内联函数,它不只支持静态申请设备号,也支持动态申请设备号,并将主设备号返回,函数原型以下所示。
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
参数说明:
同一类字符设备会在内核中申请256个,若用不到,会形成资源浪费
注销函数:
static inline void unregister_chrdev(unsigned int major, const char *name) { __unregister_chrdev(major, 0, 256, name); }
3)初始化cdev
将cdev结构体与file_operations结构相关联
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
4)注册设备
cdev_add函数用于向内核的cdev_map散列表(管理当前系统中的全部字符设备)添加一个新的字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
5)file_operation *fops
本身编写一个字符设备驱动:https://tutorial.linux.doc.embedfire.com/zh_CN/latest/linux_driver/character_device.html