VxWorks 6.9 内核编程指导之读书笔记 -- Singnals

Signals

信号是操作系统用于异常处理和异步控制流的关键。在很多方面,信号相当于软件方面的硬件中的中断。操作系统产生的信号包括总线错误和浮点处理异常。信号也提供了API来管理和产生信号。在应用程序中,信号是最合适用来处理异常,而不是为了任务间通信。常见用法包括kill进程和任务,当定时器触发时发送事件或消息到消息队列时发送事件等。

为了兼容POSIX,VxWorks支持63种Signals,每个Signal都有独一无二的标识值和默认的处理(定义在signal.h)。值0用于表示NULL的signal。Signal可以从任务发送给任务或进程,Signal既可以被接收的任务和进程接收或忽略。是否接收或忽略依赖于signal掩码的设置。在内核里,Signal的掩码专门用于任务,如果没有任务设置接收特定的signal,那么它将被忽略。在用户空间,signal的掩码用于进程;一些信号如SIGKILL和SIGSTOP不能被忽略。

为了管理响应Signal,应用程序可以对应用程序有用的方式创建和注册signal处理函数。内核任务和ISR可以为特定任务和进程引发signal。在内核模式,signal的生成和传递运行在产生signal的任务和ISR的上下文中。为了兼容POSIX标准,发送给进程的signal被第一个可用的在进程中已经设置了处理函数的任务处理。每个内核任务都有singal掩码与之关联。signal掩码决定了哪个任务接收该signal。默认情况下,signal掩码被初始化为所有signal不阻塞(即,在内核里,掩码设置没有被继承)。掩码可以被sigorocmask函数修改。

内核里的signal处理器被注册为特定的任务。Signal在正在接收的任务的上下文中执行并使用任务执行的栈。signal处理器即使在任务被阻塞时也会被调用(挂起和搁置)。

VxWorks提供了软件Signal能力,包括POSIX函数,UNIX BSD-compatible函数和本地VxWorks函数。POSIX兼容的Signal接口包括POSIX标准1003.1中的基本Signal接口,也包括POSIX1003.1b的queued-signals扩展的接口。

此外,非POSIX的APIs提供内核和用于应用程序间的signal支持,有:taskSigqueue(),rtpSigqueue(),rtpTaskSigqueue(),taskKill(),rtpKill(),rtpTaskKill()和taskRaise()。

在VxWorks内核中--为了向前兼容--POSIX的API使用进程标识符作为参数,或者任务标识符。

注意:SIGEV_THREAD选项在内核中不支持。建议别同时使用POSIX和VxWorks的API在同一个应用程序中。POSIX的signal在内核和RTP中处理方式是不同的。内核中的处理函数总是以任务的方式,但是用户空间中,处理signal的目标可以是特定任务或整个进程。VxWorks的sigLib的实现并没有在SIGKILL,SIGCONT和SIGSTOP的操作中强加如UNIX中的特殊限制。UNIX实现的signal()不能被调用在SIGKILL和SIGSTOP。

除了Signal,VxWorks也提供了另外一种事件Event通知。当事件Event通知被完全异步发送,但同步接收时,不要求signal处理器。

VxWorks的Signal配置

默认,VxWorks包含了基本的signal组件INCLUDE_SIGNALS。该组件用sigInit()自动初始化signal。为了使用signal event能力,配置INCLUDE_SIGEVENT组件。为了包含POSIX的signal排队能力,启用INCLUDE_POSIX_SIGNALS组件。该组件在使用sigqueueInit()时自动被初始化。sigqueueInit()函数分配空间给sigqueue()使用,它要求为每一个当前入列的signal分配缓存。如果没有缓存,sigqueue将失败。

入队的最多signal数量由参数NUM_SIGNAL_QUEUES来设置。默认是16。

基本Signal函数

Signal在很多方面类似硬件中断。基本Signal能力提供了63种Signals。使用sigvec或sigaction来绑定Signal的处理函数,与ISR通过intConnect绑定ISR一样。通过kill或sigqueue来通告Signal。这类似于中断的发生。sigprocmask()函数让signal被选择性的抑制。某些Signal与硬件异常有关。如,总线错误,非法指令和浮点异常触发特殊Signal。

下面是VxWorks提供的POSIX和BSD的Signal函数的描述

POSIX 1003.1b

Compatible Routine

UNIX BSD

Compatible Routine 

Description
signal() signal() 指定Signal的处理函数。
raise() N/A 发送Signal给你自己。
sigaction() sigvec() 为Signal检查或设置处理函数。
sigsuspend() pause() 挂起任务直到Signal被递交。
sigpending() N/A 返回当前pending的信号集。

sigemptyset()

sigfillset()

sigaddset()

sigdelset()

sigismember()

N/A 操纵Signal掩码。
sigprocmask() N/A 设置阻塞Signal的掩码
sigprocmask() N/A 添加一系列Signal掩码。

VxWorks也提供了类似POSIX和BSD风格的kill()函数,发送signal给任务。还提供了作为POSIX函数的别名,如rtpKill(),从内核发送signal给进程。

排队Signal函数

sigqueue()家族函数提供kill()函数家族的替代方法来发送signal。下面是两者的区别:

  • siqgueue包含了应用程序特定的值作为发送Signal的一部分。这个值提供了signal处理函数的合适的上下文。sigval的值(定义在signal.h);signal处理函数可以在它参数的si_value字段中找到它,一个结构体siginfo_t。
  • sigqueue函数为任何任务启用了多个signal的入列能力。相反,kill函数只能提交一个signal,即使在处理函数运行之前到来多个signal。

