1、前言

众所周知,Linux内核主要包括三种驱动模型,字符设备驱动,块设备驱动以及网络设备驱动。

其中,Linux字符设备驱动,可以说是Linux驱动开发中最常见的一种驱动模型。

我们该系列文章,主要为了帮助大家快速入门Linux驱动开发,该篇主要来了解一些字符设备驱动的框架和机制。

系列文章基于Kernel 4.19

2、关键数据结构

2.1 cdev

 struct cdev {
     struct kobject kobj;
     struct module *owner;
     const struct file_operations *ops;
     struct list_head list;
     dev_t dev;
     unsigned int count;
 } __randomize_layout;

结构体名称:cdev

文件位置:include/linux/cdev.h

主要作用:cdev可以理解为char device,用来抽象一个字符设备。

核心成员及含义:

  • kobj:表示一个内核对象。

  • owner:指向该模块的指针

  • ops:指向文件操作的指针,包括open、read、write等操作接口

  • list:用于将该设备加入到内核模块链表中

  • dev:设备号,由主设备号和次设备号构成

  • count:表示有多少个同类型设备,也间接表示设备号的范围

  • __randomize_layout:一个编译器指令,用于随机化结构体的布局,以增加安全性。

2.2 file_operations

 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 (*read_iter) (struct kiocb *, struct iov_iter *);
     ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
     int (*iterate) (struct file *, struct dir_context *);
     int (*iterate_shared) (struct file *, struct dir_context *);
     __poll_t (*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 *);
     unsigned long mmap_supported_flags;
     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 (*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 **, void **);
     long (*fallocate)(struct file *file, int mode, loff_t offset,
               loff_t len);
     void (*show_fdinfo)(struct seq_file *m, struct file *f);
 #ifndef CONFIG_MMU
     unsigned (*mmap_capabilities)(struct file *);
 #endif
     ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
             loff_t, size_t, unsigned int);
     int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
             u64);
     int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
             u64);
     int (*fadvise)(struct file *, loff_t, loff_t, int);
 } __randomize_layout;
 ​

结构体名称:file_operations

文件位置:include/linux/fs.h

主要作用:正如其名,主要用来描述文件操作的各种接口,Linux一切接文件的思想,内核想要操作哪个文件,都需要通过这些接口来实现。

核心成员及含义:

  • open:打开文件的函数

  • read:读取文件的函数。

  • write:写入文件的函数。

  • release:关闭文件的函数。

  • flush:刷新文件的函数,通常在关闭文件时调用。

  • llseek:改变文件读写指针位置的函数。

  • fsync:将文件数据同步写入磁盘的函数。

  • poll:询问文件是否可被非阻塞读写

2.3 dev_t

 typedef u32 __kernel_dev_t;
 ​
 typedef __kernel_dev_t dev_t;

类型名称:dev_t

文件位置:include/linux/types.h

主要作用:表示字符设备对应的设备号,其中包括主设备号和次设备号。

3、数据结构之间关系

上图绘制是对字符设备驱动程序的数据结构以及API的关系图,

有需要原始文件,可在公~号【嵌入式艺术】获取。

4、字符设备驱动整体架构

4.1 加载与卸载函数

驱动首先实现的就是加载和卸载函数,也是驱动程序的入口函数。

我们一般这么定义驱动的加载卸载函数:

 static int __init xxx_init(void)
 {
 ​
 }
 ​
 static void __exit xxx_exit(void)
 {
     
 }
 ​
 module_init(xxx_init);
 module_exit(xxx_exit);

这段代码就是实现一个通用驱动的加载与卸载,关于module_init和module_exit的实现机制,可以查看之前总结文章。

4.2 设备号管理

4.2.1 设备号的概念

每一类字符设备都有一个唯一的设备号,其中设备号又分为主设备号和次设备号,那么这两个分别作用是什么呢?

  • 主设备号:用于标识设备的类型,

  • 次设备号:用于区分同类型的不同设备

简单来说,主设备号用于区分是IIC设备还是SPI设备,而次设备号用于区分IIC设备下,具体哪一个设备,是MPU6050还是EEPROM。

4.2.2 设备号的分配

了解了设备号的概念,Linux中设备号有那么多,那么我们该如何去使用正确的设备号呢?

设备号的分配方式有两种,一种是动态分配,另一种是静态分配,也可以理解为一种是内核自动分配,一种是手动分配。

静态分配函数:

 int register_chrdev_region(dev_t from, unsigned count, const char *name);
  • from:表示已知的一个设备号

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

  • name:表示设备或者驱动的名称

