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

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的路线

驱动开发-字符设备的内部实现_第1张图片

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)

你可能感兴趣的:(驱动开发)