Nuttx signal

Signal

基本数据结构和信号掩码说明

信号相关主要数据结构

struct sigactq
{
  FAR struct sigactq *flink;     /* Forward link */
  struct sigaction act;          /* Sigaction data */
  uint8_t   signo;               /* Signal associated with action */
};

结构体sigactq用于描述一个信号的action,指针flink将sigactq结构体连接管理,结构体act包含了信号的服务函数和一些控制信息,signo为信号的信号值。为系统注册一个信号,底层将会用一个sigact结构体来描述之

struct sigpendq
{
  FAR structsigpendq *flink;    /* Forward link */
siginfo_t info;                /* Signal information */
uint8_t   type;                /* (Used to manage allocations) */
};

结构体sigpendq用来描述一个被pend的信号,该信号通过指针flink连接管理,成员info包含信号的详细信息,type被用作该结构体的内存管理信息,用以标识该结构体是从那块内存区域分配的。

struct sigq_s
{
  FAR structsigq_s *flink;      /* Forward link */
union
     {
    void (*sighandler)(intsigno, siginfo_t *info, void *context);
      } action;                      /* Signal action */
sigset_t  mask;                /* Additional signals to mask while the
                                  * the signal-catching function executes */
siginfo_t info;                /* Signal information */
uint8_t   type;                /* (Used to manage allocations) */
};

结构体sigq是信号队列节点,当任务注册了信号,并且接受到了信号,分配一个信号队列节点,将该节点挂载到任务tcb-> sigpendactionq链表上等待运行信号服务函数。指针flink将sigq信号队列节点连接在链表中方便管理,action是一个函数指针,指向信号的服务函数。mask掩码用于当该信号的服务函数运行过程时阻塞其他的信号。Info是信号的详细描述信息。type作为该结构体的内存管理标志,用于标识该结构体从那块内存区域分配的。

在系统初始化过程中,会预先分配sigq_t数据结构、sigpendq_t数据结构和sigactq_t数据结构,并且分别被链接到5个链表上,供后续使用。

g_sigpendingactiong_sigpendingirqaction是sigq资源链表头,用于分配普通sigq资源,而g_sigpendingirqaction用于在中断中分配sigq资源。

Nuttx signal_第1张图片

g_sigpendingsignalg_sigpendingirqsignal是sigpend资源链表头,g_sigpendingsignal用于分配普通的sigpend资源,而g_sigpendingirqsignal用于在中断中分配sigpend资源。

Nuttx signal_第2张图片

g_sigfreeaction是sigact资源链表头,用于分配sigact资源。
Nuttx signal_第3张图片

tcb中于信号相关的数据结构

struct tcb_s {
…
#ifndef CONFIG_DISABLE_SIGNALS
    sigset_t   sigprocmask;                /* Signals that are blocked            */
    sigset_t   sigwaitmask;                /* Waiting for pending signals         */
    sq_queue_t sigpendactionq;             /* List of pending signal actions      */
    sq_queue_t sigpostedq;                 /* List of posted signals              */
    siginfo_t  sigunbinfo;                 /* Signal info when task unblocked     */
#endif
…
}
  • sigprocmask是该任务tcb阻塞信号集。如果某个信号属于阻塞信号集,那么如果发送信号到该tcb,信号将被阻塞,除非该信号是该任务等待的信号,或者该任务取消对该信号的阻塞。

  • sigwaitmask是该任务等待的信号集。

  • sigpendactionq链表用于挂载该任务将要服务的信号节点sigq,等到合该任务开始运行时,sigpendactionq上的节点所代表的信号的服务函数将被运行。信号服务函数运行前,该信号的sigq节点将从sigpendactionq链表上转移到sigpostedq链表上。

  • sigpostedq链表上挂载正在执行信号服务函数的信号节点sigq。当信号服务函数返回后,信号节点sigq将被移除sigpostedq,然后被释放。
    当任务等到它所等待的信号

  • sigunbinfo用于记录信号信息。

task_group中信号相关的数据结构

struct task_group_s {
…
#ifndef CONFIG_DISABLE_SIGNALS
  /* POSIX Signal Control Fields ************************************************/

    sq_queue_t tg_sigactionq;         /* List of actions for signals              */
    sq_queue_t tg_sigpendingq;        /* List of pending signals                  */
#endif

}
  • tg_sigactionq链表用于记录注册到某个任务tcb的信号。
  • tg_sipendingq链表用于记录某个任务tcb接收到的信号。

