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

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驱动函数

相关文章:

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

1 设备的概念 在学习驱动和其开发之前&#xff0c;首先要知道所谓驱动&#xff0c;其对象就是设备。 1.1 主设备号&次设备号&#xff1a; 在Linux中&#xff0c;各种设备都以文件的形式存在/dev目录下&#xff0c;称为设备文件。最上层的应用程序可以打开&#xff0c;关…...

Node.js中npm中ws的WebSocket协议的实现

在Node.js中&#xff0c;ws是一个非常有用的模块&#xff0c;它提供了WebSocket协议的实现。WebSocket协议是一种在Web浏览器和服务器之间进行双向通信的协议&#xff0c;它可以使得Web应用程序更加交互式和实时。在本文中&#xff0c;我们将详细介绍npm中ws的内容。 ws是什么…...

PHP HTTPoxy CGI 应用程序漏洞 CVE-2016-5385

HTTPoxy CGI 应用程序漏洞 CVE-2016-5385 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议 漏洞名称 漏洞描述 在Oracle Communications BRM 10.x/12.x&#xff08;云软件&#xff09;中发现漏洞。它已经被宣布为关键。此漏洞影响组件用户数据库的未…...

qt-C++笔记之使用QLabel和QPushButton实现一个bool状态的指示灯

qt-C笔记之使用QLabel和QPushButton实现一个bool状态的指示灯 code review! 文章目录 qt-C笔记之使用QLabel和QPushButton实现一个bool状态的指示灯1.QPushButton实现2.QLabel实现2.QLabel实现-对错符号 1.QPushButton实现 运行 代码 #include <QtWidgets>class Ind…...

自动驾驶技术入门平台分享:百度Apollo开放平台9.0全方位升级

目录 平台全方位的升级 全新的架构 工具服务 应用软件&#xff08;场景应用&#xff09; 软件核心 硬件设备 更强的算法能力 9.0版本算法升级总结 更易用的工程框架 Apollo开放平台9.0版本的技术升级为开发者提供了许多显著的好处&#xff0c;特别是对于深度开发需求…...

Elementor Pro v3.18.1和(完整模板套件)介绍说明

WordPress 插件:免费下载 Elementor Pro v3.18.1 免费最新版本 [所有功能已激活] Elementor Pro 是一个功能强大的 WordPress 插件,使用户无需编码即可构建和设计网站。它是 Elementor 页面构建器的付费版本,提供额外的功能和小部件来创建更复杂的设计。在这篇博文中,我们将探讨…...

Windows如何安装使用TortoiseSVN客户端并实现公网访问本地SVN Server

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…...

Mybatis配置-映射器(mappers)

现在&#xff0c;我们已经配置了MyBatis的行为&#xff0c;准备定义我们的映射SQL语句。但首先&#xff0c;我们需要告诉MyBatis在哪里找到它们。在这方面&#xff0c;Java并没有提供很好的自动发现机制&#xff0c;所以最好的方法是直接告诉MyBatis在哪里找到映射文件。 您可以…...

python 音视频合并

目录 moviepy ffmpeg命令合成&#xff1a; 添加字幕文件&#xff1a; 添加字幕文本&#xff1a; pipeline添加字幕&#xff1a; moviepy python&#xff08;opencv pyaudio moviepy&#xff09;实现录制音视频文件并合并_ubuntu使用python的sounddeviceopencv录制音视频…...

HttpUtils——助力高效网络通信

使用HttpClient发送请求、接收响应很简单&#xff0c;一般需要如下几步即可: 1、创建HttpClient对象。 2、创建请求方法的实例&#xff0c;并指定请求URL。如果需要发送GET请求&#xff0c; 创建HttpGet对象&#xff1b;如果需要发送POST请求&#xff0c;创建HttpPost对象。 3…...

WAF绕过常见方法

