【Linux】【字符设备驱动】深入解析

在这里插入图片描述

Linux字符设备驱动程序用于控制不支持随机访问的硬件设备,如串行端口、打印机、调制解调器等。这类设备通常以字符流的形式与用户空间程序进行交互。本节将深入探讨字符设备驱动的设计原理、实现细节及其与内核其他组件的交互。

1. 引言

字符设备驱动程序是Linux内核的重要组成部分,它们负责处理来自用户空间应用程序的读写请求,并将这些请求转换为对硬件设备的实际操作。字符设备驱动程序通常用于控制串行端口、打印机、调制解调器等设备。

2. 字符设备的基本概念

2.1 设备号

在Linux系统中,每个字符设备都有一个唯一的设备号,设备号由主设备号和次设备号组成。主设备号用于标识一组相关设备,次设备号用于区分同一组中的不同设备。

2.1.1 设备号分配

设备号由内核动态分配或手动指定。例如:

sudo mknod /dev/my_char_dev c 240 0

这里,240 是主设备号,0 是次设备号。

2.2 设备文件

字符设备文件通常位于 /dev 目录下,它们代表了实际的硬件设备。设备文件可以通过 mknod 命令创建,也可以通过 udev 规则自动创建。

2.2.1 创建设备文件

使用 mknod 命令创建一个字符设备文件:

sudo mknod /dev/my_char_dev c 240 0

2.3 设备驱动程序

字符设备驱动程序负责处理来自用户空间应用程序的请求,并将这些请求转换为对硬件设备的操作。驱动程序通常包括初始化函数、读写操作函数等。

2.3.1 初始化函数

驱动程序初始化函数负责注册设备号、初始化 file_operations 结构体、注册字符设备等。

2.3.2 文件操作结构

struct file_operations 结构体包含了各种文件操作函数指针,如 readwriteopen 等。

3. 字符设备驱动程序的底层原理

3.1 内核与用户空间交互

字符设备驱动程序通过内核提供的接口与用户空间应用程序进行交互。当用户空间程序对设备文件进行读写操作时,内核会调用相应的驱动程序函数。

3.1.1 系统调用

系统调用是内核与用户空间交互的主要方式。当用户空间程序调用 readwrite 系统调用时,内核会调用对应的驱动程序函数。

3.1.2 文件操作结构

struct file_operations 结构体定义了一系列文件操作函数,这些函数由内核在适当的时机调用。

struct file_operations {int (*read)(struct file *, char __user *, size_t, loff_t *);int (*write)(struct file *, const char __user *, size_t, loff_t *);int (*open)(struct inode *, struct file *);int (*release)(struct inode *, struct file *);...
};

3.2 设备注册与管理

3.2.1 设备号注册

设备号注册是在内核中创建一个设备文件的步骤之一。设备号由主设备号和次设备号组成,通过 register_chrdev_region 函数注册。

register_chrdev_region(dev_num, 1, "my_char_dev");
3.2.2 设备文件操作结构初始化

设备文件操作结构初始化包括设置读写操作函数等。

cdev_init(&c_dev, &fops);
3.2.3 设备注册

设备注册是将设备文件操作结构添加到内核中,使设备可以被访问。

cdev_add(&c_dev, dev_num, 1);

3.3 设备操作函数

字符设备驱动程序需要实现一系列设备操作函数,如读、写、打开、关闭等。

3.3.1 读操作

读操作函数负责从设备中读取数据,并返回给用户空间程序。

static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{if (*ppos >= PAGE_SIZE)return 0;if (copy_to_user(buf, &buf[*ppos], count))return -EFAULT;*ppos += count;return count;
}
3.3.2 写操作

写操作函数负责将用户空间程序的数据写入设备。

static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{if (*ppos >= PAGE_SIZE)return -ENOSPC;if (copy_from_user(&buf[*ppos], buf, count))return -EFAULT;*ppos += count;return count;
}
3.3.3 打开和关闭操作

