驱动开发——字符设备

news2024/9/25 7:24:49

字符设备

Linux 将系统设备分为:字符设备、块设备、网络设备。

Linux系统框架

工作原理

	字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,
按照字节流进行读写操作的设备,读写数据是分先后顺序的。
	在Linux的世界里面一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件
的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。

1.在Linux文件系统中,每个文件都用一个struct inode结构体来描述,
  这个结构体里面记录了	这个文件的所有信息,例如:文件类型,访问权限等。

struct inode信息

2.在Linux操作系统中,每个驱动程序在应用层的/dev目录下都会有一个
  设备文件和它对应,并且该文件会有对应的主设备号和次设备号。
3.在Linux操作系统中,每个驱动程序都要分配一个主设备号,字符设备的设备号
  保存在struct cdev结构体中。
  
   struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;//接口函数集合
    struct list_head list;//内核链表
    dev_t dev;    //设备号
    unsigned int count;//次设备号个数
   };
4.在Linux操作系统中,每打开一次文件,Linux操作系统在VFS层都会分配一个struct file
  结构体来描述打开的这个文件。该结构体用于维护文件打开权限、文件指针偏移值、私有
  内存地址等信息。(strcut file有两个非常重要的字段:文件描述符和缓冲区)

在这里插入图片描述

字符驱动相关函数

注册

	内核共提供了三个函数来注册一组字符设备编号,这三个函数分别是 	
register_chrdev_region()、alloc_chrdev_region()和 register_chrdev()。

注意事项

1.使用register_chrdev注册字符设备,其内部申请struct cdev 结构,
  并调用cdev_add函数添加设备。
2.使用register_chrdev_region/alloc_chrdev_region注册字符设备,需要在外部事先
  定义struct cdev 结构,然后使用函数cdev_init初始化它,最后还需在外部调用cdev_add
  函数添加设备。
//比较老的内核注册的形式 早期的驱动
static inline int register_chrdev(unsigned int major, const char *name,
          const struct file_operations *fops)
功能:
  注册或者分配设备号,并注册fops到cdev结构体,
  如果major>0,功能为注册该主设备号,
  如果major=0,功能为动态分配主设备号。
参数:
  @major : 主设备号
  @name : 设备名称,执行 cat /proc/devices显示的名称
  @fops  : 文件系统的接口指针
返回值
  如果major>0   成功返回0,失败返回负的错误码
  如果major=0  成功返回主设备号,失败返回负的错误码
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:
  注册一个范围()的设备号
参数:
  @from 设备号
  @count 注册的设备个数
  @name 设备的名字
返回值:
  成功返回0,失败返回错误码(负数)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
功能:
  注册一个主设备号由内核动态分配,次设备号为baseminor~baseminor+count的设备驱动
参数:
  @dev: 用来获取设备号
  @baseminor:次设备号起始值
  @count: 次设备号个数
  @name: 设备名称     
返回值:
  成功返回0,失败返回错误码(负数)     
//注销
static inline void unregister_chrdev(unsigned int major, const char *name)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:
  初始化cdev结构体
参数:
  @cdev cdev结构体地址
  @fops 操作字符设备的函数接口地址
返回值:
  无
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:
  添加一个字符设备到操作系统
参数:
  @p cdev结构体地址
  @dev 设备号
  @count 次设备号个数
返回值:
  成功返回0,失败返回错误码(负数)
void cdev_del(struct cdev *p)
功能:
  从系统中删除一个字符设备
参数:
  @p cdev结构体地址
返回值:
  无
struct file_operations { 
 
    struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES 
 
 
    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 (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL 
 
    unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入 
 
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令 
 
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl 
 
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替 
 
 
    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 *, struct dentry *, int datasync); //刷新待处理的数据 
 
    int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据 
 
    int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化 
 
    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 **); 
 
};

实例

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>

/* 1. 确定主设备号 */
static int major;

static int hello_open(struct inode *inode, struct file *file)
{
	printk("hello_open\n");
	return 0;
}

static int hello2_open(struct inode *inode, struct file *file)
{
	printk("hello2_open\n");
	return 0;
}


/* 2. 构造file_operations */
static struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.open  = hello_open,
};

static struct file_operations hello2_fops = {
	.owner = THIS_MODULE,
	.open  = hello2_open,
};


#define HELLO_CNT   2

static struct cdev hello_cdev;
static struct cdev hello2_cdev;
static struct class *cls;

