5.3.7.自动创建字符设备驱动的设备文件 class_create device_create

news2024/12/23 9:10:39

5.3.7.自动创建字符设备驱动的设备文件
5.3.7.1、问题描述:


(1)整体流程回顾
(2)使用mknod创建设备文件的缺点
(3)能否自动生成和删除设备文件
5.3.7.2、解决方案:udev是PC机(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3)应用层启用udev,内核驱动中使用相应接口
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除


5.3.7.3、内核驱动设备类相关函数
(1)class_create
(2)device_create
5.3.7.4、编程实践

 1.module_test.c 驱动程序

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>      //copy_from_user
#include <linux/errno.h>  // 错误码
#include <mach/regs-gpio.h>//arch/arm/mach-s5pv210/include/mach/regs-gpio.h  这两个顺序不能放错,c语言基础
#include <mach/gpio-bank.h> //arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>//包含 内核的 memset  、strcmp
#include <linux/io.h>  //ioremap
#include <linux/ioport.h>  //request_mem_region
#include <linux/cdev.h> // cdev结构体和 新注册接口函数
#include <linux/device.h> //class_create



//#define MYMAJOR 200  /* 定义 register_chrdev 注册设备的 主设备号 : 先  cat /proc/devices  查看 200 有没有被占用  ,   新老注册函数 通用*/
#define MYCNT   1      /* 注册 几个次 设备号  ,新注册接口函数 */
#define MYNAME  "test_char" /* 定义 register_chrdev 注册设备的 设备名字  新老注册函数 通用 */

/*********静态内存*****************************/

#define GPJ0CON	  S5PV210_GPJ0CON	   // FD500240 虚拟地址	
#define GPJ0DAT	  S5PV210_GPJ0DAT	   // FD500244


#define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)
/*********静态内存end*****************************/

/*********动态内存*****************************/

#define  GPJ0CON_PA	   0xE0200240          // 寄存器真实物理 地址
#define  GPJ0DAT_PA	   0xE0200244	   // 

unsigned int *pGPJOCON;  // ioremap 返回的 虚拟地址
unsigned int *pGPJODAT;

/*********动态内存end*****************************/


//int mymajor; /* 定义 register_chrdev 注册设备号 (这个是老驱动模块 )*/

char kbuf[100];/* 内核空间的 buf*/

static dev_t mydev; /*register_chrdev_region 第一个参数 主设备号,这里用来接收 一个主次 设备号 ,  新注册接口函数*/

//static struct cdev test_device_cdev; /* 定义一个cdev 结构体变量 test_device_cdev ,cdev_init函数的 第一个参数   新注册接口函数 */
static struct cdev *pcdev;/* 我们不 定义一个cdev 结构体变量 test_device_cdev ,而是定义一个 *pcdev 指针 */
/* 不管 static struct cdev结构体占用多大的内存, 我们这个 *pcdev 指针 只占内存的 4 个字节*/


/*********自动创建字符设备驱动的设备文件*****************************/

static struct class *test_class;

/*********自动创建字符设备驱动的设备文件end*****************************/

/* NOTE  自己定义函数指针  test_chrdev_open  */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
	/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
	printk(KERN_INFO "test_chrdev_open module_test.c->test_chrdev_open \n");  
	
	/* 在应用app.c 执行open 时,就会执行 LED */
	rGPJ0CON = 0x11111111;
	//rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5));  //LED亮
	
	return 0;

} /* test_chrdev_open() */




/* NOTE  自己定义函数指针 test_chrdev_release ,   release对应的就是 close  */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_release \n");


		/* 在应用app.c 执行close 时,就会执行 LED灭 */
	rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
	return 0;
}


static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
{
	int ret = -1;
	
	printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_read \n");
	
	/* 从内核 的 kbuf, 复制到用户的 ubuf  */
	ret = copy_to_user(ubuf,kbuf,size);    /* 成功后 就会拷贝到用户 ubuf */
	if(ret)  /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
	{
		printk(KERN_ERR "copy_to_user  fail \n"); 
		return -EINVAL;
	}
	printk(KERN_INFO "copy_to_user  OK!!! module_test.c->test_chrdev_write\n");
	
	
	
	
	return 0;
}

