Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1728882
  • 博文数量: 98
  • 博客积分: 667
  • 博客等级: 上士
  • 技术积分: 1631
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-27 15:59
个人简介

一沙一世界 一树一菩提

文章分类

全部博文(98)

文章存档

2021年(8)

2020年(16)

2019年(8)

2017年(1)

2016年(11)

2015年(17)

2014年(9)

2013年(4)

2012年(19)

2011年(1)

2009年(4)

分类: LINUX

2015-06-09 19:45:35

这几天,主要做一些多线程网络服务器代码的优化事宜,用到了线程队列来逻辑处理到来的客户连接,也因此用到了锁机制。有意无意的查阅锁的效率问题,然后自然而然就查到了无锁算法效率较高的说法,最后就查到了linux内核的自旋锁上了。也就是__preempt_spin_lock函数上。这个函数自己第一眼看,看不明白,然后自己看了会才明白,所以记下,以后登录回顾。
本着我为人人,人人为我的AV精神,拿来分享,可能理解也不完全对,哪有不对的地方,大家积极指出,互相学习。
static inline void __preempt_spin_lock(spinlock_t *lock)
{
    .................
    do{
            preempt_enable();
            while(spin_is_locked(lock))
                cpu_relax();
            preempt_disable();
    }while(!__raw_spin_trylock(lock));
}
这段函数其实涉及到的东西挺多,我是一点一点挖出来的。
先说下这段代码的意思:
在获取自旋锁前,先允许抢占,这还没有获得锁呢,你禁止抢占就有点过分啦。然后判断这个锁在其它地方是不是已经被使用,如果使用就执行cpu_relax,这个函数可以认为是忙等待,也就是让cpu休息一会,然后再检测锁有没有被释放,还没有,则继续等待,如果释放了,那么往下继续,先禁止抢占,最后尝试加锁,返回0表示加锁成功,函数执行完毕,返回1表示加锁失败,继续循环等待直到加锁成功。
这段代码其实反映的就是自旋锁定义。一旦获得锁就会运行完毕,获得不了锁则占用某个cpu一直等待,直到获得锁。
这里面有几个点一一记下:
一   preempt_enable和preempt_disable
获得锁以后,临界区必须执行完毕,哪怕有高优先级任务,但是没有禁止中断,也就是说自旋锁获得期间中断是可以执行的,但是中断执行完,还是要回到这段代码的。如果这段代码的外围在加上禁止中断,那整个世界就安静了,趁这段得之不易的时间,抓紧干活吧。
二 cpu_relax是忙等待,这对于cpu来说,太浪费了,所以如果获得锁需要等待的时间很长的话,不适合自旋锁。
三 如果临界区执行时间长的话,也不建议采用自旋锁,因为自旋锁获得前禁止抢占,如果执行临界区代码时间很长,相当于独占cpu很长时间,其它任务就没有机会或者说分摊的执行时间少。
四 注意__raw_spin_trylock的返回值,一定要落实真否获得锁。

关于抢占内核和非抢占内核,个人感觉这个blog说的不错,特意拿来参考,推荐指数:5颗星。原地址:http://blog.csdn.net/sailor_8318/article/details/2870184

非抢占式内核

非抢占式内核是由任务主动放弃CPU的使用权。非抢占式调度法也称作合作型多任务,各个任务彼此合作共享一个CPU。异步事件还是由中断服务来处理。中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。

非抢占式内核的优点有:

·中断响应快(与抢占式内核比较);

·允许使用不可重入函数;

·几乎不需要使用信号量保护共享数据。运行的任务占有CPU,不必担心被别的任务抢占。这不是绝对的,在打印机的使用上,仍需要满足互斥条件。

非抢占式内核的缺点有:

·任务响应时间慢。高优先级的任务已经进入就绪态,但还不能运行,要等到当前运行着的任务释放CPU。

·非抢占式内核的任务级响应时间是不确定的,不知道什么时候最高优先级的任务才能拿到CPU的控制权,完全取决于应用程序什么时候释放CPU。

抢占式内核

使用抢占式内核可以保证系统响应时间。最高优先级的任务一旦就绪,总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态,当前任务的CPU使用权就会被剥夺,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。

抢占式内核的优点有:

·使用抢占式内核,最高优先级的任务什么时候可以执行,可以得到CPU的使用权是可知的。使用抢占式内核使得任务级响应时间得以最优化。

