一文秒懂|Linux字符设备驱动

原创 嵌入式艺术 2023-11-30 08:45

一文秒懂|Linux字符设备驱动

image-20231123091238538

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:指向文件操作的指针,包括openreadwrite等操作接口
  • list:用于将该设备加入到内核模块链表中
  • dev:设备号,由主设备号和次设备号构成
  • count:表示有多少个同类型设备,也间接表示设备号的范围
  • __randomize_layout:一个编译器指令,用于随机化结构体的布局,以增加安全性。

 

2.2 file_operations

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_tint);
    ssize_t (*read) (struct file *, char __user *, size_tloff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_tloff_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 intunsigned long);
    long (*compat_ioctl) (struct file *, unsigned intunsigned 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_tloff_tint datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, intsize_tloff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned longunsigned longunsigned longunsigned 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_tunsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_tunsigned 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_tsize_tunsigned 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_tloff_tint);
} __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、数据结构之间关系

image-20231123085448145

上图绘制是对字符设备驱动程序的数据结构以及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_initmodule_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的对象与字符设备进行绑定,其作用是来处理该字符设备的openreadwrite等操作。

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

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接口
  • 字符设备驱动整体框架

希望对大家有所帮助。

嵌入式艺术
MEET SPRING
万物更生,新岁开启


