驱动第四天

驱动第四天
【file_operations和cdev的区别和联系】
cdev是用来描述字符设备的,而file_operations是用来描述字符设备的操作的。本来cdev是可以直接用来描述字符设备的所有情况的,但是由于在C语言中,结构体是不能有函数的,即字符设备的操作。所以,我们把这个字符设备的操作单独拿出来,装在一个叫做file_operations结构体中,结构体中装的是所有应用程序要操作的函数指针,用来描述字符设备的动态特征。所以,实际上,file_operations可以认为是cdev的一个成员,而cdev只是描述字符设备的静态特征。而我们想要完整的描述一个字符设备,就必须要描述其静态特征和动态特征,即cdev和file_operations一起描述这个字符设备驱动。
不使用全局变量和静态变量,则该代码一定是可重入代码!
【原子操作】
其根本就是解决v->count的竞态问题,其原理,如果程序能在一条指令之内完成,就直接实现,因为指令在执行阶段是不能被打断的,属于临界资源。如果程序不能在一条指令之内完成,则在执行程序之前,要先关闭中断,屏蔽掉所有的中断(关闭中断的时间不能太长,否则系统就会崩溃),这样,该程序就会变成可重入程序,就不会被打断。然后的测试实现,就是通过P、V操作保护共享资源。
[1] 让mdev或udev程序自动创建设备文件节点
    1. 创建设备类(用已经有的设备类)
    2. 导出设备信息到sysfs(用户空间)
       字符设备.txt
       
[2] 避免代码成为独占共享资源(避免代码使用的竞态)
    写程序的时候,程序必须可重入, 对C语言来说只要每个函数尽可能做到单输入(只能从参数输入,最好没有全局变量和静态局部变量)
    单输入出(只能从返回值和参数输出)
   
[3] 并发
    多个任务同时执行(宏观)叫并发
    程序执行的方式:
   

    操作系统中的并发:
   

[4] 竞态
    并发执行的任务竞争使用独占的共享资源(使用的任务个数受到限制)的状态叫竞态
    比如:
    10台打印机,10以下的人用,不会产生竞态,如果10个以上的人用产生竞态
   
[5] 竞态的解决
    1. 避免产生竞态
       程序--可重入--程序的每个函数尽量可能做到单输入单输入出(所有的输入来自于函数的参数,函数的输出都是通过返回值来传递)
       
    2. 解决竞态
     
[6] 操作系统中的并发
    1. 多cpu执行的程序并发
    2. 普通程序和中断程序从宏观来说是并发
    3. 单cpu,操作系统采用可抢占(进程1的优先级高,进程2如果正在运行的话)从宏观上看是并发(多CPU)
   
【并发和竞态】
[1] 并发
    多个程序同时(宏观)执行
   
[2] 竞态
    并发的程序可能同时竞争适用独占的共享资源的状态
   
[3] 操作系统的并发
    1. 多cpu同时执行多线程
    2. 单cpu上,中断程序和普通程序之间的并发
    3. 单cpu上,普通程序之间的并发(抢占--类似多cpu的情况)
   
[4] 解决竞态的方法
    1. 避免竞态
       在写代码时,避免代码本身成为独占共享资源的方法是"使得代码可重入",想使代码可重入,最好的方法是子程序具有单入口和单出口
       
    2. 解决竞态
   
[5] 中断
    1. 原理
       消除单cpu上的并发
       
    2. 适用
       1. 只能解决单cpu上的竞态
       2. 关闭中断的时间不能太长(时钟中断是操作系统的基准时间,关闭中断其实就关闭调度)
       
    3. 使用方法
       #include <linux/irqflags.h>
       
       local_irq_disable(); // 关闭中断
       ... // 临界资源(临界区:访问独占共享资源的代码,临界区访问的资源叫临界资源)
       local_irq_enable();
       
       // 中断操作最好成对出现,这一种用到的远多于上一中
       local_irq_save(flags); // 保存当前中断状态,并关闭中断
       ...
       local_irq_restore(flags); // 恢复中断状态
       