// 写函数的本质:将应用层 传递过来的数据先 复制到 内核中,然后将之正确的方式写入硬件完成的操作!(数据从应用层到驱动层的复制,)
// 内核有一个 虚拟地址空间,应用层有一个 虚拟地址空间
static ssize_t test_chrdev_write(struct file *file, const char __user *user_buf,
			size_t count, loff_t *ppos)
{
	int ret = -1;
	
	printk(KERN_INFO "test_chrdev_release module_test.c->test_chrdev_write\n");
	
	
	memset(kbuf,0,sizeof(kbuf)); /*清除kbuf */
	//使用改函数将: 应用层传过来的 ubuf 中的内容 拷贝到驱动空间中的 一个 kbuf 中
	/* 不能用memcpy(kbuf,buf); 因为 2 个 不在一个地址空间中,不能 比较 霍元甲和成龙 谁更厉害 ,不在一个年龄段*/
	ret = copy_from_user(kbuf,user_buf,count);    /*  成功后 就会 放到 kbuf 中 */
	if(ret)  /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
	{
		printk(KERN_ERR "copy_from_user fail \n"); 
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user OK!!! module_test.c->test_chrdev_write\n");
	
	
	/* 真正的 驱动的 数据从 应用层 复制 到 驱动中后,我们就要根据这个数据去写硬件的操作,所以下面就应该操作硬件 */
	
	if(kbuf[0] == '1')
	{
		rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5));  //LED亮
	}
	else if (kbuf[0]=='0')
	{
		rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5)); //LED灭
	}

	return 0;
}


//自定义  file_operations 结构体 及其元素填充
/* NOTE  定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,                    /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
	
	.open		= test_chrdev_open,  /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open  函数指针*/
	.release	= test_chrdev_release,         /* release对应的就是 close    函数指针 */
	.write		= test_chrdev_write, 
	.read		= test_chrdev_read, 
};









// 模块安装函数
static int __init chrdev_init(void)
{	
	int retval;  /*register_chrdev_region 的返回值 */
	
	
	printk(KERN_INFO "chrdev_init helloworld init an zhuang qu dong \n");
	// 使用新的cdev接口来注册字符设备驱动
	// 新的接口注册字符设备驱动需要2步
	
	// 第1步:注册/分配主次设备号
	retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME); /* 自动分配 主次设备号*/
	if (retval  < 0) {
		printk(KERN_ERR "Unable to alloc_chrdev_region  %s  error \n",MYNAME);
		goto flag1;
	}
	printk(KERN_INFO " alloc_chrdev_region  success ok \n");
	printk(KERN_INFO "major = %d, minor = %d \n", MAJOR(mydev), MINOR(mydev));  /* MAJOR(mydev), MINOR(mydev)用这两个宏打印 主次设备号*/

#if 0
	mydev = MKDEV(MYMAJOR,0);/* MKDEV : 算出 主 次 设备号 ,我们这里有 主设备号200,  次设备号起始为 0 */
	retval = register_chrdev_region(mydev, MYCNT, MYNAME); 
	if (retval) {
		printk(KERN_ERR "Unable to register minors for %s  error \n",MYNAME);
		return -EINVAL;
	}
	printk(KERN_INFO " register_chrdev_region success ok \n");
