Linux驱动学习(三)--字符设备架构与注册
1.内核如何维护设备号的?
chrdevs指针数组
在内核中有一个重要的全局变量:chrdevs指针数组,位于char_dev.c文件中

chrdevs指针数组的每一个成员指向一个char_device_struct结构体,该结构体中,最重要的变量是cdev指针
cdev结构体
cdev结构体:

该结构体定义位于cdev.h
在cdev中,kobj和owner成员不用太关注,内核会自己填充;dev填充设备号的成员;我们需要关心的最重要的成员是file_operations *ops
file_operations结构体
file_operations结构(位于fs.h)
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
常用的函数有:
owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
llseek 函数用于修改文件当前的读写位置
read 函数用于读取设备文件
write 函数用于向设备文件写入(发送)数据
poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写
unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应
compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl
mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制
open 函数用于打开设备文件
release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
cdev之间通过链表串起来
需要我们自己实现的东西有:
- cdev
- read
- write
2.用户层如何找到驱动的?
答案:借助文件系统
mknod命令
创建设备节点命令:
mknod /dev/loh c 237 0
- loh:设备名称
- c:字符设备
- 237:主设备号
- 0:次设备号
输入该命令过后,在内核中会产生一个inode结构体,inode会记录文件的相关信息
inode结构体
inode结构体(位于fs.h)
struct inode {umode_t i_mode;unsigned short i_opflags;kuid_t i_uid;kgid_t i_gid;unsigned int i_flags;#ifdef CONFIG_FS_POSIX_ACLstruct posix_acl *i_acl;struct posix_acl *i_default_acl;
#endifconst struct inode_operations *i_op;struct super_block *i_sb;struct address_space *i_mapping;#ifdef CONFIG_SECURITYvoid *i_security;
#endif/* Stat data, not accessed from path walking */unsigned long i_ino;/** Filesystems may only read i_nlink directly. They shall use the* following functions for modification:** (set|clear|inc|drop)_nlink* inode_(inc|dec)_link_count*/union {const unsigned int i_nlink;unsigned int __i_nlink;};dev_t i_rdev;loff_t i_size;struct timespec i_atime;struct timespec i_mtime;struct timespec i_ctime;spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */unsigned short i_bytes;unsigned int i_blkbits;blkcnt_t i_blocks;#ifdef __NEED_I_SIZE_ORDEREDseqcount_t i_size_seqcount;
#endif/* Misc */unsigned long i_state;struct mutex i_mutex;unsigned long dirtied_when; /* jiffies of first dirtying */struct hlist_node i_hash;struct list_head i_wb_list; /* backing dev IO list */struct list_head i_lru; /* inode LRU list */struct list_head i_sb_list;union {struct hlist_head i_dentry;struct rcu_head i_rcu;};u64 i_version;atomic_t i_count;atomic_t i_dio_count;atomic_t i_writecount;const struct file_operations *i_fop; /* former ->i_op->default_file_ops */struct file_lock *i_flock;struct address_space i_data;
#ifdef CONFIG_QUOTAstruct dquot *i_dquot[MAXQUOTAS];
#endifstruct list_head i_devices;union {struct pipe_inode_info *i_pipe;struct block_device *i_bdev;struct cdev *i_cdev;};__u32 i_generation;#ifdef CONFIG_FSNOTIFY__u32 i_fsnotify_mask; /* all events this inode cares about */struct hlist_head i_fsnotify_marks;
#endif#ifdef CONFIG_IMAatomic_t i_readcount; /* struct files open RO */
#endifvoid *i_private; /* fs or device private pointer */
};
其中比较重要我们需要关心的是设备号成员:

有了这个设备号,我们就可以找到 chrdevs数组成员
3.汇总图


此图参考:Linux字符设备驱动开发(一)
4.字符设备注册
常用相关函数:
- void cdev_init(struct cdev *, const struct file_operations *);
- int cdev_add(struct cdev *, dev_t, unsigned);
- void cdev_del(struct cdev *);
实验
加载模块

mknod创建设备节点