VxWorks包含了为应用程序保留的signal,连续编号从SIGRTMIN到SIGRTMAX。这些预留的signal的数量由RTSIG_MAX宏来管理(值是16),定义在POSIX 1003.1标准中。Signal值本身没有被POSIX指定。为了移植,可以为这些signal通过SIGRTMIN加偏移量来指定(如使用SIGRTMIN+2来引用3个预留的signal值)。所有signal通过sigqueue按数值的顺序来入队,低数值放在高数值的前面。

POSIX1003.1也引入了一种替代方式来接收signal。sigwaitinfo不同于sigsuspend或pause,它允许应用程序响应signal而不必使用注册signal函数的机制。当signal可用时,sigwaitinfo返回signal的值,而不调用signal处理函数,即使它被注册。sigtimedwait类似,除了它还提供了超时。

 Routine Description
 sigqueuue() 发送一个入队的signal给任务。 
 sigwaitinfo() 等待一个signal。
 sigtimedwait()  等待signa,l带有超时。

非POSIX的VxWorks排队函数,这些函数是为了帮助V5.x的移植。新开发的RTP应用程序应该使用上面的函数,而不是下面的这些函数。

 Routine  Description
 taskSigqueue()  从进程中的任务发送入队的signal给同一进程中的其它任务,或其它进程中的PUBLIC任务,或从内核任务发送到其它进程。
 rtpSigqueue()  从内核任务发送入队的signal给进程,或从进程发送到另外的进程。
 rtpTaskSigqueue()  从内核任务发送signal给内核空间中进程中特定的任务。

Signal事件

Signal事件能力允许线程或任务在特定事件发生时接收事件通知(如在消息队列中消息到达,或定时器触发等)。下面的函数用于注册时间活动相应的事件通知:mq_notify(),timer_create(),timer_open(),aio_read(),aio_write()和lio_listio()。

POSIX 1003.1-2001标准定义了3个signal事件通知类型:

SIGEV_NONE

当事件发生时,没有要求通知。这对于使用轮询的异步I/O操作的应用程序非常有用。

SIGEV_SIGNAL

当事件发生时,产生一个signal

SIGEV_THREAD

提供回调函数来完成异步通知,该函数在新线程上下文中的调用。这种方式对于多线程的进程提供了一种比signal更自然的方式。VxWorks在用户模式支持该选项,在内核模式不支持。

通知类型可以通过sigevent结构体来指定。结构体指针用于注册事件通知。要使用该功能,必须启用INCLUDE_SIGEVENT组件。

Signal处理函数

Signal比起作为任务间通信而言,更适合用于错误和异常处理。通常,Signal处理函数应该被看作类似ISRs:不应该在Signal的处理函数中调用其它能引起阻塞的函数。因为Signal是异步,很难确定当特殊Signal触发时什么资源可用。

为了安全,调用可以安全使用的函数,除非你确定不会引起死锁,否则不要违法该惯例。此外,当为signal使用C++或当以C或汇编编写的处理函数中调用C++函数时,尤其应该小心。使用C++会引入以下问题:

  • 当中断或signal发生时,intConnect和signal函数需要要执行函数的地址,但是非静态成员函数的地址不能被使用,所以必须实现静态成员函数。
  • 在Signal处理函数中,对象不能被实例化和删除。
  • 在Signal处理函数中要执行C++代码应该限制嵌入式C++。不能使用异常或RTTI。

大部分Signal是异步提交给应用程序执行。因此需要考虑非预期信号的发生,并优雅地处理。不像ISR,signal处理函数运行在中断任务的上下文中。VxWorks并没有区别普通任务和signal的上下文,它只区别普通任务和ISR的上下文。因此,系统无法区别是普通任务还是正在执行signal处理的任务,它们是一样的。

当编写信号处理函数时,确保

  • 在退出之前释放资源
  1. 释放任何分配的内存
  2. 关闭任何打开的文件
  3. 释放任何互斥的资源,如信号量
  • 任何修改过的数据结构处于健全的状态
  • 通知内核返回适当的错误值

在Signal处理函数和任务间的互斥必须小心处理。通常,用户应该在signal处理函数中避免以下活动

  • 获取可能已经被其它代码获取的互斥资源(可能导致死锁)。
  • 修改当Signal递交时已经被其他代码修改的共享数据内存。
  • 使用longjmp来修改任务执行的流程。如果longjmp用在signal处理函数去重新初始化一个正在运行的任务,必须确保当该任务正在拥有关键资源时(如内核互斥),signal没有发送到该任务。举个例子,如果signal被发送到正在执行malloc的任务,signal的处理函数调用longjmp使得内核处于不一致的状态。

这些场景对于调试非常困难,应该避免。安全的做法是同步应用程序代码,或从signal处理函数设置专用的标志和数据结构,从其它地方读取该标志和数据结构。应用程序应该周期性地检查标志是否在后台被Singal处理函数修改,然后相应的处理。volatile关键字在signal和其它代码访问内存时非常有用。

在signal处理函数中获取互斥信号量是非常糟糕的想法。互斥信号量可能被递归获取。因此,signal的处理函数能够很轻松地重新获取被其它代码占有的互斥。因为,signal处理函数是异步执行实体,因而打破了互斥应该支持的排斥性。

在signal处理函数中获取二进制信号量也是糟糕的做法。如果已经被其它元素获取,signal处理函数将引起任务的阻塞。这种死锁无法恢复。如计数信号量可用,则会有与互斥有相同的问题,如果不可用,则与二进制信号量一样引起无法恢复的死锁。

一般来说,signal应该只用于通知和处理异常或错误条件。在进程间通信或在应用程序数据流路径中的signal的使用会引起上面描述的缺点。

 

posted on 2016-09-29 11:29  黑暗帝国  阅读(1055)  评论(0编辑  收藏  举报

导航