1.信号

信号作为一种操作系统机制,用于处理异常条件并对控制流进行异步通知。在很多方面,信号是一种类似于硬件中断的软件机制。为响应总线错误、浮点数异常等,操作系统都会产生对应的信号。信号机制提供了用于产生与管理信号的API。

对于应用程序,信号最适合用于错误和异常处理,对于任务间通信则不太适合。常见的使用方式包括:使用信号杀死进程和任务、当定时器到达或当一个消息到达消息队列,则发送一个信号事件,等等。

为了与POSIX保持一致,VxWorks提供了63个信号,每个信号都有单独的信号值以及默认的动作(在signal.h中定义)。NULL signal将使用预留的信号值0。

信号可以在任务到任务或任务到进程之间发送。任务可以选择接受或忽略一个信号。信号是被接收或被忽略,都取决于信号掩码(signal mask)的设置情况。在内核中,信号掩码是任务所特有的,如果没有任务被设置为接受某个特定的信号,那么这个信号将被忽略。在用户空间中,信号掩码是进程所特有的。有些信号如SIGKILL和SIGSTOP是不能被忽略的。

为了响应信号,可以创建并注册信号处理函数,使得任务可以响应一个特定的信号。一个内核任务或ISR可以向特定的任务和进程发送信号。在内核中,信号的产生与传递过程都是在产生信号的任务上下文或ISR中进行。为了与POSIX标准保持一致,一个发送给进程的信号,都是由第一个已经设置了可以处理该信号的进程负责。

每个内核任务都有一个与之相关的信号掩码。信号掩码决定了任务将接受哪些信号。默认情况下,信号掩码在初始化时被设置为不屏蔽任何信号(在内核中不存在掩码继承)。可以使用sigprocmask()函数修改掩码。

在内核中,可以为一个特定的任务注册信号处理函数。信号处理函数在接收信号的任务的上下文中执行,并且使用了该任务的执行栈空间。即使任务被阻塞,也会调用信号处理函数。

VxWorks提供了软件信号机制,其中包括了POSIX函数、UNIX BSD兼容的函数以及VxWorks原生的函数。POSIX兼容的信号接口既包括由POSIX1003.1规定的基本信号接口,也包含了由POSIX1003.1B规定的队列信号扩展。

此外,非POSIX API提供了内核与用户应用程序之间的信号。包括taskSigquue()、rtpSigqueue()、rtpTaskSigqueue()、taskKill()、rtpTaskKill()、taskRaise()。

在VxWorks内核中(为了向后兼容),当类POSIX接口需要使用进程ID作为其参数时,应该使用任务ID代替。

一个RTP任务(用户模式)可以向如下目标发送信号:

  • 自己
  • 进程中的其他任何任务
  • 系统中任意进程中的任意任务
  • 父进程
  • 系统中的任何其他进程

一个用户空间的任务不能像内核任务发送信号,即使该任务是一个公共任务。通常发送给一个进程中的任务的信号,都会导致该进程终止。

与内核空间的信号不同,用户空间的信号的产生是在发送任务的上下文进行的,但是信号的传递过程是在接受任务的上下文中进行。

对于进程而言,信号处理函数作用在整个进程,而不是进程中的某个特定任务。

信号处理是在进程范围完成的。比如,如果进程中的一个任务注册了一个信号处理函数,且该任务等待着该信号,那么发送给给进程的信号将由这个任务负责处理。此外,任何没有屏蔽这个信号的任务都贱给处理该信号。如果没有任务等待一个给定的信号,那么这个信号将在进程中挂起,直到有一个任务可以接收该信号为止。

每个任务都有一个信号掩码。信号掩码决定了该任务可以接受哪些信号。当创建任务时,其信号掩码将继承自创建它的任务。如果父进程是一个内核任务(即该进程是从内核中创建),那么这个任务的信号掩码将被全部初始化为非屏蔽状态,同时也将继承每个信号的默认动作。后续可以使用sigpromask()函数修改屏蔽状态与默认动作。

注意:内核进程与实时进程对POSIX信号的处理是不同的。在内核中,一个信号的目标总是一个任务;但是在用户空间,信号的目标既可以是一个任务,也可以是整个进程。

除了信号之外,VxWorks还通过事件机制提供了其他类型的事件通知机制。信号事件是完全异步的,但VxWorks事件机制则不同。事件机制在发送时是异步的,在接收时是同步的,并且不需要一个信号处理函数。

2.VxWorks信号配置

默认条件下,VxWorks已经包含了基本的信号机制组件(INCLUDE_SIGNALS)。其他组件可以按需添加。

3.信号相关的基础函数