前面写了WAF如何检测&#xff0c;现在直接上WAF常见的一些绕过方法。 方法1:变换大小写 实例: 比如WAF拦截了union&#xff0c;那就使用Union、UnloN等方式绕过。 方法2:编码绕过 实例1: WAF检测敏感字~&#xff0c;则可以用Ox7e代替&#xff0c;如extractvalue(1,concat(~…...

SpringCloud微服务 【实用篇】| Docker镜像、容器、数据卷操作

目录 一&#xff1a;Docker基本操作 1. 镜像操作 镜像相关命令 2. 容器操作 容器相关命令 3. 数据卷&#xff08;容器数据管理&#xff09; 数据卷 操作数据卷 挂载数据卷 挂载的方式区别 前些天突然发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0…...

OSPF面试总结

OSPF 基本特点 属于IGP、LS支持无类域间路由没有环路&#xff08;区域内运行LS、区域间是DV,所以所有的区域要和区域0相连&#xff09;收敛速度快使用组播发送数据 224.0.0.5、224.0.0.6 什么时候用224.0.0.5&#xff1f;支持多条等价路由支持协议报文认证 OSPF路由的计算过程…...

【算法系列篇】递归、搜索和回溯(四)

文章目录 前言什么是决策树1. 全排列1.1 题目要求1.2 做题思路1.3 代码实现 2. 子集2.1 题目要求2.2 做题思路2.3 代码实现 3. 找出所有子集的异或总和再求和3.1 题目要求3.2 做题思路3.3 代码实现 4. 全排列II4.1 题目要求4.2 做题思路4.3 代码实现 前言 前面我们通过几个题目…...

Windows 系统下本地单机搭建 Redis(一主二从三哨兵)

目录 一、Redis环境准备&#xff1a; 1、下载redis 2、Windows下的.msi安装和.zip格式区别&#xff1a; 二、哨兵介绍&#xff1a; 1、一主二从三哨兵理论图&#xff1a; 2.哨兵的主要功能&#xff1a; 3.哨兵用于实现 redis 集群的高可用&#xff0c;本身也是分布式的&…...

数据库访问被拒怎么操作?

就一点&#xff1a; &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; cmd打开命令窗口直接输入 mysql -u root -p 然后加密码打开数据库服务再去试试&#xff01;&#xff01; &#xff01;&#xff01;&#xff01;&#xff01;&…...

Vue 2 生命周期即将结束

本文章翻译自 Vue 2 is Approaching End Of Life 文章原作者 youyuxi 2024 年即将到来&#xff0c;我们想借此机会提醒 Vue 社区&#xff0c;Vue 2 将于 2023 年 12 月 31 日达到生命周期结束 (EOL) Vue 2.0 于 2016 年发布&#xff0c;已有 7 年多的时间。这是 Vue 成为主流框…...

Python---端口和端口号的介绍

1. 问题思考 不同电脑上的飞秋之间进行数据通信&#xff0c;它是如何保证把数据给飞秋而不是给其它软件呢? 其实&#xff0c;每运行一个网络程序都会有一个端口&#xff0c;想要给对应的程序发送数据&#xff0c;找到对应的端口即可。 端口效果图: 2. 什么是端口 端口是传…...

Electron训练笔记

终端乱码解决办法&#xff1a;更改编号下载卡住解决办法&#xff1a;Electron RequestError: connect ETIMEDOUT 20.205.243.166:443electron本质是一个依赖库&#xff0c;改依赖库提供了部分对象&#xff0c;可以实现对于window的调用。electron有一个主进程&#xff0c;多个渲…...

2023 英特尔On技术创新大会直播 | 窥探未来科技的边界

2023 英特尔On技术创新大会直播 | 窥探未来科技的边界 写在最前面观后感其他有趣的专题课程 写在最前面 嘿&#xff0c;你是不是对科技和创新充满好奇&#xff1f;2023 英特尔 On 技术创新大会线上活动邀请你一起探索最前沿的科技世界&#xff01; 这不仅是一场普通的聚会&…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...