static int hello_init(void)
{
	dev_t devid;
	
	/* 3. 告诉内核 */
#if 0
	major = register_chrdev(0, "hello", &hello_fops); /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */
#else
	if (major) {
		// 事先知道可用的主设备号,和起始次设备号
		devid = MKDEV(major, 0);
		register_chrdev_region(devid, HELLO_CNT, "hello");  /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
	} else {
		// 事先不知道可用的主设备号,由内核动态分配
		alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
		major = MAJOR(devid);                     
	}
	
	cdev_init(&hello_cdev, &hello_fops);
	cdev_add(&hello_cdev, devid, HELLO_CNT);

	devid = MKDEV(major, 2);
	register_chrdev_region(devid, 1, "hello2");
	cdev_init(&hello2_cdev, &hello2_fops);
	cdev_add(&hello2_cdev, devid, 1);
	
#endif

	cls = class_create(THIS_MODULE, "hello");
	// 使用register_chrdev注册,下面的四个设备节点都将对应该设备驱动,都能调用hello_open
	// 使用 register_chrdev_region/alloc_chrdev_region (60/63)注册,设备节点/dev/hello0、/dev/hello1对应hello_fops设备驱动,调用hello_open打开
	// 使用 register_chrdev_region/alloc_chrdev_region (71)注册,设备节点/dev/hello2对应hello2_fops设备驱动,调用hello2_open打开
	// /dev/hello3节点未注册到设备驱动,无法打开设备。
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
	class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
	class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
	class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
	
	
	return 0;
}

static void hello_exit(void)
{
	class_device_destroy(cls, MKDEV(major, 0));
	class_device_destroy(cls, MKDEV(major, 1));
	class_device_destroy(cls, MKDEV(major, 2));
	class_device_destroy(cls, MKDEV(major, 3));
	class_destroy(cls);

	cdev_del(&hello_cdev);
	unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);

	cdev_del(&hello2_cdev);
	unregister_chrdev_region(MKDEV(major, 2), 1);
}

module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");



在这里插入图片描述


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

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

相关文章

第5章 性能分析方法

有时看到修改后程序的运行时间发生变化时&#xff0c;却不清楚具体原因是什么。单独的时间信息有时无法给出问题发生的根本原因。 程序运行时硬件和软件都可以采集性能数据&#xff0c;硬件是指运行程序的CPU&#xff0c;软件是指操作系统和所有可用于分析的工具。通常软件栈提…

设计模式篇---抽象工厂(包含优化)

文章目录 概念结构实例优化 概念 抽象工厂&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 工厂方法是有一个类型的产品&#xff0c;也就是只有一个产品的抽象类或接口&#xff0c;而抽象工厂相对于工厂方法来说&#xff0c;是有…

qsort函数详解

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解qsort函数&#xff0c;如果你觉得我写的不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 文章目录 一. qsort函数参数详解1.数组首元素地址base2.数组的元素个数num和元素所占内存空间大小w…

Xxl-job安装部署以及SpringBoot集成Xxl-job使用

1、安装Xxl-job&#xff1a; 可以使用docker拉取镜像部署和源码编译两种方式&#xff0c;这里选择源码编译安装。 代码拉取地址&#xff1a; https://github.com/xuxueli/xxl-job/tree/2.1.2 官方开发文档&#xff1a; https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%…

uni-app的Vue.js实现微信小程序的紧急事件登记页面功能

主要功能实现 完成发生时间选择功能&#xff0c;用户可以通过日期选择器选择事件发生的时间。实现事件类型选择功能&#xff0c;用户可以通过下拉选择框选择事件的类型。添加子养殖场编号输入框&#xff0c;用户可以输入与事件相关的子养殖场编号。完成事件描述输入功能&#…

C++笔记之条件变量(Condition Variable)与cv.wait 和 cv.wait_for的使用

C笔记之条件变量&#xff08;Condition Variable&#xff09;与cv.wait 和 cv.wait_for的使用 参考博客&#xff1a;C笔记之各种sleep方法总结 code review! 文章目录 C笔记之条件变量&#xff08;Condition Variable&#xff09;与cv.wait 和 cv.wait_for的使用1.条件变量&…

msf和cs联动

cs设置外部监听器 在vps上执行 vim /etc/ssh/sshd_config AllowTcpForwarding yes GatewayPorts yes TCPKeepAlive yes PasswordAuthentication yes systemctl restart sshd.service 在kali上执行&#xff0c;进行端口转发 ssh -C -f -N -g -R 0.0.0.0:1234:192.168.1.30:…

STM32开关输入控制220V灯泡亮灭源代码(附带PROTEUSd电路图)

