驱动开发-字符设备的内部实现

2023-09-13 21:01:05

1、字符设备驱动内部的注册过程

对register_chrdev内部的实现过程分析,注册字符驱动的过程有以下几步

1、分配struct cdev对象空间

2、初始化struct cdev对象

3、注册cdev对象

以上三步完成了字符设备驱动的注册

2、struct cdev结构体分析

只要有一个驱动存在于系统内核中,就会存在一个struct cdev对象,对象中是关于当前驱动的相关驱动的相关描述信息

struct cdev {
    struct kobject kobj;//基类对象
    struct module *owner;//模块对象指针  THIS_MODULE
    const struct file_operations *ops;//操作方法结构体指针
    struct list_head list;//用于构成链表的成员
    dev_t dev;//第一个设备号  
    unsigned int count;//设备资源数量
};

3、注册字符设备驱动分步实现相关API分析

1.分配 字符设备驱动对象
a.struct cdev cdev;
b.struct cdev *cdev = cdev_alloc();
    /*
        struct cdev *cdev_alloc(void)
        功能:申请一个字符设备驱动对象空间
        参数:无
        返回值:成功返回申请的空间首地址
        失败返回NULL
    */
 2.字符设备驱动对象初始化
 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
 功能:实现字符设备驱动的部分初始化
 参数:
     cdev:字符设备驱动对象指针
     fops:操作方法结构体指针
返回值:无
3.设备号的申请
3.1 静态指定设备号
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:静态申请设备号并注册一定数量的设备资源
    参数:
        from:静态指定的设备号(第一个设备的设备号)
        count:申请的设备数量
        name:设备名或者驱动名
    返回值:成功返回0,失败返回错误码
3.2 动态申请设备号
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
    功能:动态申请设备号并注册一定数量的设备资源
    参数:
        dev:存放申请的到的设备号的空间首地址
        baseminor:次设备号的起始值
        count:申请的设备资源数量
        name:设备名或者驱动名
     返回值:成功返回0,失败返回错误码   
 4.根据申请的设备号和驱动对象注册驱动
 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
 功能:注册字符设备驱动对象
 参数:
     cdev:字符设备驱动对象指针
     dev:申请的设备号的第一个值
 count:申请的设备资源的数量
返回值:成功返回0,失败返回错误码
    
***********注销过程*****************
1.注销驱动对象
void cdev_del(struct cdev *p)
参数:
    p:要注销的对象空间指针
返回值:无

2.释放申请的设备号和设备资源
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
    from:申请的第一个设备号
    count:申请的设备资源的数量
返回值:无

释放字符设备驱动对象空间
3.void kfree(void *addr)
功能:释放申请的内核空间
参数:要释放的空间首地址
返回值:无

需要注意的是,每个步骤失败后销毁每个步骤前上一个步骤申请成功的资源

out5:
    //释放前一次提交成功的设备信息
    for(--i;i>=0;i--)
    {
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);//释放目录
out4:
    cdev_del(cdev);
out3:
    unregister_chrdev_region(MKDEV(major,minor),3);
out2:
    kfree(cdev);
out1:
    return ret;

4、struct inode结构体的作用

        只要文件存在于操作系统上,那么在系统内核中就一定会存在一个struct inode结构体对象用来描述当前文件的相关信息

4.1inode结构体内容

struct inode {
    umode_t            i_mode;//文件的权限
    unsigned short        i_opflags;
    kuid_t            i_uid;//文件的用户ID
    kgid_t            i_gid;//组ID
    unsigned int        i_flags;
    dev_t            i_rdev;//设备号
    union {
        
        struct block_device    *i_bdev;//块设备
        struct cdev        *i_cdev;//字符设备
        char            *i_link;
        unsigned        i_dir_seq;
    };

4.2 open函数回调驱动中操作方法open的路线

5、struct file结构体相关内容

open函数的第一个参数是文件路径,可以进而找到inode对象,从而回调到驱动的方法,但是read()、write()这些函数操作对象不是文件路径,而是文件描述符,使用如何通过文件描述符回调到驱动的操作方法了

5.1、文件描述符是什么

    文件描述符是一个进程里面打开文件时得到的一个非负整数,一个进程最多可以有1024个文件描述符,不同进程的文件描述符是独立的,文件描述符依赖于进程存在,要明白文件描述符在进程中的作用

5.2、struct task_struct结构体

只要一个进程存在于操作系统上,系统内核中一定会存在一个struct task_struct结构体对应保存进程的相关信息

struct task_struct {
    volatile long            state;//进程状态
    int             on_cpu;//表示进程在哪个CPU上执行
    int                prio;//进程优先级
    pid_t                pid;//进程号
    struct task_struct __rcu    *real_parent;//父进程
    struct files_struct        *files;//打开的文件相关结构体

};
struct files_struct {
     