注册一个信号,即是将关联某个任务tcb与某个信号的服务函数。当任务接收到信号后,对应信号的服务函数期望被运行。这里注意,注册的信号对应的sigact是挂载在任务tcb->group结构体中的sg_sigactionq链表中的,而非任务tcb中。任务tcb接收到的信号sigpend也被挂载到任务tcb->group结构体重的sg_sigpendingq链表中。

信号的种类

Nuttx支持32个信号,最小的信号值为SIGRTMIN,其值为0,最大的信号值为SIGRTMAX,其值为31。使用一个32bit的整形变量,其中的每一个bit的值用来标志一个对应的信号。部分信号值定义如下,其余信号用户可以自定义。

#define     SIGUSR1 1   // for user signal 1
#define SIGUSR2 2   // for user signal 2
#define     SIGALRM 3   // default signal used with POSIX timer
#define     SIGCHILD    4   // used for child thread to signal parent thread
#define     SIGPOLL 5   // sent when asynchronous I/O event occurs
#define     SIGCONDTIMEDOUT 16  // used in the implementation of pthread_cond_timedwait
#define SIGWORK             17  // used to wake up the work queue

信号动作标志

信号动作标志用于指示该信号动作相关的信息。

#define     SA_NOCLDSTOP    (1<<0)
#define     SA_SIGINFO      (1<<1)
#define     SA_NOCLDWAIT    (1<<2)
  • SA_NOCLDSTOP用于指示进程,当子进程退出时,不要产生SIGCHILD。
  • SA_SIGINFO指示,当收到信号后,信号动作服务函数使用三个参数,而不是一个参数。
  • SA_NOCLDWAIT指示,当信号值为SIGCHLD时,不予对子进程退出状态的处理。

信号处理掩码方式

信号处理掩码方式决定了对于特定的信号值,任务如何对待这些信号。

#define SIG_BLOCK   1
#define SIG_UNBLOCK 2
#define SIG_SETMASK 3
  • SIG_BLOCK: 阻塞指定的信号集。其结果是信号阻塞掩码为新的信号集set与之前设定的阻塞信号集逻辑相或。
  • SIG_UNBLOCK: 不阻塞信号集set。其结果是清除任务之前阻塞信号集中的set信号集信号。
  • SIG_SETMASK: 设定对指定的信号集阻塞。与SIG_BLOCK的不同之处在于,SIG_SETMASK重新设定阻塞的信号集。

函数sigprocmask()用于设置任务阻塞掩码。该函数有三个参数,第一个表示使用哪种方式设置阻塞掩码,取值为上述三种方式之一。第二个参数是信号集,其意义决定于第一个参数。第三个参数返回之前设定的阻塞掩码值。该函数原型为:

Int sigprocmask(int how, sigset_t *set, sigset_t *oset)

信号辅助操作

libc中提供了一些操作信号的函数接口。

添加信号到信号集

sigaddset()函数用于将一个信号signo添加到信号集set中,其函数原型为:

int sigaddset(sigset_t *set, int signo)

如果信号signo是非法信号,该函数返回ERROR,否则返回OK。该操作的结果是信号集set中的第signo位被置1。

从信号集中删除信号

sigdelset()函数用于从信号集set中删除信号signo,其函数原型为:

int sigdelset(sigset_t *set, int signo)

如果信号signo是非法信号,该函数返回ERROR,否则返回OK。该操作的结果是信号集set中的第signo位清零。

清空信号集

sigemptyset()函数用于清空信号集set,即信号集set的所有位被清零,该操作总是成功的,返回OK。其函数原型为:

int sigemptyset(sigset_t *set)

全部置位信号集

sigfillset()函数用于全部置位信号集set,即将set的所有位置位,该操作总是成功的,返回OK。其函数原型为:

int sigfillset(sigset_t *set)

阻塞某个信号

函数sighold()用于设置当前任务的阻塞信号掩码,将某个信号添加到任务的阻塞信号集中,最终实现是通过函数sigprocmask()实现的。其函数原型为:

int sighold(int signo)

忽略某个信号

函数sigignore()使得某个任务忽略某个信号。其底层实现是通过sigaction()函数重新注册信号,将该信号的服务函数设置为SIG_IGN,即为NULL。如果系统中已经注册了该信号,那么将该信号对应的sigact从信号组的group->tg_sigactionq链表中移除。其函数原型为:

