第6章 信号量,中断和时间
信号量(Signal)是进程间通讯(IPC)的一种形式——是一个进程给另一个进程发送信息的方法。但是信息不可能很多——一个信号量不可能携带详细的信息,即使是传送者的身份也不能被传递;唯一能够确定的事实是信号量的确被发送了。(然而和经典信号量不同,POSIX实时信号量允许传送稍微多一点的信息。)实际上,信号量对于双向通讯是没有用处的。还有,根据某些限定,信号量的接受者不必以任何方式作出响应,甚至可以直接忽略大部分信号量。
虽然有这么多的限制,然而信号量仍然是一种功能强大的十分有用的机制——勿庸置疑,这是Unix IPC中使用最频繁的机制。每当进程退出或者废弃一个空指针时,每当使用Ctrl+C键终止程序运行时,都要传递信号量。
第9章会更详细的讨论IPC机制。对于本章的讨论来说,信号量的内容就足够讨论了。
正如在Linux内核本身的代码注释中所说明的一样,中断(Interrupt)对于内核来说和信号量是类似的。中断一般都是从磁盘之类的硬件设备送往内核,用以提示内核该设备需要加以注意。一个重要的硬件中断源就是定时器设备,它周期性地通知内核已经通过的时间。如同第5章中阐述的一样,中断也可以由用户进程通过软件产生。
在本章中,我们首先讨论一下Linux中信号量和中断的实现,最后再浏览一下Linux的时间处理方式。
虽然内核对代码的要求标准非常严格,本章所涉及的代码仍然特别清晰明白。本章使用的一般方法是首先介绍相关的数据结构和它们之间的关系,接下来讨论操纵和查询它们的函数。
锁的概述
锁的基本思想是限制对共享资源的访问——共享资源包括共享的文件,共享的内存片,以及在一次只能由一个CPU执行的代码段。概括的说,在单处理器上运行的Linux内核并不需要锁,这是因为在编写Linux内核时就已经注意到要尽量避免各种可能需要锁的情况了。但是,在多处理器机器上,一个处理器有时需要防止其它处理器对它的有害的介入。
include/asm-i386/spinlock.件(从12582行开始)并不使用难看的#ifdef把所有对锁函数的调用封装起来,它包含一系列对单处理器平台(UP)基本为空的宏,然而在多处理器平台(SMP)上这些宏将展开成为实际代码。因而内核的其它代码对UP和SMP(当涉及到这种特性时)都是相同的,但是它们两个的效果却是迥然不同的。
第10章中涉及SMP的部分会对锁做深入的介绍。但是,由于你在代码中将到处都能够看到对锁宏的调用,特别是在本章所讨论到的代码中这一点尤为明显,所以你应该首先对宏的用途有初步了解——以及为什么现在在大多数情况下我们都可以安全地将其忽略(我们将在讨论的过程中对其中的异常情况进行说明)。
信号量
Linux内核将信号量分为两类:
非实时的(Nonrealtime)——大部分是些传统的信号量,例如SIGSEGVSIGHUPSIGKILL
实时的(realtime)——由POSIX 1003.1b标准规定,它们同非实时信号量有细微的区别。特别是实时信号量具有进程可以配置的意义——就像是非实时信号量SIGUSR1SIGUSR2一样——额外的信息能够和这些信号量一起传送。它们也会排队,因此如果在第一个信号量处理完成之前有多个信号量实例到达,所有的信号量都能够被正确传送;这对于非实时信号量则是不可能的。
在第7章中我们将会对实时性对于Linux内核的意义进行更详细的介绍——特别是实时性所不能够说明的内容。
信号量数目的宏定义从12048行开始。实时信号量的数目在SIGRTMINSIGRTMAX(分别在12087行和12088行)所定义的范围之内。
数据结构
本节讨论信号量代码使用的最重要的数据结构。
sigset_t
12035:sigset_t表示信号量的集合。根据使用地点的不同,它的意思也不同——例如,它可能记录着正在等待某一个进程的信号量(如16425行struct task_structsignal成员)的集合,也可能是某个进程已经请求阻塞了的信号量(如同一行中定义的同一结构的blocked成员)的集合。随着本书的进行,我们会逐渐看到这些类似的应用。
12036:sigset_t的唯一一个组成部分是一组unsigned long(无符号长整型数),其中的每
一位都代表一个信号量。注意到无符号长整型类型在整个内核代码中是作为一个字来处理的,这和你所希望的可能有所出入——即使是在当前x86 CPU的讨论中,有时候字也被用于说明16位类型。由于Linux是一个真32位操作系统,将32位看作是一个字在绝大多数情况下是正确的。(将Linux称为真32位操作系统也有一些不准确,因为在64位CPU上它也是一个真64位操作系统。)union是什么类型
    这个数组的大小_NSIG_WORDS在12031行直接计算。(_NSIG_BPW中的“BPW”是“bits per word(每字位数)”的缩写。)在不同的平台上,_NSIG_WORDS的大小从1(Alpha平台中)到4(MIPS平台中)不等。如你所见,在x86平台中,该值正好是2,这意味着在x86平台上2个无符号数就可以包含足够的位数来代表所有Linux使用的信号量。
