开发者学堂课程【物联网开发 - Linux驱动开发实操演练:字符设备驱动02】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/657/detail/10874
字符设备驱动02
内容介绍:
一、Cdev 结构体
二、常用的驱动接口
一、Cdev 结构体
提到了一个描述所有字符。字符设备驱动的结构体。这个结构体叫 CDeV。结构体CDeV 什么意思呢?
C 就是一个 char 的意思,就是字符,然后 dev 是 device,就是 CdeV 结合起来就是一个字符设备,这个设备,这是内核,给它起个名字叫 cdev 结构体。
看一下这个 CDeV 结构体里到底有哪些成员变量?
描述所有字符设备驱动的结构体:
Cdev 结构体:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count:
};
看到的这个成员变量,实际上不是很多,有几个比较重要的成员变量,给大家详细进行分析一下。
内核设备用来描述字符设备锁通用的结构体。
Kobj:关于设备模型的相关内容
Owner: 关于设备模型的相关内容,赋值 THIS_MODILE(模块)
再往下,这是一个 count,是设备计数。或者设备个数。
也就是驱动,一般情况下是一个驱动,对应的是一个驱动的一个设备,但是有的时候,实际上一个驱动的是可以驱动同类型的多个设备的。
那比如说一个驱动驱动的,同一个同一类设备驱动了三个,那时候这个 count,实际上就可以写个三是,也就是驱动了对应的三个同类型的设备. 看完 CdeV 结构体里面的成员变量以后,就知道了,这里重点,分析的第一就是设备号,第二个,就是这个结构体。
operation 指针:
File_operations * 结构体指针:
除了 owner 之外全部都为函数指针
称此结构体为操作方法集(提供给应用层)。通过函数指针内核来调用设备,有这么一个结构体指针,而且,里面都是一些函数指针,有什么用呢?
这个 Linux 内核运行起来以后,应用程序想操作设备的时候,是不是必须得通过系统调用,然后,调到驱动的方法,那怎样才能调到驱动的方法,就是通过这个函数指针的方式去调用的,具体的设备驱动,具体的写驱动的工程师的工程师来完成,那在写的时候,不同的驱动,它的这个操作方法其实是不一样的,因此只需要把函数完成以后,把函数名复制函数赋值给这个函数指针以后,就能调到这个设备驱动,调用这个操作方法了。
二、常用的驱动接口
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 );
unsignedIint (*poll)(struct_file *struct poll_table_struct );
long (*unlocked_ioctl)(struct file *unsigned int,unsigned long);
int (*mmap)(struct file *struct vm_area_struct );
Int (*open)(struct inode *struct file *);
int (*release)(struct inode *struct file *);
int(*fasync)(int,struct file *int);
};
Read write:是调用驱动中的 read write 的方法。
Open: 底层最终调的是底层驱动 open 所对应的一个方法。
剩下的后面在介绍。
List_head 列表,内核在管理 CDeV 时,是通过一种列表的形式,去管理的所有的设备。
Dev_t: 设备号:
是唯一用来描述设备的一个编号,用来标识设备的。
数据类型:
代码:
21:typedef __n32__kernel dev t;
22:
23:typedef__kernel_fd_set fd_set;
24:typedef__kernel_dev_t dev_t;
25:typedef__kernel_ino_t ino_t;
26:typedef__kernel_mode_t mode_t;
27:typedef unsigned short umode_t;
28:typedef__kernel_nlink_t nlink_t;
32位无符号整型:主数据号+次设备号‘
这个设备跟这个设备号是一一对应的,一个驱动,可以驱动同类型的多个设备,那驱动同类型的多个设备的时候,主设备号去描述的是一类设备,然后次设备号,就是不同的设备,比如同样都是一个这个usb设备驱动,这个主设备号就是相同的,次设备号,比如一设备对应的是一,二设备对应的是二,三设备对应的是三。
内核提供了两个宏:
代码:
MAJOR(dev_t dev)
//从设备号中提取主设备号
MINOR(dev_t dev)
//从设备号中提取次设备号
MKDEV(int ma,int mi)
//根据主次设备号生成设备号
有了此三个宏,设备号的操作就可以完成了。
设备号的提取和生成如何实现:
代码:
#define MAJOR(dey) ((unsigned int) ((dey) >> MINORBITS))
#define MINOR(dev) ((unsignedint) ((dey) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
#define print_dov_t(buffer,dev)
sprint((buffer3“%u:%u\n“,MAJOR(dev), MINOR(dev))
#define format_dev_t(buffer,dev)
({
sprintf(uffer,”%u:%u”,MJoR(dev),MAJOR(dov);
buffer;
})
为宏函数。
将设备号右移20位,然后强转成一个无符号整型。
得到的是高12位。
所以主设备号是由高设备号组成。
代码:
#define MAJOR(dey) ((unsigned int) ((dey) >> MINORBITS))
#define MINOR(dev) ((unsignedint) ((dey) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
#define print_dov_t(buffer,dev)
sprint((buffer3“%u:%u\n“,MAJOR(dev), MINOR(dev))
#define MINOR(dev) ((unsigned int) ((dev)&((1U<<20)-1)))
一左移20位减一,想一下一左移二十位,后面有20个协议,就是说前面全是零,后面的20位全要。
得到的是低20位。
设备号:高12位的主设备号和第12位的次设备号组成。
Count:设备计数(个数)一个驱动可驱动同一个或者多类设备。