#endif	
	// 第2步:注册字符设备驱动
	pcdev = cdev_alloc();  /* 给 pcdev 分配内存,指针实例化 */
	//cdev_init(pcdev, &test_fops);/* 初始化,两个参数都是取地址,说明都是指针,cdev 结构体变量 test_device_cdev和 file_operations 结构体变量 test_fops*/
	pcdev->owner = THIS_MODULE; /* THIS_MODULE 这个是test_fops 结构体 的一个 元素 */
	pcdev->ops = &test_fops;     /* 这两行就是 代替 cdev_init(pcdev, &test_fops); */
	
	retval = cdev_add(pcdev, mydev, MYCNT);  /* 完成真正的 驱动注册*/
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add error error \n");
		goto flag2;
	}
	printk(KERN_INFO " cdev_add success ok \n");
	
	// 第3步:注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
	// 给udev,让udev自动创建和删除设备文件
	test_class = class_create(THIS_MODULE, "liang_class");  /*THIS_MODULE 是内核提供的 宏,"liang_class" 这是 一个 类 名  */

	if (IS_ERR(test_class)) {
		printk(KERN_ERR "liang_class:"  "Could not register class 'liang_class'\n");
		return -EINVAL;
	}
	
	// device_create 最后1个参数字符串"test",就是我们将来要在/dev目录下创建的设备文件的名字
	// 所以我们这里要的文件名是/dev/test
	device_create(test_class, NULL, mydev, NULL, "test");
	
	
	
	//使用动态映射 操作 寄存器   第3步:
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON_PA")) /* 向内核申请(报告)需要映射的内存资源。*/
		goto flag3;
		
		
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CDAT_PA")) /* 向内核申请(报告)需要映射的内存资源。*/
		goto flag3;
		
	//第4步:	
	pGPJOCON = ioremap(GPJ0CON_PA, 4);//真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址
	pGPJODAT = ioremap(GPJ0CON_PA+4, 4);//真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址
	
	
	*pGPJOCON = 0x11111111;
	*(pGPJOCON+1) = ((0<<3)|(0<<4)|(0<<5));  //LED亮	//*pGPJODAT = ((0<<3)|(0<<4)|(0<<5));  等同于 *(pGPJOCON+1) = ((0<<3)|(0<<4)|(0<<5)); 
	
	return 0;
	
// 如果第4步才出错跳转到这里来	

// 如果第3步才出错跳转到这里来
flag3:
	// 真正注销字符设备驱动用cdev_del
	cdev_del(pcdev);

// 如果第2步才出错跳转到这里来
flag2:
	// 在这里把第1步做成功的东西给注销掉
	unregister_chrdev_region(mydev, MYCNT);  /* 去注销申请的主次设备号 */
// 如果第1步才出错跳转到这里来
flag1:	
	return -EINVAL;	

}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit xie zai \n");

	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	cdev_del(pcdev);
	// 第二步去注销申请的主次设备号
	unregister_chrdev_region(mydev, MYCNT);
	
	//删除 设备类 操作: device_destroy 和   class_destroy
	device_destroy(test_class, mydev);

	class_destroy(test_class);
	

/**** 动态内存 释放   ****/
	*pGPJODAT = ((1<<3)|(1<<4)|(1<<5));   //LED灭
	iounmap(pGPJOCON);//解除映射时,传给他一个虚拟地址
	iounmap(pGPJODAT);//解除映射时,传给他一个虚拟地址
	
	release_mem_region(GPJ0CON_PA,4);//释放申请的内存 物理内存
	release_mem_region(GPJ0DAT_PA,4); //释放申请的内存
	
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

2. app.c 和 Makefile 无更改

运行结果:

 代码大概浏览:

5.3.8.设备类相关代码分析1
5.3.8.1、sys文件系统简介
(1)sys文件系统的设计思想
(2)设备类的概念
(3)/sys/class/xxx/中的文件的作用

5.3.8.2、    class_create 代码 解析

 

class_create
	__class_create
		__class_register
			kset_register
				kobject_uevent

5.3.8.3、   device_create 代码 解析

device_create
	device_create_vargs
		kobject_set_name_vargs
		device_register
			device_add
				kobject_add
					device_create_file
					device_create_sys_dev_entry
					devtmpfs_create_node
					device_add_class_symlinks
					device_add_attrs
					device_pm_add
					kobject_uevent
	好难  看不懂了

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/856634.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言笔记6