打开操作函数负责初始化设备,关闭操作函数负责释放设备资源。

static int dev_open(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device opened.\n");return 0;
}static int dev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device closed.\n");return 0;
}

4. 字符设备驱动程序的编写

4.1 模块初始化和卸载

字符设备驱动程序通常是一个内核模块,模块需要定义初始化和卸载函数。

4.1.1 初始化函数

初始化函数通常包括注册设备号、初始化 file_operations 结构体、注册字符设备等。

static int __init dev_init(void)
{// 注册字符设备register_chrdev_region(dev_num, 1, "my_char_dev");// 初始化字符设备结构cdev_init(&c_dev, &fops);// 添加字符设备到设备类class = class_create(THIS_MODULE, "my_char_class");device = device_create(class, NULL, dev_num, NULL, "my_char_dev");// 注册字符设备cdev_add(&c_dev, dev_num, 1);return 0;
}
4.1.2 卸载函数

卸载函数负责注销字符设备、销毁设备类等。

static void __exit dev_exit(void)
{// 删除字符设备cdev_del(&c_dev);// 移除设备device_destroy(class, dev_num);// 销毁设备类class_unregister(class);// 注销字符设备区域unregister_chrdev_region(dev_num, 1);
}

4.2 设备操作函数

字符设备驱动程序需要实现一系列设备操作函数,如读、写、打开、关闭等。

4.2.1 读操作

读操作函数负责从设备中读取数据,并返回给用户空间程序。

static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{if (*ppos >= PAGE_SIZE)return 0;if (copy_to_user(buf, &buf[*ppos], count))return -EFAULT;*ppos += count;return count;
}
4.2.2 写操作

写操作函数负责将用户空间程序的数据写入设备。

static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{if (*ppos >= PAGE_SIZE)return -ENOSPC;if (copy_from_user(&buf[*ppos], buf, count))return -EFAULT;*ppos += count;return count;
}
4.2.3 打开和关闭操作

打开操作函数负责初始化设备,关闭操作函数负责释放设备资源。

static int dev_open(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device opened.\n");return 0;
}static int dev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device closed.\n");return 0;
}

5. 设备文件的创建

设备文件通常位于 /dev 目录下,可以通过 mknod 命令或 udev 规则创建。

5.1 使用 mknod 创建设备文件

sudo mknod /dev/my_char_dev c 240 0

5.2 使用 udev 规则自动创建设备文件

可以编写 udev 规则来自动创建设备文件:

# /etc/udev/rules.d/99-my-device.rulesKERNEL=="my_char_dev", MODE="0660", OWNER="root", GROUP="users", SYMLINK+="my_char_dev"

6. 用户空间程序

用户空间程序用于读写字符设备文件。

6.1 用户空间程序示例

编写一个简单的用户空间程序来读写设备文件:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd;char buffer[256];ssize_t bytes_written, bytes_read;// 打开设备文件fd = open("/dev/my_char_dev", O_RDWR);if (fd == -1) {perror("Failed to open device");return 1;}// 写入数据bytes_written = write(fd, "Hello, World!", 13);if (bytes_written == -1) {perror("Failed to write to device");close(fd);return 1;}// 读取数据bytes_read = read(fd, buffer, sizeof(buffer));if (bytes_read == -1) {perror("Failed to read from device");close(fd);return 1;}printf("Read from device: %.*s\n", (int)bytes_read, buffer);// 关闭设备文件close(fd);return 0;
}

6.2 编译和运行用户空间程序

编译用户空间程序:

gcc -o my_prog my_prog.c

运行用户空间程序:

./my_prog

7. 字符设备驱动的调试

7.1 使用 printk 调试

printk 函数可以用于在内核模块中输出调试信息。

printk(KERN_INFO "Hello, World!\n");

7.2 使用 syslog 调试

syslog 函数可以用于将调试信息发送到系统日志。