int sigignore(int signo)

检查信号是否属于信号集

函数sigismember()用于检测某个信号signo是否属于信号集set,即信号集的第signo位是否置位。如果置位,返回1,否则返回0。函数原型为:

int sigismember(const sigset_t *set, int signo)

任务等待信号

函数sigpause()使得一个任务被挂起,等待某个信号。当它所等待的信号到来,任务又开始重新运行。该函数的实现机制为,首先将信signo号从任务的阻塞掩码中移除,然后挂起任务,直到任务接收到信号signo。挂起任务使用sigsuspend()函数,设置任务的阻塞掩码将任务置于挂起等待信号状态。函数sigpause()的原型为:

int sigpause(int signo)

发送信号

发送信号函数raise()实质是kill()的包装,raise()发送信号给当前任务本身。如果系统有注册该信号,那么raise()函数返回时,信号的服务函数已经执行完成。raise()函数的原型为:

int raise(int signo)

取消阻塞信号

函数sigrelse()取消任务对signo的阻塞,函数原型为:

int sigrelse(int signo)

重新绑定信号服务函数

函数signal()重新绑定信号和服务函数,返回之前绑定的服务函数。如果绑定的函数是有效的函数(非SIG_DFL),那么在信号服务函数运行的时候,将忽略该信号。函数原型为:

void (*signal(int signo, void(*func)(int signo)))(int signo)

设定信号服务函数

函数sigset()signal()函数的包装,不同之处在于,如果设定的信号服务函数为SIG_HOLD,那么阻塞该信号,否则,使用signal()重新绑定信号服务函数,并且设定任务对于该信号是阻塞的。如果设置任务对信号阻塞失败,则恢复绑定信号之前的服务函数。sigset()执行成功,返回之前绑定的服务函数。 函数原型如下:

void (*sigset(int signo, void(*func)(int signo)))(int signo)

sigaction注册信号

检查某个信号信号值关联的信号动作,或者关联某个信号值和信号动作。
函数原型

intsinaction(intsigno, structsigaction *act, structsigaction *oact)

当传入参数act为NULL时,返回信号signo关联的信号动作到oact指针中,否则关联signo与信号动作act,并返回先前与signo关联的信号动作到oact指针中。

简化模型,首先分析一个没有被关联任何信号动作的信号的处理情形,然后再讨论已关联了信号动作的情形。

第一种情形,既然某个信号值没有关联任何信号动作,那么该任务的group中的信号动作链表(链表头为group->tg_sigactionq.head)中没有该信号对应的sigact。从系统g_sigfreeaction链表中分配一个sigact,初始化sigact->signo,将该信号动作添加到该任务的group->tg_sigactionq链表中,最后以传入参数act初始化信号动作的sigact->act成员,即复制传入参数act的个成员标量到新分配的sigact->act

第二种情形,某个信号已经关联了信号动作,则将新的信号动作的act复制到已有的信号动作的act。
从这里我们知道,注册一个信号,其实将sigact注册到任务tcb所在的group->tg_sigactionq链表中的。

发送信号

发送信号一个信号到某个任务,其信号源较多,如用户可以通过kill发送信号到指定的任务(si_code为0);可以通过sigqueue向指定的任务发送多个信号(si_code为1);发送信号源可以是Timer定时时间到达(si_code为2);异步I/O完成(si_code为3);消息队列非空(si_code为4);子进程退出(si_code为5);子进程被杀死(si_code为6);子进程异常退出(si_code为7);子进程进入陷进(si_code为8);子进程停止(si_code为9);子进程接着运行(si_code为10);

sig_dispatch

如果nuttx支持group members,那么发送信号使用group_signal将信号发送到与指定PID相关联的group。一般的配置,不会使能HAVE_GROUP_MEMBERS宏选项,除非启用了任务间的父子关系(CONFIG_SCHED_HAVE_PARENT),且调度需要关注子任务的返回状态(CONFIG_SCHED_CHILD_STATUS)。
此时,可以给任务group发送信号,通过group_signal函数实现。
如果nuttx不支持group,采用sig_tcbdispatch将信号发送到指定pid对应的任务。

sig_tcbdispatch

该函数将信号发送到指定pid对应的任务。

