linux如何保护共享资源

linux如何保护共享资源,第1张

主要有一下几种:中断屏蔽、 原子 *** 作、自旋锁、信号量、环形缓冲区。在本文中对于这些机制的具体的实现函数,以及原理不再做任何的表述。本一、原子变量假设我们所需要保护的共享资源只是一个整数值,此时我们可以采用的机制有自旋锁,信号量,和原子变量,当然中断屏蔽也是可以的。但是如果选择最优的机制,我们应该选择原子变量。原因:1.对一个整数值的 *** 作是很简单的,也就是说此对全局变量形成的临界区是很小的.如果我们采用其他的机制,例如锁的机制,信号量等就显得有些浪费.也就是说,你的锁机制的代码量可能比临界区的代码量还要多.2. 对一个缺乏经验的程序员来讲,由于思维缺乏逻辑,使用锁机制会存在很多潜在的风险.例如: 死锁.等问题而采用原子变量的方法,可以避免锁机制产生的弊端.二、自旋锁自旋锁机制和信号量机制都可以对资源进行互斥访问,但是从性能上讲,自旋锁的性能优于信号量。但是自旋锁机制本身在不停的自旋(也就是查询锁是否可用),导致此机制有一些缺点:1。假定我们的驱动程序获得了一个自旋锁,在临界区开始了他的工作期间,驱动程序丢掉了处理器。也许他调用了一个函数,这个函数使进程休眠。也许发生了内核的抢占。但是我们的代码拥有这个自旋锁,如果其他的进程想要获得此自旋锁,需要等很长时间,甚至造成死锁。所以我们应用自旋锁时应遵循:任何拥有自旋锁的代码必须是原子的。所以他不能休眠,所以不能调用能够引起休眠的函数。例如:copy_to_user,copy_from_user,kmalloc等。也不能因为任何原因放弃处理器,除了中断以外(有时中断也不行,可以采用禁止中断的自旋锁函数 *** 作)。三、信号量拥有信号量的进程是可以休眠的。这也正是他对于自旋锁的优势。其他的暂不论述。

Linux内核中的RCU机制

RCU的设计思想比较明确,通过新老指针替换的方式来实现免锁方式的共享保护。但是具体到代码的层面,理解起来多少还是会有些困难。下面我准备了关于Linux内核中的RCU机制的文章,提供给大家参考!

RCU读取侧进入临界区的标志是调用rcu_read_lock,这个函数的代码是:

static inline void rcu_read_lock(void)

{

__rcu_read_lock()

__acquire(RCU)

rcu_read_acquire()

}

该实现里面貌似有三个函数调用,但实质性的工作由第一个函数__rcu_read_lock()来完成,__rcu_read_lock()通过调用 preempt_disable()关闭内核可抢占性。但是中断是允许的,假设读取者正处于rcu临界区中且刚读取了一个共享数据区的指针p(但是还没有访问p中的数据成员),发生了一个中断,而该中断处理例程ISR恰好需要修改p所指向的数据区,按照RCU的设计原则,ISR会新分配一个同样大小的数据区new_p,再把老数据区p中的数据拷贝到新数据区,接着是在new_p的基础上做数据修改的工作(因为是在new_p空间中修改,所以不存在对p的并发访问,因此说RCU是一种免锁机制,原因就在这里),ISR在把数据更新的工作完成后,将new_p赋值给p(p=new_p),最后它会再注册一个回调函数用以在适当的时候释放老指针p。因此,只要对老指针p上的所有引用都结束了,释放p就不会有问题。当中断处理例程做完这些工作返回后,被中断的进程将依然访问到p空间上的数据,也就是老数据,这样的结果是RCU机制所允许的。RCU规则对读取者与写入者之间因指针切换所造成的短暂的资源视图不一致问题是允许的。

接下来关于RCU一个有趣的问题是:何时才能释放老指针。我见过很多书中对此的'回答是:当系统中所有处理器上都发生了一次进程切换。这种程式化的回答常常让刚接触RCU机制的读者感到一头雾水,为什么非要等所有处理器上都发生一次进程切换才可以调用回调函数释放老指针呢?这其实是RCU的设计规则决定的: 所有对老指针的引用只可能发生在rcu_read_lock与rcu_read_unlock所包括的临界区中,而在这个临界区中不可能发生进程切换,而一旦出了该临界区就不应该再有任何形式的对老指针p的引用。很明显,这个规则要求读取者在临界区中不能发生进程切换,因为一旦有进程切换,释放老指针的回调函数就有可能被调用,从而导致老指针被释放掉,当被切换掉的进程被重新调度运行时它就有可能引用到一个被释放掉的内存空间。