#include <linux/syscalls.h>
...
syslog(KERN_INFO, "Hello, World!");

8. 字符设备驱动的优化

8.1 避免死锁

在多线程或多进程环境下,需要小心处理设备的读写操作,避免死锁。

8.1.1 互斥锁

使用互斥锁来保护共享资源。

static DEFINE_MUTEX(my_mutex);mutex_lock(&my_mutex);
// 执行关键操作
mutex_unlock(&my_mutex);

8.2 提高性能

通过优化读写操作,减少不必要的上下文切换,提高设备驱动程序的性能。

8.2.1 非阻塞 I/O

支持非阻塞 I/O 可以提高设备驱动程序的性能。

static int dev_poll(struct file *file, poll_table *wait)
{poll_wait(file, wait, POLLIN | POLLOUT);return POLLIN | POLLOUT;
}static const struct file_operations fops = {.owner          = THIS_MODULE,.read           = dev_read,.write          = dev_write,.open           = dev_open,.release        = dev_release,.poll           = dev_poll,
};

9. 字符设备驱动的应用案例

9.1 串行通信设备驱动

串行通信设备驱动用于控制串行端口,如 COM 端口。

// 串行通信设备驱动示例// 设备打开操作
static int dev_open(struct inode *inode, struct file *file)
{// 初始化串行端口init_serial_port();return 0;
}// 设备关闭操作
static int dev_release(struct inode *inode, struct file *file)
{// 释放串行端口资源release_serial_port();return 0;
}// 设备读操作
static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{// 从串行端口读取数据read_from_serial_port(buf, count);return count;
}// 设备写操作
static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{// 将数据写入串行端口write_to_serial_port(buf, count);return count;
}

9.2 LED 控制设备驱动

LED 控制设备驱动用于控制 LED 灯。

// LED 控制设备驱动示例// 设备打开操作
static int dev_open(struct inode *inode, struct file *file)
{// 初始化 LEDinit_led();return 0;
}// 设备关闭操作
static int dev_release(struct inode *inode, struct file *file)
{// 释放 LED 资源release_led();return 0;
}// 设备读操作
static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{// 读取 LED 状态read_led_status(buf, count);return count;
}// 设备写操作
static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{// 设置 LED 状态set_led_status(buf, count);return count;
}

10. 字符设备驱动的高级特性

10.1 设备属性

设备属性允许用户通过 /sys/class/<class>/device 目录下的文件来查询和修改设备状态。

10.1.1 添加设备属性

使用 sysfs API 添加设备属性:

