任务和信号信号是在软件层次上对中断机制的模拟,在原理上说,一个任务接收到一个信号,与CPU接收到中断请求是一致的。信号是异步的,任务不必通过任何操作来等待信号的到达,它甚至不知道信号何时会到达。
信号的来源包括:
硬件来源,比如按键触发
软件来源,比如kill,raise等系统函数,比如一些非法运算操作等
任务组Nuttx为进程和线程提供了信号接口,可以在任务上下文中或在任务上下文之间,通过信号这种异步通信机制,来改变任务的控制流。在任何一个任务中或中断处理函数中,可以给指定TASK ID的其他任务发送信号。接收到信号的任务将在具有优先级时执行任务指定的信号处理函数。信号处理程序是一个用户提供的函数,它绑定到一个特定的信号,并在接收到信号时执行任何必要的操作。默认情况下,没有对任何信号设置预定义动作,所有信号的默认操作都是忽略(如果用户没有提供信号处理函数),从这个意义上说,所有Nuttx默认情况下都是实时信号。
发送信号给多线程任务组Nuttx既支持任务task,又支持线程pthreads。task和pthreads的主要区别在于task之间的独立性要高得多。task可以创建pthreads,这些pthreads将共享task的资源。主task线程和它所包含的pthreads,一起被称为任务组,在Nuttx中使用任务组来模拟POSIX的进程。
多线程任务组中的信号行为是复杂的。Nuttx使用任务组模拟进程,并遵循POSIX规则进行信号发送。通常,当向一个任务组发送信号时,需要向创建该任务组的主task线程的ID号发送(实际上,其他任务不应该知道该任务组中创建的内部线程ID)。任务组会记住该ID(即使主任务线程退出)。
当向一个多线程任务组发送信号时,会出现以下情况:
当任务组接收到一个信号,那么任务组中只有一个不阻塞该信号的不确定线程会接收到信号。
当任务组接收到一个信号,并且有多个线程在等待该信号,有且只有一个不确定的线程将接收该信号。
可以使用sigprocmask()或pthread_sigmask()接口来屏蔽信号。信号被屏蔽后,将不会在具有屏蔽该信号的线程中接收到。在创建新的线程时,新线程将会继承父线程的信号掩码,因此如果在一个线程上阻塞某个信号,那么在它所创建的线程中也会阻塞该信号。
API接口可以通过信号掩码来控制哪个线程接收信号,例如,创建一个线程,该线程的唯一目的是捕获某个特定的信号并且做出响应:在主任务中阻塞信号;这时该信号会在任务组中被所有的线程阻塞。在一个“信号处理线程”中,使能了信号,这个线程将是唯一接收信号的线程。
本来想一上来就分析数据结构,看了一圈源代码,发现还是先从应用层的API入手,有个全面的认识后,再逐层去分析底层的原理。
API如下:
int sigemptyset(sigset_t *set) ; /* 清空set信号集, 排除所有信号 */int sigfillset(sigset_t *set); /* 置位set信号集,包含所有信号 */int sigaddset(sigset_t *set, int signo); /* 将信号signo添加进set信号集 */int sigdelset(sigset_t *set, int signo); /* 将signo信号从set信号集中删除 */int sigismember(const sigset_t *set, int signo); /* 判断signo信号是否属于set信号集 */int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); /* 信号安装函数,将sigaction与一个特定的信号进行绑定,sigaction结构体在下文会介绍 */int sigignore(int signo); /* 忽略signo信号 */void (*sigset(int signo, void (*disp)(int)))(int); /* 改变signo信号的配置, disp可以是SIG_DFL、SIG_IGN,或者信号处理Handler */int sigprocmask(int how, const sigset_t *set, sigset_t *oset); /* 根据how的策略,来改变当前阻塞的信号集 */int sighold(int signo); /* 将signo信号添加进进程的阻塞信号集 */int sigrelse(int signo); /* 将sigo信号从进程的阻塞信号集中移除 */int sigpending(sigset_t *set); /* 返回在阻塞期间收到的阻塞信号的集合 */int sigsuspend(const sigset_t *set); /* 在接收到某个信号之前,临时用set替换进程的信号掩码,并暂停进程执行,直到收到信号为止 */int sigpause(int signo); /* 将signo信号从信号掩码中移除,暂停进程,直到收到信号为止 */int sigwaitinfo(const sigset_t *set, struct siginfo *info); /* 调用的sigtimedwait */int sigtimedwait(const sigset_t *set, struct siginfo *info, const struct timespec *timeout); /* 将set作为阻塞信号集,当多个信号到达时,返回最小的返回,如果没有信号到达,在timeout时间内,进程会暂停,直到收到信号或者时间到期 */int sigqueue (int tid, int signo, union sigval value); /* 向tid Task发送signo信号,信号携带value数据 */int kill(pid_t pid, int sig); /* 向pid Task发送sig信号 */int pause(void); /* 暂停当前调用线程,直到收到一个non-blocked信号 */
数据结构从上述接口中可以看出,大致可以分为以下几类:
对信号集/信号本身的操作:比如信号集的清空与置位、将信号从信号集中删除、增加信号到信号集中、判断信号是否属于信号集等
对信号的行为响应:比如首先需要信号安装、设置信号的Handler、忽略某个信号、阻塞某些信号、在接收到某些信号前暂停当前进程(会涉及到任务的切换)等
发送信号:向某个特定的task发送信号,信号中还能携带数据
数据结构又分为两部分:Kernel部分和User部分,其中Kernel部分也需要用到User部分的定义。
User部分,定义在include/signal.h中,主要描述信号的基本数据结构以及API接口
信号集的定义,总共包含32中信号,Nuttx提供了部分信号,其余的用户可以自定义
/* This defines a set of 32 signals (numbered 0 through 31). * REVISIT: Signal 0 is, however, not generally usable since that value has * special meaning in some circumstances (e.g., kill()). */typedef uint32_t sigset_t; /* Bit set of 32 signals */#define __SIGSET_T_DEFINED 1/* Signal set management definitions and macros. */#define NULL_SIGNAL_SET ((sigset_t)0x00000000)#define ALL_SIGNAL_SET ((sigset_t)0xffffffff)#define MIN_SIGNO 0#define MAX_SIGNO 31#define GOOD_SIGNO(s) ((((unsigned)(s))<=MAX_SIGNO))#define SIGNO2SET(s) ((sigset_t)1 << (s))/* A few of the real time signals are used within the OS. They have * default values that can be overridden from the configuration file. The * rest are all user signals. * * The signal number zero is wasted for the most part. It is a valid * signal number, but has special meaning at many interfaces (e.g., Kill()). * * These are the semi-standard signal definitions: */#define SIGUSR1 1 /* User signal 1 */#define SIGUSR2 2 /* User signal 2 */#define SIGALRM 3 /* Default signal used with POSIX timers (used only */ /* no other signal is provided) */#define SIGCHLD 4 /* Used by child threads to signal parent thread */#define SIGPOLL 5 /* Sent when an asynchronous I/O event occurs *//* The following are non-standard signal definitions */#define SIGCONDTIMEDOUT 16 /* Used in the implementation of pthread_cond_timedwait */#define SIGWORK 17 /* Used to wake up the work queue */
信号事件的定义,主要用于向消息队列发送信号,通知某个task队列中已经有消息了
/* Values for the sigev_notify field of struct sigevent */#define SIGEV_NONE 0 /* No asynchronous notification is delivered */#define SIGEV_SIGNAL 1 /* Notify via signal,with an application-defined value */#ifdef CONFIG_SIG_EVTHREAD#define SIGEV_THREAD 3 /* A notification function is called */#endif/* This defines the type of the siginfo si_value field */union sigval { int sival_int; /* Integer value */ FAR void *sival_ptr; /* Pointer value */};/* This structure contains elements that define a queue signal. The following is * used to attach a signal to a message queue to notify a task when a message is * available on a queue */#ifdef CONFIG_CAN_PASS_STRUCTStypedef CODE void (*sigev_notify_function_t)(union sigval value);#elsetypedef CODE void (*sigev_notify_function_t)(FAR void *sival_ptr);#endifstruct sigevent{ uint8_t sigev_notify; /* Notification method: SIGEV_SIGNAL, SIGEV_NONE, or SIGEV_THREAD */ uint8_t sigev_signo; /* Notification signal */ union sigval sigev_value; /* Data passed with notification */#ifdef CONFIG_SIG_EVTHREAD sigev_notify_function_t sigev_notify_function; /* Notification function */ FAR pthread_attr_t *sigev_notify_attributes; /* Notification attributes (not used) */#endif};
信号的定义,描述信号的内部细节信息,用于在信号Handler中的参数传递
/* These are the possible values of the signfo si_code field */#define SI_USER 0 /* Signal sent from kill, raise, or abort */#define SI_QUEUE 1 /* Signal sent from sigqueue */#define SI_TIMER 2 /* Signal is result of timer expiration */#define SI_ASYNCIO 3 /* Signal is the result of asynch IO completion */#define SI_MESGQ 4 /* Signal generated by arrival of a message on an */ /* empty message queue */#define CLD_EXITED 5 /* Child has exited (SIGCHLD only) */#define CLD_KILLED 6 /* Child was killed (SIGCHLD only) */#define CLD_DUMPED 7 /* Child terminated abnormally (SIGCHLD only) */#define CLD_TRAPPED 8 /* Traced child has trapped (SIGCHLD only) */#define CLD_STOPPED 9 /* Child has stopped (SIGCHLD only) */#define CLD_CONTINUED 10 /* Stopped child had continued (SIGCHLD only) *//* The following types is used to pass parameters to/from signal handlers */struct siginfo{ uint8_t si_signo; /* Identifies signal */ uint8_t si_code; /* Source: SI_USER, SI_QUEUE, SI_TIMER, SI_ASYNCIO, or SI_MESGQ */ uint8_t si_errno; /* Zero or errno value associated with signal */ union sigval si_value; /* Data passed with signal */#ifdef CONFIG_SCHED_HAVE_PARENT pid_t si_pid; /* Sending task ID */ int si_status; /* Exit value or signal (SIGCHLD only). */#endif};typedef struct siginfo siginfo_t;#define __SIGINFO_T_DEFINED 1
信号action的定义,当信号deliver的时候,Task所采取的行动,其中sigaction中sa_mask位域,表示的是当Handler在执行期间,需要阻塞的信号
/* struct sigaction flag values */#define SA_NOCLDSTOP (1 << 0) /* Do not generate SIGCHILD when * children stop (ignored) */#define SA_SIGINFO (1 << 1) /* Invoke the signal-catching function * with 3 args instead of 1 * (always assumed) */#define SA_NOCLDWAIT (1 << 2) /* If signo=SIGCHLD, exit status of child * processes will be discarded *//* Special values of of sa_handler used by sigaction and sigset. They are all * treated like NULL for now. This is okay for SIG_DFL and SIG_IGN because * in NuttX, the default action for all signals is to ignore them. */#define SIG_ERR ((_sa_handler_t)-1) /* And error occurred */#define SIG_DFL ((_sa_handler_t)0) /* Default is SIG_IGN for all signals */#define SIG_IGN ((_sa_handler_t)0) /* Ignore the signal */#define SIG_HOLD ((_sa_handler_t)1) /* Used only with sigset() *//* Non-standard convenience definition of signal handling function types. * These should be used only internally within the NuttX signal logic. */typedef CODE void (*_sa_handler_t)(int signo);typedef CODE void (*_sa_sigaction_t)(int signo, FAR siginfo_t *siginfo, FAR void *context);/* The following structure defines the action to take for given signal */struct sigaction{ union { _sa_handler_t _sa_handler; _sa_sigaction_t _sa_sigaction; } sa_u; sigset_t sa_mask; int sa_flags; };/* Definitions that adjust the non-standard naming */#define sa_handler sa_u._sa_handler#define sa_sigaction sa_u._sa_sigaction
Kernle部分,定义在include/sched/signal/signal.h中,主要描述了Kernel中是如何实现信号机制的
描述一个信号的action的结构,指针flink将sigactq链接起来管理,系统注册一个信号,底层将用一个sigactq结构体来对应
/* The following defines the sigaction queue entry */struct sigactq{ FAR struct sigactq *flink; /* Forward link */ struct sigaction act; /* Sigaction data */ uint8_t signo; /* Signal associated with action */};typedef struct sigactq sigactq_t;
描述pending信号(未决信号)的结构,其中info中包括信号的详细信息,该信号会通过flink链接管理
/* The following defines the queue structure within each TCB to hold pending * signals received by the task. These are signals that cannot be processed * because: (1) the task is not waiting for them, or (2) the task has no * action associated with the signal. */struct sigpendq{ FAR struct sigpendq *flink; /* Forward link */ siginfo_t info; /* Signal information */ uint8_t type; /* (Used to manage allocations) */};typedef struct sigpendq sigpendq_t;
描述需要被执行的信号节队列点结构,当任务注册了信号并接收到信号后,会分配一个信号队列节点,将该节点挂载到任务tcb->sigpendactionq链表上等待运行信号服务函数。其中action指向信号处理函数,mask用于当信号处理函数运行时阻塞其他信号,info是信号的详细信息
/* The following defines the queue structure within each TCB to hold queued * signal actions that need action by the task */struct sigq_s{ FAR struct sigq_s *flink; /* Forward link */ union { void (*sighandler)(int signo, 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) */};typedef struct sigq_s sigq_t;
上述三种结构,struct sigactq描述信号的action, struct sigpendq描述未决的信号, struct sigq_s描述的是信号与action的对应关系,可以认为是一个纽带,将信号和Action绑定到一起。这几个结构的名字让我懵逼了好久。还有更懵逼的在下边。
基于上述的三个结构体,系统维护了5个全局队列,用于最终信号的处理,信号处理过程中,这三个结构体的节点,将在这5个全局队列中进行流动,有点类似于任务调度中任务队列的意思。
存放action的队列,存放sigactq_t,用于Action资源的分配
/* The g_sigfreeaction data structure is a list of available signal action * structures. */extern sq_queue_t g_sigfreeaction;
存放信号队列节点的队列,存放sigq_t,此时Action和信号已经完成了绑定,用于sigq_t资源的分配。存放信号队列节点的队有两种:用于普通分配的队列和用于中断中分配的队列。
/* The g_sigpendingaction data structure is a list of available pending * signal action structures. */extern sq_queue_t g_sigpendingaction;/* The g_sigpendingirqaction is a list of available pending signal actions * that are reserved for use by interrupt handlers. */extern sq_queue_t g_sigpendingirqaction;
存放未决信号的队列,存放sigpendq_t,用于未决信号资源的分配。同2相似,它也存在两种队列:用于普通分配的队列和用于中断中分配的队列。
/* The g_sigpendingsignal data structure is a list of available pending * signal structures. */extern sq_queue_t g_sigpendingsignal;/* The g_sigpendingirqsignal data structure is a list of available pending * signal structures that are reserved for use by interrupt handlers. */extern sq_queue_t g_sigpendingirqsignal;
那么这三种数据结构以及几个全局队列又是怎么对应到Task数据结构中的呢,先看看Task中与信号相关的位域吧,有两部分:
第一部分:
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时,信号被阻塞。除非该信号是等待的信号,或者Tcb任务取消了对该信号的阻塞,信号才会被deliver。
sigwaitmask:该任务等待的信号集。
sigpendactionq:用于挂载该任务需要服务的信号节点sigq_t,任务开始执行时,sigpendactionq中的节点所代表的信号处理函数将被运行。在信号处理函数运行前,该信号的sigq_t节点将从sigpendactionq队列中转移到sigpostedq队列中。
sigpostedq:用于挂载该任务正在执行信号处理函数的信号节点sigq_t,当信号处理函数执行完毕后,信号节点sigq_t将被从队列中移除,然后被释放。
sigunbinfo:用于记录信号信息
从上可以看出,上述结构中的sigpendactionq会存放sigq_t资源,也就是已经完成了信号和Action绑定后的节点。显然,sigq_t资源会在tcb->sigpendactionq字段指向的队列和g_sigpendingaction/g_sigpendingirqaction之间流动。
第二部分,在struct task_group_s中,如果定义了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... }
上述两个位域含义很清晰,一个用于放置Action,一个用于放置信号。对应到前边的五个全局队列,可以知道:sigactq_t资源在task_group->tg_sigactionq指向的队列和g_sigfreeaction队列中流动;sigpendq_t资源在task_group->tg_sigpendingq指向的队列和g_sigpendingsignal/g_sigpendingirqsignal队列中流动。
注册信号到这里为止,基本上将所有的数据结构及资源捋清了。注册信号就是关联一个Task和某个信号处理函数,当Task接收到信号后,对应信号的信号处理函数被运行。而涉及到这个处理流程的所有资源(上述结构体描述),就是在这些资源队列中进行流转。
来一张图吧
信号机制
通过int sigaction(int signo, FAR const struct sigaction *act, FAR struct sigaction *oact)接口可以查询和设置信号关联的处理方式。在该函数中,完成了以下几个功能:
将act对应的sigaction设置进本task中,并将之前的sigaction以oact的形式传递出来。
根据signo查询task_group->tg_sigactionq中是否有对应的sigactq_t,没有的话从系统g_sigfreeaction链表中分配一个。
根据act对应的sigaction中Handler的处理方式(忽略信号,还是提供处理函数),更新sigactq_t结构,最终将sigactq_t的结构插入到task_group->tg_sigactionq中。
整个过程,就是将sigaction注册进Task中的链表中,还是直接看源代码来得更清晰,代码里有详尽的注释,理解起来比较容易。
/**************************************************************************** * Name: sigaction * * Description: * This function allows the calling process to examine and/or specify the * action to be associated with a specific signal. * * The structure sigaction, used to describe an action to be taken, is * defined to include the following members: * * - sa_u.sa_handler: Pointer to a signal-catching function * - sa_u.sa_sigaction: Alternative form of the signal-catching function * - sa_mask: An additional set of signals to be blocked during execution * of a signal catching function * - sa_flags. Special flags to affect the behavior of a signal. * * If the argument 'act' is not NULL, it points to a structure specifying * the action to be associated with the specified signal. If the argument * 'oact' is not NULL, the action previously associated with the signal * is stored in the location pointed to by the argument 'oact.' * * When a signal is caught by a signal-catching function installed by * sigaction() function, a new signal mask is calculated and installed for * the duration of the signal-catching function. This mask is formed by * taking the union of the current signal mask and the value of the * sa_mask for the signal being delivered and then including the signal * being delivered. If and when the user's signal handler returns, the * original signal mask is restored. * * Once an action is installed for a specific signal, it remains installed * until another action is explicitly requested by another call to sigaction(). * * Parameters: * sig - Signal of interest * act - Location of new handler * oact - Location to store only handler * * Return Value: * 0 (OK), or -1 (ERROR) if the signal number is invalid. * (errno is not set) * * Assumptions: * * POSIX Compatibility: * - There are no default actions so the special value SIG_DFL is treated * like SIG_IGN. * - All sa_flags in struct sigaction of act input are ignored (all * treated like SA_SIGINFO). The one exception is if CONFIG_SCHED_CHILD_STATUS * is defined; then SA_NOCLDWAIT is supported but only for SIGCHLD * ****************************************************************************/int sigaction(int signo, FAR const struct sigaction *act, FAR struct sigaction *oact) { FAR struct tcb_s *rtcb = this_task(); FAR struct task_group_s *group; FAR sigactq_t *sigact; /* Since sigactions can only be installed from the running thread of * execution, no special precautions should be necessary. */ DEBUGASSERT(rtcb != NULL && rtcb->group != NULL); group = rtcb->group; /* Verify the signal number */ if (!GOOD_SIGNO(signo)) { set_errno(EINVAL); return ERROR; } /* Find the signal in the signal action queue */ sigact = sig_findaction(group, signo); /* Return the old sigaction value if so requested */ if (oact) { if (sigact) { COPY_SIGACTION(oact, &sigact->act); } else { /* There isn't an old value */ oact->sa_u._sa_handler = NULL; oact->sa_mask = NULL_SIGNAL_SET; oact->sa_flags = 0; } } /* If the argument act is a null pointer, signal handling is unchanged; * thus, the call can be used to enquire about the current handling of * a given signal. */ if (!act) { return OK; }#if defined(CONFIG_SCHED_HAVE_PARENT) && defined(CONFIG_SCHED_CHILD_STATUS) /* Handle a special case. Retention of child status can be suppressed * if signo == SIGCHLD and sa_flags == SA_NOCLDWAIT. * * POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated * when a child process terminates. In NuttX, a SIGCHLD signal is * generated in this case; but in some other implementations, it may not * be. */ if (signo == SIGCHLD && (act->sa_flags & SA_NOCLDWAIT) != 0) { irqstate_t flags; /* We do require a critical section to muck with the TCB values that * can be modified by the child thread. */ flags = enter_critical_section(); /* Mark that status should be not be retained */ rtcb->group->tg_flags |= GROUP_FLAG_NOCLDWAIT; /* Free all pending exit status */ group_removechildren(rtcb->group); leave_critical_section(flags); }#endif /* Handle the case where no sigaction is supplied (SIG_IGN) */ if (act->sa_u._sa_handler == SIG_IGN) { /* Do we still have a sigaction container from the previous setting? */ if (sigact) { /* Yes.. Remove it from signal action queue */ sq_rem((FAR sq_entry_t *)sigact, &group->tg_sigactionq); /* And deallocate it */ sig_releaseaction(sigact); } } /* A sigaction has been supplied */ else { /* Do we still have a sigaction container from the previous setting? * If so, then re-use for the new signal action. */ if (!sigact) { /* No.. Then we need to allocate one for the new action. */ sigact = sig_allocateaction(); /* An error has occurred if we could not allocate the sigaction */ if (!sigact) { set_errno(ENOMEM); return ERROR; } /* Put the signal number in the queue entry */ sigact->signo = (uint8_t)signo; /* Add the new sigaction to signal action queue */ sq_addlast((FAR sq_entry_t *)sigact, &group->tg_sigactionq); } /* Set the new sigaction */ COPY_SIGACTION(&sigact->act, act); } return OK; }发送信号
发送信号以kill()函数来解释是再合适不过了。
在kill()函数中,根据传进来的PID号,找到对应的Task,并向该Task发送信号,关键代码如下:
int kill(pid_t pid, int signo){ ... /* Keep things stationary through the following */ sched_lock(); /* Create the siginfo structure */ 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); sched_unlock(); ... }
调用到sig_dispatch()接口,完成信号的分发,而在sig_dispatch()接口中,又将调用sig_tcbdispatch()接口,最核心的部分在于sig_tcbdispatch(),事实上上层信号最终的分发都在这个接口中实现。
sig_tcbdispatch()函数,主要完成以下几点功能:
如果分发的信号在目标Task中是是masked,而且Task的状态没有变成等待该信号的话,就将信号添加进pending队列中,也就是task_group->tg_sigpendingq队列中;而如果Task的状态变成了需要等待这个之前mask掉的信号,这时候就调用up_unblock_task()接口,完成任务的切换。
如果分发的信号在目标Task中是unmask,此时需要调用sig_queueaction()接口,将一个sigq_t结构添加进tcb->sigpendactionq队列中。当然,在sig_queueaction()接口中,会去从上文中提到过的全局队列中获取sigq_t结构资源。加入到tcb->sigpendactionq队列后,调用up_schedule_sigaction()接口,该接口主要是更新Task对应的Tcb中的内容,最终调用up_unblock_task()进行任务切换的时候,能去处理信号。
最终信号发送成功,有两件事完成了:1)在目标Task的tcb->sigpendactionq队列中,成功的添加了sigq_t结构,该结构完成了信号和Action的匹配;2)更新了目标Task的tcb->xcp中的内容,更新完这个后,当完成任务切换的时候,Context Restore的时候会将tcb->xcp中的内容恢复到寄存器中,因此也就能跳转到信号处理函数中执行。
关键代码如下:
/**************************************************************************** * Name: sig_tcbdispatch * * Description: * All signals received the task (whatever the source) go through this * function to be processed. This function is responsible for: * * - Determining if the signal is blocked. * - Queuing and dispatching signal actions * - Unblocking tasks that are waiting for signals * - Queuing pending signals. * * This function will deliver the signal to the task associated with * the specified TCB. This function should *not* typically be used * to dispatch signals since it will *not* follow the group signal * deliver algorithms. * * Returned Value: * Returns 0 (OK) on success or a negated errno value on failure. * ****************************************************************************/int sig_tcbdispatch(FAR struct tcb_s *stcb, siginfo_t *info){ ... /************************* MASKED SIGNAL HANDLING ************************/ /* Check if the signal is masked -- if it is, it will be added to the list * of pending signals. */ if (sigismember(&stcb->sigprocmask, info->si_signo)) { /* Check if the task is waiting for this pending signal. If so, then unblock it. * This must be performed in a critical section because signals can be queued * from the interrupt level. */ flags = enter_critical_section(); if (stcb->task_state == TSTATE_WAIT_SIG && sigismember(&stcb->sigwaitmask, info->si_signo)) { memcpy(&stcb->sigunbinfo, info, sizeof(siginfo_t)); stcb->sigwaitmask = NULL_SIGNAL_SET; up_unblock_task(stcb); leave_critical_section(flags); } /* Its not one we are waiting for... Add it to the list of pending * signals. */ else { leave_critical_section(flags); ASSERT(sig_addpendingsignal(stcb, info)); } } /************************ UNMASKED SIGNAL HANDLING ***********************/ else { #ifdef CONFIG_SMP int cpu;#endif /* Queue any sigaction's requested by this task. */ ret = sig_queueaction(stcb, info); /* Deliver of the signal must be performed in a critical section */ flags = enter_critical_section();#ifdef CONFIG_SMP /* If the thread is running on another CPU, then pause that CPU. We can * then setup the for signal delivery on the running thread. When the * CPU is resumed, the signal handler will then execute. */ cpu = sched_cpu_pause(stcb);#endif /* CONFIG_SMP */ /* Then schedule execution of the signal handling action on the * recipient's thread. */ up_schedule_sigaction(stcb, sig_deliver);#ifdef CONFIG_SMP /* Resume the paused CPU (if any) */ if (cpu >= 0) { /* I am not yet sure how to handle a failure here. */ DEBUGVERIFY(up_cpu_resume(cpu)); }#endif /* CONFIG_SMP */ /* Check if the task is waiting for an unmasked signal. If so, then * unblock it. This must be performed in a critical section because * signals can be queued from the interrupt level. */ if (stcb->task_state == TSTATE_WAIT_SIG) { memcpy(&stcb->sigunbinfo, info, sizeof(siginfo_t)); stcb->sigwaitmask = NULL_SIGNAL_SET; up_unblock_task(stcb); } leave_critical_section(flags); /* If the task neither was waiting for the signal nor had a signal * handler attached to the signal, then the default action is * simply to ignore the signal */ /*********************** OTHER SIGNAL HANDLING ***********************/ /* If the task is blocked waiting for a semaphore, then that task must * be unblocked when a signal is received. */... }传递信号
信号最终的deliver,需要理解两个函数,以及一个过程。两个函数指的是up_schedule_sigaction()和up_sigdeliver(),在arch/arm/src/arm下实现,一个过程指的是Context切换的过程,参考我之前的一篇文章:Nuttx Task Schedule
先来看看up_schedule_sigaction()函数,在函数中又分为几种情况:1)发送的信号是给当前任务的,且当前任务不在中断上下文中;2)发送的信号是给当前任务的,且当前任务在中断上下文中;3)发送的信号是给其他任务的。
发送给当前任务,且不在中断中,直接调用sig_deliver()函数,完成信号的deliver。
发送给当前任务,且在中断中,需要先将中断上下文中保存的PC和CPSR值保存到tcb->xcp.saved_pc和tcb->xcp.saved_cpsr中,然后再将up_sigdeliver()函数指针值及新设置的CPSR覆盖中断上下文中的PC和CPSR值,因此当中断返回的时候,中断上下文将恢复到寄存器中,此时便会去执行up_sigdeliver()函数,在up_sigdeliver()中调用sig_deliver()完成信号处理。这时候可能会有疑问,那之前的中断上下文被破坏了,怎么恢复呢?别忘了tcb->xcp.saved_pc和tcb->xcp.saved_cpsr,这个结构保存了最开始的中断上下文,因此在up_sigdeliver()执行中,会去恢复原来的中断上下文。
发送给其他任务,处理的方式与在中断中发送给当前任务的情况类似。
/**************************************************************************** * Name: up_schedule_sigaction * * Description: * This function is called by the OS when one or more * signal handling actions have been queued for execution. * The architecture specific code must configure things so * that the 'igdeliver' callback is executed on the thread * specified by 'tcb' as soon as possible. * * This function may be called from interrupt handling logic. * * This operation should not cause the task to be unblocked * nor should it cause any immediate execution of sigdeliver. * Typically, a few cases need to be considered: * * (1) This function may be called from an interrupt handler * During interrupt processing, all xcptcontext structures * should be valid for all tasks. That structure should * be modified to invoke sigdeliver() either on return * from (this) interrupt or on some subsequent context * switch to the recipient task. * (2) If not in an interrupt handler and the tcb is NOT * the currently executing task, then again just modify * the saved xcptcontext structure for the recipient * task so it will invoke sigdeliver when that task is * later resumed. * (3) If not in an interrupt handler and the tcb IS the * currently executing task -- just call the signal * handler now. * ****************************************************************************/void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver) { irqstate_t flags; sinfo("tcb=0x%p sigdeliver=0x%p\n", tcb, sigdeliver); /* Make sure that interrupts are disabled */ flags = enter_critical_section(); /* Refuse to handle nested signal actions */ if (!tcb->xcp.sigdeliver) { /* First, handle some special cases when the signal is * being delivered to the currently executing task. */ sinfo("rtcb=0x%p CURRENT_REGS=0x%p\n", this_task(), CURRENT_REGS); if (tcb == this_task()) { /* CASE 1: We are not in an interrupt handler and * a task is signalling itself for some reason. */ if (!CURRENT_REGS) { /* In this case just deliver the signal now. */ sigdeliver(tcb); } /* CASE 2: We are in an interrupt handler AND the * interrupted task is the same as the one that * must receive the signal, then we will have to modify * the return state as well as the state in the TCB. * * Hmmm... there looks like a latent bug here: The following * logic would fail in the strange case where we are in an * interrupt handler, the thread is signalling itself, but * a context switch to another task has occurred so that * CURRENT_REGS does not refer to the thread of this_task()! */ else { /* Save the return lr and cpsr and one scratch register * These will be restored by the signal trampoline after * the signals have been delivered. */ tcb->xcp.sigdeliver = sigdeliver; tcb->xcp.saved_pc = CURRENT_REGS[REG_PC]; tcb->xcp.saved_cpsr = CURRENT_REGS[REG_CPSR]; /* Then set up to vector to the trampoline with interrupts * disabled */ CURRENT_REGS[REG_PC] = (uint32_t)up_sigdeliver; CURRENT_REGS[REG_CPSR] = SVC_MODE | PSR_I_BIT | PSR_F_BIT; /* And make sure that the saved context in the TCB * is the same as the interrupt return context. */ up_savestate(tcb->xcp.regs); } } /* Otherwise, we are (1) signaling a task is not running * from an interrupt handler or (2) we are not in an * interrupt handler and the running task is signalling * some non-running task. */ else { /* Save the return lr and cpsr and one scratch register * These will be restored by the signal trampoline after * the signals have been delivered. */ tcb->xcp.sigdeliver = sigdeliver; tcb->xcp.saved_pc = tcb->xcp.regs[REG_PC]; tcb->xcp.saved_cpsr = tcb->xcp.regs[REG_CPSR]; /* Then set up to vector to the trampoline with interrupts * disabled */ tcb->xcp.regs[REG_PC] = (uint32_t)up_sigdeliver; tcb->xcp.regs[REG_CPSR] = SVC_MODE | PSR_I_BIT | PSR_F_BIT; } } leave_critical_section(flags); }
当调用up_schedule_sigaction()接口,更新了tcb->xcp的值后,当遇到任务切换点的时候,比如up_unblock_task(),就有可能去执行信号处理函数了,这个就是up_sigdeliver()函数的作用了。
up_sigdeliver()函数主要完成以下几个功能:
在栈上边regs中将现场保存好,并且将之前tcb->xcp.saved_pc和tcb->xcp.saved_cpsr中的值恢复到regs栈上边对应位置。
恢复中断状态
调用信号处理Handler进行处理
调用up_fullcontextrestore(regs),进行任务的恢复。
可以看出,这个过程很像中断处理的过程,因此信号的处理机制,有的地方称为软中断处理机制
还是放上代码吧:
/**************************************************************************** * Name: up_sigdeliver * * Description: * This is the a signal handling trampoline. When a signal action was * posted. The task context was mucked with and forced to branch to this * location with interrupts disabled. * ****************************************************************************/void up_sigdeliver(void) { struct tcb_s *rtcb = this_task(); uint32_t regs[XCPTCONTEXT_REGS]; sig_deliver_t sigdeliver; /* Save the errno. This must be preserved throughout the signal handling * so that the user code final gets the correct errno value (probably * EINTR). */ int saved_errno = rtcb->pterrno; board_autoled_on(LED_SIGNAL); sinfo("rtcb=%p sigdeliver=%p sigpendactionq.head=%p\n", rtcb, rtcb->xcp.sigdeliver, rtcb->sigpendactionq.head); ASSERT(rtcb->xcp.sigdeliver != NULL); /* Save the real return state on the stack. */ up_copyfullstate(regs, rtcb->xcp.regs); regs[REG_PC] = rtcb->xcp.saved_pc; regs[REG_CPSR] = rtcb->xcp.saved_cpsr; /* Get a local copy of the sigdeliver function pointer. we do this so that * we can nullify the sigdeliver function pointer in the TCB and accept * more signal deliveries while processing the current pending signals. */ sigdeliver = rtcb->xcp.sigdeliver; rtcb->xcp.sigdeliver = NULL; /* Then restore the task interrupt state */ up_irq_restore(regs[REG_CPSR]); /* Deliver the signals */ sigdeliver(rtcb); /* Output any debug messages BEFORE restoring errno (because they may * alter errno), then disable interrupts again and restore the original * errno that is needed by the user logic (it is probably EINTR). */ sinfo("Resuming\n"); (void)up_irq_save(); rtcb->pterrno = saved_errno; /* Then restore the correct state for this thread of execution. */ board_autoled_off(LED_SIGNAL); up_fullcontextrestore(regs); }
上面讨论的两个函数,都会最终调用到sig_deliver()函数,完成最终实际上的Handler处理,sig_deliver()函数处理那些被链接到tcb->sigpendactionq中的sigq_t,将sigq_t结构转移到tcb->sigpostedq队列中,紧接着执行信号处理函数。处理完当前的信号后,紧接着调用sig_unmaskpendingsignal()函数,查询tcb_group->tg_sigpendingq中是否还有pending的信号需要去处理,并调用sig_tcbdispatch()继续进行分发。最终处理完本次任务后,将sigq_t结构体从任务tcb->sigpostedq中移除,并释放该结构。
代码如下:
/**************************************************************************** * Name: sig_deliver * * Description: * This function is called on the thread of execution of the signal * receiving task. It processes all queued signals then returns. * ****************************************************************************/void sig_deliver(FAR struct tcb_s *stcb) { FAR sigq_t *sigq; FAR sigq_t *next; sigset_t savesigprocmask; irqstate_t flags; int saved_errno; sched_lock(); /* Save the thread errno. When we finished dispatching the * signal actions and resume the task, the errno value must * be unchanged by the operation of the signal handling. In * particular, the EINTR indication that says that the task * was reawakened by a signal must be retained. */ saved_errno = stcb->pterrno; for (sigq = (FAR sigq_t *)stcb->sigpendactionq.head; (sigq); sigq = next) { next = sigq->flink; sinfo("Sending signal sigq=0x%x\n", sigq); /* Remove the signal structure from the sigpendactionq and place it * in the sigpostedq. NOTE: Since signals are processed one at a * time, there should never be more than one signal in the sigpostedq */ flags = enter_critical_section(); sq_rem((FAR sq_entry_t *)sigq, &(stcb->sigpendactionq)); sq_addlast((FAR sq_entry_t *)sigq, &(stcb->sigpostedq)); leave_critical_section(flags); /* Call the signal handler (unless the signal was cancelled) * * Save a copy of the old sigprocmask and install the new * (temporary) sigprocmask. The new sigprocmask is the union * of the current sigprocmask and the sa_mask for the signal being * delivered plus the signal being delivered. */ savesigprocmask = stcb->sigprocmask; stcb->sigprocmask = savesigprocmask | sigq->mask | SIGNO2SET(sigq->info.si_signo); /* Deliver the signal. In the kernel build this has to be handled * differently if we are dispatching to a signal handler in a user- * space task or thread; we have to switch to user-mode before * calling the task. */#if defined(CONFIG_BUILD_PROTECTED) || defined(CONFIG_BUILD_KERNEL) if ((stcb->flags & TCB_FLAG_TTYPE_MASK) != TCB_FLAG_TTYPE_KERNEL) { /* The sigq_t pointed to by sigq resides in kernel space. So we * cannot pass a reference to sigq->info to the user application. * Instead, we will copy the siginfo_t structure onto the stack. * We are currently executing on the stack of the user thread * (albeit temporarily in kernel mode), so the copy of the * siginfo_t structure will be accessible by the user thread. */ siginfo_t info; memcpy(&info, &sigq->info, sizeof(siginfo_t)); up_signal_dispatch(sigq->action.sighandler, sigq->info.si_signo, &info, NULL); } else#endif { /* The kernel thread signal handler is much simpler. */ (*sigq->action.sighandler)(sigq->info.si_signo, &sigq->info, NULL); } /* Restore the original sigprocmask */ stcb->sigprocmask = savesigprocmask; /* Now, handle the (rare?) case where (a) a blocked signal was * received while the signal handling executed but (b) restoring the * original sigprocmask will unblock the signal. */ sig_unmaskpendingsignal(); /* Remove the signal from the sigpostedq */ flags = enter_critical_section(); sq_rem((FAR sq_entry_t *)sigq, &(stcb->sigpostedq)); leave_critical_section(flags); /* Then deallocate it */ sig_releasependingsigaction(sigq); } stcb->pterrno = saved_errno; sched_unlock(); }
作者:Loyen