关于microsoft visual 的学习笔记 CtrlF5就是启动编译程序 先CtrlA进行全选&#xff0c;然后AitF8就自动的调节代码的格式 #include <stdio.h> #include <stdlib.h> int main() {//system启动程序(在一个程序中启动另外一个程序)//如果程序环境变量中找不到程序&am…

OpenCV实战(29)——视频对象追踪

OpenCV实战&#xff08;29&#xff09;——视频对象追踪 0. 前言1. 追踪视频中的对象2. 中值流追踪器算法原理3. 完整代码小结系列链接 0. 前言 我们已经学习了如何跟踪图像序列中点和像素的运动。但在多数应用中&#xff0c;通常要求追踪视频中的特定移动对象。首先确定感兴趣…

FFmpeg安装和使用

sudo apt install ffmpeg sudo apt-get install libavfilter-devcmakelist模板 CMakeLists.txt cmake_minimum_required(VERSION 3.16) project(ffmpeg_demo)# 设置ffmpeg依赖库及头文件所在目录&#xff0c;并存进指定变量 set(ffmpeg_libs_DIR /usr/lib/x86_64-linux-gnu) …

SpringBoot自动装配及run方法原理探究

自动装配 1、pom.xml spring-boot-dependencies&#xff1a;核心依赖在父工程中&#xff01;我们在写或者引入一些SpringBoot依赖的时候&#xff0c;不需要指定版本&#xff0c;就因为有这些版本仓库 1.1 其中它主要是依赖一个父工程&#xff0c;作用是管理项目的资源过滤及…

冠达管理:“高温超导”不是“室温超导”,5天4板百利电气再次澄清

短短半个月&#xff0c;“室温超导”在惊喜、质疑间回转&#xff0c;但资本市场对“超导概念股”的炒作还在进行&#xff0c;8月7日室温超导概念持续疯涨。同花顺显现&#xff0c;到8月7日收盘&#xff0c;18只超导概念股中&#xff0c;有16只股票飘红。 广东研山私募证券投资&…

如何将GPS坐标点如何网格化?

目录 题主问题&#xff1a; 解答&#xff1a; 高效判断点是否在正六边形蜂窝内的方法 代码实现&#xff1a;ArcGIS中实现指定面积蜂窝&#xff08;正六边形&#xff09;方法 碰巧自己前段时间处理过类似的数据&#xff0c;讲一下自己的解决思路。 题主问题&#xff1a; 解…

【小练习】交互式网格自定义增删改(进行中)

学习SQL和PLISQL数据类型的区别和应用场景 Oracle plsql 基础篇1 数据类型以及流程控制_bb_tarek的博客-CSDN博客https://blog.csdn.net/bb_tarek/article/details/17555713?ops_request_misc&request_id&biz_id102&utm_termplsql%E5%9F%BA%E6%9C%AC%E6%95%B0%E6…

9.异常

文章目录 9.1 Java 异常类层次结构图9.2 Throwable 类常用方法9.3 try-catch-finally9.4使用 try-with-resources 来代替try-catch-finally 9.1 Java 异常类层次结构图 在 Java 中&#xff0c;所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个…

CentOS安装Postgresql

PG基本安装步骤 安装postgresql&#xff1a; sudo yum install postgresql-server初始化数据库&#xff1a;安装完毕后&#xff0c;需要初始化数据库并创建初始用户&#xff1a; sudo postgresql-setup initdb启动和停止服务&#xff1a; sudo systemctl start postgresql sudo…

06微服务间的通信方式

一句话导读 微服务设计的一个挑战就是服务间的通信问题&#xff0c;服务间通信理论上可以归结为进程间通信&#xff0c;进程可以是同一个机器上的&#xff0c;也可以是不同机器的。服务可以使用同步请求响应机制通信&#xff0c;也可以使用异步的基于消息中间件间的通信机制。同…

【TS第三讲】完善TS开发环境

