linux驱动学习(五)之字符设备

需要板子一起学习的可以这里购买(含资料):点击跳转

一、 linux设备驱动分类

1、字符设备---char

应用程序与驱动程序在进行数据传输时,数据以"字节"为单位。

特点:

[1] 按照顺序进行数据传输
[2] 数据传输是实时的,并没有缓存
[3] 字符设备是没有文件系统的

1)驱动程序的作用:是应用程序访问底层硬件设备的一座桥梁

2)应用程序如何去访问驱动程序:系统调用

   应用程序:系统IO(open、read、write、ioctl、close)

  驱动程序: 文件操作集---> struct file_operations 

3)驱动程序如何去访问底层硬件 ---> MMU

   驱动程序中的地址为虚拟地址 ----> MMU(io内存映射) ---->底层硬件的物理地址
   虚拟地址----->是通过在驱动程序中,来申请的虚拟地址

4)什么设备叫作字符设备呢?

    大多数的设备都是字符设备,比如:led, lcd, beep, 按键, SPI等

2、块设备---block

    应用程序与驱动程序在进行数据传输时,数据以"块"为单位,1block = 1024kb。

特点:

[1]有缓存

[2]有文件系统,比如:NTFS ,ext4

大容量设备都是块设备:U盘,SD卡,EMMC--电子硬盘

[root@GEC6818 /mnt]#cat /proc/partitions 
major minor  #blocks  name179        0    7634944 mmcblk0  ---->电子硬盘179        1      65536 mmcblk0p1 -----> 电子硬盘第一个分区179        2     772096 mmcblk0p2179        3     438272 mmcblk0p3179        4          1 mmcblk0p4179        5       8192 mmcblk0p5179        6      22528 mmcblk0p6179        7    6324224 mmcblk0p7//u盘
[root@GEC6818 /dev]#ls -lh sda*
brw-rw-rw-    1 root     root        8,   0 Jan  1 00:30 sda ---> 占1G
brw-rw-rw-    1 root     root        8,   1 Jan  1 00:30 sda1 ----> 31G

应用程序如何访问块设备:

第一步:挂载U盘到根文件系统上

[root@GEC6818 /]#mount -t vfat /dev/sda1 /udata

第二步:像访问普通文件一样来访问块设备的内容
   标准的IO函数

3、网络设备----socket

网卡类的设备:有线网卡、无线网卡 ...,另外,网络设备是没有设备文件的
     
应用程序:
    socket套接字:根据ip地址 + 端口号

二、字符设备驱动设计流程

以led灯为例

1、定义并初始一个字符设备(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;
};

比如:struct cdev led_cdev;

2、根据应用程序的API来定义并且初始化字符设备的文件操作集(struct file_operations).

3、给字符设备申请一个设备号 --> 主设备号<<20 + 次设备号.

4、初始化字符设备.

5、把当前的字符设备加入到内核.

6、创建class.

7、创建设备(device)。其中,device属于class,应用程序可以通过设备文件来访问驱动程序.

8、申请物理内存区,申请SFR(Special Function Register,特殊功能寄存器)地址区,比如:GPIOEOUT,GPIOEOUTENB

9、内存的动态映射,得到物理地址的虚拟地址.

10、在驱动程序中,访问虚拟地址.

具体实现如下:

1)定义一个字符设备(struct cdev)

#include <linux/cdev.h>
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};关于struct cdev结构体的说明:
1)struct kobject kobj; ---->内核管理驱动时,使用的一个object对象,内核自己使用,跟驱动程序设计者无关
2)struct module *owner; ----> 模块的拥有者,当前驱动设计模块是属于谁的,固定写法:THIS_MODULE
3)const struct file_operations *ops; ---> cdev的文件操作集一般由用户或者设计者来实现static struct file_operations gec6818_led_ops;
4)struct list_head list; --->管理字符设备的链表,由内核本身去负责
5)dev_t dev; ---->设备号
6)unsigned int count; ---->字符设备的次设备号的数目

在linux内核中,使用cdev来描述字符设备,每个字符设备都有一个自己的cdev,比如:

static struct cdev gec6818_led;

2) 定义一个字符设备的文件操作集(struct file_operations)

#include <linux/fs.h>
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 (*readdir) (struct file *, void *, filldir_t);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 gec6818_led_release(struct inode *node, struct file *file)
{printk("gec6818_led_release\n");return 0;
}
static struct file_operations gec6818_led_ops = {.owner = THIS_MODULE,.open = gec6818_led_open,.write = gec6818_led_write,.release = gec6818_led_release,//当应用程序调用close时,执行驱动程序中的接口函数
};

