Linux驱动之字符设备驱动框架与示例模板

news2024/11/18 11:40:28

目录

一、字符设备驱动简介

二、字符设备驱动开发步骤

1.确定设备号

2.定义 file_operations 结构体

3.实现操作函数

4.注册和注销字符设备

5.编译和加载模块

6.用户空间交互:

三、字符设备驱动示例模板

四、字符设备驱动开发总结


一、字符设备驱动简介

        字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

        在详细的学习字符设备驱动架构之前,我们先来简单的了解一下 Linux 下的应用程序是如何调用驱动程序的, Linux 应用程序对驱动程序的调用流程如下图所示:

        在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

        比如现在有个叫做/dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。 open和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数。如果要获取led 灯的状态,就用 read 函数从驱动中读取相应的状态。

        应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。

        open closewriteread 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如下图所示:

        其中关于 C 库以及如何通过系统调用“陷入” 到内核空间这个我们不用去管,我们重点关注的是应用程序和具体的驱动,应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open 这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,内容如下所示:

/* 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 (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
            unsigned int flags);
    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);
    loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                   struct file *file_out, loff_t pos_out,
                   loff_t len, unsigned int remap_flags);
    int (*fadvise)(struct file *, loff_t, loff_t, int);
    int (*uring_cmd)(struct io_uring_cmd *ioucmd, unsigned int issue_flags);
    int (*uring_cmd_iopoll)(struct io_uring_cmd *, struct io_comp_batch *,
                unsigned int poll_flags);
}

        在字符设备驱动开发中最常用的就是上面这些函数,关于其他的函数大家可以查阅相关文档。我们在字符设备驱动开发中最主要的工作就是实现上面这些函数,不一定全部都要实现,但是像 open、 release、 write、 read 等都是需要实现的,当然了,具体需要实现哪些函数还是要看具体的驱动要求。

二、字符设备驱动开发步骤

下面是开发字符设备驱动的基本步骤:

1.确定设备号

  • 如果使用静态分配设备号,可以在代码中指定一个固定的设备号。

  • 如果使用动态分配设备号,可以调用 'alloc_chrdev_region' 函数在模块初始化时分配设备号。

  • 如果使用 'udev' 等工具进行设备号的动态分配和管理,则需要在驱动程序中声明一个 'dev_t' 类型的变量,并使用 'MKDEV' 宏将主设备号和次设备号合并为一个设备号。

2.定义 file_operations 结构体

  • 创建一个结构体,用于定义字符设备驱动程序对外提供的操作接口。常见的函数包括 'open''release''read'、'write''ioctl' 等。

  • 在驱动程序的初始化函数中,将这些操作函数与对应的函数指针关联起来。

3.实现操作函数

  • 实现字符设备驱动中定义的操作函数,根据设备的需求来进行相应的操作。

  • 'open' 函数中可以进行设备的初始化工作,例如分配内存、初始化设备状态等。

  • 'release' 函数中可以进行设备的清理工作,例如释放内存、关闭设备等。

  • 'read' 函数中可以从设备读取数据,并将数据传递给用户空间。

  • 'write' 函数中可以接收用户空间传递的数据,并将数据写入设备。

  • 'ioctl' 函数中可以处理设备的特殊控制命令。

4.注册和注销字符设备

  • 在驱动程序的初始化函数中,调用 'alloc_chrdev_region' 函数分配设备号。

  • 使用 'cdev_init' 初始化 'cdev' 结构体,并将 file_operations 结构体指针赋值给 'cdev' 的成员。

  • 使用 'cdev_add' 将设备添加到内核中,使其可用。

  • 在驱动程序的退出函数中,使用 'cdev_del''unregister_chrdev_region' 函数注销设备。

5.编译和加载模块

  • 将驱动程序的源代码编译为内核模块,生成对应的 .ko 文件。

  • 使用 'insmod' 命令加载模块到内核中。

6.用户空间交互:

  • 用户程序可以通过系统调用来访问字符设备,例如使用 'open''read''write''ioctl' 等函数来打开、读取和写入设备。

  • 用户程序可以使用文件描述符来标识打开的设备,通过文件描述符进行读写操作。

        以上是字符设备驱动开发的基本步骤。在实际开发中,还需要进行错误处理、设备管理、内存分配和释放等工作,具体的实现细节会根据设备的特性和需求而有所不同。为了开发出高质量的驱动程序,建议仔细阅读相关的文档、示例代码和最佳实践,并进行充分的测试和验证。

三、字符设备驱动示例模板

        字符驱动框架是一种在Linux内核中实现设备驱动程序的方法。它允许开发者编写基于字符设备的驱动程序,以便与用户空间中的字符设备进行通信。下面是一个简单的字符驱动框架模板,包括了必要的函数和数据结构。

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "mydevice"
#define BUF_SIZE 1024

static dev_t dev;
static struct cdev cdev;
static char buffer[BUF_SIZE];
static int buffer_len = 0;

static int device_open(struct inode *inode, struct file *filp)
{
    // 设备打开时的操作
    return 0;
}

static int device_release(struct inode *inode, struct file *filp)
{
    // 设备关闭时的操作
    return 0;
}

static ssize_t device_read(struct file *filp, char *user_buf, size_t count, loff_t *f_pos)
{
    // 从设备读取数据
    size_t to_copy = min(count, (size_t)buffer_len);
    if (copy_to_user(user_buf, buffer, to_copy) != 0)
        return -EFAULT;

    return to_copy;
}

static ssize_t device_write(struct file *filp, const char *user_buf, size_t count, loff_t *f_pos)
{
    // 向设备写入数据
    size_t to_copy = min(count, (size_t)BUF_SIZE);
    if (copy_from_user(buffer, user_buf, to_copy) != 0)
        return -EFAULT;

    buffer_len = to_copy;
    return to_copy;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

static int __init chardev_init(void)
{
    // 模块初始化函数
    if (alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME) < 0)
    {
        printk(KERN_ALERT "Failed to allocate character device region\n");
        return -1;
    }

    cdev_init(&cdev, &fops);
    if (cdev_add(&cdev, dev, 1) < 0)
    {
        unregister_chrdev_region(dev, 1);
        printk(KERN_ALERT "Failed to add character device\n");
        return -1;
    }

    printk(KERN_INFO "Character device registered: %s\n", DEVICE_NAME);
    return 0;
}

static void __exit chardev_exit(void)
{
    // 模块退出函数
    cdev_del(&cdev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "Character device unregistered\n");
}

module_init(chardev_init);
module_exit(chardev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Character Device Driver");

        这个模板定义了一个名为 'mydevice' 的字符设备驱动。驱动程序会将用户空间的数据写入到 'buffer' 中,并从 'buffer' 中读取数据返回给用户空间。其中,'device_open' 'device_release' 函数在设备打开和关闭时被调用,'device_read' 'device_write' 函数用于读取和写入设备数据。

        该模板使用 'alloc_chrdev_region' 函数为字符设备分配主次设备号,并使用 'cdev_init' 'cdev_add' 函数将设备添加到系统中。在模块退出时,使用 'cdev_del' 'unregister_chrdev_region' 函数来注销设备。

        请注意,这只是一个简单的字符驱动框架模板,仅用于演示目的。实际的字符驱动可能需要更多的功能和错误处理。在开发字符驱动程序时,请仔细参考Linux内核文档和示例代码,以确保正确性和稳定性。

四、字符设备驱动开发总结

        字符驱动是一种在操作系统内核中实现的设备驱动程序,用于与字符设备进行交互。字符设备是一种以字节为单位进行输入和输出的设备,例如终端、串口、打印机等。字符驱动框架提供了一组函数和数据结构,使得开发者可以编写自定义的字符设备驱动程序。

在Linux内核中,字符驱动的实现基于以下几个核心组件:

1.设备号:每个字符设备驱动在注册时都会被分配一个唯一的设备号。设备号包括主设备号和次设备号。主设备号标识设备驱动程序,次设备号标识具体的设备实例。

2.file_operations 结构体:这是一个函数指针结构体,定义了设备驱动程序对外提供的操作接口。常见的函数包括 'open''release''read''write''ioctl' 等。开发者需要实现这些函数来处理设备的打开、关闭、读取和写入等操作。

3.cdev 结构体:'cdev' 是字符设备驱动的核心结构体,它代表一个字符设备实例。它包含了对应的 file_operations 结构体指针以及设备号等信息。通过使用 'cdev' 结构体,开发者可以注册和管理字符设备。

4.字符设备注册和注销:在字符驱动的初始化阶段,需要使用 'alloc_chrdev_region' 函数为驱动程序分配设备号。然后使用 'cdev_init' 初始化 'cdev' 结构体,并使用 'cdev_add' 将设备添加到系统中。在驱动程序退出时,需要使用 'cdev_del''unregister_chrdev_region' 函数来注销设备。

5.用户空间交互:字符驱动允许用户空间程序通过系统调用来访问设备。用户程序可以打开设备、读取和写入设备数据,并通过 'ioctl' 等方式发送控制命令。


         关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。

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

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

相关文章

未来Mac下载站怎么打不开了

重要公告&#xff1a; 未来软件园因业务需要现更换域名 原域名&#xff1a;Mac.orsoon.com 更为新域名&#xff1a;未来mac下载-Mac软件-mac软件下载-mac软件大全 程序已全面转移&#xff0c;请访问新域名

MySQL中的索引和事务 (数据库系列5)

目录 前言&#xff1a; 1.索引 1.1 索引的概念 1.2索引的作用 1.3索引的使用场景 1.4索引的使用 1.4.1查看索引 1.4.2创建索引 1.4.3删除索引 1.5索引背后的数据结构 1.5.1 B-树 1.5.2 B树 2.事务 2.1事务的概念 2.2数据库事务的四个特性 2.2.1原子性 2.2.2一…

城会玩,Selenium+Docker成功解决这一大难题

01、需求背景 日常测试中会遇到对web应用进行UI自动化的测试场景&#xff0c;一般常用的工具是使用Selenium&#xff0c;一套简单的UI自动化架构如下&#xff1a; 上图即为简单搭建的一套UI自动化测试架构&#xff0c;但 串行执行测试用例&#xff1a; 一台机器只能安装一个…

《英雄联盟》提示丢失D3DCompiler_43.dll的三个解决方法

在我们打开游戏《英雄联盟》的时候&#xff0c;计算机报错提示“由于找不到D3DCompiler_43.dll&#xff0c;无法继续执行此代码”&#xff0c;“D3DCompiler_43.dll丢失”是怎么回事呢&#xff1f;D3DCompiler_43.dll是一个Microsoft DirectX的组件文件&#xff0c;它是用于编译…

博客系统(使用前后端分离)

博客系统 前言一.准备工作1.1 准备好前端文件1.2 设计数据库1.3 编写基本的数据库代码1.4 封装好数据库的连接操作1.5 根据设计的表创建实体类1.6 根据实体类,提供一些简单的增删改查操作 二.博客要实现的功能2.1 博客列表页功能2.2 博客详情页2.3 博客登录页2.4 页面强制登录功…

涵子来信——AI的无限未来——谈谈想法

大家好&#xff1a; 这一次&#xff0c;我想要跟大家讲一讲我对AI的看法和未来的展望&#xff0c;谈谈我的想法。 AI&#xff08;Artificial Intelligence&#xff0c;中文人工智能&#xff09;&#xff0c;是我们生活中处处都可以见到的&#xff0c;小到一个语音助手&#x…

ylb-接口13实名认证

总览&#xff1a; 在api模块下的service包&#xff0c;创建一个充值接口RechargeService&#xff0c;并创建一个&#xff08;根据userID查询它的充值记录&#xff09;方法&#xff1a; package com.bjpowernode.api.service;import com.bjpowernode.api.model.RechargeRecord…

迪赛智慧数——柱状图(多色柱状图):旅行灵感来源

效果图 涉足旅行就是一次睿智的选择&#xff0c;心系未来、永不停步&#xff0c;让精神和思维得到滋养&#xff0c;更加懂得珍惜和感恩&#xff0c;这是旅行给予生活的灵感。西方一位哲人也说过&#xff0c;“生命的意义在于尝试&#xff0c;体验不同的可能”&#xff0c;旅行能…

基于springboot+Redis的前后端分离项目(九)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 附近的商户、用户签到、UV统计 &#xff08;一&#xff09;附近的商户1.附近商户-GEO数据结构的基本用法2.附近商户-导入店铺数据到…

Python实现登陆界面+生日界面

文章目录 1. 需求分析1.1 功能分析1.2 性能分析 2. 技术原理3. 详细设计3.1 登录界面3.2 注册界面3.3 修改密码3.4 注销账户3.5 生日界面 4. 功能实现4.1 登陆界面4.2 注册界面4.3 修改密码4.4 注销账户4.5 生日界面 1. 需求分析 1.1 功能分析 ① 登录界面实现用户的登录、注…

消费者行为分析VR情景模拟演练系统

VR虚拟现实技术是一种先进的技术&#xff0c;利用VR开展消费者行为分析课程是一种创新的教育方式&#xff0c;它可以提高学生的学习兴趣和效果&#xff0c;同时也可以为企业提供更好的人才培训和发展机会。 1.帮助学生更好地理解和应用心理学概念&#xff1a;VR技术可以让学生…

【Vite搭建Vue3项目】如何使用自定义的svg

Vite搭建Vue3项目如何使用自定义的svg 1. 准备一份svg图标集放入到自己想放的目录2. 下载对应的插件并进行配置3. 测试使用 绪论&#xff1a;当用 vite 构建 vue3 项目的时候&#xff0c;咱可以使用 Element-plus 为我们提供的图标&#xff0c;但是它是一个个标签&#xff0c;当…

HTTP1.1 wireshark分析

本地springboot启动一个简单的服务&#xff0c;然后请求测试 tcpdump -i lo0 -nnvv -w tmp.cap tcpdump 本地回环网卡 http1.1 HTTP/1.0 每进行一次通信&#xff0c;都需要经历建立连接、传输数据和断开连接三个阶段。当一个页面引用了较多的外部文件时&#xff0c;这个建立…

两种异步日志方案的介绍

文章目录 一、日志写入逻辑1.1 相关接口函数1.2 写入逻辑 二、log4cpp 日志框架2.1 下载和编译2.2 日志级别2.3 日志格式2.4 日志输出2.5 日志回滚 三、muduo 异步日志库3.1 异步日志机制3.2 双缓冲机制3.3 前端日志写入3.4 后端日志落盘3.5 coredump 查找未落盘的日志3.6 总结…

复习第六课 C语言-排序,初识指针

目录 【1】冒泡排序&#xff08;从小到大&#xff09; 【2】选择排序 【3】二维数组 【4】指针 【5】指针修饰 【6】大小端 【7】初见二级指针 练习&#xff1a; 【1】冒泡排序&#xff08;从小到大&#xff09; #include <stdio.h> //数组哪里的\0?自己和字符串…

论文阅读-2:基于深度学习的大尺度遥感图像建筑物分割研究

一、该网络中采用了上下文信息捕获模块。通过扩大感受野&#xff0c;在保留细节信息的同时&#xff0c;在中心部分进行多尺度特征的融合&#xff0c;缓解了传统算法中细节信息丢失的问题&#xff1b;通过自适应地融合局部语义特征&#xff0c;该网络在空间特征和通道特征之间建…

SSH框架简介篇

文章目录 概述目录结构 strutsSpringHibernate总结 概述 SSH框架&#xff08;Struts Spring Hibernate&#xff09;是一种广泛应用的Java企业级开发框架组合&#xff0c;它将Struts、Spring和Hibernate三个优秀的框架有机地结合在一起&#xff0c;提供了一套完整的解决方案&…

cmake 函数相关

目录 cmake函数和宏基础 demo cmake函数和宏的参数处理 cmake函数和宏的基本使用 demo cmake函数和宏使用变量 demo demo cmake函数和宏需要注意的地方 demo cmake函数和宏的关键字参数 demo 使用第二种形式cmake_parse_arguments() demo 关键字list demo singl…

GDB 调试代码

目录 一、其他调试代码的工具 二、GDB调试 1、调试准备 2、开始调试 3、调试命令 1.运行程序 2.退出gdb 3.传参 4.查看代码 5.设置或删除断点及相关操作 6.继续运行 7.运行中打印某些值及其类型 8.自动的打印某些值和信息及其相关操作 9.单步调试 10.设置变量的…

http-server 的安装与使用

文章目录 问题背景http-server简介安装nodejs安装http-server开启http服务http-server参数 问题背景 打开一个文档默认使用file协议打开&#xff0c;不能发送ajax请求&#xff0c;只能使用http协议才能请求资源&#xff0c;所以此时我们需要在本地建立一个http服务&#xff0c…