Linux驱动入门实验班——Hello驱动(后附百问网课程视频链接)

news2024/9/21 20:33:43

目录

1.如何编写驱动程序

2.编写驱动程序

①确定主设备号

         register_chrdev函数 

②file_operations结构体

③实现对应的函数,填入结构体 

        copy_from_user函数

        copy_to_user函数

④注册驱动程序

          方式一

        方式二

⑤入口函数

⑥出口函数

⑦提供设备信息,创建设备节点

        class_create()函数

        device_creat()函数

3.驱动程序源码

4.测试程序源码

5.Makefile

6.APP使用驱动的4种方式 

7.中断引入


1.如何编写驱动程序

2.编写驱动程序

①确定主设备号

         register_chrdev函数 

函数原型:
    int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
函数功能:

    为字符设备注册一个主号码。
入参:

    1. unsigned int major:用于动态分配的主要设备号或0,如果给0内核回自动分配主设备号

    2. const char *name:这一系列设备的名称

    3. const struct file_operations:与此设备相关联的文件操作
回参:
    返回主设备号

②file_operations结构体

static void __init hello_exit(void)
{
	
	class_destroy(hello_class);
	device_destroy(hello_class, MKDEV(major, 0));
	unregister_chrdev(major,"hello");
}

③实现对应的函数,填入结构体 

        copy_from_user函数

函数原型:
    copy_from_user(void *to, const void __user *from, unsigned long n)
作用:
    将@form地址中的数据拷贝到@to地址中去,拷贝长度是n
入参:
    1. to 将数据拷贝到内核的地址
    2. from 需要拷贝数据的地址
    3. n 拷贝数据的长度(字节)
回参:
    失败返回没有被拷贝的字节数,成功返回0.

        copy_to_user函数

函数原型:
    long copy_to_user(void __user *to, const void *from, unsigned long n);
作用:
    将内核空间的数据复制到用户空间。
入参:
    1. void __user *to:这是用户空间的目标地址,数据将被复制到这个地址。
    2. const void *from:这是内核空间的源地址,数据将从这个地址被复制。
    3. unsigned long n:要复制的字节数。
回参:
    返回值是一个 long 类型;
    如果复制成功,返回 0;
    如果在复制过程中发生错误,返回一个负数,表示未能成功复制的字节数。

④注册驱动程序

          方式一

register_chrdev函数 

函数原型:
    int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
函数功能:

    为字符设备注册一个主号码。
入参:

    1. unsigned int major:用于动态分配的主要设备号或0

    2. const char *name:这一系列设备的名称

    3. const struct file_operations:与此设备相关联的文件操作

        方式二

alloc_chrdev_region函数

函数原型:
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
函数功能:
    alloc_chrdev_region 更简便、更智能的方法是让内核给我们自动分配一个主设备号
入参:
    
    1. dev_t *dev : 输出型参数 ,就是要分配的主 次 设备号 
 
    2. unsigned baseminor : 次设备号的 起始 号
 
    3. unsigned count : 几个 次设备号
 
    4. const char *name :  设备名字
回参:
    小于<0 代表错误

函数使用:
int ret; //记录返回值
static dev_t mydev; /*这里用来接收 一个主次 设备号 , */
dev_t 类型的变量 !!!
 
ret = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME); /* 自动分配 主次设备号*/
 
12: 代表次设备号 从 12 开始

cdv_init函数 

函数原型:
    int cdv_init(struct device *dev, const struct device_driver *drv, void *data);
函数功能:
    cdv_init 函数主要负责完成以下任务:
    初始化设备相关的硬件资源,如寄存器配置、中断设置等。
    将设备与对应的驱动进行关联和注册。
    分配和初始化与设备驱动相关的内存和数据结构。

入参:   
    1. struct device *dev:指向要初始化的设备结构体的指针,包含了设备的相关信息,如设备的名称、属        性等。
    
    2. const struct device_driver *drv:指向设备驱动结构体的指针,包含了驱动的相关信息,如驱动的名称、操作函数等。
     
    3. void *data:一个通用的指针,用于传递特定于驱动或设备的私有数据。

回参:
    返回 0 表示初始化成功。
    返回非零值(通常是负数)表示初始化过程中出现错误,不同的非零值可能代表不同的错误码。

