设备号
设备号:
Linux规定每一个字符设备或者块设备都必须有一个专属的设备号。一个设备号由主设备号和次设备号组成,主设备号用来表示某一类驱动,如鼠标、键盘属于USB驱动,而次设备号用于便是这个驱动下的各个设备,如第n个鼠标。
Linux中使用定义的dev_t数据类型表示设备号。dev_t定义在include/linux/type.h里。dev_t属于u32类型,其中高12位是主设备号,低20位是次设备号。
设备号操作宏:
在文件include/linux/kdev_t.h中提供了操作设备号的宏定义,主要有:
1.宏MAJOR表示从dev_t中获取主设备号
2.宏MINOR便是从dev_t中获取次设备号
3.宏MKDEV用于将主设备号和次设备号醉成dev_v类型的设备号
设备号分配:
在编写字符设备驱动代码时,可以分为静态分配设备号和动态分配设备号,函数定义在include/linux/fs.h里
静态分配设备号通过register_chrdev_region函数选择一个固定的设备号作为主设备号,可以使用“cat /proc/devices”查看哪些设备号已被占用
动态分配设备号通过alloc_chrdev_region函数自动分配一个违背使用的设备号
int register_chrdev_region(dev_t, unsigned, const char *);
/*
参数1:设备号起始值,例如MKDEV(100, 0)表示主设备号起始值为100,次设备号起始值为0
参数2:次设备号的数量
参数3:设备的名称
成功返回0,失败返回负数
*/
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
/*
参数1:保存自动申请的设备号
参数2:次设备号的起始地址
参数3:申请的设备号数量
参数3:设备的名称
成功返回0,失败返回负数
*/
extern void unregister_chrdev_region(dev_t, unsigned);
/*
设备号释放函数
参数1:钥匙放的设备号
参数2:释放设备号的数量
*/
设备结构体
注册字符类设备:
Linux中,使用cdev结构体描述一个字符型设备,cdev结构体定义在include/linux/cdev.h。
struct cdev
{
struct kobject kobj;
struct module *owner; //所属模块
const struct file_oprations *ops;//文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
}
初始化cdev结构体cdev_init函数
void cdev_init(struct cdev *cdev, const struct file_oprations *fops);
/*
建立cdev和file_oprations文件操作结构体之间的联系
*/
向系统添加cdev结构体cdev_add函数
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
/*
cdev-要添加的字符设备
dev-设备号
count-要添加的设备数
*/
删除cdev结构体cdev_del函数
void cdev_del(struct cdev *cdev);
file_operations结构体
file_operations作用:
在Linux中一切皆文件,访问一个设备就像访问一个文件,在应用程序中我们可以调用open,read,write,close,ioctl来操作驱动,在驱动程序中会通过file_operations对这些操作函数重写,file_operations就是将系统调用和驱动程序连接起来的媒介。
file_operations结构体定义在include/linux/fs.h文件中,这个结构体非常庞大
struct file_operations{
struct module *owner; //改结构体模块的指针
loff_t (*llseek)(struct file *, loff_t , int); //用于修改文件当前的读写位置
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *); //用于读取设备文件
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *); //用于向设备写数据
int (*open)(struct inode *, struct file *); //用于打开设备文件
int (*release)(struct inode *, struct file *); //用于释放设备文件
......
}
设备节点
每个设备在Linux系统中都有一个对应的设备文件代表它,应用程序操作对应的设备文件便可以操作对应的文件,例如
fd=open("/dev/xxx", O_RDWR);
图中,c表示它是一个字符设备,81是主设备号,40是次设备号,Linux通过主设备号找到对应的file_operations结构体,通过次设备号找到是同类设备的第几个,用来确定驱动程序。
Linux下创建节点的方式
1.手动创建
通过命令mknod创建
mknod 设备节点名称 设备类型(字符设备-c,块设备-b) 主设备号 次设备号
2.在注册设备的时候自动创建
Linux中可以通过udev来实现设备节点的创建和删除,udev可以根据喜用中的设备状态来创建或者删除设备节点,在嵌入式Linux中,使用udev的简化版mdev,udev通过/sys/class/路径下创建的设备文件,自动创建设备节点文件。
class_create函数
class_create函数定义在include/linux/device.h文件当中,使用这个函数会在/sys/class下创建文件,这个函数有两个参数,第一个参数owner一般为THIS_MODULE,第二个参数是类的名字。
device_create函数
使用class_create创建好类以后,需要在类下面用device_create创建一个设备。
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
/*
cls-表示设备创建在哪个类下面
parent-父设备(NULL)
devt-设备号
drvdata-设备可能用到的数据(NULL)
fmt-设备节点的名字
*/
device_destroy函数
删除创建的设备。
extern void device_destroy(struct class *cls, dev_t devt);
/*
cls-删除的设备所属的类
detv-要删除的设备号
*/
class_destroy函数
删除创建的类。
extern void class_destroy(struct class *cls);
/*
cls-删除的设备所属的类
*/
内核空间和用户空间
Linux可访问的内存空间分为了两个部分,一部分是内核空间,一部分是用户空间。操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。
内核空间中的代码控制硬件资源,用户空间中的代码只能通过内核暴露出来的系统调用接口来使用硬件资源,保证系统的安全性和稳定性。
从用户空间切换到内核空间的方式有三种:系统调用、软中断和硬件中断。
用户空间和内核孔家的数据交换需要通过copy_from_user和copy_to_user两个函数完成传输,这两个函数定义在了linux/include/asm-arm/uaccess.h中。
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
/*
作用:把内核空间的数据复制到用户空间
*to-用户空间的指针
*from-内核空间的指针
n-拷贝的字节数
成功返回0
*/
unsigned long copy_from_user(void __user *to, const void *from, unsigned long n);
/*
作用:把用户空间的数据复制到内核空间
*to-内核空间的指针
*from-用户空间的指针
n-拷贝的字节数
成功返回0
*/
文件私有数据
文件私有数据就是将私有数据private_data指向设备结构体,然后再read,write等函数中通过private_data访问设备结构体。
struct device_test dev1;
struct file *file;
file->private_data=&dev1;//初始化
struct device_test *test_dev=(struct device_test *)file->private_data;//访问
可以用私有数据结构体在一个驱动中控制多个设备结构体变量,使用container_of函数通过结构体变量中某个成员的首地址获取整个结构体的首地址,然后传给私有数据结构体。
container_of(ptr, type, member);
/*
ptr-成员函数地址
type-结构体类型
member-结构体郑源具体名字
*/
杂项设备
Linux中,把无法归类的五花八门的设备定义为杂项设备,这些设备的主设备号固定为10,一般自动分配次设备号,它们会自己调用class_create()和device_create()来自动创建设备节点。
设备驱动执行错误之后
在驱动中创建一个设备的流程为:申请设备号、创建字符设备、创建类、创建设备节点。
在设备驱动报错推出程序时,要遵循“先进后出”原则,因此顺序为:删除类class_destroy、删除字符设备cdev_del、释放设备号unregister_chrdev_region、返回错误码。