struct sigaction
12165:struct sigaction代表信号量到达时进程应该执行的动作。它被封装在struct k_sigaction(12172行)结构中,而该结构又是被封装在struct signal_struct 结构中的,后者是struct task_struct结构的sig成员所指向的一个实例(16424行)。如果这个指针为空,进程就会退出而不必接受任何信号量。否则,每个进程对于每个信号量数目都需要若干
_NSIG struct sigaction结构和一个struct sigaction结构。
12166:sa_handler__sighandler_t类型——一个在12148行定义的函数指针类型)描述了进程希望处理信号量的方式。其值可以是下面中的一个:
SIG_DFL(12151行)申请处理信号量的缺省操作,不管该操作是什么——这是由信号量所决定的。注意它和NULL是等同的。
SIG_IGN(12153行)意味着信号量应该忽略。但是,并不是所有的信号量都可以被忽略的。
所有的其它值都是在信号量到达时所需要调用的用户空间函数的地址。
12167:sa_flags进一步调整信号量处理代码所完成的工作。可能的标志集合从12108行开始定义。这些标志允许用户代码在信号量实例发送以后(或者保留用户定制的操作时)请求恢复缺省操作,等等。这一点在宏定义块前面的标签注释中已经说明了。
12168:sa_restorer是本书中所没有涉及的一些信号量处理代码细节所使用的。
12169:sa_mask是一系列其它信号量的集合,进程在处理这些信号量的过程中可能需要进行锁定。例如,如果一个进程在处理SIGCHLD的时候希望锁定SIGHUP和    SIGINT,进程的第SIGCHLDsa_mask就会对与SIGHUPSIGINT相关的位进行置位。
siginfo_t
11851:struct siginfo(也称为siginfo_t)是伴随着信号量,特别是在实时信号量,所传递的额外信息。
11852:勿庸置疑,si_signo是信号量的数目。
11853:si_errno应该是信号量传递时传送者的errno的值,这样接收者就可以对它进行检测。内核本身并不关心这个值;当在某些情况下需要设置这个值时,内核将其设置为0。我推测如果这样,即使调用者没有设置这个值,它们仍然会发现si_error的值被设为已知状态。
11854:si_code记录了信号量的来源(不是发送者的进程ID号,也就是PID——它在别处记录)。有效的信号量来源在11915行及其随后部分使用宏进行了定义。
11856:该结构的最后一部分是union类型的;该union类型依赖于si_code的值。
11857:union的第一部分是_pad,它将siginfo_t的长度扩展填充为128*sizeofint字节(在x86平台上一共是512个字节)。留意一下这个数组的大小,也就是SI_PAD_SIZE(11849行),代表了该结构的前三个成员——如果增加了更多的成员,SI_PAD_SIZE就需要进行相应修改。
struct signal_queue
17132:struct signal_queue结构用来确保所有的实时信号量都被正确传送了,如果可能,每一个都包含着额外信息(siginfo_t)。如同后面你将会看到的一样,内核会为每个进程都设置一个队列,用来存放该进程的挂起的实时信号量。这个队列类型本身很小,仅仅由一个指向下一个节点的指针和siginfo_t本身组成。
应用函数
有关信号量的一个最重要的数据结构是sigset_t,它是由一系列在include/linux/signal.件中定义的简单函数所操纵的,这些函数的定义从17123行开始。在x86平台上,这些相同的函
数可以——而且已经——使用汇编语言更加有效的实现了;这些更高效的版本从12204行开始。(m68k端口是唯一一个例外的端口,它使用体系结构特有的代码实现。)由于平台无关的版本和x86特有的版本都很重要,我们会对两者都加以介绍。
平台无关的sigset_t函数
配合sigset_t使用的平台无关的函数在include/linux/sigal.件中,从17123行开始。称为“bitops”(位级的操作)的函数将在后面介绍。
sigaddset
17145:sigaddset把一个信号量加入集合——也就是说,它修改了集合中的一位。
17147:为了便于位操作,将基于0的信号量转化为基于1的信号量。
17149:如果信号量中填入一个无符号长整型数,恰当的位就会被设置。
17151:否则,sigaddset就需要绕很多弯路,首先装入恰当的数组元素,接着设置该元素中相关位。
17148行的代码和该文件中后面的其它代码一样,第一次见到时可能会令人感到有些困惑。在内核代码中,速度是压倒一切的因素。从而,也许你并不会看到类似于下面的运行期间进行决定的代码:
    if (_NSIG_WORDS == 1)
      set->sig[0] |=1UL << sig;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。