【学习记录】从0开始的Linux学习之旅——字符型设备驱动及应用

一、概述

    Linux操作系统通常是基于Linux内核,并结合GNU项目中的工具和应用程序而成。Linux操作系统支持多用户、多任务和多线程,具有强大的网络功能和良好的兼容性。基于前面应用与驱动的开发学习,本文主要讲述如何在linux系统上把应用与驱动的链路打通,即在应用中使用新增的驱动接口。

二、概念及原理

    应用程序通过系统调用与内核进行交互,而驱动程序则提供了硬件设备的访问接口,内核本身则提供了系统调用、驱动框架等基础设施。
    驱动开发:Linux 驱动开发是指为 Linux 内核开发各种设备驱动程序,用于控制和管理硬件设备。驱动程序运行在内核空间,直接与硬件进行交互。Linux 内核提供了丰富的接口和框架,开发者可以编写各种类型的设备驱动,包括网络设备、存储设备、输入设备等。驱动程序通过内核提供的接口与用户空间的应用程序进行通信。
    应用开发:Linux 应用开发是指在 Linux 系统上开发各种类型的应用程序,包括命令行工具、图形界面应用、服务器端应用等。Linux 提供了丰富的开发环境和工具链,开发者可以使用各种编程语言和开发工具进行应用开发。应用程序运行在用户空间,通过系统调用与操作系统内核进行交互,执行各种任务和功能。
    内核开发:Linux 内核开发是指对 Linux 内核本身进行开发和维护。Linux 内核是操作系统的核心,负责管理系统资源、调度任务、提供系统调用等功能。内核开发包括对内核功能的添加和修改,修复内核漏洞,优化性能等工作。内核开发人员通常会编写和维护内核的各种子系统和模块,包括调度器、文件系统、网络协议栈等。
    设备类型:Linux 中的设备类型主要分为字符设备和块设备两种,它们分别适用于以字符为单位和以数据块为单位进行输入输出的场景。
    用户空间与内核空间:用户空间和内核空间分别代表了操作系统中不同的内存空间和权限级别,它们共同构成了操作系统的运行环境,保证了系统的稳定性、安全性和可管理性。用户与内核的交互只能通过特定的系统接口,这样就保证了内核的稳定性。

三、准备工作

  1. 安装虚拟机VMware
  2. 安装ubuntu 22.04
  3. 安装vim、vscode等工具
sudo apt update
sudo apt install vim code

    另外需要先熟悉单独的驱动开发及应用开发,具体可参考最底下相关文章链接。

四、代码实现

4.1 驱动代码实现

    除了原本的模块加载和卸载接口,这里我们还要实现一些接口可供应用层使用。众所周知,Linux中万物皆文件,那我们就来实现操作文件所需的最常用的几个接口:open、close、write、read。
    首先新建一个driver.c的文件,在文件中添加如下代码。