1) 如果该信号是对应任务阻塞的信号,那么,如果对应的任务处在等待信号状态,且本次发送的信号是任务等待的信号集之一,那么复制信号信息info到对应任务tcb->sigunbinfo中,清除任务的sigwaitmask,最后调度任务,该任务将被加入readytorun链表。否则,从系统g_sigpendingsignal(或g_sigpendingirqsignal如果是在中断中)链表上分配一个sigpend,以信号的info信息初始化sigpend->info,最终将sigpend添加到任务的group->tg_sigpendingq链表中。

这里我们要注意,如果一个信号被pend,那么,它是被pend到信号的group->tg_sigpendingq链表上。

2) 如果该信号不是任务阻塞的信号,那么先查找任务group->tg_sigactionq链表,是否已有该信号的信号值signo对应的sigact存在,如果存在有效的sigact,那么从系统地g_sigpendingaction(或者如果在中断中,则从g_sigpendingirqaction)链表中分配一个sigq结构并初始化,然后添加到对应的任务tcb->sigpendactionq链表中。任务tcb->sigpendactionq链表中存储的是将要被传递的信号信息。

接下来是设法让信号接收者能够运行信号服务函数,该过程中主要调用信号传递函数sigdeliver()实现信号服务函数的调用。信号传递函数将在后面分析。
如果信号接受者任务是当前执行的任务,即信号是发送给其本身的,那么区别是否是在中断中发送信号。这点可以通过判断宏CURRENT_REGS是否为NULL来区分。如果是在中断中,则中断发生时,系统会将寄存器寄存器赋值给CURRENT_REGS。

1) 如果信号不是在中断中发送的,那么从tcb->sigpendactionq中取出sigq,先放置到tcb->sigpostedq中,接着通过调用信号传递函数sigdeliver,执行信号的服务函数,最后再将sigq从tcb->sigpostedq中移除并释放到g_sigpendingaction或者g_sigpendingirqaction或者内存中,依据sigq->type的值而定。

2) 如果信号是在中断中发送的,那么设置中断的返回地址为函数up_sigdeliver,而将原本的中断返回地址寄存在tcb->xcp.saved_pc中,设置tcb->xcp.sigdeliver函数为sigdeliver函数。这样,当本次中断返回时,首先执行up_sigdeliver函数,将中断返回地址恢复(即将tcb->xcp.saved_pc中恢复PC指针到中断PC指针寄存器中),接着调用tcb->xcp.sigdeliver函数执行信号服务函数。执行完成后将tcb->xcp.sigdeliver赋值为NULL。中断返回后,将返回到中断前的地址接着执行。

    tcb->xcp.sigdeliver       = (FAR void *)sigdeliver;
    tcb->xcp.saved_pc         = CURRENT_REGS[REG_PC];   
    …
    CURRENT_REGS[REG_PC]      = (uint32_t)up_sigdeliver;
    …

up_sigdeliber()函数中,恢复tcb->xcp.saved_pc到寄存器中,中断返回,程序接着从tcb->xcp.saved_pc处继续执行。

我们可以总结到,当一个信号是发送其自身的,如果该信号是在中断函数被发出,那么在中断返回时,信号的服务函数将被立即执行。如果信号不是在中断函数中发出,那么信号的服务函数将被立即执行。

如果信号接受者任务不是它本身,这种情形类似于上述2)的情形,不同之处在于2)中开始使将中断返回地址保存在tcb->xcp.saved_pc中,而在当前的情形下,保存到tcb->xcp.saved_pc中的是信号接受者tcb中保存的该任务被调度运行时将要执行的指令指针。这点容易理解,毕竟任务的一次调度和任务的中断非常类似。

    tcb->xcp.sigdeliver       = (FAR void *)sigdeliver;
    tcb->xcp.saved_pc         = tcb->xcp.regs[REG_PC];
    …
    tcb->xcp.regs[REG_PC]      = (uint32_t)up_sigdeliver;
    …

up_sigdeliber()函数中,恢复tcb->xcp.saved_pc到寄存器中,任务切换后,程序接着从tcb->xcp.saved_pc处继续执行。

同样,我们可以总结到,如果信号的接收者不是发出信号者任务本身,那么信号服务函数将在信号接收任务被调度运行之初被执行。

一个任务收到一个信号,如果该任务没有处于运行态,那么信号将唤醒任务。具体来说,如果任务被挂起处于等待信号的状态,那么调度任务,使得任务就绪;如果任务被挂起处于等待信号量状态,那么强制让任务获得信号量,调度任务,使得任务就绪;如果任务被挂起处于等待消息状态,那么强制停止等待,调度任务,使得任务就绪。

