【学习记录】从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.o

KDIR := /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

寒冬不再寒冷:气膜体育馆如何打造温馨运动天地

取暖季即将来临&#xff0c;随着气温逐渐下降&#xff0c;人们在寒冷的冬季里如何保持运动热情和身体的健康成为了一项挑战。而在这个时候&#xff0c;气膜体育馆成为了运动爱好者们的理想场所&#xff0c;提供如春般温暖舒适的运动环境。那么&#xff0c;让我们一起揭秘气膜体…

2023年8月8日 Go生态洞察:Go 1.21 版本发布探索

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【Java】深入剖析Java枚举类

目录 定义1&#xff09;定义2&#xff09;内部实现3&#xff09;方法与源码 高级特性1&#xff09;switch用法2&#xff09;自定义传值与构造函数3&#xff09;枚举实现抽象方法4&#xff09;枚举注解属性5&#xff09;枚举实现接口 总结 定义 1&#xff09;定义 枚举类是Jav…

Oracle的错误信息帮助:Error Help

今天看手册时&#xff0c;发现上面有个提示&#xff1a; Error messages are now available in Error Help. 点击 View Error Help&#xff0c;显示如下&#xff0c;其实就是oerr命令的图形化版本&#xff1a; 点击Database Error Message Index&#xff0c;以下界面等同于命令…

蒙商出海考察“走出去”“引进来”,探索践行“一带一路”倡议

蒙商新思路&#xff0c;出海正当时&#xff0c;内蒙古自治区促进民营经济发展项目新加坡、马来西亚交流行圆满结束 2023年11月19日-11月25日&#xff0c;内蒙古自治区促进民营经济项目新加坡、马来西亚交流考察行顺利举行&#xff0c;此次出海考察行是自治区发展改革委、工商联…

VBA_MF系列技术资料1-237

MF系列VBA技术资料 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-04属于定…

Qt开发学习笔记01

设置窗口背景图 在 .h 文件中添加引用和方法 #include <QPainter> #include <QPixmap> void paintEvent(QPaintEvent *);.cpp 文件中实现 paintEvent void sur_dev::paintEvent(QPaintEvent *ev) {QPainter painter(this);QPixmap pix;pix.load(":/image/bj01…

什么是接口测试?这篇文章让你明白!

要成为一名合格的测试工程师&#xff0c;接口测试相关的知识和技能&#xff0c;是不可缺少的。如今&#xff0c;我们随便打开一个大公司的JD&#xff0c;上面基本会要求接口测试经验。 那么&#xff0c;接口测试到底要测些什么&#xff1f; 我相信很多小伙伴跟几年前初入测试…

力扣题:公共前缀/单词-11.18

力扣题-11.18 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;14.最长公共前缀 解题思想&#xff1a;先找到最小的字符串长度&#xff0c;然后进行字符串的遍历即可 class Solution(object):def longestCommonPrefix(self, strs):""&qu…

cookie总结

cookie和session&#xff1a; 一、Cookie和Session二、使用Cookie保存用户上次的访问时间。三、Cookie常用方法总结乱码问题解决&#xff1a; 一、Cookie和Session 会话&#xff1a;用户从打开浏览器到关闭的整个过程就叫1次会话。 比如有的网站登录过一次&#xff0c;下次再进…

深入了解Java 8日期时间新玩法:DateTimeFormatter与ZoneOffset的使用

推荐语 在这篇文章中&#xff0c;我们将深入探讨Java中的DateTimeFormatter和ZoneOffset类的功能和使用方法。这些类是在Java 8中引入的新的日期时间API的一部分&#xff0c;它们为我们提供了更灵活、更易用的日期和时间处理能力。尽管这些类在Java 8中已经出现&#xff0c;但…

「Verilog学习笔记」同步FIFO

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1ns /**********************************RAM************************************/ module dual_port_RAM #(parameter DEPTH 16,parameter WIDTH 8)(in…

机器人刚性碰撞任务的阻抗控制性能

问题描述 对于机器人刚性碰撞任务&#xff0c;阻抗控制可以有效地提高机器人的适应性和稳定性。 在刚性碰撞任务中&#xff0c;机器人在接触外部物体时需要快速适应并调整自身的运动轨迹和速度&#xff0c;以实现精确的操控和稳定的交互。阻抗控制可以通过调整机器人的阻抗参…

王炸升级!PartyRock 10分钟构建 AI 应用

前言 一年一度的亚马逊云科技的 re:Invent 可谓是全球云计算、科技圈的狂欢&#xff0c;每次都能带来一些最前沿的方向标&#xff0c;这次也不例外。在看完一些 keynote 和介绍之后&#xff0c;我也去亲自体验了一些最近发布的内容。其中让我感受最深刻的无疑是 PartyRock 了。…

MySQL生成UUID并去除-

uuid()函数 uuid() 函数可以使mysql生成uuid,但是uuid中存在-,如下图&#xff1a; 去除uuid的- 默认生成的uuid含有-&#xff0c;我们可以使用replace函数替换掉-&#xff0c;SQL如下 select replace(uuid(),"-","") as uuid;Insert语句中使用UUID 如果…

【ArcGIS Pro微课1000例】0052:基于SQL Server创建企业级地理数据库案例

文章目录 环境搭建创建企业级数据库连接企业级数据库环境搭建 ArcGIS:ArcGIS Pro 3.0.1Server.ecp:版本为10.7SQL Server:版本为SQL Server Developer 2019创建企业级数据库 企业级地理数据库的创建需要通过工具箱来实现。工具位于:数据管理工具→地理数据库管理→创建企业…

C语言第十七集(待修)

11.30的视频 1.结构体可以这样重新赋值 注:字符数组不能用来赋值 2.匿名结构体重新赋值方法: 注:在创建x时就已经使用过一次匿名结构体了 但是,在使用匿名结构体时,可以一次性创立多个变量 3.结构体内存对齐和对其规则详细搜: 4.总之,我们在创建结构体时,要将占用空间小的成…

溯源取证-WEB流量分析-简单

话不多说直接干&#xff1a; 题干&#xff1a; 开发团队在公司的一个 Web 服务器上发现了异常文件&#xff0c;开发团队怀疑该服务器上存在潜在的恶意活动&#xff0c;网络团队准备了一个包含关键网络流量的 pcap 文件&#xff0c;供安全团队分析&#xff0c;而你的任务是分析…

虾皮关键词广告怎么选

在虾皮&#xff08;Shopee&#xff09;平台上&#xff0c;关键词广告是提高商品曝光度和销量的有效手段。然而&#xff0c;选择合适的关键词对于广告效果至关重要。本文将为您提供一些建议&#xff0c;帮助您选择适合虾皮关键词广告的关键词。 先给大家推荐一款shopee知虾数据…

干货分享|300平米A级机房设计方案

本方案中XXX计算机中心机房建设工程&#xff0c;是XXX的数据中心&#xff0c;机房位于建筑的X层&#xff0c;计算机机房面积300㎡。采购设备以及装修工艺主要用于XXX所属计算机机房装修工程。 考虑到中心机房投资大、使用周期长&#xff0c;而业主业务发展快&#xff0c;现代技…