#include <linux/init.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>#define DEVICE_NAME	"myfirstdev"
#define CLASS_NAME	"myfirstdev_class"
/****************************模块的文件操作接口***************************/
static int majorNumber;
static struct class* charClass = NULL;
static struct device* charDevice = NULL;/* 用来存储应用层传下来数据 */
static char DevMsg[100];/* 打开接口 */
static int dev_open(struct inode *inodep,struct file *f){printk(KERN_INFO "打开设备!\n");return 0;
}/* 关闭接口 */
static int dev_release(struct inode *inodep,struct file *f){printk(KERN_INFO "关闭设备!\n");return 0;
}/* 读接口 */
static ssize_t dev_read(struct file *f,char *buffer,size_t len,loff_t *offset){int error_count = 0;/* 可以传一些数据到应用层 */if (len > sizeof(DevMsg)){printk(KERN_INFO "读取数据字节数过长,共获取了%d字节\n", len);return 0;}/* 把模块内的数据缓存拷贝到用户缓存中 */error_count = copy_to_user(buffer, DevMsg, len);if (error_count == 0){printk(KERN_INFO "成功发送%d个字节数据给到用户\n", len);return 0;}else{printk(KERN_INFO "发送失败\n");return -EFAULT;}
}/* 写接口 */
static ssize_t dev_write(struct file *f,const char *buffer,size_t len,loff_t *offset){int error_count = 0;/* 可以保存一些应用层传下来的数据 */if (len > sizeof(DevMsg)){printk(KERN_INFO "写入数据字节数过长,需要写入%d字节\n", len);return 0;}/* 内核空间与用户空间的数据交互必须通过这个接口 */copy_from_user(DevMsg, buffer, len);printk(KERN_INFO "写入数据成功,共写入%d个字节\n", len);return len;
}/* 文件接口挂接 */
static struct file_operations fops = {.open = dev_open,.release = dev_release,.read = dev_read,.write = dev_write,
};/****************************模块的加载和卸载接口****************************/
/* 定义模块的初始化函数 */
static int Driver_Init(void)
{/* 先注册字符型设备 */majorNumber = register_chrdev(0, DEVICE_NAME, &fops);printk(KERN_INFO "注册的设备名为:%s\n", DEVICE_NAME);/* 创建设备类 */charClass = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(charClass)){unregister_chrdev(majorNumber, DEVICE_NAME);printk(KERN_ALERT "注册设备失败\n");return PTR_ERR(charClass);}/* 创建设备驱动 */charDevice = device_create(charClass,NULL,MKDEV(majorNumber, 0),NULL,DEVICE_NAME);if (IS_ERR(charDevice)){class_destroy(charClass);unregister_chrdev(majorNumber, DEVICE_NAME);printk(KERN_ALERT "创建设备失败\n");return PTR_ERR(charDevice);}printk(KERN_INFO "字符型设备驱动加载成功!\n");return 0;
}/* 定义模块的退出函数 */
static void Driver_Exit(void)
{/* 先销毁设备驱动 */device_destroy(charClass, MKDEV(majorNumber, 0));class_unregister(charClass);class_destroy(charClass);/* 再注销字符型设备 */unregister_chrdev(majorNumber, DEVICE_NAME);printk(KERN_INFO "字符型设备驱动卸载成功!\n");
}/* 注册模块的初始化和退出函数,这个是给内核识别的 */
module_init(Driver_Init);
module_exit(Driver_Exit);/* 声明该模块符合GPL协议——必须加,不然编译会出错 */
MODULE_LICENSE("GPL");/* 下面是声明作者姓名、设备类型和版本号,可加可不加 */
MODULE_AUTHOR("Chewie");
MODULE_DESCRIPTION("A simple char driver");
MODULE_VERSION("0.1");

    再新增一个Makefile文件,添加如下内容。

obj-m := driver.oKDIR := /lib/modules/$(shell uname -r)/buildall:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean

    编译模块

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

    编译成功无告警后会生成xx.ko文件,加载.ko模块,在应用使用期间都需要保持加载状态。

sudo insmod driver.ko

    如不需要使用此模块时,需要使用rmmod卸载模块。

sudo rmmod driver

4.2 应用代码实现

    同样的,即然驱动实现了对应的文件接口,那应用层就可以直接打开对应的驱动文件进行操作。新建一个app.c的文件,添加如下代码。

#include <stdio.h>
#include <fcntl.h>#define DEVICE_NODE	"/dev/myfirstdev"int main(){int file_desc;int ret;char msg[100];char write_msg[] = "hello";/* 打开刚才的设备驱动文件 */file_desc = open(DEVICE_NODE, O_RDWR);if (file_desc < 0){printf("无法打开设备文件\n");return -1;}/* 从设备中写入数据 */ret = write(file_desc, write_msg, strlen(write_msg));printf("写入的数据为:%s\n", write_msg);if (ret < 0){printf("写入数据失败\n");close(file_desc);return -1;}/* 从设备中读取数据 */ret = read(file_desc, msg, sizeof(msg));printf("读出的数据为:%s\n", msg);if (ret < 0){printf("读取数据失败\n");close(file_desc);return -1;}printf("读出写入的数据为:%s\n", msg);/* 关闭设备 */close(file_desc);return 0;
}

    再新增一个Makefile文件,添加如下内容。