嵌入式艺术 分享一些高级嵌入式相关知识,包括:计算机基础、操作系统、Linux驱动、Linux内核、RT-thread等,除此之外,并且会组织一些简单的训练项目,一起成长。 我的创作理念:专注分享高质量的嵌入式文章,让大家读有所得!
评论
  • RGB灯光无法同步?细致的动态光效设定反而成为产品客诉来源!随着科技的进步和消费者需求变化,电脑接口设备单一功能性已无法满足市场需求,因此在产品上增加「动态光效」的形式便应运而生,藉此吸引消费者目光。这种RGB灯光效果,不仅能增强电脑周边产品的视觉吸引力,还能为用户提供个性化的体验,展现独特自我风格。如今,笔记本电脑、键盘、鼠标、鼠标垫、耳机、显示器等多种电脑接口设备多数已配备动态光效。这些设备的灯光效果会随着音乐节奏、游戏情节或使用者的设置而变化。想象一个画面,当一名游戏玩家,按下电源开关,整
    百佳泰测试实验室 2025-02-27 14:15 137浏览
  • 振动样品磁强计是一种用于测量材料磁性的精密仪器,广泛应用于科研、工业检测等领域。然而,其测量准确度会受到多种因素的影响,下面我们将逐一分析这些因素。一、温度因素温度是影响振动样品磁强计测量准确度的重要因素之一。随着温度的变化,材料的磁性也会发生变化,从而影响测量结果的准确性。因此,在进行磁性测量时,应确保恒温环境,以减少温度波动对测量结果的影响。二、样品制备样品的制备过程同样会影响振动样品磁强计的测量准确度。样品的形状、尺寸和表面处理等因素都会对测量结果产生影响。为了确保测量准确度,应严格按照规
    锦正茂科技 2025-02-28 14:05 134浏览
  • 1,微软下载免费Visual Studio Code2,安装C/C++插件,如果无法直接点击下载, 可以选择手动install from VSIX:ms-vscode.cpptools-1.23.6@win32-x64.vsix3,安装C/C++编译器MniGW (MinGW在 Windows 环境下提供类似于 Unix/Linux 环境下的开发工具,使开发者能够轻松地在 Windows 上编写和编译 C、C++ 等程序.)4,C/C++插件扩展设置中添加Include Path 5,
    黎查 2025-02-28 14:39 140浏览
  • 一、VSM的基本原理震动样品磁强计(Vibrating Sample Magnetometer,简称VSM)是一种灵敏且高效的磁性测量仪器。其基本工作原理是利用震动样品在探测线圈中引起的变化磁场来产生感应电压,这个感应电压与样品的磁矩成正比。因此,通过测量这个感应电压,我们就能够精确地确定样品的磁矩。在VSM中,被测量的样品通常被固定在一个震动头上,并以一定的频率和振幅震动。这种震动在探测线圈中引起了变化的磁通量,从而产生了一个交流电信号。这个信号的幅度和样品的磁矩有着直接的关系。因此,通过仔细
    锦正茂科技 2025-02-28 13:30 100浏览
  •         近日,广电计量在聚焦离子束(FIB)领域编写的专业著作《聚焦离子束:失效分析》正式出版,填补了国内聚焦离子束领域实践性专业书籍的空白,为该领域的技术发展与知识传播提供了重要助力。         随着芯片技术不断发展,芯片的集成度越来越高,结构也日益复杂。这使得传统的失效分析方法面临巨大挑战。FIB技术的出现,为芯片失效分析带来了新的解决方案。它能够在纳米尺度上对芯片进行精确加工和分析。当芯
    广电计量 2025-02-28 09:15 116浏览
  • 在2024年的科技征程中,具身智能的发展已成为全球关注的焦点。从实验室到现实应用,这一领域正以前所未有的速度推进,改写着人类与机器的互动边界。这一年,我们见证了具身智能技术的突破与变革,它不仅落地各行各业,带来新的机遇,更在深刻影响着我们的生活方式和思维方式。随着相关技术的飞速发展,具身智能不再仅仅是一个技术概念,更像是一把神奇的钥匙。身后的众多行业,无论愿意与否,都像是被卷入一场伟大变革浪潮中的船只,注定要被这股汹涌的力量重塑航向。01为什么是具身智能?为什么在中国?最近,中国具身智能行业的进
    艾迈斯欧司朗 2025-02-28 15:45 221浏览
  • 美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?‌美国加州CEC能效认证与美国DOE能效认证在多个方面存在显著差异‌。认证范围和适用地区‌CEC能效认证‌:仅适用于在加利福尼亚州销售的电器产品。CEC认证的范围包括制冷设备、房间空调、中央空调、便携式空调、加热器、热水器、游泳池加热器、卫浴配件、光源、应急灯具、交通信号模块、灯具、洗碗机、洗衣机、干衣机、烹饪器具、电机和压缩机、变压器、外置电源、消费类电子设备
    张工nx808593 2025-02-27 18:04 120浏览
  • 更多生命体征指标风靡的背后都只有一个原因:更多人将健康排在人生第一顺位!“AGEs,也就是晚期糖基化终末产物,英文名Advanced Glycation End-products,是存在于我们体内的一种代谢产物” 艾迈斯欧司朗亚太区健康监测高级市场经理王亚琴说道,“相信业内的朋友都会有关注,最近该指标的热度很高,它可以用来评估人的生活方式是否健康。”据悉,AGEs是可穿戴健康监测领域的一个“萌新”指标,近来备受关注。如果站在学术角度来理解它,那么AGEs是在非酶促条件下,蛋白质、氨基酸
    艾迈斯欧司朗 2025-02-27 14:50 400浏览
  •           近日受某专业机构邀请,参加了官方举办的《广东省科技创新条例》宣讲会。在与会之前,作为一名技术工作者一直认为技术的法例都是保密和侵权方面的,而潜意识中感觉法律有束缚创新工作的进行可能。通过一个上午学习新法,对广东省的科技创新有了新的认识。广东是改革的前沿阵地,是科技创新的沃土,企业是创新的主要个体。《广东省科技创新条例》是广东省为促进科技创新、推动高质量发展而制定的地方性法规,主要内容包括: 总则:明确立法目
    广州铁金刚 2025-02-28 10:14 103浏览
  • 在物联网领域中,无线射频技术作为设备间通信的核心手段,已深度渗透工业自动化、智慧城市及智能家居等多元场景。然而,随着物联网设备接入规模的不断扩大,如何降低运维成本,提升通信数据的传输速度和响应时间,实现更广泛、更稳定的覆盖已成为当前亟待解决的系统性难题。SoC无线收发模块-RFM25A12在此背景下,华普微创新推出了一款高性能、远距离与高性价比的Sub-GHz无线SoC收发模块RFM25A12,旨在提升射频性能以满足行业中日益增长与复杂的设备互联需求。值得一提的是,RFM25A12还支持Wi-S
    华普微HOPERF 2025-02-28 09:06 143浏览
  • 应用趋势与客户需求,AI PC的未来展望随着人工智能(AI)技术的日益成熟,AI PC(人工智能个人电脑)逐渐成为消费者和企业工作中的重要工具。这类产品集成了最新的AI处理器,如NPU、CPU和GPU,并具备许多智能化功能,为用户带来更高效且直观的操作体验。AI PC的目标是提升工作和日常生活的效率,通过深度学习与自然语言处理等技术,实现更流畅的多任务处理、实时翻译、语音助手、图像生成等功能,满足现代用户对生产力和娱乐的双重需求。随着各行各业对数字转型需求的增长,AI PC也开始在各个领域中显示
    百佳泰测试实验室 2025-02-27 14:08 252浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