函数作用:以from设备号开始,连续分配count个同类型的设备号

动态分配函数:

 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
  • dev:设备号的指针,用于存放分配的设备号的值

  • baseminor:次设备号开始分配的起始值

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

  • name:表示设备或者驱动的名称

函数作用:从baseminor次设备号开始,连续分配count个同类型的设备号,并自动分配一个主设备号,将主、次组成的设备号信息赋值给*dev

这两个函数最大的区别在于:

  • register_chrdev_region:调用前,已预先定义好了主设备号和次设备号,调用该接口后,会将自定义的设备号登记加入子系统中,方便系统追踪系统设备号的使用情况。

  • alloc_chrdev_region:调用前,未定义主设备号和次设备号;调用后,主设备号以0来表示,以自动分配,并且将自动分配的设备号,同样加入到子系统中,方便系统追踪系统设备号的使用情况。

这两个函数的共同点在于:

系统维护了一个数组列表,用来登记所有的已使用的设备号信息,这两个接口归根到底也是将其设备号信息,登记到系统维护的设备号列表中,以免后续冲突使用。

在Linux中,我们可以通过cat /proc/devices命令,查看所有i登记的设备号列表。

后面有时间,我们可以详细聊设备号的自动分配机制,管理机制。

4.2.3 设备号的注销

设备号作为一种系统资源,当所对应的设备卸载时,当然也要将其所占用的设备号归还给系统,无论时静态分配,还是动态分配,最终都是调用下面函数来注销的。

 void unregister_chrdev_region(dev_t from, unsigned count);
  • from:表示已知的一个设备号

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

函数作用:要注销from主设备号下的连续count个设备

4.2.4 设备号的获取

设备号的管理很简单,在关键数据结构中,我们看到设备号的类型是dev_t,也就是u32类型表示的一个数值。

其中主设备号和次设备号的分界线,由MINORBITS宏定义指定:

 #define MINORBITS   20

也就是主设备号占用高12bit,次设备号占用低20bit

并且,内核还提供了相关API接口,来获取主设备号和次设备号,以及生成设备号的接口,如下:

#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

以上,通过移位操作,来实现主次设备号的获取。

4.2.4 通用代码实现

#define CUSTOM_DEVICE_NUM 0
#define DEVICE_NUM 1
#device DEVICE_NAME "XXXXXX"
static dev_t global_custom_major = CUSTOM_DEVICE_NUM;

static int __init xxx_init(void)
{
dev_t custom_device_number= MKDEV(global_custom_major, 0); // custom device number
/* device number register*/
if (global_custom_major) {
ret = register_chrdev_region(custom_device_number, DEVICE_NUM, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&custom_device_number, 0, DEVICE_NUM, DEVICE_NAME);
global_custom_major = MAJOR(custom_device_number);
}
}

static void __exit xxx_exit(void)
{
unregister_chrdev_region(MKDEV(global_mem_major, 0), DEVICE_NUM);
}

module_init(xxx_init);
module_exit(xxx_exit);

该函数实现了设备号的分配,如果主设备号为0,则采用动态配分的方式,否则采用静态分配的方式。

更多干货可见:高级工程师聚集地,助力大家更上一层楼!

4.3 字符设备的管理

了解完设备号的管理之后,我们来看下字符设备是如何管理的。

4.3.1、字符设备初始化

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
  • cdev:一个字符设备对象,也就是我们创建好的字符设备

  • fops:该字符设备的文件处理接口

函数作用:初始化一个字符设备,并且将所对应的文件处理指针与字符设备绑定起来。

4.3.2、字符设备注册

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
  • p:一个字符设备指针,只想待添加的字符设备对象

  • dev:该字符设备所负责的第一个设备编号

  • count:该类型设备的个数

函数作用:添加一个字符设备驱动到Linux系统中。

4.3.3、字符设备注销

void cdev_del(struct cdev *p);
  • p:指向字符设备对象的指针

函数作用:从系统中移除该字符设备驱动

4.4 文件操作接口的实现

因为在Linux中,一切皆文件的思想,所以每一个字符设备,也都有一个文件节点来对应。

我们在初始化字符设备的时候,会将struct file_operations的对象与字符设备进行绑定,其作用是来处理该字符设备的open、read、write等操作。

我们要做的就是去实现我们需要的函数接口,如:

static const struct file_operations global_mem_fops = {
.owner = THIS_MODULE,
.llseek = global_mem_llseek,
.read = global_mem_read,
.write = global_mem_write,
.unlocked_ioctl = global_mem_ioctl,
.open = global_mem_open,
.release = global_mem_release,
};