# 定义编译器和编译选项
CC = gcc
CFLAGS = -Wall -g# 定义目标文件和源文件
TARGET = app
SRCS = app.c
OBJS = $(SRCS:.c=.o)# 默认构建规则
all: $(TARGET)# 生成目标可执行文件
$(TARGET): $(OBJS)$(CC) $(CFLAGS) -o $@ $^# 生成目标文件
%.o: %.c$(CC) $(CFLAGS) -c -o $@ $<# 清理生成的文件
clean:rm -f $(OBJS) $(TARGET)

    在当前目录下,使用make命令编译应用程序。编译无错误后会生成app文件,执行以下命令运行程序,因为这里程序里调用了内核驱动,所以需要sudo权限。

sudo ./app

    这里可以打开系统日志看下整个过程。

sudo tail -f /var/log/kern.log

在这里插入图片描述

五、相关链接

【学习记录】从0开始的Linux学习之旅——驱动模块编译与加载
【学习记录】从0开始的Linux学习之旅——应用开发(helloworld)

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

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

相关文章

参考信号速度变化存在跳跃时容易发生不稳定的阻抗调节

问题描述 当参考信号速度存在跳跃变化时&#xff0c;阻抗调节系统容易发生不稳定。这是因为阻抗调节系统需要根据参考信号的速度来调整其输出阻抗&#xff0c;以匹配负载阻抗&#xff0c;从而保持系统的稳定性。 当参考信号速度突然变化时&#xff0c;阻抗调节系统可能无法及…

『TypeScript』深入理解变量声明、函数定义、类与接口及泛型

&#x1f4e3;读完这篇文章里你能收获到 了解TypeScript变量声明与类型注解掌握TypeScript函数与方法的使用掌握TypeScript类与接口的使用掌握TypeScript泛型的应用 文章目录 一、变量声明与类型注解1. 变量声明2. 类型注解3. 类型推断 二、函数与方法定义1. 函数定义2. 方法定…

Kubernetes权威指南:从Docker到Kubernetes实践全接触(第5版)读书笔记 目录

完结状态&#xff1a;未完结 文章目录 前言第1章 Kubernetes入门 11.1 了解Kubernetes 2 附录A Kubernetes核心服务配置详解 915总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; Kubernetes权威指南&#xff1a;从Docker到Kubernetes实践全接触&…

Jmeter 性能测试基础!

压力测试   压力测试分两种场景&#xff1a;一种是单场景&#xff0c;压一个接口的&#xff1b;第二种是混合场景&#xff0c;多个有关联的接口。压测时间&#xff0c;一般场景都运行10-15分钟。如果是疲劳测试&#xff0c;可以压一天或一周&#xff0c;根据实际情况来定。 压…

【编程技术】CUDA TencoreCore编程实例说明

概述 通过一个m16n8k16矩阵乘法的CUDA TencoreCore编程实例&#xff0c;展示load/store mma 的矩阵乘法运行过程 动画实例 CUDA TensoreCore 编程实例

springboot 在自定义注解中注入bean,解决注入bean为null的问题

问题&#xff1a; 在我们开发过程中总会遇到比如在某些场合中需要使用service或者mapper等读取数据库&#xff0c;或者某些自动注入bean失效的情况 解决方法&#xff1a; 1.在构造方法中通过工具类获取需要的bean 工具类代码&#xff1a; import org.springframework.beans…

Spring到底是如何解决循环依赖问题的?

Spring作为当前使用最广泛的框架之一&#xff0c;其重要性不言而喻。所以充分理解Spring的底层实现原理对于咱们Java程序员来说至关重要&#xff0c;那么今天笔者就详细说说Spring框架中一个核心技术点&#xff1a;如何解决循环依赖问题&#xff1f; 什么是循环依赖问题&#x…

深入理解Java中的逃逸分析

目录 1. 对象作用域分析2. 栈上分配3. 同步省略&#xff08;锁消除&#xff09;4. 标量替换 逃逸分析是一种编译器优化技术&#xff0c;用于确定对象的作用域和生命周期。其主要特点包括&#xff1a;对象作用域分析、栈上分配、同步省略和标量替换。现在将详细阐述这些特点&…

延长UPS电源寿命的12大方法

为提高机房UPS电源工作质量&#xff0c;加强日常管理&#xff0c;确保机房UPS不间断电源设备始终处于良好的环境中&#xff0c;设备安全稳定运行。让我们看看UPS不间断电源的使用寿命能达到多长时间&#xff1f;如何维护&#xff1f;UPS电源维护&#xff0c;延长UPS电源寿命。 …