[6] 原子操作
    1. 原理
       linux系统中的原子操作保护的共享资源仅仅是一个整数
   
    2. 适用
       1. 自旋锁可以基于原子操作来实现
       2. 适用于系统中独占资源为整数的情况
       
    3. 使用
       #include <asm/atomic.h>
             
        // v->counter = i
       void atomic_set(atomic_t *v, int i);
       
       // v->counter = 0
       atomic_t v = ATOMIC_INIT(0);
       
        // retun v->counter
        atomic_read(atomic_t *v);
   
       // v->counter += i;
       void atomic_add(int i, atomic_t *v);
       // v->counter -=i
       void atomic_sub(int i, atomic_t *v);
       
       // v->counter++
       void atomic_inc(atomic_t *v);
       
       // v->counter--
       void atomic_dec(atomic_t *v);
       // (++v->counter) == 0
       int atomic_inc_and_test(atomic_t *v);
       
       // (--v->counter) == 0
       int atomic_dec_and_test(atomic_t *v);
       
       // (v->counter - i) == 0
       int atomic_sub_and_test(int i, atomic_t *v);
 
       // return (v->counter + i)
       int atomic_add_return(int i, atomic_t *v);
       
       // return (v->counter - i)
       int atomic_sub_return(int i, atomic_t *v);
       
       // return ++v->counter;
       int atomic_inc_return(atomic_t *v);
       
       // return --v->counter
       int atomic_dec_return(atomic_t *v);
       // addr[nr] = 1
       void set_bit(nr, void *addr);
       // addr[nr] = 0
       void clear_bit(nr, void *addr);
       
       // ~addr[nr]
       void change_bit(nr, void *addr);
       
       // 检测第nr位是否为1 (拿到内核代码最好确认一下)
       test_bit(nr, void *addr);
       // 检测第nr位是否为1,然后设置第nr位为1,如果来的nr位1,则返回真,否则假
       int test_and_set_bit(nr, void *addr);
       
       // 检测第nr位是否为1,然后清除第nr位为0,如果来的nr位1,则返回真,否则假
       int test_and_clear_bit(nr, void *addr);
       
       // 检测第nr位是否为1,然后改变第nr位为0,如果来的nr位1,则返回真,否则假
       int test_and_change_bit(nr, void *addr);
       
       原理:解决v->counter的竞态访问(防止v->counter成为共享资源)
       实现原理:通过关闭中断实现,也有可能通过一条指令来实现
       
       例:防止多次打开文件
           static atomic_t xxx_available = ATOMIC_INIT(1); /*定义原子变量*/
           
           static int xxx_open(struct inode *inode, struct file *filp)
           {
               ...
               if (!atomic_dec_and_test(&xxx_available)) {
                   atomic_inc(&xxx_available);
                   return - EBUSY; /*已经打开*/
               }
               ...
               return 0; /* 成功 */
               
               功能等价:
               if (!((--xxx_available->counter) == 0)) {
                   xxx_available->counter++;
                   return - EBUSY; /*已经打开*/
               }
               ...
               return 0; /* 成功 */
               
               错误的情况:
               A进程 B进程
               --xxx_available->counter
               切换到进程B
                                               --xxx_available->counter
                                               切换回A进程
               xxx_available->counter == 0?
               !(-1 == 0)会产生没有人能打开文件
           }
           
           static int xxx_release(struct inode *inode, struct file *filp)
           {
               atomic_inc(&xxx_available); /* 释放设备 */
               return 0;
           }


【学习总结】

/***********************************信号量相关操******************************************/

/*

 * 注意:信号量本质上就是一个整数,获取信号量,如果信号量的值大于0,则进程继续,如果信号量为0或者更小,进程必须等待直到其他人释放该信号量,信号量必须对系统其它部分可用前被初始化,如果在拥有一个信号量时发送错误,必须在将错误状态返回之前释放该信号量,否则这段临界区资源将永远不能再次被访问,信号量必须在设备对系统其它部分可用前被初始化

 */

#include <linux/semaphire.h>

struct semaphore sem; //定义一个信号量

void sema_init(struct semaphore *sem int val); //创建一个信号量并初始化为val

DECLARE_MUTEX(name); //初始化信号量为1

DECLARE_MUTEX_LOCKED(name); //初始化信号量为0

void init_MUTEX(struct semaphore *sem) //运行时动态初始化为1

void init_MUTEX_LOCKED(struct semaphore *sem) //运行时动态初始化为0

void down(struct semaphore *sem); //获取信号量,不可中断,如果获取不到则进入睡眠

int down_interruptible(struct semaphore *sem);

//获取信号量,可被中断,如果被中断该函数返回一个非0值,而调用者不会拥有该信号量

//用法如下:

if(down_interruptible(struct semaphore *sem))

 return -ERESTARTSYS;

 

int down_trylock(struct semaphore *sem);

//获取信号量,但永远不会睡眠,如果信号量不可获得,则立刻返回一个非零值

void up(struct semaphore *sem); //释放该信号量,唤醒等待者

/**********************************end***********************************************/

 

/***************************读取者/写入者信号量*****************************************/

/*

 *注意:其允许多个进程可以并发读取,但只允许一个进程写入,一个rwsem可允许一个写入者或无限多个读取者拥有该信号量,写    入这具有更高的优先级,当某个给定的写入访问试图进入临界区时,在所有写入者完成其工作前,不会允许读取者访问,在驱动程序中使用它的机会相对较少

 */

