Linux是支持多任务的操作系统,即多个应用程序可以在系统中同时运行。在多处理器环境下,执行多个应用程序的进程可以被并行处理,但进程并不是内核中能执行的唯一活动。内核中还存在几种其他形式的活动,在系统的运行过程中起着重要作用。
什么是进程?进程(process)是一个正在执行的程序实例,各进程拥有自己独立的地址空间。进程通常在执行某个应用程序时启动,应用执行完成后结束。创建、控制和结束进程是操作系统内核的一项重要任务。在用户地址空间执行的进程是互斥的,它们只能访问系统分配给它们的存储空间。用户地址空间的进程也不能直接访问内核功能。
当用户进程需要访问设备或使用操作系统内核的功能时,必须通过系统调用(systemcall)来完成。系统调用将处理器切换到保护模式,随后访问内核的地址空间,在保护模式下,所有的设备和内存资源通过内核实现API访问。
除了常规的进程和系统调用外,在内核中还包含了几种其他活动,这几种活动对网络子系统而言尤其重要,因为网络功能就是在内核中处理的,在本节后面的部分,我们将较详细介绍以下几种活动:
● 内核线程(kernel thread)。
● 中断(硬件中断)。
● 软件中断。
● tasklet。
● bottom half。
在设计内核活动时,最重要的一点是使这些活动能并行执行;另一方面,在多处理器环境下也要考虑同一种活动的同一实例是否可以在不同处理器上并行运行,或同一种活动的两个不同的实例是否可以同时在不同处理器上运行。表1-1中列出了以上所述活动的相同实例和不同实例是否可以在不同CPU上同时执行的情况。
表1-1 内核中同一活动在不同CPU上同时执行
在这些活动中,另一个需要明确的是,哪种活动的优先级更高,即一种活动是否可以被其他的活动中断。表1-2列出了以上活动之间是否可被其他活动中断的关系。这些信息特别重要,因为要避免某个活动因不可预期的打断而造成系统数据的破坏。
表1-2 活动间可被其他活动中断的关系
外部设备用硬件中断来通知操作系统有重要的事件发生,中断发生后,CPU会暂时停止当前程序的执行,转去执行中断处理程序,中断处理程序结束后再恢复原来被停止程序的执行。
硬件中断是一种系统资源,当我们为设备编写中断处理程序来处理外部事件时,要向系统申请中断资源(即硬件中断信号线,通常称为中断号),并将中断处理程序与中断源相关联。我们可以在运行时用内核提供的一对函数来申请和释放中断资源。
函数request-irq申请中断号为irq的资源,将申请到的中断号irq与设备名为devname,设备索引号为dev_id设备的中断处理程序handler相连。该设备的中断类型为flags。
函数free-irq释放中断资源。
在这里需要对中断类型标志flags做进一步说明,在网络子系统中,我们主要区分两类中断类型(虽然在内核中还有其他的中断类型)。
快速中断(fast interrup)的中断处理程序运行时间非常短,所以当前被打断的活动暂停时间也很短。快速中断的特点是,屏蔽当前运行中断处理程序CPU的其他中断,这样中断处理程序的执行就不会被别的中断打断。要将中断申请注册为快速中断,需在申请中断资源时将中断类型标志flags设为SA_INTERRUPT。
慢速中断(slow interrupt)的中断处理程序在执行期间可以被别的中断打断。慢速中断处理程序执行的时间较长(相对快速中断而言),所以占用CPU的时间也长。
中断处理程序的执行通常可以暂停所有别的活动。不同的中断在几个CPU上可以同时运行,但某个中断的处理程序一次只能在一个CPU上执行。
如果你想查看当前CPU是否在中断活动中,则可以调用内核的API。
硬件中断可以打断当前CPU上所有其他的活动,并会屏蔽CPU其他硬件中断。硬件中断是系统中有限的资源之一,硬件中断处理程序应尽可能快地执行完。在执行中断处理程序期间,如果设备有其他的外部事件发生,由于当前中断资源和CPU已被占用,其他的外部事件就不能得到CPU的响应,所以中断处理程序的执行要尽可能的快。但不是所有事件处理都可以在很短时间内完成,例如网络数据包的接收处理,从网络适配器收到的数据包到将数据包传送到用户接收进程,需要几千个CPU时钟才能完成。接收网络数据包这类事件虽然是由中断触发,但对网络数据包的处理却不能全部放在中断处理程序中来做。
为了节省系统资源,使中断处理程序的运行时间尽可能短,像上述这类费时的事件处理在Linux内核中将其分成两个部分来完成。
top half只完成中断触发后最重要的任务处理,这里top half就对应中断处理程序。在第5章我们分析网络设备驱动程序的中断处理程序时会看到,网络适配器的中断处理程序只将收到的数据包复制到内核的缓冲区后就立即结束返回,释放占有的中断和CPU。对数据包的协议分析和处理不在中断处理程序中进行。
bottom half完成所有非紧急部分的处理。bottom half代码由top half调度,放在以后某个安全的时间运行。比如上述的网络数据包复制到内核以后的分析处理,就在这部分代码中完成。
bottom half 与 top half 最大的区别是,在bottom half执行期间,打开所有硬件中断。它们之间的关系是:top half将在设备缓冲区的数据复制到内核地址空间缓冲区,调度bottom half后退出,这个过程非常快。bottom half执行余下的处理任务,这样在bottom half工作过程中,CPU可以响应新的外部中断请求。Linux中有两种不同的机制来实现bottomhalf过程:tasklet和workqueue。
tasklet是可以被调度执行的特殊函数,在系统某个特定的安全时间运行在软件中断的执行现场。tasklet的特点是:
● 由函数tasklet_schedule调度执行,一个tasklet只运行一次。
● 一个tasklet一次只能在一个CPU上执行。
● 不同的tasklet可以同时在不同的CPU上运行。
实现tasklet的过程可分为以下几个步骤完成:
①编写tasklet的处理函数,如func。
②用宏declare tasklet (name,func,data)声明一个新的tasklet,name为该tasklet的名称,func为该tasklet要执行的函数,data是传送给tasklet处理函数的数据。
③用tasklet_schedule调度tasklet执行。
除此以外,函数tasklet_disable和tasklet_enable可以分别用于禁止和允许tasklet的运行。下面给出一个简单的例子来了解tasklet的实现。
workqueue与tasklet极其类似,是可以由内核代码在将来某个时间调用执行的特殊函数。workqueue与tasklet的不同在于:
● tasklet在软件中断执行现场运行,所有的tasklet代码必须是原子操作。workqueue函数在内核进程现场执行,执行起来更灵活,workqueue函数可以休眠。
● tasklet总是在最初调度它的处理器上执行。默认情况下workqueue与tasklet一样。
● 内核可以将workqueue函数推迟到一定时间以后才调用执行。
软件中断是在硬件中断执行完后由内核的调度器(scheduler)调度执行的活动。软件中断和硬件中断的主要区别在于:硬件中断可以随时立刻打断CPU现行活动(如中断允许);软件中断是由内核调度器调度执行的活动。软件中断必须要等到调度器调用它才能执行,软件中断的调度由内核函数do_softirq完成。
软件中断的处理程序在do_softirq后开始执行。软件中断的执行时间只有两处:
● 系统调度结束后(在schedule中)被调度执行。
● 硬件中断结束后(在do_IRQ中)被调度执行。
Linux 内核中最多可以定义32个软件中断,目前使用的有:
其中网络子系统使用的软件中断有如下几种。
● NET_RX_SOFTIRQ:处理网络接收到的数据包。
● NET_TX_SOFTIRQ:处理要发送的网络数据包。
另一个软件中断TASKLET_FOFTIRQ用于实现tasklet的概念。
软件中断与tasklet和bottom half有很大区别,软件中断最重要的特性是:
● 软件中断可以同时在多个处理器上运行,所以在编写软件中断处理程序时必须要考虑重入问题。如果在软件中断处理程序中要访问共享全局变量,必须采用锁定机制执行并发访问。
● 软件中断本身不能被同类的软件中断打断。
● 软件中断在执行时只能被硬件中断打断。