oa系统品牌/台州网站建设优化
Linux 字符设备驱动开发是内核模块开发中的一个重要部分,主要用于处理字节流数据设备(如串口、键盘、鼠标等)。字符设备驱动的核心任务是定义如何与用户空间程序交互,通常通过一组文件操作函数进行。这些函数会映射到 open
、read
、write
等系统调用。
下面将详细介绍字符设备驱动开发的步骤,包括编写、注册、操作函数实现、测试等。
1. 字符设备驱动开发流程
步骤 1: 创建一个内核模块
字符设备驱动是作为内核模块加载的,可以动态加载到 Linux 内核中。我们从编写一个简单的字符设备驱动模块开始。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h> // 文件系统支持
#include <linux/cdev.h> // 字符设备支持
#include <linux/uaccess.h> // 用户空间访问支持#define DEVICE_NAME "mychardev"
#define BUFFER_SIZE 1024static int major; // 主设备号
static char device_buffer[BUFFER_SIZE]; // 设备数据缓冲区
static struct cdev my_cdev; // 字符设备结构体
static dev_t dev_num; // 设备号// 文件操作函数
static int device_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static int device_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}static ssize_t device_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {size_t bytes_read = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_to_user(buffer, device_buffer, bytes_read)) {return -EFAULT;}printk(KERN_INFO "Read %zu bytes from device\n", bytes_read);return bytes_read;
}static ssize_t device_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {size_t bytes_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_from_user(device_buffer, buffer, bytes_write)) {return -EFAULT;}printk(KERN_INFO "Wrote %zu bytes to device\n", bytes_write);return bytes_write;
}// 文件操作函数结构体
static struct file_operations fops = {.owner = THIS_MODULE,.open = device_open,.release = device_release,.read = device_read,.write = device_write,
};// 模块加载时的初始化函数
static int __init mychardev_init(void) {// 动态分配主设备号和次设备号alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);major = MAJOR(dev_num);printk(KERN_INFO "Registered with major number %d\n", major);// 初始化 cdev 结构体并添加到系统cdev_init(&my_cdev, &fops);cdev_add(&my_cdev, dev_num, 1);return 0;
}// 模块卸载时的清理函数
static void __exit mychardev_exit(void) {// 删除 cdevcdev_del(&my_cdev);// 释放设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "Device unregistered\n");
}module_init(mychardev_init);
module_exit(mychardev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("A simple character device driver");
步骤 2: 编写 Makefile
为了编译驱动模块,需要编写一个 Makefile
来调用内核构建系统。
obj-m += mychardev.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
步骤 3: 编译驱动
编译驱动:
make
生成的模块文件为 mychardev.ko
。
步骤 4: 加载和卸载驱动
加载字符设备驱动模块:
sudo insmod mychardev.ko
检查是否成功加载驱动:
dmesg | tail
卸载模块:
sudo rmmod mychardev
步骤 5: 创建设备文件
Linux 使用设备文件与用户空间通信。驱动模块加载时,需要为字符设备创建设备文件。
首先通过 /proc/devices
查看分配的主设备号:
cat /proc/devices | grep mychardev
使用 mknod
创建设备文件:
sudo mknod /dev/mychardev c <major_number> 0
sudo chmod 666 /dev/mychardev
2. 文件操作函数实现
1. open
和 release
函数
这些函数会在打开和关闭设备文件时被调用。它们通常用于初始化设备或者释放设备资源。
open
:每次用户空间程序通过open()
调用打开设备文件时调用,通常用于设备初始化。release
:每次关闭设备文件时调用,用于释放资源。
static int device_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static int device_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}
2. read
和 write
函数
这些函数分别实现用户空间程序对设备的读取和写入操作。
read
:读取设备的数据,用户空间调用read()
时触发。write
:将用户空间的数据写入设备,用户空间调用write()
时触发。
static ssize_t device_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {size_t bytes_read = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_to_user(buffer, device_buffer, bytes_read)) {return -EFAULT;}printk(KERN_INFO "Read %zu bytes from device\n", bytes_read);return bytes_read;
}static ssize_t device_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {size_t bytes_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;if (copy_from_user(device_buffer, buffer, bytes_write)) {return -EFAULT;}printk(KERN_INFO "Wrote %zu bytes to device\n", bytes_write);return bytes_write;
}
3. 设备号分配与 cdev
结构
字符设备必须注册到内核中,以使内核能够通过设备号找到驱动程序。主设备号用于标识驱动程序,次设备号用于标识设备实例。
- 使用
alloc_chrdev_region
动态分配设备号。 - 使用
cdev_init
和cdev_add
将字符设备添加到内核中。 - 使用
cdev_del
删除设备。
alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, dev_num, 1);
4. 测试驱动
编写一个简单的用户空间测试程序来与字符设备驱动交互。
用户空间测试程序
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>#define DEVICE_PATH "/dev/mychardev"
#define BUFFER_SIZE 1024int main() {int fd;char write_buffer[BUFFER_SIZE] = "Hello, Kernel!";char read_buffer[BUFFER_SIZE];// 打开设备文件fd = open(DEVICE_PATH, O_RDWR);if (fd < 0) {perror("Failed to open the device");return EXIT_FAILURE;}// 写数据到设备printf("Writing to device: %s\n", write_buffer);if (write(fd, write_buffer, strlen(write_buffer)) < 0) {perror("Failed to write to the device");close(fd);return EXIT_FAILURE;}// 清空读取缓冲区memset(read_buffer, 0, sizeof(read_buffer));// 从设备读取数据if (read(fd, read_buffer, sizeof(read_buffer)) < 0) {perror("Failed to read from the device");close(fd);return EXIT_FAILURE;}// 输出从设备读取到的数据printf("Read from device: %s\n", read_buffer);// 关闭设备文件close(fd);return EXIT_SUCCESS;
}
当成功编写、加载并测试字符设备驱动时,用户空间程序会通过标准输出显示与驱动交互的结果。下面是驱动程序的纯输出示例,假设测试程序成功与字符设备驱动交互:
用户空间测试程序输出
Writing to device: Hello, Kernel!
Read from device: Hello, Kernel!
内核日志 (dmesg
) 输出
[ 123.456789] Registered with major number 240
[ 123.456890] Device opened
[ 123.457123] Wrote 13 bytes to device
[ 123.457345] Read 13 bytes from device
[ 123.457567] Device closed
输出解释
-
用户空间测试程序输出:
- 测试程序将字符串
"Hello, Kernel!"
写入字符设备,并随后读取回相同的字符串。 Writing to device: Hello, Kernel!
表示程序已成功向设备写入数据。Read from device: Hello, Kernel!
表示程序已成功从设备读取数据。
- 测试程序将字符串
-
内核日志 (
dmesg
) 输出:Registered with major number 240
表示字符设备驱动成功注册,并分配了主设备号 240。Device opened
表示设备文件被打开,说明用户空间程序调用了open()
系统调用。Wrote 13 bytes to device
表示用户空间程序写入了 13 字节的数据到设备。Read 13 bytes from device
表示用户空间程序从设备读取了 13 字节的数据。Device closed
表示设备文件被关闭,说明用户空间程序调用了close()
系统调用。
这些输出可以帮助你确认驱动程序的各个操作函数被正确调用,并且用户空间程序与字符设备的交互是成功的。
5. 总结
通过上述步骤可以开发一个简单的字符设备驱动程序。字符设备驱动的核心是通过 file _operations
结构体实现的操作函数,包括 open
、read
、write
和 release
等。在用户空间编写简单的测试程序,使用 open()
、read()
、write()
系统调用与字符设备进行交互,从而验证驱动程序的正确性。
相关文章:

