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_destoryinclude/linux/device.h
copy_to_user() copy_from_user()include/linux/uaccess.h
VFS structure definitionsinclude/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);

动态创建设备文件

当收到ueventudev根据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)变量的位置,将文件位置指针向前/向后移动。

写一个伪字符设备驱动

  1. 动态申请设备号
  2. 创建cdev结构体变量和file_operiations结构体变量
  3. 使用fops初始化字符设备结构体变量
  4. 向内核VFS注册设备
  5. 实现file operiation的方法
  6. 初始化file operiation变量
  7. 创建设备文件 class_create() device_create()
  8. 驱动清理函数功能实现
#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;
}

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

Docker入门介绍

【一】从 dotCloud 到 Docker——低调奢华有内涵 1、追根溯源:dotCloud 时间倒回到两年前&#xff0c;有一个名不见经传的小公司&#xff0c;他的名字叫做:dotCloud。 dotCloud 公司主要提供的是基于 PaaS(Platform as a Service&#xff0c;平台及服务) 平台为开发者或开发商…

职场晋升101读书笔记

1.如果你把生活想象为一场游戏&#xff0c;把面临的每一个问题都当作一个需要破解的谜&#xff0c;每解开一个谜都能获得一个宝石。如果你这样想&#xff0c;这个过程&#xff0c;就有意思多了。 2.最后结束面谈时&#xff0c;别寒暄两句就撤了&#xff0c;记得做一个总结&…

Electron中调用dll

截止目前Electron的官方稳定版本已经更新到了28.1.1。我在创建Electron项目时用的28.0.0版本&#xff0c;后面在项目中有用到调用dll方法的需求&#xff0c;大致的实现就是将后端给的dll文件引入到项目中&#xff0c;安装ffi-napi依赖&#xff0c;然后进行使用。但是在Electron…

Vue3 + TS + Element-Plus —— 项目系统中封装表格+搜索表单 十分钟写五个UI不在是问题

前期回顾 纯前端 —— 200行JS代码、实现导出Excel、支持DIY样式&#xff0c;纵横合并-CSDN博客https://blog.csdn.net/m0_57904695/article/details/135537511?spm1001.2014.3001.5501 目录 一、&#x1f6e0;️ newTable.vue 封装Table 二、&#x1f6a9; newForm.vue …

pandas笔记:找出在一个dataframe但不在另一个中的index

1 问题描述 假设我们有两个dataframe&#xff08;这一段代码&#xff09;来自transbigdata 笔记&#xff1a;官方文档案例1&#xff08;出租车GPS数据处理&#xff09;-CSDN博客 data tbd.clean_outofshape(data, sz, col[Lng, Lat], accuracy500) data data2 tbd.clean_ta…

【Vue3】2-3 : 选项式API的编程风格与优势

本书目录&#xff1a;点击进入 一、选项式API - 三大优势 ▶ 只有一个参数&#xff0c;不会出现参数顺序的问题&#xff0c;随意调整配置的位置 传入的是一个对象&#xff0c;没有参数顺序问题 对比反面教材&#xff1a; ▶ 非常清晰&#xff0c;语法化特别强 ▶ 非常…

P1032 [NOIP2002 提高组] 字串变换

题目描述 已知有两个字串 A,B 及一组字串变换的规则&#xff08;至多 66 个规则&#xff09;&#xff0c;形如&#xff1a; 1A1​→B1​。A2​→B2​。 规则的含义为&#xff1a;在 A 中的子串 A1​ 可以变换为 B1​&#xff0c;A2​ 可以变换为 B2​⋯。 例如&#xff1a;…

大数据导出Excel

1、针对大数据导出到excel&#xff0c;动态生成datatable数据集 DataTable dt new DataTable(); dt.Columns.Add("Name", typeof(string)); dt.Columns.Add("Age", typeof(string)); for (int i 0; i &…

华为“纯血”鸿蒙加速进场 高校、企业瞄准生态开发新风口

近日&#xff0c;华为终端BG CEO、智能汽车解决方案BU董事长余承东在2024年新年信中提出&#xff0c;开启华为终端未来大发展的新十年。 他特别提到&#xff0c;未来要构建强大的鸿蒙生态&#xff0c;2024年是原生鸿蒙的关键一年&#xff0c;将加快推进各类鸿蒙原生应用的开发…

flask web学习之表单(一)

