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

Linux驱动---字符设备

目录

一、基础简介

1.1、Linux设备驱动分类

1.2、字符设备驱动概念

二、驱动基本构成

2.1、驱动模块的加载和卸载

2.2、添加LICENNSE以及其他信息

三、字符设备驱动开发步骤

3.1、分配主次设备号

3.1.1 主次设备号

3.1.2静态注册设备号

3.1.3动态注册设备号

3.1.4释放设备号

3.2文件操作函数fops设置

3.2.1file_operations结构体

3.2.2数据交互

3.2.3ioctl实现

3.3字符设备结构的分配和初始化

3.3.1分配cdev 结构体

3.3.2初始化cdev结构体

3.3.3注册字符设备

3.3.4注销字符设备

3.4创建设备节点

3.4.1创建和删除类

3.4.2创建和删除设备文件

四、代码演示

4.1驱动部分演示

4.1.1驱动代码

 4.1.2驱动编译

4.1.3安装驱动 

4.2应用空间程序测试

4.2.1测试代码

 4.2.2编译测试

五、 总结


一、基础简介

1.1、Linux设备驱动分类

        有一句话,相信大家一定不会感觉到陌生---“Linux下一切皆是文件”!所以我们可以这样理解,Linux内核会将设备抽象成文件,然后我们通过文件I/O就可以对设备进行操作。而Linux内核又按照访问特性将其分成三类:字符设备、块设备、网络设备

  • 字符设备:在数据读取操作时,以字节为单位进行的,比如串口、LED、蜂鸣器等等。
  • 块设备:在数据读取操作时,以块或扇区为单位进行的,比如硬盘、U盘、eMMC等等。
  • 网络设备:通过数据包传输的设备,比如以太网卡、无线网卡等。这类设备在/dev/下没有对应的设备节点,如果想要查看,需要使用 ifconfig 。

1.2、字符设备驱动概念

        接下来,我们将从最简单的字符设备入手,开始学习驱动的概念。我们需要先了解一下Linux下的应用程序是如何调用驱动程序的,其关系如图所示:

        我们的驱动程序成功加载后,会在 /dev 目录下生成一个对应的文件,应用程序通过这个名为 /dev/xxx 的文件进行相应的操作即可实现对硬件的操作。比如现在有个叫 /dev/led 这个文件,我们在应用程序中调用了open()函数,它会通过系统调用从用户空间切换到内核空间,在执行驱动程序中对应的open()函数,从而实现了对硬件的操作。

        我们会发现,每一个系统调用都会有一个与之对应的驱动函数。说到这里,就必须要提到file_operations结构体,此结构体就是Linux内核操作函数的集合,会在 3.3.1 进行详细整理。

二、驱动基本构成

        在正式写驱动代码前,我们需要知道驱动程序必不可少的几部分,这也是与应用程序不同的地方。

2.1、驱动模块的加载和卸载

 Linux驱动有两种运行方式:1、将驱动编译进Linux内核中,这样当Linux内核启动的时候就会自动运行驱动程序;2、将驱动编译成模块(Linux下模块拓展名为.ko),在Linux内核启动之后,通过 insmod 命令加载内核模块

        我们平时调试的时候一般都选择第二种方法,因为在调试过程中我们只需要加载或者卸载驱动模块即可,不需要重新编译整个内核。

        我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

        module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用 insmod 命令加载驱动的时候, xxx_init 这个函数就会被调用。 module_exit()函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用 rmmod 命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

2.2、添加LICENNSE以及其他信息

        Linux 是以 GNU 通用公共版权( GPL )的版本 2 作为许可的,所以LICENSE是必须添加的!模块的作者等其他信息是可选择性添加的。

MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息

三、字符设备驱动开发步骤

        当我们了解了字符设备驱动的基础知识,我们就要开始学习字符设备设备的开发步骤了。与应用层开发不同,驱动开发的框架是固定的,所以学习框架是十分重要的!

3.1、分配主次设备号