如果任务在等待信号量或者消息状态而被消息唤醒,那么设置任务的errno,使得用户可以通过查询任务的errno来区别任务是由于获取到信号量或者消息恢复运行还是由于信号中断所导致。

Kill 发送信号

Kill系统调用用与于向指定的任务发送信号。不支持发送信号到任务组,只能向某个任务发送信号。

首先初始化一个siginfo的结构体,用于描述本次发送信号的信号值、信号发送源头(SI_USER)、发送信号线程的pid以及初始化错误码。最后通过sig_dispatch()函数完成信号的发送。

info.si_signo           = signo;
info.si_code            = SI_USER;
info.si_errno           = EINTR;
info.si_value.sival_ptr = NULL;
#ifdef CONFIG_SCHED_HAVE_PARENT
info.si_pid             = rtcb->pid;
info.si_status          = OK;
#endif
  /* Send the signal */

    ret = sig_dispatch(pid, &info);

kill()发送信号,只带两个参数,一个是信号接收者的pid,另一个是信号值signo。而后面要提到的另一种发送信号的方式sigqueue,相比kill,可以多携带一个参数。

如果要使用kill发送信号,那么在注册信号时不用设置act->sa_flags,信号的服务函数也只有一个输入参数。

但是目前nuttx并没有对act->sa_flags做检查,内核里面在调用信号的服务函数时,为服务函数同样传递了三个参数。比如将kill信号的服务函数写成如下也是可以的:

void sig_service(int signo, siginfo_t *info, void *context)
{
    if (signo == info->si_signo)
        printf("sig ok\r\n");
}

一般地,kill对应信号的服务函数声明形式如下:

typedef void (*_sa_handler_t)(int signo)

sigqueue发送信号

通过sigqueue发送信号与通过kill发送信号类似,先初始化一个info结构体,再通过sig_dispatch函数将信号发送给指定的任务。

不同之处在于,通过sigqueue发送信号的si_code为SI_QUEUE,同时附带了参数value。

 /* Create the siginfo structure */

info.si_signo           = signo;
info.si_code            = SI_QUEUE;
info.si_errno           = OK;
#ifdef CONFIG_CAN_PASS_STRUCTS
info.si_value           = value;
#else
info.si_value.sival_ptr = sival_ptr;
#endif
#ifdef CONFIG_SCHED_HAVE_PARENT
info.si_pid             = rtcb->pid;
info.si_status          = OK;
#endif

  /* Send the signal */

sched_lock();
ret = sig_dispatch(pid, &info);
sched_unlock();

sigqueue发送信号可以比kill多携带一个参数,这给有些需要携带参数的设计带来了方便。信号服务函数中,通过info->si_value来得到发送信号时传入的参数。

一般地,sigqueue对应信号的服务函数声明形式如下:

typedef void(*_sa_sigaction_t)(int signo, siginfo_t *siginfo, void *context)

timer_signotify发送信号

目前,该函数根据timer定时时间通知方式(SIGEV_SIGNAL与SIGEV_THREAD)决定采用signal还是工作队列处理timer定时时间到事件。

如果采用signal的方式,那么先初始化info结构体,其中si_code初始化为SI_TIMER。其实,这种方式是sigqueue的实现方式,除了si_code不同。

  /* Notify client via a signal? */
if (timer->pt_event.sigev_notify == SIGEV_SIGNAL)
    {
      /* Yes.. Create the siginfo structure */

info.si_signo           = timer->pt_event.sigev_signo;
info.si_code            = SI_TIMER;
info.si_errno           = OK;
#ifdef CONFIG_CAN_PASS_STRUCTS
info.si_value           = timer->pt_event.sigev_value;
#else
info.si_value.sival_ptr = timer->pt_event.sigev_value.sival_ptr;
#endif
#ifdef CONFIG_SCHED_HAVE_PARENT
info.si_pid             = 0;  /* Not applicable */
info.si_status          = OK;
#endif

      /* Send the signal */

DEBUGVERIFY(sig_dispatch(timer->pt_owner, &info));
    }
…

timer定时器发送信号的详细实现,请参考timer定时器一节内容。

sig_mqnotempty消息队列非空发信号

消息队列非空发信号与sigqueue类似,不同之处在于消息队列非空发信号的si_code域的值为SI_MESGQ。当消息队列接收到消息后变为非空,发送信号通知等待消息的任务。

  /* Create the siginfo structure */

