linux驱动的学习 驱动开发初识

1 设备的概念

在学习驱动和其开发之前,首先要知道所谓驱动,其对象就是设备

1.1 主设备号&次设备号:

在Linux中,各种设备都以文件的形式存在/dev目录下,称为设备文件最上层的应用程序可以打开,关闭,读写这些设备文件,从而完成对设备的操作

为了管理这些设备,系统为设备编了号,每个设备都拥有主设备号次设备号主设备号用于区分不同种类的设备,而次设备号用于区分同一类型的多个设备(对于常用的设备如硬盘,Linux赋予的主设备号一般是3)

  • 在/dev目录下输入“ls -l”,就可以看到设备文件对应的主次设备号

1.2 设备号的用处

在了解了什么是主次设备号之后,就要了解设备号的用处:

  • 用户态中:当用户调用了如open, read, write等函数想要操作设备文件时,需要两个参数,第一个是文件名,第二个就是设备号
  • 内核态中:存在着一个驱动链表,用于管理所有设备的驱动,而驱动在链表中的位置就由设备号来检索

2 驱动的概念

参考:最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客

Q1:什么是驱动?

A:驱动与底层硬件直接打交道,充当了硬件(设备)与应用软件中间的桥梁


Q2:驱动的功能?

  1. 对设备初始化和释放
  2. 把数据从内核传送到硬件 & 从硬件读取数据
  3. 读取应用程序传送给设备文件的数据 & 回送应用程序请求的数据
  4. 检测和处理设备出现的错误

Q3:驱动的分类?

Linux驱动分为三个基础大类:字符设备驱动块设备驱动网络设备驱动

  • 字符设备(Char Device)
  1. 字符设备是个能够像字节流(类似文件)一样被访问的设备
  2. 对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生
  3. 字符设备驱动程序通常至少要实现open、close、read和write系统调用
  4. 比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输
  • 块设备(Block Device)
  1. 一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备
  2. 块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方
  3. 块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问
  4. 只有一个块设备可以支持一个安装的文件系统
  5. 比如我们常见的电脑硬盘、SD卡、U盘、光盘等
  • 网络设备(Net Device)
  1. 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备
  2. 访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点
  3. 内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(socket函数)而不是read、write等
  4. 比如我们常见的网卡设备、蓝牙设备

回顾上节Linux系统的构造:

可见,驱动位于内核态,上面是系统调用;下面是硬件(设备)

2.1 上层用户操控设备的流程:

每一个系统调用,在驱动中都有与之对应的一个驱动函数

以open为例:其系统调用为sys_open,那么驱动文件中就会有一个与其对应的“实现open的驱动函数:xxx_open()”,其大致步骤就是:用户层C库的open系统调用层的open驱动函数的open

以上是一个我作为初学者总结的大概流程,下图是一个更详细的流程:

2.2 Linux驱动的运行方式

  1. 驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序
  2. 驱动编译成模块(Linux 下模块扩展名为.ko),并在Linux 内核启动以后使用相应命令加载驱动模块

3 驱动开发实战

尝试使用“将驱动编译成模块的方式”来编写“字符设备的驱动

驱动开发不是一件容易的事情,对于初学者,可以先根据一个固定的框架来学习驱动的开发:

3.1 基本的字符设备驱动框架

