字符设备驱动

news2024/11/23 17:09:26

        字符设备就是按字节流进行读写的设备,读写数据分先后顺序,如点灯,IIC,SPI,LCD等都是字符设备,这些设备的驱动就叫字符设备驱动。

        include/linux/fs.h中 file_operations 结构体为内核驱动操作函数集合,C库中调用open,read,write等函数时,具体在驱动层执行的函数指针会关联记录在此结构体中。

流程简述

        内核起来之后,使用模块加载命令加载.ko文件时,在驱动层便会开始执行宏 module_init 载入的函数,一个基本的字符设备驱动,加载流程分以下几步:

  1. 确定设备号,可以是动态分配,也可以是静态指定;
  2. 关联 file_operations 结构体变量,因为里面存放着具体执行动作的函数指针;
  3. 关联设备号;
  4. 将字符设备添加到内核。

        卸载流程会调用宏 module_exit 载入的函数,简单来说需要实现注销设备号,调用相关函数删除字符设备结构体,并释放相关的资源。

        对于开发着而言,最主要的是 file_operations 结构体对象的实现,然后是正确的框架搭建。


 内核设备号

        内核中每个设备都有一个设备号,设备号由主,次两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,内核中提供了一个 dev_t 的数据类型表示设备号,定义在 include/linux/types.h 中,dev_t 实际上是 unsigned int 类型,是32位的数据类型。其中高 12 位为主设备号,低 20 位为次设备号。

        在 include/linux/kdev_t.h 中提供了几个关于设备号的操作宏,见下图

        设备号的分配分静态和动态分配。

静态分配

        有一些常用的设备号已经被内核开发者分配掉了,具体的分配情况可以查看 Documentation/devices.txt 文档,具体能不能用还得看我们硬件平台运行的过程中有没有使用这个主设备号,使用 cat /proc/devices 即可查看当前系统中正在使用的设备号。

动态分配

        因为静态分配会带来设备号冲突的问题,所以推荐使用动态分配的方式,在注册字符设备之前先申请一个设备号,注销设备时释放这个设备号即可。设备号的申请,释放函数如下,在文件 fs/char_dev.c 中:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

void unregister_chrdev_region(dev_t from, unsigned count)

alloc_chrdev_region 用于申请设备号,参数解释如下:

dev                  用于存放申请到的设备号;

baseminor        次设备号的起始地址,alloc_chrdev_region函数可以申请一段连续的设备号,主设备号相同,但次设备号不同,次设备号以 baseminor 为起始地址开始递增;

count               要申请的设备号数量;

name                设备名字

unregister_chrdev_region 用于释放掉设备号,参数解释如下:

 form                     要释放的设备号;

count                    表示从 from 开始,要释放的设备号数量。


字符设备的注册与注销

老版本内核

        2.4版本之前的内核,注册是需要预先确定主设备号的,所以容易造成设备号冲突,而且会将一个主设备号下的所有次设备号都使用掉,浪费次设备号。

        老版本字符设备的注册和注销函数原型如下:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

static inline void unregister_chrdev(unsigned int major, const char *name)

register_chrdev 函数有三个参数,参数解释如下:

major            主设备号,内核中每个设备都有一个设备号,由主设备号和次设备号两部分,这里只传入主设备号;

name            指向字符串,表示这一系列设备的名称,一般就写设备名字;

fops             指向 file_operations 类型指针,与该设备相关的文件操作集合。

unregister_chrdev 函数有两个参数,参数解释如下:

major            主设备号;

name            设备名字。

新版本内核

确定设备号

        为了规避老版本内核在设备号冲突和浪费的问题,新内核的解决方法是在使用设备号的时候向内核申请,由内核来分配可以使用的设备号。也就是使用上文提到的 alloc_chrdev_region 函数动态分配设备号。

        新内核也支持静态设备号,如果给定了设备的主次设备号可以使用如下函数来注册给定的设备号。

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数解释如下:

from      要申请的起始设备号

count    要申请的连续设备号个数

