与CAO关于Linux内核的几次讨论

  • 2009-12-7 8:58:04

发件人: Cao Qingtao 发送时间: 2009-11-24 13:58:51 收件人: li_chunlin 抄送: 主题: 回复: 继续请教

春林,你好!

哎呀,十分抱歉,我上周三开始度假去了,和老婆去了一趟三亚,昨天下午才回来,你的信我刚看到。请见我的答复。

— 09年11月16日,周一, li_chunlin li_chunlin@263.net 写道:

发件人: li_chunlin li_chunlin@263.net 主题: 继续请教 收件人: “Qingtao Cao” harrytaurus2002@yahoo.com.cn 日期: 2009年11月16日,周一,下午9:15

老曹,好:

在读你推荐的那本书,继续请教几个Linux的问题。

问题一: 使能linux内核抢占的配置之后,内核中的那些代码段是可以被抢占的?如何保证某些代码段在执行的过程中不被抢占呢?书上说加锁的代码可以防止被抢占,锁指的是什么东西?

答:每个进程的thread_info中都有一个关于当前执行流是否是“原子的”的计数器,好像叫preempt_count,其实也就是一个长字,被分成了若干字段,当前执行流如果被中断打断,则内核调用ISR之前,就会把相关字段加1,退出时减1。如果当前执行流主动获得spinlock,则另外的一个字段也会加1,在释放spinlock时减1。好像还有一个字段是记录主动调用preempt_disable/enable函数的次数。

注意,当进程在用户态时,这个字段为0。如果主动进入系统调用,则也为0。如果进程在用户态,或者进入系统调用后(把这两种情况统成为具有“进程上下文”),被中断嵌套,则该字段>0。如果在ISR中唤醒了一个优先级更高的进程,则从该ISR返回到用户态或者被中断的系统调用执行流时,计数器由1递减为0,此时就会发生调度,分别称为进程切换,或者内核抢占。

该字段的本质就是当前执行流原子性的度量。只有当为0时,当前执行流才不是原子的,因此才能发生上下文切换。

显然,在ISR中,或者持有spinlock,或者主动调用了preempt_disable的执行流,这个字段都大于0,此时就不会发生内核抢占了。

如果在系统调用(比如设备驱动程序中)调用了spin_lock_irqsave,则在持有自旋锁的同时本地关闭中断,则不会被中断打断。如果只持有自选锁则允许被中断嵌套。中断上下文也分为两部分,ISR和softirq或者tasklet。如果你希望在设备驱动程序中和中断上下文的任何部分都保持同步,就必须这样做。

这个字段在LKD中有详细解释。

问题二: 上次说ISR中使用spinlock保护被多处理器竞争使用的资源,那么普通的kernel code中也是使用spinlock达到相同的目的吗? kernel code中使用信号量来保护被多进程/kernel线程竞争使用的资源,ISR中存在这种问题么?即是否考虑ISR和进程/kernel线程之间的资源竞争?亦即ISR执行过程中会发生进程调度吗?

答:由于ISR自身不会被递归调用(内核会保证这一点,通过状态机算法实现),因此一个ISR只可能和优先级更高的ISR存在竞争条件,或者当同一种中断源会被中断控制器分配到多个CPU上响应时。注意我这里所说的ISR指的是中断处理的上半部分,即在CPU响应中断信号时调用的那段代码。内核和许多设备驱动中为了使得中断处理尽可能短小,把许多工作都延迟到下半部分中进行,比如softirq和tasklet,它们会被异步地执行。因此下半部分和上半部分之间就可能存在竞争条件,需要关中断来保持同步。而下半部分自身之间可能存在竞争条件,要通过spinlock来同步。下半部分属于中断上下文的一部分(这是由于内核会在中断的上半部分结束后,检查并处理被激活的下半部分),它们和系统调用执行流之间的同步问题也需要通过关中断来解决。

