字符设备
- 设备号 = 主设备号(占高字节)+次设备号(占低字节)。
- 在/proc/devices列举出所有已经注册的设备号。
- 文件操作对象,提供open、write、read。
框架
- 获取并注册设备号
不同linux版本对应字节数不一样,一般用内核提供的函数获取 MKDEV(ma,mi)。
方式1:
register_chrdev 第一个参数为0则动态注册,第一个参数非0则静态注册
卸载1:
unregister_chrdev
方式2:
静态获取:register_chrdev_region(设备号通过MKDEV(ma,mi)获取)
动态注册:alloc_chrdev_region
cdev_init()//声明文件io操作结构体
cdev_add();
卸载2:
cdev_del(&led->dev);
unregister_chrdev_region(led->dev_no, 1); - 创建设备节点(/dev目录下)
手动:mknod /dev/设备 类型 主设备号 次设备号;这种在断电后则失效
自动(通过udev/mdev机制):class_create、device_class;创建类后创建设备文件 - 初始化文件io操作函数
struct file_operations my_ops={};
copy_to_user();//内核到用户,对应于用户read
copy_from_user();//用户到内核,对应于用户write
驱动中的read参数除了第四个其他都与用户态的一致,非安全操作中驱动中直接使用*buf直接赋值,但是若buf为NULL指针则会造成内核崩溃,对于这两个函数其实是一个安全措施,会对用户中所传进来的参数进行判断。返回值大于0表示出错,剩下多少个没有拷贝成功,成功返回0。 - 通过ioremap地址转换控制外设,返回虚拟地址,iounmap取消映射。
- 内核中对寄存器操作使用 readl、writel,一般不使用直接解引用赋值。
关键部分讲解
- 对于inode对象与file对象的重要属性应用:
inode: 其成员i_rdev保存设备号,i_cdev保存字符设备结构体。
file:其成员private_data,类型为void*,是驱动私有的,内核保证不会使用该指针。 - 对于注册字符串部分,推荐使用方式2,方式1只能注册一个设备号,其字符设备对象在内核帮我们注册了,一个字符设备对应一个设备号,而方式2可以注册多个设备号,即一个字符设备对象对应多个设备,例如串口一般有多个,若调用方式1则需重复调用多次注册设备号,主设备号相同,次设备号递增+1,一个字符对象对应多个设备。
具体实现(1对2):
dev_t dev;
dev = MKDEV(主设备号,次设备号);
register_chrdev_region(dev, 2, "设备名");
cdev_init(&字符设备, &my_fops);
字符设备.owner = THIS_MODULE;
cdev_add(&字符设备, dev, 2);
- 对于不同设备对应同一个驱动的区分,设备的唯一身份就是设备号,而设备号在inode对象中的成员i_rdev中记录,其中他们一般主设备号相同,次设备号不同,我们可以通过open中内核传入的inode对象判断次设备号来区分不同设备,并将得到的设备放入file对象中的私有数据中,在其他io函数中可取出来使用。
具体实现如下:
static int int my_open(struct inode *no, struct file *fp)
{
switch(MINOR(no->i_rdev)){
default:
case 0:
filp->private_data = 设备0结构体
case 1:
filp->private_data = 设备1结构体
}
return 0;
}
- 对于一个驱动对应多个设备的另一实现:一个字符设备对象对应一个设备,定义多组。
对于不同设备而言,他们对文件io传入的参数均不同,即可直接通过file私有数据空间的不同来进行差异化操作。
dev_t dev;
dev = MKDEV(主设备号,次设备号);
register_chrdev_region(dev, 2, "设备名");
for(i=0; i<2;i++){
cdev_init(&字符设备i, &my_fops);
字符设备i.owner = THIS_MODULE;
cdev_add(&字符设备i, dev+i, 1);
}
还有一个问题就是如何将差异化数据保存在file对象的私有数据空间中:
linux内核中定义了一个宏 container_of,他能够根据结构体成员的地址方向得到结构体的起始地址,通过字符设备对象调用该宏获取到自定义的设备对象保存在file对象私有数据空间中,则在其他io中即可通过file对象使用,使用实例:
file->private_data = container_of(inode->i_cdev, 自定义设备对象类型, cdev);