注释很重要,认真看

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件static struct class *pin4_class;  
static struct device *pin4_class_dev;static dev_t devno;                //设备号
static int major =231;  		   //主设备号
static int minor =0;			   //次设备号
static char *module_name="pin4";   //模块名//_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n");  //内核的打印函数和printf类似return 0;
}//_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_write\n");  //内核的打印函数和printf类似return 0;
}static struct file_operations pin4_fops = { //结构体的类型是“file_operations”,名字可以自定义
//该结构体的成员就包含实现open和write的驱动函数
//当上层用户想要open或者write这个设备时,就会最终跳转到这个驱动代码中实现的open和write操作函数
//此处只赋值了该结构体中的三个成员变量(在keil中是不能这样写的,linux中可以),这个结构体其实有很多成员,如果想要实现更多的驱动函数,可以把更多的该结构体成员赋值并在这段代码中重写.owner = THIS_MODULE,.open  = pin4_open,.write = pin4_write,
};int __init pin4_drv_init(void)   //真实驱动入口
{int ret;devno = MKDEV(major,minor);  //创建设备号ret   = register_chrdev(major, module_name, &pin4_fops);  //注册驱动,告诉内核:把这个驱动加入到内核驱动的链表中//以下两句代码目的是“生成设备文件”,也可以通过“mknod”命令手动生成,但是一般不会这样做pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //先创建‘类’pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //再创建‘设备’return 0;
}void __exit pin4_drv_exit(void)
{device_destroy(pin4_class,devno); //先销毁‘设备’class_destroy(pin4_class); //在销毁‘类’unregister_chrdev(major, module_name);  //卸载驱动
}module_init(pin4_drv_init);  //入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");    //linux内核遵循GPL协议

在代码中添加大量“static”的原因是:内核的代码太多了,为了防止出现重名导致歧义,加入static可以保证变量的作用域只在当前代码中,从而不会影响到其他的代码。

file_operation 结构体是 Linux 内核驱动操作函数集合

比如,我在写某设备的驱动文件,并想为其实现一个read的驱动函数,那么我就需要:

  1. 定义一个类型为file_operation的结构体XXX,其中XXX是自定义的结构体名
  2. 在XXX结构体中使用“.read = XXX_read,”,其中XXX_read是自定义的函数名
  3. 在驱动代码里重写XXX_read函数,函数的参数格式和file_operation类型结构体中给出的write成员的参数格式保持一致,函数体就是想要实现的具体内容

3.2 驱动的编译

  • 打开虚拟机,进入Linux源码的路径:

  • 然后进入“drivers/char/”子目录(driver:驱动;char:字符型设备):

  • 在这个路径下创建一个新的C文件"mydriver_pin4.c",内容为刚刚的字符驱动框架:

  • 修改当前路径下的Makefile,确保这个新的驱动会被编译到:

  • 回到linux内核源码的路径,运行以下指令尝试编译:
ARCH=arm CROSS_COMPILE=/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules

回顾在前几节学习内核编译时的代码:

ARCH=arm CROSS_COMPILE=/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNEL=kernel7 make -j4 zImage modules dtbs

区别就是:代码的最后少了“zImage”和“dtbs”。原因也很简单,现在只需要编译驱动,不需要再次生成镜像文件等其他文件。

  • 将编译好的“mydriver_pin4.ko”通过以下的scp命令发送到树莓派:
scp drivers/char/mydriver_pin4.ko pi@192.168.2.26:/home/pi/mjm_code

 此时,编译好的驱动文件就出现在树莓派上了:

3.3 驱动的加载(卸载)

由于现在刚刚把驱动编译成了.ko的模块,所以需要运行以下指令来加载驱动模块:

sudo insmod mydriver_pin4.ko

补充:

  • 驱动的卸载:
sudo rmmod mydriver_pin4.ko //此时驱动名字后不用加".ko"
  • 查看内核模块:
lsmod

 运行成功后,就可以在/dev下看到生成的设备文件“pin4”了:

如果用“ls -l” ,就可以看到这个设备文件的主次设备号,和框架代码中的设置相同:

 

3.4 驱动的测试

在树莓派下写一个测试驱动的C代码:

pin4_test.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>int main()
{int driver_fd;driver_fd = open("/dev/pin4",O_RDWR); //以可读可写打开的方式打开驱动if(driver_fd < 0){perror("fail to open driver file:");}else{printf("open driver file success!\n");}driver_fd = write(driver_fd,'a',1); //向驱动文件写一个字节if(driver_fd < 0){perror("fail to write to driver file:");}else{printf("write success!\n");}return 0;
}

编译然后运行:

1. gcc pin4_test.c -o pin4_test
2. sudo ./pin4_test