3) 给设备申请一个设备号---dev_t

   1、什么是设备号
     每个设备文件(字符设备or块设备)都有一个设备号,相当于设备文件的ID.
     设备号由主设备和次设备号组成.  

dev_t dev; ---> 字符设备的设备号
/*
typedef __kernel_dev_t		dev_t;
typedef __u32 __kernel_dev_t; ---->无符号整型数字
*/

   2、设备号的运算函数

#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi)) ----> 设备号
dev = MKDEV(ma,mi);  //根据主设备号与次设备号得到设备号

    3、得到主设备号与次设备号

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS)) ----> 主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK)) ----> 次设备号
unsigned int major = MAJOR(dev); //根据设备号得到主设备号
unsigned int minor = MINOR(dev); //根据设备号得到次设备号

     4、把得到的设备号注册到内核中

    一) 静态注册:由用户先确定一个设备号,通过注册函数去申请

int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:dev_t from ----> 要申请的设备号unsigned count ---> 次设备号的数目const char *name ----> 设备名字 --->字符串
返回值:成功:0失败: <0
比如:register_chrdev_region(108,4,"ttyGS");

  二) 动态注册:由内核动态分配一个设备号给现有设备

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数说明:dev_t *dev --->做为输出参数,通过alloc_chrdev_region得到的设备号给devunsigned baseminor -->第一个次设备号unsigned count -->次设备号数目const char *name ----> 设备名字 --->字符串
返回值:成功:0失败: <0

   5、设备号的作用
主设备号:描述一个硬件是属于哪一类的设备,比如:uart,lcd,摄像头

次设备号:描述某种类型的设备中具体的哪一个设备,比如:uart0,uart1

crw-rw----    1 root     root      247,   0 Jan  1  1970 ttyGS0
crw-rw----    1 root     root      247,   1 Jan  1  1970 ttyGS1
crw-rw----    1 root     root      247,   2 Jan  1  1970 ttyGS2
crw-rw----    1 root     root      247,   3 Jan  1  1970 ttyGS3247 ---->主设备号 ----->表示串口
0   ----->次设备号 -----> 串口1
ttyGS -----> 表示该硬件的设备文件,如果该类型的设备有多个,由从0开始,由系统自动加1

   6、释放设备号给系统

void unregister_chrdev_region(dev_t from, unsigned count)
参数说明:dev_t from ----> 要申请的设备号unsigned count -->次设备号数目

4) 初始化字符设备

#include <linux/cdev.h>
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数说明:struct cdev *cdev  -----> 字符设备的结构const struct file_operations *fops ----> 字符设备对应的文件操作集

5) 把设备加入到内核中

#include <linux/cdev.h>
//把字符设备增加到系统
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数说明:struct cdev *cdev  -----> 字符设备的结构dev_t dev -----> 字符设备的设备号unsigned count ----> 次设备号数目
失败:返回一个负错误码//从系统中,把字符设备删除
void cdev_del(struct cdev *p)

6) 创建class,一个设备会有一个class

[root@GEC6818 /sys/class]#ls ---->查看设备的class
android_usb   graphics      leds          pvrusb2       spi_master
arvo          hwmon         lirc          pyra          spidev
axppower      i2c-adapter   mdio_bus      rc            switch
backlight     i2c-dev       mem           regulator     timed_output//创建class
#include <linux/device.h>
struct class *class_create(struct module *owner, const char *name)
参数说明:struct module *owner ----> 模块的拥有者const char *name -----> class的名字 ----> /sys/class--->查看返回值成功:&struct class pointer  ----class的地址失败:NULL//销毁class
void class_destroy(struct class *cls)

7) 创建device

//创建一个device
#include <linux/device.h>
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
参数说明:struct class *class ----> 说明device是属于哪 一个class,class_create返回值struct device *parent ----> 该设备的父亲,一般设置为NULLdev_t devt -----> 增加到系统中的字符设备的设备号void *drvdata ----> 增加一个device时,返回的数值,如果不需要返回,则使用NULLconst char *fmt ----> 该设备的名字返回值:成功:struct device *失败:NULL//销毁device
void device_destroy(struct class *class, dev_t devt)

8) 申请物理内存区,申请SFR地址区