应用程序
驱动代码
/**cdev.c*Original Author: luoyunheng, 2025-02-20** Linux驱动之字符设备注册
*/#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>static int major = 222;
static int minor = 0;static dev_t devno;
static struct cdev dev;static int hello_open(struct inode *inode, struct file *filep)
{printk("hello_open()\n");return 0;
}static struct file_operations hello_ops =
{.open = hello_open,
};static int hello_init(void)
{int result;int error;printk("hello_init\n");devno = MKDEV(major, minor);result = register_chrdev_region(devno, 1, "loh");if (result < 0 ) {printk("register dev number failed\n");return result;}cdev_init(&dev, &hello_ops);error = cdev_add(&dev, devno, 1);if (error < 0) {printk("cdev_add fail\n");unregister_chrdev_region(devno, 1);return error;}return 0;
}static void hello_exit(void)
{printk("hello_exit\n");cdev_del(&dev);unregister_chrdev_region(devno, 1);return;
}module_init(hello_init);
module_exit(hello_exit);
应用程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char **argv)
{int fd;fd = open("/dev/loh", O_RDWR);if(fd < 0) {perror("");return 0;} return 0;
}
相关文章:
Linux驱动学习(三)--字符设备架构与注册
1.内核如何维护设备号的? chrdevs指针数组 在内核中有一个重要的全局变量:chrdevs指针数组,位于char_dev.c文件中 chrdevs指针数组的每一个成员指向一个char_device_struct结构体,该结构体中,最重要的变量是cdev指针…...
软件工程应试复习(考试折磨版)
针对学校软件工程考试,参考教材《软件工程导论(第6版)》1-8章 学习的艺术:不断地尝试,我一定会找到高效用的方法,让学习变成一门艺术,从应试备考中解救出我的时间同胞们。 好嘞!既然…...
【JAVAEE】多线程
【JAVAEE】多线程 一、进程1.1 进程的定义1.2 进程和线程的联系 二、线程2.1 JConsole工具2.2 创建线程2.2.1 Thread类,start(),run()2.2.2 继承Thread类2.2.3 实现Runnable接口2.2.4 匿名内部类2.2.5 使用…...
5.10 P-Tuning v2:多层级提示编码的微调革新
P-Tuning v2:多层级提示编码的微调革新 一、技术架构解析 #mermaid-svg-4Wy6vkXZi67hY9PZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-4Wy6vkXZi67hY9PZ .error-icon{fill:#552222;}#mermaid-svg-4Wy6vkXZi67h…...
LLM中的Benchmark是什么
LLM中的Benchmark是什么 “DeepSeek推动价值重估Benchmark” DeepSeek这家公司或其相关技术的发展,促使Benchmark这家机构对相关资产或企业的价值进行重新评估。“Benchmark”在这里是一家研究机构或金融分析机构。 “Benchmark”常见的意思是“基准;水准点,基准点”,作…...
PMP项目管理—整合管理篇—6.实施整体变更控制
文章目录 基本信息4W1HITTO输入工具与技术输出 变更请求请求时间修改内容变更来源变更类型 实施已批准变更的形式纠正措施预防措施缺陷补救更新 变更控制工具 实施整体变更控制规则态度规则项目变更控制委员会(CCB)变更应对分析计划、执行期后期或收尾 基…...
深度学习之特征提取
前言 深度学习就是把输入转换成一个高维的向量,之后利用这个向量去完成分类、回归等任务。 深度学习特征工程知识图谱 1. 特征提取的本质 核心目标:将原始数据→高维语义特征向量 监督驱动:标签决定特征提取方向 典型架构: …...
Gurobi 并行计算的一些问题
最近尝试用 gurobi 进行并行计算,即同时用多个 cpu 核计算 gurobi 的 model,但是发现了不少问题。总体来看,gurobi 对并行计算的支持并不是那么好。 gurobi 官方对于并行计算的使用在这个网址,并有下面的大致代码: i…...
堆、栈、最小堆
堆是什么 结构属性 堆是一棵完全二叉树,即除最后一层外,其他层节点均填满,且最后一层节点从左到右连续分布。 排序属性: 根据类型不同,堆分为: 最大堆(Max-Heap) :每…...
基于 Spring AI 的 HIS 系统智能化改造
【Spring AI 的背景与现状】 Spring AI 是 Spring 生态里整的一个新活儿,专门给开发者提供搞 AI 驱动的应用的工具和框架。虽然 Spring AI 已经鼓捣了挺长时间,但截至现在(2025年2月),它还没正式发布。不过࿰…...
React进阶之前端业务Hooks库(五)
前端业务Hooks库 Hooks原理useStateuseEffect上述问题useState,useEffect 复用的能力练习:怎样实现一套React过程中的hooks状态 & 副作用Hooks原理 不能在循环中、条件判断、子函数中调用,只能在函数最外层去调用useEffect 中,deps 为空,执行一次useState 使用: imp…...
常见锁类型介绍
下面结合代码详细介绍 Mutex、RW Lock、Futex、自旋锁、信号量、条件变量 和 synchronized,并分析它们的适用场景、特点以及为什么这些锁适用于特定场景。我们将从锁的实现机制和性能特点出发,解释其适用性。 1. Mutex(互斥锁) 代…...
Java中,Scanner和System.out超时的解决方法及原理
ACM 模式的原理 在输入输出的时候,会先将输入输出的东西放在一个文件里,这个文件也叫做 IO 设备 为什么 Scanner 会慢 new 一个 Scanner ,在 Scanner 里面调用 next 的时候,程序会直接访问 IO 设备。在调用一个 next 的时候&…...
一种数据高效具身操作的原子技能库构建方法
25年1月来自京东、中科大、深圳大学、海尔集团、地平线机器人和睿尔曼智能科技的论文“An Atomic Skill Library Construction Method for Data-Efficient Embodied Manipulation”。 具身操控是具身人工智能领域的一项基本能力。尽管目前的具身操控模型在特定场景下表现出一定…...
云创智城YunCharge 新能源二轮、四轮充电解决方案(云快充、万马爱充、中电联、OCPP1.6J等多个私有单车、汽车充电协议)之新能源充电行业系统说明书
云创智城YunCharge 新能源充电行业系统说明书 ⚡官方文档 ⚡官网地址 1. 引言 随着全球环境保护和能源危机的加剧,新能源汽车行业得到了快速发展,充电基础设施建设也随之蓬勃发展。新能源充电行业系统旨在提供高效、便捷的充电服务,满足电…...
JVM垃圾回收器深度底层原理分析与知识体系构建
一、垃圾回收的基本步骤 标记(Marking) 从GC Roots(如虚拟机栈、方法区静态变量、本地方法栈等)出发,遍历对象引用链,标记所有可达对象为存活对象,未被标记的则视为垃圾。此阶段需暂停用户线程&…...
30.[前端开发-JavaScript基础]Day07-数组Array-高阶函数-日期Date-DOM
JavaScript的DOM操作 (一) 1 什么是DOM? 认识DOM和BOM 深入理解DOM 2 认识DOM Tree DOM Tree的理解 3 DOM的整体结构 DOM的学习顺序 DOM的继承关系图 document对象 4 节点、元素导航 节点(Node)之间的导航&…...
IP、网关、子网掩码、DNS 之间的关系详解
IP、网关、子网掩码、DNS 之间的关系详解 在计算机网络中,IP、网关、子网掩码和 DNS 是几个关键概念,它们协同工作,共同保障网络通信的顺畅。本文将详细探讨它们之间的关系。 一、IP 地址 IP 地址是网络中设备的唯一标识,如同现…...
【Day50 LeetCode】图论问题 Ⅷ
一、图论问题 Ⅷ 1、dijkstra算法 堆优化 采用堆来优化,适合节点多的稀疏图。代码如下: # include<iostream> # include<vector> # include<list> # include<queue> # include<climits>using namespace std;class myco…...
结构体介绍及内存大小分配问题
结构体 一.结构体的介绍1.1结构体的声明1.2匿名结构体1.3结构的自引用1.4使用 typedef 简化结构体类型名 二.结构体内存对齐2.1内存对齐规则2.2结构体内存对齐原因2.3修改默认对齐数 在 C 语言中,结构体(struct)是一种用户自定义的数据类型&a…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