如果加了sudo还是没法运行,可以根据错误提示修改,可能需要给驱动文件一个执行权限:

sudo chmod 666 /dev/pin4
//666代表让所有用户都有所有权限

 可见,运行成功,没有报错!此时还可以另开一个窗口输入“dmesg”查看内核打印的信息:

 可见内核也按照框架代码中的printk成功打印了信息!驱动测试成功!

同时,结果也再次印证了:当用户在最上层对 驱动文件 调用C库的open函数后,最后的结果还是调用最底层 驱动文件里实现的open驱动函数

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生之深入解析减少Docker镜像大小的优化技巧

一、什么是 Docker&#xff1f; Docker 是一种容器引擎&#xff0c;可以在容器内运行一段代码&#xff0c;Docker 镜像是在任何地方运行应用程序而无需担心应用程序依赖性的方式。要构建镜像&#xff0c;docker 使用一个名为 Dockerfile 的文件&#xff0c;Dockerfile 是一个包…

数据分析场景下,企业大模型选型的思路与建议

来源/作者&#xff1a;爱分析 随着大模型带来能力突破&#xff0c;让AI与数据分析相互结合&#xff0c;使分析结果更好支撑业务&#xff0c;促进企业内部数据价值释放&#xff0c;成为了当下企业用户尤为关注的话题。本次分享主要围绕数据分析场景下大模型底座的选型思路&#…

Kafka 安装与部署

目录 Kafka 下载 &#xff08;1&#xff09;将 kafka_2.11-2.4.1.tgz 上传至 /opt/software/ &#xff08;2&#xff09;解压安装包至 /opt/module/ [huweihadoop101 ~]$ cd /opt/software/ [huweihadoop101 software]$ tar -zxvf kafka_2.11-2.4.1.tgz -C ../module/&#…

什么是供应链安全及其工作原理?

6000公里长的丝绸之路将丝绸、谷物和其他货物从中国运送到帕尔米拉。尽管蒙古治下的和平保护丝绸之路免受海盗、强盗和内部盗窃的侵害&#xff0c;但商人仍然装备精良&#xff0c;并依赖于大型商队旅行和战略性放置的小型堡垒所提供的安全。 为什么供应链安全很重要&#xff1…

Content-Type是什么

目录 Content-Type是什么 获取方式 设置方式 常见类型 application/x-www-form-urlencoded multipart/form-data application/json text/xml text/html text/plain Content-Type是什么 Content-Type出现在请求标头和响应标头中&#xff0c;意思是内容类型&#xff0…

JAVA主流日志框架梳理学习及使用

前言&#xff1a;目前市面上有挺多JAVA的日志框架&#xff0c;比如JUL(JDK自带的日志框架),Log4j,Logback,Log4j2等&#xff0c;有人可能有疑问说还有slf4j&#xff0c;不过slf4j不是一种日志框架的具体实现&#xff0c;而是一种日志门面&#xff08;日志门面可以理解为是一种统…

PyTorch机器学习与深度学习

近年来&#xff0c;随着AlphaGo、无人驾驶汽车、医学影像智慧辅助诊疗、ImageNet竞赛等热点事件的发生&#xff0c;人工智能迎来了新一轮的发展浪潮。尤其是深度学习技术&#xff0c;在许多行业都取得了颠覆性的成果。另外&#xff0c;近年来&#xff0c;Pytorch深度学习框架受…

定制 Electron 窗口标题栏

Electron 是一款流行的桌面应用开发框架&#xff0c;基于 Web 技术构建&#xff0c;提供了强大的跨平台能力。在开发过程中&#xff0c;经常需要定制窗口标题栏以创造独特的用户体验。 1. 完全隐藏默认标题栏 有时候&#xff0c;我们希望创建一个自定义的标题栏&#xff0c;完…

通话状态监听-Android13

通话状态监听-Android13 1、Android Telephony 模块结构2、监听和广播获取通话状态2.1 注册2.2 通话状态通知2.3 通话状态 3、通知状态流程* 关键日志 frameworks/base/core/java/android/telephony/PhoneStateListener.java 1、Android Telephony 模块结构 Android Telephony…