Linux字符设备驱动开发
Linux 字符设备驱动开发是内核模块开发中的一个重要部分,主要用于处理字节流数据设备(如串口、键盘、鼠标等)。字符设备驱动的核心任务是定义如何与用户空间程序交互,通常通过一组文件操作函数进行。这些函数会映射到 open、read、…...

HTML5+JavaScript绘制闪烁的网格错觉
HTML5JavaScript绘制闪烁的网格错觉 闪烁的网格错觉(scintillating grid illusion)是一种视觉错觉,通过简单的黑白方格网格和少量的精心设计,能够使人眼前出现动态变化的效果。 闪烁的栅格错觉,是一种经典的视觉错觉…...

每日OJ题_牛客_拼三角_枚举/DFS_C++_Java
目录 牛客_拼三角_枚举/DFS 题目解析 C代码1 C代码2 Java代码 牛客_拼三角_枚举/DFS 拼三角_枚举/DFS 题目解析 简单枚举,不过有很多种枚举方法,这里直接用简单粗暴的枚举方式。 C代码1 #include <iostream> #include <algorithm> …...

[uni-app]小兔鲜-01项目起步
项目介绍 效果演示 技术架构 创建项目 HBuilderX创建 下载HBuilderX编辑器 HBuilderX/创建项目: 选择模板/选择Vue版本/创建 安装插件: 工具/插件安装/uni-app(Vue3)编译器 vue代码不能直接运行在小程序环境, 编译插件帮助我们进行代码转换 绑定微信开发者工具: 指定微信开…...

