linux设备驱动学习(三)——并发控制

1.并发介绍

   一般来说,操作系统都是支持并发执行能力的,多个执行单元访问同一个模块时,如果不能支持并发,则会让这个模块功能紊乱,像读写操作时。两个用户同时读写,那么可能一个用户执行读操作时,另一个用户可以执行了它的写操作,这就会出现功能不协调的情况。因此这里通过并发,将代码放在临界区,通过特定的互斥机制来对这一块进行保护,使得多个执行单元访问这块时只能执行一个,其余都需要等待或其他。

2.并发处理方法

(1)中断屏蔽

CPU一般都具备屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序所抢占,防止某些竞态条件的发生。具体而言,中断屏蔽将使得中断与进程之间的并发不再发生,而且,由于Linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也得以避免了。

local_irq_disable() /* 屏蔽中断 */
. . .
critical section /* 临界区*/
. . .

local_irq_enable() /* 开中断*/

(2)自旋锁

自旋锁(Spin Lock)是一种典型的对临界资源进行互斥访问的手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(Test-And-Set)某个内存变量

spinlock_t lock;        /* 定义一个自旋锁*/
spin_lock_init(&lock);   /* 初始化一个自旋锁*/
spin_lock (&lock) ;   /* 获取自旋锁,保护临界区 */
. . ./* 临界区*/

spin_unlock (&lock) ;   /* 解锁*/

自旋锁主要针对SMP或单CPU但内核可抢占的情况,对于单CPU和内核不支持抢占的系统,自旋锁退化为空操作

(3)信号量

信号量(Semaphore)是操作系统中最典型的用于同步和互斥的手段,信号量的值可以是0、1或者n。信号量与操作系统中的经典概念PV操作对应。

P(S):①将信号量S的值减1,即S=S-1;
②如果S≥0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):①将信号量S的值加1,即S=S+1;
②如果S>0,唤醒队列中等待信号量的进程。
Linux中与信号量相关的操作主要有下面几种。

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

void sema_init(struct semaphore *sem, int val);  //初始化型号量,并设置信号量的值为val

/*获得信号量*/

void down(struct semaphore * sem); //获得信号量,它会导致睡眠,因此不能在中断上下文中使用。

int down_interruptible(struct semaphore * sem);   //获得信号量,进入睡眠状态的进程能被信号打断

int down_trylock(struct semaphore * sem);   /*该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。。它不会导致调用者睡眠,可以在中断上下文中使用。*/

/*释放信号量*/

void up(struct semaphore * sem);  

(4)互斥体

struct mutex my_mutex;   //定义一个互斥体

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

/*获取互斥体*/

void mutex_lock(struct mutex *lock);
int mutex_lock_interruptible(struct mutex *lock);
int mutex_trylock(struct mutex *lock);

/*释放互斥体*/

void mutex_unlock(struct mutex *lock);

mutex的使用方法和信号量用于互斥的场合完全一样:

eg:

struct mutex my_mutex; /* 定义mutex */
mutex_init(&my_mutex); /* 初始化mutex */
mutex_lock(&my_mutex); /* 获取mutex */
... /* 临界资源*/

mutex_unlock(&my_mutex); /* 释放mutex */

互斥体与自旋锁区别:

   互斥体和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在互斥体本身的实现上,为了保证互斥体结构存取的原子性,需要自旋锁来互斥。所以自旋锁属于更底层的手段。

   互斥体是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用资源时间较长时,用互斥体才是较好的选择。

   当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它可节省上下文切换的时间。

由此,可以总结出自旋锁和互斥体选用的3项原则。
1)当锁不能被获取到时,使用互斥体的开销是进程上下文切换时间,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)。若临界区比较小,宜使用自旋锁,若临界区很大,应使用互斥体。
2)互斥体所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。

3)互斥体存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在互斥体和自旋锁之间只能选择自旋锁。当然,如果一定要使用互斥体,则只能通过mutex_trylock()方式进行,不能获取就立即返回以避免阻塞。

(5)完成量(Completion)

它用于一个执行单元等待另一个执行单元执行完某事。

struct completion my_completion; //定义名为my_completion的完成量

/*  初始化完成量*/

init_completion(&my_completion);

reinit_completion(&my_completion)

/*等待完成量*/

void wait_for_completion(struct completion *c);

/*唤醒完成量*/

void complete(struct completion *c);
void complete_all(struct completion *c);

eg:





  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值