//main.c文件 /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program body************************************************************************…

525. 连续数组

525. 连续数组 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 525. 连续数组 https://leetcode.cn/problems/contiguous-array/description/ 完成情况&#xff1a; 解题思路&#xff1a; 参考代码&#xff1a; …

奇舞周刊第503期:图解串一串 webpack 的历史和核心功能

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 图解串一串 webpack 的历史和核心功能 提到打包工具&#xff0c;可能你会首先想到 webpack。那没有 webpack 之前&#xff0c;都是怎么打包的呢&#xff1f;webpack 都有哪些功能&…

喜讯?宁德时代首个零碳工厂成功建立,碳中和“任重道远”

8月19日消息&#xff0c;据宁德时代消息&#xff0c;广东瑞庆时代新能源科技有限公司宣布获得国际认可机构SGS颁发的PAS2060碳中和认证证书&#xff0c;从而正式成为一家零碳工厂。这标志着宁德时代首个储能电池为主的零碳工厂成功建立&#xff0c;也是公司继四川时代宜宾工厂之…

编程练习(3)

一.选择题 第一题&#xff1a; 函数传参的两个变量都是传的地址&#xff0c;而数组名c本身就是地址&#xff0c;int型变量b需要使用&符号&#xff0c;因此答案为A 第二题&#xff1a; 本题考察const修饰指针变量&#xff0c;答案为A,B,C,D 第三题&#xff1a; 注意int 型变…

速通蓝桥杯嵌入式省一教程:(五)用按键和屏幕实现嵌入式交互系统

一个完整的嵌入式系统&#xff0c;包括任务执行部分和人机交互部分。在前四节中&#xff0c;我们已经讲解了LED、LCD和按键&#xff0c;用这三者就能够实现一个人机交互系统&#xff0c;也即搭建整个嵌入式系统的框架。在后续&#xff0c;只要将各个功能加入到这个交互系统中&a…

赏味不足:详细来聊下轻资产运作,我从不做重资产

来源&#xff1a;BV1F84y1g7u3 好 大家好&#xff0c;是这样子的 对吧&#xff0c;因为最近聊了也很多&#xff0c;然后我在过程当中也发现&#xff0c;很多人对于不管是创业还是做副业&#xff0c;还是说找一条多种路&#xff0c;就是说可能不是那么清晰&#xff0c;然后我在这…

使用Dockerfile部署java项目

1、移动java包到创建的目录下 2、编写Dockerfile文件 在同一目录下使用如下命令创建文件 touch Dockerfile 文件内容如下&#xff1a; #依赖的父镜像 FROM java:8 #作者 MAINTAINER maxurui #jar包添加到镜像中 ADD springboot3-0.0.1-SNAPSHOT.jar springboot3-0.0.1-SNAPSHO…

射频同轴线阻抗

射频同轴线阻抗 射频同轴线的阻抗与线的绝缘介质的介电常数有关&#xff0c;与线的屏蔽层半径与内部导线半径的比值有关&#xff1a; R 0 1 2 π μ ′ ϵ ′ ln ⁡ ( r 2 r 1 ) \begin{align} R_0\frac{1}{2\pi}\sqrt{\frac{\mu^{}}{\epsilon^{}}}\ln(\frac{r_2}{r_1}) \en…

图数据库_Neo4j中文版_Centos7.9安装Neo4j社区版3.5.9_基于jdk1.8---Neo4j图数据库工作笔记0012

由于我们在国内使用啊,具体还是要用中文版滴,找了好久这个neo4j,原来还是有中文版的, https://we-yun.com/doc/neo4j-chs/ 中文版下载地址在这里: 所有版本都在这里了,需要哪个自己去下载就可以了,要注意下载以后,参考: https://we-yun.com/blog/prod-56.html 在这个位置下载…

Linux系统--进程间通信

文章目录 什么是进程间通信匿名管道命名管道 system V共享内存 system V消息队列 信号量 一、什么是进程间通信 首先由于进程运行是具有独立性的&#xff0c;具有独立的页表&#xff0c;PCB和虚拟地址空间等&#xff0c;父子进程间数据互补干扰。这就让进程间通信难度加大。由…

C语言:字符函数和字符串函数

往期文章 C语言&#xff1a;初识C语言C语言&#xff1a;分支语句和循环语句C语言&#xff1a;函数C语言&#xff1a;数组C语言&#xff1a;操作符详解C语言&#xff1a;指针详解C语言&#xff1a;结构体C语言&#xff1a;数据的存储 目录 往期文章前言1. 函数介绍1.1 strlen1.…