cdv_add函数 

函数原型:
    int cdv_add(struct some_struct *param1, int param2, void *param3);
函数功能:
    “cdv_add”函数用于在 Linux 驱动中执行添加某个对象、资源或配置的操作。

入参:   
    1. struct some_struct *param1:可能是一个指向特定数据结构的指针,该数据结构包含了与要添加的对象相关的详细信息。

    2. int param2:可能是一个标志位、索引值或其他整数类型的参数,用于指定添加操作的某些特定条件或选项。

    3. void *param3:通用的指针,可能用于传递额外的自定义数据。

回参:
    返回 0 表示添加成功。
    返回负数表示添加过程中出现错误,不同的负数可能代表不同的错误类型。

⑤入口函数

static int __init hello_init(void)

写好入口函数后,需要使用module_init告诉内核这是入口函数

module_init(hello_init);

⑥出口函数

static void __init hello_exit(void)

同理,写好出口函数后,需要使用module_exit告诉内核这是出口函数

module_exit(hello_init);

⑦提供设备信息,创建设备节点

        class_create()函数

函数原型:
    #define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})
函数功能:
    宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在/sys/class/目录下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。宏class_create()在实现时,调用了函数__class_create()。

入参:

    1. owner:一个struct module结构体类型的指针,指向函数__class_create()即将创建的、“拥有”这个struct class的模块。一般赋值为THIS_MODULE,此结构体的详细定义见文件include/linux/module.h。

    2. name:char类型的指针,代表即将创建的struct class变量的名字,用于给struct class的name字段赋值。通俗地说,就是指向struct class名称的字符串的指针。

        device_creat()函数

函数原型:
    struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

函数功能:

    函数device_create()用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,\将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。\函数能够自动地在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建与逻辑类对应的设备文件。

入参:

    1.struct class *cls:输入参数代表与即将创建的逻辑设备相关的逻辑类。

    2.struct device *parent:输入参数代表即将创建的逻辑设备的父设备的指针,子设备与父设备的关系是:当父设备不可用时,子设备不可用,子设备依赖父设备,父设备不依赖子设备。

    3.dev_t devt:输入参数是逻辑设备的设备号。

    4.void *drvdata:输入参数是void类型的指针,代表回调函数的输入参数。

    5.const char *fmt:输入参数是逻辑设备的设备名,即在目录/sys/devices/virtual/和/dev创建的逻辑设备目录的目录名。

3.驱动程序源码

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

static int major;
char kernel_buf[1024];
#define MIN(a,b) a > b ? a : b
static struct class *hello_class;


static int hello_open (struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t hello_write (struct file *file, const char __user *buf, size_t size, loff_t *setoff)
{
	int err;
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));
	return MIN(1024, size);
}

static ssize_t hello_read (struct file *file, char __user *buf, size_t size, loff_t *setoff)
{
	int err;
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return MIN(1024, size);
}

static int hello_release (struct inode *node, struct file *file)
{
	return 0;
}

static const struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_open,
	.read    = hello_read,
	.write   = hello_write,
	.release = hello_release,
};

static int __init hello_init(void)
{
	int err;
	 
	major = register_chrdev(0,"hello",&hello_drv);
	hello_class = class_create(THIS_MODULE, "hello");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class))
	{
		unregister_chrdev(major, "hello");
		return -1;
	}

	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");
	return 0;
}

static void __init hello_exit(void)
{
	
	class_destroy(hello_class);
	device_destroy(hello_class, MKDEV(major, 0));
	unregister_chrdev(major,"hello");
}


module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

4.测试程序源码

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	int len;
	char buf[1024];
	
	if(argc < 2)
	{
		printf("Usage : %s -w <string>", argv[0]);
		printf("        %s -r\n", argv[0]);
		return -1;
	}

	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	if ((strcmp(argv[1], "-w")) == 0 && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 1024);
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}

	close(fd);

	return 0;
}

5.Makefile


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f hello_drv_test

obj-m	+= hello_drv.o

使用Makefile编译通过后,将.ko文件可执行程序放到开发板上,先使用insmod装载驱动,然后再执行程序,完成后通过rmmod卸载驱动。

