Linux字符设备驱动

一、字符设备驱动结构

1. cdev结构体

在Linux内核中,使用cdev结构体来描述一个字符设备

struct cdev {struct kobject kobj;	//内嵌kobject对象struct module *owner;	//所属的模块const struct file_operations *ops;	//该设备的文件操作结构体struct list_head list;dev_t dev;	//设备号unsigned int count;
};

cdev相关的操作

cdev_init: 初始化cdev的函数,实际上就是将cdevfile_operation进行关联

void cdev_init(struct cdev *cdev, struct file_operations *fops)
{memset(cdev, 0, sizeof *cdev);INIT_LIST_HEAD(&cdev->list);kobject_init(&cdev->kobj, &ktype_cdev_default);cdev->ops = fops; /*  将传入的文件操作结构体指针赋值给 cdev 的 ops*/
}

cdev_alloc: 动态申请一个cdev

struct cdev *cdev_alloc(void)
{struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);if (p) {INIT_LIST_HEAD(&p->list);kobject_init(&p->kobj, &ktype_cdev_dynamic);}return p;
}

cdev_add/cdev_del: 向内核中添加/删除cdev,即对设备的注册和注销。通常发生在加载/卸载模块时

设备号
cdev结构体的dev_t定义了设备号,前12bit代表主设备号,后20bit代表次设备号。同一驱动可支持多个同类设备,因此同一类设备一般使用相同的主设备号,次设备号从0开始用来描述驱动的不同设备序号

MKDEV(int major, int minor)		//组成一个设备号
MAJOR(dev_t dev)	//获取主设备号
MINOR(dev_t dev)	//获取次设备号

在调用cdev_add函数注册设备之前,需要先申请设备号

//已知设备号时使用,直接申请
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//未知设备号时使用,向内核申请一个未被占用的设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);

在调用cdev_del函数注销设备前,需要释放设备号

void unregister_chrdev_region(dev_t from, unsigned count);

2. file_operation结构体

应用程序调用的open/read/write等函数,最终时调用的对应设备的file_operation结构体中的对应函数,所以驱动程序设计的主题内容就是实现file_operation结构体中的成员函数

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);
};

llseek(): 修改一个文件的当前读写位置
read()/write(): 向设备中读/写数据
unlocked_ioctl(): 提供设备相关控制命令的实现
mmap(): 将设备内存映射到进程的虚拟内存
open()/release(): 打开/关闭设备。

3. 组成

加载/卸载函数
字符设备驱动模块加载函数中,需要实现设备号的申请和cdev的注册

static int __init xxx_init(void)
{.../*  初始化 cdev */cdev_init(&xxx_dev.cdev, &xxx_fops); xxx_dev.cdev.owner = THIS_MODULE;/*  获取字符设备号 */if (xxx_major) {register_chrdev_region(xxx_dev_no, 1, DEV_NAME);} else {alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);}/*  注册设备 */ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); ...
}

卸载时需要释放设备号和注销设备

static void _ _exit xxx_exit(void)
{unregister_chrdev_region(xxx_dev_no, 1); /*  释放占用的设备号 */cdev_del(&xxx_dev.cdev); /*  注销设备 */...
}

file_operations结构体成员函数
大多数字符设备会实现read()、write()、ioctl()函数。由于用户空间不能直接访问内核空间的内存,所以使用copy_from_user()copy_to_user()进行交互

/*  读设备 */
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t*f_pos)
{...copy_to_user(buf, ..., ...);...
}
/*  写设备 */
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{...copy_from_user(..., buf, ...);...
}
/* ioctl 函数 */
long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{...switch (cmd) {case XXX_CMD1:...break;case XXX_CMD2:...break;default:/*  不能支持的命令 */return - ENOTTY;}return 0;
}

驱动程序结构

在这里插入图片描述

二、实例

实例是宋宝华的Linux设备驱动开发详解里的例子。实现了一个虚拟的globalmem设备的驱动程序,这个设备会分配一块内存空间,主要是实现对这块内存的相关驱动函数
代码实现

/** a simple char device driver: globalmem without mutex** Copyright (C) 2014 Barry Song  (baohua@kernel.org)** Licensed under GPLv2 or later.*/#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>#define GLOBALMEM_SIZE	0x1000		//要分配的内存空间大小
#define MEM_CLEAR 0x1		//清内存cmd
#define GLOBALMEM_MAJOR 230		//主设备号static int globalmem_major = GLOBALMEM_MAJOR;	//定义主设备号
module_param(globalmem_major, int, S_IRUGO);	//可以接受传参来定义主设备号//定义globalmem设备的类型
struct globalmem_dev {struct cdev cdev;	//所有字符设备都必须包含的结构体unsigned char mem[GLOBALMEM_SIZE];	//不同设备可以自定义不同的变量
};//定义一个globalmem设备
struct globalmem_dev *globalmem_devp;//globalmem设备的open函数,filep代表打开动作的状态,inode代表文件本身的属性
static int globalmem_open(struct inode *inode, struct file *filp)
{//将globalmem设备结构体传给对应的file,以便后续其他函数使用globalmem_devp结构体filp->private_data = globalmem_devp;	return 0;
}//当所有调用globalmem的进程都关闭时,才会调用release
static int globalmem_release(struct inode *inode, struct file *filp)
{return 0;
}//可以接收MEM_CLEAR 命令,里面实现相关对应命令的操作
static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct globalmem_dev *dev = filp->private_data;	//通过传入的file来获取globalmem_dev switch (cmd) {case MEM_CLEAR:memset(dev->mem, 0, GLOBALMEM_SIZE);	//对应命令的操作printk(KERN_INFO "globalmem is set to zero\n");break;default:return -EINVAL;}return 0;
}//当应用程序调用read函数时,最终会调用此函数
static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size, loff_t * ppos)
{unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct globalmem_dev *dev = filp->private_data; //通过传入的file来获取globalmem_dev if (p >= GLOBALMEM_SIZE)return 0;if (count > GLOBALMEM_SIZE - p)count = GLOBALMEM_SIZE - p;if (copy_to_user(buf, dev->mem + p, count)) {	//将数据从内核空间copy到用户空间ret = -EFAULT;} else {*ppos += count;ret = count;printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);}return ret;
}//当应用程序调用write函数时,最终会调用此函数
static ssize_t globalmem_write(struct file *filp, const char __user * buf,size_t size, loff_t * ppos)
{unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct globalmem_dev *dev = filp->private_data;if (p >= GLOBALMEM_SIZE)return 0;if (count > GLOBALMEM_SIZE - p)count = GLOBALMEM_SIZE - p;if (copy_from_user(dev->mem + p, buf, count))ret = -EFAULT;else {*ppos += count;ret = count;printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);}return ret;
}static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{loff_t ret = 0;switch (orig) {case 0:if (offset < 0) {ret = -EINVAL;break;}if ((unsigned int)offset > GLOBALMEM_SIZE) {ret = -EINVAL;break;}filp->f_pos = (unsigned int)offset;ret = filp->f_pos;break;case 1:if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {ret = -EINVAL;break;}if ((filp->f_pos + offset) < 0) {ret = -EINVAL;break;}filp->f_pos += offset;ret = filp->f_pos;break;default:ret = -EINVAL;break;}return ret;
}//定义file_operation结构体并填充成员函数
static const struct file_operations globalmem_fops = {.owner = THIS_MODULE,.llseek = globalmem_llseek,.read = globalmem_read,.write = globalmem_write,.unlocked_ioctl = globalmem_ioctl,.open = globalmem_open,.release = globalmem_release,
};//初始化并向内核注册globalmem_dev设备
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{//创建一个设备号int err, devno = MKDEV(globalmem_major, index);//关联cdev和file_operationscdev_init(&dev->cdev, &globalmem_fops);dev->cdev.owner = THIS_MODULE;//注册cdev到内核err = cdev_add(&dev->cdev, devno, 1);if (err)printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}//模块初始化函数
static int __init globalmem_init(void)
{int ret;//创建一个次设备号为0的设备号dev_t devno = MKDEV(globalmem_major, 0);//向内核注册设备号if (globalmem_major)ret = register_chrdev_region(devno, 1, "globalmem");else {ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");globalmem_major = MAJOR(devno);}if (ret < 0)return ret;//申请一块globalmem_devp内存并清0globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);if (!globalmem_devp) {ret = -ENOMEM;goto fail_malloc;}//初始化并向内核注册globalmem_dev设备globalmem_setup_cdev(globalmem_devp, 0);return 0;fail_malloc:unregister_chrdev_region(devno, 1);return ret;
}
//模块加载时会调用此函数
module_init(globalmem_init);//模块卸载函数
static void __exit globalmem_exit(void)
{cdev_del(&globalmem_devp->cdev);	//从内核中注销设备kfree(globalmem_devp);	//释放申请的设备内存unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);	//释放设备号
}
module_exit(globalmem_exit);MODULE_AUTHOR("Barry Song <baohua@kernel.org>");
MODULE_LICENSE("GPL v2");

验证

### 加载驱动 ###
sudo insmod globalmem.ko	#加载驱动
lnsmod	#查看是否已经被加载
cat /proc/devices	#可以看到多出的主设备号为230名字为globalmem的字符设备驱动
mknod /dev/globalmem c 230 0	#创建/dev/globalmem设备节点### 读写验证 ###
echo "hello world" > /dev/globalmem	#将会调用write函数将hello wrold写进去
cat /dev/globalmem	#将会调用read函数将字符串读出来

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

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

相关文章

SpringBoot新手快速入门系列教程六:基于MyBatis的一个简单Mysql读写例子

MyBatis和JPA是两种不同的Java持久层框架&#xff0c;各有其优缺点。以下是它们的比较&#xff1a; MyBatis 优点 灵活性高&#xff1a;MyBatis允许手动编写SQL查询&#xff0c;可以完全控制SQL执行过程&#xff0c;非常适合复杂的查询和需要高度优化的查询。SQL分离&#x…

用proteus软件如何设计一个基于8086微处理器的简易温度计?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

#数据结构 顺序表

线性表 顺序表 每种结构都有它存在意义 线性表的顺序存储实现指的是用一组连续的存储单元存储线性表的数据元素。 概念 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性表&#xff0c;一般情况下采用数组存储。在数组上完成数据的增查改删。 逻辑结构&#…

IDEA配Git

目录 前言 1.创建Git仓库&#xff0c;获得可提交渠道 2.选择本地提交的项目名 3.配置远程仓库的地址 4.新增远程仓库地址 5.开始进行commit操作 6.push由于邮箱问题被拒绝的解决方法&#xff1a; 后记 前言 以下操作都是基于你已经下载了Git的前提下进行的&#xff0c…

CSRF靶场通关合集

目录 前言 CSRF漏洞总结 1.PiKachu靶场 1.1CSRF(get) 1.2 CSRF(post)请求 1.3 CSRF Token 2.DVWA靶场 难度低 难度中 难度高 前言 最近系统的将从web渗透到内网渗透的知识点做一个回顾,同时结合一些实战的案例来演示,下面是对刚开始学习时对靶场的一个总结. CSRF漏洞…

OPENCV(图像入门笔记)

使用OpenCV读取图像 使用cv.imread()函数读取图像。 第一个参数为图像名称 第二个参数是一个标志&#xff0c;它指定了读取图像的方式。分别有三种 cv.IMREAD_COLOR&#xff1a; 加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。 cv.IMREAD_GRAYSCALE&#xff1a;以…

【Nvidia+AI相机】涂布视觉检测方案专注提高锂电池质量把控标准

锂电池单元的质量在多个生产制造领域都至关重要&#xff0c;特别是在新能源汽车、高端消费电子等行业。这些领域的产品高度依赖锂电池提供持续、稳定的能量供应。优质的锂电池单元不仅能提升产品的性能和用户体验&#xff0c;还能确保使用安全。因此&#xff0c;保证锂电池单元…

go语言Gin框架的学习路线(六)

gin的路由器 Gin 是一个用 Go (Golang) 编写的 Web 框架&#xff0c;以其高性能和快速路由能力而闻名。在 Gin 中&#xff0c;路由器是框架的核心组件之一&#xff0c;负责处理 HTTP 请求并将其映射到相应的处理函数上。 以下是 Gin 路由器的一些关键特性和工作原理的简要解释…

昇思25天学习打卡营第19天|LSTM+CRF序列标注

概述 序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。 条件随机场&#xff08…

水箱高低水位浮球液位开关

水箱高低水位浮球液位开关概述 水箱高低水位浮球液位开关是一种用于监测和控制水箱中液位的自动化设备&#xff0c;它能够在水箱液位达到预设的高低限制时&#xff0c;输出开关信号&#xff0c;以控制水泵或电磁阀的开闭&#xff0c;从而维持水箱液位在一个安全的范围内。这类设…

【排序算法】插入排序(希尔排序)

一.直接插入排序 1.基本思想 直接插入排序是一种简单的插入排序法&#xff0c;其核心思想是对一个已经有序的序列插入一个数据&#xff0c;该数据依次比较有序序列中的值&#xff0c;直到插入到合适的位置。在我们玩扑克牌整理牌序的时候&#xff0c;用到的就是直接插入排序的…

Vue3.js“非原始值”响应式实现基本原理笔记(二)

如果您觉得这篇文章有帮助的话&#xff01;给个点赞和评论支持下吧&#xff0c;感谢~ 作者&#xff1a;前端小王hs 阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主 此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来 书籍&a…

28行代码完成深度学习模型——线性模型 01

在这里插入代码片## 线性模型 机器学习中的线性模型是一种预测模型&#xff0c;它基于线性关系来预测输出值。这种模型假设输入特征&#xff08;自变量&#xff09;和输出&#xff08;因变量&#xff09;之间存在线性关系。线性模型通常具有以下形式&#xff1a; y x*w b 其…

【TB作品】数码管独立按键密码锁,ATMEGA16单片机,Proteus仿真 atmega16数码管独立按键密码锁

文章目录 基于ATmega16的数码管独立按键密码锁设计实验报告实验背景硬件介绍主要元器件电路连接 设计原理硬件设计软件设计 程序原理延时函数独立按键检测密码显示主函数 资源代码 基于ATmega16的数码管独立按键密码锁设计实验报告 实验背景 本实验旨在设计并实现一个基于ATm…

数据库系统原理练习 | 作业1-第1章绪论(附答案)

整理自博主本科《数据库系统原理》专业课完成的课后作业&#xff0c;以便各位学习数据库系统概论的小伙伴们参考、学习。 *文中若存在书写不合理的地方&#xff0c;欢迎各位斧正。 专业课本&#xff1a; 目录 一、选择题 二&#xff1a;简答题 三&#xff1a;综合题 一、选择…

DAY21-力扣刷题

1.买卖股票的最佳时机 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int[] prices) {int minpriceInteger.MAX_VALUE;int maxprofit0;for(int i0;i<prices.length;i){if(prices[i]<minprice){minpriceprices[…

昇思MindSpore学习笔记5-01生成式--LSTM+CRF序列标注

摘要&#xff1a; 记录昇思MindSpore AI框架使用LSTMCRF模型分词标注的步骤和方法。包括环境准备、score计算、Normalizer计算、Viterbi算法、CRF组合,以及改进的双向LSTMCRF模型。 一、概念 1.序列标注 标注标签输入序列中的每个Token 用于抽取文本信息 分词(Word Segment…

InetAddress.getLocalHost().getHostAddress()阻塞导致整个微服务崩溃

InetAddress.getLocalHost().getHostAddress()阻塞导致整个微服务崩溃 import java.net.InetAddress;public class GetHostIp {public static void main(String[] args) {try {long start System.currentTimeMillis();String ipAddress InetAddress.getLocalHost().getHostA…

【计算机网络】物理层(作业)

1、若信道在无噪声情况下的极限数据传输速率不小于信噪比为30dB 条件下的极限数据传输速率&#xff0c;则信号状态数至少是&#xff08;D&#xff09;。 A. 4B. 16C. 8D. 32 解析&#xff1a;可用奈奎斯特采样定理计算无噪声情况下的极限数据传输速率&#xff0c;用香农第二定…

Docker 容器网络及其配置说明

Docker 容器网络及其配置说明 docker容器网络docker的4种网络模式bridge 模式container模式host 模式none 模式应用场景 docker 容器网络配置Linux 内核实现名称空间的创建创建 Network Namespace操作 Network Namespace 转移设备veth pair创建 veth pair实现 Network Namespac…