name     设备名称        

        不管是通过 alloc_chrdev_region 函数分配的设备号,还是通过 register_chrdev_region 函数注册的指定设备号,在注销字符设备之后都要释放掉设备号,使用上文提到的 unregister_chrdev_region 函数释放设备号。

注册设备

        在确定了设备号之后,新版本内核就需要使用到 cdev 结构体来注册字符设备,cdev 定义在 include/linux/cdev.h 中,见下图。

        其中有两个重要的成员变量,ops 和 dev,ops 是字符设备文件操作函数集合,dev 是设备号。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备。

        定义好 cdev 变量之后需要使用 cdev_init 函数对其进行初始化,cdev_init 原型如下:

void cdev_init(struct cdev *, const struct file_operations *);

        调用 cdev_init 函数将cdev 变量与字符设备文件操作函数关联之后,需要使用 cdev_add 函数向内核添加字符设备,完成 cdev 变量与设备号的绑定,cdev_add 函数原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数 count为要添加的设备数量。

从内核中删除字符设备需要使用 cdev_del函数,函数原型如下

void cdev_del(struct cdev *p)

参数 p 就是要删除的字符设备。


添加LICENSE 和作者信息

        在编写的驱动中必须要添加 LICENSE 信息,作者可以选择性添加,使用如下两个函数添加:

MODULE_LICENSE()        //添加模块 LICENSE 信息

MODULE_AUTHOR()       //添加模块作者信息

LICENSE 一般写GPL协议,MODULE_LICENSE(“GPL”)


模块的加载和卸载

        驱模块的加载有两种方式,

        第一种是直接将驱动模块编译进内核中,这样当内核启动的时候就会自动运行驱动程序;

        第二种是将驱动模块编译成模块 .ko 文件,在内核启动之后使用相应命令加载驱动模块,这种方式的好处是不用重启内核就可以实现驱动模块的加载和卸载。

        有两种命令可以加载 .ko 文件,insmode 和 modprobe;

        insmode 命令不能解决模块的依赖问题,

        modprobe 命令会分析模块的依赖关系,然后会将所依赖的模块都加载到内核中,modprobe 命令默认会去 /lib/modules/<kernel-version>目录中查找模块,一般自己制作的 rootfs 是不会有这个目录的,所以需手动创建。

        模块的卸载使用 rmmod 命令,也可以使用 modprobe -r ***.ko 命令。区别在于,使用 modprobe -r 去卸载模块时也会卸载掉所依赖的其他模块。

        驱动程序中,模块的加载和卸载函数如下

module_init(xxx_init);    //注册模块加载函数

module_exit(xxx_exit);   //注册模块卸载函数

        这里需要注意,驱动程序中,模块的加载函数要使用 __init 来修饰,模块的卸载函数要使用 __exit 来修饰 。


代码举例

#include "linux/types.h"
#include "linux/init.h"
#include "linux/module.h"
#include "linux/ide.h"
#include "linux/fs.h"
#include "linux/kdev_t.h"
#include "linux/cdev.h"

static dev_t g_DeviceID;
static struct cdev  g_Cdev;

static int chrdevTest_open(struct inode *pInode, struct file *pFile)
{
    return 0;
}

static ssize_t chrdevTest_read(struct file *pFile, char __user *pBuf, size_t cnt, loff_t *tLoff)
{
    return 0;
}

static ssize_t chrdevTest_write(struct file *pFile, const char __user *pData, size_t cnt, loff_t *tLoff)
{
    return 0;
}

static int chrdevTest_release(struct inode *pInode, struct file *pFile)
{
    return 0;
}

static struct file_operations chrdevTest_fops = 
{
    .owner      = THIS_MODULE,
    .open       = chrdevTest_open,
    .read       = chrdevTest_read,
    .write      = chrdevTest_write,
    .release    = chrdevTest_release,
};