static ssize_t show_attr(struct device *dev, struct device_attribute *attr, char *buf)
{return sprintf(buf, "%d\n", some_value);
}static ssize_t store_attr(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{some_value = atoi(buf);return count;
}static DEVICE_ATTR(my_attr, 0644, show_attr, store_attr);static struct attribute *my_attrs[] = {&dev_attr_my_attr.attr,NULL,
};static struct device_type my_dev_type = {.name = "my_char_dev",.attrs = my_attrs,
};class_create(THIS_MODULE, "my_char_class");
device_create(class, NULL, dev_num, NULL, "my_char_dev");
device_create_file(device, &dev_attr_my_attr);

10.2 设备同步

为了保证数据的一致性和完整性,设备驱动程序需要处理同步问题。

10.2.1 原子操作

使用原子操作来保护数据的一致性。

static atomic_t my_counter;atomic_inc(&my_counter);
atomic_dec(&my_counter);

11. 字符设备驱动的错误处理

11.1 错误检测

在设备驱动程序中检测和处理错误是必要的。

11.1.1 返回值检查

使用返回值来检查函数是否成功执行。

if (ioctl(fd, MY_IOCTL_CMD, &arg) < 0) {perror("ioctl failed");return -1;
}

11.2 错误报告

在设备驱动程序中报告错误信息。

11.2.1 日志记录

使用 printksyslog 记录错误信息。

printk(KERN_ERR "Error occurred in driver.\n");

12. 总结

Linux字符设备驱动程序用于控制不支持随机访问的硬件设备,如串行端口、打印机等。通过编写字符设备驱动程序,可以实现对这些设备的高效控制。希望本文能帮助读者更好地理解和掌握Linux字符设备驱动程序的开发技巧,并深入了解其底层原理。

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

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

相关文章

计算机毕业设计Python异常流量检测 流量分类 流量分析 网络流量分析与可视化系统 网络安全 信息安全 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

排序算法之选择排序堆排序

算法时间复杂度辅助空间复杂度稳定性选择排序O(N^2)O(1)不稳定堆排序O(NlogN)O(1)不稳定 1.选择排序 这应该算是最简单的排序算法了&#xff0c;每次在右边无序区里选最小值&#xff0c;没有无序区时&#xff0c;就宣告排序完毕 比如有一个数组&#xff1a;[2,3,2,6,5,1,4]排…

搜索二维矩阵 II(java)

题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 代码思路&#xff1a; 用暴力算法&#xff1a; class Solution {public boolean searchMatrix(…

week 9 - Entity-Relationship Modelling

一、数据库设计的重要性 • 设计数据库可使查询更高效、简洁。 • 减少数据冗余&#xff08;data redundancy&#xff09;&#xff0c;提升表的整洁性。 二、Key Components of ER Modelling 实体-关系建模的基本构成 1. 实体&#xff08;Entity&#xff09;&#xff1a;表…

玻璃效果和窗户室内效果模拟

一、玻璃效果 首先来讲如何模拟玻璃效果。玻璃的渲染包括三部分&#xff0c;普通场景物体的渲染、反射和折射模拟、毛玻璃模拟。作为场景物体&#xff0c;那么类似其它场景物体Shader一样&#xff0c;可以使用PBR、BlingPhong或者Matcap&#xff0c;甚至三阶色卡通渲染都可以。…

STL算法之set相关算法

STL一共提供了四种与set(集合)相关的算法&#xff0c;分别是并集(union)、交集(intersection)、差集(difference)、对称差集(symmetric difference)。 目录 set_union set_itersection set_difference set_symmetric_difference 所谓set&#xff0c;可细分为数学上定义的和…

房屋结构安全监测系统守护房屋安全卫士

一、系统背景 随着时间的流逝&#xff0c;建筑物的主体结构、设备设施等会因为自然老化、材料疲劳、使用环境的变化以及维护不当等各种因素的影响&#xff0c;逐渐出现性能下降甚至安全隐患。因此&#xff0c;进行房屋安全监测显得尤为重要。房屋结构安全是指建筑物的结构体系在…

uniapp实现组件竖版菜单

社区图片页面 scroll-view scroll-view | uni-app官网 (dcloud.net.cn) 可滚动视图区域。用于区域滚动。 需注意在webview渲染的页面中&#xff0c;区域滚动的性能不及页面滚动。 <template><view class"pics"><scroll-view class"left"…

241127学习日志——[CSDIY] [InternStudio] 大模型训练营 [20]

CSDIY&#xff1a;这是一个非科班学生的努力之路&#xff0c;从今天开始这个系列会长期更新&#xff0c;&#xff08;最好做到日更&#xff09;&#xff0c;我会慢慢把自己目前对CS的努力逐一上传&#xff0c;帮助那些和我一样有着梦想的玩家取得胜利&#xff01;&#xff01;&…

多模态图像生成模型Qwen2vl-Flux,利用Qwen2VL的视觉语言理解能力增强FLUX,可集成ControlNet

Qwen2vl-Flux 是一种先进的多模态图像生成模型&#xff0c;它利用 Qwen2VL 的视觉语言理解能力增强了 FLUX。该模型擅长根据文本提示和视觉参考生成高质量图像&#xff0c;提供卓越的多模态理解和控制。让 FLUX 的多模态图像理解和提示词理解变得很强。 Qwen2vl-Flux有以下特点…

Web day06 JDBC Mybatis

目录 JDBC: MyBatis 框架&#xff1a; 环境配置&#xff1a; 编写持久层&#xff08;dao层&#xff09;接口 并写sql语句&#xff1a; 单元测试&#xff1a; JDBC MyBatis 优缺点&#xff1a; 数据库链接池&#xff1a; 运用Mybaits增删改查&#xff1a; 删除&#xff1…

vscode python code runner执行乱码

打开vscode code runner插件配置&#xff0c;如图所示&#xff1a; 然后在setting.json修改运行python的默认命令&#xff1a; 将原来 替换成 "python":"set PYTHONIOENCODINGutf8 && python", 参考&#xff1a;Vscode——python环境输出中文乱…

在VMware虚拟机上安装Kali Linux的详细教程(保姆级教程)

在VMware虚拟机上安装Kali Linux的详细教程 引言 Kali Linux是一个基于Debian的Linux发行版&#xff0c;专为渗透测试和安全审计而设计。它内置了数百种安全工具&#xff0c;广泛应用于网络安全领域。通过在VMware虚拟机上安装Kali Linux&#xff0c;您可以在不影响主操作系统…

分布式调用 - 服务间的远程调用RPC

文章目录 导图PreRPC 概述RPC 调用过程RPC 动态代理1. 接口定义 (SeverProvider)2. 实现类 (ServerProviderImpl)3. 动态代理类 (DynamicProxy)4. 客户端 (Client)5. 代码工作流程6. 总结和注意点7. 结果输出8. 小结 RPC 序列化1. JSON (JavaScript Object Notation)2. Hessian…

Qt关于padding设置不起作用的的解决办法

观察以下的代码&#xff1a; MyWidget::MyWidget(QWidget *parent): QWidget{parent},m_btn(new QToolButton(this)) {this->setFixedSize(500,500);m_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);m_btn->setIcon(QIcon("F:tabIcon/person-white.s…

监控视频汇聚平台:Liveweb视频监控管理平台方案详细介绍

Liveweb国标视频综合管理平台是一款以视频为核心的智慧物联应用平台。它基于分布式、负载均衡等流媒体技术进行开发&#xff0c;提供广泛兼容、安全可靠、开放共享的视频综合服务。该平台具备多种功能&#xff0c;包括视频直播、录像、回放、检索、云存储、告警上报、语音对讲、…

网关整合sentinel无法读取nacos配置问题分析

sentinel无法读取nacos配置问题分析 1.spring-cloud-gateway整合sentinel2.问题现象3.原因猜测4.源码分析4. 结语 最近公司需要上线一个集约项目&#xff0c;虽然为内网项目&#xff0c;但曾经有过内网被攻破&#xff0c;导致内部系统被攻击的案例&#xff0c;且集约系统同时在…

使用ECharts创建带百分比标注的环形图

在数据可视化领域&#xff0c;环形图是一种非常有效的图表类型&#xff0c;它能够清晰地展示各部分与整体的关系。今天&#xff0c;我们将通过ECharts来创建一个带百分比标注的环形图&#xff0c;并详细解释如何实现这一效果。 1. 数据准备 首先&#xff0c;我们定义了一些基础…

AIGC训练效率与模型优化的深入探讨

文章目录 1.AIGC概述2.AIGC模型训练效率的重要性3.模型优化的概念与目标4.模型优化策略4.1 学习率调节4.2 模型架构选择4.3 数据预处理与增强4.4 正则化技术4.5 量化与剪枝 5.代码示例6.结论 人工智能领域的发展&#xff0c;人工智能生成内容&#xff08; AIGC&#xff09;越来…

keil 5. Flash Timeout. Reset the Target and try it again.

使用官方STM32 ST-LINK Utility 烧写软件 KEIL 5, 设置DFP 包支持FLASH烧写算法 Keil 5, Flash Timeout. Reset the Target and try it again.-CSDN博客