#include <linux/rwsem.h>

struct rw_semaphore rwsem; //定义读写信号量

void init_rwsem(struct rw_semaphore *sem); //初始化读写信号量

对于只读访问可使用下面的函数:

void down_read(struct rw_semaphore *sem);

//它可以和其它读取者并发地访问,并且可能将调用进程置于不可中断的睡眠

int down_read_trylock(struct rw_semaphore *sem);

 //它不会在读取访问不可获得时等待,它在授以访问时返回非,其它情况返回0

void up_read(struct rw_semaphore *sem); //释放读取信号量

对于写入访问可使用下面的函数:

void down_write(struct rw_semaphore *sem);

int down_write_trylock(struct rw_semaphore *sem);

void up_read(struct rw_semaphore *sem); //释放写入信号量

void downgrade_write(struct rw_semaphore *sem);

//当某个快速改变获得了写入锁,而其后是更长时间的只读访问的话,我们可以再结束修改后调用它来允许其它读取者访问

/**********************************end*****************************************************/

/*********************************互斥体****************************************************/

struct mutex my_mutex; //定义互斥体

mutex_init(&my_mutex); //初始化互斥体

获取互斥体:

mutex_lock(struct mutex *lock);

mutex_lock_interruptible(struct mutex *lock); //可中断

mutex_trylock(struct mutex *lock); //尝试获取,如果获取不到就立刻返回不会休眠

释放互斥体:

mutex_unlock(struct mutex *lock);

/******************************end**********************************************/

/************************完成量(completion**************************************/

/*注意:完成量主要用于进程的同步,它允许一个线程告诉另一个线程某个工作已经完成*/

#include <linux/completion.h>

struct completion completion;

DECLARE_COMPLETION(completion); //创建完成量

init_completion(&completion);

void wait_for_completion(struct completion *c); //等待其他进程的完成量,该函数执行一个非中断的等待

void completion(struct completion *c); //进程完成了它的工作,可调用它来告诉其他进程(调用wait_for_completion)其工作已经完成,其只唤醒一个等待的进程

void completion_all(struct completion *c);//其唤醒所有等待的进程

/********************************end***************************************************/

/*******************************自旋锁**************************************************/

/*

 *注意:和信号量不同,自旋锁可在不能休眠的代码中使用(比如中断例程),其在获取不到自旋锁时,调用者在自旋(代码进入忙循环并重复检查这个锁,直到该锁可用为止),所有的自旋锁都是不可中断的,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止,因为它不可睡眠的

 *

 * 一个自旋锁是一个互斥设备,它只能有两个值:锁定和解锁。它通常实现为某个整数值中某一位,希望获得某特定锁的代码测试相关位,如果锁可用,则锁定位被设置,然后代码继续进入临界区,相反,如果被其他人获得,则代码进入忙并重复检查这个锁,直到该锁可用为止,当存在自旋锁时,等待执行忙循环的处理器做不了任何事情

 *

 * 想象一下你的驱动请求了一个自旋锁并且在它的临界区里做它的事情在中间某处你的驱动失去了处理器或许它已调用了一个函数( copy_from_user,假设使进程进入睡眠或者也许内核抢占发威一个更高优先级的进程将你的代码推到一边你的代码现在持有一个锁在可见的将来的时间不会释放这个锁如果某个别的线程想获得同一个锁它会在最好的情况下,等待在处理器中自旋 )很长时间最坏的情况系统可能完全死锁.所以适用于自旋锁的核心原则是:任何拥有自旋锁的代码都必须是原子的,它不能休眠,事实上,它不能因为任何原因放弃处理器除了服务中断以外(某些情况下,此时也不能放弃处理器),内核抢占的情况由自旋锁本身处理,任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止,而休眠时不可预期的,当我们编写需要在自旋锁下执行的代码时,必须注意每一个所调用的函数

 *

 * 另外当我们的代码拥有锁在访问临界资源时产生了中断,中断服务程序也要获取锁才能访问设备资源,这时处理器将永远自旋下去,这时,我们需要在拥有自旋锁时禁止中断,这就使得我们的自旋锁必须在尽可能短的时间内拥有

 *

 * 如果每个获得锁的函数要调用其他试图获取这个锁的函数,我们的代码会死锁,无论信号量还是锁,都不允许锁拥有者第二次获得这个锁

 */

#include <linux/spinlock.h>

spinlock_t splock=SPIN_LOCK_UNLOCKED; //初始化自旋锁

void spin_lock_init(spinlock_t *lock);

void spin_lock(spinlock_t *lock); //进入临界区前,代码必须调用它以获取锁,不可中断