static int __init chrdevTest_init(void)
{
    int retVal = 0;


    retVal = alloc_chrdev_region(&g_DeviceID, 0, 1, "chrdevTest");
 
    if(retVal < 0)
    {
        printk("alloc Device ID failed, value:%d\r\n", retVal);
        return 1;
    }
    else
    {
        printk("alloc Device ID success, major:%d minor:%d\r\n", MAJOR(g_DeviceID), MINOR(g_DeviceID));
    }

    cdev_init(&g_Cdev, &chrdevTest_fops);
    retVal = cdev_add(&g_Cdev, g_DeviceID, 1);

    if(retVal < 0)
    {
        printk("cdev add failed, retval:%d\r\n", retVal);
    } 
    else
    {
        printk("cdev add success\r\n");
    }

    return 0;
}

static void __exit chrdevTest_exit(void)
{
    cdev_del(&g_Cdev);
    unregister_chrdev_region(g_DeviceID, 1);
    printk("unregister\r\n");
}

module_init(chrdevTest_init);
module_exit(chrdevTest_exit);

MODULE_LICENSE("GPL");

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

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

相关文章

如何关闭tomcat?tomcat端口号被占用怎么办

我tomcat一跑就报被占用怎么办&#xff1f;我没开tomcat呀&#xff01;&#xff01; 这种情况一般是你上一次打开tomcat没有关tomcat服务就关闭了变成软件&#xff08;如强行关闭正在运行tomcat的idea&#xff09;&#xff0c;这样你在开tomcat就会显示端口号占用了&#xff0…

API 渗透测试从入门到精通系列文章(上)

导语&#xff1a;这是关于使用 Postman 进行渗透测试系列文章的第一部分。 这是关于使用 Postman 进行渗透测试系列文章的第一部分。我原本计划只发布一篇文章&#xff0c;但最后发现内容太多了&#xff0c;如果不把它分成几个部分的话&#xff0c;很可能会让读者不知所措。 所…

SMOKE Single-Stage Monocular 3D Object Detection via Keypoint Estimation 论文学习

论文地址&#xff1a;SMOKE: Single-Stage Monocular 3D Object Detection via Keypoint Estimation Github 地址&#xff1a;https://github.com/open-mmlab/mmdetection3d/tree/main/configs/smoke 1. 解决了什么问题&#xff1f; 预测物体的 3D 朝向角和平移距离对于自动驾…

hive之入门配置

学习hive之路就此开启啦&#xff0c;让我们共同努力 目录 Hive网站&#xff1a; Hive的安装部署&#xff1a; 启动并使用Hive&#xff1a; 安装Mysql: 安装Mysql依赖包&#xff1a; 启动Mysql: 查看密码&#xff1a; 登录root: 密码错误报错&#xff1a; 元数据库配置…

信创国产中间件概览

信创国产中间件概览 中间件国内中间件市场份额第一梯队仍然是IBM> 和Oracle&#xff0c;市场份额合计51%。第二梯队为五大国产厂商&#xff0c;包括东方通、普元信息、宝兰德、中创中间件、金蝶天燕&#xff0c;市场份额合计15%。东方通应用服务器TongWeb对标 开源&#xf…

人脸检测和行人检测3:Android实现人脸检测和行人检测检测(含源码,可实时检测)

人脸检测和行人检测3&#xff1a;Android实现人脸检测和行人检测检测(含源码&#xff0c;可实时检测) 目录 人脸检测和行人检测3&#xff1a;Android实现人脸检测和行人检测(含源码&#xff0c;可实时检测) 1. 前言 2. 人脸检测和行人检测数据集说明 3. 基于YOLOv5的人脸检…

Databend 开源周报第 91 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 新数据类型&…

【Robot Framework】RF关键字大全

收录工作当中最常用的Robot Framework关键字 内容较多&#xff0c;可以CtrlF快速搜索自己想要的 1. RF循环使用&#xff08;FOR循环&#xff09; {list1} create list LOG TXT INI INF C CPP JAVA JS CSS LRC H ASM S ASP FOR ${file_type} IN {list1} log 构造请求参数 ${t…

第二十二章 解释器模式

文章目录 前言一、解释器模式基本介绍解释器模式的原理类图 二、通过解释器模式来实现四则运算完整代码抽象表达式类 Expression变量表达式类 VarExpression抽象运算符号解析器 SymbolExpression加法解释器 AddExpression减法解释器 SubExpression计算器类 CalculatorClint 测试…