3.1.1 主次设备号

        Linux中,每个设备都有一个设备号。设备号由两部分组成,分别是主设备号和次设备号。主设备号用于标识某一个具体的驱动,次设备号用于标识使用该驱动的某一个设备。在编写Linux内核驱动时,每个设备都要有一个独一无二的设备号(包括主、次设备号),它通常使用 dev_t 类型(在<linux/types.h>中)来定义。

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

        我们可以看到,dev_t是一个32位的数据,其中高12位为主设备号(0~4095),低20位为次设备号。在驱动编程中,我们不应该管哪些位是主设备号,哪些位是次设备号,而应该统一使用 <linux/kdev_t.h>中的一套宏设置/获取一个dev_t 的主、次编号:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

        其中,宏MAJOR用于从dev_t中获取主设备号;宏MINOR用于从dev_t中获取次设备号;宏MKDEV用于将给定的主设备号和次设备号的值组合成dev_t类型的设备号。

3.1.2静态注册设备号

        我们可以通过 cat /proc/devices  来查看所有已被系统使用的设备号,我们可以选择一个未被使用的设备号来进行静态注册,其中静态注册设备号的API函数如下:

int register_chrdev_region(dev_t first, unsigned int count, char *name);
//first:要分配的起始设备号,其为 dev_t 类型,可以由 MKDEV() 宏来生成 。first的次编号部分通常是从0开始,但不是强制的
//count:请求分配的设备号的总数
//name:设备名称
//成功返回值是0。出错的情况下,返回一个负的错误码

3.1.3动态注册设备号

        我们可以使用动态注册一个设备号,在根据宏来获取它的主次设备号,其中动态注册设备号的API函数如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//dev:这是一个输出参数,用来保存申请到的 dev_t 类型设备号。这样我们可以使用 MAJOR() 宏从它里面提取出相应设备的主设备号
//baseminor:传入给内核的次设备号起始值,通常次设备号从0开始编号
//count:要申请的设备号数量
//name:设备名称
//成功返回值是0。出错的情况下,返回一个负的错误码

        动态分配的缺点是我们无法提前创建设备节点,因为分配给我们的主设备号会发生变化,只能通过查看 /proc/devices 文件才能知道它的值,然后再创建设备节点。

3.1.4释放设备号

        通常我们在驱动安装时会申请主、次设备号,那很显然我们应该在驱动卸载时应该释放主次设备号。设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)
//from:要释放的设备号
//count:表示从 from 开始,要释放的设备号数量

3.2文件操作函数fops设置

3.2.1file_operations结构体

        我们在上文提到了Linux内核操作函数的集合---file_operations结构体,接下来我们详细整理一下。

#include <linux/fs.h>struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct f

相关文章:

Linux驱动---字符设备

目录 一、基础简介 1.1、Linux设备驱动分类 1.2、字符设备驱动概念 二、驱动基本构成 2.1、驱动模块的加载和卸载 2.2、添加LICENNSE以及其他信息 三、字符设备驱动开发步骤 3.1、分配主次设备号 3.1.1 主次设备号 3.1.2静态注册设备号 3.1.3动态注册设备号 3.1.4释…...

php7.3安装php7.3-gmp扩展踩坑总结

环境&#xff1a; 容器里面为php7.3.3版本 服务器也为php7.3.3-14版本&#xff0c;但是因为业务量太大需要在服务器里面跑脚本 容器里面为 alpine 系统&#xff0c;安装各种扩展 服务器里面开发服为 ubuntu 16.04.7 LTS (Xenial Xerus) 系统 服务器线上为 ubuntu 20.04.6 LTS (…...

javaEE-8.JVM(八股文系列)

目录 一.简介 二.JVM中的内存划分 JVM的内存划分图: 堆区:​编辑 栈区:​编辑 程序计数器&#xff1a;​编辑 元数据区&#xff1a;​编辑 经典笔试题&#xff1a; 三,JVM的类加载机制 1.加载: 2.验证: 3.准备: 4.解析: 5.初始化: 双亲委派模型 概念: JVM的类加…...

大语言模型轻量化:知识蒸馏的范式迁移与工程实践

大语言模型轻量化&#xff1a;知识蒸馏的范式迁移与工程实践 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 摘要 在大型语言模型&#xff…...

数据结构:时间复杂度

文章目录 为什么需要时间复杂度分析&#xff1f;一、大O表示法&#xff1a;复杂度的语言1.1 什么是大O&#xff1f;1.2 常见复杂度速查表 二、实战分析&#xff1a;解剖C语言代码2.1 循环结构的三重境界单层循环&#xff1a;线性时间双重循环&#xff1a;平方时间动态边界循环&…...

[创业之路-276]:从燃油汽车到智能汽车:工业革命下的价值变迁