info.si_signo           = signo;
info.si_code            = SI_MESGQ;
info.si_errno           = OK;
#ifdef CONFIG_CAN_PASS_STRUCTS
info.si_value           = value;
#else
info.si_value.sival_ptr = sival_ptr;
#endif
#ifdef CONFIG_SCHED_HAVE_PARENT
info.si_pid             = rtcb->pid;
info.si_status          = OK;
#endif

  /* Process the receipt of the signal */

sched_lock();
ret = sig_dispatch(pid, &info);
sched_unlock();

task_sigchild子任务退出

如果启用了任务间的父子关系,那么当子任务退出时,子任务发送信号到父任务。何sigqueue类似,不同之处在于si_code的值为CLD_EXITED。

/* Create the siginfo structure.  We don't actually know the cause.
* That is a bug. Let's just say that the child task just exited
* for now.
*/

info.si_signo           = SIGCHLD;
info.si_code            = CLD_EXITED;
info.si_errno           = OK;
info.si_value.sival_ptr = NULL;
#ifndef CONFIG_DISABLE_PTHREAD
info.si_pid             = chgrp->tg_task;
#else
info.si_pid             = ctcb->pid;
#endif
info.si_status          = status;

/* Send the signal to one thread in the group */

(void)group_signal(pgrp, &info);
…

其他发送信号方式

除了上述的集中发送信号方式,其他的几种发送信号的方式目前在内核代码中没有找到对应的源码,或者还没有实现,或者还没有理清楚。

信号传递

我们可以给任何任务在任何时机(包括中断服务函数,但不包括信号服务函数)发送信号。如果在中断服务函数中发送信号给当前运行的任务,那么该信号的服务函数在中断返回前在信号传递函数中被调用执行。如果发送给其他挂起的任务,这点和在非中断服务函数中发送信号给其他任务的情形就非常类似,那么在信号接收任务被调度运行时,最先运行信号传递函数,在信号传递函数中调用信号的服务函数。

如果信号被任务阻塞,则该信号会初始化一个sigpend结构体,被添加到任务组group->tg_sigpendingq中,也就是说该信号被pend了。等到任务取消对该信号的阻塞,或者任务没有对该信号设置阻塞,那么从任务组group->tg_sigactionq中找出与信号signo对应的sigact,初始化一个sigq_t结构体并链接到任务tcb->sigpendactinoq

信号传递函数sig_deliver()就是处理这些已经被链接到任务tcb->sigpendactinq中的sigq。在一个循环中依次从tcb->sigpendactionq中取出sigq,先将它们转移到任务tcb->sigpostedq,执行信号的服务函数。接着重新查询任务组group->tg_sigpendigq上pend的信号sigpend,重新调用sig_tcbdispatch()函数将对应的信号发送。最后将本次操作的sigq结构体体从任务tcb->sigpostedq上移除,并释放sigq。

这个地方需要注意,在信号传递函数sig_deliver()里面的循环中,执行完信号服务函数后,又重新做了对任务group->tg_sigpeingq链表上的所有sigpend重新使用sig_tcbdispatch()函数发送信号,原因是信号的服务函数真正是推迟执行的,在信号服务函数开始执行时,任务可能被再次发送信号,或者任务的阻塞信号掩码发生变化,所以需要重新对任务组group->tg_sigpendingq链表重新扫描。

实例

实例1, 使用kill发送信号

这个例程中,主任务创建一个子任务, 子任务设置它本身的非阻塞信号集,注册信号,然后子任务通过等待信号量被挂起,直到子任务接收到一个信号,才恢复执行。子任务最后取消对我们指定信号的捕获。主任务创建完子任务后,稍作延迟,对子任务发送指定的信号,最后主任务等待子任务退出并删除子任务。

例程中对有些错误处理做的不到位,领会精神

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// user defined signo
#define WAKEUP_INT 18

static sem_t sig_sem;

// signal service function
static void wakeup_func(int signo)
{
    if (signo == WAKEUP_INT) {
        printf("task sig func run\r\n");
    }
}

