【学习记录】从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.hqwc.cn/news/256378.html

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

相关文章

Vulnhub项目:EMPIRE: LUPINONE

一、靶机地址 靶机地址&#xff1a;Empire: LupinOne ~ VulnHub 靶机描述&#xff1a; 来&#xff0c;看一看&#xff0c;同样的配方&#xff0c;不同的设计&#xff0c;难度为中等&#xff0c;迷路了就成困难了&#xff0c;不得不说&#xff0c;还真是&#xff01; 几次陷入…

家用保险柜什么牌子好?

家用保险柜的品牌有很多&#xff0c;其中比较知名的有虎牌、得力、永发、思锐、迪堡、艾谱、全能、杰宝-大王、金虎、花都、飞云、威盾斯等等。这些品牌都有各自的特点和优势&#xff0c;例如虎牌品牌是来自于河北&#xff0c;每年生产60多台&#xff0c;质量可靠&#xff1b;得…

【从零认识ECS云服务器 | 快速上线个人网站】阿里云手动搭建WordPress网站

第一步&#xff1a;部署 LAMP/LNMP 环境&#xff0c;需要在ECS实例中安装操作系统&#xff08;Linux&#xff0c;本例中使用的操作系统版本为CentOS 7.9 64位&#xff09;、Web服务器软件&#xff08;Apache/Nginx&#xff09;、数据库软件&#xff08;MySQL&#xff09;、网站…

采样率越高噪声越大?

ADC采样率指的是模拟到数字转换器&#xff08;ADC&#xff09;对模拟信号进行采样的速率。在数字信号处理系统中&#xff0c;模拟信号首先通过ADC转换为数字形式&#xff0c;以便计算机或其他数字设备能够处理它们。 ADC采样率通常以每秒采样的次数来表示&#xff0c;单位为赫…

网络广播音柱在多场景中的应用

网络广播音柱在多场景中的应用 首先&#xff0c;网络音响在家庭娱乐方面有着突出的表现。在家里&#xff0c;我们可以通过它享受高质量的音乐、电影和游戏。无论是听悠扬的音乐旋律&#xff0c;还是看电影时震撼的音效&#xff0c;它都能提供逼真的沉浸式音效。此外&#xff0…

Linux:缓冲区的概念理解

文章目录 缓冲区什么是缓冲区&#xff1f;缓冲区的意义是什么&#xff1f;缓冲区的刷新方式 理解缓冲区用户缓冲区和内核缓冲区缓冲区在哪里&#xff1f; 本篇主要总结的是关于缓冲区的概念理解&#xff0c;以及再次基础上对文件的常用接口进行一定程度的封装 缓冲区 什么是缓…

就一个css的bug,害我找了大半天儿

大家好&#xff0c;我是风筝 事情是这样子的&#xff0c;我前两天用 Hugo 搭了一个个人网站&#xff0c;我添加了几个菜单&#xff0c;其中有一个菜单是「可爱的 Java」。 但是&#xff0c;当网站跑起来之后&#xff0c;发现「可爱的 Java」在菜单栏并不是原样输出的&#xf…

vim常见操作

vim常见操作 文章目录 vim常见操作1. 回退/前进2. 搜索3. 删除4. 定位到50行5. 显示行号6. 复制粘贴7. 剪贴8. 替换9. vim打开文件的时候出现 1. 回退/前进 1.esc进入命令模式 2.ctrlr 前进 u 回退2. 搜索 1&#xff09; esc进入命令模式 2&#xff09; /text  查找text&am…

硬件基础:差模和共模

一直以来&#xff0c;都难以理解差模和共模这两个概念&#xff0c;什么差分信号、差模信号、共模信号&#xff0c;差模干扰、共模干扰……虽然看了一些资料&#xff0c;但貌似说法还挺多的&#xff0c;理解起来仍然是一头雾水。所以&#xff0c;专门用一篇文章来好好研究下这个…

正则表达式:深度解析与实用指南

目录 引言 正则表达式的基本概念 1. 字面量字符 2. 元字符 3. 量词 4. 分组和捕获 正则表达式的语法规则 1. 字符类 2. 转义字符 3. 锚点 4. 向前查找和向后查找 实际应用中的正则表达式技巧 1. 邮箱验证 2. URL 提取 3. 电话号码格式化 高级正则表达式技巧 1.…

【vtkWidgetRepresentation】第五期 vtkLineRepresentation

很高兴在雪易的CSDN遇见你 内容同步更新在公众号“VTK忠粉” 【vtkWidgetRepresentation】第五期 一条直线的交互 前言 本文分享vtkLineRepresentation&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xf…

ESP32-Web-Server编程-通过 Base64 编码在网页中插入图片

ESP32-Web-Server编程-通过 Base64 编码在网页中插入图片 概述 不同于上节 ESP32-Web-Server编程-在网页中通过 src 直接插入图片&#xff0c;本节引入 Base64 编码来显示图片。 Base64 是一种用64个字符来编码表示任意二进制数据的方法。任何符号都可以转换成 Base64 字符集…