6.APP使用驱动的4种方式 

驱动程序:提供能力,不提供策略。

1.阻塞(休眠唤醒):条件不满足时休眠,条件满足由别人唤醒。

2.非阻塞(查询):不断的查询。

3.poll(定个闹钟)

4.异步通知

7.中断引入

中断处理流程:

①保存现场

②分辨中断源

③调用中断处理函数

④恢复现场

课程链接:

12_Hello驱动上机实验_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1XK411D7wK?p=13&spm_id_from=pageDriver&vd_source=3a9afee9fda50350a1c881b4325e007d

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

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

相关文章

Type-C接口取电芯片-LDR6500

取电芯片&#xff0c;特别是针对Type-C接口的取电芯片&#xff0c;如LDR6328系列&#xff0c;是近年来电子设备领域的一个重要技术组件。这些芯片通过智能协议控制&#xff0c;实现高效、安全的充电过程&#xff0c;并广泛应用于智能手机、平板电脑、笔记本电脑、小家电等各类需…

18. 基于ES实战海量数据检索

18. 基于ES实战海量数据检索 一. 概述二. Elasticsearch 全文检索1. 分布式搜索引擎2. 搜索引擎种类3. 倒排索引三. elastic使用1. 官网介绍2. docker安装3. elasticsearch-head工具4. 分词与内置分词4.1 内置分词器(了解即可)4.2 `IK`中文分词器*****************************…

打造高效信息发布平台小程序:设计思路与实践

在当今这个信息爆炸的时代&#xff0c;信息发布平台已成为连接用户与内容的桥梁&#xff0c;小程序以其独特的优势成为众多企业和个人开发者青睐的选择。开发一款专注于信息发布与共享的小程序&#xff0c;旨在为用户打造一个便捷、高效、互动性强的信息获取平台&#xff0c;具…

使用docker-compose快速部署Prometheus+grafana环境

初始化 Prometheusgrafana 创建相关目录并给予权限&#xff0c;持久化目录需要给777权限&#xff0c;否则容器启动失败 cd ~ && mkdir prometheus && chmod 777 prometheus cd prometheus && mkdir grafana_data prometheus_data && chmod 777…

过滤器实验

1.过滤器实验 首先写两个页面之间能够互相跳转 Test1.html通过超链接跳转到test2.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <!--&l…

LDR6020在Type-C手机同时充电与USB2.0数据传输方案

随着科技的飞速发展&#xff0c;Type-C接口已成为智能手机等移动设备的主流充电和数据传输接口。为了满足用户对于高效充电与稳定数据传输的双重需求&#xff0c;乐得瑞科技推出的LDR6020芯片凭借其卓越的性能和丰富的功能&#xff0c;为Type-C手机提供了同时充电与USB2.0数据传…

欢迪迈手机商城设计与开发

TOC springboot137欢迪迈手机商城设计与开发 绪论** 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0…

JavaScript高阶笔记总结(Xmind格式):第一天

Xmind鸟瞰图&#xff1a; 简单文字总结&#xff1a; js高阶知识总结&#xff1a; 理解Object&#xff1a; 1.返回一个由一个给定对象的自身可枚举属性组成的数组&#xff1a;Object.keys(对象名) 2.in 判断属性是否存在&#xff1a;"属性名" in 对象名 …

巧妙的获取Kimi和Deepseek提示词--结构化提示词真实用啊

文章目录 前言一、Kimi官方提示词Kimi的“提示词专家”Kimi的“爆款网文生成器”Kimi的“小红书爆款生成器”Kimi的“i人嘴替”Kimi的“费曼学习”Kimi的“留学顾问”Kimi的“公文笔杆”Kimi的“公文笔杆”Kimi的“猜猜我在想谁”Kimi的“塔罗师”Deepseek-Chat官方提示词如何防…

JDK源码——ThreadLocal

提供的方法 remove(): 移除当前线程的局部变量值。调用此方法后&#xff0c;当前线程将不再持有任何局部变量值。 set(T): 为当前线程设置一个新的局部变量值。参数T是要设置的值的类型。 get(): 获取当前线程的局部变量值。返回类型为T。 withInitial(Supplier): 创建一个新的…