        struct file __rcu * fd_array[NR_OPEN_DEFAULT];//结构体指针数组
        };
  fd_array是一个指针数组,数组中每一个成员都指向一个struct file类型的对象,而数组的下标就是我们常说的文件描述符

struct file 结构体分析

struct file {
  struct path        f_path;//文件路径
    struct inode        *f_inode;    /* cached value */
    const struct file_operations    *f_op;//操作方法结构体
    unsigned int         f_flags;//open函数的第二个参数赋值给f_flags
    fmode_t            f_mode;//打开的文件的权限
    void            *private_data;//私有数据,可以实现函数件数据的传递

        };

6、linux内核中竞态产生原因

表面原因:

多个进程同时访问同一个驱动资源,就会出现对资源争抢的情况

本质原因:

单核处理器:如果支持资源抢占,就会出现竞态

多核处理器:核与核之间权限一样,本身就会出现资源抢占问题

中断和进程,会出现竞态

中断和中断:如果中断控制器支持中断嵌套,则会出现竞态,否则不会。ARM芯片使用的中断控制器是GIC,不支持中断嵌套

7、竞态的解决方法

7.1 中断屏蔽(不太常用)

中断屏蔽是针对于单核处理器实现的竞态解决方案,如果进程想要访问临界资源,可以在访问资源之前先将中断屏蔽掉,当进程访问临界资源结束后在恢复中断的使能。一般屏蔽中断的时间要尽可能短,长时间屏蔽中断可能会导致用户数据的丢失甚至内核的崩溃。一般中断屏蔽仅仅留给内核开发者测试使用。

local_irq_disable()//中断屏蔽
临界资源
local_irq_enable()//取消中断屏蔽

7.2自旋锁

自旋锁:一个进程想要访问临界资源,首先要获取自选锁,如果获取自旋锁成功,就访问临界资源,如果失败,进程就会进入自旋状态,自旋锁又称为盲等锁

其特点:

自旋状态下进程处于运行状态,会耗费CPU的资源

自旋锁保护的临界资源尽可能的小,临界区中不能有延时、耗时、休眠等操作,也不能有copy_to_user和copy_from_user

自旋锁会出现死锁状态

自旋锁可以用于进程的上下文,也可以用于中断的上下文

自旋锁使用时会关闭抢占   ,尽量保证上锁的时间短

1.定义自旋锁
spinlock_t lock;
2.初始化自旋锁
spin_lock_init(&lock);
3.上锁(获取锁)
void spin_lock(spinlock_t *lock)
4.解锁(释放锁)
void spin_unlock(spinlock_t *lock)

7.3信号量

信号量概述:一个进程想要访问临界资源,先要获取信号量,如果没有获取到,进程进入休眠状态

特点:

休眠状态下的进程不会消耗CPU的资源,进程状态的切换需要消耗CPU的资源

信号量的临界区可以很大,也可以很小,可以有延时,耗时,休眠等操作

信号量不会出现死锁

信号量只能用于进程的上下文切换

信号量不会关闭抢占

API

1.定义一个信号量
struct semaphore sema;
2.初始化信号量
void sema_init(struct semaphore *sem, int val)
参数:
sem:信号量指针
val:给信号量的初始值
3.获取信号量(上锁)
void down(struct semaphore *sem)//信号量数值-1
4.释放信号量(解锁)
void up(struct semaphore *sem);

7.4互斥体

互斥体:

一个进程想要访问临界资源需要先获取互斥体,如果获取不到,系统会切换到休眠状态

特点:

  • 获取不到互斥体的进程会切换到休眠状态休眠状态下的进程不消耗CPU的资源,进程状态的切换需要消耗CPU资源
  • 互斥体保护的临界区可以很大,也可以有延时、耗时、休眠的操作
  • 互斥体不会出现死锁
  • 互斥体只能用于进程上下文
  • 互斥体不会关闭抢占
  • 获取不到互斥体的进程不会立即进入休眠状态,而是稍微等一会儿,互斥体的效率要比信号量更高
  • API
    1.定义互斥体
    struct mutex mutex;
    2.初始化互斥体
    mutex_init(&mutex);
    3.上锁
    void  mutex_lock(struct mutex *lock)
    4.解锁
    void  mutex_unlock(struct mutex *lock)

更多推荐

区块链:去中心化革命下的创新与发展!

区块链作为一项重要的技术实验,确实具有重大的影响力。它代表了一场去中心化的运动,吸引了许多研究人员、工程师、建设者和用户的参与,创造了我们目前所见到的一些最有趣的技术。区块链的透明特性赋予了用户对于数据和交易的控制权,保护了用户的匿名性,同时也提供了交易结算状态和服务运作的清晰度。用户可以选择参与区块链网络,并获得相应

2023年电赛---运动目标控制与自动追踪系统(E题)OpenMV方案

如果有嵌入式企业需要招聘校园大使,湖南区域的日常实习,任何区域的暑假Linux驱动实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效前言(1)废话少说,很多人可能无法访问GitHub,所以我直接贴出可能要用的代码。此博客还会进行更新,先贴教程和代码(2)<

14:00面试,14:06就出来了,问的问题有点变态。。。

从小厂出来,没想到在另一家公司又寄了。到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到5月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%,这下搞的饭都吃不起了。还在有个朋友内推我去了一家互联网公司,兴冲冲见面试官,没想到一道题把我给问死了:如果模块请求http改为了h

消息队列——rabbitmq的不同工作模式

目录Workqueues工作队列模式Pub/Sub订阅模式Routing路由模式Topics通配符模式工作模式总结Workqueues工作队列模式C1和C2属于竞争关系,一个消息只有一个消费者可以取到。代码部分只需要用两个消费者进程监听同一个队里即可。两个消费者呈现竞争关系。用一个生产者推送10条消息for(inti=

什么是线程?为什么需要线程?和进程的区别?

目录前言一.线程是什么?1.1.为什么需要线程1.2线程的概念1.3线程和进程的区别二.线程的生命周期三.认识多线程总结🎁个人主页:tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主🎥本文由tq02原创,首发于CSDN🙉🎄本章讲解内容:线程的讲解🎥学习专栏:C语言JavaSEMySQL基

2023年电赛---运动目标控制与自动追踪系统(E题)OpenART mini的代码移植到OpenMV

如果有嵌入式企业需要招聘校园大使,湖南区域的日常实习,任何区域的暑假Linux驱动实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效前言(1)已经有不少同学根据我上一篇博客完成了前三问,恭喜恭喜。有很多同学卡在了第四问。(2)我说了OpenARTmini的

React、Vue框架如何实现组件更新,原理是什么?

引言React和Vue都是当今最流行的前端框架,它们都实现了组件化开发模式。为了优化性能,两者都采用了虚拟DOM技术。当组件状态发生改变时,它们会使用虚拟DOM进行局部渲染比对,只更新必要的DOM节点,从而避免重新渲染整个组件树。本文将从React和Vue的组件更新原理入手,剖析两者虚拟DOMdifer算法的异同点。R

C语言指针详解

C语言指针详解字符指针1.如何定义2.类型和指向的内容3.代码例子指针数组1.如何定义2.类型和内容数组指针1.如何定义2.类型和指向类型3.数组名vs&数组名数组指针运用数组参数&指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指针1.如何定义2.类型和指向内容3.函数名vs&函数名4.两个有趣的代码函数

2023 电赛 E 题 K210 方案

第一章:K210介绍K210芯片是一款基于RISC-V架构的嵌入式人工智能芯片,具备低功耗、高性能的特点。它拥有强大的图像处理和机器学习能力,适用于边缘计算设备和物联网应用。为了方便开发者,K210芯片提供了丰富的外设接口,包括摄像头接口、显示接口、WiFi、蓝牙等,同时支持多种编程语言和开发环境,如MicroPyth

天气API强势对接

🤵‍♂️个人主页:@香菜的个人主页,加ischongxin,备注csdn✍🏻作者简介:csdn认证博客专家,游戏开发领域优质创作者,华为云享专家,2021年度华为云年度十佳博主🐋希望大家多多支持,我们一起进步!😄如果文章对你有帮助的话,欢迎评论💬点赞👍🏻收藏📂加关注+目录前言:墨迹天气数据接口1、找到目

【E题】2023年电赛运动目标控制与自动追踪系统方案

系统的设计和制作可以按照以下步骤进行:设计红色光斑位置控制系统:选择合适的红色激光笔,并将其固定在一个二维电控云台上。使用电机和编码器来控制电控云台的水平和垂直运动。设计一个控制电路,可以通过输入控制信号来控制电机的运动,从而控制红色光斑的位置。确保控制电路可以接收来自用户输入的目标位置信息,并将其转换为相应的电机控制

热文推荐