LDD学习笔记 -- Linux字符设备驱动
LDD学习笔记 -- Linux字符设备驱动
- 虚拟文件系统 VFS
- 设备号
- 相关Kernel APIs
- 动态申请设备号
- 动态创建设备文件
- 内核空间和用户空间的数据交换
- 系统调用方法
- read
- write
- lseek
- 写一个伪字符设备驱动
- 在主机上测试pcd(HOST)
- 在目标板上测试pcd(TARGET)
字符驱动程序用于与Linux内核中的设备进行交互;
字符设备指的是像内存区域这样的硬件组件,通常称为伪设备;
用户空间应用程序通常使用open
read
write
等系统调用与这些设备通信;
虚拟文件系统 VFS
把用户空间的系统调用连接到设备驱动的系统调用实现方法上。
内核的虚拟文件系统 virtual file system,在内核空间
设备驱动需要使用内核的API向虚拟文件系统注册
设备号
Major numbers(指示特定的驱动) + Minor numbers(表示指定的设备文件)
设备创建时候在VFS注册设备号,虚拟文件系统,将设备文件的设备号与驱动程序列表进行比较,选择正确的驱动程序,并将用户请求连接到对应驱动程序的文件操作方法。
相关Kernel APIs
kernel functions and data structures(Creation) | (Deletion) | kernel header file |
---|---|---|
alloc_chrdev_region() | unregister_chrdev_region() | include/linux/fs.h |
cdev_init() cdev_add() | cdev_del() | include/linux/cdev.h |
device_creat() class_creat() | device_destory() class_destory | include/linux/device.h |
copy_to_user() copy_from_user() | include/linux/uaccess.h | |
VFS structure definitions | include/linux/cdev.h |
动态申请设备号
alloc_chrdev_region()
可以动态申请主设备号,保证唯一性,传输设备号(dev_t [u32]
)地址和次设备号起始(一般0)和个数。
dev_t device_number; //32bit
int minor_no = MINOR(device_number); //后20bit `kdev_t.h`
int major_no = MAJOR(device_number); //前12bitMKDEV(int major, int minor);
动态创建设备文件
当收到uevent
,udev
根据uevent内存储的细节在dev
目录下创建设备文件。
class_create
:在sysf中创建一个目录/sys/Class/<your_class_name>
device_create
:在上面目录下使用设备名创建一个子目录/sys/Class/<your_class_name>/<your_device_name> /dev
这里的dev文件存储设备名主副设备号等
udev
:用户空间的应用,动态创建设备文件/sys/Class/<your_class_name>/<your_device_name> /dev
--> dev/your_device_name
内核空间和用户空间的数据交换
用户空间的指针不是完全可信的,用户地址空间有时可能无效,虚拟内存管理器可以交换出这些内存位置。
内核级代码不能直接引用用户级内存指针;
使用内核数据复制工具copy_to_user
copy_from_user
。工具会检查用户空间指针是否有效
系统调用方法
read
用户级进程执行read
系统调用从文件中读取。文件可以是普通文件,也可以是一个设备文件(处理具体设备)。
例如前面的伪字符设备,有一块内存数组(设备内存buffer)。当用户程序在该设备文件上发出read系统调用时,应该将数据从设备buffer传到用户buffer。该数据拷贝发生在内核端到用户端。
write
将数据从用户空间复制到内核空间,
用户程序想把一些数据写入设备内存buffer。
lseek
改变f_pos(struct file)
变量的位置,将文件位置指针向前/向后移动。
写一个伪字符设备驱动
- 动态申请设备号
- 创建
cdev
结构体变量和file_operiations
结构体变量 - 使用fops初始化字符设备结构体变量
- 向内核VFS注册设备
- 实现
file operiation
的方法 - 初始化
file operiation
变量 - 创建设备文件
class_create()
device_create()
- 驱动清理函数功能实现
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <uapi/asm-generic/errno-base.h>#define DEV_MEM_SIZE 512/* pseudo device's memory */
char device_buffer[DEV_MEM_SIZE];/* This hold the device number */
dev_t device_number;/* Cdev variable */
struct cdev pcd_cdev;loff_t pcd_llseek(struct file *filp, loff_t offset, int whence)
{pr_info("%s\n", __func__);loff_t temp;switch (whence){case SEEK_SET:if ((offset > DEV_MEM_SIZE) || (offset < 0))return -EINVAL;filp->f_pos = offset;break;case SEEK_CUR:temp = filp->f_pos + offset;if ((temp > DEV_MEM_SIZE) || (offset < 0))return -EINVAL;filp->f_pos = temp;break;case SEEK_END:temp = DEV_MEM_SIZE + offset;if ((temp > DEV_MEM_SIZE) || (offset < 0))return -EINVAL;filp->f_pos = temp;break;default:return -EINVAL;}pr_info("New value of the file position = %lld\n", filp->f_pos);return filp->f_pos;// return 0;}ssize_t pcd_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
{pr_info("%s :Read requested for %zu bytes\n", __func__, count);if((*f_pos + count) > DEV_MEM_SIZE)count = DEV_MEM_SIZE - *f_pos;if(copy_to_user(buff, &device_buffer[*f_pos], count)){return -EFAULT;}*f_pos += count;pr_info("Number of bytes successful read = %zu\n", count);pr_info("Update file position = %lld\n", *f_pos);return count;
}
ssize_t pcd_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
{pr_info("%s :Write requested for %zu bytes, current file position = %lld\n", __func__, count, *f_pos);if((*f_pos + count) > DEV_MEM_SIZE)count = DEV_MEM_SIZE - *f_pos;if(!count)return -ENOMEM;if(copy_from_user(&device_buffer[*f_pos], buff, count)){return -EFAULT;}*f_pos += count;pr_info("Number of bytes successful writtens = %zu\n", count);pr_info("Update file position = %lld\n", *f_pos);return count;
}
int pcd_open(struct inode *inode, struct file *filp)
{pr_info("%s\n", __func__);return 0;
}
int pcd_release(struct inode *inode, struct file *filp)
{pr_info("%s\n", __func__);return 0;
}/* file operations variable */
struct file_operations pcd_fops = {.open = pcd_open,.write = pcd_write,.read = pcd_read,.llseek = pcd_llseek,.release = pcd_release,.owner = THIS_MODULE
};struct class *class_pcd;
struct device *device_pcd;static int __init pcd_driver_init(void)
{pr_info("pcd_driver_init\n");/* 1. Dynamically allocate a device number */alloc_chrdev_region(&device_number, 0, 1, "pcd");pr_info("Device number <major>:<minor> = %d:%d\n", MAJOR(device_number), MINOR(device_number));/* 2. Initialize the cdev structure with fops */cdev_init(&pcd_cdev, &pcd_fops);/* 3. Register a device(cdev structure) with VFS */pcd_cdev.owner = THIS_MODULE;cdev_add(&pcd_cdev, device_number, 1);/* creat device class under /sys/class / */class_pcd = class_create(THIS_MODULE, "pcd_class");/* populate the sysfs with device information */device_pcd = device_create(class_pcd, NULL, device_number, NULL, "pcd");pr_info("Module init was successful\n");return 0;
}/* This is module clean-up entry point */
static void __exit pcd_driver_exit(void)
{pr_info("my hello module exit\n");device_destroy(class_pcd, device_number);class_destroy(class_pcd);cdev_del(&pcd_cdev);unregister_chrdev_region(device_number, 1);pr_info("module unloaded\n");
}/* registration */
module_init(pcd_driver_init);
module_exit(pcd_driver_exit);/* This is description information about the module */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NAME");
MODULE_DESCRIPTION("A pseudo device driver");
在主机上测试pcd(HOST)
使用echo
命令测试向PCD写数据
使用cat
命令测试从PCD读数据
在目标板上测试pcd(TARGET)
需要在用户空间写一个应用程序(测试应用)来测试字符设备驱动程序。使用对应目标板的编译工具链编译.c
文件成目标板上的可执行文件,有没有.exe
后缀都可,自己知道就行。
arm-buildroot-linux-gnueabihf-gcc ./pcd_drv_test.c -o pcd_dev_test
将上面设备驱动编译出的目标板的.ko
文件和我们的测试应用文件都放到目标板上。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./pcd_drv_test -w hello fpn233~* ./pcd_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[512];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf(" %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/pcd", O_RDWR);if (fd == -1){printf("can not open file /dev/pcd\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 512 ? len : 512;write(fd, argv[2], len);}else{len = read(fd, buf, 512); buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}
相关文章:

LDD学习笔记 -- Linux字符设备驱动
LDD学习笔记 -- Linux字符设备驱动 虚拟文件系统 VFS设备号相关Kernel APIs动态申请设备号动态创建设备文件内核空间和用户空间的数据交换系统调用方法readwritelseek 写一个伪字符设备驱动在主机上测试pcd(HOST)在目标板上测试pcd(TARGET) 字符驱动程序用于与Linux内核中的设备…...
杰理AC63串口收发实例
在event.h文件中预定义串口消息 #define DEVICE_EVENT_FROM_MY_UART ((M << 24) | (Y << 16) | (U << 8) | \0)在app_spp_and_le.c文件里对SYS_DEVICE_EVENT做处理,添加收到DEVICE_EVENT_FROM_MY_UART消息时的处理函数my_rx_handler(); cas…...

麦芯(MachCore)开发教程1 --- 设备软件中间件
黄国强 2024/1/10 acloud163.com 对任何公司来说,在短时间内开发一款高质量设备专用软件,是一件不太容易做到的事情。麦芯是笔者发明的一款设备软件中间件产品。麦芯致力于给设备厂商提供一个开发工具和平台,让客户快速高效的开发自己的设备专…...
reset命令
作用:将当前 HEAD 重置为指定状态 Git 的四个区域 Workspace:工作区,就是你平时存放项目代码的地方;Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表…...
Linux内核--进程管理(十二)LinuxIO基础知识与概念
目录 一、引言 二、IO基本概念 ------>2.1、内存空间划分 ------>2.2、读写操作 ------>2.3、用户态切换到内核态的3种方式 三、PIO&DMA ------>3.1、PIO 工作原理 ------>3.2、DMA 工作原理 四、缓冲IO和直接IO ------>4.1、缓冲 IO ------&…...

gem5学习(11):将缓存添加到配置脚本中——Adding cache to the configuration script
目录 一、Creating cache objects 1、Classic caches and Ruby 二、Cache 1、导入SimObject(s) 2、创建L1Cache 3、创建L1Cache子类 4、创建L2Cache 5、L1Cache添加连接函数 6、为L1ICache和L1DCache添加连接函数 7、为L2Cache添加内存侧和CPU侧的连接函数 完整代码…...

上海雏鸟科技无人机灯光秀跨年表演点亮三国五地夜空
2023年12月31日晚,五场别开生面的无人机灯光秀跨年表演在新加坡圣淘沙、印尼雅加达、中国江苏无锡、浙江衢州、陕西西安等五地同步举行。据悉,这5场表演背后均出自上海的一家无人机企业之手——上海雏鸟科技。 在新加坡圣淘沙西乐索海滩,500架…...

学生备考护眼台灯怎么样选择?2024五款好用台灯安利
随着现代人生活水平的提高,人们对保护视力和眼健康的重视也日益提高。然而,长时间使用电子设备和不合适的光线环境却成为了我们眼健康的潜在威胁。所以,为了有效地保护我们的眼睛,护眼台灯成为了许多人的选择。 护眼台灯作为一种能…...

Java学习,一文掌握Java之SpringBoot框架学习文集(6)
🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…...
美团点评秋招前端测评分享
一. 选择题 1. 甲乙二人各自加工一批同样数量的零件,甲完成一半时,乙完成150个,甲全部完成时,乙完成全部的5/6,求这批零件一共有(C)个 A. 320 B. 400 C. 360 D. 420 2. 分析如…...

docker安装nodejs,并更改为淘宝源
拉取官方 Node.js 镜像 docker pull node:latest创建 Dockerfile,并更改 NPM 下载源为淘宝源,设置为全局持久化 # 使用最新版本的Node.js作为基础镜像 FROM node:latest# 设置工作目录为/app WORKDIR /app # 更改 NPM 下载源为淘宝源,并设置…...

Vue中的class和style绑定
聚沙成塔每天进步一点点 本文内容 ⭐ 专栏简介动态绑定class对象语法数组语法 动态绑定style对象语法多重值 ⭐ 写在最后 ⭐ 专栏简介 Vue学习之旅的奇妙世界 欢迎大家来到 Vue 技能树参考资料专栏!创建这个专栏的初衷是为了帮助大家更好地应对 Vue.js 技能树的学习…...

出版实务 | 出版物的成本及其构成
文章目录 出版物成本的总体构成直接成本开发成本制作成本 间接成本期间费用 本量利分析原则特点和作用变动成本项目固定成本项目本量利分析的基本公式及其应用定价发行折扣率销售数量单位销售收入销售收入总额单位销售税金销售税金总额变动成本总额单位变动成本固定成本总额单位…...

docker 部署项目的操作文档,安装nginx
目录 1 部署环境检查2 相关知识点2.1 docker默认镜像存放地址2.2 docker 的镜像都是tar 包?2.3 Docker-compose 是直接使用镜像创建容器?2.4 Docker Compose down 就是将容器删除?2.5 删除,会删除挂载嘛2.6 DockerFile 和 docker …...
spring boot 源码解读与原理分析
一、概述 Spring Boot是一个基于Spring框架的开源项目,旨在简化Spring应用程序的创建和部署。它通过自动配置和约定大于配置的原则,使得开发者能够快速构建独立、可运行的、生产级别的Spring应用程序。本文将对Spring Boot的源码进行解读,并…...

Python基础(二十四、JSON和pyecharts)
文章目录 一、JSON1.JSON介绍2.JSON格式数据转化3.示例 二、pyecharts1.安装pyecharts包2.查看官方示例 三、开发示例 一、JSON 1.JSON介绍 JSON是一种轻量级的数据交互格式,采用完全独立于编程语言的文本格式来存储和表示数据(就是字符串)…...

Java 并发之《深入理解 JVM》关于 volatile 累加示例的思考
在周志明老师的 《深入理解 JVM》一书中关于 volatile 关键字线程安全性有一个示例代码(代码有些许改动,语义一样): public class MyTest3 {private static volatile int race 0;private static void increase() {race;}public …...
GPM合并资料整理-GEM部分
一、性能数据上报项 1. CPU模块 上报键值说明采集平台cpu当前进程cpu使用率平均值Android & iOStotcpu系统cpu总使用率平均值Android & iOScpu_temp_maxcpu最高温度Androidcpu_temp_avgcpu温度平均值Androidgpu_temp_avggpu温度平均值Androidgpu_temp_maxgpu最高温度…...
STM32使用1.69寸液晶显示模块使用缓冲区实现快速刷新全屏显示字符串功能
一个1.69寸SPI接口的液晶显示模块,有320*24076800个点,每个点有2个字节表示RGB的颜色,所以需要153.6K个字节的数据来刷新全屏,如果SPI口输出数据不是高速并且不紧密排列的话,刷新就会比较慢,有从下到下的肉…...
SpringBoot AOP
依赖引入 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>代码实现 以给公共字段注入值为例 公共字段与枚举类: private LocalDateTime createT…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...

MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...