现在我们看到为什么rcu_read_lock只需要关闭内核可抢占性就可以了,因为它使得即便在临界区中发生了中断,当前进程也不可能被切换除去。 内核开发者,确切地说,RCU的设计者所能做的只能到这个程度。接下来就是使用者的责任了,如果在rcu的临界区中调用了一个函数,该函数可能睡眠,那么RCU的设计规则就遭到了破坏,系统将进入一种不稳定的状态。

这再次说明,如果想使用一个东西,一定要搞清楚其内在的机制,象上面刚提到的那个例子,即便现在程序不出现问题,但是系统中留下的隐患如同一个定时炸d, 随时可能被引爆,尤其是过了很长时间问题才突然爆发出来。绝大多数情形下,找到问题所花费的时间可能要远远大于静下心来仔细搞懂RCU的原理要多得多。

RCU中的读取者相对rwlock的读取者而言,自由度更高。因为RCU的读取者在访问一个共享资源时,不需要考虑写入者的感受,这不同于rwlock的写入者,rwlock reader在读取共享资源时需要确保没有写入者在 *** 作该资源。两者之间的差异化源自RCU对共享资源在读取者与写入者之间进行了分离,而rwlock的 读取者和写入者则至始至终只使用共享资源的一份拷贝。这也意味着RCU中的写入者要承担更多的责任,而且对同一共享资源进行更新的多个写入者之间必须引入某种互斥机制,所以RCU属于一种"免锁机制"的说法仅限于读取者与写入者之间。所以我们看到:RCU机制应该用在有大量的读取 *** 作,而更新 *** 作相对较少的情形下。此时RCU可以大大提升系统系能,因为RCU的读取 *** 作相对其他一些有锁机制而言,在锁上的开销几乎没有。

实际使用中,共享的资源常常以链表的形式存在,内核为RCU模式下的链表 *** 作实现了几个接口函数,读取者和使用者应该使用这些内核函数,比如 list_add_tail_rcu, list_add_rcu,hlist_replace_rcu等等,具体的使用可以参考某些内核编程或者设备驱动程序方面的资料。

在释放老指针方面,Linux内核提供两种方法供使用者使用,一个是调用call_rcu,另一个是调用synchronize_rcu。前者是一种异步 方式,call_rcu会将释放老指针的回调函数放入一个结点中,然后将该结点加入到当前正在运行call_rcu的处理器的本地链表中,在时钟中断的 softirq部分(RCU_SOFTIRQ), rcu软中断处理函数rcu_process_callbacks会检查当前处理器是否经历了一个休眠期(quiescent,此处涉及内核进程调度等方面的内容),rcu的内核代码实现在确定系统中所有的处理器都经历过了一个休眠期之后(意味着所有处理器上都发生了一次进程切换,因此老指针此时可以被安全释放掉了),将调用call_rcu提供的回调函数。

synchronize_rcu的实现则利用了等待队列,在它的实现过程中也会向call_rcu那样向当前处理器的本地链表中加入一个结点,与 call_rcu不同之处在于该结点中的回调函数是wakeme_after_rcu,然后synchronize_rcu将在一个等待队列中睡眠,直到系统中所有处理器都发生了一次进程切换,因而wakeme_after_rcu被rcu_process_callbacks所调用以唤醒睡眠的 synchronize_rcu,被唤醒之后,synchronize_rcu知道它现在可以释放老指针了。

所以我们看到,call_rcu返回后其注册的回调函数可能还没被调用,因而也就意味着老指针还未被释放,而synchronize_rcu返回后老指针肯定被释放了。所以,是调用call_rcu还是synchronize_rcu,要视特定需求与当前上下文而定,比如中断处理的上下文肯定不能使用 synchronize_rcu函数了。

p V *** 作是关于互斥信号量的 *** 作

是关于进程同步问题所用到的。

应该选D

不过这题有点弱,A也说得过去,无所谓高级低级

解释一下

信号量其实是一个变量,由信号量保护共享资源。

信号量在创建的时候要设一个初始值

表示同时有几个任务可以访问信号量所保护的资源。

初始值为1就变成了互斥(MUTEX)

即 同时只有一个任务可以访问。

简单说 P *** 作就是如果信号量大于1,再把信号量减1。

v *** 作就是把信号量自增1。

运用:

需要访问信号量所保护的共享资源时

调用P,

结束访问时

调用V。

这样,就实现了对共享资源的保护。

呵呵,复习了一下。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/8578318.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-18
下一篇 2023-04-18

发表评论

登录后才能评论

评论列表(0条)

保存