信号与硬件中断很像。基础的信号机制提供了63个信号。

  • 可以使用sigvec()或sigaction()函数为信号绑定信号处理函数;
  • 可以通过kill()或sigqueue()函数发送一个信号;
  • 可以通过sigpromask()函数屏蔽特定的信号。

某些信号与硬件异常有关。例如,总线错误、非法指令、浮点数异常都会触发特定的信号。

VxWorks也提供POSIX或类BSD的kill()函数,用于向一个任务发送信号。

内核信号函数包括:

VxWorks 7 Signal Mechanism

VxWorks也提供额外的函数作为POSIX函数的替补,比如rtpKill(),用于从内核向进程发送信号。

用户层信号函数包括:

VxWorks 7 Signal Mechanism

VxWorks 7 Signal Mechanism

4.信号队列函数

sigqueue()系列函数与kill()系列函数类似,都是用于发送信号。

两者的区别如下:

  • sigqueue()函数将应用程序指定的值作为信号的一部分发送。该值可以供信号处理函数使用,其类型为sigval;信号处理函数将在siginfo_t结构体的si_value成员中获取到该值。
  • 对于任何任务,sigqueue()函数将把多个信号加入队列中。与此相反,就算信号处理函数执行之前已经有多个信号到达,kill()函数也仅发送一个信号。

VxWorks中包含了为应用程序保留的信号,信号值从SIGRTMIN到SIGRTMAX。根据POSIX1003.1标准,由RTSIG_MAX宏设置保留的信号数量(默认为16)。信号值不是由POSIX规定。为了满足可移植性,应该基于SIGRTMIN指定信号的值。所有由sigqueue()函数传递的信号都按照数字顺序排入队列,信号值小的信号排在信号值大的信号之前。

POSIX1003.1还引入了其他方法接收信号。sigwaitinfo()函数与sigsuspend()或pause()函数不同,其可以允许应用程序在不注册处理函数的情况下响应信号:当一个信号可用时,sigwaitinfo()函数将信号的值作为结果返回,就算已经注册了一个处理函数也不会触发它。sigtimedwait()函数也类似,区别在于后者可以设置超时。

基本的POSIX信号队列函数描述如下。这些函数应该由执行在实时进程中的应用程序中调用。

VxWorks 7 Signal Mechanism

其他的非POSIX的VxWorks信号队列函数描述如下。这些函数可以用于将VxWorks5.x内核应用程序移植到RTP中。

VxWorks 7 Signal Mechanism

5.信号事件

信号事件机制允许一个线程或任务按照信号的方式接受特定事件的通知。

下列函数可以用于为特定的事件活动注册信号通知:

  • mq_notify()
  • timer_create()
  • timer_open()
  • aio_read()
  • aio_write()
  • lio_listio()

POSIX 1003.1-2001标准定义了三种信号事件通知类型:

SIGEV_NONE

表明当事件发生时,不需要通知。该类型适用于使用异步I/O轮询的应用程序。

SIGEV_SIGNAL

表明当事件发生时将产生信号。

SIGEV_THREAD

为在一个新线程上下文中调用的函数所调用,提供异步通知的回调函数。可以为进程中的多个线程提供比signal更好的通知方式。VxWorks仅在用户模式支持该选项。

通知类型定义在sigeventCommon.h头文件的sigevent结构体中。结构体中的一个指针用于注册信号通知。

6.信号处理函数

相较于作为一种通用的任务间通信机制,信号更适用于错误和异常处理。此外,信号处理函数也应该像ISR一样对待:在信号处理函数中不应该调用函数,以免导致处理函数阻塞。

因为信号是异步的,所以当某个特定信号触发时,很难预测哪些资源是不可用的。

所以,为了确保万无一失,应该仅调用如下函数:

VxWorks 7 Signal Mechanism

编写信号处理函数的注意事项:

(1)在退出前释放资源

  • 释放任何已分配的内存
  • 关闭任何已打开的文件
  • 释放任何互斥资源,如信号量

(2)使任何修改过的数据结构保持正常状态

(3)对于RTP应用程序,通过一个合适的错误返回值修改父进程

(4)对于内核应用程序,使用一个合适的错误返回值通知内核

信号处理函数与任务之间的互斥操作必须小心管理。

通常,应该在信号处理函数中避免以下操作:

  • 获取同样能够被应用程序中其他任务获取的互斥资源(如信号量)。这可能导致死锁;
  • 尝试修改应用程序中其他任务也可以修改的共享数据内存。这可能破坏数据。
  • 使用longjmp()函数改变任务执行的流程。如果longjmp()用于在信号处理函数中重新初始化一个正在运行的任务,必须确保接收信号的任务没有使用关键资源。例如,一个信号被发送给一个正在使用malloc()函数进行内存分配的任务,然后信号处理函数调用了longjmp(),这将导致内核进入一个不稳定的状态。