//申请物理内存区
struct resource *request_mem_region(resource_size_t start, resource_size_t n,const char *name)
参数说明:resource_size_t start -----> 虚拟内存的起始地址resource_size_t n ----> 申请虚拟内存的大小const char *name ----> 虚拟内存对应的名字返回值成功:struct resource *失败:NULL//释放物理内存区
void release_mem_region(resource_size_t start, resource_size_t n)

9) IO动态映射

//IO动态映射 ---- 根据物理地址得到虚拟的起始地址
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数说明:phys_addr_t offset ---- >物理地址的起始地址unsigned long size -----> IO映射的大小//释放IO映射
static inline void iounmap(void __iomem *addr)

10) 测试

1 插入和删除驱动
[root@GEC6818 /6818_driver]#insmod led_drv.ko 
[  173.611000] gec6818_led_init
[root@GEC6818 /6818_driver]#rmmod led_drv.ko 
[  220.248000] gec6818led_exit2 查看/dev下设备文件、主设备号、次设备
[root@GEC6818 /dev]#ls -l gec6818_led_device 
crw-rw----    1 root     root      100,   0 Jan  1 02:46 gec6818_led_device  //提供给应用程序来访问驱动程序3 查看class
[root@GEC6818 /sys/class]#ls -l gec6818_led_class/
total 0
lrwxrwxrwx    1 root     root             0 Jan  1 02:51 gec6818_led_device

觉得有帮助的话,打赏一下呗。。

           

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

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

相关文章

vscode专区

1.展示多行的文件导航标签,而非只有1行 1.1打开设置 1.2搜索该设置"workbench.editor.wrap.tabs",并勾选 1.3效果对比

vue+vscode 快速搭建运行调试环境与发布

1.安装node.js Node.js — Run JavaScript Everywhere 默认不断next 2.更换镜像地址 运行-cmd 执行以下代码安装 npm config set registry https://registry.npmmirror.com 检查node.js和镜像是否是否成功 node -v npm -v npm config get registry 3.安装打包工具 …

吊车报警的工作原理和使用场景_鼎跃安全

在现代建筑施工过程中&#xff0c;经常使用大型机械设备&#xff0c;如挖掘机、吊车、打桩机等&#xff0c;这些设备在施工过程中发挥着越来越重要的作用&#xff1b;同时&#xff0c;这些设备的作业频繁进行作业&#xff0c;对于接触到高压电线的风险也随之增加。大型机械设备…

Leetcode学习

回文数 反转一半数字 第一个想法是将数字转换为字符串&#xff0c;并检查字符串是否为回文。 但是&#xff0c;这需要额外的非常量空间来创建问题描述中所不允许的字符串。 第二个想法是将数字本身反转&#xff0c;然后将反转的数字与原始数字比较&#xff0c;如果它们是相同…

【计算机毕设】基于SpringBoot的中小企业设备管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 在中小企业中&#xff0c;设备管理是确保生产和运营效率的重要环节。传统的设备管理通常依赖于手工记录和人工管理&#xff0c;容易导致数据不准确、…

近屿OJAC带你解读:什么是ML?

概念定义 ML是机器学习&#xff08;Machine Learning&#xff09;的缩写。机器学习是人工智能的一个分支&#xff0c;它使计算机系统能够从数据中学习和改进&#xff0c;而无需进行明确的编程指令。简单来说&#xff0c;机器学习涉及到开发算法和统计模型&#xff0c;让计算机…

UE4 使用自带的插件制作音频可视化

1.插件默认为开启 2.新建共感NRT&#xff0c;选择要使用的音频 3.添加音频组件&#xff0c;添加共感NRT变量&#xff0c;选择新建的共感NRT对象 4.编写蓝图

基础—SQL—DQL(数据查询语言)分页查询

一、引言 上一篇博客学习了排序查询&#xff0c;这次来讲查询的最后一个部分&#xff1a;分页查询。 涉及到的关键字是&#xff1a;LIMIT 。 二、DQL—分页查询 对于分页&#xff0c;不管以后做的是传统的管理系统还是做互联网的项目&#xff0c;基本上都会遇到分页查询的操…

计网ppt标黄知识点整理第(4)章节——谢希仁版本、期末复习自用

路由器&#xff1a;查找转发表&#xff0c;转发分组。 IP网的意义&#xff1a;当互联网上的主机进行通信时&#xff0c;就好像在一个网络上通信一样&#xff0c;看不见互连的各具体的网络异构细节。如果在这种覆盖全球的 IP 网的上层使用 TCP 协议&#xff0c;那么就…