Java语言程序设计基础篇_编程练习题*16.21(秒表倒计时)

目录 题目&#xff1a;*16.21&#xff08;秒表倒计时&#xff09; 习题思路 代码示例 结果展示 题目&#xff1a;*16.21&#xff08;秒表倒计时&#xff09; 编写一个程序&#xff0c;允许用户在文本域中以秒为单位输入时间&#xff0c;然后按下Enter键来进行倒计时&#xff0…

剪映新手必看!剪映如何实现图片/视频在文字上显示(剪映如何实现图片/视频轨道在文字轨道之上显示/剪映如何实现自由轨道/层级)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 剪映 📒📝 解决方案⚓️ 相关链接 ⚓️📖 介绍 📖 剪映,这款广受好评的视频编辑应用,以其用户友好的界面和强大的功能,成为了许多视频创作者的首选。抱着好奇心,今天我也试用了一下,但在使用过程中,我遇到了一个问…

AI for reading ML paper

心流 心流 Kimi Kimi Humata Humata Bytez Bytez Chatgpt4 scholar 学术版chatgpt4&#xff0c;需要充值&#xff1b; 还有更多AI工具等待你发现&#xff1b;

LVS的NAT模式实战

目录 1.NAT模式的工作原理 2.NAT模式实战---环境准备 1.环境规划 2.克隆主机&#xff0c;生成nat模式的机器 1.设置主机名称 2.更改ip地址 3.添加网卡 4.查看网卡 5.修改网卡配置文件 6.时间同步 7.停用其他服务 8.添加web服务 1.安装nginx 2.修改index.html文件 …

NFS实现多服务器文件的共享

文章目录 一、简介二、部署1、准备1、服务端和客户端&#xff1a;安装nfs-utils2、服务端&#xff1a;创建共享目录3、服务端&#xff1a;配置exports文件4、客户端挂载5、客户端&#xff1a;卸载 三、附录1、NFS服务基本命令2、/etc/exports参数解释3、exportfs命令 参考资料 …

Mysql-B树和B+树的区别

当我们为ID去建立一个主键索引的时候&#xff0c;Mysql底层就会为我们去维护一棵树的结构&#xff0c;从而提升我们的数据检索效率&#xff0c;树的共同特性&#xff1a;小的索引在左边&#xff0c;大的索引在右边&#xff0c;每一次结点的寻址&#xff0c;都是一次磁盘的IO&am…

STM32 F103C8T6学习笔记19:定时器读取旋转编码器

今日学习STM32 F103C8T6 单片机驱动读取旋转编码器&#xff0c;并传输数据给串口&#xff1a; 文章提供测试代码讲解、完整工程下载、测试效果图 目录 旋转编码器&#xff1a; 输入捕获&#xff1a; 硬件连接&#xff1a; 代码编程&#xff1a; 测试效果视频: 测试工程下载&…

Mybatis(2)

目录 一. 参数传递 1. 单个参数 2. 多个参数 二. mybatis增删改 1. 新增 2. 删除 3. 修改 三. mybatis查询 1. 单张表查询 2. 多表查询 2.1 查询单个学生信息 2.2 mybatis自动映射级别 2.3 查询所有学生信息 3. 嵌套查询 3.1 查询单个学生信息 3.2 查询多个学生…

DDColor部署安装,在服务器Ubuntu22.04系统——点动科技

DDColor图片上色项目的部署安装&#xff0c;在服务器Ubuntu22.04系统——点动科技 一、ubuntu22.04基本环境配置1.1 更换清华Ubuntu镜像源1.2 更新包列表&#xff1a;2. 安装英伟达显卡驱动2.1 使用wget在命令行下载驱动包2.2 更新软件列表和安装必要软件、依赖2.2 卸载原有驱动…

24.8.9.11数据结构|链栈和队列

链栈 1、理解 实际上是一个仅在表头进行操作的单链表,头指针指向栈顶结点或头结点,以下恋栈均指带头结点的链栈. 2、 基本操作 1、定义结构&#xff1a;节点含有数据域和指针域 2、初始化操作&#xff1a;建立一个带头结点的空栈 3、取栈顶元素操作&#xff1a;取出栈的栈顶元…