驱动学习(四)字符设备
文章目录
1. 什么是字符设备?
是指只能一个字节一个字节的读写的设备,不能随机的读取设备中的某一段数据源,读取数据需要按照先后顺序。字符设备是面向字节流的。
2. 常见的字符设备有哪些?
鼠标、键盘、串口、控制台tty
3. 什么是块设备?
可以从设备的任意位置读取一定长度数据的设备
4. 常见的块设备有哪些?
硬盘、磁盘、光盘、U盘、SD卡
5. 字符设备驱动框架
init:
{
申请设备号(静态申请、动态申请)
创建字符设备
初始化字符设备
将设备号和字符设备关联
}
exit:
{
销毁字符设备
删除申请的设备号
}
5.1 设备号
5.1.1 什么是设备号?
设备号是设备在内核中的身份和标志,是内核区分不同设备的唯一信息。设备号是由主设备号和次设备号构成,主设备号表示一类设备,次设备号表示该类设备中的一个设备。
设备号是一个32位的无符号数,高12位是主设备号,低20位是次设备号。
设备号使用到的头文件和宏函数
<linux/kdev_t.h>
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) //掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))//主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //完整设备号
5.1.2 申请设备号
静态申请
@头文件
<linux/fs.h>
int register_chrdev_region(dev_t from,unsigned count,const char *name)
@from:设备号,由主设备和次设备号构成
@count:子设备个数
@*name:设备名称
@返回值:成功为0
@注意: cat /proc/devices-->查看内核中所有已注册的设备号
动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
@dev:设备号指针
@baseminor:子设备的第一个编号
@count:子设备个数
@*name:设备名称
@返回值:成功为0
注销设备号
void unregister_chrdev_region(dev_t from, unsigned count)
@from:设备号
@count:子设备个数
5.2 创建与删除字符设备
5.2.1 创建字符设备
struct cdev *cdev_alloc(void) //创建一个存放字符设备的结构体的内存空间
@无入参
@返回值:指向字符设备结构体的指针
在linux内核中,struct cdev用来描述一个字符设备
struct cdev {
struct kobject kobj;//内嵌的内核对象
struct module *owner;//该字符设备所在的内核模块的对象指针
const struct file_operations *ops;//该结构描述了字符设备所能实现的所有方法
struct list_head list;//用来将已向内核注册的所有的字符设备形成链表
dev_t dev;//设备号,由主设备和次设备构成
unsigned int count;//隶属于同一个主设备号的次设备个数
};
5.2.2 删除字符设备
cdev_del(描述设备信息的结构体指针);删除字符设备
5.3 初始化字符设备
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev:被初始化的字符设备指针
fops:字符设备操作函数指针集
结构体中存放的函数指针用来指向操作字符设备的函数
struct file_operations {
struct module *owner;
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 (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
...
}
字符设备驱动框架中使用的三个结构:
struct file_operations:是一个函数指针的集合,定义能够在设备上进行的操作。结构中的成员指向驱动函数,每一个函数实现一个特别的操作,对于不支持的操作保留或者赋NULL。
struct inode:用来记录文件的物理上的信息。它和代表打开文件的struct file结构不同,一个文件可以有多个struct file结构,但是只能有一个inode结构。
struct file:代表内核中一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核打开时创建,在文件关闭后释放。
5.4 将字符设备和设备号关联
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p:字符设备结构体指针
dev:设备号
count:子设备个数
返回值:成功为0
6. 用户空间和内核空间数据拷贝
6.1 用户空间到内核空间
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t testRead(struct file *pFile,char __user *to,size_t count,loff_t *pOffset)
*pFile:内核打开的文件
to:用户空间指针
count:用户期望读取的字节数
*pOffset:文件读写位置
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
to:用户空间指针
from:数据源--》内核空间指针
n:拷贝的字节数
返回值:成功为0
6.2 内核空间到用户空间
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t testWrite(struct file *pFile,const char __user *to,size_t count,loff_t *pOffset)
*pFile:内核打开的文件
to:用户空间指针
count:用户期望写入的字节数
*pOffset:文件读写位置
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
作用:将用户空间数据拷贝到内核空间
*to:内核buf指针
*from:用户数据源
n:拷贝的字节数
返回值:成功为0
7. 测试
7.1 静态申请设备号,用户空间和内核空间数据拷贝
环境:ubuntu 20.04
流程:安装编译好的字符驱动模块,再创建一个字符设备文件,操作这个设备文件模拟实际驱动使用过程,查看内核打印信息验证结果
代码
1 #include <linux/module.h> //模块驱动的头文件
2 #include <linux/cdev.h> //设备信息描述的头文件
3 #include <linux/fs.h> //静态申请设备号头文件
4 #include <linux/kdev_t.h> //设备号用到的头文件和宏函数
5 #include <linux/uaccess.h>
6
7 #define BUF_SIZE 100
8
9 int major = 200; //主设备号
10 int min = 0; //次设备号
11 int deviceNum = 0; //完整设备号
12 struct cdev* pCdev = NULL; //描述设备信息的结构体
13 char *deviceName = "testCharDevMod";//设备名
14 char buff[BUF_SIZE] = "chrdev-test-2022-7-10";
15
16 int testOpen(struct inode *pNode,struct file *pFile)
17 {
18 printk("------into test open------\n");
19 printk("------leave test open------\n");
20 return 0;
21 }
22 int testClose(struct inode *pNode,struct file *pFile)
23 {
24 printk("------into test close------\n");
25 printk("------leave test close------\n");
26 return 0;
27 }
28 ssize_t testRead(struct file *pFile,char __user *buf,size_t count,loff_t *pOffset)
29 {
30 int res = -1;
31 printk("------into testRead------\n");
32 if(count > BUF_SIZE-1)
33 {
34 count = BUF_SIZE - 1;
35 }
36 res = copy_to_user(buf,buff,count);
37 if(res)
38 {
39 printk("copy_to_user error\n");
40 return -EFAULT;
41 }
42 printk("copy_to_user ok\n");
43 printk("\t buff = %s\t\n",buff);
44 printk("------leave testRead------\n");
45 return count;
46 }
47
48 ssize_t testWrite(struct file *pFile,const char __user *buf,size_t count,loff_t *pOffset)
49 {
50 int res = -1;
51 printk("------into testWrite------\n");
52 if(count > BUF_SIZE-1)
53 {
54 count = BUF_SIZE - 1;
55 }
56 res = copy_from_user(buff,buf,count);
57 if(res)
58 {
59 printk("copy_from_user error\n");
60 return -EFAULT;
61 }
62 printk("copy_from_user ok\n");
63 printk("\t buff = %s\t\n",buff);
64 printk("------leave testWrite------\n");
65 return count;
66 }
67
68 struct file_operations fp_arr =
69 {
70 .owner = THIS_MODULE,
71 .open = testOpen,
72 .read = testRead,
73 .write = testWrite,
74 .release = testClose
75 };
76
77 int driverr_init(void) //模块初始化函数
78 {
79 int res = 0;
80 printk("*********into driver init\n");
81 deviceNum = MKDEV(major,min);
82 res = register_chrdev_region(deviceNum,1,deviceName);
83 if(res)
84 {
85 printk("register_chrdev_region error\n");
86 return res;
87 }
88 printk("register_chrdev_region OK!\n");
89 pCdev = cdev_alloc();
90 if(NULL == pCdev)
91 {
92 printk("cdev_alloc error\n");
93 unregister_chrdev_region(deviceNum,1);
94 return -1;
95 }
96 printk("cdev_alloc ok\n");
97 cdev_init(pCdev,&fp_arr);
98 printk("cdev_init ok\n");
99 res = cdev_add(pCdev,deviceNum,1);
100 if(res)
101 {
102 printk("cdev_add error\n");
103 cdev_del(pCdev);
104 }
105 printk("cdev_add ok\n");
106 printk("*********leave driver init\n");
107 return 0;
108 }
109
110 void driver_clear(void) //模块清除函数
111 {
112 printk("*********into driver clear\n");
113 cdev_del(pCdev);
114 unregister_chrdev_region(deviceNum,1);
115 printk("*********leave driver clear\n");
116 }
117
118 module_init(driverr_init); //模块加载函数
119 module_exit(driver_clear); //模块卸载函数
120
121
122 MODULE_LICENSE("GPL");
123 MODULE_AUTHOR("cfy");
124 MODULE_ALIAS("liangzai");
125 MODULE_DESCRIPTION("2022-7-10");
测试代码
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <unistd.h>
6 #include <string.h>
7
8 #define BUF_SIZE 100
9
10 int main()
11 {
12 int fd = open("/dev/testchrdev0",O_RDWR);
13 if(fd < 0)
14 {
15 printf("open testchrdev0 err\n");
16 return -1;
17 }
18 printf("open testchrdev0 ok\n");
19
20 char buf[BUF_SIZE] = {0};
21 //测试读
22 read(fd,buf,BUF_SIZE-1);
23 printf("read data:%s\n",buf);
24 bzero(buf,sizeof(buf));
25
26 //测试写
27 printf(">> input:");
28 scanf("%s",buf);
29 write(fd,buf,sizeof(buf));
30 bzero(buf,sizeof(buf));
31 read(fd,buf,BUF_SIZE-1);
32 printf("write end read data:%s\n",buf);
33
34 close(fd);
35 return 0;
36
37
38 }
~
安装字符设备驱动模块,并查看申请的设备号
查看内核打印信息
静态申请成功、设备创建成功、设备初始化成功、信息关联成功
创建一个字符设备文件,名字可以随便起,但是设备号一定要是刚刚申请到的,比如我申请的就是,主设备号200,次设备号0
sudo mknod /dev/testchrdev0 c 200 0
运行测试程序,记得修改创建的字符设备文件的权限,不然可能无法读写这个文件
sudo chmod 777 /dev/testchrdev0
完整内核打印信息
验证无误
7.2动态申请设备号,用户空间和内核空间数据拷贝
环境:ubuntu 20.04
流程:安装编译好的字符驱动模块,再创建一个字符设备文件,操作这个设备文件模拟实际驱动使用过程,查看内核打印信息验证结果
1 #include <linux/module.h> //模块驱动的头文件
2 #include <linux/cdev.h> //设备信息描述的头文件
3 #include <linux/fs.h> //静态申请设备号头文件
4 #include <linux/kdev_t.h> //设备号用到的头文件和宏函数
5 #include <linux/uaccess.h>
6
7 #define BUF_SIZE 100
8
9 int major = 0; //主设备号
10 int min = 0; //次设备号
11 int deviceNum = 0; //完整设备号
12 struct cdev* pCdev = NULL; //描述设备信息的结构体
13 char *deviceName = "testCharDevMod";//设备名
14 char buff[BUF_SIZE] = "chrdev-test-2022-7-10";
15
16 int testOpen(struct inode *pNode,struct file *pFile)
17 {
18 printk("------into test open------\n");
19 printk("------leave test open------\n");
20 return 0;
21 }
22 int testClose(struct inode *pNode,struct file *pFile)
23 {
24 printk("------into test close------\n");
25 printk("------leave test close------\n");
26 return 0;
27 }
28 ssize_t testRead(struct file *pFile,char __user *buf,size_t count,loff_t *pOffset)
29 {
30 int res = -1;
31 printk("------into testRead------\n");
32 if(count > BUF_SIZE-1)
33 {
34 count = BUF_SIZE - 1;
35 }
36 res = copy_to_user(buf,buff,count);
37 if(res)
38 {
39 printk("copy_to_user error\n");
40 return -EFAULT;
41 }
42 printk("copy_to_user ok\n");
43 printk("\t buff = %s\t\n",buff);
44 printk("------leave testRead------\n");
45 return count;
46 }
47
48 ssize_t testWrite(struct file *pFile,const char __user *buf,size_t count,loff_t *pOffset)
49 {
50 int res = -1;
51 printk("------into testWrite------\n");
52 if(count > BUF_SIZE-1)
53 {
54 count = BUF_SIZE - 1;
55 }
56 res = copy_from_user(buff,buf,count);
57 if(res)
58 {
59 printk("copy_from_user error\n");
60 return -EFAULT;
61 }
62 printk("copy_from_user ok\n");
63 printk("\t buff = %s\t\n",buff);
64 printk("------leave testWrite------\n");
65 return count;
66 }
67
68 struct file_operations fp_arr =
69 {
70 .owner = THIS_MODULE,
71 .open = testOpen,
72 .read = testRead,
73 .write = testWrite,
74 .release = testClose
75 };
76
77 int driverr_init(void) //模块初始化函数
78 {
79 int res = 0;
80 printk("*********into driver init\n");
81 res = alloc_chrdev_region(&deviceNum,min,1,deviceName);
82 if(res)
83 {
84 printk("alloc_chrdev_region error\n");
85 return res;
86 }
87 printk("alloc_chrdev_region OK!\n");
88 printk("major = %d minor = %d \n",MAJOR(deviceNum),MINOR(deviceNum));
89 pCdev = cdev_alloc();
90 if(NULL == pCdev)
91 {
92 printk("cdev_alloc error\n");
93 unregister_chrdev_region(deviceNum,1);
94 return -1;
95 }
96 printk("cdev_alloc ok\n");
97 cdev_init(pCdev,&fp_arr);
98 printk("cdev_init ok\n");
99 res = cdev_add(pCdev,deviceNum,1);
100 if(res)
101 {
102 printk("cdev_add error\n");
103 cdev_del(pCdev);
104 }
105 printk("cdev_add ok\n");
106 printk("*********leave driver init\n");
107 return 0;
108 }
109
110 void driver_clear(void) //模块清除函数
111 {
112 printk("*********into driver clear\n");
113 cdev_del(pCdev);
114 unregister_chrdev_region(deviceNum,1);
115 printk("*********leave driver clear\n");
116 }
117
118 module_init(driverr_init); //模块加载函数
119 module_exit(driver_clear); //模块卸载函数
120
121
122 MODULE_LICENSE("GPL");
123 MODULE_AUTHOR("cfy");
124 MODULE_ALIAS("liangzai");
125 MODULE_DESCRIPTION("2022-7-10");
测试代码
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <unistd.h>
6 #include <string.h>
7
8 #define BUF_SIZE 100
9
10 int main()
11 {
12 int fd = open("/dev/testchrdev0",O_RDWR);
13 if(fd < 0)
14 {
15 printf("open testchrdev0 err\n");
16 return -1;
17 }
18 printf("open testchrdev0 ok\n");
19
20 char buf[BUF_SIZE] = {0};
21 //测试读
22 read(fd,buf,BUF_SIZE-1);
23 printf("read data:%s\n",buf);
24 bzero(buf,sizeof(buf));
25
26 //测试写
27 printf(">> input:");
28 scanf("%s",buf);
29 write(fd,buf,sizeof(buf));
30 bzero(buf,sizeof(buf));
31 read(fd,buf,BUF_SIZE-1);
32 printf("write end read data:%s\n",buf);
33
34 close(fd);
35 return 0;
36
37
38 }
安装字符设备驱动模块,并查看申请的设备号,主设备号为236
查看内核打印信息
动态申请设备号成功,主设备号为236,次设备号为0、设备创建成功、设备初始化成功、信息关联成功
创建一个字符设备文件,名字可以随便起,但是设备号一定要是刚刚申请到的,比如我申请的就是,主设备号236,次设备号0
sudo mknod /dev/testchrdev0 c 236 0
sudo chmod 777 /dev/testchrdev0 修改权限
运行测试程序
完整内核打印信息
分别是动态申请设备号、创建设备、初始化设备、关联设备、打开设备文件、读、写、读、关闭文件,与所作操作一致,验证完毕。
7.3 总结
静态申请设备号时需要明确知道系统里面哪些主设备号是没有用的,可以将还没有用的主设备号进行静态分配。
动态申请设备号时,主设备号会优先使用234-255之间的号码。一般情况下建议使用动态分配的方式,这样主设备号不容易造成冲突。
接下来是自动创建设备文件。