void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

//spin_lock_irqsave会在获取锁之前禁止中断,而先前的中断状态保存在flags

void spin_lock_irq(spinlock_t *lock);

void spin_lock_bh(spinlock_t *lock);//其在获取锁之前禁止软中断,但是会让硬件中断保持打开

void spin_unlock(spinlock_t *lock); //释放锁

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);

void spin_unlock_irq(spinlock_t *lock);

void spin_unlock_bh(spinlock_t *lock);

//非阻塞自旋锁:这两个函数在成功时返回非零值,否则返回0

int spin_trylock(spinlock_t *lock);

int spin_trylock_bh(spinlock_t *lock);

/********************************end***************************************************/

 

/**************************读取者/写入者自旋锁******************************************/

//注意:其与读取者/写入者信号量类似

#include <linux/spinlock.h>

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; //声明及其初始化

rwlock_t my_rwlock;

rwlock_init(&my_rwlock); /* Dynamic way */

//对于读取者,获取锁:

void read_lock(rwlock_t *lock);

void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_lock_irq(rwlock_t *lock);

void read_lock_bh(rwlock_t *lock);

//对于读取者,释放锁:

void read_unlock(rwlock_t *lock);

void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void read_unlock_irq(rwlock_t *lock);

void read_unlock_bh(rwlock_t *lock);

//对于写入者,获取锁:

void write_lock(rwlock_t *lock);

void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock);

void write_lock_bh(rwlock_t *lock);

int write_trylock(rwlock_t *lock);

//对于写入者,释放锁:

void write_unlock(rwlock_t *lock);

void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void write_unlock_irq(rwlock_t *lock);

void write_unlock_bh(rwlock_t *lock);

/***********************************end***********************************************/

 

/*******************************原子变量操作********************************************/

//注意:原子变量是整形数据,原子操作指的是在代码执行过程中不会被别的代码路径所中断的操作,即不会睡眠或中断,内核代码可以安全的调用它们而不会被打断

#include <asm/atomic.h>

void atomic_set(atomic_t *v, int i);

atomic_t v = ATOMIC_INIT(0); //可以是 ATOMIC_INIT(1)

//设置原子变量 v 为整数值 i. 你也可在编译时使用宏定义 ATOMIC_INIT初始化原子值.

int atomic_read(atomic_t *v);

//获取 v 的当前值.,返回原子变量的值

void atomic_add(int i, atomic_t *v);

// v 指向的原子变量加 i. 返回值是 void, 因为有一个额外的开销来返回新值并且大部分时间不需要知道它.

void atomic_sub(int i, atomic_t *v);

// *v 减去 i.

void atomic_inc(atomic_t *v);

void atomic_dec(atomic_t *v);

//自增或自减一个原子变量.

int atomic_inc_and_test(atomic_t *v);

int atomic_dec_and_test(atomic_t *v);

int atomic_sub_and_test(int i, atomic_t *v);

//进行一个特定的操作并且测试结果如果在操作后原子值是 0, 那么返回值是真否则它是假注意没有 atomic_add_and_test.

int atomic_add_negative(int i, atomic_t *v);

//加整数变量 i  v. 如果结果是负值返回值是真否则为假.

int atomic_add_return(int i, atomic_t *v);

int atomic_sub_return(int i, atomic_t *v);

int atomic_inc_return(atomic_t *v);

int atomic_dec_return(atomic_t *v);

//就像 atomic_add 和其类似函数除了它们返回原子变量的新值给调用者.

一般用法:

static atomic_t v = ATOMIC_INIT(1);

if(!atomic_dec_and_test(&v))

//第一个进程执行时返回0也即为真,取非后为假,所以跳过这段语句继续执行,其他进程只能执行到此就返回了

  {

   atomic_inc(&v);

   return xxx;

   }

 …… ……

 atomic_inc(&v);//释放

/*********************************end***********************************************/

 

/******************************位原子操作*********************************************/

//注意:这些函数可用来访问和修改一个共享标志

#include <asm/bitops.h>

void set_bit(nr, void *addr);

//设置在 addr 指向的数据项中的第 nr 位为1

void clear_bit(nr, void *addr);

//清除在 addr 指向的数据项中的第 nr 位为0

void change_bit(nr, void *addr);

//反转在 addr 指向的数据项中的第 nr 

test_bit(nr, void *addr);

//这个函数是唯一一个不需要是原子的位操作它简单地返回这个位的当前值.

int test_and_set_bit(nr, void *addr);

int test_and_clear_bit(nr, void *addr);

int test_and_change_bit(nr, void *addr);

//原子地动作如同前面列出的除了它们还返回这个位以前的值.

/*****************************end*********************************************/

 注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值