【C++】仅需一文速通继承

文章目录 1.继承的概念及定义继承的概念继承的定义定义格式:继承关系和访问限定符继承基类成员访问方式的变化 2.基类和派生类对象赋值转换3.继承中的作用域4.派生类的默认成员函数题目:设计出一个类A,让这个类不能被继承(继承了也没用) 5.继承与友元6.继承与静态成员7.复杂的菱…

VK Cup 2017 - Round 1 A - Bear and Friendship Condition(并查集维护大小 + dfs 遍历图统计边数)

题目大意&#xff1a; 给你一些n个点m条边&#xff0c;如果三个点&#xff08;a,b,c&#xff09;是合法的&#xff0c;当且仅当 a-b,b-c,c-a都有一条边&#xff0c;问你这个图是否合法&#xff0c;如果有一个或两个点视为合法 思路 考虑什么图才是个合法图&#xff1a;除了点…

Spring 更简单的读取和存储对象

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 前面介绍了通过配置文件的方式来存储 Bean 对象&#xff0c;那么有没有更简单的方式去存储 Bean 对象&#xff1f; 有以下 2 种方…

【论文】LearningDepth from Single Monocular Images

2005 NIPS 文章目录 特征提取卷积核的使用Multiscale 多尺度提取特征特征的相对深度 模型结论特征提取数据集导致的error 文章使用了Markov 随机场(Markov Random Fields, MRF) 从单图像上直接估计出图像的深度信息。 与RGBD输入数据不同的是&#xff0c;文章中采用了YCbCr数据…

知识点总结-DAY1

1. 请解释OSI模型中每一层的作用 应用层&#xff1a;为用户提供服务&#xff0c;处理应用程序之间交换的数据。 表示层&#xff1a;处理数据在网络上的表示形式&#xff0c;如加密和解密、压缩和解压缩等。 会话层&#xff1a;建立、维护和终止两个节点之间的会话&#xff0c…

安全防御 --- IPSec理论

IPSec 1、概述&#xff1a; 是IETF&#xff08;Internet Engineering Task Force&#xff09;制定的一组开放的网络安全协议&#xff0c;在IP层通过数据来源认证、数据加密、数据完整性和抗重放功能来保证通信双方Internet上传输数据的安全性。 IPSec安全服务 机密性完整性…

雨季时,骑行经过泥泞路段该怎么办?

泥泞路段骑行是一项需要技巧和勇气的挑战。在泥泞路段骑行&#xff0c;骑友又叫玩泥巴&#xff0c;不仅需要良好的车技和身体素质&#xff0c;还需要有足够的经验和判断力&#xff0c;以应对各种突发情况。下面&#xff0c;将从多个角度介绍泥泞路段骑行的挑战和技巧&#xff0…

宏观经济笔记--社会消费品零售总额

我们讨论了GDP的三个分项&#xff1a;投资、消费、净出口。投资我们前面已经介绍了&#xff0c;消费这一个分项我们还一直没有讨论。消费最重要的数据是每个月月中统计局公布的社会消费品零售总额。 一般的论调中&#xff0c;认为消费是三个GDP驱动项中最健康的一项&#xff0…

2023-5-4-Lua语言学习

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

openQA----基于openQA新增指定版本的openSUSE的iso镜像进行测试

【原文链接】openQA----基于openQA新增指定版本的openSUSE的iso镜像进行测试 &#xff08;1&#xff09;执行如下命令下载openSUSE的测试脚本&#xff0c;它会从openSUSE的测试脚本github地址 /usr/share/openqa/script/fetchneedles&#xff08;2&#xff09;然后执行如下命…

在 SourceTree 中使用 rebase (win10)

原始状态 创建两个分支 dev1 dev2, 并且推送到远端 切换到dev1 做一些修改并提交dev1-1&#xff0c;注意不要推送到到远端 切换到master分支&#xff0c;拉取最新的代码 切换到dev1 分支&#xff0c;进行变基操作&#xff0c;右击master分支 推送dev1分支到远端 切换到master分…