1、字符设备驱动框架
1.1、字符设备
定义:只能以一个字节一个字节的方式读写的设备,不能随机的读取设备中的某一段数据,读取数据需要按照先后顺序。(字符设备是面向字节流的)
常见的字符设备:鼠标 键盘 串口 控制台
块设备:可以从设备的任意位置读取一定长度数据的设备。
常见的块设备:硬盘 磁盘 光盘 U盘 SD卡 TF卡
1.2、字符设备驱动框架
init流程:–> HelloModule
- 申请设备号(静态申请 动态申请)
- 创建一个字符设备
- 初始化字符设备
- 将设备号和字符设备关联起来
exit流程:–> HelloExit
- 删除字符设备
- 删除设备号
1.2.1 设备号
定义:设备号是设备在内核中的身份和标识,是内核区分不同设备的唯一信息,设备号是由主设备号和次设备号构成,主设备号表示一类设备,次设备号表示该类设备中的某一个设备。
设备号:是一个32bit的无符号整数,高12bit是主设备号,低20bit是次设备号。
内核函数和库函数的区别:库函数是语言本身的一部分,而系统函数是内核提供给应用程序的接口,属于系统的一部分。
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) // 前20位置1
/*
* 1 << 20 -1 : 1 0000 0000 0000 0000 0000 - 1
* : 0 1111 1111 1111 1111 1111
*/
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 获取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) // 获取次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) // 合成总设备号
1.2.2 申请设备号
申请设备号的两种方式:
静态申请
-
int register_chrdev_region(dev_t from, unsigned count, const char *name)
作用:静态申请设备号
from:设备号(由主次设备号构成)
count:子设备个数
*name:设备名称
返回值:0–>成功 非0–>失败 -
void unregister_chrdev_region(dev_t from, unsigned count)
作用:从内核中移除设备号
from:设备号(由主次设备号构成)
count:子设备个数
动态申请
-
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
作用:动态申请设备号
*dev:指向设备号的指针
baseminor:子设备的第一个编号
count:子设备个数
*name:设备名称
返回值:0–>成功 非0–>失败
1.2.3 创建字符设备
-
struct cdev *cdev_alloc(void)
作用:创建一块用于存放字符设备的空间
返回值:是指向创建成功的字符设备的指针
在Linux内核中用struct cdev 来描述一个字符设备
THIS_MODULE --> 当前模块本身
-
void cdev_del(struct cdev *p)
作用:删除字符设备
*p:指向字符设备的指针
1.2.4 初始化字符设备 --> 绑定驱动方法
-
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
作用:初始化字符设备
*cdev:指向字符设备的指针
*fope:指向操作字符设备的函数集的指针
1.2.5 字符设备与设备号关联
-
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
作用:将字符设备和设备号关联,并将字符设备添加到内核中
*p:指向字符设备的指针
dev:设备号
count:子设备的个数
返回值:成功为0 失败非0
退出函数
- 删除设备 cdev_del
- 删除设备号unregister_chrdev_region
测试步骤
-
sudo insmod hello.ko
-
dmesg |tail -->250 0
-
cat /proc/devices–>查看设备号 250 0
-
sudo mknod /dev/haha0 c 250 0
-
ls -l /dev/haha*—>查看创建字符设备文件
-
sudo ./test–>open hahao ok!
-
dmesg |tail–>helloopen/helloClose
-
sudo rmmod hello.ko
-
sudo rm /dev/haha0
区分字符设备驱动框架中使用的s三个结构体:
struct file
:代表内核中一个打开的文件。系统中每个打开的文件在内核中都有关联的struct file
。
struct inode
:用来记录文件在物理上的位置信息。它和打开文件的struct file结构不同,一个文件可以对应多个struct file
,但是只有一个struct inode
struct file_operations
:是内核给用户提供的驱动接口函数集,用户可以定义函数集中的任何驱动方法。(对于不支持的一般不写)
2、实现用户空间和内核空间的数据拷贝
用户代码对字符设备的任何操作,最终都要落实到设备对应的底层操作函数上
- 内核空间 --> 用户空间 read --> HelloRead
- 用户空间 --> 内核空间 write --> HelloWrite
应用层:
fd = open("/dev/haha0");
read(fd,)
close(fd)
驱动层:
增加HelloRead
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
char g_buf[]="hello test---";
ssize_t HelloRead (struct file *pFile, char __user *buf, size_t count, loff_t *p)
{
copy_to_user(buf, g_buf, count);
}
-
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
作用:从内核空间拷贝数据到用户空间
*to:用户空间指针
*from:内核空间指针(数据源)
n:拷贝的字节数 -
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
作用:从用户空间拷贝数据到内核空间
*to:用户空间指针
*from:内核空间指针(数据源)
n:拷贝的字节数 -
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
测试步骤
- sudo insmod hello.ko
- dmesg |tail
- sudo mknod /dev/haha0 c 250 0
- ls -l /dev/hah*
- sudo ./test–>查看是否读到数据?
- sudo rmmod hello
- sudo rm /dev/haha0