抢占式内核的缺点有:

·不能直接使用不可重入型函数。调用不可重入函数时,要满足互斥条件,这点可以使用互斥型信号量来实现。如果调用不可重入型函数时,低优先级的任务CPU的使用权被高优先级任务剥夺,不可重入型函数中的数据有可能被破坏。


什么时候需要重新调度

·时钟中断处理例程检查当前任务的时间片,当任务的时间片消耗完时,scheduler_tick()函数就会设置need_resched标志;

·信号量、等到队列、completion等机制唤醒时都是基于waitqueue的,而waitqueue的唤醒函数为,其调用将被唤醒的任务更改为就绪状态并设置need_resched标志。

·设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态;

·改变任务的优先级时,可能会使高优先级的任务进入就绪状态;

·新建一个任务时,可能会使高优先级的任务进入就绪状态;

·对CPU(SMP)进行负载均衡时,当前任务可能需要放到另外一个CPU上运行;

    抢占发生的时机

·当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。

·当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数。

·一个任务在内核态中显式的调用schedule()函数。任务主动放弃CPU使用权。

·一个任务在内核态中被阻塞,导致需要调用schedule()函数。任务主动放弃CPU使用权。

   什么时候不允许抢占

有几种情况Linux内核不应该被抢占,除此之外,Linux内核在任意一点都可被抢占。这几种情况是:

·内核正进行中断处理。在Linux内核中进程不能抢占中断(中断只能被其他中断中止、抢占,进程不能中止、抢占中断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。

·内核正在进行中断上下文的Bottom Half(中断的下半部)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。

·内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的保护状态中。内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等。

·内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。

·内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占。


spin_lock和raw_spin_lock的历史

这也是网上截来的,感觉挺好,留于此地,推荐指数:5颗星。原地址:http://blog.csdn.net/droidphone/article/details/7395983
在2.6.33之后的版本,内核加入了raw_spin_lock系列,使用方法和spin_lock系列一模一样。而且在内核的主线版本中,spin_lock系列只是简单地调用了raw_spin_lock系列的函数,但内核的代码却是有的地方使用spin_lock,有的地方使用raw_spin_lock。是不是很奇怪?要解答这个问题,我们要回到2004年,MontaVista Software, Inc的开发人员在邮件列表中提出来一个Real-Time Linux Kernel的模型,旨在提升Linux的实时性,之后Ingo Molnar很快在他的一个项目中实现了这个模型,并最终产生了一个Real-Time preemption的patch。
该模型允许在临界区中被抢占,而且申请临界区的操作可以导致进程休眠等待,这将导致自旋锁的机制被修改,由原来的整数原子操作变更为信号量操作。当时内核中已经有大约10000处使用了自旋锁的代码,直接修改spin_lock将会导致这个patch过于庞大,于是,他们决定只修改哪些真正不允许抢占和休眠的地方,而这些地方只有100多处,这些地方改为使用raw_spin_lock,但是,因为原来的内核中已经有raw_spin_lock这一名字空间,用于代表体系相关的原子操作的实现,于是linus本人建议:
  • 把原来的raw_spin_lock改为arch_spin_lock;
  • 把原来的spin_lock改为raw_spin_lock;
  • 实现一个新的spin_lock;
写到这里不知大家明白了没?对于2.6.33和之后的版本,我的理解是:
  • 尽可能使用spin_lock;
  • 绝对不允许被抢占和休眠的地方,使用raw_spin_lock,否则使用spin_lock;
  • 如果你的临界区足够小,使用raw_spin_lock;
对于没有打上Linux-RT(实时Linux)的patch的系统,spin_lock只是简单地调用raw_spin_lock,实际上他们是完全一样的,如果打上这个patch之后,spin_lock会使用信号量完成临界区的保护工作,带来的好处是同一个CPU可以有多个临界区同时工作,而原有的体系因为禁止抢占的原因,一旦进入临界区,其他临界区就无法运行,新的体系在允许使用同一个临界区的其他进程进行休眠等待,而不是强占着CPU进行自旋操作。写这篇文章的时候,内核的版本已经是3.3了,主线版本还没有合并Linux-RT的内容,说不定哪天就会合并进来,也为了你的代码可以兼容Linux-RT,最好坚持上面三个原则。


阅读(2060) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~