每天坚持写java锻炼能力---第一天(6.4)

今天的目标是菜单&#xff1a; B站/马士兵的项目菜单 package java1;import java.util.Scanner;public class Test {public static void main(String[] args) {while(true){ //3.加入死循环&#xff0c;让输入一直有System.out.println();System.out.println("--->项…

Linux 系统怎么快速「批量重命名」文件

如果需要对文件批量重命名&#xff0c;怎么办&#xff0c;是不是要找个工具&#xff0c;下载看这么使用。其实在 Linux、macOS 系统上使用脚本可以轻松搞定。 如&#xff0c;这里有一批图片文件&#xff0c;后缀名可能是jpg、jpeg、png 等&#xff0c;名称如 “我是待重命名的…

【WRF调试运行第一期】安装WRF模型所需平台

WRF实践实操第一期&#xff1a;安装WRF模型所需平台 1 操作系统2 先决条件软件3 程序流&#xff08;Program Flow&#xff09;4 文件说明软件安装1-Cygwin参考 安装 WRF&#xff08;Weather Research and Forecasting&#xff09;模型需要准备适当的硬件和软件平台。 相关介绍可…

【linux根分区扩容】

前言&#xff1a; 今天在安装软件的时候发现我的linux的根分区空间不足了&#xff0c;在网上搜索哈资料解决了。 解决根分区空间不足的问题方法&#xff1a; 第一&#xff1a;用lsblk命令查看 发现还有一些空间不在了。 第二&#xff1a;安装扩容工具&#xff1a; yum inst…

springCloud中将redis共用到common模块

一、 springCloud作为公共模块搭建框架 springCloud 微服务模块中将redis作为公共模块进行的搭建结构图&#xff0c;如下&#xff1a; 二、redis 公共模块的搭建框架 如上架构&#xff0c;代码如下pom.xml 关键代码&#xff1a; <dependencies><!-- SpringBoot Boo…

Thread Local六连问,你扛得住吗?

一、Thread Local 是什么? 线程本地变量。当使用ThreadLocal维护变量时&#xff0c;ThreadLocal为每个使用该变量的线程提供独立的变量副本&#xff0c;所以每个线程都可以独立地改变自己的副本&#xff0c;而不影响其他线程&#xff0c;做到了线程隔离。 二、Thread Local …

windows hash简介

一、hash简介 1、Windows系统使用两种方法对用户的密码进行哈希处理。它们分别是LAN Manager(LM)哈希和 NT LAN Manager(NTLM)哈希 2、所谓哈希(hash)&#xff0c;就是使用一种加密函数进行计算后的结果。这个加密函数对一个任意长度的 字符串数据进行一次数学加密函数运算…

电厂三维人员定位系统的应用与优势有哪些?

在电力行业的快速发展中&#xff0c;电厂的安全生产和管理显得尤为重要。近年来&#xff0c;随着信息技术的不断进步&#xff0c;电厂三维人员定位系统逐渐成为电厂安全管理的新利器。该系统利用三维技术&#xff0c;实现对电厂内部人员位置的实时监控与定位&#xff0c;大大提…

主机加固的最后一米防护

智慧互联的浪潮正席卷全球&#xff0c;它不仅重塑了传统的工业格局&#xff0c;也催生了无数创新的商业模式。随着物联网和互联网技术的飞速发展&#xff0c;智能化、自动化、联网化已成为未来各个行业的发展方向。然而&#xff0c;智慧物联的开放性、系统的漏洞以及基于用户、…

初识JAVA中的包装类,时间复杂度及空间复杂度

目录&#xff1a; 一.包装类 二.时间复杂度 三.空间复杂度 一.包装类&#xff1a; 在Java中&#xff0c;由于基本类型不是继承自Object&#xff0c;为了在泛型代码中可以支持基本类型&#xff0c;Java 给每个基本类型都对应了一个包装类型。 1 基本数据类型和对应的包装类 &am…

【Lua】IntelliJ IDEA 写注释或选中变量单词时偶尔会选中相邻的内容或下一行内容

例如: --UI代码local a 0 当你想在a变量上方加一行 --UI代码注释时&#xff0c;会发现敲打daima中文拼音时&#xff08;还未按回车&#xff09;就会选中当前行以及下一行前半部分。 打完按空格就会变成这样子&#xff01; 原因是因为开启了英文检测&#xff0c;需要关掉它。 …