信号发送
信号事件的产生有两种来源:硬件来源(比如我们按下了键盘上的某个键或者其他硬件故障)、软件来源,包括非法运算操作,调用发送信号函数等。本节根据不同的信号来源来介绍信号发送函数的使用。
信号产生源
信号产生源 | 说明 |
---|---|
SI_KILL/SI_USER | 使用 kill 函数发送的信号 |
SI_QUEUE | 使用 sigqueue 函数发送的信号 |
SI_TIMER | POSIX 定时器发送的信号 |
SI_ASYNCIO | 异步 I/O 系统完成发送的信号 |
SI_MESGQ | 接收到一条消息产生的信号 |
SI_KERNEL | SylixOS 内核内部使用 |
非排队信号
SylixOS 可以通过下面函数发送非排队信号,这意味着,如果发送的信号在线程的信号屏蔽字中(信号被屏蔽了),此时该信号被发送了多次,那么当此信号被取消屏蔽时,将只被递送一次。
#include <signal.h>
int raise(int iSigNo);
int kill(LW_HANDLE ulId, int iSigNo);
函数 raise 原型分析:
- 此函数成功时返回 0,失败时返回-1 并设置错误号。
- 参数 iSigNo 是信号值。
函数 kill 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 ulId 是线程句柄。
- 参数 iSigNo 是信号值。
raise 函数允许线程向自己发送信号;kill 函数将信号发送给指定的线程,如果是进程,则会将信号发送给主线程。
pthread_kill 函数是 POSIX 线程中的发送信号函数,在 SylixOS 中是通过调用 kill 函数实现。
#include <pthread.h>
int pthread_kill(pthread_t thread, int signo);
函数 pthread_kill 原型分析:
- 此函数成功时返回 0,失败时返回相应的错误号。
- 参数 thread 是线程句柄。
- 参数 signo 是信号值。
需要注意的是,thread 参数句柄由 pthread_create 函数返回。这类信号的产生源类型为:SI_KILL。
队列信号
前面我们介绍过,SylixOS 支持 POSIX 的实时扩展部分,因此 SylixOS 信号机制中实现了信号排队。
#include <signal.h>
int sigqueue(LW_HANDLE ulId, int iSigNo, const union sigval sigvalue);
函数 sigqueue 原型分析:
- 此函数成功时返回 0,失败时返回-1 并设置错误号。
- 参数 ulId 是线程句柄。
- 参数 iSigNo 是信号值。
- 参数 sigvalue 是信号传递的参数。
调用 sigqueue 函数将主动发一次队列类型信号,这意味着,多次发送同一个信号,信号将被排队。如果信号被屏蔽,在发送完多次,信号解除屏蔽后,发送了多少次,将被递送多少次。
通常一个信号只包含一个数字信息:信号本身。POSIX 的实时扩展部分除了对信号排队以外,还允许应用程序在递送信号时传递更多的信息。这些信息嵌入在 union sigval 中。除了系统提供的信息,应用程序还可以向信号处理程序传递整数或者指向包含更多信息的缓冲区指针。
sigqueue 函数的第三个参数就是应用程序传递给信号处理程序的信息,union sigval 信息如下:
typedef union sigval {
INT sival_int;
PVOID sival_ptr;
} sigval_t;
- sival_int:将传递一个整形值。
- sival_ptr:指向一个包含更多信息的缓冲区结构。
这是一个联合体类型,也就是说,应用程序一次只能传递这两种类型中的其中一种。
使用排队信号必须做以下几个操作:
- 使用 sigaction 函数安装信号处理程序时指定 SA_SIGINFO 标志。如果没有给出这个标志,则在 SylixOS 中,应用程序信息将不会被传递到信号处理函数中。
- 在 sigaction 结构的 sa_sigaction 成员中(而不是通常的 sa_handler)提供信号处理程序。如果应用程序使用 sa_handler 成员,则不能获得 sigqueue 函数传递的额外信息。
sigqueue 函数除了可以使用参数 sigvalue 向信号处理程序传递整数和指针值外,其他功能和 kill 函数类似。信号不能被无限排队,在 POSIX 定义中,_POSIX_SIGQUEUE_MAX 限制了信号排队最大值,到达相应的限制后,sigqueue 就会失败,并设置相应的 errno 值。这类信号的产生源类型为:SI_QUEUE。
下面实例展示了 sigqueue 函数的使用方法。该程序定义了一个存有更多信息的结构体 cls,在信号安装时,指定 sa_flags 标志为 SA_SIGINFO 并且使用 sa_sigaction 成员,然后调用 sigqueue 函数发送信号 SIGUSR1 并且附加我们的额外信息在 sival_ptr 中。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
typedef struct {
int num;
char name[64];
} cls;
void sig_handler (int signum, struct siginfo *info, void *arg)
{
cls *name = (cls *)info->si_value.sival_ptr;
fprintf(stdout, "num: %d, name: %s\n", name->num, name->name);
}
int main (int argc, char *argv[])
{
struct sigaction act;
int error;
cls name = {1, "sylixos"};
union sigval val;
act.sa_sigaction = sig_handler;
act.sa_flags = SA_SIGINFO;
sigemptyset(&act.sa_mask);
error = sigaction(SIGUSR1, &act, NULL);
if (error != 0) {
fprintf(stderr, "sigaction install signal SIGUSR1 failed.\n");
return (-1);
}
val.sival_ptr = (void *)&name;
error = sigqueue(getpid(), SIGUSR1, val);
if (error != 0) {
fprintf(stderr, "sigqueue send signal failed.\n");
return (-1);
}
sleep(5);
return (0);
}
在 SylixOS Shell 下运行程序,从运行结果可以看出,信号处理函数成功的收到了我们所传递的信息。
# ./Function_Sigqueue
num: 1, name: sylixos
定时器信号
在 SylixOS 中,可以通过定时器使得工作在指定的时间点去处理,定时器提供了一种延迟处理工作的方法。
进程定时器信号
SylixOS 为进程提供了三种类型的定时器,每一种定时器以不同的时间域递减其值,当定时器超时时,相应的信号被发送到进程,之后定时器重载。
如下表是 SylixOS 进程定时器支持的类型,可在 <sys/time.h> 中发现其定义。
定时器类型 | 描述 |
---|---|
ITIMER_REAL | 以系统的真实时间来减,发送 SIGALRM 信号 |
ITIMER_VIRTUAL | 以该进程在用户态时间来减,发送 SIGVTALRM 信号 |
ITIMER_PROF | 以该进程在内核态和用户态时间来减,发送 SIGPROF 信号 |
ITIMER_REAL 类型的定时器,在每一次的系统 TICK,都会更新系统所有进程的 ITIMER_REAL 类型时间,如果发生超时,则发送 SIGALRM 信号;ITIMER_VIRTUAL 类型的定时器,只更新当前进程在用户态的运行时间,如果发生超时,则发送 SIGVTALRM 信号;ITIMER_PROF 类型的定时器,更新当前进程在用户态和内核态的运行时间,如果发生超时,则发送 SIGPROF 信号。这类信号的产生源类型为:SI_TIMER。
SylixOS 提供了以下函数来对三种定时器进行操作:
#include <sys/time.h>
int setitimer(int iWhich,
const struct itimerval *pitValue,
struct itimerval *pitOld);
int getitimer(int iWhich, struct itimerval *pitValue);
函数 setitimer 原型分析:
- 此函数成功时返回 0,失败时返回-1 并设置错误号。
- 参数 iWhich 是定时器类型,如上表所示类型。
- 参数 pitValue 是定时器参数指针。
- 输出参数 pitOld 保存之前定时器参数指针。
函数 getitimer 原型分析:
- 此函数成功时返回 0,失败时返回-1 并设置错误号。
- 参数 iWhich 是定时器类型如上表所示类型。
- 输出参数 pitValue 获得当前定时器信息指针。
setitimer 函数可在进程上下文中设置一个定时器,在指定的时间超时后,能够产生相应的信号,getitimer 函数能够获得指定定时器的定时信息。setitimer 函数通过 itimerval 结构设置定时器到期时间及重载时间,这种定时器的时间精度是微妙。
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
下面实例展示了进程定时器的使用方法。程序安装 SIGARLM、SIGVTALRM、SIGPROF 三种信号,三种信号产生间隔都是 4 秒,程序最后使用循环的方式等待信号。
需要注意的是,这里不能使用 pause 函数或者睡眠函数(例如:sleep, usleep 等)使进程处于挂起状态,因为 ITIMER_VIRTUAL 和 ITIMER_PROF 类型定时器只有在进程运行时才会递减其值。
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
void sig_handler (int signum)
{
switch (signum) {
case SIGALRM:
fprintf(stdout, "catch SIGALRM sinal.\n");
break;
case SIGVTALRM:
fprintf(stdout, "catch SIGVTALRM sinal.\n");
break;
case SIGPROF:
fprintf(stdout, "catch SIGPROF sinal.\n");
break;
default:
fprintf(stdout, "catch other signal.\n");
}
}
int main (int argc, char *argv[])
{
struct itimerval newtime0, newtime1, newtime2, oldtime;
struct timeval value;
signal(SIGALRM, sig_handler);
signal(SIGVTALRM, sig_handler);
signal(SIGPROF, sig_handler);
value.tv_sec = 4;
value.tv_usec = 0;
newtime0.it_interval = value;
newtime0.it_value = value;
newtime1.it_interval = value;
newtime2.it_interval = value;
value.tv_sec = 2;
value.tv_usec = 0;
newtime1.it_value = value;
value.tv_sec = 3;
value.tv_usec = 0;
newtime2.it_value = value;
setitimer(ITIMER_REAL, &newtime0, &oldtime);
setitimer(ITIMER_VIRTUAL, &newtime1, &oldtime);
setitimer(ITIMER_PROF, &newtime2, &oldtime);
while (1) {
; /* 不能调用pause函数或睡眠函数 */
}
return (0);
}
在 SylixOS Shell 下运行程序,从运行结果可看成定时器的运行效果。
# ./Process_Timer
catch SIGVTALRM sinal.
catch SIGPROF sinal.
catch SIGALRM sinal.
catch SIGVTALRM sinal.
catch SIGPROF sinal.
catch SIGALRM sinal.
……
SylixOS 同时也提供了更加简单的定时器函数——以秒或微妙为单位的闹钟函数。
#include <unistd.h>
unsigned int alarm(UINT uiSeconds);
useconds_t ualarm(useconds_t usec, useconds_t usecInterval);
函数 alarm 原型分析:
- 此函数成功时返回前次闹钟剩余秒数,失败时返回 0 并设置错误号。
- 参数 uiSeconds 指定多少秒后产生闹钟信号。
函数 ualarm 原型分析:
- 此函数成功时返回前次闹钟剩余秒数,失败时返回 0 并设置错误号。
- 参数 usec 是初始微妙数。
- 参数 usecInterval 是间隔微妙数。
使用 alarm 或 ualarm 函数设置一个定时器,在将来的某个时刻该定时器会超时,并产生 SIGALRM 信号。如果不捕捉此信号,则其默认动作是终止进程。
每个进程只能有一个闹钟时间。如果在调用 alarm 或 ualarm 时,之前已为该进程注册了闹钟且还没有超时,则该闹钟时间的剩余值将作为本次调用的值返回,之前注册的闹钟时间则被新值代替。
虽然 SIGALRM 的默认动作是终止进程,但是大多数使用闹钟的进程都会捕捉此信号。如果此时进程要终止,则在终止之前它可以执行所需的清理操作。如果要捕捉 SIGALRM 信号,需要在调用 alarm 或 ualarm 之前注册我们的信号函数,下面通过实例来看闹钟函数的使用。
#include <stdio.h>
#include <signal.h>
#include < unistd.h>
void alarm_handler (int signum)
{
fprintf(stdout, "alarm signal.\n");
}
int main (int argc, char *argv[])
{
if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
fprintf(stderr, "install signal handler failed.\n");
return -1;
}
alarm(1);
pause();
return (0);
}
在 SylixOS Shell 下运行程序:
# ./Alarm_Clock_Function
Alarm signal.
POSIX 定时器信号
在 SylixOS 中可以通过调用 timer_create 创建特定的定时器,这种定时器和进程定时器不同的是,它可以将信号发送给任一指定的线程或者指定的一个函数,而不只是本进程的主线程。
#include <sys/time.h>
int timer_create(clockid_t clockid, struct sigevent *sigeventT, timer_t *ptimer);
int timer_delete(timer_t timer);
int timer_gettime(timer_t timer, struct itimerspec *ptvTime);
int timer_getoverrun(timer_t timer);
int timer_settime(timer_t timer, int iFlag,
const struct itimerspec *ptvNew,
struct itimerspec *ptvOld);
函数 timer_create 原型分析:
- 此函数成功时返回 0,失败返回-1 并设置错误号。
- 参数 clockid 是时钟源类型,如下表所示。
- 参数 sigeventT 是信号事件。
- 输出参数 ptimer 返回定时器句柄。
函数 timer_delete 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 timer 是定时器句柄。
函数 timer_gettime 原型分析:
- 此函数成功返回 0,错误返回-1 并设置错误号。
- 输出参数 ptvTime 返回定时器的时间参数。
函数 timer_getoverrun 原型分析:
- 此函数成功返回 timer 超时次数,失败返回-1 并设置错误号。
- 参数 timer 是定时器句柄。
函数 timer_settime 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 timer 是定时器句柄。
- 参数 iFlag 是定时器标志。
- 参数 ptvNew 是定时器新的时间信息。
- 输出参数 ptvOld 保存之前的定时器时间信息。
调用 timer_create 函数可以创建一个 POSIX 定时器,创建的定时器我们需要指定时钟源类型,如果 sigeventT 为 NULL,则设置默认的信号事件(超时发送 SIGALRM 信号),如果 sigeventT 不为 NULL,则设置应用程序指定的信号事件,应用程序需要指定 ptimer 缓冲区地址来存放创建的定时器句柄,如果 ptimer 为 NULL,则返回-1 并设置 errno 为 EINVAL。下面是时钟源类型的定义:
时钟源名称 | 说明 |
---|---|
CLOCK_REALTIME | 代表实际的物理时间 |
CLOCK_MONOTONIC | 单调增长时间 |
定时器的属性和行为都包含在了结构体 struct sigevent 中。
typedef struct sigevent {
INT sigev_signo;
union sigval sigev_value;
INT sigev_notify;
void (*sigev_notify_function)(union sigval);
#if LW_CFG_POSIX_EN > 0
pthread_attr_t *sigev_notify_attributes;
#else
PVOID sigev_notify_attributes;
#endif /* LW_CFG_POSIX_EN > 0 */
LW_OBJECT_HANDLE sigev_notify_thread_id; /* Linux-specific */
/* equ pthread_t */
……
} sigevent_t;
成员 sigev_signo 是我们想要定时器超时发送的信号,sigev_notify 信号通知类型如下表所示。sigev_notify_function 是需要通知的函数,sigev_notify_thread_id 是需要通知的线程 ID,应用程序需配合 sigev_noify 类型选择不同的信号通知方式。
宏名 | 说明 |
---|---|
SIGEV_NONE | 不做信号通知 |
SIGEV_SIGNAL | 发送信号通知 |
SIGEV_THREAD | 通知 sigev_notify_function 函数,SylixOS 目前不支持该通知方式 |
SIGEV_THREAD_ID | 通知 sigev_notify_thread_id 线程,应用提供的线程句柄,SylixOS 中该标志支持两种组合: 单独使用 SIGEV_THREAD_ID 复合使用 SIGEV_THREAD_ID |
调用 timer_delete 函数将删除一个已经创建的 POSIX 定时器,如果删除的定时器不存在,则返回-1 并设置 errno 为 EINVAL,调用 timer_gettime 函数将返回定时器的时间信息,需要注意的是,如果定时器存在但没有运行,则返回成功且时间值为 0,调用 timer_getoverrun 函数获得定时器超时的次数,在 SylixOS 中,同样可通过 siginfo_t 结构体的 si_overrun 成员获得此值(例如调用 sigwaitinfo 函数),POSIX 规定如果返回的超时值大于 DELAYTIMER_MAX,则返回 DELAYTIMER_MAX 值,其实,SylixOS 提供了下面函数来返回大于 DELAYTIMER_MAX 的值。需要注意的是,此函数的使用存在着限制,这里并没有指定函数所属的头文件,也就是说应用程序不可以直接使用,实际上,此函数是 SylixOS 为 timerfd(见“标准 I/O 设备”章节)提供的扩展。
INT timer_getoverrun_64(timer_t timer, UINT64 *pu64Overruns, BOOL bClear);
调用 timer_create 函数创建的定时器并未启动,调用 timer_settime 函数则将创建的定时器关联到一个到期时间并启动定时器。定时器使用 itimerspec 结构设置到期时间值(it_value)和重载时间值(it_interval),如果重载时间值为 0 且到期时间值不为 0,则定时器不会自动重载,一旦到期定时器自动停止。如果到期时间值和重载时间值同时为 0,则定时器停止。POSIX 定时器提供了纳秒级的时间精度。
struct itimerspec {
struct timespec it_interval; /* 定时器重载值 */
struct timespec it_value; /* 到下一次到期为止剩余时间 */
};
定时器标志 iFlag 标示了定时器的时间类型,POSIX 如下定义绝对时钟,绝对时钟时间是指大于当前时间点的某一个时间点,非绝对时钟又称相对时钟,这种时钟的时间类型 POSIX 没有规定具体值,也就是说任何一个非 0x1 值 SylixOS 都认为是一个相对时钟时间,相对时钟时间是指一个时间长度。
#include <sys/time.h>
#define TIMER_ABSTIME 0x1 /* 绝对时钟 */
下面这段程序展示了线程定时器的使用方法:
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <pthread.h>
void *timer_thread (void *arg)
{
sigset_t sigset;
int sig;
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigprocmask(SIG_BLOCK, &sigset, NULL);
for (;;) {
sigwait(&sigset, &sig);
if (sig == SIGUSR1) {
fprintf(stdout, "Find signal SIGUSR1.\n");
}
}
return NULL;
}
int main (int argc, char *argv[])
{
struct sigevent sigev;
timer_t timerid;
pthread_t tid;
struct timespec interval;
struct itimerspec tspec;
int ret;
ret = pthread_create(&tid, NULL, timer_thread, NULL);
if (ret) {
fprintf(stderr, "pthread create failed.\n");
return -1;
}
sigev.sigev_notify = SIGEV_THREAD_ID;
sigev.sigev_signo = SIGUSR1;
sigev.sigev_notify_thread_id = tid;
ret = timer_create(CLOCK_MONOTONIC, &sigev, &timerid);
if (ret) {
fprintf(stderr, "timer create failed.\n");
return -1;
}
/*
* 我们以秒为单位
*/
interval.tv_sec = 2;
interval.tv_nsec = 0;
tspec.it_interval = interval;
tspec.it_value = interval;
ret = timer_settime(timerid, 0, &tspec, LW_NULL);
if (ret) {
fprintf(stderr, "timer settime failed.\n");
return -1;
}
pthread_join(tid, NULL);
return 0;
}
在 SylixOS Shell 下运行这段程序:
#./Timer_Usage
Find signal SIGUSR1.
Find signal SIGUSR1.
......
程序实现了每隔 2 秒定时器给用户线程发送一个 SIGUSR1 信号,线程中通过调用 sigwait 来阻塞等待 SIGUSR1 信号的到来。这里定时器时钟源使用了 CLOCK_MONOTONIC,信号通知参数 sigev_notify 使用了选项 SIGEV_THREAD_ID 和 SIGEV_SIGNAL。