文章目录 一、使用Flask-WTF处理表单1.1 安装Flask-WTF库1.2 定义WTForms表单类常用的WTForm字段实例化字段类常用参数常用的WTForm验证器 1.3 输出HTML代码使用render_kw属性在调用字段时传入 1.4 在模板中渲染表单 在web程序中&#xff0c;表单是用户交互最常见的方式之一。用…

CSS3中transform2D变形详解

CSS3变形 在CSS3中&#xff0c;动画效果包括3个部分&#xff1a; 变形(transform)过渡(transition)动画(animation) 在实际开发中&#xff0c;有时需要实现元素的各种变形效果&#xff0c;如平移&#xff0c;缩放&#xff0c;旋转&#xff0c;倾斜等。 在CSS3中&#xff0c…

Zetta Decision API连接,提升电商平台营销系统效率

高效无代码开发连接电商与客服系统 在电商和客户服务系统的运营中&#xff0c;有效的连接和数据集成是至关重要的。Zetta Decision提供了一种无需传统API开发就能实现系统连接的解决方案&#xff0c;这对于希望快速整合多个平台和服务的电商企业尤其重要。通过Zetta Decision&…

基于自适应遗传算法的车间调度matlab仿真,可以任意调整工件数和机器数,输出甘特图

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 编码与初始化 4.2 适应度函数 4.3 遗传操作 4.4 自适应机制 4.5 终止条件 5.完整程序 1.程序功能描述 基于自适应遗传算法的车间调度matlab仿真,可以任意调整工件数和机器数,输出甘…

Linux基础配置

文章目录 说明1.安装 ifconfig 和 vim 命令2.设置root密码3.设置允许root用户密码登录4.网络配置5.设置时间同步6.关闭防火墙7.主机名与IP地址的映射 说明 配置基于ubuntu22版本,当前unbuntu18适用,后续安装持续更新 1.安装 ifconfig 和 vim 命令 sudo apt install net-tool…

[渗透测试学习] Crocodile - HackTheBox

文章目录 靶机:CrocodileTask1Task2Task3Task4Task5Task6Task7Task8Task9Task10靶机:Crocodile 考察的是FTP传输协议相关知识 Task1 问题:在扫描过程中,什么NMAP扫描开关使用默认脚本? -sCTask2 问题

web学习笔记(十四)

目录 1.数组 1.1数组的概念 1.2数组的创建 1.3数组的索引和获取 1.4数组的遍历、长度和检测数据类型 1.5数组的操作 1.6深拷贝和浅拷贝 2.数组常用的方法&#xff08;重点&#xff09;&#xff1a; 2.1常用方法汇总 2.2 补充&#xff1a; 1.数组 1.1数组的概念 数组是…

【STM32】HAL库的STOP低功耗模式UART串口唤醒BUG,第一个接收字节出错的问题(尚未解决,疑难杂症)

【STM32】HAL库的STOP低功耗模式UART串口唤醒BUG&#xff0c;第一个接收字节出错的问题&#xff08;尚未解决&#xff0c;疑难杂症&#xff09; 文章目录 BUG复现调试代码推测原因及改进方案尝试中断时钟供电外设唤醒方式校验码硬件问题 切换到STOP0模式尝试最终结论和猜想附录…

[SpringBoot]如何在一个普通类中获取一个Bean

最近在项目中出现了一个这种情况&#xff1a;我一顿操作猛如虎的写了好几个设计模式&#xff0c;然后在设计模式中的类中想将数据插入数据库&#xff0c;因此调用Mapper持久层&#xff0c;但是数据怎么都写不进去&#xff0c;在我一顿操作猛如虎的查找下&#xff0c;发现在普通…

在WindowsServer2012中部署war项目

目录 前言 一.jdk安装 二.Tomact安装 三.MySQL安装 ​编辑​编辑​编辑​编辑​编辑​编辑​编辑 四.开放端口号 MySQL开放端口号 Tomact开放端口号 ​编辑 五.项目部署 1.将war放置在tomact中 2.配置项目sql脚本 3.最终效果 前言 安装Java开发工具包&#xff08…

【libpcap】获取报文pcap的ns级别的时间戳

1.安装libpcap 首先&#xff0c;下载最新的 libpcap 源代码。你可以从 tcpdump.org 获取最新版本 1 解压下载的libpcap tar -zxvf libpcap-version.tar.gz 2 进入解压目录进行安装 cd libpcap-version ./configure make sudo make install2 解析报文时间戳 #include <pca…