From 6c7900930539ff64d3b58e2bf361c6a77504cea3 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Thu, 5 May 2016 18:55:39 +0800 Subject: [PATCH 01/12] add translation --- interrupts/interrupts-9.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 interrupts/interrupts-9.md diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md new file mode 100644 index 0000000..be6296d --- /dev/null +++ b/interrupts/interrupts-9.md @@ -0,0 +1,30 @@ +中断和中断处理。 第九部分。 +================================================================================ + +延后中断(软中断,Tasklets和Workqueues)介绍 +-------------------------------------------------------------------------------- + +这是[linux内核揭密](https://www.gitbook.com/book/xinqiu/linux-insides-cn/details)中断部分的第九小节,在[之前章节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们了解了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中`init_IRQ`的实现。接下来的这一节我们将继续深入学习和外部硬件中断相关的初始化。 + +在[init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c)中我们可以看到在`init_IRQ`函数后面调用了`softirq_init`函数。这个函数在源文件[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义,从名字我们可以看出,它的作用是初始化`软中断`或者也可以说是初始化`延后中断`。那么什么是延后中断?在讲解内核初始化过程的[部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-9.html)第九小结我们已经对他有了一些了解,Linux内核中一共有三种'延后中断': + +* `软中断`; +* `tasklets`; +* `工作队列`; + +在这一小节我们将详细介绍这三种实现。就像我说的,我们对这个主题有一些了解。那么,现在是时间深入了解一下了。 + +延后中断 +---------------------------------------------------------------------------------- + +对中断处理有一些严格的要求,总的来说有两种: + +* 中断处理必须快速执行完毕 +* 有时中断处理必须做很多冗长的事情 + +就像你所想到的,我们几乎不可能同时做到这两点,之前的中断被分为两部分: + +* 前半部 +* 后半部 + +`后半部`曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。这种遗留称谓现在作为名词代表所有延后中断执行的方式。伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为`ksoftirqd`(稍后将详细讨论)的内核线程。`ksoftirqd`中断处理方式几乎和硬件中断处理一样重要。中断延后处理会在系统负载较低的时候才执行一个中断的具体实现行为。如你所知,中断处理代码运行于禁止响应后续中断的中断处理上下文中,所以要避免长时间执行。但有时中断处理却又有很多的工作需要执行,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做少量的最重要工作,接下来提交第二部分到内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分就会开始执行被延后的剩余中断任务。以上是对延后中断处理的简要介绍。 From 0b307e20dfc2e66ba5cf4d67b97fab68a50c3051 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Sat, 7 May 2016 12:36:39 +0800 Subject: [PATCH 02/12] add more --- interrupts/interrupts-9.md | 160 +++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index be6296d..b7e48e0 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -28,3 +28,163 @@ * 后半部 `后半部`曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。这种遗留称谓现在作为名词代表所有延后中断执行的方式。伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为`ksoftirqd`(稍后将详细讨论)的内核线程。`ksoftirqd`中断处理方式几乎和硬件中断处理一样重要。中断延后处理会在系统负载较低的时候才执行一个中断的具体实现行为。如你所知,中断处理代码运行于禁止响应后续中断的中断处理上下文中,所以要避免长时间执行。但有时中断处理却又有很多的工作需要执行,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做少量的最重要工作,接下来提交第二部分到内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分就会开始执行被延后的剩余中断任务。以上是对延后中断处理的简要介绍。 + +就像上面说的,延后中断(或者叫`软中断`)和`tasklets`是由一些内核线程(每个处理器一个线程)来执行的。每个处理器都有自己的内核线程,名字叫做`ksoftirqd/n`,n是处理器的编号。我们可以通过系统命令`systemd-cgls`看到它们: + +``` +$ systemd-cgls -k | grep ksoft +├─ 3 [ksoftirqd/0] +├─ 13 [ksoftirqd/1] +├─ 18 [ksoftirqd/2] +├─ 23 [ksoftirqd/3] +├─ 28 [ksoftirqd/4] +├─ 33 [ksoftirqd/5] +├─ 38 [ksoftirqd/6] +├─ 43 [ksoftirqd/7] +``` + +由`spawn_ksoftirqd`函数启动这些线程。就像我们看到的,这个函数在早期的[initcall](http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/index.html)被调用。 + +```C +early_initcall(spawn_ksoftirqd); +``` + +延后中断在Linux内核编译时就静态的确定了,`open_softirq`函数负责`softirq`初始化。`open_softirq`在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义: + +```C +void open_softirq(int nr, void (*action)(struct softirq_action *)) +{ + softirq_vec[nr].action = action; +} +``` + +这个函数有两个参数: + +* `softirq_vec`数组的索引序号 +* 一个指向软中断处理函数的指针 + +我们首先来看`softirq_vec`数组: + +```C +static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; +``` + +它在同一个源文件中定义。`softirq_vec`数组包含了`NR_SOFTIRQS`(其值为10)个不同`softirq`类型的`softirq_action`。当前版本的Linux内核定义了十种软中断向量。其中两个tasklet相关,两个网络相关,两个块处理层相关,两个定时器相关,另外调度器和RCU也各占一个。所有这些都在一个枚举中定义: + +```C +enum +{ + HI_SOFTIRQ=0, + TIMER_SOFTIRQ, + NET_TX_SOFTIRQ, + NET_RX_SOFTIRQ, + BLOCK_SOFTIRQ, + BLOCK_IOPOLL_SOFTIRQ, + TASKLET_SOFTIRQ, + SCHED_SOFTIRQ, + HRTIMER_SOFTIRQ, + RCU_SOFTIRQ, + NR_SOFTIRQS +}; +``` + +以上软中断的名字在如下的数组中定义: + +```C +const char * const softirq_to_name[NR_SOFTIRQS] = { + "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL", + "TASKLET", "SCHED", "HRTIMER", "RCU" +}; +``` + +我们也可以在'/proc/softirqs'的输出中看到他们: + +``` +~$ cat /proc/softirqs + CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 + HI: 5 0 0 0 0 0 0 0 + TIMER: 332519 310498 289555 272913 282535 279467 282895 270979 + NET_TX: 2320 0 0 2 1 1 0 0 + NET_RX: 270221 225 338 281 311 262 430 265 + BLOCK: 134282 32 40 10 12 7 8 8 +BLOCK_IOPOLL: 0 0 0 0 0 0 0 0 + TASKLET: 196835 2 3 0 0 0 0 0 + SCHED: 161852 146745 129539 126064 127998 128014 120243 117391 + HRTIMER: 0 0 0 0 0 0 0 0 + RCU: 337707 289397 251874 239796 254377 254898 267497 256624 +``` + +可以看到`softirq_vec`数组的元素类型为`softirq_action`。这是软中断机制里一个重要的数据结构,它只有一个指向中断处理函数的成员: + +```C +struct softirq_action +{ + void (*action)(struct softirq_action *); +}; +``` + +现在我们可以理解到`open_softirq`函数实际上用`softirq_action`参数填充了`softirq_vec`数组。由`open_softirq`注册的延后中断处理函数会由`raise_softirq`调用。这个函数只有一个参数 -- 软中断序号`nr`。来看下它的实现: + +```C +void raise_softirq(unsigned int nr) +{ + unsigned long flags; + + local_irq_save(flags); + raise_softirq_irqoff(nr); + local_irq_restore(flags); +} +``` + +可以看到在`local_irq_save`和`local_irq_restore`两个宏中间调用了`raise_softirq_irqoff`函数。`local_irq_save`的定义位于[include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h)头文件,它保存了[eflags](https://en.wikipedia.org/wiki/FLAGS_register)寄存器中的[IF](https://en.wikipedia.org/wiki/Interrupt_flag)标志位并且禁用了当前处理器的中断。`local_irq_restore`宏定义于同一头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为`softirq`中断运行于中断上下文中,并且???????????????????? + +`raise_softirq_irqoff`函数设置当前处理器上和nr参数对应的软中断标志位(`__softirq_pending`)。这是通过以下代码做到的: + +```C +__raise_softirq_irqoff(nr); +``` + +然后,通过`in_interrupt`函数获得`irq_count`值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个cpu是否已经有软中断需要处理。如果我们不处于中断上下文中,我们就退出`raise_softirq_irqoff`函数,装回`IF`标志位并允许当前处理器的中断。如果在中断上下文中,就会调用`wakeup_softirqd`函数: + +```C +if (!in_interrupt()) + wakeup_softirqd(); +``` + +`wakeup_softirqd`函数会激活当前处理器上的`ksoftirqd`内核线程: + +```C +static void wakeup_softirqd(void) +{ + struct task_struct *tsk = __this_cpu_read(ksoftirqd); + + if (tsk && tsk->state != TASK_RUNNING) + wake_up_process(tsk); +} +``` + +每个`ksoftirqd`内核线程都运行`run_ksoftirqd`函数来检测是有有延后中断需要处理,如果有的话就会调用`__do_softirq`函数。`__do_softirq`读取当前处理器对应的`__softirq_pending`软中断标记,并调用所有已被标记中断对应的处理函数。在执行一个延后函数的同时,可能会发生新的软中断。这会导致用户态代码由于`__do_softirq`要处理很多延后中断而很长时间不能返回。为了解决这个问题,系统限制了延后中断处理的最大耗时: + +```C +unsigned long end = jiffies + MAX_SOFTIRQ_TIME; +... +... +... +restart: +while ((softirq_bit = ffs(pending))) { + ... + h->action(h); + ... +} +... +... +... +pending = local_softirq_pending(); +if (pending) { + if (time_before(jiffies, end) && !need_resched() && + --max_restart) + goto restart; +} +... +``` + From a37b13b61882534e81436475a8c22872200a086a Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Sat, 7 May 2016 12:41:13 +0800 Subject: [PATCH 03/12] fixs --- interrupts/interrupts-9.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index b7e48e0..3a40504 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -1,7 +1,7 @@ 中断和中断处理。 第九部分。 ================================================================================ -延后中断(软中断,Tasklets和Workqueues)介绍 +延后中断(软中断,Tasklets和工作队列)介绍 -------------------------------------------------------------------------------- 这是[linux内核揭密](https://www.gitbook.com/book/xinqiu/linux-insides-cn/details)中断部分的第九小节,在[之前章节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们了解了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中`init_IRQ`的实现。接下来的这一节我们将继续深入学习和外部硬件中断相关的初始化。 @@ -163,7 +163,7 @@ static void wakeup_softirqd(void) } ``` -每个`ksoftirqd`内核线程都运行`run_ksoftirqd`函数来检测是有有延后中断需要处理,如果有的话就会调用`__do_softirq`函数。`__do_softirq`读取当前处理器对应的`__softirq_pending`软中断标记,并调用所有已被标记中断对应的处理函数。在执行一个延后函数的同时,可能会发生新的软中断。这会导致用户态代码由于`__do_softirq`要处理很多延后中断而很长时间不能返回。为了解决这个问题,系统限制了延后中断处理的最大耗时: +每个`ksoftirqd`内核线程都运行`run_ksoftirqd`函数来检测是否有延后中断需要处理,如果有的话就会调用`__do_softirq`函数。`__do_softirq`读取当前处理器对应的`__softirq_pending`软中断标记,并调用所有已被标记中断对应的处理函数。在执行一个延后函数的同时,可能会发生新的软中断。这会导致用户态代码由于`__do_softirq`要处理很多延后中断而很长时间不能返回。为了解决这个问题,系统限制了延后中断处理的最大耗时: ```C unsigned long end = jiffies + MAX_SOFTIRQ_TIME; From faa8ac8368528a190427d7c909050092cc4d4d5d Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Sun, 8 May 2016 18:20:19 +0800 Subject: [PATCH 04/12] finish softirq --- interrupts/interrupts-9.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index 3a40504..54dddd4 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -188,3 +188,21 @@ if (pending) { ... ``` +除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在[arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c)的函数`do_IRQ`被调用时,这是Linux内核中执行延后中断的主要时机。在这个函数要完成中断处理时它会调用[arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h)中定义的`exiting_irq`函数,`exiting_irq`又调用了`irq_exit`。`irq_exit`函数会检测当前处理器上下文是否有延后中断,有的话就会调用`invoke_softirq`: + +```C +if (!in_interrupt() && local_softirq_pending()) + invoke_softirq(); +``` + +这样就调用到了我们上面提到的`__do_softirq`。每个`softirq`都有如下的阶段:通过`open_softirq`函数注册一个软中断,通过`raise_softirq`函数标记一个延后中断来激活它,然后所有被标记的软中断将会在Linux内核下一次执行周期性延后中断检测时得以调度,对应此类型中断的处理函数也就得以执行。 + +如上所讲,软中断是静态分配的,但这对于后期加载的内核模块是一个问题。基于软中断的`tasklets`解决了这个问题。 + +Tasklets +-------------------------------------------------------------------------------- + + + +工作队列 +-------------------------------------------------------------------------------- From 2a9cae1544dfddea7e16531cbb145ecdae27fde2 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Sun, 8 May 2016 19:13:28 +0800 Subject: [PATCH 05/12] add more --- interrupts/interrupts-9.md | 78 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index 54dddd4..06de7ff 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -202,6 +202,84 @@ if (!in_interrupt() && local_softirq_pending()) Tasklets -------------------------------------------------------------------------------- +如果你阅读Linux内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是`tasklets`。正如上面说的,`tasklets`是构建于`softirq`中断之上,他是基于下面两个软中断实现的: + +* `TASKLET_SOFTIRQ`; +* `HI_SOFTIRQ`. + +简而言之,`tasklets`是运行时分配和初始化的软中断。和软中断不同的是,同一类型的`tasklets`可以同一时间运行在不同的处理器上。我们已经了解到一些软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的`softirq_init`函数实现,这个函数在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义如下: + +```C +void __init softirq_init(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + per_cpu(tasklet_vec, cpu).tail = + &per_cpu(tasklet_vec, cpu).head; + per_cpu(tasklet_hi_vec, cpu).tail = + &per_cpu(tasklet_hi_vec, cpu).head; + } + + open_softirq(TASKLET_SOFTIRQ, tasklet_action); + open_softirq(HI_SOFTIRQ, tasklet_hi_action); +} +``` + +可以看到在函数开头定义了一个integer类型的变量cpu。接下来他会作为参数传递给宏`for_each_possible_cpu`来获得系统中所有的处理器。如果`possible_cpu`对你来说是一个新的术语,你可以阅读[CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html)章节来了解更多知识。简单的说,`possible_cpu`是系统运行期间随时插入的处理器集合。所有的`possible processor`存储在`cpu_possible_bits`位图中,你可以在[kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)中找到他的定义: + +```C +static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly; +... +... +... +const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits); +``` + +好了,我们定义了integer类型变量`cpu`并且通过`for_each_possible_cpu`宏遍历了所有处理器,初始化了两个`per-cpu`变量: + +* `tasklet_vec`; +* `tasklet_hi_vec`; + +这两个`per-cpu`变量和`softirq_init`函数都定义在[code](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中,他们被定义为`tasklet_head`类型: + +```C +static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); +static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec); +``` + +`tasklet_head`结构代表一组`Tasklets`,它包含两个成员,head和tail: + +```C +struct tasklet_head { + struct tasklet_struct *head; + struct tasklet_struct **tail; +}; +``` + +`tasklet_struct`数据类型在[include/linux/interrupt.h](https://github.com/torvalds/linux/blob/master/include/linux/interrupt.h)中定义,它代表一个`Tasklet`。这本书之前部分我们没有见过这个单词,那我们先试着理解一下`Tasklet`究竟为何物。实际上,`Tasklet`是处理延后中断的一种机制,来看一下`tasklet_struct`的具体定义: + +```C +struct tasklet_struct +{ + struct tasklet_struct *next; + unsigned long state; + atomic_t count; + void (*func)(unsigned long); + unsigned long data; +}; +``` + +这个数据结构包含有下面5个成员: + +* 调度队列中的下一个`Tasklet`; +* 当前这个`Tasklet`的状态; +* 代表这个`Tasklet`是否处于活动状态; +* `Tasklet`的回调函数; +* 回调的参数. + + + 工作队列 From c189ef914458c2512deea9cbb8d6a6927e318641 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Mon, 9 May 2016 18:52:35 +0800 Subject: [PATCH 06/12] finish tasklets --- interrupts/interrupts-9.md | 97 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index 06de7ff..9399e0a 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -278,8 +278,105 @@ struct tasklet_struct * `Tasklet`的回调函数; * 回调的参数. +上面代码中,在`softirq_init`函数中初始化了两个tasklets数组:`tasklet_vec`和`tasklet_hi_vec`。Tasklets和高优先级Tasklets分别存储于这两个数组中。初始化完成后我们看到代码[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)在`softirq_init`函数的最后又两次调用了`open_softirq`: +```C +open_softirq(TASKLET_SOFTIRQ, tasklet_action); +open_softirq(HI_SOFTIRQ, tasklet_hi_action); +``` +`open_softirq`函数的主要作用是用软中断处理函数初始化软中断,接下来让我们看看它是怎么做的。和Tasklets相关的软中断处理函数有两个,分别是`tasklet_action`和`tasklet_hi_action`。其中`tasklet_hi_action`和`HI_SOFTIRQ`关联在一起,`tasklet_action`和`TASKLET_SOFTIRQ`关联在一起。 + +Linux内核提供一些API供操作Tasklets之用。首先是`tasklet_init`函数,它接受一个`task_struct`数据结构,一个处理函数,和另外一个参数,并利用这些参数来初始化所给的`task_struct`结构: + +```C +void tasklet_init(struct tasklet_struct *t, + void (*func)(unsigned long), unsigned long data) +{ + t->next = NULL; + t->state = 0; + atomic_set(&t->count, 0); + t->func = func; + t->data = data; +} +``` + +另外还有如下两个宏可以静态的初始化一个tasklet: + +```C +DECLARE_TASKLET(name, func, data); +DECLARE_TASKLET_DISABLED(name, func, data); +``` + +Linux内核提供三个函数标记一个tasklet已经准备就绪: + +```C +void tasklet_schedule(struct tasklet_struct *t); +void tasklet_hi_schedule(struct tasklet_struct *t); +void tasklet_hi_schedule_first(struct tasklet_struct *t); +``` + +第一个函数使用普通优先级调度一个tasklet,第二个使用高优先级,第三个则用更高优先级。所有这三个函数的实现都很类似,所以我们只看一下第一个`tasklet_schedule`的实现: + +```C +static inline void tasklet_schedule(struct tasklet_struct *t) +{ + if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) + __tasklet_schedule(t); +} + +void __tasklet_schedule(struct tasklet_struct *t) +{ + unsigned long flags; + + local_irq_save(flags); + t->next = NULL; + *__this_cpu_read(tasklet_vec.tail) = t; + __this_cpu_write(tasklet_vec.tail, &(t->next)); + raise_softirq_irqoff(TASKLET_SOFTIRQ); + local_irq_restore(flags); +} +``` + +我们看到它检测并设置所给的tasklet为`TASKLET_STATE_SCHED`状态,然后以所给tasklet为参数执行了`__tasklet_schedule`函数。`__tasklet_schedule`看起来和前面见到的`raise_softirq`很像。一开始它保存中断标志并禁用中断,继而将新的tasklet添加到`tasklet_vec`,然后调用了我们前面见过的`raise_softirq_irqoff`函数。当Linux内核调度器决定去运行一个延后函数,`tasklet_action`函数会被被作为和`TASKLET_SOFTIRQ`相关联的延后函数调用。同样的,`tasklet_hi_action`会被作为和`HI_SOFTIRQ`相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action`使用`tasklet_vec`而`tasklet_hi_action`使用`tasklet_hi_vec`。 + +让我们看下`tasklet_action`函数的实现: + +```C +static void tasklet_action(struct softirq_action *a) +{ + local_irq_disable(); + list = __this_cpu_read(tasklet_vec.head); + __this_cpu_write(tasklet_vec.head, NULL); + __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head)); + local_irq_enable(); + + while (list) { + if (tasklet_trylock(t)) { + t->func(t->data); + tasklet_unlock(t); + } + ... + ... + ... + } +} +``` + +在`tasklet_action`开始时利用`local_irq_disable`宏禁用了当前处理器的中断(你可以阅读本书[第二部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-2.html)了解更多关于此宏的信息)。接下来获取到当前处理器对应的普通优先级tasklet列表并把它设置为`NULL`,这是因为所有的tasklet都将被执行。然后使能当前处理器的中断,循环遍历tasklet列表,每一次遍历都会对当前tasklet调用`tasklet_trylock`函数来更新它的状态为`TASKLET_STATE_RUN`: + +```C +static inline int tasklet_trylock(struct tasklet_struct *t) +{ + return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state); +} +``` + +如果这个操作成功了就会执行此tasklet的处理函数(我们在`tasklet_init`中所设置的),然后调用`tasklet_unlock`函数清除他的`TASKLET_STATE_RUN`状态。 + +通常情况下,这就是`tasklet`的所有概念。当然这些还不足以覆盖所有的`tasklets`,但是我想这是一个继续学习下去的很好的切入点。 + +`tasklets`在Linux内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后延后函数 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 工作队列 From f528fd0e906443fcf08ad25c882c3bcd69aee759 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Tue, 10 May 2016 10:23:43 +0800 Subject: [PATCH 07/12] add more --- interrupts/interrupts-9.md | 96 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index 9399e0a..bbb79ca 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -381,3 +381,99 @@ static inline int tasklet_trylock(struct tasklet_struct *t) 工作队列 -------------------------------------------------------------------------------- + +`工作队列`是另外一个处理延后函数的概念,它大体上和`tasklets`类似。工作队列运行于内核进程上下文,而`tasklets`运行于软中断上下文。这意味着`工作队列`函数不必像`tasklets`一样必须是原子性的。Tasklets总是运行于它提交自的那个处理器,工作队列在默认情况下也是这样。`工作队列`在Linux内核代码[kernel/workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c)中由如下的数据结构表示: + +```C +struct worker_pool { + spinlock_t lock; + int cpu; + int node; + int id; + unsigned int flags; + + struct list_head worklist; + int nr_workers; +... +... +... +``` + +因为这个结构有非常多的成员,这里就不把它们全部罗列出来,下面只讨论上面列出的这几个。 + +工作队列最基础的用法,是作为创建内核线程的接口来处理提交到队列里的工作任务。所有这些内核线程称之为`worker thread`。工作队列内的任务是由代码[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)中定义的`work_struct`表示的,起定义如下: + +```C +struct work_struct { + atomic_long_t data; + struct list_head entry; + work_func_t func; +#ifdef CONFIG_LOCKDEP + struct lockdep_map lockdep_map; +#endif +}; +``` + +这里有两个字段比较有意思:`func`--将被`工作队列`调度执行的函数,`data`--这个函数的参数。Linux内核提供了称之为`kworker`的特定于每个cpu的内核线程: + +``` +systemd-cgls -k | grep kworker +├─ 5 [kworker/0:0H] +├─ 15 [kworker/1:0H] +├─ 20 [kworker/2:0H] +├─ 25 [kworker/3:0H] +├─ 30 [kworker/4:0H] +... +... +... +``` + +这些线程会被用来调度执行工作队列的延后函数(就像`ksoftirqd`之于`软中断`)。除此之外我们还可以为一个`工作队列`创建一个新的工作线程。Linux内核提供了如下宏静态创建一个队列: + +```C +#define DECLARE_WORK(n, f) \ + struct work_struct n = __WORK_INITIALIZER(n, f) +``` + +它需要两个参数:工作队列的名字和工作队列的函数。我们还可以在运行时动态创建: + +```C +#define INIT_WORK(_work, _func) \ + __INIT_WORK((_work), (_func), 0) + +#define __INIT_WORK(_work, _func, _onstack) \ + do { \ + __init_work((_work), _onstack); \ + (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \ + INIT_LIST_HEAD(&(_work)->entry); \ + (_work)->func = (_func); \ + } while (0) +``` + +这个宏需要一个`work_struct`数据结构作为将要创建的工作队列,和一个将在这个队列里调度运行的函数。通过这其中一个宏穿件一个`work`后,我们需要把它放到`工作队列`中去。可以通过`queue_work`或者`queue_delayed_work`来做到这一点: + +```C +static inline bool queue_work(struct workqueue_struct *wq, + struct work_struct *work) +{ + return queue_work_on(WORK_CPU_UNBOUND, wq, work); +} +``` + +`queue_work`只是调用了`queue_work_on`函数指定相应的处理器。注意这里给`queue_work_on`函数传递了`WORK_CPU_UNBOUND`参数,它作为代表工作队列要绑定到哪一个处理器的枚举一员,定义于[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。`queue_work_on`函数测试并设置所给`任务`的`WORK_STRUCT_PENDING_BIT`标志位,然后以所给的工作队列和工作任务为参数执行`__queue_work`函数: + +```C +bool queue_work_on(int cpu, struct workqueue_struct *wq, + struct work_struct *work) +{ + bool ret = false; + ... + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) { + __queue_work(cpu, wq, work); + ret = true; + } + ... + return ret; +} +``` + From 87d25ddf89a9bc6355d832e3a748c2ef23d75990 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Wed, 11 May 2016 10:37:54 +0800 Subject: [PATCH 08/12] finish all --- interrupts/interrupts-9.md | 49 +++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index bbb79ca..d9e99a5 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -1,4 +1,4 @@ -中断和中断处理。 第九部分。 +中断和中断处理(九) ================================================================================ 延后中断(软中断,Tasklets和工作队列)介绍 @@ -477,3 +477,50 @@ bool queue_work_on(int cpu, struct workqueue_struct *wq, } ``` +`__queue_work`函数得到参数`work poll`。是的,是`work poll`而不是`workqueue`。实际上,所有的`works`都没有放在`workqueue`中,而是放在Linux内核中由`worker_pool`数据结构所定义的`work poll`。如上所述,`workqueue_struct`数据结构的`pwqs`成员代表一个`worker_pool`列表。当我们创建一个`workqueue`,????。每一个和`worker_pool`相关联的`pool_workqueue`都分配在相同的处理器上对应的优先级队列。??????。在`__queue_work`函数里使用`raw_smp_processor_id`设置cpu为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给`work_struct`对应的`pool_workqueue`并将`work`插入到`workqueue`: + +```C +static void __queue_work(int cpu, struct workqueue_struct *wq, + struct work_struct *work) +{ +... +... +... +if (req_cpu == WORK_CPU_UNBOUND) + cpu = raw_smp_processor_id(); + +if (!(wq->flags & WQ_UNBOUND)) + pwq = per_cpu_ptr(wq->cpu_pwqs, cpu); +else + pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu)); +... +... +... +insert_work(pwq, work, worklist, work_flags); +``` + +现在我们可以创建`works`和`workqueue`,我们需要知道他们究竟会在何时被执行。就像前面提到的,所有的`works`都会在内核线程中执行。当内核线程得到调度,它开始执行`workqueue`中的`works`。每一个工作队列内核线程都会在`worker_thread`函数里执行一个循环。这些内核线程会做很多不同的事情,其中一些和本章前面提到的很类似。当开始执行时,所有的`work_struct`和`works`都会从他的`workqueue`移除。 + + +总结 +-------------------------------------------------------------------------------- + +现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九部分。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了`IRQs`的初始化和`irq_desc`数据结构。在这一节我们看到了用于延后函数的三个概念:`软中断`,`tasklet`和`工作队列`。 + +下一节将是`中断和中断处理`的最后一节。我们将会了解真正的硬件驱动,并试着学习它是怎样和中断子系统一起工作的。 + +如果你有任何问题或建议,请给我发评论或者给我发[Twitter](https://twitter.com/0xAX)。 + +**请注意英语并不是我的母语,我为任何表达不清楚的地方感到抱歉。如果你发现任何错误请发PR到[linux-insides](https://github.com/0xAX/linux-insides)。(译者注:翻译问题请发PR到[linux-insides-cn](https://www.gitbook.com/book/xinqiu/linux-insides-cn))** + + +链接 +-------------------------------------------------------------------------------- + +* [initcall](http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/index.html) +* [IF](https://en.wikipedia.org/wiki/Interrupt_flag) +* [eflags](https://en.wikipedia.org/wiki/FLAGS_register) +* [CPU masks](http://0xax.gitbooks.io/linux-insides/content/Concepts/cpumask.html) +* [per-cpu](http://0xax.gitbooks.io/linux-insides/content/Concepts/per-cpu.html) +* [Workqueue](https://github.com/torvalds/linux/blob/master/Documentation/workqueue.txt) +* [Previous part](http://0xax.gitbooks.io/linux-insides/content/interrupts/interrupts-8.html) From 40622ff20317228184f3e5bbf518d36d4c8d2a8f Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Fri, 13 May 2016 14:22:06 +0800 Subject: [PATCH 09/12] update --- interrupts/interrupts-9.md | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index d9e99a5..898d9a4 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -4,20 +4,9 @@ 延后中断(软中断,Tasklets和工作队列)介绍 -------------------------------------------------------------------------------- -这是[linux内核揭密](https://www.gitbook.com/book/xinqiu/linux-insides-cn/details)中断部分的第九小节,在[之前章节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们了解了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中`init_IRQ`的实现。接下来的这一节我们将继续深入学习和外部硬件中断相关的初始化。 +这是Linux内核[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节,在[上一节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们分析了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中的`init_IRQ`实现。接下来的这一节我们将继续深入学习外部硬件中断的初始化。 -在[init/main.c](https://github.com/torvalds/linux/blob/master/init/main.c)中我们可以看到在`init_IRQ`函数后面调用了`softirq_init`函数。这个函数在源文件[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义,从名字我们可以看出,它的作用是初始化`软中断`或者也可以说是初始化`延后中断`。那么什么是延后中断?在讲解内核初始化过程的[部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-9.html)第九小结我们已经对他有了一些了解,Linux内核中一共有三种'延后中断': - -* `软中断`; -* `tasklets`; -* `工作队列`; - -在这一小节我们将详细介绍这三种实现。就像我说的,我们对这个主题有一些了解。那么,现在是时间深入了解一下了。 - -延后中断 ----------------------------------------------------------------------------------- - -对中断处理有一些严格的要求,总的来说有两种: +中断处理会有一些特点,其中最主要的两个是: * 中断处理必须快速执行完毕 * 有时中断处理必须做很多冗长的事情 @@ -27,9 +16,20 @@ * 前半部 * 后半部 -`后半部`曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。这种遗留称谓现在作为名词代表所有延后中断执行的方式。伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为`ksoftirqd`(稍后将详细讨论)的内核线程。`ksoftirqd`中断处理方式几乎和硬件中断处理一样重要。中断延后处理会在系统负载较低的时候才执行一个中断的具体实现行为。如你所知,中断处理代码运行于禁止响应后续中断的中断处理上下文中,所以要避免长时间执行。但有时中断处理却又有很多的工作需要执行,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做少量的最重要工作,接下来提交第二部分到内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分就会开始执行被延后的剩余中断任务。以上是对延后中断处理的简要介绍。 +`后半部`曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。现在它已作为一个遗留称谓代表内核中所有延后中断的机制。如你所知,中断处理代码运行于中断处理上下文中,此时禁止响应后续的中断,所以要避免中断处理代码长时间执行。但有些中断却又需要执行很多工作,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做尽量少的重要工作,接下来提交第二部分给内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分被延后的剩余任务就会开始执行。 -就像上面说的,延后中断(或者叫`软中断`)和`tasklets`是由一些内核线程(每个处理器一个线程)来执行的。每个处理器都有自己的内核线程,名字叫做`ksoftirqd/n`,n是处理器的编号。我们可以通过系统命令`systemd-cgls`看到它们: +当前实现延后中断的有如下三种途径: + +* `软中断`; +* `tasklets`; +* `工作队列`; + +在这一小节我们将详细介绍这三种实现,现在是时间深入了解一下了。 + +软中断 +---------------------------------------------------------------------------------- + +伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为`ksoftirqd`(稍后将详细讨论)的内核线程。每个处理器都有自己的内核线程,名字叫做`ksoftirqd/n`,n是处理器的编号。我们可以通过系统命令`systemd-cgls`看到它们: ``` $ systemd-cgls -k | grep ksoft @@ -49,7 +49,7 @@ $ systemd-cgls -k | grep ksoft early_initcall(spawn_ksoftirqd); ``` -延后中断在Linux内核编译时就静态的确定了,`open_softirq`函数负责`softirq`初始化。`open_softirq`在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义: +软中断在Linux内核编译时就静态的确定了。`open_softirq`函数负责`softirq`初始化,它在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义: ```C void open_softirq(int nr, void (*action)(struct softirq_action *)) @@ -69,7 +69,7 @@ void open_softirq(int nr, void (*action)(struct softirq_action *)) static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; ``` -它在同一个源文件中定义。`softirq_vec`数组包含了`NR_SOFTIRQS`(其值为10)个不同`softirq`类型的`softirq_action`。当前版本的Linux内核定义了十种软中断向量。其中两个tasklet相关,两个网络相关,两个块处理层相关,两个定时器相关,另外调度器和RCU也各占一个。所有这些都在一个枚举中定义: +它在同一源文件中定义。`softirq_vec`数组包含了`NR_SOFTIRQS`(其值为10)个不同`softirq`类型的`softirq_action`。当前版本的Linux内核定义了十种软中断向量。其中两个tasklet相关,两个网络相关,两个块处理相关,两个定时器相关,另外调度器和RCU也各占一个。所有这些都在一个枚举中定义: ```C enum @@ -114,7 +114,7 @@ BLOCK_IOPOLL: 0 0 0 0 0 0 RCU: 337707 289397 251874 239796 254377 254898 267497 256624 ``` -可以看到`softirq_vec`数组的元素类型为`softirq_action`。这是软中断机制里一个重要的数据结构,它只有一个指向中断处理函数的成员: +可以看到`softirq_vec`数组的类型为`softirq_action`。这是软中断机制里一个重要的数据结构,它只有一个指向中断处理函数的成员: ```C struct softirq_action @@ -136,7 +136,7 @@ void raise_softirq(unsigned int nr) } ``` -可以看到在`local_irq_save`和`local_irq_restore`两个宏中间调用了`raise_softirq_irqoff`函数。`local_irq_save`的定义位于[include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h)头文件,它保存了[eflags](https://en.wikipedia.org/wiki/FLAGS_register)寄存器中的[IF](https://en.wikipedia.org/wiki/Interrupt_flag)标志位并且禁用了当前处理器的中断。`local_irq_restore`宏定义于同一头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为`softirq`中断运行于中断上下文中,并且???????????????????? +可以看到在`local_irq_save`和`local_irq_restore`两个宏中间调用了`raise_softirq_irqoff`函数。`local_irq_save`的定义位于[include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h)头文件,它保存了[eflags](https://en.wikipedia.org/wiki/FLAGS_register)寄存器中的[IF](https://en.wikipedia.org/wiki/Interrupt_flag)标志位并且禁用了当前处理器的中断。`local_irq_restore`宏定义于相同头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为将要运行的`softirq`中断处理运行于中断上下文中。 `raise_softirq_irqoff`函数设置当前处理器上和nr参数对应的软中断标志位(`__softirq_pending`)。这是通过以下代码做到的: @@ -144,7 +144,7 @@ void raise_softirq(unsigned int nr) __raise_softirq_irqoff(nr); ``` -然后,通过`in_interrupt`函数获得`irq_count`值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个cpu是否已经有软中断需要处理。如果我们不处于中断上下文中,我们就退出`raise_softirq_irqoff`函数,装回`IF`标志位并允许当前处理器的中断。如果在中断上下文中,就会调用`wakeup_softirqd`函数: +然后,通过`in_interrupt`函数获得`irq_count`值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个cpu是否处于中断环境。如果我们处于中断上下文中,我们就退出`raise_softirq_irqoff`函数,装回`IF`标志位并允许当前处理器的中断。如果不在中断上下文中,就会调用`wakeup_softirqd`函数: ```C if (!in_interrupt()) @@ -188,26 +188,26 @@ if (pending) { ... ``` -除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在[arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c)的函数`do_IRQ`被调用时,这是Linux内核中执行延后中断的主要时机。在这个函数要完成中断处理时它会调用[arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h)中定义的`exiting_irq`函数,`exiting_irq`又调用了`irq_exit`。`irq_exit`函数会检测当前处理器上下文是否有延后中断,有的话就会调用`invoke_softirq`: +除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在[arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c)的`do_IRQ`函数被调用时,这是Linux内核中执行延后中断的主要时机。在这个函数将要完成中断处理时它会调用[arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h)中定义的`exiting_irq`函数,`exiting_irq`又调用了`irq_exit`。`irq_exit`函数会检测当前处理器上下文是否有延后中断,有的话就会调用`invoke_softirq`: ```C if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); ``` -这样就调用到了我们上面提到的`__do_softirq`。每个`softirq`都有如下的阶段:通过`open_softirq`函数注册一个软中断,通过`raise_softirq`函数标记一个延后中断来激活它,然后所有被标记的软中断将会在Linux内核下一次执行周期性延后中断检测时得以调度,对应此类型中断的处理函数也就得以执行。 +这样就调用到了我们上面提到的`__do_softirq`。每个`softirq`都有如下的阶段:通过`open_softirq`函数注册一个软中断,通过`raise_softirq`函数标记一个软中断来激活它,然后所有被标记的软中断将会在Linux内核下一次执行周期性软中断检测时得以调度,对应此类型软中断的处理函数也就得以执行。 -如上所讲,软中断是静态分配的,但这对于后期加载的内核模块是一个问题。基于软中断的`tasklets`解决了这个问题。 +从上述可看出,软中断是静态分配的,这对于后期加载的内核模块将是一个问题。基于软中断实现的`tasklets`解决了这个问题。 Tasklets -------------------------------------------------------------------------------- -如果你阅读Linux内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是`tasklets`。正如上面说的,`tasklets`是构建于`softirq`中断之上,他是基于下面两个软中断实现的: +如果你阅读Linux内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是`tasklets`。正如上面说的,`tasklets`构建于`softirq`中断之上,他是基于下面两个软中断实现的: * `TASKLET_SOFTIRQ`; * `HI_SOFTIRQ`. -简而言之,`tasklets`是运行时分配和初始化的软中断。和软中断不同的是,同一类型的`tasklets`可以同一时间运行在不同的处理器上。我们已经了解到一些软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的`softirq_init`函数实现,这个函数在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义如下: +简而言之,`tasklets`是运行时分配和初始化的软中断。和软中断不同的是,同一类型的`tasklets`可以在同一时间运行于不同的处理器上。我们已经了解到一些关于软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的`softirq_init`函数实现,这个函数在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义如下: ```C void __init softirq_init(void) @@ -226,7 +226,7 @@ void __init softirq_init(void) } ``` -可以看到在函数开头定义了一个integer类型的变量cpu。接下来他会作为参数传递给宏`for_each_possible_cpu`来获得系统中所有的处理器。如果`possible_cpu`对你来说是一个新的术语,你可以阅读[CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html)章节来了解更多知识。简单的说,`possible_cpu`是系统运行期间随时插入的处理器集合。所有的`possible processor`存储在`cpu_possible_bits`位图中,你可以在[kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)中找到他的定义: +可以看到在函数开头定义了一个名为cpu的integer类型变量。接下来他会作为参数传递给宏`for_each_possible_cpu`来获得系统中所有的处理器。如果`possible_cpu`对你来说是一个新的术语,你可以阅读[CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html)章节来了解更多知识。简单的说,`possible_cpu`是系统运行期间插入的处理器集合。所有的`possible processor`存储在`cpu_possible_bits`位图中,你可以在[kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)中找到他的定义: ```C static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly; @@ -241,7 +241,7 @@ const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits); * `tasklet_vec`; * `tasklet_hi_vec`; -这两个`per-cpu`变量和`softirq_init`函数都定义在[code](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中,他们被定义为`tasklet_head`类型: +这两个`per-cpu`变量和`softirq_init`函数都定义在相同[代码](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中,他们被定义为`tasklet_head`类型: ```C static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); @@ -274,9 +274,9 @@ struct tasklet_struct * 调度队列中的下一个`Tasklet`; * 当前这个`Tasklet`的状态; -* 代表这个`Tasklet`是否处于活动状态; +* 这个`Tasklet`是否处于活动状态; * `Tasklet`的回调函数; -* 回调的参数. +* 回调函数的参数; 上面代码中,在`softirq_init`函数中初始化了两个tasklets数组:`tasklet_vec`和`tasklet_hi_vec`。Tasklets和高优先级Tasklets分别存储于这两个数组中。初始化完成后我们看到代码[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)在`softirq_init`函数的最后又两次调用了`open_softirq`: @@ -285,7 +285,7 @@ open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action); ``` -`open_softirq`函数的主要作用是用软中断处理函数初始化软中断,接下来让我们看看它是怎么做的。和Tasklets相关的软中断处理函数有两个,分别是`tasklet_action`和`tasklet_hi_action`。其中`tasklet_hi_action`和`HI_SOFTIRQ`关联在一起,`tasklet_action`和`TASKLET_SOFTIRQ`关联在一起。 +`open_softirq`函数的主要作用是初始化软中断,接下来让我们看看它是怎么做的。和Tasklets相关的软中断处理函数有两个,分别是`tasklet_action`和`tasklet_hi_action`。其中`tasklet_hi_action`和`HI_SOFTIRQ`关联在一起,`tasklet_action`和`TASKLET_SOFTIRQ`关联在一起。 Linux内核提供一些API供操作Tasklets之用。首先是`tasklet_init`函数,它接受一个`task_struct`数据结构,一个处理函数,和另外一个参数,并利用这些参数来初始化所给的`task_struct`结构: @@ -338,7 +338,7 @@ void __tasklet_schedule(struct tasklet_struct *t) } ``` -我们看到它检测并设置所给的tasklet为`TASKLET_STATE_SCHED`状态,然后以所给tasklet为参数执行了`__tasklet_schedule`函数。`__tasklet_schedule`看起来和前面见到的`raise_softirq`很像。一开始它保存中断标志并禁用中断,继而将新的tasklet添加到`tasklet_vec`,然后调用了我们前面见过的`raise_softirq_irqoff`函数。当Linux内核调度器决定去运行一个延后函数,`tasklet_action`函数会被被作为和`TASKLET_SOFTIRQ`相关联的延后函数调用。同样的,`tasklet_hi_action`会被作为和`HI_SOFTIRQ`相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action`使用`tasklet_vec`而`tasklet_hi_action`使用`tasklet_hi_vec`。 +我们看到它检测并设置所给的tasklet为`TASKLET_STATE_SCHED`状态,然后以所给tasklet为参数执行了`__tasklet_schedule`函数。`__tasklet_schedule`看起来和前面见到的`raise_softirq`很像。一开始它保存中断标志并禁用中断,继而将新的tasklet添加到`tasklet_vec`,然后调用了我们前面见过的`raise_softirq_irqoff`函数。当Linux内核调度器决定去运行一个延后函数,`tasklet_action`函数会被作为和`TASKLET_SOFTIRQ`相关联的延后函数调用。同样的,`tasklet_hi_action`会被作为和`HI_SOFTIRQ`相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action`使用`tasklet_vec`而`tasklet_hi_action`使用`tasklet_hi_vec`。 让我们看下`tasklet_action`函数的实现: @@ -374,9 +374,9 @@ static inline int tasklet_trylock(struct tasklet_struct *t) 如果这个操作成功了就会执行此tasklet的处理函数(我们在`tasklet_init`中所设置的),然后调用`tasklet_unlock`函数清除他的`TASKLET_STATE_RUN`状态。 -通常情况下,这就是`tasklet`的所有概念。当然这些还不足以覆盖所有的`tasklets`,但是我想这是一个继续学习下去的很好的切入点。 +通常情况下,这就是`tasklet`的所有概念。当然这些还不足以覆盖所有的`tasklets`,但是我想大家可以以此为切入点继续学习下去。 -`tasklets`在Linux内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后延后函数 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 +`tasklets`在Linux内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后中断机制 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 工作队列 @@ -428,7 +428,7 @@ systemd-cgls -k | grep kworker ... ``` -这些线程会被用来调度执行工作队列的延后函数(就像`ksoftirqd`之于`软中断`)。除此之外我们还可以为一个`工作队列`创建一个新的工作线程。Linux内核提供了如下宏静态创建一个队列: +这些线程会被用来调度执行工作队列的延后函数(就像`ksoftirqd`之于`软中断`)。除此之外我们还可以为一个`工作队列`创建一个新的工作线程。Linux内核提供了如下宏静态创建一个队列任务: ```C #define DECLARE_WORK(n, f) \ @@ -450,7 +450,7 @@ systemd-cgls -k | grep kworker } while (0) ``` -这个宏需要一个`work_struct`数据结构作为将要创建的工作队列,和一个将在这个队列里调度运行的函数。通过这其中一个宏穿件一个`work`后,我们需要把它放到`工作队列`中去。可以通过`queue_work`或者`queue_delayed_work`来做到这一点: +这个宏需要一个`work_struct`数据结构作为将要创建的队列任务,和一个将在这个任务里调度运行的函数。通过这两个宏的其中一个创建一个`work`后,我们需要把它放到`工作队列`中去。可以通过`queue_work`或者`queue_delayed_work`来做到这一点: ```C static inline bool queue_work(struct workqueue_struct *wq, @@ -460,7 +460,7 @@ static inline bool queue_work(struct workqueue_struct *wq, } ``` -`queue_work`只是调用了`queue_work_on`函数指定相应的处理器。注意这里给`queue_work_on`函数传递了`WORK_CPU_UNBOUND`参数,它作为代表工作队列要绑定到哪一个处理器的枚举一员,定义于[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。`queue_work_on`函数测试并设置所给`任务`的`WORK_STRUCT_PENDING_BIT`标志位,然后以所给的工作队列和工作任务为参数执行`__queue_work`函数: +`queue_work`只是调用了`queue_work_on`函数指定相应的处理器。注意这里给`queue_work_on`函数传递了`WORK_CPU_UNBOUND`参数,它作为代表队列任务要绑定到哪一个处理器的枚举一员,定义于[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。`queue_work_on`函数测试并设置所给`任务`的`WORK_STRUCT_PENDING_BIT`标志位,然后以所给的工作队列和队列任务为参数执行`__queue_work`函数: ```C bool queue_work_on(int cpu, struct workqueue_struct *wq, @@ -477,7 +477,7 @@ bool queue_work_on(int cpu, struct workqueue_struct *wq, } ``` -`__queue_work`函数得到参数`work poll`。是的,是`work poll`而不是`workqueue`。实际上,所有的`works`都没有放在`workqueue`中,而是放在Linux内核中由`worker_pool`数据结构所定义的`work poll`。如上所述,`workqueue_struct`数据结构的`pwqs`成员代表一个`worker_pool`列表。当我们创建一个`workqueue`,????。每一个和`worker_pool`相关联的`pool_workqueue`都分配在相同的处理器上对应的优先级队列。??????。在`__queue_work`函数里使用`raw_smp_processor_id`设置cpu为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给`work_struct`对应的`pool_workqueue`并将`work`插入到`workqueue`: +`__queue_work`函数得到参数`work poll`。是的,是`work poll`而不是`workqueue`。实际上,所有的`works`都没有放在`workqueue`中,而是放在Linux内核中由`worker_pool`数据结构所定义的`work poll`。如上所述,`workqueue_struct`数据结构的`pwqs`成员是一个`worker_pool`列表。当我们创建一个`workqueue`,他针对每一个处理器都创建了`worker_pool`。每一个和`worker_pool`相关联的`pool_workqueue`都分配在相同的处理器上对应的优先级队列,`workqueue`通过他们和`worker_pool`交互。在`__queue_work`函数里使用`raw_smp_processor_id`设置cpu为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给`work_struct`对应的`pool_workqueue`并将`work`插入到`workqueue`: ```C static void __queue_work(int cpu, struct workqueue_struct *wq, @@ -505,7 +505,7 @@ insert_work(pwq, work, worklist, work_flags); 总结 -------------------------------------------------------------------------------- -现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九部分。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了`IRQs`的初始化和`irq_desc`数据结构。在这一节我们看到了用于延后函数的三个概念:`软中断`,`tasklet`和`工作队列`。 +现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了`IRQs`的初始化和`irq_desc`数据结构,在这一节我们看到了用于延后函数的三个概念:`软中断`,`tasklet`和`工作队列`。 下一节将是`中断和中断处理`的最后一节。我们将会了解真正的硬件驱动,并试着学习它是怎样和中断子系统一起工作的。 From 1dfd6d195f470e89f1348ae23503d2e44ed5f442 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Fri, 13 May 2016 16:20:57 +0800 Subject: [PATCH 10/12] add space to quote --- interrupts/interrupts-9.md | 104 ++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index 898d9a4..98ea1d7 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -4,7 +4,7 @@ 延后中断(软中断,Tasklets和工作队列)介绍 -------------------------------------------------------------------------------- -这是Linux内核[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节,在[上一节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们分析了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中的`init_IRQ`实现。接下来的这一节我们将继续深入学习外部硬件中断的初始化。 +这是Linux内核[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节,在[上一节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们分析了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中的 `init_IRQ` 实现。接下来的这一节我们将继续深入学习外部硬件中断的初始化。 中断处理会有一些特点,其中最主要的两个是: @@ -16,20 +16,20 @@ * 前半部 * 后半部 -`后半部`曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。现在它已作为一个遗留称谓代表内核中所有延后中断的机制。如你所知,中断处理代码运行于中断处理上下文中,此时禁止响应后续的中断,所以要避免中断处理代码长时间执行。但有些中断却又需要执行很多工作,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做尽量少的重要工作,接下来提交第二部分给内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分被延后的剩余任务就会开始执行。 + `后半部` 曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。现在它已作为一个遗留称谓代表内核中所有延后中断的机制。如你所知,中断处理代码运行于中断处理上下文中,此时禁止响应后续的中断,所以要避免中断处理代码长时间执行。但有些中断却又需要执行很多工作,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做尽量少的重要工作,接下来提交第二部分给内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分被延后的剩余任务就会开始执行。 当前实现延后中断的有如下三种途径: -* `软中断`; -* `tasklets`; -* `工作队列`; +* `软中断` ; +* `tasklets` ; +* `工作队列` ; 在这一小节我们将详细介绍这三种实现,现在是时间深入了解一下了。 软中断 ---------------------------------------------------------------------------------- -伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为`ksoftirqd`(稍后将详细讨论)的内核线程。每个处理器都有自己的内核线程,名字叫做`ksoftirqd/n`,n是处理器的编号。我们可以通过系统命令`systemd-cgls`看到它们: +伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为 `ksoftirqd` (稍后将详细讨论)的内核线程。每个处理器都有自己的内核线程,名字叫做 `ksoftirqd/n` ,n是处理器的编号。我们可以通过系统命令 `systemd-cgls` 看到它们: ``` $ systemd-cgls -k | grep ksoft @@ -43,13 +43,13 @@ $ systemd-cgls -k | grep ksoft ├─ 43 [ksoftirqd/7] ``` -由`spawn_ksoftirqd`函数启动这些线程。就像我们看到的,这个函数在早期的[initcall](http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/index.html)被调用。 +由 `spawn_ksoftirqd` 函数启动这些线程。就像我们看到的,这个函数在早期的[initcall](http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/index.html)被调用。 ```C early_initcall(spawn_ksoftirqd); ``` -软中断在Linux内核编译时就静态的确定了。`open_softirq`函数负责`softirq`初始化,它在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义: +软中断在Linux内核编译时就静态的确定了。`open_softirq` 函数负责 `softirq` 初始化,它在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义: ```C void open_softirq(int nr, void (*action)(struct softirq_action *)) @@ -60,16 +60,16 @@ void open_softirq(int nr, void (*action)(struct softirq_action *)) 这个函数有两个参数: -* `softirq_vec`数组的索引序号 +* `softirq_vec` 数组的索引序号 * 一个指向软中断处理函数的指针 -我们首先来看`softirq_vec`数组: +我们首先来看 `softirq_vec` 数组: ```C static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; ``` -它在同一源文件中定义。`softirq_vec`数组包含了`NR_SOFTIRQS`(其值为10)个不同`softirq`类型的`softirq_action`。当前版本的Linux内核定义了十种软中断向量。其中两个tasklet相关,两个网络相关,两个块处理相关,两个定时器相关,另外调度器和RCU也各占一个。所有这些都在一个枚举中定义: +它在同一源文件中定义。`softirq_vec` 数组包含了 `NR_SOFTIRQS` (其值为10)个不同 `softirq` 类型的 `softirq_action`。当前版本的Linux内核定义了十种软中断向量。其中两个tasklet相关,两个网络相关,两个块处理相关,两个定时器相关,另外调度器和RCU也各占一个。所有这些都在一个枚举中定义: ```C enum @@ -114,7 +114,7 @@ BLOCK_IOPOLL: 0 0 0 0 0 0 RCU: 337707 289397 251874 239796 254377 254898 267497 256624 ``` -可以看到`softirq_vec`数组的类型为`softirq_action`。这是软中断机制里一个重要的数据结构,它只有一个指向中断处理函数的成员: +可以看到 `softirq_vec` 数组的类型为 `softirq_action`。这是软中断机制里一个重要的数据结构,它只有一个指向中断处理函数的成员: ```C struct softirq_action @@ -123,7 +123,7 @@ struct softirq_action }; ``` -现在我们可以理解到`open_softirq`函数实际上用`softirq_action`参数填充了`softirq_vec`数组。由`open_softirq`注册的延后中断处理函数会由`raise_softirq`调用。这个函数只有一个参数 -- 软中断序号`nr`。来看下它的实现: +现在我们可以理解到 `open_softirq` 函数实际上用 `softirq_action` 参数填充了 `softirq_vec` 数组。由 `open_softirq` 注册的延后中断处理函数会由 `raise_softirq` 调用。这个函数只有一个参数 -- 软中断序号 `nr`。来看下它的实现: ```C void raise_softirq(unsigned int nr) @@ -136,22 +136,22 @@ void raise_softirq(unsigned int nr) } ``` -可以看到在`local_irq_save`和`local_irq_restore`两个宏中间调用了`raise_softirq_irqoff`函数。`local_irq_save`的定义位于[include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h)头文件,它保存了[eflags](https://en.wikipedia.org/wiki/FLAGS_register)寄存器中的[IF](https://en.wikipedia.org/wiki/Interrupt_flag)标志位并且禁用了当前处理器的中断。`local_irq_restore`宏定义于相同头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为将要运行的`softirq`中断处理运行于中断上下文中。 +可以看到在 `local_irq_save` 和 `local_irq_restore` 两个宏中间调用了 `raise_softirq_irqoff` 函数。`local_irq_save` 的定义位于[include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h)头文件,它保存了[eflags](https://en.wikipedia.org/wiki/FLAGS_register)寄存器中的[IF](https://en.wikipedia.org/wiki/Interrupt_flag)标志位并且禁用了当前处理器的中断。`local_irq_restore` 宏定义于相同头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为将要运行的 `softirq` 中断处理运行于中断上下文中。 -`raise_softirq_irqoff`函数设置当前处理器上和nr参数对应的软中断标志位(`__softirq_pending`)。这是通过以下代码做到的: +`raise_softirq_irqoff` 函数设置当前处理器上和nr参数对应的软中断标志位(`__softirq_pending`)。这是通过以下代码做到的: ```C __raise_softirq_irqoff(nr); ``` -然后,通过`in_interrupt`函数获得`irq_count`值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个cpu是否处于中断环境。如果我们处于中断上下文中,我们就退出`raise_softirq_irqoff`函数,装回`IF`标志位并允许当前处理器的中断。如果不在中断上下文中,就会调用`wakeup_softirqd`函数: +然后,通过 `in_interrupt` 函数获得 `irq_count` 值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个cpu是否处于中断环境。如果我们处于中断上下文中,我们就退出 `raise_softirq_irqoff` 函数,装回 `IF` 标志位并允许当前处理器的中断。如果不在中断上下文中,就会调用 `wakeup_softirqd` 函数: ```C if (!in_interrupt()) wakeup_softirqd(); ``` -`wakeup_softirqd`函数会激活当前处理器上的`ksoftirqd`内核线程: +`wakeup_softirqd` 函数会激活当前处理器上的 `ksoftirqd` 内核线程: ```C static void wakeup_softirqd(void) @@ -163,7 +163,7 @@ static void wakeup_softirqd(void) } ``` -每个`ksoftirqd`内核线程都运行`run_ksoftirqd`函数来检测是否有延后中断需要处理,如果有的话就会调用`__do_softirq`函数。`__do_softirq`读取当前处理器对应的`__softirq_pending`软中断标记,并调用所有已被标记中断对应的处理函数。在执行一个延后函数的同时,可能会发生新的软中断。这会导致用户态代码由于`__do_softirq`要处理很多延后中断而很长时间不能返回。为了解决这个问题,系统限制了延后中断处理的最大耗时: +每个 `ksoftirqd` 内核线程都运行 `run_ksoftirqd` 函数来检测是否有延后中断需要处理,如果有的话就会调用 `__do_softirq` 函数。`__do_softirq` 读取当前处理器对应的 `__softirq_pending` 软中断标记,并调用所有已被标记中断对应的处理函数。在执行一个延后函数的同时,可能会发生新的软中断。这会导致用户态代码由于 `__do_softirq` 要处理很多延后中断而很长时间不能返回。为了解决这个问题,系统限制了延后中断处理的最大耗时: ```C unsigned long end = jiffies + MAX_SOFTIRQ_TIME; @@ -188,26 +188,26 @@ if (pending) { ... ``` -除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在[arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c)的`do_IRQ`函数被调用时,这是Linux内核中执行延后中断的主要时机。在这个函数将要完成中断处理时它会调用[arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h)中定义的`exiting_irq`函数,`exiting_irq`又调用了`irq_exit`。`irq_exit`函数会检测当前处理器上下文是否有延后中断,有的话就会调用`invoke_softirq`: +除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在[arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c)的 `do_IRQ` 函数被调用时,这是Linux内核中执行延后中断的主要时机。在这个函数将要完成中断处理时它会调用[arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h)中定义的 `exiting_irq` 函数,`exiting_irq` 又调用了 `irq_exit`。`irq_exit` 函数会检测当前处理器上下文是否有延后中断,有的话就会调用 `invoke_softirq`: ```C if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); ``` -这样就调用到了我们上面提到的`__do_softirq`。每个`softirq`都有如下的阶段:通过`open_softirq`函数注册一个软中断,通过`raise_softirq`函数标记一个软中断来激活它,然后所有被标记的软中断将会在Linux内核下一次执行周期性软中断检测时得以调度,对应此类型软中断的处理函数也就得以执行。 +这样就调用到了我们上面提到的 `__do_softirq` 。每个 `softirq` 都有如下的阶段:通过 `open_softirq` 函数注册一个软中断,通过 `raise_softirq` 函数标记一个软中断来激活它,然后所有被标记的软中断将会在Linux内核下一次执行周期性软中断检测时得以调度,对应此类型软中断的处理函数也就得以执行。 -从上述可看出,软中断是静态分配的,这对于后期加载的内核模块将是一个问题。基于软中断实现的`tasklets`解决了这个问题。 +从上述可看出,软中断是静态分配的,这对于后期加载的内核模块将是一个问题。基于软中断实现的 `tasklets` 解决了这个问题。 Tasklets -------------------------------------------------------------------------------- -如果你阅读Linux内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是`tasklets`。正如上面说的,`tasklets`构建于`softirq`中断之上,他是基于下面两个软中断实现的: +如果你阅读Linux内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是 `tasklets` 。正如上面说的,`tasklets` 构建于 `softirq` 中断之上,他是基于下面两个软中断实现的: * `TASKLET_SOFTIRQ`; * `HI_SOFTIRQ`. -简而言之,`tasklets`是运行时分配和初始化的软中断。和软中断不同的是,同一类型的`tasklets`可以在同一时间运行于不同的处理器上。我们已经了解到一些关于软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的`softirq_init`函数实现,这个函数在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义如下: +简而言之, `tasklets` 是运行时分配和初始化的软中断。和软中断不同的是,同一类型的 `tasklets` 可以在同一时间运行于不同的处理器上。我们已经了解到一些关于软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的 `softirq_init` 函数实现,这个函数在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义如下: ```C void __init softirq_init(void) @@ -226,7 +226,7 @@ void __init softirq_init(void) } ``` -可以看到在函数开头定义了一个名为cpu的integer类型变量。接下来他会作为参数传递给宏`for_each_possible_cpu`来获得系统中所有的处理器。如果`possible_cpu`对你来说是一个新的术语,你可以阅读[CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html)章节来了解更多知识。简单的说,`possible_cpu`是系统运行期间插入的处理器集合。所有的`possible processor`存储在`cpu_possible_bits`位图中,你可以在[kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)中找到他的定义: +可以看到在函数开头定义了一个名为cpu的integer类型变量。接下来他会作为参数传递给宏 `for_each_possible_cpu` 来获得系统中所有的处理器。如果 `possible_cpu` 对你来说是一个新的术语,你可以阅读[CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html)章节来了解更多知识。简单的说, `possible_cpu` 是系统运行期间插入的处理器集合。所有的 `possible processor` 存储在 `cpu_possible_bits` 位图中,你可以在[kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)中找到他的定义: ```C static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly; @@ -236,19 +236,19 @@ static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly; const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits); ``` -好了,我们定义了integer类型变量`cpu`并且通过`for_each_possible_cpu`宏遍历了所有处理器,初始化了两个`per-cpu`变量: +好了,我们定义了integer类型变量 `cpu` 并且通过 `for_each_possible_cpu` 宏遍历了所有处理器,初始化了两个 `per-cpu` 变量: * `tasklet_vec`; * `tasklet_hi_vec`; -这两个`per-cpu`变量和`softirq_init`函数都定义在相同[代码](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中,他们被定义为`tasklet_head`类型: +这两个 `per-cpu` 变量和 `softirq_init` 函数都定义在相同[代码](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中,他们被定义为 `tasklet_head` 类型: ```C static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec); ``` -`tasklet_head`结构代表一组`Tasklets`,它包含两个成员,head和tail: +`tasklet_head` 结构代表一组 `Tasklets` ,它包含两个成员,head和tail: ```C struct tasklet_head { @@ -257,7 +257,7 @@ struct tasklet_head { }; ``` -`tasklet_struct`数据类型在[include/linux/interrupt.h](https://github.com/torvalds/linux/blob/master/include/linux/interrupt.h)中定义,它代表一个`Tasklet`。这本书之前部分我们没有见过这个单词,那我们先试着理解一下`Tasklet`究竟为何物。实际上,`Tasklet`是处理延后中断的一种机制,来看一下`tasklet_struct`的具体定义: +`tasklet_struct` 数据类型在[include/linux/interrupt.h](https://github.com/torvalds/linux/blob/master/include/linux/interrupt.h)中定义,它代表一个 `Tasklet` 。这本书之前部分我们没有见过这个单词,那我们先试着理解一下 `Tasklet` 究竟为何物。实际上, `Tasklet` 是处理延后中断的一种机制,来看一下 `tasklet_struct` 的具体定义: ```C struct tasklet_struct @@ -272,22 +272,22 @@ struct tasklet_struct 这个数据结构包含有下面5个成员: -* 调度队列中的下一个`Tasklet`; -* 当前这个`Tasklet`的状态; -* 这个`Tasklet`是否处于活动状态; -* `Tasklet`的回调函数; +* 调度队列中的下一个 `Tasklet` ; +* 当前这个 `Tasklet` 的状态; +* 这个 `Tasklet` 是否处于活动状态; +* `Tasklet` 的回调函数; * 回调函数的参数; -上面代码中,在`softirq_init`函数中初始化了两个tasklets数组:`tasklet_vec`和`tasklet_hi_vec`。Tasklets和高优先级Tasklets分别存储于这两个数组中。初始化完成后我们看到代码[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)在`softirq_init`函数的最后又两次调用了`open_softirq`: +上面代码中,在 `softirq_init` 函数中初始化了两个tasklets数组: `tasklet_vec` 和 `tasklet_hi_vec` 。Tasklets和高优先级Tasklets分别存储于这两个数组中。初始化完成后我们看到代码[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)在 `softirq_init` 函数的最后又两次调用了 `open_softirq` : ```C open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action); ``` -`open_softirq`函数的主要作用是初始化软中断,接下来让我们看看它是怎么做的。和Tasklets相关的软中断处理函数有两个,分别是`tasklet_action`和`tasklet_hi_action`。其中`tasklet_hi_action`和`HI_SOFTIRQ`关联在一起,`tasklet_action`和`TASKLET_SOFTIRQ`关联在一起。 +`open_softirq` 函数的主要作用是初始化软中断,接下来让我们看看它是怎么做的。和Tasklets相关的软中断处理函数有两个,分别是 `tasklet_action` 和 `tasklet_hi_action` 。其中 `tasklet_hi_action` 和 `HI_SOFTIRQ` 关联在一起, `tasklet_action` 和 `TASKLET_SOFTIRQ` 关联在一起。 -Linux内核提供一些API供操作Tasklets之用。首先是`tasklet_init`函数,它接受一个`task_struct`数据结构,一个处理函数,和另外一个参数,并利用这些参数来初始化所给的`task_struct`结构: +Linux内核提供一些API供操作Tasklets之用。首先是 `tasklet_init` 函数,它接受一个 `task_struct` 数据结构,一个处理函数,和另外一个参数,并利用这些参数来初始化所给的 `task_struct` 结构: ```C void tasklet_init(struct tasklet_struct *t, @@ -316,7 +316,7 @@ void tasklet_hi_schedule(struct tasklet_struct *t); void tasklet_hi_schedule_first(struct tasklet_struct *t); ``` -第一个函数使用普通优先级调度一个tasklet,第二个使用高优先级,第三个则用更高优先级。所有这三个函数的实现都很类似,所以我们只看一下第一个`tasklet_schedule`的实现: +第一个函数使用普通优先级调度一个tasklet,第二个使用高优先级,第三个则用更高优先级。所有这三个函数的实现都很类似,所以我们只看一下第一个 `tasklet_schedule` 的实现: ```C static inline void tasklet_schedule(struct tasklet_struct *t) @@ -338,9 +338,9 @@ void __tasklet_schedule(struct tasklet_struct *t) } ``` -我们看到它检测并设置所给的tasklet为`TASKLET_STATE_SCHED`状态,然后以所给tasklet为参数执行了`__tasklet_schedule`函数。`__tasklet_schedule`看起来和前面见到的`raise_softirq`很像。一开始它保存中断标志并禁用中断,继而将新的tasklet添加到`tasklet_vec`,然后调用了我们前面见过的`raise_softirq_irqoff`函数。当Linux内核调度器决定去运行一个延后函数,`tasklet_action`函数会被作为和`TASKLET_SOFTIRQ`相关联的延后函数调用。同样的,`tasklet_hi_action`会被作为和`HI_SOFTIRQ`相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action`使用`tasklet_vec`而`tasklet_hi_action`使用`tasklet_hi_vec`。 +我们看到它检测并设置所给的tasklet为 `TASKLET_STATE_SCHED` 状态,然后以所给tasklet为参数执行了 `__tasklet_schedule` 函数。 `__tasklet_schedule` 看起来和前面见到的 `raise_softirq` 很像。一开始它保存中断标志并禁用中断,继而将新的tasklet添加到 `tasklet_vec` ,然后调用了我们前面见过的 `raise_softirq_irqoff` 函数。当Linux内核调度器决定去运行一个延后函数, `tasklet_action` 函数会被作为和 `TASKLET_SOFTIRQ` 相关联的延后函数调用。同样的, `tasklet_hi_action` 会被作为和 `HI_SOFTIRQ` 相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action` 使用 `tasklet_vec` 而 `tasklet_hi_action` 使用 `tasklet_hi_vec` 。 -让我们看下`tasklet_action`函数的实现: +让我们看下 `tasklet_action` 函数的实现: ```C static void tasklet_action(struct softirq_action *a) @@ -363,7 +363,7 @@ static void tasklet_action(struct softirq_action *a) } ``` -在`tasklet_action`开始时利用`local_irq_disable`宏禁用了当前处理器的中断(你可以阅读本书[第二部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-2.html)了解更多关于此宏的信息)。接下来获取到当前处理器对应的普通优先级tasklet列表并把它设置为`NULL`,这是因为所有的tasklet都将被执行。然后使能当前处理器的中断,循环遍历tasklet列表,每一次遍历都会对当前tasklet调用`tasklet_trylock`函数来更新它的状态为`TASKLET_STATE_RUN`: +在 `tasklet_action` 开始时利用 `local_irq_disable` 宏禁用了当前处理器的中断(你可以阅读本书[第二部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-2.html)了解更多关于此宏的信息)。接下来获取到当前处理器对应的普通优先级tasklet列表并把它设置为 `NULL` ,这是因为所有的tasklet都将被执行。然后使能当前处理器的中断,循环遍历tasklet列表,每一次遍历都会对当前tasklet调用 `tasklet_trylock` 函数来更新它的状态为 `TASKLET_STATE_RUN` : ```C static inline int tasklet_trylock(struct tasklet_struct *t) @@ -372,17 +372,17 @@ static inline int tasklet_trylock(struct tasklet_struct *t) } ``` -如果这个操作成功了就会执行此tasklet的处理函数(我们在`tasklet_init`中所设置的),然后调用`tasklet_unlock`函数清除他的`TASKLET_STATE_RUN`状态。 +如果这个操作成功了就会执行此tasklet的处理函数(我们在 `tasklet_init` 中所设置的),然后调用 `tasklet_unlock` 函数清除他的 `TASKLET_STATE_RUN` 状态。 -通常情况下,这就是`tasklet`的所有概念。当然这些还不足以覆盖所有的`tasklets`,但是我想大家可以以此为切入点继续学习下去。 +通常情况下,这就是 `tasklet` 的所有概念。当然这些还不足以覆盖所有的 `tasklets` ,但是我想大家可以以此为切入点继续学习下去。 -`tasklets`在Linux内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后中断机制 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 + `tasklets` 在Linux内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后中断机制 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 工作队列 -------------------------------------------------------------------------------- -`工作队列`是另外一个处理延后函数的概念,它大体上和`tasklets`类似。工作队列运行于内核进程上下文,而`tasklets`运行于软中断上下文。这意味着`工作队列`函数不必像`tasklets`一样必须是原子性的。Tasklets总是运行于它提交自的那个处理器,工作队列在默认情况下也是这样。`工作队列`在Linux内核代码[kernel/workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c)中由如下的数据结构表示: + `工作队列` 是另外一个处理延后函数的概念,它大体上和 `tasklets` 类似。工作队列运行于内核进程上下文,而 `tasklets` 运行于软中断上下文。这意味着 `工作队列` 函数不必像 `tasklets` 一样必须是原子性的。Tasklets总是运行于它提交自的那个处理器,工作队列在默认情况下也是这样。 `工作队列` 在Linux内核代码[kernel/workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c)中由如下的数据结构表示: ```C struct worker_pool { @@ -401,7 +401,7 @@ struct worker_pool { 因为这个结构有非常多的成员,这里就不把它们全部罗列出来,下面只讨论上面列出的这几个。 -工作队列最基础的用法,是作为创建内核线程的接口来处理提交到队列里的工作任务。所有这些内核线程称之为`worker thread`。工作队列内的任务是由代码[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)中定义的`work_struct`表示的,起定义如下: +工作队列最基础的用法,是作为创建内核线程的接口来处理提交到队列里的工作任务。所有这些内核线程称之为 `worker thread` 。工作队列内的任务是由代码[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)中定义的 `work_struct` 表示的,起定义如下: ```C struct work_struct { @@ -414,7 +414,7 @@ struct work_struct { }; ``` -这里有两个字段比较有意思:`func`--将被`工作队列`调度执行的函数,`data`--这个函数的参数。Linux内核提供了称之为`kworker`的特定于每个cpu的内核线程: +这里有两个字段比较有意思: `func` --将被 `工作队列` 调度执行的函数, `data` --这个函数的参数。Linux内核提供了称之为 `kworker` 的特定于每个cpu的内核线程: ``` systemd-cgls -k | grep kworker @@ -428,7 +428,7 @@ systemd-cgls -k | grep kworker ... ``` -这些线程会被用来调度执行工作队列的延后函数(就像`ksoftirqd`之于`软中断`)。除此之外我们还可以为一个`工作队列`创建一个新的工作线程。Linux内核提供了如下宏静态创建一个队列任务: +这些线程会被用来调度执行工作队列的延后函数(就像 `ksoftirqd` 之于 `软中断` )。除此之外我们还可以为一个 `工作队列` 创建一个新的工作线程。Linux内核提供了如下宏静态创建一个队列任务: ```C #define DECLARE_WORK(n, f) \ @@ -450,7 +450,7 @@ systemd-cgls -k | grep kworker } while (0) ``` -这个宏需要一个`work_struct`数据结构作为将要创建的队列任务,和一个将在这个任务里调度运行的函数。通过这两个宏的其中一个创建一个`work`后,我们需要把它放到`工作队列`中去。可以通过`queue_work`或者`queue_delayed_work`来做到这一点: +这个宏需要一个 `work_struct` 数据结构作为将要创建的队列任务,和一个将在这个任务里调度运行的函数。通过这两个宏的其中一个创建一个 `work` 后,我们需要把它放到 `工作队列` 中去。可以通过 `queue_work` 或者 `queue_delayed_work` 来做到这一点: ```C static inline bool queue_work(struct workqueue_struct *wq, @@ -460,7 +460,7 @@ static inline bool queue_work(struct workqueue_struct *wq, } ``` -`queue_work`只是调用了`queue_work_on`函数指定相应的处理器。注意这里给`queue_work_on`函数传递了`WORK_CPU_UNBOUND`参数,它作为代表队列任务要绑定到哪一个处理器的枚举一员,定义于[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。`queue_work_on`函数测试并设置所给`任务`的`WORK_STRUCT_PENDING_BIT`标志位,然后以所给的工作队列和队列任务为参数执行`__queue_work`函数: + `queue_work` 只是调用了 `queue_work_on` 函数指定相应的处理器。注意这里给 `queue_work_on` 函数传递了 `WORK_CPU_UNBOUND` 参数,它作为代表队列任务要绑定到哪一个处理器的枚举一员,定义于[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。 `queue_work_on` 函数测试并设置所给 `任务` 的 `WORK_STRUCT_PENDING_BIT` 标志位,然后以所给的工作队列和队列任务为参数执行 `__queue_work` 函数: ```C bool queue_work_on(int cpu, struct workqueue_struct *wq, @@ -477,7 +477,7 @@ bool queue_work_on(int cpu, struct workqueue_struct *wq, } ``` -`__queue_work`函数得到参数`work poll`。是的,是`work poll`而不是`workqueue`。实际上,所有的`works`都没有放在`workqueue`中,而是放在Linux内核中由`worker_pool`数据结构所定义的`work poll`。如上所述,`workqueue_struct`数据结构的`pwqs`成员是一个`worker_pool`列表。当我们创建一个`workqueue`,他针对每一个处理器都创建了`worker_pool`。每一个和`worker_pool`相关联的`pool_workqueue`都分配在相同的处理器上对应的优先级队列,`workqueue`通过他们和`worker_pool`交互。在`__queue_work`函数里使用`raw_smp_processor_id`设置cpu为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给`work_struct`对应的`pool_workqueue`并将`work`插入到`workqueue`: + ` __queue_work` 函数得到参数 `work poll` 。是的,是 `work poll` 而不是 `workqueue` 。实际上,所有的 `works` 都没有放在 `workqueue` 中,而是放在Linux内核中由 `worker_pool` 数据结构所定义的 `work poll` 。如上所述, `workqueue_struct` 数据结构的 `pwqs` 成员是一个 `worker_pool` 列表。当我们创建一个 `workqueue` ,他针对每一个处理器都创建了 `worker_pool` 。每一个和 `worker_pool` 相关联的 `pool_workqueue` 都分配在相同的处理器上对应的优先级队列, `workqueue` 通过他们和 `worker_pool` 交互。在 `__queue_work` 函数里使用 `raw_smp_processor_id` 设置cpu为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给 `work_struct` 对应的 `pool_workqueue` 并将 `work` 插入到 `workqueue` : ```C static void __queue_work(int cpu, struct workqueue_struct *wq, @@ -499,15 +499,15 @@ else insert_work(pwq, work, worklist, work_flags); ``` -现在我们可以创建`works`和`workqueue`,我们需要知道他们究竟会在何时被执行。就像前面提到的,所有的`works`都会在内核线程中执行。当内核线程得到调度,它开始执行`workqueue`中的`works`。每一个工作队列内核线程都会在`worker_thread`函数里执行一个循环。这些内核线程会做很多不同的事情,其中一些和本章前面提到的很类似。当开始执行时,所有的`work_struct`和`works`都会从他的`workqueue`移除。 +现在我们可以创建 `works` 和 `workqueue` ,我们需要知道他们究竟会在何时被执行。就像前面提到的,所有的 `works` 都会在内核线程中执行。当内核线程得到调度,它开始执行 `workqueue` 中的 `works` 。每一个工作队列内核线程都会在 `worker_thread` 函数里执行一个循环。这些内核线程会做很多不同的事情,其中一些和本章前面提到的很类似。当开始执行时,所有的 `work_struct` 和 `works` 都会从他的 `workqueue` 移除。 总结 -------------------------------------------------------------------------------- -现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了`IRQs`的初始化和`irq_desc`数据结构,在这一节我们看到了用于延后函数的三个概念:`软中断`,`tasklet`和`工作队列`。 +现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了 `IRQs` 的初始化和 `irq_desc` 数据结构,在这一节我们看到了用于延后函数的三个概念: `软中断` , `tasklet` 和 `工作队列` 。 -下一节将是`中断和中断处理`的最后一节。我们将会了解真正的硬件驱动,并试着学习它是怎样和中断子系统一起工作的。 +下一节将是 `中断和中断处理` 的最后一节。我们将会了解真正的硬件驱动,并试着学习它是怎样和中断子系统一起工作的。 如果你有任何问题或建议,请给我发评论或者给我发[Twitter](https://twitter.com/0xAX)。 From 201837bcc242af463ec7db373b1eed7309bc27c2 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Fri, 13 May 2016 16:42:52 +0800 Subject: [PATCH 11/12] change state --- README.md | 2 +- contributors.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d2edaf4..6069ecb 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Linux Insides |├ 3.6|[@cloudusers](https://github.com/cloudusers)|正在进行| |├ 3.7|[@cloudusers](https://github.com/cloudusers)|正在进行| |├ 3.8|[@cloudusers](https://github.com/cloudusers)|正在进行| -|├ 3.9|[@zhangyangjing](https://github.com/zhangyangjing)|正在进行| +|├ 3.9|[@zhangyangjing](https://github.com/zhangyangjing)|已完成| |└ 3.10||未开始| | 4. System calls|[@qianmoke](https://github.com/qianmoke)|正在进行| | 5. Timers and time management|[@icecoobe](https://github.com/icecoobe)|正在进行| diff --git a/contributors.md b/contributors.md index 28f44f8..e966da0 100644 --- a/contributors.md +++ b/contributors.md @@ -21,3 +21,5 @@ [@hailincai](https://github.com/hailincai) [@zmj1316](https://github.com/zmj1316) + +[@zhangyangjing](https://github.com/zhangyangjing) From 9d3b54b14ce06137095334004130c2ad6d080e27 Mon Sep 17 00:00:00 2001 From: Yangjing Zhang Date: Wed, 8 Jun 2016 01:09:07 +0800 Subject: [PATCH 12/12] fix space issues --- interrupts/interrupts-9.md | 96 +++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/interrupts/interrupts-9.md b/interrupts/interrupts-9.md index 98ea1d7..2214742 100644 --- a/interrupts/interrupts-9.md +++ b/interrupts/interrupts-9.md @@ -1,10 +1,10 @@ 中断和中断处理(九) ================================================================================ -延后中断(软中断,Tasklets和工作队列)介绍 +延后中断(软中断,Tasklets 和工作队列)介绍 -------------------------------------------------------------------------------- -这是Linux内核[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节,在[上一节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-8.html)我们分析了源文件[arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c)中的 `init_IRQ` 实现。接下来的这一节我们将继续深入学习外部硬件中断的初始化。 +这是 Linux 内核[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节,在[上一节](https://www.gitbook.com/book/xinqiu/linux-nsides-cn/content/interrupts/interrupts-8.html)我们分析了源文件 [arch/x86/kernel/irqinit.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irqinit.c) 中的 `init_IRQ` 实现。接下来的这一节我们将继续深入学习外部硬件中断的初始化。 中断处理会有一些特点,其中最主要的两个是: @@ -16,20 +16,20 @@ * 前半部 * 后半部 - `后半部` 曾经是Linux内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。现在它已作为一个遗留称谓代表内核中所有延后中断的机制。如你所知,中断处理代码运行于中断处理上下文中,此时禁止响应后续的中断,所以要避免中断处理代码长时间执行。但有些中断却又需要执行很多工作,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做尽量少的重要工作,接下来提交第二部分给内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分被延后的剩余任务就会开始执行。 +`后半部` 曾经是 Linux 内核延后中断执行的一种方式,但现在的实际情况已经不是这样了。现在它已作为一个遗留称谓代表内核中所有延后中断的机制。如你所知,中断处理代码运行于中断处理上下文中,此时禁止响应后续的中断,所以要避免中断处理代码长时间执行。但有些中断却又需要执行很多工作,所以中断处理有时会被分为两部分。第一部分中,中断处理先只做尽量少的重要工作,接下来提交第二部分给内核调度,然后就结束运行。当系统比较空闲并且处理器上下文允许处理中断时,第二部分被延后的剩余任务就会开始执行。 当前实现延后中断的有如下三种途径: -* `软中断` ; -* `tasklets` ; -* `工作队列` ; +* `软中断` +* `tasklets` +* `工作队列` 在这一小节我们将详细介绍这三种实现,现在是时间深入了解一下了。 软中断 ---------------------------------------------------------------------------------- -伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为 `ksoftirqd` (稍后将详细讨论)的内核线程。每个处理器都有自己的内核线程,名字叫做 `ksoftirqd/n` ,n是处理器的编号。我们可以通过系统命令 `systemd-cgls` 看到它们: +伴随着内核对并行处理的支持,出于性能考虑,所有新的下半部实现方案都基于被称之为 `ksoftirqd` (稍后将详细讨论)的内核线程。每个处理器都有自己的内核线程,名字叫做 `ksoftirqd/n`,n是处理器的编号。我们可以通过系统命令 `systemd-cgls` 看到它们: ``` $ systemd-cgls -k | grep ksoft @@ -43,13 +43,13 @@ $ systemd-cgls -k | grep ksoft ├─ 43 [ksoftirqd/7] ``` -由 `spawn_ksoftirqd` 函数启动这些线程。就像我们看到的,这个函数在早期的[initcall](http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/index.html)被调用。 +由 `spawn_ksoftirqd` 函数启动这些线程。就像我们看到的,这个函数在早期的 [initcall](http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/index.html) 被调用。 ```C early_initcall(spawn_ksoftirqd); ``` -软中断在Linux内核编译时就静态的确定了。`open_softirq` 函数负责 `softirq` 初始化,它在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义: +软中断在 Linux 内核编译时就静态地确定了。`open_softirq` 函数负责 `softirq` 初始化,它在 [kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c) 中定义: ```C void open_softirq(int nr, void (*action)(struct softirq_action *)) @@ -69,7 +69,7 @@ void open_softirq(int nr, void (*action)(struct softirq_action *)) static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; ``` -它在同一源文件中定义。`softirq_vec` 数组包含了 `NR_SOFTIRQS` (其值为10)个不同 `softirq` 类型的 `softirq_action`。当前版本的Linux内核定义了十种软中断向量。其中两个tasklet相关,两个网络相关,两个块处理相关,两个定时器相关,另外调度器和RCU也各占一个。所有这些都在一个枚举中定义: +它在同一源文件中定义。`softirq_vec` 数组包含了 `NR_SOFTIRQS` (其值为10)个不同 `softirq` 类型的 `softirq_action`。当前版本的 Linux 内核定义了十种软中断向量。其中两个 tasklet 相关,两个网络相关,两个块处理相关,两个定时器相关,另外调度器和 RCU 也各占一个。所有这些都在一个枚举中定义: ```C enum @@ -97,7 +97,7 @@ const char * const softirq_to_name[NR_SOFTIRQS] = { }; ``` -我们也可以在'/proc/softirqs'的输出中看到他们: +我们也可以在 `/proc/softirqs` 的输出中看到他们: ``` ~$ cat /proc/softirqs @@ -136,7 +136,7 @@ void raise_softirq(unsigned int nr) } ``` -可以看到在 `local_irq_save` 和 `local_irq_restore` 两个宏中间调用了 `raise_softirq_irqoff` 函数。`local_irq_save` 的定义位于[include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h)头文件,它保存了[eflags](https://en.wikipedia.org/wiki/FLAGS_register)寄存器中的[IF](https://en.wikipedia.org/wiki/Interrupt_flag)标志位并且禁用了当前处理器的中断。`local_irq_restore` 宏定义于相同头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为将要运行的 `softirq` 中断处理运行于中断上下文中。 +可以看到在 `local_irq_save` 和 `local_irq_restore` 两个宏中间调用了 `raise_softirq_irqoff` 函数。`local_irq_save` 的定义位于 [include/linux/irqflags.h](https://github.com/torvalds/linux/blob/master/include/linux/irqflags.h) 头文件,它保存了 [eflags](https://en.wikipedia.org/wiki/FLAGS_register) 寄存器中的 [IF](https://en.wikipedia.org/wiki/Interrupt_flag) 标志位并且禁用了当前处理器的中断。`local_irq_restore` 宏定义于相同头文件中,它做了完全相反的事情:装回之前保存的中断标志位然后允许中断。这里之所以要禁用中断是因为将要运行的 `softirq` 中断处理运行于中断上下文中。 `raise_softirq_irqoff` 函数设置当前处理器上和nr参数对应的软中断标志位(`__softirq_pending`)。这是通过以下代码做到的: @@ -144,7 +144,7 @@ void raise_softirq(unsigned int nr) __raise_softirq_irqoff(nr); ``` -然后,通过 `in_interrupt` 函数获得 `irq_count` 值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个cpu是否处于中断环境。如果我们处于中断上下文中,我们就退出 `raise_softirq_irqoff` 函数,装回 `IF` 标志位并允许当前处理器的中断。如果不在中断上下文中,就会调用 `wakeup_softirqd` 函数: +然后,通过 `in_interrupt` 函数获得 `irq_count` 值。我们在这一章的第一[小节](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-1.html)已经知道它是用来检测一个 cpu 是否处于中断环境。如果我们处于中断上下文中,我们就退出 `raise_softirq_irqoff` 函数,装回 `IF` 标志位并允许当前处理器的中断。如果不在中断上下文中,就会调用 `wakeup_softirqd` 函数: ```C if (!in_interrupt()) @@ -188,26 +188,26 @@ if (pending) { ... ``` -除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在[arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c)的 `do_IRQ` 函数被调用时,这是Linux内核中执行延后中断的主要时机。在这个函数将要完成中断处理时它会调用[arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h)中定义的 `exiting_irq` 函数,`exiting_irq` 又调用了 `irq_exit`。`irq_exit` 函数会检测当前处理器上下文是否有延后中断,有的话就会调用 `invoke_softirq`: +除周期性检测是否有延后中断需要执行之外,系统还会在一些关键时间点上检测。一个主要的检测时间点就是当定义在 [arch/x86/kernel/irq.c](https://github.com/torvalds/linux/blob/master/arch/x86/kernel/irq.c) 的 `do_IRQ` 函数被调用时,这是 Linux 内核中执行延后中断的主要时机。在这个函数将要完成中断处理时它会调用 [arch/x86/include/asm/apic.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/apic.h) 中定义的 `exiting_irq` 函数,`exiting_irq` 又调用了 `irq_exit`。`irq_exit` 函数会检测当前处理器上下文是否有延后中断,有的话就会调用 `invoke_softirq`: ```C if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); ``` -这样就调用到了我们上面提到的 `__do_softirq` 。每个 `softirq` 都有如下的阶段:通过 `open_softirq` 函数注册一个软中断,通过 `raise_softirq` 函数标记一个软中断来激活它,然后所有被标记的软中断将会在Linux内核下一次执行周期性软中断检测时得以调度,对应此类型软中断的处理函数也就得以执行。 +这样就调用到了我们上面提到的 `__do_softirq`。每个 `softirq` 都有如下的阶段:通过 `open_softirq` 函数注册一个软中断,通过 `raise_softirq` 函数标记一个软中断来激活它,然后所有被标记的软中断将会在 Linux 内核下一次执行周期性软中断检测时得以调度,对应此类型软中断的处理函数也就得以执行。 从上述可看出,软中断是静态分配的,这对于后期加载的内核模块将是一个问题。基于软中断实现的 `tasklets` 解决了这个问题。 Tasklets -------------------------------------------------------------------------------- -如果你阅读Linux内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是 `tasklets` 。正如上面说的,`tasklets` 构建于 `softirq` 中断之上,他是基于下面两个软中断实现的: +如果你阅读 Linux 内核源码中软中断相关的代码,你会发现它很少会被用到。内核中实现延后中断的主要途径是 `tasklets`。正如上面说的,`tasklets` 构建于 `softirq` 中断之上,他是基于下面两个软中断实现的: * `TASKLET_SOFTIRQ`; * `HI_SOFTIRQ`. -简而言之, `tasklets` 是运行时分配和初始化的软中断。和软中断不同的是,同一类型的 `tasklets` 可以在同一时间运行于不同的处理器上。我们已经了解到一些关于软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的 `softirq_init` 函数实现,这个函数在[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)中定义如下: +简而言之,`tasklets` 是运行时分配和初始化的软中断。和软中断不同的是,同一类型的 `tasklets` 可以在同一时间运行于不同的处理器上。我们已经了解到一些关于软中断的知识,当然上面的文字并不能详细讲解所有的细节,但我们现在可以通过直接阅读代码一步步的更深入了解软中断。我们返回到开始部分讨论的 `softirq_init` 函数实现,这个函数在 [kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c) 中定义如下: ```C void __init softirq_init(void) @@ -226,7 +226,7 @@ void __init softirq_init(void) } ``` -可以看到在函数开头定义了一个名为cpu的integer类型变量。接下来他会作为参数传递给宏 `for_each_possible_cpu` 来获得系统中所有的处理器。如果 `possible_cpu` 对你来说是一个新的术语,你可以阅读[CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html)章节来了解更多知识。简单的说, `possible_cpu` 是系统运行期间插入的处理器集合。所有的 `possible processor` 存储在 `cpu_possible_bits` 位图中,你可以在[kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)中找到他的定义: +可以看到在函数开头定义了一个名为 cpu 的 integer 类型变量。接下来他会作为参数传递给宏 `for_each_possible_cpu` 来获得系统中所有的处理器。如果 `possible_cpu` 对你来说是一个新的术语,你可以阅读 [CPU masks](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Concepts/cpumask.html) 章节来了解更多知识。简单的说,`possible_cpu` 是系统运行期间插入的处理器集合。所有的 `possible processor` 存储在 `cpu_possible_bits` 位图中,你可以在 [kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c) 中找到他的定义: ```C static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly; @@ -236,7 +236,7 @@ static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly; const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits); ``` -好了,我们定义了integer类型变量 `cpu` 并且通过 `for_each_possible_cpu` 宏遍历了所有处理器,初始化了两个 `per-cpu` 变量: +好了,我们定义了 integer 类型变量 `cpu` 并且通过 `for_each_possible_cpu` 宏遍历了所有处理器,初始化了两个 `per-cpu` 变量: * `tasklet_vec`; * `tasklet_hi_vec`; @@ -248,7 +248,7 @@ static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec); ``` -`tasklet_head` 结构代表一组 `Tasklets` ,它包含两个成员,head和tail: +`tasklet_head` 结构代表一组 `Tasklets`,它包含两个成员,head 和 tail: ```C struct tasklet_head { @@ -257,7 +257,7 @@ struct tasklet_head { }; ``` -`tasklet_struct` 数据类型在[include/linux/interrupt.h](https://github.com/torvalds/linux/blob/master/include/linux/interrupt.h)中定义,它代表一个 `Tasklet` 。这本书之前部分我们没有见过这个单词,那我们先试着理解一下 `Tasklet` 究竟为何物。实际上, `Tasklet` 是处理延后中断的一种机制,来看一下 `tasklet_struct` 的具体定义: +`tasklet_struct` 数据类型在 [include/linux/interrupt.h](https://github.com/torvalds/linux/blob/master/include/linux/interrupt.h) 中定义,它代表一个 `Tasklet`。这本书之前部分我们没有见过这个单词,那我们先试着理解一下 `Tasklet` 究竟为何物。实际上,`Tasklet` 是处理延后中断的一种机制,来看一下 `tasklet_struct` 的具体定义: ```C struct tasklet_struct @@ -272,22 +272,22 @@ struct tasklet_struct 这个数据结构包含有下面5个成员: -* 调度队列中的下一个 `Tasklet` ; -* 当前这个 `Tasklet` 的状态; -* 这个 `Tasklet` 是否处于活动状态; -* `Tasklet` 的回调函数; -* 回调函数的参数; +* 调度队列中的下一个 `Tasklet` +* 当前这个 `Tasklet` 的状态 +* 这个 `Tasklet` 是否处于活动状态 +* `Tasklet` 的回调函数 +* 回调函数的参数 -上面代码中,在 `softirq_init` 函数中初始化了两个tasklets数组: `tasklet_vec` 和 `tasklet_hi_vec` 。Tasklets和高优先级Tasklets分别存储于这两个数组中。初始化完成后我们看到代码[kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c)在 `softirq_init` 函数的最后又两次调用了 `open_softirq` : +上面代码中,在 `softirq_init` 函数中初始化了两个 tasklets 数组:`tasklet_vec` 和 `tasklet_hi_vec`。Tasklets 和高优先级 Tasklets 分别存储于这两个数组中。初始化完成后我们看到代码 [kernel/softirq.c](https://github.com/torvalds/linux/blob/master/kernel/softirq.c) 在 `softirq_init` 函数的最后又两次调用了 `open_softirq`: ```C open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action); ``` -`open_softirq` 函数的主要作用是初始化软中断,接下来让我们看看它是怎么做的。和Tasklets相关的软中断处理函数有两个,分别是 `tasklet_action` 和 `tasklet_hi_action` 。其中 `tasklet_hi_action` 和 `HI_SOFTIRQ` 关联在一起, `tasklet_action` 和 `TASKLET_SOFTIRQ` 关联在一起。 +`open_softirq` 函数的主要作用是初始化软中断,接下来让我们看看它是怎么做的。和 Tasklets 相关的软中断处理函数有两个,分别是 `tasklet_action` 和 `tasklet_hi_action`。其中 `tasklet_hi_action` 和 `HI_SOFTIRQ` 关联在一起,`tasklet_action` 和 `TASKLET_SOFTIRQ` 关联在一起。 -Linux内核提供一些API供操作Tasklets之用。首先是 `tasklet_init` 函数,它接受一个 `task_struct` 数据结构,一个处理函数,和另外一个参数,并利用这些参数来初始化所给的 `task_struct` 结构: +Linux 内核提供一些 API 供操作 Tasklets 之用。首先是 `tasklet_init` 函数,它接受一个 `task_struct` 数据结构,一个处理函数,和另外一个参数,并利用这些参数来初始化所给的 `task_struct` 结构: ```C void tasklet_init(struct tasklet_struct *t, @@ -301,14 +301,14 @@ void tasklet_init(struct tasklet_struct *t, } ``` -另外还有如下两个宏可以静态的初始化一个tasklet: +另外还有如下两个宏可以静态地初始化一个 tasklet: ```C DECLARE_TASKLET(name, func, data); DECLARE_TASKLET_DISABLED(name, func, data); ``` -Linux内核提供三个函数标记一个tasklet已经准备就绪: +Linux 内核提供三个函数标记一个 tasklet 已经准备就绪: ```C void tasklet_schedule(struct tasklet_struct *t); @@ -316,7 +316,7 @@ void tasklet_hi_schedule(struct tasklet_struct *t); void tasklet_hi_schedule_first(struct tasklet_struct *t); ``` -第一个函数使用普通优先级调度一个tasklet,第二个使用高优先级,第三个则用更高优先级。所有这三个函数的实现都很类似,所以我们只看一下第一个 `tasklet_schedule` 的实现: +第一个函数使用普通优先级调度一个 tasklet,第二个使用高优先级,第三个则用更高优先级。所有这三个函数的实现都很类似,所以我们只看一下第一个 `tasklet_schedule` 的实现: ```C static inline void tasklet_schedule(struct tasklet_struct *t) @@ -338,7 +338,7 @@ void __tasklet_schedule(struct tasklet_struct *t) } ``` -我们看到它检测并设置所给的tasklet为 `TASKLET_STATE_SCHED` 状态,然后以所给tasklet为参数执行了 `__tasklet_schedule` 函数。 `__tasklet_schedule` 看起来和前面见到的 `raise_softirq` 很像。一开始它保存中断标志并禁用中断,继而将新的tasklet添加到 `tasklet_vec` ,然后调用了我们前面见过的 `raise_softirq_irqoff` 函数。当Linux内核调度器决定去运行一个延后函数, `tasklet_action` 函数会被作为和 `TASKLET_SOFTIRQ` 相关联的延后函数调用。同样的, `tasklet_hi_action` 会被作为和 `HI_SOFTIRQ` 相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action` 使用 `tasklet_vec` 而 `tasklet_hi_action` 使用 `tasklet_hi_vec` 。 +我们看到它检测并设置所给的 tasklet 为 `TASKLET_STATE_SCHED` 状态,然后以所给 tasklet 为参数执行了 `__tasklet_schedule` 函数。`__tasklet_schedule` 看起来和前面见到的 `raise_softirq` 很像。一开始它保存中断标志并禁用中断,继而将新的 tasklet 添加到 `tasklet_vec`,然后调用了我们前面见过的 `raise_softirq_irqoff` 函数。当 Linux 内核调度器决定去运行一个延后函数,`tasklet_action` 函数会被作为和 `TASKLET_SOFTIRQ` 相关联的延后函数调用。同样的,`tasklet_hi_action` 会被作为和 `HI_SOFTIRQ` 相关联的延后函数调用。这些函数之所以如此相似是因为他们之间只有一个地方不同 --- `tasklet_action` 使用 `tasklet_vec` 而 `tasklet_hi_action` 使用 `tasklet_hi_vec`。 让我们看下 `tasklet_action` 函数的实现: @@ -363,7 +363,7 @@ static void tasklet_action(struct softirq_action *a) } ``` -在 `tasklet_action` 开始时利用 `local_irq_disable` 宏禁用了当前处理器的中断(你可以阅读本书[第二部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-2.html)了解更多关于此宏的信息)。接下来获取到当前处理器对应的普通优先级tasklet列表并把它设置为 `NULL` ,这是因为所有的tasklet都将被执行。然后使能当前处理器的中断,循环遍历tasklet列表,每一次遍历都会对当前tasklet调用 `tasklet_trylock` 函数来更新它的状态为 `TASKLET_STATE_RUN` : +在 `tasklet_action` 开始时利用 `local_irq_disable` 宏禁用了当前处理器的中断(你可以阅读本书[第二部分](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/interrupts-2.html)了解更多关于此宏的信息)。接下来获取到当前处理器对应的普通优先级 tasklet 列表并把它设置为 `NULL` ,这是因为所有的 tasklet 都将被执行。然后使能当前处理器的中断,循环遍历 tasklet 列表,每一次遍历都会对当前 tasklet 调用 `tasklet_trylock` 函数来更新它的状态为 `TASKLET_STATE_RUN`: ```C static inline int tasklet_trylock(struct tasklet_struct *t) @@ -372,17 +372,17 @@ static inline int tasklet_trylock(struct tasklet_struct *t) } ``` -如果这个操作成功了就会执行此tasklet的处理函数(我们在 `tasklet_init` 中所设置的),然后调用 `tasklet_unlock` 函数清除他的 `TASKLET_STATE_RUN` 状态。 +如果这个操作成功了就会执行此 tasklet 的处理函数(我们在 `tasklet_init` 中所设置的),然后调用 `tasklet_unlock` 函数清除他的 `TASKLET_STATE_RUN` 状态。 -通常情况下,这就是 `tasklet` 的所有概念。当然这些还不足以覆盖所有的 `tasklets` ,但是我想大家可以以此为切入点继续学习下去。 +通常情况下,这就是 `tasklet` 的所有概念。当然这些还不足以覆盖所有的 `tasklets`,但是我想大家可以以此为切入点继续学习下去。 - `tasklets` 在Linux内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后中断机制 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 +`tasklets` 在 Linux 内核中是一个[广泛](http://lxr.free-electrons.com/ident?i=tasklet_init)使用的概念,但就像我在本章开头所写的,还有第三个延后中断机制 -- 工作队列。接下来我们将会看看它又是怎样一种机制。 工作队列 -------------------------------------------------------------------------------- - `工作队列` 是另外一个处理延后函数的概念,它大体上和 `tasklets` 类似。工作队列运行于内核进程上下文,而 `tasklets` 运行于软中断上下文。这意味着 `工作队列` 函数不必像 `tasklets` 一样必须是原子性的。Tasklets总是运行于它提交自的那个处理器,工作队列在默认情况下也是这样。 `工作队列` 在Linux内核代码[kernel/workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c)中由如下的数据结构表示: +`工作队列`是另外一个处理延后函数的概念,它大体上和 `tasklets` 类似。工作队列运行于内核进程上下文,而 `tasklets` 运行于软中断上下文。这意味着`工作队列`函数不必像 `tasklets` 一样必须是原子性的。Tasklets 总是运行于它提交自的那个处理器,工作队列在默认情况下也是这样。`工作队列`在 Linux 内核代码 [kernel/workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c) 中由如下的数据结构表示: ```C struct worker_pool { @@ -401,7 +401,7 @@ struct worker_pool { 因为这个结构有非常多的成员,这里就不把它们全部罗列出来,下面只讨论上面列出的这几个。 -工作队列最基础的用法,是作为创建内核线程的接口来处理提交到队列里的工作任务。所有这些内核线程称之为 `worker thread` 。工作队列内的任务是由代码[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)中定义的 `work_struct` 表示的,起定义如下: +工作队列最基础的用法,是作为创建内核线程的接口来处理提交到队列里的工作任务。所有这些内核线程称之为 `worker thread`。工作队列内的任务是由代码 [include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h) 中定义的 `work_struct` 表示的,起定义如下: ```C struct work_struct { @@ -414,7 +414,7 @@ struct work_struct { }; ``` -这里有两个字段比较有意思: `func` --将被 `工作队列` 调度执行的函数, `data` --这个函数的参数。Linux内核提供了称之为 `kworker` 的特定于每个cpu的内核线程: +这里有两个字段比较有意思:`func` --将被`工作队列`调度执行的函数,`data` --这个函数的参数。Linux 内核提供了称之为 `kworker` 的特定于每个 cpu 的内核线程: ``` systemd-cgls -k | grep kworker @@ -428,7 +428,7 @@ systemd-cgls -k | grep kworker ... ``` -这些线程会被用来调度执行工作队列的延后函数(就像 `ksoftirqd` 之于 `软中断` )。除此之外我们还可以为一个 `工作队列` 创建一个新的工作线程。Linux内核提供了如下宏静态创建一个队列任务: +这些线程会被用来调度执行工作队列的延后函数(就像 `ksoftirqd` 之于`软中断`)。除此之外我们还可以为一个`工作队列`创建一个新的工作线程。Linux 内核提供了如下宏静态创建一个队列任务: ```C #define DECLARE_WORK(n, f) \ @@ -450,7 +450,7 @@ systemd-cgls -k | grep kworker } while (0) ``` -这个宏需要一个 `work_struct` 数据结构作为将要创建的队列任务,和一个将在这个任务里调度运行的函数。通过这两个宏的其中一个创建一个 `work` 后,我们需要把它放到 `工作队列` 中去。可以通过 `queue_work` 或者 `queue_delayed_work` 来做到这一点: +这个宏需要一个 `work_struct` 数据结构作为将要创建的队列任务,和一个将在这个任务里调度运行的函数。通过这两个宏的其中一个创建一个 `work` 后,我们需要把它放到`工作队列`中去。可以通过 `queue_work` 或者 `queue_delayed_work` 来做到这一点: ```C static inline bool queue_work(struct workqueue_struct *wq, @@ -460,7 +460,7 @@ static inline bool queue_work(struct workqueue_struct *wq, } ``` - `queue_work` 只是调用了 `queue_work_on` 函数指定相应的处理器。注意这里给 `queue_work_on` 函数传递了 `WORK_CPU_UNBOUND` 参数,它作为代表队列任务要绑定到哪一个处理器的枚举一员,定义于[include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。 `queue_work_on` 函数测试并设置所给 `任务` 的 `WORK_STRUCT_PENDING_BIT` 标志位,然后以所给的工作队列和队列任务为参数执行 `__queue_work` 函数: +`queue_work` 只是调用了 `queue_work_on` 函数指定相应的处理器。注意这里给 `queue_work_on` 函数传递了 `WORK_CPU_UNBOUND` 参数,它作为代表队列任务要绑定到哪一个处理器的枚举一员,定义于 [include/linux/workqueue.h](https://github.com/torvalds/linux/blob/master/include/linux/workqueue.h)。`queue_work_on` 函数测试并设置所给`任务`的 `WORK_STRUCT_PENDING_BIT` 标志位,然后以所给的工作队列和队列任务为参数执行 `__queue_work` 函数: ```C bool queue_work_on(int cpu, struct workqueue_struct *wq, @@ -477,7 +477,7 @@ bool queue_work_on(int cpu, struct workqueue_struct *wq, } ``` - ` __queue_work` 函数得到参数 `work poll` 。是的,是 `work poll` 而不是 `workqueue` 。实际上,所有的 `works` 都没有放在 `workqueue` 中,而是放在Linux内核中由 `worker_pool` 数据结构所定义的 `work poll` 。如上所述, `workqueue_struct` 数据结构的 `pwqs` 成员是一个 `worker_pool` 列表。当我们创建一个 `workqueue` ,他针对每一个处理器都创建了 `worker_pool` 。每一个和 `worker_pool` 相关联的 `pool_workqueue` 都分配在相同的处理器上对应的优先级队列, `workqueue` 通过他们和 `worker_pool` 交互。在 `__queue_work` 函数里使用 `raw_smp_processor_id` 设置cpu为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给 `work_struct` 对应的 `pool_workqueue` 并将 `work` 插入到 `workqueue` : +`__queue_work` 函数得到参数 `work poll`。是的,是 `work poll` 而不是 `workqueue`。实际上,所有的 `works` 都没有放在 `workqueue` 中,而是放在 Linux 内核中由 `worker_pool` 数据结构所定义的 `work poll`。如上所述,`workqueue_struct` 数据结构的 `pwqs` 成员是一个 `worker_pool` 列表。当我们创建一个 `workqueue`,他针对每一个处理器都创建了 `worker_pool`。每一个和 `worker_pool` 相关联的 `pool_workqueue` 都分配在相同的处理器上对应的优先级队列,`workqueue` 通过他们和 `worker_pool` 交互。在 `__queue_work` 函数里使用 `raw_smp_processor_id` 设置 cpu 为当前处理器在[第四章](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/Initialization/linux-initialization-4.html)你可以找到更多相关信息),得到与所给 `work_struct` 对应的 `pool_workqueue` 并将 `work` 插入到 `workqueue`: ```C static void __queue_work(int cpu, struct workqueue_struct *wq, @@ -499,19 +499,19 @@ else insert_work(pwq, work, worklist, work_flags); ``` -现在我们可以创建 `works` 和 `workqueue` ,我们需要知道他们究竟会在何时被执行。就像前面提到的,所有的 `works` 都会在内核线程中执行。当内核线程得到调度,它开始执行 `workqueue` 中的 `works` 。每一个工作队列内核线程都会在 `worker_thread` 函数里执行一个循环。这些内核线程会做很多不同的事情,其中一些和本章前面提到的很类似。当开始执行时,所有的 `work_struct` 和 `works` 都会从他的 `workqueue` 移除。 +现在我们可以创建 `works` 和 `workqueue`,我们需要知道他们究竟会在何时被执行。就像前面提到的,所有的 `works` 都会在内核线程中执行。当内核线程得到调度,它开始执行 `workqueue` 中的 `works`。每一个工作队列内核线程都会在 `worker_thread` 函数里执行一个循环。这些内核线程会做很多不同的事情,其中一些和本章前面提到的很类似。当开始执行时,所有的 `work_struct` 和 `works` 都会从他的 `workqueue` 移除。 总结 -------------------------------------------------------------------------------- -现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了 `IRQs` 的初始化和 `irq_desc` 数据结构,在这一节我们看到了用于延后函数的三个概念: `软中断` , `tasklet` 和 `工作队列` 。 +现在结束了[中断和中断处理](https://www.gitbook.com/book/xinqiu/linux-insides-cn/content/interrupts/index.html)的第九节。这一节中我们继续讨论了外部硬件中断。在之前部分我们看到了 `IRQs` 的初始化和 `irq_desc` 数据结构,在这一节我们看到了用于延后函数的三个概念:`软中断`,`tasklet` 和`工作队列`。 下一节将是 `中断和中断处理` 的最后一节。我们将会了解真正的硬件驱动,并试着学习它是怎样和中断子系统一起工作的。 -如果你有任何问题或建议,请给我发评论或者给我发[Twitter](https://twitter.com/0xAX)。 +如果你有任何问题或建议,请给我发评论或者给我发 [Twitter](https://twitter.com/0xAX)。 -**请注意英语并不是我的母语,我为任何表达不清楚的地方感到抱歉。如果你发现任何错误请发PR到[linux-insides](https://github.com/0xAX/linux-insides)。(译者注:翻译问题请发PR到[linux-insides-cn](https://www.gitbook.com/book/xinqiu/linux-insides-cn))** +**请注意英语并不是我的母语,我为任何表达不清楚的地方感到抱歉。如果你发现任何错误请发 PR 到 [linux-insides](https://github.com/0xAX/linux-insides)。(译者注:翻译问题请发 PR 到 [linux-insides-cn](https://www.gitbook.com/book/xinqiu/linux-insides-cn))** 链接