mutex,semaphore的实现为同步的,必要时会以阻塞的方式获得它们,因此它们只能用到进程上下文中,也就是系统调用执行流中。而在任何使得preempt_count大于0的代码中都应该禁止使用它们(显而易见,当该计数器大于0时,表示当前执行流为原子的,既然是原子的也就应该尽量短小,不能阻塞,更不允许发生阻塞而调度的情况)

除了下半部分之外,还可以进一步把中断上半部分的后继工作延迟到内核线程中来处理,比如使用queue_work相关机制,此时就具有进程上下文了。内核线程之间的同步可以使用semaphore/mutex,和中断上下文的同步要关中断。

一定要明白把中断处理区分为上半部分和下半部分的概念,以及各种下半部分的执行机制,这样就可以想明白SMP上内核中可能存在的各种竞争条件以及相应的处理办法了,呵呵。这些东西在LKD这本书上都有。

有问题再问我哦!:-)

多谢!!

li_chunlin

2009-11-16

发件人: Cao Qingtao 发送时间: 2009-11-24 14:06:47 收件人: li_chunlin 抄送: 主题: 回复: 继续请教

锁,在LKD这本书里统统指spinlock。

假设进程在用户态执行,被一个中断打断,则在该中断上下文(无论上半部分和下半部分)执行期间,preemp_count==1。如果此时没有关中断,那么CPU可以响应更高有限级的中断,此时preemp_count==2。从内层中断上下文返回时,计数器由2递减为1,仍然大于0,因此不会发生进程切换,只有等到从外层中断上下文返回计数器由1递减为0时,才能发生进程调度。

原则:当前执行流具有原子特性时就不会发生上下文切换: 1,在中断上下文中; 2,持有锁时; 3,调用了preempt_disable或者类似函数,比如get_cpu等之后

— 09年11月16日,周一, li_chunlin li_chunlin@263.net 写道:

发件人: li_chunlin li_chunlin@263.net 主题: 继续请教 收件人: “Qingtao Cao” harrytaurus2002@yahoo.com.cn 日期: 2009年11月16日,周一,下午9:15 老曹,好:

在读你推荐的那本书,继续请教几个Linux的问题。

问题一: 使能linux内核抢占的配置之后,内核中的那些代码段是可以被抢占的?如何保证某些代码段在执行的过程中不被抢占呢?书上说加锁的代码可以防止被抢占,锁指的是什么东西?

问题二: 上次说ISR中使用spinlock保护被多处理器竞争使用的资源,那么普通的kernel code中也是使用spinlock达到相同的目的吗? kernel code中使用信号量来保护被多进程/kernel线程竞争使用的资源,ISR中存在这种问题么?即是否考虑ISR和进程/kernel线程之间的资源竞争?亦即ISR执行过程中会发生进程调度吗?

多谢!!

li_chunlin

2009-11-16

— 09年11月29日,周日, lichunlin clin.li@samsung.com 写道:

发件人: lichunlin clin.li@samsung.com 主题: 几点结论 收件人: harrytaurus2002@yahoo.com.cn 日期: 2009年11月29日,周日,下午9:57 老曹,好:

上次的mail收到,仔细琢磨了一下,又结合书本知识,得出了些结论,不知是否准确? [哎呀,真的很抱歉今天才给你回复,呵呵,前两天白天都太忙了呀!]

1. 对于不允许内核抢占的系统,除非主动放弃CPU,否则当前进程不可能在内核态被切换。

[正确. 当前进程进入内核态要么是主动进入的,即发出系统调用,此时内核执行流具有进程上下文.要么是被动进入的,比如被中断打断.当然,在系统调用执行流期间也可能被中断打断.对于不允许内核抢占的系统而言,只有当从内核态返回用户态时才能发生进程上下文切换.]

2. 进程调度发生在系统调用返回和中断返回时,内核抢占仅发生在中断返回时。