文章目录 &#x1f31f; 写在前面&#x1f31f; ts-node&#x1f31f; nodemon&#x1f31f; nodemon文件类型&#x1f31f; nodemon文件范围&#x1f31f; 写在最后 &#x1f31f; 写在前面 &#x1f525;探索TypeScript世界&#xff0c;驭Vue3Ts潮流&#xff0c;开启前端之旅…

【Ubuntu】简化反向代理和个性化标签页体验

本文将介绍如何使用Docker部署Nginx Proxy Manager和OneNav&#xff0c;两个功能强大且易用的工具。Nginx Proxy Manager用于简化和管理Nginx反向代理服务器的配置&#xff0c;而OneNav则提供个性化的新标签页体验和导航功能。通过本文的指导&#xff0c;您将学习如何安装和配置…

【打印整数二进制的奇数位和偶数位】

打印整数二进制的奇数位和偶数位 1.题目 获取一个整数二进制序列中所有的偶数位和奇数位&#xff0c;分别打印出二进制序列 2.题目分析 打印一个整数的二进制位中的偶数位和奇数位&#xff0c;可以对整数进行移位操作&#xff0c;再将移位的二进制位与1进行&操作。 按位&a…

HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制概述

ArkUI通过自定义组件的build()函数和builder装饰器中的声明式UI描述语句构建相应的UI。 在声明式描述语句中开发者除了使用系统组件外&#xff0c;还可以使用渲染控制语句来辅助UI的构建&#xff0c;这些渲染控制语句包括控制组件是否显示的条件渲染语句&#xff0c;基于数组数…

Rocky Linux更换为国内源

Rocky Linux提供的可供切换的源列表&#xff1a;Mirrors - Mirror Manager 其中以 COUNTRY 列为 CN 的是国内源。 选择其中一个Rocky Linux 源使用帮助 — USTC Mirror Help 文档 操作前请做好备份 对于 Rocky Linux 8&#xff0c;使用以下命令替换默认的配置 sed -e s|^mirr…

Java用方法实现登录名和密码的校验

Java用方法实现登录名和密码的校验 需求分析代码实现小结Time 需求分析 系统正确的登录名和密码是:学习/123&#xff0c;请在控制台开发一个登录界面&#xff0c;接收用户输入的登录名和密码&#xff0c;判断用户是否登录成功&#xff0c;登录成功后展示:“欢迎进入系统!”&…

一文5000字详解Python中PO模式的设计与实现

在使用 Python 进行编码的时候&#xff0c;会使用自身自带的编码设计格式&#xff0c;比如说最常见的单例模式等。本文将为大家介绍PageObject自动化设计模式(PO模式)的设计与实现&#xff0c;感兴趣的可以了解一下 在使用 Python 进行编码的时候&#xff0c;会使用自身自带的…

Nginx(3)

目录 1.Nginx虚拟主机1.1基于IP虚拟主机1.2基于端口虚拟主机1.3基于域名实现的虚拟主机 2.日志详解 1.Nginx虚拟主机 虚拟主机&#xff0c;Nginx配置中的多个server{}区域对应不同的业务(站点) 虚拟主机方式基于域名的虚拟主机不同的域名访问不同的站点基于IP的虚拟主机不同的…

纯跟踪(Pure Pursuit)路径跟踪算法研究(1)

纯跟踪(Pure Pursuit)路径跟踪算法研究&#xff08;1&#xff09; 下午主要读了几篇论文 《自动泊车路径纯跟踪算法应用研究》 《基于纯追踪算法和樽海鞘优化算法的无人驾驶路径跟踪算法研究》 《基于自适应PP和MPC的智能车辆路径跟踪控制》 首先在公式推导方面还不是很清晰 最…

缓解针对LLM应用程序的存储提示注入攻击

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 LLM提供提示文本&#xff0c;并根据其已训练和访问的所有数据进行响应。为了用有用的上下文补充提示&#xff0c;一些 AI 应用程序捕获来自用户的输入&#xff0c;并在将最终提示发送到 LLM 之前将用户看不…