linux 系统信号和中断常识

linux 系统信号和中断常识

什么是中断

  1. 中断基本概念

中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。引起中断发生的事件被称为中断源。中断源向CPU发出的请求中断处理信号称为中断请求,而CPU收到中断请求后转到相应的事件处理程序称为中断响应。
在有些情况下,尽管产生了中断源和发出了中断请求,但CPU内部的处理器状态、字PSW的中断允许位已被清除,从而不允许CPU响应中断。这种情况称为禁止中断。CPU禁止中断后只有等到PSW的中断允许位被重新设置后才能接收中断。禁止中断也称为关中断,PSW的中断允许位的设置也被称为开中断。开中断和关中断是为了保证某段程序执行的原子性。
还有一个比较常用的概念是中断屏蔽。中断屏蔽是指在中断请求产生之后,系统有选择地封锁一部分中断而允许另一部分中断仍能得到响应。不过,有些中断请求是不能屏蔽甚至不能禁止的,也就是说,这些中断具有最高优先级,只要这些中断请求一旦提出,CPU必须立即响应。例如,电源掉电事件所引起的中断就是不可禁止和不可屏蔽的。

  1. 中断分类与等级

根据系统对中断处理的需要,操作系统一般对中断进行分类并对不同的中断赋予不同的处理优先级,以便在不同的中断同时发生时,按轻重缓急进行处理。
根据中断源产生的条件,可把中断分为外中断和内中断。外中断是指来自处理器和内存外部的中断,包括I/0设备发出的I/O中断、外部信号中断(例如用户键人ESC键)。各种定时器引起的时钟中断以及调试程序中设置的断点等引起的调试中断等。外中断在狭义上一般被称为中断。
内中断主要指在处理器和内存内部产生的中断。内中断一般称为陷阱(trap)或异常。它包括程序运算引起的各种错误,如地址非法、校验错、页面失效、存取访问控制错、算术操作溢出、数据格式非法、除数为零、非法指令、用户程序执行特权指令、分时系统中的时间片中断以及从用户态到核心态的切换等都是陷阱的例子。
为了按中断源的轻重缓急处理响应中断,操作系统为不同的中断赋予不同的优先级。例如在UNIX系统中,外中断和陷阱的优先级共分为8级。为了禁止中断或屏蔽中断,CPU的处理器状态字PSW中也设有相应的优先级。如果中断源的优先级高于PSW的优先级,则CPU响应该中断源的请求;反之,CPU屏蔽该中断源的中断请求。
各中断源的优先级在系统设计时给定,在系统运行时是固定的。而处理器的优先级则根据执行情况由系统程序动态设定。
除了在优先级的设置方面有区别之外,中断和陷阱还有如下主要区别:
陷阱通常由处理器正在执行的现行指令引起,而中断则是由与现行指令无关的中断源引起的。陷阱处理程序提供的服务为当前进程所用,而中断处理程序提供的服务则不是为了当前进程的。
CPU执行完一条指令之后,下一条指令开始之前响应中断,而在一条指令执行中也可以响应陷阱。例如执行指令非法时,尽管被执行的非法指令不能执行结束,但CPU仍可对其进行处理。

  1. 软中断

软中断的概念主要来源于UNIX系统。软中断是对应于硬中断而言的。通过硬件产生相应的中断请求,称为硬中断。而软中断则不然,它是在通信进程之间通过模拟硬中断而实现的一种通信方式。中断源发出软中断信号后,CPU或者接收进程在“适当的时机”进行中断处理或者完成软中断信号所对应的功能。这里“适当的时机”,表示接收软中断信号的进程须等到该接收进程得到处理器之后才能进行。如果该接收进程是占据处理器的,那么,该接收进程在接收到软中断信号后将立即转去执行该软中断信号所对应的功能。

  1. 中断处理过程
    一旦CPU响应中断,转人中断处理程序,系统就开始进行中断处理。下面对中断处理过程进行详细说明:

    1. CPU检查响应中断的条件是否满足。CPU响应中断的条件是:有来自于中断源的中断请求、CPU允许中断。如果中断响应条件不满足,则中断处理无法进行。

    2. 如果CPU响应中断,则CPU关中断,使其进入不可再次响应中断的状态。

    3. 保存被中断进程现场。为了在中断处理结束后能使进程正确地返回到中断点,系统必须保存当前处理器状态字PSW和程序计数器PC等的值。这些值一般保存在特定堆栈或硬件寄存器中。

    4. 分析中断原因,调用中断处理子程序。在多个中断请求同时发生时,处理优先级最高的中断源发出的中断请求。在系统中,为了处理上的方便,通常都是针对不同的中断源编制有不同的中断处理子程序(陷阱处理子程序)。这些子程序的人口地址(或陷阱指令的人口地址)存放在内存的特定单元中。再者,不同的中断源也对应着不同的处理器状态字PSW。这些不同的PSW被放在相应的内存单元中,与中断处理子程序人口地址一起构成中断向量。显然,根据中断或陷阱的种类,系统可由中断向量表迅速地找到该中断响应的优先级、中断处理子程序(或陷阱指令)的入口地址和对应的PSW。

    5. 执行中断处理子程序。对陷阱来说,在有些系统中则是通过陷阱指令向当前执行进程发出软中断信号后调用对应的处理子程序执行。

    6. 退出中断,恢复被中断进程的现场或调度新进程占据处理器。

    7. 开中断,CPU继续执行。

  2. 设备管理程序与中断方式