[进程调度分为用户态调度和内核态调度两种. 前者是从某个内核控制路径(中断上下文,或系统调用执行流)返回用户态时发生, 后者就是从中断上下文返回被打断的系统调用执行流时就发生了,此时还在内核态,故称为内核抢占. 还有一种情况,就是在系统调用执行流期间持有了spinlock,但是没有关中断,然后内核去响应中断了.则从中断返回持有spinlock的临界区时,由于当前执行流的preempt_count > 0,所以不会发生内核抢占,直到系统调用执行流释放改spinlock时,preempt_count = 0,会补上一次内核抢占. 当然,理论上应该在持有spinlock时也关闭中断,从而保证持有spinlock的临界区近可能短小. 与此类似的情况还有get_cpu函数和put_cpu函数,它门也可以用户显式地关闭,打开内核抢占.]

3. 为了避免某个资源被多个进程竞争使用,可以使用信号量对访问资源的临界区进行保护。

[正确. 在进程上下文中就可以肆无忌惮地使用任何可能的同步措施,本质原因就是进程上下文是可以被调度的. 反之亦然,在不可调度的中断上下文中(无论上半部分还是下半部分),都不能使用阻塞式同步操作,根本原因就是中断上下文是随机发生的,不可调度]

4. 由于spinlock可以禁止内核抢占,也可以使用spin_lock对访问共享资源的临界区进行保护以避免某个资源被多个进程竞争使用,这是作为不同于3的另一种方法,但要求临界区中不能有阻塞当前进程的操作。

[正确 持有spinlock时preempt_count会大于0,这等于告诉内核”我已进入临界区,不应该被打断”(这样做是为了尽量减小临界区的长度,降低瓶颈),所以内核不会在当前执行流的preempt_count大于0时发生内核抢占. spinlock设计之初是为了解决SMP上多CPU之间的同步问题.]

5. 为了避免某个资源被运行在不同CPU上的进程竞争使用,只能使用spin_lock,信号量在这种情况下是无能为力的。

[错误 注意区分用户态多线程编程中semaphore的使用,和内核执行流中semaphore的使用,二者都叫信号量,但是各有各的实现. 内核中使用信号量的惟一场合就是在进程上下文中,比如当你实现一个系统调用时,比如在设备驱动程序中,使用适当的信号量同步若干进程上下文对该设备的使用. 在SMP上,可能有处在不同CPU上的进程都打开了同一个设备文件,由于它门都是进程上下文,在设备驱动程序中就可以使用semaphore同步之.如果设备能够触发中断,则在设备的中断处理程序和设备的其它部分之间就可能需要关中断来同步.如果中断处理程序能够安装并使用下半部分,则还存在下半部分和上半部分,下半部分之间,以及下半部分和系统调用相关处理代码之间的同步问题,分别使用spin_lock_irqsave, spin_lock, spin_lock_irqsave来解决. 注意这里说的下半部分之间的同步是指softirq之间,如果使用tasklet,则它不会和自身发生竞争条件,只会和其它tasklet发生竞争条件.如果一个设备驱动只使用了一个tasklet,则只需关心它和中断处理的上半部分,以及和系统调用处理之间的同步问题. ]

6. 如果某个资源既可能在进程上下文中被访问,亦可能在ISR中被访问,则在进程上下文中访问该资源之前要关中断。

[正确]

7. 在ISR中使用spinlock的唯一目的就是防止某个资源被运行在其它CPU上的ISR竞争访问。 [正确 这正是spinlock之本意,解决SMP上的同步问题. Linux上一个ISR是不会重入的,而且通常内核也把一种ISR调度到一个固定的CPU来响应(从而提高cache命中率),但是该ISR所激活的下半部分可能在其它CPU上被处理.]

2.4的内核允许抢占吗?有spin_lock吗? [官方内核不支持.要打LKD的作者Robert M. Love的一个内核抢占补丁才行:-) spinlock存在.]

With Best Regards!

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

LI CHUNLIN (李 春林)

Senior Engineer

DTV System Part SAMSUNG ELECTRONICS(CHINA) R&D CENTER

E-MAIL: clin.li@samsung.com

 
tech/essays/cao_linux.txt · Last modified: 2014/11/10 08:22 (external edit)
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki