Posts Tagged ‘linux’

使用vim+ctags+cscope阅读内核源码

11 10 月, 2010

有网络的时候,使用LXR(The Linux Cross Referencer)在线阅读源码固然是一件很爽的事情。在无网络的情况下,只通过vim进入内核源码那样查找就显得有些勉强。接下来,本文将介绍一种快速定位源码的工具:ctags和cscope。通过vim编辑器,再配合使用上述两种工具就可以快速定位想要查看的源码。好了,开始吧!

1.下载最新内核源码包,解压至主目录下

接下来的步骤当然可以在当前系统内核源码目录下进行,不过为了保险期间还是最好在主目录下专门创建一个存放源码的目录。下文所说的源码目录均指主目录下的源码目录。

2.安装ctags

使用命令:sudo apt-get install ctags

3.安装cscope

使用命令:sudo apt-get install cscope

4.成标签文件

在源码目录下通过命令make tags生成标签文件

5.生成索引文件

在源码目录下通过命令make cscope生成索引文件。

6.将索引文件导入vim中

使用命令打开vim的配置文件:sudo gedit /etc/vim/vimrc;然后在该文件下添加如下代码:

if filereadable("cscope.out")
    cs add cscope.out
endif

这样每次打开vim就可以直接使用cscope了。

完成以上步骤以后就可以通过vim进行源码的快速定位了。上述安装的ctags和cscope都属于源码索引工具,因此仅安装两者之一也是可以的。不错,虽然ctags可以快速定位所输入的标签,但cscope的使用方法则更灵活。所以两者同时安装使用起来更方便。下面是一些简单的使用方法,仅供参考。

1.使用ctags

ctags的使用特别简单,首先进入源码目录下,打开vim编辑器,在命令模式下输入:tag tag_name 按回车即可。通常tag_name可以是结构体名称,函数名称,宏变量名称等。通常输入上述命令后,因为找到的标签并不唯一,所以还需要配合使用下述命令:

tfirst:跳至第一个

tnext:跳至下一个

tlast:跳至最后一个

2.使用cscope

虽然ctags使用方便快捷,但有时候使用cscope更灵活,首先可以看cscope的帮助文件:

cscope 命令:
add  :添加一个新的数据库             (用法: add file|dir [pre-path] [flags])
find :查询一个模式                   (用法: find c|d|e|f|g|i|s|t name)
c:找到调用这个函数的函数
d:找到被这个函数调用的函数
e:找到这个 egrep 模式
f:找到此文件夹
g:找到这个定义
i:找文件 #包括这个文件
s:找到这个 C 符号
t:找到对其的赋值
help :显示此信息                     (用法: help)
kill :结束一个连接                   (用法: kill #)
reset:重置所有连接                   (用法: reset)
show :显示连接                       (用法: show)

通过上面的帮助文件可以发现如果想找到request_irq函数的定义处代码,即可使用这个命令:cs find g request_irq来进行查找。大多数情况下查找的结果并不唯一,因为需要在多个结果中通过头文件来继续查看。

通常是先通过cscope大致定位到头文件,再通过ctags在该头文件中详细定位。其实这两种工具并不局限于上述用法,更多用法可以再深入使用的过程中慢慢摸索,而且配合正则表达式等效果会更好。

与中断有关的数据结构

6 10 月, 2010

Latest Update:2010/10/12

1.概述

通过前文,我们已经知道了中断通常由上下两部分组成。在上部分,也就是中断处理程序,完成中断请求的响应以及完成那些对时间要求紧迫的工作;而在下部分,通常完成那些被推后的工作,因为这部分工作对时间的要求相对宽松一些。通过了解上下两部分的工作情况,可以更好的理解中断这个概念。从下半部分执行机制来看——不管是tasklet还是工作队列——这些推后的工作总是在上半部分被调用,然后交给内核在适当的时间来完成。那么,中断上部分具体是如何工作的?内核对中断是如何处理的?

在开始分析之前,需要说明的是接下来分析的属于内核对外设产生的中断的处理情况,异常的处理过程在本文的最后会有简单解释。

所有的中断的处理程序在init_IRQ函数中都被初始化为interrupt[i]。interrupt数组中每一项均指向一个代码片段:

pushl $n-256
/*省略部分代码*/
jmp common_interrupt

该代码片段除了将中断向量号压入堆栈,还会跳到一个公共处理程序common_interrup:

//在linux/arch/x86/kernel/entry_32.S
 863 common_interrupt:
 864         addl $-0x80,(%esp)      /* Adjust vector into the [-256,-1] range *     /
 865         SAVE_ALL
 866         TRACE_IRQS_OFF
 867         movl %esp,%eax
 868         call do_IRQ
 869         jmp ret_from_intr

这段公共处理程序会将中断发生前的所有寄存器的值压入堆栈,也就是保存被中断任务的现场。然后调用do_IRQ函数,在do_IRQ函数中会调用(并非直接调用那么简单)到handle_IRQ_event函数,在此函数中会执行实际的中断服务例程。当中断服务例程执行完毕后,会返回到上面的那段汇编程序中,转入ret_from_intr代码段从中断返回。

在上述文字描述的基础上,可以通过下图进一步加深中断的处理过程:

从上面的概述中,我们可以很快定位我们未来要分析的函数:do_IRQ()和hand_IRQ_event();在分析上述函数之前,我们很有必要先分析一下关于中断的三个重要的数据结构。

2.struct irq_desc

在一开始我们分析中断的时候,谈及到了中断向量。在内核中,每个中断向量都有相应的有一个irq_desc结构体(稍早内核版本中为irq_desc_t)来描述一个中断向量(也就是中断源),具体代码如下:

  31struct irq_desc;
  32typedef void (*irq_flow_handler_t)(unsigned int irq,
  33                                            struct irq_desc *desc);

 175struct irq_desc {
 176        unsigned int            irq;
 177        struct timer_rand_state *timer_rand_state;
 178        unsigned int            *kstat_irqs;
 179#ifdef CONFIG_INTR_REMAP
 180        struct irq_2_iommu      *irq_2_iommu;
 181#endif
 182        irq_flow_handler_t      handle_irq;
 183        struct irq_chip         *chip;
 184        struct msi_desc         *msi_desc;
 185        void                    *handler_data;
 186        void                    *chip_data;
 187        struct irqaction        *action;
 188        unsigned int            status;
 189
 190        unsigned int            depth;
 191        unsigned int            wake_depth;
 192        unsigned int            irq_count;
 193        unsigned long           last_unhandled;
 194        unsigned int            irqs_unhandled;
 195        raw_spinlock_t          lock;
 196#ifdef CONFIG_SMP
 197        cpumask_var_t           affinity;
 198        const struct cpumask    *affinity_hint;
 199        unsigned int            node;
 200#ifdef CONFIG_GENERIC_PENDING_IRQ
 201        cpumask_var_t           pending_mask;
 202#endif
 203#endif
 204        atomic_t                threads_active;
 205        wait_queue_head_t       wait_for_threads;
 206#ifdef CONFIG_PROC_FS
 207        struct proc_dir_entry   *dir;
 208#endif
 209        const char              *name;
 210}

 216#ifndef CONFIG_SPARSE_IRQ
 217extern struct irq_desc irq_desc[NR_IRQS];
 218#endif

所有这样的描述符组织在一起形成irq_desc[NR_IRQS]数组。下面我们对上述结构体的部分字符进行解释;

irq:通过数据类型可知这便是这个描述符所对应的中断号;
handle_irq:指向该IRQ线的公共服务程序;
chip:它是一个struct irq_chip类型的指针,是中断控制器的描述符,与平台有关,下文有详细描述;
handler_data:用于handler_irq的参数;
chip_data:用于chip的参数;
action:一个struct irqaction类型的指针(下文有该结构的详细描述);它指向一个单链表,该单链表是由该中断线上所有中断服务程序(对应struct irqaction)所连接起来的;
status:描述中断线当前的状态;
depth:中断线被激活时,值为0;其值为正数时,表示被禁止的次数;
irq_count:记录该中断线发生中断的次数;
irqs_unhandled:该IRQ线上未处理中断发生的次数;
name: /proc/interrupts 中显示的中断名称;

3.struct irqaction

当多个设备共享一条IRQ线时,因为每个设备都要有各自的ISR。为了能够正确处理此条IRQ线上的中断处理程序(也就是区分每个设备),就需要我们使用irqaction结构体。在这个结构体中,会有专门的handler字段指向该设备的真正的ISR。共享同一条IRQ线上的多个这样的结构体会连接成了一个单链表,即所谓的中断请求队列。中断产生时,该IRQ线的中断请求队列上所有的ISR都会被依次调用,因此每个设备的ISR必须判断当前的中断是否是自己所属的设备产生的。irqaction结构具体的定义如下:

  98typedef irqreturn_t (*irq_handler_t)(int, void *);

 113struct irqaction {
 114        irq_handler_t handler;
 115        unsigned long flags;
 116        const char *name;
 117        void *dev_id;
 118        struct irqaction *next;
 119        int irq;
 120        struct proc_dir_entry *dir;
 121        irq_handler_t thread_fn;
 122        struct task_struct *thread;
 123        unsigned long thread_flags;
 124};

handler:指向一个具体的硬件设备的中断服务例程,可以从此指针的类型发现与前文我们所定义的中断处理函数声明相同;
flags:对应request_irq函数中所传递的第三个参数,可取IRQF_DISABLED、IRQF_SAMPLE_RANDOM和IRQF_SHARED其中之一;
name:对应于request_irq函数中所传递的第四个参数,可通过/proc/interrupts文件查看到;
next:指向下一个irqaction结构体;
dev_id:对应于request_irq函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体;
irq:中断号

如果一个IRQ线上有中断请求,那么内核将依次次调用在该中断线上注册的每一个中断服务程序,但是并不是所有中断服务程序都被执行。一般硬件设备都会提供一个状态寄存器,以便中断服务程序进行检查是否应该为这个硬件服务。也就是说在整个中断请求队列中,最多会有一个ISR被执行,也就是该ISR对应的那个设备产生了中断请求时;不过当该IRQ线上某个设备未找到匹配的ISR时,那这个中断就不会被处理。此时irq_desc结构中的irqs_unhandled字段就会加1。

4.struct irq_chip

struct irq_chip是一个中断控制器的描述符。通常不同的体系结构就有一套自己的中断处理方式。内核为了统一的处理中断,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。这样的结构体就好比一个插板,我们可以使用各种插头。至于插板内部是如何实现的,使用者并不需要过于关心。

比如经典的中断控制器是2片级联的8259A,那么得15个irq_desc描述符,每一个描述符的irq_chip都指向描述8259A的i8259A_irq_type变量(arch/alpha/kernel/irq_i8259.c):

 86struct irq_chip i8259a_irq_type = {
  87        .name           = "XT-PIC",
  88        .startup        = i8259a_startup_irq,
  89        .shutdown       = i8259a_disable_irq,
  90        .enable         = i8259a_enable_irq,
  91        .disable        = i8259a_disable_irq,
  92        .ack            = i8259a_mask_and_ack_irq,
  93        .end            = i8259a_end_irq,
  94};

这一点类似于VFS中所采用的原理:通过struct file_operations提供统一的接口,每种文件系统都必须具体实现这个结构体中所提供的接口。struct irq_chip具体代码如下:

111struct irq_chip {
112        const char      *name;
113        unsigned int    (*startup)(unsigned int irq);
114        void            (*shutdown)(unsigned int irq);
115        void            (*enable)(unsigned int irq);
116        void            (*disable)(unsigned int irq);
117
118        void            (*ack)(unsigned int irq);
119        void            (*mask)(unsigned int irq);
120        void            (*mask_ack)(unsigned int irq);
121        void            (*unmask)(unsigned int irq);
122        void            (*eoi)(unsigned int irq);
123
124        void            (*end)(unsigned int irq);
125        int             (*set_affinity)(unsigned int irq,
126                                        const struct cpumask *dest);
127        int             (*retrigger)(unsigned int irq);
128        int             (*set_type)(unsigned int irq, unsigned int flow_type);
129        int             (*set_wake)(unsigned int irq, unsigned int on);
130
131        void            (*bus_lock)(unsigned int irq);
132        void            (*bus_sync_unlock)(unsigned int irq);
133
134        /* Currently used only by UML, might disappear one day.*/
135#ifdef CONFIG_IRQ_RELEASE_METHOD
136        void            (*release)(unsigned int irq, void *dev_id);
137#endif
138        /*
139         * For compatibility, ->typename is copied into ->name.
140         * Will disappear.
141         */
142        const char      *typename;
143};

name:中断控制器的名字;
Startup:启动中断线;
Shutdown:关闭中断线;
Enable:允许中断;
Disable:禁止中断;

5.数据结构之间的关系

首先我们通过下图来了解上述三个数据结构的关系:

通过上述分析,我们可以大致的知道中断处理程序的调用过程:在do_IRQ函数中,通过对irq_desc结构体中handler_irq字段的引用,调用handler_irq所指向的公共服务程序;在这个公共服务程序中会调用hand_IRQ_event函数;在hand_IRQ_event函数中,通过对irqaction结构体中handler字段的引用最终调用我们所写的中断处理程序。

另外,通过上述分析,我们知道struct irq_chip描述了中断最底层的部分;而struct irqacton则描述最上层具体的中断处理函数;而与中断向量所对应的struct desc则类似一个中间层,将中断中的硬件相关的部分和软件相关的部分连接起来。

6.内核对异常的处理

与中断不同,各种异常都有固定的中断向量(0~31)以及固定的异常处理程序。因此,当异常发生时,将直接跳转到相应的服务程序中执行。

中断下半部-工作队列

5 10 月, 2010

本文包含那些内容?
工作队列和tasklet的区别;中断上下文;工作队列的使用;
本文适合那些人阅读?
想了解linuxer;学习驱动开发的beginner;学习内核模块编程beginner;其他super linux NBer;
参考书籍:
Linux内核设计与实现
Linux操作系统原理与应用

为什么还需要工作队列?


工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文(process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。

对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。

因此,选择tasklet还是工作队列来完成下半部分应该不难选择。当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是你的最佳选择;否则,还是用tasklet吧。

中断上下文


在了解中断上下文时,先来回顾另一个熟悉概念:进程上下文(这个中文翻译真的不是很好理解,用“环境”比它好很多)。一般的进程运行在用户态,如果这个进程进行了系统调用,那么此时用户空间中的程序就进入了内核空间,并且称内核代表该进程运行于内核空间中。由于用户空间和内核空间具有不同的地址映射,并且用户空间的进程要传递很多变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。这样就产生了进程上下文。

所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容。当内核需要切换到另一个进程时(上下文切换),它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态继续执行。上述所说的工作队列所要做的工作都交给工作者线程来处理,因此它可以表现出进程的一些特性,比如说可以睡眠等。

对于中断而言,是硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理,中断上下文就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境。因此处于中断上下文的tasklet不会有睡眠这样的特性。

工作队列的使用


内核中通过下述结构体来表示一个具体的工作:

struct work_struct
{
	unsigned long pending;//这个工作是否正在等待处理
	struct list_head entry;//链接所有工作的链表,形成工作队列
	void (*func)(void *);//处理函数
	void *data;//传递给处理函数的参数
	void *wq_data;//内部使用数据
	struct timer_list timer;//延迟的工作队列所用到的定时器
};

而这些工作(结构体)链接成的链表就是所谓的工作队列。工作者线程会在被唤醒时执行链表上的所有工作,当一个工作被执行完毕后,相应的work_struct结构体也会被删除。当这个工作链表上没有工作时,工作线程就会休眠。

通过如下宏可以创建一个要推后的完成的工作:

DECLARE_WORK(name,void(*func)(void*),void *data);

也可以通过下述宏动态创建一个工作:

INIT_WORK(struct work_struct *work,void(*func)(void*),void *data);

与tasklet类似,每个工作都有具体的工作队列处理函数,原型如下:

void work_handler(void *data)

将工作队列机制对应到具体的中断程序中,即那些被推后的工作将会在func所指向的那个工作队列处理函数中被执行。

实现了工作队列处理函数后,就需要schedule_work函数对这个工作进行调度,就像这样:

schedule_work(&work);

这样work会马上就被调度,一旦工作线程被唤醒,这个工作就会被执行(因为其所在工作队列会被执行)。

中断下半部-tasklet

4 10 月, 2010

本文包含那些内容?
tasklet机制概述;tasklet机制的使用方法;一个阐述tasklet机制调用关系的举例。
本文适合那些人阅读?
想了解linuxer;学习驱动开发的beginner;学习内核模块编程beginner;其他super linux NBer;
参考书籍:
Linux内核设计与实现
Linux操作系统原理与应用


tasklet的实现


tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。正如在前文中你所知道的那样,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。对应到我们此刻所说的tasklet就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用tasklet,如下图示。

tasklet由tasklet_struct结构体来表示,每一个这样的结构体就表示一个tasklet。在<linux/interrupt.h>中可以看到如下的定义:

tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

在这个结构体中,第一个成员代表链表中的下一个tasklet。第二个变量代表此刻tasklet的状态,一般为TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。count成员是一个引用计数器,只有当其值为0时候,tasklet才会被激活;否则被禁止,不能被执行。而接下来的func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。


使用tasklet


在使用tasklet前,必须首先创建一个tasklet_struct类型的变量。通常有两种方法:静态创建和动态创建。这样官方的说法仍然使我们不能理解这两种创建到底是怎么一回事。不够透过源码来分析倒是可以搞明白。

在<linux/interrupt.h>中的两个宏:

 464#define DECLARE_TASKLET(name, func, data) \
 465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
 466
 467#define DECLARE_TASKLET_DISABLED(name, func, data) \
 468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

就是我们进行静态创建tasklet的两种方法。通过第一个宏创建的tasklet处于激活状态,再通过调度函数被挂起尽而被内核执行;而通过第二个宏创建的tasklet处于禁止状态。从两个宏的定义可以看到,所谓的静态创建就是直接定义个一个名为name的tasklet_struct类型的变量,并将宏中各个参数相应的赋值给这个name变量的各个成员。注意,两个宏在功能上差异就在于对name变量count成员的赋值上,具体原因在第一部分已经说明。也许你对ATOMIC_INIT这样的初始化方式感到疑惑,那么看完定义后,你就会一目了然:

 //在arch/x86/include/asm/atomic.h中
 15#define ATOMIC_INIT(i)  { (i) }
 //在linux/types.h中
 190typedef struct {
 191        int counter;
 192} atomic_t;

与静态创建相对的是动态创建,通过给tasklet_init函数传递一个事先定义的指针,来动态创建一个tasklet。这个函数源码如下。

 470void tasklet_init(struct tasklet_struct *t,
 471                  void (*func)(unsigned long), unsigned long data)
 472{
 473        t->next = NULL;
 474        t->state = 0;
 475        atomic_set(&t->count, 0);
 476        t->func = func;
 477        t->data = data;
 478}

相信你在阅读上面的代码是基本上没有什么难以理解的地方,不过这里还是要特别说明一下atomic_set函数:

  //在arch/x86/include/asm/atomic.h中
  35static inline void atomic_set(atomic_t *v, int i)
  36{
  37        v->counter = i;
  38}

首先tasklet_init当中,将&t->count传递给了此函数。也就是说将atomic_t类型的成员count的地址传递给了atomic_set函数。而我们在此函数中却要为count变量中的成员counter赋值。如果说我们当前要使用i,那么应该是如下的引用方法:t-》count.i。明白了吗?

ok,通过上述两种方法就可以创建一个tasklet了。同时,你应该注意到不管是上述那种创建方式都有func参数。透过上述分析的源码,我们可以看到func参数是一个函数指针,它指向的是这样的一个函数:

void tasklet_handler(unsigned long data);

如同上半部分的中断处理程序一样,这个函数需要我们自己来实现。

创建好之后,我们还要通过如下的方法对tasklet进行调度:

tasklet_schedule(&my_tasklet)

通过此函数的调用,我们的tasklet就会被挂起,等待机会被执行


一个举例


在此只分析上下两部分的调用关系,完整代码在这里查看。

//define a argument of tasklet struct
static struct tasklet_struct mytasklet;

static void mytasklet_handler(unsigned long data)
{
	printk("This is tasklet handler..\n");
}

static irqreturn_t myirq_handler(int irq,void* dev)
{
	static int count=0;
	if(count<10)
	{
		printk("-----------%d start--------------------------\n",count+1);
            	printk("The interrupt handeler is working..\n");
             	printk("The most of interrupt work will be done by following tasklet..\n");
            	tasklet_init(&mytasklet,mytasklet_handler,0);
          	tasklet_schedule(&mytasklet);
             	printk("The top half has been done and bottom half will be processed..\n");
	}
	count++;
      	return IRQ_HANDLED;
}

从代码中可以看到,在上半部中通过调用tasklet,使得对时间要求宽松的那部分中断程序推后执行。

对中断进行上部分和下部分的划分

3 10 月, 2010

上文中我们通过一个简单的例子分析了一个中断程序的基本结构。可以看到,中断处理程序在处理中断时起到了关键作用,也是一个中断程序必不可少的部分。不过,现如今的中断处理流程都会分为两部分:上半部分(top half)和下半部分(bottom half)。为什么要将一个中断分为如此两部分?下面的几个经典原因可以很好的诠释这个问题。

1.中断可以随时的打断处理机对其他程序的执行,如果被打断的代码对系统很重要,那么此时中断处理程序的执行时间应该是越短越好。

2.通过上文我们知道,中断处理程序正在执行时,会屏蔽同条中断线上的中断请求;而更严重的是,如果设置了IRQF_DISABLED,那么该中断服务程序执行是会屏蔽所有其他的中断请求。那么此时应该让中断处理程序执行的越快越好。

上面的几个例子都要求中断服务程序的执行时间越短越好。一般的,中断处理程序会在上半部分执行。而事实上,几乎所有的情况,上半部分就只执行中断处理程序。因此,我们可以这样认为:一个完整的中断处理流程是由中断处理程序和下半部分共同完成的。

这样划分是有一定原因的,因为我们必须有一个快速、异步而且简单的处理程序专门来负责对硬件的中断请求做出快速响应,与此同时也要完成那些对时间要求很严格的操作。而那些对时间要求相对宽松,其他的剩余工作则会在稍候的任意时间执行,也就是在所谓的下半部分去执行。

总之,这样划分一个中断处理过程主要是希望减少中断处理程序的工作量(当然了,理想情况是将全部工作都抛给下半段。但是中断处理程序至少应该完成对中断请求的相应。),因为在它运行期间至少会使得同级的中断请求被屏蔽,这些都直接关系到整个系统的响应能力和性能。而在下半段执行期间,则会允许响应所有的中断。

和上半段只能通过中断处理程序实现不同的是,下半部可以通过多种机制来完成:小任务(tasklet),工作队列,软中断。在本博客后续的文章当中你会看到,不管是那种机制,它们均为下半部提供了一种执行机制,比上半部灵活多了。至于何时执行,则由内核负责。

以上是上下部分划分的基本概述,通过tasklet工作队列机制,你可以更深刻的理解下部分的执行。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key