目录 前言&#xff1a; 从燃油汽车到智能汽车&#xff1a;工业革命下的价值变迁 前言&#xff1a; 燃油汽车&#xff0c;第一次、第二次工业革命&#xff0c;机械化、电气化时代的产物&#xff0c;以机械和电气自动化为核心价值。 智能汽车&#xff0c;第三次、第四次工业革…...

vue页面和 iframe多页面无刷新方案和并行 并接入 micro 微前端部分思路

前: 新进了一家公司,公司是做电商平台的, 用的系统竟然还是jsp的网站,每次修改页面还需要我下载idea代码,作为一个前端, 这可不能忍,于是向上申请,意思你们后台做的太辣鸡,我要重做,经领导层商议从去年6月开始到今年12月把系统给重构了 公司系统采用的是每个jsp页面都是一个ifr…...

Linux特权组全解析:识别GID带来的权限提升风险

组ID&#xff08;Group ID&#xff0c;简称 GID&#xff09;是Linux系统中用来标识不同用户组的唯一数字标识符。每个用户组都有一个对应的 GID&#xff0c;通过 GID&#xff0c;系统能够区分并管理不同的用户组。 在Linux系统中&#xff0c;系统用户和组的配置文件通常包括以…...

RTMP 和 WebRTC

WebRTC(Web Real-Time Communication)和 RTMP(Real-Time Messaging Protocol)是两种完全不同的流媒体协议,设计目标、协议栈、交互流程和应用场景均有显著差异。以下是两者的详细对比,涵盖协议字段、交互流程及核心设计思想。 一、协议栈与设计目标对比 特性RTMPWebRTC传…...

系统通解:超多视角理解

在科学研究和工程应用中&#xff0c;我们常常面临各种复杂系统&#xff0c;需要精确描述其行为和变化规律。从物理世界的运动现象&#xff0c;到化学反应的进程&#xff0c;再到材料在受力时的响应&#xff0c;这些系统的行为往往由一系列数学方程来刻画。通解&#xff0c;正是…...

11.享元模式 (Flyweight)

定义 Flyweight 模式&#xff08;享元模式&#xff09; 是一种结构型设计模式&#xff0c;它旨在通过共享对象来有效支持大量细粒度对象的复用。该模式主要通过共享细节来减少内存使用&#xff0c;提升性能&#xff0c;尤其在需要大量对象时非常有效。 基本思想&#xff1a; …...

Python 自学秘籍:开启编程之旅,人生苦短,我用python。

从2009年&#xff0c;用了几次python后就放弃了&#xff0c;一直用的php&#xff0c;现在人工智能时代&#xff0c;完全没php什么事情。必须搞python了&#xff0c;虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代&#xff…...

验证工具:SVN版本控制

1-SVN概念 SVN(Subversion)是一种集中式版本控制系统,它用于文件和目录的版本管理,允许多个用户协同工作,同时追踪每个文件和目录的历史修改记录。以下是关于SVN版本控制的详细介绍: 一、SVN的基本概念 仓库(Repository):SVN的仓库是一个集中存储所有文件和目录的地…...

每日一题洛谷P5721 【深基4.例6】数字直角三角形c++

#include<iostream> using namespace std; int main() {int n;cin >> n;int t 1;for (int i 0; i < n; i) {for (int j 0; j < n - i; j) {printf("%02d",t);t;}cout << endl;}return 0; }...

React开发中箭头函数返回值陷阱的深度解析

React开发中箭头函数返回值陷阱的深度解析 一、箭头函数的隐式返回机制&#xff1a;简洁背后的规则二、块函数体中的显式返回要求&#xff1a;容易被忽视的细节三、真实场景下的案例分析案例1&#xff1a;忘记return导致组件渲染失败案例2&#xff1a;异步操作中的返回值陷阱 四…...

解决每次打开终端都需要source ~/.bashrc的问题(记录)

新服务器或者电脑通常需要设置一些环境变量&#xff0c;例如新电脑安装了Anaconda等软件&#xff0c;在配置环境变量后发现每次都需要重新source&#xff0c;非常麻烦&#xff0c;执行下面添加脚本实现一劳永逸 vim .bash_profile# .bash_profileif [ -f ~/.bashrc ]; then. ~…...

解决DeepSeek服务器繁忙问题:本地部署与优化方案

deepseek服务器崩了&#xff0c;手把手教你如何在手机端部署一个VIP通道&#xff01; 引言 随着人工智能技术的快速发展&#xff0c;DeepSeek等大语言模型的应用越来越广泛。然而&#xff0c;许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…...

