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_operations
	cdev_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内存并清0
	globalmem_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.mfbz.cn/a/778039.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

确认下单:购物车页面点击 去结算 按钮发起两个请求trade(显示购物车的商品信息和计算商品的总金额)findUserAddressList

文章目录 1、确认下单&#xff1a;购物车页面点击去结算1.1、在OrderController类中创建 trade 方法1.2、在CartController类中创建 checkedCartInfos1.3、CartServiceImpl 实现 checkedCartInfos的业务功能1.4、在service-cart-client模块下定义远程openFeign接口1.5、在SpzxO…

Java - 程序员面试笔记记录 实现 - Part3

4.1 线程与进程 线程是程序执行的最小单元&#xff0c;一个进程可以拥有多个线程&#xff0c;各个线程之间共享程序的内存空间以及一些进程级资源&#xff0c;但拥有自己的栈空间。 4.3 Java 多线程 方法一&#xff1a;继承 Thread 类&#xff0c;重写 run 方法&#xff1b;…

qt QGridLayout 简单实验1

1.概要 2.实验 2.1 实验1 简单实验跨行 2.1.1 代码 #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~W…

Golang语法规范和风格指南(一)——简单指南

1. 前引 一个语言的规范的学习是重要的&#xff0c;直接关系到你的代码是否易于维护和理解&#xff0c;同时学习好对应的语言规范可以在前期学习阶段有效规避该语言语法和未知编程风格的冲突。 这里是 Google 提供的规范&#xff0c;有助于大家在开始学习阶段对 Golang 进行一…

如何魔改vnstat-docker项目使其支持每1分钟采样?

文章目录 一、概述二、官网参考1. 官网地址2. 查看打包过程3.打包命令 三、修改过的文件四、部署运行1. 编排文件2. 运行效果 一、概述 接前文 网络流量监控神器vnStat初探 我们已经了解了vnStat的作用、使用和docker部署。 同时也了解到官方版本支持的采样统计间隔最小为5分…

【Unity】unity学习扫盲知识点

1、建议检查下SystemInfo的引用。这个是什么 Unity的SystemInfo类提供了一种获取关于当前硬件和操作系统的信息的方法。这包括设备类型&#xff0c;操作系统&#xff0c;处理器&#xff0c;内存&#xff0c;显卡&#xff0c;支持的Unity特性等。使用SystemInfo类非常简单。它的…

Linux操作系统的引导过程

系统初始化进程与文件、systemd概述、单元类型、切换运行级别、查看系统默认默认运行、永久切换、常见的系统服务&#xff08;centos&#xff09;-CSDN博客 centos 7系统升级内核&#xff08;ELRepo仓库&#xff09;、小版本升级、自编译内核-CSDN博客 ss命令详细使用讲解文…

tongweb+ths6011测试websocket(by lqw)

本次使用的tongweb版本7049m4&#xff0c;测试包ws_example.war&#xff08;在tongweb安装目录的samples/websocket下&#xff09;&#xff0c;ths版本6011 首先在tongweb控制台部署一下ws_example.war,部署后测试是否能访问&#xff1a; 然後ths上的httpserver.conf的參考配…

游戏服务器搭建选VPS还是专用服务器?

游戏服务器搭建选VPS&#xff0c;VPS能够提供控制、性能和稳定性。它不仅仅是让游戏保持活力。它有助于减少延迟问题&#xff0c;增强您的游戏体验。 想象一下&#xff1a;你正沉浸在一场游戏中。 胜利在望。突然&#xff0c;屏幕卡住——服务器延迟。 很崩溃&#xff0c;对…

PageCache页缓存

一.PageCache基本结构 1.PageCache任务 PageCache负责使用系统调用向系统申请页的内存,给CentralCache分配大块儿的内存,以及合并前后页空闲的内存,整体也是一个单例,需要加锁. PageCache桶的下标按照页号进行映射,每个桶里span的页数即为下标大小. 2.基本结构 当每个线程的…

文件、文本阅读与重定向、路径与理解指令——linux指令学习(一)

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

Python 空间和时间高效的二项式系数(Space and time efficient Binomial Coefficient)

这里函数采用两个参数n和k&#xff0c;并返回二项式系数 C(n, k) 的值。 例子&#xff1a; 输入&#xff1a; n 4 和 k 2 输出&#xff1a; 6 解释&#xff1a; 4 C 2 等于 4!/(2!*2!) 6 输入&#xff1a; n 5 和 k 2 输出&#xff1a; 10 解释&#xff1a; 5 C …

moonlight+sunshine+ParsecVDisplay ipad8-windows 局域网串流

1.sunshine PC 安装 2.设置任意账户密码登录 3.setting 里 network启用UPNP IPV4IPV6 save apply 4.ParsecVDisplay虚拟显示器安装 5.ipad appstore download moonlight 6.以ipad 8 为例 2160*1620屏幕分辨率 7.ParsecVDisplay里面 custom设置2160*1620 240hz&#xff0c;…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

CSS中 实现四角边框效果

效果图 关键代码 border-radius:10rpx ;background: linear-gradient(#fff, #fff) left top,linear-gradient(#fff, #fff) left top,linear-gradient(#fff, #fff) right top,linear-gradient(#fff, #fff) right top,linear-gradient(#fff, #fff) left bottom,linear-gradient(…

CentOS7安装Mysql8.4.0

简介 本文介绍了Linux CentOS系统下Mysql8.4.0的下载和安装方法 环境 (rpm -q centos-release) centos-release-7-2.1511.el7.centos.2.10.x86_64 正文 一、去官网下载Mysql8.4.0 下载参考我另一篇mysql5.7.4的安装 CentOS7.9安装Mysql5.7-m14_centos下mysql5.7下载-CSDN博客…

flutter开发实战-Webview及dispose关闭背景音

flutter开发实战-Webview及dispose关闭背景音 当在使用webview的时候&#xff0c;dispose需要关闭网页的背景音或者音效。 一、webview的使用 在工程的pubspec.yaml中引入插件 webview_flutter: ^4.4.2webview_cookie_manager: ^2.0.6Webview的使用代码如下 初始化WebView…

AJAX-个人版-思路步骤整理版

前置知识&#xff1a;老式的web创建工程方法就是创建项目然后添加web工件&#xff0c;然后添加lib依赖如&#xff1a;tomcat,servlet&#xff0c;等。 传统请求 对于传统请求操作&#xff1a;整体流程也就是创建静态页面&#xff0c; <!DOCTYPE html> <html lang&q…

每日一题~ leetcode 402 (贪心+单调栈)

click me! 这个贪心的推导在leetcode上已经很明确了。 click me! 删除k个数&#xff0c;可以先考虑删除一个数。这也是一种常见的思路。&#xff08;如果进行同样的操作多次&#xff0c;可以先只 考虑一次操作如何实现&#xff0c;或者他的影响。完成这一次操作后&#xff0c;…

MySQL基础篇(二)

如何创建一个数据库&#xff1a; create database 数据库名; 使用数据库&#xff1a; use 数据库名&#xff1b; 如何查看都有哪些数据库&#xff1a; use databases;//后面需要加s 容易忘 如何查看都有哪些表&#xff1a; use tables;//后面需要加s 如何清屏&#xff…