Arma3/武装突袭3东风战役最后一关游戏无法保存的解决办法

Arma3这个游戏玩进去还是非常有可玩性的&#xff0c;可是在玩过了它本体自带的东风系列战役后&#xff0c;在最精髓的最后一关——game over这个关卡&#xff0c;却有个非常头疼的问题。 逃跑其实是非常简单的&#xff0c;但是想要无伤环游全岛确十分困难&#xff0c;因为这关卡…

【密码学】群的证明(习题)

0.前置知识 1.习题 记录一次密码学作业~群的判定 2.求解

MATLAB 点云中心化 (40)

MATLAB 点云中心化 一、算法介绍二、算法实现一、算法介绍 使用点云集合中的坐标计算质心,这里将其作为中心,将每个点坐标减去该中心坐标,即可得到中心化的点云,这在很多处理中是必须进行的一个步骤:相当于点云移动到以质心为原点的坐标系 (主要是计算质心和点云偏移两个…

AI日报:OpenAI扩大创业基金计划

欢迎订阅专栏 《AI日报》 获取人工智能邻域最新资讯 文章目录 OpenAI拓宽Converge启动程序变压器模型背后的思想建立启动融资新闻AutoGen AI支点其他 OpenAI拓宽Converge启动程序 ChatGPT制造商OpenAI正在扩大其Converge AI创业计划。 OpenAI的Converge产品于2022年12月首次…

构建高效持久层:深度解析 MyBatis-Plus(02)

目录 引言1. 逻辑删除1.1 概述1.2 逻辑删除的优势1.3.为什么使用逻辑删除1.4 综合案例 2. 乐观锁和悲观锁2.1.什么是乐观锁和悲观锁2.2.乐观锁和悲观锁的区别2.3.综合案例 3. 分页插件总结 引言 在现代软件开发中&#xff0c;数据库操作是不可或缺的一环。为了提高系统的性能、…

【马来西亚会议】第四届计算机技术与全媒介融合设计国际学术会议(CTMCD 2024)

第四届计算机技术与全媒介融合设计国际学术会议&#xff08;CTMCD 2024) 2023 4th International Conference on Computer Technology and Media Convergence Design 第四届计算机技术与全媒介融合设计国际学术会议&#xff08;CTMCD 2024&#xff09;将于 2024年2月23日-25日…

数据分析思维导图

参考&#xff1a; https://zhuanlan.zhihu.com/p/567761684?utm_id0 1、数据分析步骤地图 2、数据分析基础知识地图 3、数据分析技术知识地图 4、数据分析业务流程 5、数据分析师能力体系 6、数据分析思路体系 7、电商数据分析核心主题 8、数据科学技能书知识地图 9、数据挖掘…

机器学习算法---回归

类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统计学检验箱…

算法:程序员的数学读书笔记

目录 ​0的故事 ​一、按位计数法 二、不使用按位计数法的罗马数字 三、十进制转二进制​​​​​​​ ​四、0所起到的作用​​​​​​​ 逻辑 一、为何逻辑如此重要 二、兼顾完整性和排他性 三、逻辑 四、德摩根定律 五、真值表 六、文氏图 七、卡诺图 八、逻…

详解—C++ [异常]

目录 一、C语言传统的处理错误的方式 二、C异常概念 三、异常的使用 3.1 异常的抛出和捕获 3.2 异常的重新抛出 3.3异常安全 3.4 异常规范 四、自定义异常体系 五、C标准库的异常体系 六、异常的优缺点 6.1、C异常的优点&#xff1a; 6.2、C异常的缺点&#xff1a;…

设计模式 原型模式 与 Spring 原型模式源码解析(包含Bean的创建过程)

原型模式 原型模式(Prototype模式)是指&#xff1a;用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型&#xff0c;创建新的对象。 原型模式是一种创建型设计模式&#xff0c;允许一个对象再创建另外一个可定制的对象&#xff0c;无需知道如何创建的细节。 工作原…