当前位置: 首页 > news >正文

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

目录

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:用于动态分配的主要设备号或02. 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.88all: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 cleanrm -rf modules.orderrm -f hello_drv_testobj-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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • ARM CoreLink 系列 5.1.1 -- CI-700 System Address Map 】
  • 全开源智慧停车场微信小程序源码/智能停车系统源码/停车自助缴费系统/停车场管理收费+物业管理+物联网+自助缴费功能
  • MySQL- 索引下推
  • C++ 知识点(长期更新)
  • 分布式版本控制概述
  • 如何使用 Go 连接 MO
  • 检索增强生成算法
  • CMake常用语法、函数
  • NacosRce到docker逃逸实战
  • HCIP第九章(MPLS理论)
  • Spring Cloud全解析:配置中心之springCloudConfig使用消息总线进行动态刷新
  • 测试金山文档 | WPS云文档
  • 使用Java调用Apache commons-text求解字符串相似性实战
  • Spring中的BeanFactoryAware
  • OCR调研
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • 0x05 Python数据分析,Anaconda八斩刀
  • C++类的相互关联
  • CentOS 7 防火墙操作
  • CSS居中完全指南——构建CSS居中决策树
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • HomeBrew常规使用教程
  • java8 Stream Pipelines 浅析
  • JDK 6和JDK 7中的substring()方法
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • SegmentFault 2015 Top Rank
  • Spring Boot快速入门(一):Hello Spring Boot
  • Tornado学习笔记(1)
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 从重复到重用
  • 大整数乘法-表格法
  • - 概述 - 《设计模式(极简c++版)》
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 记一次和乔布斯合作最难忘的经历
  • 跨域
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • ​渐进式Web应用PWA的未来
  • ​马来语翻译中文去哪比较好?
  • #pragam once 和 #ifndef 预编译头
  • $().each和$.each的区别
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (2)Java 简介
  • (2024,Vision-LSTM,ViL,xLSTM,ViT,ViM,双向扫描)xLSTM 作为通用视觉骨干
  • (7)摄像机和云台
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (阿里云在线播放)基于SpringBoot+Vue前后端分离的在线教育平台项目
  • (八十八)VFL语言初步 - 实现布局
  • (二)Linux——Linux常用指令
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • .DFS.
  • .gitattributes 文件
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .net Signalr 使用笔记
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?