/*
*   signal task description
*   1. unblock signal WAKEUP_INT
*   2. install signal service for signal WAKEUP_INT
*   3. sleep on sig_sem by sem_wait() waiting for signal wakeup the task 
*   4. uninstall signal WAKEUP_INT
*/
int sig_task(int argc, char **argv)
{
    int status;
    sigset_t set;
    struct sigaction act;
    struct sigaction oact;

    // unblock the signal
    (void)sigemptyset(&set);
    (void)sigaddset(&set, WAKEUP_INT);
    status = sigprocmask(SIG_UNBLOCK, &set, NULL);

    if (status != OK) {
        printf("sig_task: sigprocmask failed, status %d\r\n", status);
    }

    act.sa_handler = wakeup_func;

    sigfillset(&act.sa_mask);
    sigdelset(&act.sa_mask, WAKEUP_INT);

    status = sigaction(WAKEUP_INT, &act, &oact);
    if (status != OK) {
        printf("sig_task: sigaction failed, status %d\r\n", status);
    }

    status = sem_wait(&sig_sem);
    // check why the process continue running
    if (status != OK) {
        if (EINTR == errno) {
            printf("sem_wait interrupted by signal");
        }
    }

    act.sa_handler = SIG_DFL;
    sigaction(WAKEUP_INT, &act, &oact);
    fflush(stdout);

    printf("sig func run\r\n");

    return 0;
}

/ *
*   signal_test description:
*   1. create sig_task
*   2. send signal WAKEUP_INT using kill()
*   3. wait for sig_task exit and delete it
*/
void signal_test(void)
{
    int sigpid;
    int status; 

    printf("signal test run...\r\n");

    sem_init(&sig_sem,0, 0);

    sigpid = task_create("sigtask", 200,
                           1024, sig_task, NULL);

    if (sigpid < 0) {
        printf("create signal task failed\r\n");
        return;
    }

    status = kill(sigpid, WAKEUP_INT);

    if (status != OK) {
        printf("kill failed, status %d\r\n", status);
    }

    sleep(5);

    waitpid(sigpid, NULL, 0);
    task_delete(sigpid);

    return;    
}

实例2, 使用sigqueue发送信号

实例2 和 实例1基本一样,不同之处在于,在实例2中使用sigqueue发送信号到子任务,并且传递参数给信号的服务函数。在子任务的信号服务函数中,打印验证传入的参数是否正确。

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define WAKEUP_INT 19

static sem_t sig_sem;


static void wakeup_func(int signo, siginfo_t *info, void *param)
{
    if (signo == WAKEUP_INT) {
        printf("received signal, prarm: %s\r\n", (char*)info->si_value.sival_ptr);
    }
}

static int sig_task(int argc, char **argv)
{
    int status;
    sigset_t set;
    struct sigaction act;
    struct sigaction oact;

    // unblock the signal
    (void)sigemptyset(&set);
    (void)sigaddset(&set, WAKEUP_INT);
    status = sigprocmask(SIG_UNBLOCK, &set, NULL);

    if (status != OK) {
        printf("sig_task: sigprocmask failed, status %d\r\n", status);
        return NULL;
    }

    act.sa_handler = wakeup_func;
    act.sa_flags = SA_SIGINFO;

    sigfillset(&act.sa_mask);
    sigdelset(&act.sa_mask, WAKEUP_INT);

    status = sigaction(WAKEUP_INT, &act, &oact);
    if (status != OK) {
        printf("sig_task: sigaction failed, status %d\r\n", status);
    }

    status = sem_wait(&sig_sem);
    // check why the process continue running
    if (status != OK) {
        if (EINTR == errno) {
            printf("sem_wait interrupted by signal\r\n");
        }
    }

    fflush(stdout);

    printf("sig func run\r\n");

    return 0;
}

void sigqueue_test(void)
{
    int sigpid;
    int status;
    union sigval sigval;
    const char *str = "this is sigqueue test";

    printf("signal test run...\r\n");

    sem_init(&sig_sem,0, 0);

    sigpid = task_create("sigtask", 200,
                           1024, sig_task, NULL);

    if (sigpid < 0) {
        printf("create signal task failed\r\n");
        return;
    }

    sigval.sival_ptr = str;
    status = sigqueue(sigpid, WAKEUP_INT, sigval);

    if (status != OK) {
        printf("sigqueue failed, status %d\r\n", status);
    }
    fflush(stdout);
    sleep(1);


    waitpid(sigpid, NULL, 0);
    task_delete(sigpid);

    return;

}

有不妥之处,请各位读者不吝指正

欢迎同仁一起讨论学习Nuttx操作系统

微信: east_moutain_yang

邮箱:[email protected]

你可能感兴趣的:(Nuttx)