驱动学习(四)字符设备

本文详细介绍了字符设备与块设备的概念、应用实例,以及驱动程序开发框架,包括设备号的申请、字符设备的创建与删除、数据在用户空间和内核空间的拷贝,以及动态和静态设备号的使用。通过实例演示了如何在Linux环境下编写字符设备驱动并进行测试。
摘要由CSDN通过智能技术生成

驱动学习(四)字符设备

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之间的号码。一般情况下建议使用动态分配的方式,这样主设备号不容易造成冲突。

接下来是自动创建设备文件

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值