至此,我们一个基本的字符设备驱动程序的框架就基本了然于胸了

5、总结

本篇文章,旨在通俗易懂的讲解:

  • 字符设备驱动相关数据结构

  • 数据结构关系图

  • 核心API接口

  • 字符设备驱动整体框架

希望对大家有所帮助。

一文秒懂|Linux字符设备驱动的更多相关文章

  1. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  2. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  3. Smart210学习记录----beep linux字符设备驱动

    今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...

  4. Linux字符设备驱动实现

    Linux字符设备驱动实现 要求 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序.可以有一个读,一个写进程共享该字符设备,进行聊天:也可以由多个读和多个写进程共享该字符设备,进 ...

  5. Linux字符设备驱动基本结构

    1.Linux字符设备驱动的基本结构 Linux系统下具有三种设备,分别是字符设备.块设备和网络设备,Linux下的字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中某一数据,读取数据 ...

  6. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

  7. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  8. Linux 字符设备驱动模型

    一.使用字符设备驱动程序 1. 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块 2. 创建设备文件 通 ...

  9. linux字符设备驱动学习笔记(一):简单的字符设备驱动

    最近在鼓捣lnux字符设备驱动,在网上搜集的各种关于linux设备驱动的代码和注释,要么是针对2.4的,要么是错误百出,根本就不能运行成功,真希望大家在发博客的时候能认真核对下代码的正确性,特别是要把 ...

  10. Linux字符设备驱动

    一.字符设备基础 字符设备 二.字符设备驱动与用户空间访问该设备的程序三者之间的关系 三.字符设备模型 1.Linux内核中,使用 struct cdev 来描述一个字符设备 动态申请(构造)cdev ...

随机推荐

  1. SpringBoot3文件管理

    目录 一.简介 二.工程搭建 1.工程结构 2.依赖管理 三.上传下载 1.配置管理 2.上传下载 四.Excel文件 1.Excel创建 2.Excel读取 3.解析监听 4.导入导出 五.参考源码 ...

  2. Android 项目移植后运行黑屏

    在做Android项目的移植过程中,即把名为a的代码和样式全部复制到b项目后,只是对a项目内多余代码删除和修改包名,最后替换完后,点击=>运行,运行成功,但是演示机显示黑屏,看log也没有报错. ...

  3. Linux 内核音频数据传递主要流程 (上)

    Linux 用户空间应用程序通过声卡驱动程序(一般牵涉到多个设备驱动程序)和 Linux 内核 ALSA 框架导出的 PCM 设备文件,如 /dev/snd/pcmC0D0c 和 /dev/snd/p ...

  4. 数据可视化【原创】vue复合数字形式展示

    做数据可视化的时候,经常碰到需要很灵活的数字形式展示. 先上个效果图: 如图包括名称,数量,别名,单位,上升下降,环比等等的复合数据展示,并且需要支持样式灵活配置. 此组件包括2个模块,父容器组件bo ...

  5. Python 创建或读取 Excel 文件

    Excel是一种常用的电子表格软件,广泛应用于金融.商业和教育等领域.它提供了强大的数据处理和分析功能,可进行各种计算和公式运算,并能创建各种类型的图表和可视化数据.Excel的灵活性使其成为处理和管 ...

  6. iOS发送探针日志到日志系统的简单实现

    通过参考Testin的SDK实现方式,我们大致可以确定他们背后的实现方式: 首先,通过加载Testin的SDK,然后收集各种七七八八的数据,再通过socket发送数据到云端. 云端我们已经有了,就是h ...

  7. Java实践项目 - 用户登录

    Smiling & Weeping ----以花祈愿,祝你平安 当用户输入用户名和密码将数据提交给数据库进行查询,如果存在对应的用户名和密码,则表示登陆成功,登录成功之后跳转到系统的主页就是i ...

  8. 基于Spring事务的可靠异步调用实践

    SpringTxAsync组件是仓储平台组(WMS6)自主研发的一个专门用于解决可靠异步调用问题的组件. 通过使用SpringTxAsync组件,我们成功地解决了在仓储平台(WMS6)中的异步调用需求 ...

  9. Http协议之libcurl实现

    一.libcurl简介 libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议.libcurl同样支持 ...

  10. Django框架项目之项目基础——项目介绍、需求分析、pip安装源、环境搭建、前台、后台、跨域请求

    文章目录 1 路飞学城 1.1 企业的web项目类型 1.2 企业项目开发流程 1.3 立项申请阶段 2. 需求分析 2.1 首页 2.2 登录注册 2.3 课程列表 2.4 课程详情 2.5 购物车 ...