JVM理解

1、JVM是什么&#xff1f; JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。 他是帮助我们将java代码 生成编译后 的 class 文件。 2、JRE、JDK和JVM 的关系 …

用AI批量生成文章的工具有哪些?免费AI生成工具

人工智能&#xff08;AI&#xff09;技术不断演进&#xff0c;为许多领域带来了前所未有的便利。其中&#xff0c;AI生成文章技术作为一个备受关注的领域&#xff0c;为大家提供了独特的解决方案&#xff0c;特别是在批量文章生成的需求上。 1. AI生成文章的方法 开放式AI模型…

springsecurity为什么说使用JWT就可以disable csrf

Cross-Site Request Forgery (CSRF) 是一种攻击&#xff0c;它利用了用户在浏览器中对特定网站的登录状态。攻击者可以在他们控制的网站上构造一个请求&#xff0c;当用户访问这个网站时&#xff0c;这个请求会在用户的浏览器中执行&#xff0c;并带有用户对目标网站的凭证&…

更简单的Redux工具 Toolkit

1 安装 npm install reduxjs/toolkit2 创建store目录 创建store文件夹&#xff0c;里面包含入口文件index.jsx&#xff0c;以及自定义的reduces方法main.jsx的slices文件夹&#xff0c;其中main1.jsx、main2.jsx表示每一个相对独立的reduces数据操作集 store ***slices *****…

基于Browscap对浏览器工具类优化

项目背景 原有的启动平台公共组件库comm-util的浏览器工具类BrowserUtils是基于UserAgentUtils的&#xff0c;但是该项目最后一个版本发布于 2018/01/24&#xff0c;之至今日23年底&#xff0c;已有5年没有维护更新&#xff0c;会造成最新版本的部分浏览器不能正确获取到浏览器…

使用python操作excel文档

导入xlsxwriter包 python轻量化的语言&#xff0c;用来操作文档简直易如反掌&#xff0c;首先你需要导入的是import xlsxwriter包&#xff0c;他包括了操作文档所需要的全部工具方法&#xff0c;你只需要调用就好了。 操作excel指南 首先你需要创建一个文件xlsxwriter.Workb…

http与apache

目录 1.http相关概念 2.http请求的完整过程 3.访问浏览器背后的原理过程 4.动态页面与静态页面区别 静态页面&#xff1a; 动态页面&#xff1a; 5.http协议版本 6.http请求方法 7.HTTP协议报文格式 8.http响应状态码 1xx&#xff1a;提示信息 2xx&#xff1a;成功…

代码随想录算法训练营第四十四天| 518 零钱兑换 || 377 组合总和 Ⅳ

目录 518 零钱兑换 || 377 组合总和 Ⅳ 518 零钱兑换 || 如果求组合数就是外层for循环遍历物品&#xff0c;内层for遍历背包。 class Solution { public:int change(int amount, vector<int>& coins) {vector<int> f(amount 10);//拼凑成总金额为i的总情况…

CPU密集型和IO密集型初学习

目录 1、CPU密集型 2、IO密集型 3、CPU密集型和IO密集型的区别 4、CPU密集型和IO密集型对CPU内核之间的关系 5、核心线程数计算公式 5、扩展&#xff1a;进程和线程 小结 1、CPU密集型 CPU密集型是指计算机程序或任务在执行过程中主要依赖于中央处理器&#xff08;CPU&…

IOday5作业

使用两个线程完成两个文件的拷贝&#xff0c;分支线程1完成前一半内容拷贝&#xff0c;分支线程2完成后一半内容的拷贝&#xff0c;主线程完成资源的回收 #include<myhead.h> //定义结构体 struct file {const char* srcfile;//背拷贝文件路径const char* destfile;//拷…

浏览器的缓存策略

浏览器缓存的策略主要分为两种&#xff1a;过期机制和验证机制。 过期机制&#xff1a;是指浏览器根据资源的过期时间&#xff0c;判断是否可以直接使用缓存中的副本&#xff0c;而无需向服务器发起请求。过期时间可以通过以下两种方式设置&#xff1a; Cache-Control&#xf…