【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)

【后端开发】系统设计101——通信协议&#xff0c;数据库与缓存&#xff0c;架构模式&#xff0c;微服务架构&#xff0c;支付系统&#xff08;36张图&#xff09; 文章目录 1、通信协议通信协议REST API 对比 GraphQL&#xff08;前端-web服务&#xff09;grpc如何工作&#x…...

Java基础——分层解耦——IOC和DI入门

目录 三层架构 Controller Service Dao ​编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理&#xff1f; 容器怎样才能提供依赖的bean对象呢&#xff1f; 三层架构 Controller 控制…...

武汉火影数字|VR虚拟现实:内容制作与互动科技的奇妙碰撞

VR虚拟现实是一种利用计算机技术生产三维虚拟世界的技术&#xff0c;通过头戴式显示器、手柄等设备&#xff0c;用户可以身临其境地感受虚拟世界&#xff0c;与其中的物体进行自然交互。 当内容制作遇上 VR&#xff0c;会发生什么&#xff1f; 当内容制作遇上VR&#xff0c;就像…...

一文了解性能优化的方法

背景 在应用上线后&#xff0c;用户感知较明显的&#xff0c;除了功能满足需求之外&#xff0c;再者就是程序的性能了。因此&#xff0c;在日常开发中&#xff0c;我们除了满足基本的功能之外&#xff0c;还应该考虑性能因素。关注并可以优化程序性能&#xff0c;也是体现开发能…...

SpringBoot扩展篇:@Scope和@Lazy源码解析

SpringBoot扩展篇&#xff1a;Scope和Lazy源码解析 1. 研究主题及Demo2. 注册BeanDefinition3. 初始化属性3.1 解决依赖注入3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary3.3 代理拦截处理3.4 单例bean与原型bean创建的区别 4. …...

tkvue 入门,像写html一样写tkinter

介绍 没有官网&#xff0c;只有例子 安装 像写vue 一样写tkinter 代码 pip install tkvue作者博客 修改样式 import tkvue import tkinter.ttk as ttktkvue.configure_tk(theme"clam")class RootDialog(tkvue.Component):template """ <Top…...

c++ stl 遍历算法和查找算法

概述&#xff1a; 算法主要由头文件<algorithm> <functional> <numeric> 提供 <algorithm> 是所有 STL 头文件中最大的一个&#xff0c;提供了超过 90 个支持各种各样算法的函数&#xff0c;包括排序、合并、搜索、去重、分解、遍历、数值交换、拷贝和…...

Hackmyvm Connection

基本信息 难度&#xff1a;简单 靶机&#xff1a;192.168.194.11 kali&#xff1a;192.168.194.9 扫描 常规nmap扫描起手 nmap -sT -sV -A -T4 192.168.194.11 -p- 查看smb服务开启目录 139和445端口的smb服务直接以访客账号登录&#xff0c;无需密码验证成功。对应的ht…...

内置渲染管线和通用渲染管线的区别

内置渲染管线和通用渲染管线&#xff08;URP&#xff09;有以下区别&#xff1a; 功能特性 内置渲染管线&#xff1a;提供了一套较为基础的渲染功能&#xff0c;包括几何渲染、光照计算、阴影生成和后期处理等基本环节。但自定义选项相对有限&#xff0c;渲染次序基本是固…...

Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分

上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…...

算法随笔_40: 爬楼梯

上一篇:算法随笔_39: 最多能完成排序的块_方法2-CSDN博客 题目描述如下: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&am…...

数据结构(2)——线性表与顺序表实现

目录 前言 一、线性表 二、顺序表 2.1概念 2.2类型的选择 2.3实现 1.初始化 2.检查是否需要扩容 3.尾插 4.尾删 5.头插 6.头删 7.某一个位置添加 8.某一个位置删除 9.基于某一位置的尾插删 10.查找 11.修改 12.销毁 总结 前言 今天对顺序表进行学习&#xf…...

全面解析机器学习优化算法中的进化策略

全面解析机器学习优化算法中的进化策略 全面解析机器学习优化算法中的进化策略引言什么是进化策略?基本概念核心组件算法流程数学基础高斯扰动期望值更新与其他优化方法的比较梯度下降法(Gradient Descent, GD)遗传算法(Genetic Algorithm, GA)Python案例基本实现改进版:…...