安全的价值:构建现代企业的基础
物理安全对于组织来说并不是事后才考虑的问题:它是关键的基础设施。零售商、医疗保健提供商、市政当局、学校和所有其他类型的组织都依赖安全系统来保障其人员和场所的安全。 随着安全技术能力的不断发展,许多组织正在以更广泛的视角看待他们的投资&am…...

门面(外观)模式
简介 门面模式(Facade Pattern)又叫作外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构型设计模式。 通用模板 创建子系统角色类…...

kotlin flow 使用
1 创建flow 方式1 通过携程扩展函数FlowKt中的flow扩展函数可以直接构建flow,只需要传递FlowCollector收集器实现类就可以了 private fun create1(){val intFlow createFlow()println("创建int flow: $intFlow")runBlocking {println("开始收集&…...

vue3 实现文本内容超过N行折叠并显示“...展开”组件
1. 实现效果 组件内文字样式取决与外侧定义 组件大小发生变化时,文本仍可以省略到指定行数 文本不超过时, 无展开,收起按钮 传入文本发生改变后, 组件展示新的文本 2. 代码 文件名TextEllipsis.vue <template><div ref"compRef" class"wq-text-ellip…...

根据源码解析Vue2中对于对象的变化侦测
UI render(state) VUE的特点是数据驱动视图,在这里可以把数据理解为状态,而视图就是用户可以看到的页面,页面是动态变化的,而他的变化或是用户操作引起,或是后端数据变化引起,这些都可以说是数据的状态变…...

爬虫技术深潜:探究 JsonPath 与 XPath 的语法海洋与实战岛屿
Python爬虫中JSON与XML字符串的XPath和JsonPath过滤语法区别对比 在信息爆炸的互联网时代,数据抓取成为了获取宝贵信息的关键技能。对于技术爱好者,特别是Python程序员来说,熟练掌握JSON和XML数据解析方法至关重要。本文旨在深入探讨这两种格…...

纠删码参数自适应匹配问题ECP-AMP实验方案(一)
摘要 关键词:动态参数;多属性决策;critic权重法;DBSCA聚类分析 引言 云服务存储系统是一种基于互联网的数据存储服务,它可以为用户提供大规模、低成本、高可靠的数据存储空间。云服务存储系统的核心技术之一是数据容…...

五、人物持有武器攻击
一、手部添加预制体(武器) 1、骨骼(手) 由于人物模型有骨骼和动画,在添加预制体后,会抓握武器 建一个预制体在手部位置 二、添加武器拖尾 下载拖尾特效 赋值特效中的代码,直接使用 清空里面…...

mysql索引 -- 全文索引介绍(如何创建,使用),explain关键字
目录 全文索引 引入 介绍 创建 使用 表数据 简单搜索 explain关键字 使用全文索引 mysql索引结构详细介绍 -- mysql索引 -- 索引的硬件理解(磁盘,磁盘与系统),软件理解(mysql,与系统io,buffer pool),索引结构介绍和理解(page内部,page之间,为什么是b树)-CSDN博客 全文…...

Wayfair封号的常见原因及解决方案解析
近期关于Wayfair账号封禁的问题引发了广泛讨论。许多用户报告称,他们的Wayfair账户被突然封禁,这一现象不仅影响了用户的购物体验,也对Wayfair的品牌形象造成了一定的冲击。本文将深入探讨Wayfair封号的原因,并提出相应的解决方案…...

计算机视觉方面的一些模块
# __all__ 是一个可选的列表,定义在模块级别。当使用 from ... import * 语句时,如果模块中定义了 # __all__,则只有 __all__ 列表中的名称会被导入。这是模块作者控制哪些公开API被导入的一种方式。 # 使用 * 导入的行为 # 如果模块中有 __a…...

进阶美颜功能技术开发方案:探索视频美颜SDK
视频美颜SDK(SoftwareDevelopmentKit)作为提升视频质量的重要工具,越来越多地被开发者关注与应用。接下俩,笔者将深入探讨进阶美颜功能的技术开发方案,助力开发者更好地利用视频美颜SDK。 一、视频美颜SDK的核心功能 …...

【重学 MySQL】三十八、group by的使用
【重学 MySQL】三十八、group by的使用 基本语法示例示例 1: 计算每个部门的员工数示例 2: 计算每个部门的平均工资示例 3: 结合 WHERE 子句 WITH ROLLUP基本用法示例注意事项 注意事项 GROUP BY 是 SQL 中一个非常重要的子句,它通常与聚合函数(如 COUNT…...

SSM框架VUE电影售票管理系统开发mysql数据库redis设计java编程计算机网页源码maven项目
一、源码特点 smm VUE电影售票管理系统是一套完善的完整信息管理类型系统,结合SSM框架和VUE、redis完成本系统,对理解vue java编程开发语言有帮助系统采用ssm框架(MVC模式开发),系 统具有完整的源代码和数据库&#…...

基于Hive和Hadoop的白酒分析系统
本项目是一个基于大数据技术的白酒分析系统,旨在为用户提供全面的白酒市场信息和深入的价格分析。系统采用 Hadoop 平台进行大规模数据存储和处理,利用 MapReduce 进行数据分析和处理,通过 Sqoop 实现数据的导入导出,以 Spark 为核…...

【软考】高速缓存的组成
目录 1. 说明2. 组成 1. 说明 1.高速缓存用来存放当前最活跃的程序和数据。2.高速缓存位于CPU 与主存之间。3.容量般在几千字节到几兆字节之间。4.速度一般比主存快 5~10 倍,由快速半导体存储器构成。5.其内容是主存局部域的副本,对程序员来说是透明的。…...

UniApp基于xe-upload实现文件上传组件
xe-upload地址:文件选择、文件上传组件(图片,视频,文件等) - DCloud 插件市场 致敬开发者!!! 感觉好用的话,给xe-upload的作者一个好评 背景:开发中经常会有…...

以太网交换安全:端口隔离
一、端口隔离 以太交换网络中为了实现报文之间的二层广播域的隔离,用户通常将不同的端口加人不同的 VLAN大型网络中,业务需求种类繁多,只通过 VLAN实现报文的二层隔离,会浪费有限的VLAN资源。而采用端口隔离功能,则可…...

望繁信科技CTO李进峰受邀在上海外国语大学开展流程挖掘专题讲座
2023年,望繁信科技联合创始人兼CTO李进峰博士受邀在上海外国语大学国际工商管理学院(以下简称“上外管院”)开展专题讲座,畅谈流程挖掘的发展及对企业数字化转型的价值。演讲吸引了上外教授和来自各行各业的领军企业学员百余人。 …...

nicegui组件button用法深度解读,源代码IDE运行和调试通过
传奇开心果微博文系列 前言一、button 组件基本用法1. 最基本用法示例2. 创建带图标按钮 二、button按钮组件样式定制1. 按钮的尺寸调整2. 改变颜色示例3. 按钮的自定义字体大小4. 圆角形状示例5. 自定义边框6. 添加阴影7. 复合按钮8. 浮动按钮9. 可扩展浮动操作按钮QFAB10. 按…...

数据结构:树(并查集)
并查集(Union-Find Disjoint Sets 或 Disjoint Set Union,简称DSU)是一种树型的数据结构,主要用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。在并查集中,通常将n个对象划分为不相交的…...

校园二手交易平台的小程序+ssm(lw+演示+源码+运行)
摘 要 随着社会的发展,社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…...

代码随想录训练营第46天|回文子序列
647. 回文子串 class Solution { public:int count0;void check(string& s, int left, int right){while(left>0&&right<s.length()&&s[left]s[right]){count;left--;right;}}int countSubstrings(string s) {for(int i0; i<s.length(); i){chec…...

使用 PowerShell 命令更改 RDP 远程桌面端口(无需修改防火墙设置)
节选自原文:Windows远程桌面一站式指南 | BOBO Blog 原文目录 什么是RDP开启远程桌面 检查系统版本启用远程桌面连接Windows 在Windows电脑上在MAC电脑上在Android或iOS移动设备上主机名连接 自定义电脑名通过主机名远程桌面使用Hosts文件自定义远程主…...

bilibili实现批量发送弹幕功能
代码如下: import random import time import requests from tkinter import *# 弹幕内容列表 # lis_text [ # 京口瓜洲一水间,钟山只隔数重山。,君不见黄河之水天上来,奔流到海不复回。,起舞弄清影,何似在人间! # ] lis_te…...

如何查看上网记录及上网时间?5种按步操作的方法分享!【小白也能学会!】
“知己知彼,百战不殆”,在数字时代,了解自己的上网行为和时长,不仅能帮助我们更好地管理时间,还能提升工作效率和生活质量。 今天,我们就来分享五种简单易懂的方法,即便是网络小白也能轻松学会…...