zoukankan      html  css  js  c++  java
  • 内核中防止竞争状态的手段

    1、什么是竞争状态,之前在应用编程的学习中已经提到过。

    竞争状态就是在多进程环境下,多个进程同时抢占系统资源(内存、CPU、文件IO),竞争状态对OS来说是很危险的,此时OS如果没处理好就会造成意想不到的结果。

    写程序当然不希望程序运行的结果不确定,所以我们写程序时要尽量消灭竞争状态。操作系统给我们提供了一系列的消灭竟态的机制,我们需要做的是在合适的地方使

    用合适的方法来消灭竟态。这是之前在应用编程中说的,但是在内核态中同样存在竞争状态,所以今天来说说Linux内核中提供的用来消灭竞争状态的方法。

    内核中消灭竞争状态的方法:互斥锁、信号量、原子访问、自旋锁

    首先说明本次使用的内核版本是: Linux 2.6.35.7

    一些概念:临界段、互斥锁、死锁

    同步:多CPU、多任务、中断

    2、互斥锁

    互斥也就是相互排斥的意思,排他性,在编程中引入互斥是为了保证公共访问对象在同一时间(这个同一时间指的是宏观上的同一时间)内只能被一个线程

    进行访问操作。当有一个线程在访问这个对象的时候,其他的线程是无法对他进行访问的。他的实现原理就是通过互斥锁来实现的,当线程访问公共对象时,在访问之前

    他需要先去获取访问这个对象的锁,如果此时无法获取锁,则表示此时已经有其他的线程正在访问这个对象,那么他就如进入等待队列当中,进入阻塞状态。

    其实这个过程就好比是上厕所(只有一个厕所),当厕所没人的时候你就直接进去把门关上,其他人想要上厕所就得先把门打开,发现门打不开,说明里面就有人,只能等待。

    下面相关的函数定义和声明分别在: kernelmutex.c  和  includelinuxmutex.h

    (1)定义互斥锁: DEFINE_MUTEX(mutex);          定义 + 初始化

                          struct mutex mutexname;  mutex_init(mutexname);    先定义,之后再初始化    

    (2)给互斥锁上锁: mutex_lock(struct mutex *lock);       这种方式进入休眠状态不会被打断,一直等待条件满足

                            int __must_check mutex_lock_interruptible(struct mutex *lock);   这种方式可以被中断返回,退出等待队列

                            int __must_check mutex_lock_killable(struct mutex *lock);     这种方式可以被杀掉

                            int mutex_trylock(struct mutex *lock);        这种方式会尝试上锁,不管成功与否都不会阻塞等待    

    (3)解锁:   mutex_unlock(struct mutex *lock);           

    整个互斥锁的使用形式如下:

    先定义一个互斥锁: DEFINE_MUTEX(mutex);

    在需要被保护的地方加上锁:  mutex_lock(struct mutex *lock);    或者其他的函数

    当访问完成之后需要解锁,这个一定不要忘了:  mutex_unlock(struct mutex *lock);

    3、信号量

    互斥锁其实是一种特殊的信号量,互斥锁表示只有一个线程能够同时访问某个对象,而信号量则可以定义同时访问对象的线程数。也就是说互斥锁是只能同时访问一个对象

    的信号量。下面函数定义和申明所在文件:  includelinuxsemaphore.h

    需要注意的是:互斥锁的出现先对于信号量来说比较晚,所以他的实现上更加的优秀,所以使用时如果可以请尽量使用互斥锁,而不要用信号量。

    (1)信号量的定义:  

                 宏:     DECLARE_MUTEX(name);       直接定义一个信号量name,初始化为1,表示空闲状态。一般这个用的比较多

                           struct semaphore sem;   init_MUTEX(sem);                  需要自己先定义一个变量,在调用宏初始化

                           struct semaphore sem;    init_MUTEX_LOCKED(sem);    跟上面的区别在于定义之后初始化为0,表示忙状态,所以在访问之前必须先释放锁

                 函数:  void sema_init(struct semaphore *sem, int val);    val表示能够同时访问被保护对象的线程数量,上面2个宏内部就是调用了这个函数实现的

    (2)信号量上锁:  

                          down(struct semaphore *sem);        将信号量计数值count减1,如果在执行down函数前,计数值已经为0,则表示不能在进行访问了,只能进入阻塞休眠,等待

                          int __must_check down_interruptible(struct semaphore *sem);  和上面的区别在于,阻塞可以被打断,例如信号,直接退出等待队列

                          int __must_check down_killable(struct semaphore *sem);    在阻塞等待的时可以被杀掉

                          int __must_check down_trylock(struct semaphore *sem);     尝试上锁,不管成功与否都直接返回,而不进入阻塞等待队列

                          int __must_check down_timeout(struct semaphore *sem, long jiffies);     可以设置等待超时时间,如果等待时间超过规定的时间,则直接退出等待队列

    (3)信号量解锁: 

                          up(struct semaphore *sem);            解锁信号量,将信号量计数值count加1

    使用方法跟互斥锁差不多。

    4、自旋锁

    自旋锁最初是为了在SMP系统中实现保护临界区而设计的,自旋锁的实现是为了保护一段短小的临界区操作代码,保证这个临界区的操作是原子的,从而避免并发的竞争冒险。

    因为在SMP多核心处理器中可以在微观上同时执行多个进程,当这几个进程同时对临界保护区的代码进行访问的时候,这就发生了竞争状态。为了避免这种情况的出现,可以

    通过内核实现的自旋锁来避免,他的原理就是:当进程访问临界保护区代码时,跟上面的信号量和互斥锁一样需要先去获取锁,只有成功获取到锁的进程才能够对代码进行访问

    ,并且访问是原子性的,必须当这个临界区代码执行完毕解锁之后才能恢复。没有获取到锁的进程并不会进入休眠阻塞的状态,而是会一直循环查询锁状态,这就是自旋的意思

    这点与信号量(互斥锁)是不同的。

    上面说的是SMP处理器的情况,在单核心非抢占内核系统(抢占的意思就是高优先级的线程可以优先抢占CPU)中,自旋锁所做的操作时空操作。在单核心抢占内核系统下,自旋

    锁仅仅是当作一个设置内核抢占的开关,当获取到的线程进入临界区代码中时,此时就把内核抢占系统关闭了,同时也会禁止中断,所以此时除了正在执行他自己的代码外没有其

    他的线程在动作了。

    自旋锁是循环检测“忙等”,即等待时内核无事可做(除了浪费时间),进程在CPU上保持运行,所以它保护的临界区必须小,

    且操作过程必须短。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段,所以等待自旋锁的释放不会消耗太多CPU的时间。

    (1)自旋锁和信号量的使用要点

      1): 自旋锁不能递归

      2): 自旋锁可以用在中断上下文(信号量不可以,因为可能睡眠),但是在中断上下文中获取自旋锁之前要先禁用本地中断(本CPU自己的中断),因为中断是不参与进程调度的,

           如果我们的CPU运行在中断中时丢失了CPU,那么将再也不能会在这个中断的断点处了。

      3): 自旋锁的核心要求是:拥有自旋锁的代码必须不能睡眠,要一直持有CPU直到释放自旋锁。

      4): 信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用

           。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在

           中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自

           旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。

    (1)初始化自旋锁:

                  宏:     struct spinlock_t lock;       spin_lock_init(&lock);      初始化一个自旋锁,将自旋锁设置为1,表示资源可用

    (2)自旋锁上锁:

                 函数:  spin_lock(spinlock_t *lock);         循环等待直到自旋锁解锁(置为1),之后将自旋锁锁上(置为0)

                            spin_lock_bh(spinlock_t *lock);    循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。阻止软中断的底半部的执行。

                            int spin_trylock(spinlock_t *lock);  尝试锁上自旋锁(把它置为0),不管成功与否,都直接返回,而不循环等待

                           spin_lock_irq(spinlock_t *lock);     循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关闭中断

                 宏:    spin_lock_irqsave(lock, flags);       循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断,将状态寄存器值存入flags。

    (3)自旋锁解锁:

                 函数: spin_unlock(spinlock_t *lock);               将自旋锁解锁(置为1)

                           spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);      将自旋锁解锁(置为1)。开中断,将状态寄存器值从flags存入状态寄存器。

                           spin_unlock_irq(spinlock_t *lock);      将自旋锁解锁(置为1)。开中断。

                           spin_unlock_bh(spinlock_t *lock);     将自旋锁解锁(置为1)。开启底半部的执行。

    (4)其他的一些辅助函数:

                          int spin_is_locked(spinlock_t *lock);          如果自旋锁被锁上了,返回0,否则返回1

                         spin_unlock_wait(spinlock_t *lock);       等待直到自旋锁解锁(为1),返回0;否则返回1。 

    (5)自旋锁与信号量或者互斥锁的使用

    需求                                 建议的加锁方式

    低开销加锁                        优先使用自旋锁

    短期锁定                           优先使用自旋锁

    长期锁定                           优先使用信号量或者是互斥锁

    中断上下文加锁                  优先使用自旋锁

    如果需要进入休眠、调度     优先使用信号量或者是互斥锁

    5、原子操作

    这部分我之后再补充

    参考:http://blog.csdn.net/tommy_wxie/article/details/7283307

             http://blog.chinaunix.net/uid-26990992-id-3264808.html

                           

     

     

  • 相关阅读:
    【模拟】Gym
    【二分】【半平面交】Gym
    【凸包】【三分】Gym
    【字符串哈希】【哈希表】Aizu
    【思路】Aizu
    【树状数组】Codeforces Round #423 (Div. 1, rated, based on VK Cup Finals) C. DNA Evolution
    【构造】Codeforces Round #423 (Div. 1, rated, based on VK Cup Finals) B. High Load
    【贪心】Codeforces Round #423 (Div. 1, rated, based on VK Cup Finals) A. String Reconstruction
    【模拟退火】Petrozavodsk Winter Training Camp 2017 Day 1: Jagiellonian U Contest, Monday, January 30, 2017 Problem F. Factory
    【动态规划】【二分】Petrozavodsk Winter Training Camp 2017 Day 1: Jagiellonian U Contest, Monday, January 30, 2017 Problem B. Dissertation
  • 原文地址:https://www.cnblogs.com/deng-tao/p/6045127.html
Copyright © 2011-2022 走看看