处理器的高速和输入输出设备低速之间的矛盾,是设备管理要解决的一个重要问题。为了提高整体效率,减少在程序直接控制方式中的CPU等待时间以及提高系统的并行工作效率,采用中断方式来控制输入输出设备和内存与CPU之间的数据传送,是很有必要的。在硬件结构上,这种方式要求CPU与输入输出设备(或控制器)之间有相应的中断请求线,而且在输入输出设备控制器的控制状态寄存器上有相应的中断允许位。

信号

信号驱动的异步I/O是指一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态。异步 I/O 和硬件上常提的中断的概念类似,信号是在软件层次上对中断机制的一种模拟。

  1. 信号通信机制
    软中断信号(signal,又简称为信号)是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
    进程之间可以互相通过系统调用kill发送软中断信号,内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

  2. 处理信号
    收到信号的进程对各种信号有不同的处理方法,主要分为以下三类:

    • 类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。进程通过系统调用signal来指定进程对某个信号的处理行为。
    • 忽略某个信号,对该信号不做任何处理,就象未发生过一样。
    • 对该信号的处理保留系统的默认值,对大部分的信号的缺省操作是使得进程终止。

需要注意的是,信号处理函数注册后,当信号来临并被触发调用信号处理函数,当同样类型信号再次到来时,并不会执行信号处理函数,而是使用信号的系统默认处理方式,大部分都是使得进程终止。

  1. 信号可靠性
    从可靠性方面信号分为可靠信号与不可靠信号;信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。信号值位于 SIGRTMIN 和 SIGRTMAX 之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。
    非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
    不可靠信号就是指发送的信号内核不一定能够发送给 目标进程,信号可能丢失。 不可靠信号在内核中存储的方式是位图和链表,当内核接收一个信号后,先判断它是否 已经存在,不存在就把他对应的位置一,并将信号挂入链表,存在则丢弃信号。 可靠信号则是把信号放入队列,再链入链表,所以也就保证了信号不丢失。
    非可靠信号一般都有确定的用途及含义, 可靠信号则可以让用户自定义使用
    需要注意的是:这里的“实时”和实时操作系统中的“实时”没有任何联系,实时信号在处理速度上并不会比普通信号快,它们之间的区别就是:普通信号会对多次的同一个信号进行“合并”处理,而实时信号会一一处理。这就要求我们在编写信号监听函数时,要捕获普通信号,必须时刻轮训监听,因为系统默认会丢弃同种类型的普通信号!

  2. 信号传递顺序
    如果存在多个未决信号,同一个未决信号会按照发送顺序来递送信号,不同的未决信 号按照信号的序号大小来递送,序号小的信号会先被递送到进程。另外,linux中会优 先递送不可靠信号
    Linux中的信号机制优先级是:高优先级中断->低优先级中断->软中断->信号->进程运行。
    需要注意的是,在用户态不存在未决信号。信号处理一般发生在进程从内核态返回用户态的时候。内核空间没有信号处理机制,内核态也不会处理信号否者信号拥有系统最高权限,变得不再安全

  3. 多线程中信号造成死锁
    如果一个线程持有锁,在操作临界区内容时,被信号中断了,转而去执行信号处理函数, 而信号处理函数再次对临界区加锁就会造成死锁。
    解决的方法就是使用信号等待函数,线程阻塞等待信号处理函数直到处理完毕,也就是 所说的化异步为同步。

  4. 信号响应过程

    1. A进程调用信号发送函数,发送信号给B,这是软中断,所以A进程会进入内核态运行操作系统的信号调度代码
    2. 操作系统发现B进程正在运行,于是写入管理B进程的某个数据结构
    3. 操作系统返回给A,A继续执行
    4. B进程分配的处理器时间用完了,被时钟硬件中断
    5. 操作系统的时钟硬件中断处理函数准备挂起B进程,也就是把寄存器和函数堆栈保存起来,发现B进程收到了singal
    6. 操作系统在保存好B进程的stack和register后,新开stack(为了不干扰B进程真正的代码stack),激活B进程,B进程的信号处理函数。
  5. 信号生命周期(和响应过程类似)

    • 在目的进程中安装该信号。即设置捕获该信号时进程执行的操作,采用signal 或者 sigaction 系统调用来实现。
    • 信号被某个进程产生,同时设置该信号的目的进程(使用pid),之后交给操作系统进行管理。采用kill()、arise()、alarm()等系统调用来实现。
    • 信号在目的进程被注册。就是把信号值加入到进程的PCB(task_struct)中相关的数据结构里——未决信号的数据成员,信号携带的其他信息被保留到未决信的队列的某个sigqueue结构中。
    • 信号在进程中注销。在执行信号处理函数前,要把信号在进程中注销。
    • 信号生命的终结。进程终止当前的工作,保护上下文,执行信号处理函数,之后恢复。
  6. 信号阻塞集(屏蔽集、掩码)
    信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。
    所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

信号实现机制

  1. 发送信号
    内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。
    进程的 PCB 中有关于本进程中未决信号的数据成员struct sigpending pending

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该进程被信号阻塞。

当一个可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失。这意味着同一个可靠信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个可靠信号,都会为它分配一个结构来注册该信号信息,并把该结构添加在未决信号链尾)。

当一个非可靠信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。

总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。

  1. 处理信号
    内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
    内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当进程接收到一个它忽略的信号时,进程丢弃该信号,就像没有收到该信号似的继续运行。
    如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。**而且执行用户定义的函数的方法很巧妙,内核在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。**这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。
    对于非可靠信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。
    当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。

参考文献

CS_Offer/Signal.md at master · xuelangZF/CS_Offer · GitHub
信号处理的时机
面试中关于Linux的信号常问的问题 | 等英博客
linux系统编程之信号(一):中断与信号 – mickole – 博客园
Linux系统编程——进程间通信:信号中断处理 – CSDN博客

发表评论

电子邮件地址不会被公开。 必填项已用*标注