11.现代版本的信号处理与发送
经典版本的信号处理与发送:signal/kill(raise)
现代版本的信号处理与发送:sigaction/sigqueue
int sigaction(int signum, const struct sigaction* sigact, struct sigaction* oldact);
成功返回0,失败返回-1。
signum - 信号编号
sigact - 信号行为
oldact - 输出原来的信号行为,可置NULL
当signum信号被递送时,按sigact结构所描述的行为响应之。若oldact参数非NULL,则通过该参数输出原来的响应行为。
struct sigaction {
void (*sa_handler)(int signum); // 经典版本的信号处理函数指针
void (*sa_sigaction)(int signum, siginfo_t* si, void* reserved); // 现代版本的信号处理函数指针
sigset_t sa_mask; // 信号处理期间的附加掩码集
int sa_flags; // 信号处理标志
void (*sa_restorer)(void); // 预留项,目前置NULL
};
代码实例:
void sigint1(int signum){
...
};
struct sidaction sa ={};
sa.handler = sigint1;
sigaddset(&sa.sa_mask,SIGQUIT);
sa.sa_flags = SA_NOMASK;
sigaction(SIGINT ,&sa ,NULL);
现代版本的信号处理函数:
信号编号 信号信息 保留未用
| | |
void sigint(int signum, siginfo_t* si, void* reserved) {
...
}
typedef struct siginfo {
// 发送信号进程的PID \
pid_t si_pid; |
// 信号附加数据 | 18个字段
sigval_t si_value; |
... /
} siginfo_t;
typedef union sigval {
int sival_int; // 用整型作为信号附加数据
void* sival_ptr; // 用任意类型的指针作为信号附加数据
} sigval_t;
1)增减信号掩码
缺省情况下,在信号处理函数的执行过程中,会自动屏蔽这个正在被处理的信号,而对于其它信号则不会屏蔽。通过sa_mask字段可以人为指定,在信号处理函数执行期间,除正在被处理的这个信号以外,还想屏蔽哪些信号,并在信号处理函数返回后,自动解除对它们的屏蔽。另一方面,还可以通过为sa_flags字段设置SA_NOMASK/SA_NODEFER标志位,告诉系统内核在信号处理函数执行期间,不要屏蔽这个正在被处理的信号。
2)选择信号处理函数的风格
如果sa_flags字段中没有SA_SIGINFO标志位,则sa_handler字段有效,即使用经典版本的信号处理函数。相反,如果sa_flags字段包含SA_SIGINFO标志位,则sa_sigaction字段有效,即使用现代版本的信号处理函数。
3)一次性信号处理
如果sa_flags字段中包含SA_ONESHOT/SA_RESETHAND标志位,那么对所捕获信号的处理就是一次性的,即在执行完一次信号处理函数后,即恢复为按默认方式处理。
代码:sigact.c
/* sigact.c */
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigint1(int signum) {
printf("收到%d信号!睡眠中...\n", signum);
sleep(5);
printf("睡醒了。\n");
}
void sigint2(int signum, siginfo_t* si, void* pv) {
printf("收到发自%d进程的%d信号!\n", si->si_pid, signum);
}
int main(void) {
struct sigaction sa = {};
/*
printf("经典版本信号处理函数执行期间只屏蔽SIGINT信号,不屏蔽SIGQUIT信号。\n");
sa.sa_handler = sigint1;
*//*
printf("经典版本信号处理函数执行期间既屏蔽SIGINT信号,也屏蔽SIGQUIT信号。\n");
sa.sa_handler = sigint1;
sigaddset(&sa.sa_mask, SIGQUIT);
*//*
printf("经典版本信号处理函数执行期间不屏蔽SIGINT信号,也不屏蔽SIGQUIT信号。\n");
sa.sa_handler = sigint1;
sa.sa_flags = SA_NOMASK;
*//*
printf("经典版本信号处理函数执行期间不屏蔽SIGINT信号,只屏蔽SIGQUIT信号。\n");
sa.sa_handler = sigint1;
sigaddset(&sa.sa_mask, SIGQUIT);
sa.sa_flags = SA_NOMASK;
*//*
printf("现代版本信号处理函数。\n");
sa.sa_sigaction = sigint2;
sa.sa_flags = SA_SIGINFO;
*/
printf("一次性信号处理。\n");
sa.sa_handler = sigint1;
sa.sa_flags = SA_ONESHOT;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return -1;
}
for (;;) pause();
return 0;
}
4)系统调用中断重启
诸如pause、sleep、usleep等系统调用的阻塞过程会被信号打断,即在针对某个信号的处理函数返回后,这些系统调用也会从阻塞中返回。如果不希望这类系统调用被所捕获的信号打断,即在针对该信号的处理函数返回后,能够自动重启阻塞过程,而不要返回,可以为sa_flags字段添加SA_RESTART标志位。
int sigqueue(pid_t pid, int signum,const sigval_t value);
成功返回0,失败返回-1。
pid - 接收信号进程的PID
signum - 信号编号
value - 信号附加数据
代码:sigque.c
/* sigque.c */
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void siginfo(int signum, siginfo_t* si, void* pv) {
printf("%d进程:收到来自%d进程的%d信号(附加值=%d)!\n", getpid(), si->si_pid, signum, si->si_value.sival_int);
usleep(100);
}
int main(void) {
int signum = /*50*/SIGINT;
struct sigaction sa = {};
sa.sa_sigaction = siginfo;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
if (sigaction(signum, &sa, NULL) == -1) {
perror("sigaction");
return -1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}
if (pid == 0) {
pid_t ppid = getppid();
for (int i = 0; i < 10; ++i) {
sigval_t sv;
sv.sival_int = i;
printf("%d进程:向%d进程发送%d信号(附加值=%d)...\n", getpid(), ppid, signum, sv.sival_int);
if (sigqueue(ppid, signum, sv) == -1) {
perror("sigqueue");
return -1;
}
}
printf("%d进程:我即将退出。\n", getpid());
return 0;
}
if ((pid = wait(NULL)) == -1) {
perror("wait");
return -1;
}
printf("%d进程:%d进程已退出。\n", getpid(), pid);
return 0;
}
利用信号附加数据实现简单的进程间通信。
12.定时器
执行时间=用户时间+内核时间+睡眠时间
| | | |
直观感受 消耗在用户 消耗在内核 消耗在等待I/O、睡
墙钟时间 态的时间 态的时间 眠等不被调度的时间
| | |
真实计 虚拟计 /
时器 时器 /
\_______/
|
实用计时器
利用真实计时器定时,到期信号:SIGALRM(14)
利用虚拟计时器定时,到期信号:SIGVTALRM(26)
利用实用计时器定时,到期信号:SIGPROF(27)
基于每钟计时器的定时都有两个参数:
初始间隔 - 从开始定时到发出第一个到期信号的时间间隔。
重复间隔 - 每相邻两个到期信号的时间间隔。
|---------------+-------+-------+--->
|<-初始间隔->|<重复>|
间隔
设置、开启和关闭定时器:
int setitimer(
int which,
const struct itimerval* new_value,
struct itimerval* old_value);
成功返回0,失败返回-1。
which - 指定设置哪个定时器,可取以下值:
ITIMER_REAL: 基于真实计时器的定时
ITIMER_VIRTUAL: 基于虚拟计时器的定时
ITIMER_PROF: 基于实用计时器的定时
new_value - 新设置值
old_value - 输出原设置值,可置NULL
struct itimerval {
struct timeval it_interval; // 重复间隔,取0表示只发一个信号,不重复,没有周期性
struct timeval it_value; // 初始间隔,取0表示停止定时器,不再发信号
};
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒
};
例如:5秒以后开始发SIGALRM(14)信号,以后每隔3毫秒再发一次该信号。
struct itimerval it;
it.it_value.tv_sec = 5;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 3000;
setitimer(ITIMER_REAL, &it, NULL); // 开
...
it.it_value.tv_sec = 0;
setitimer(ITIMER_REAL, &it, NULL); // 关
如果希望立即启动定时器,初始间隔至少是1微秒。
获取当前定时器设置
int getitimer(int which, struct itimerval* curr_value);
成功返回0,失败返回-1。
which - 指定设置哪个定时器,可取以下值:
ITIMER_REAL: 基于真实计时器的定时
ITIMER_VIRTUAL: 基于虚拟计时器的定时
ITIMER_PROF: 基于实用计时器的定时
curr_value - 当前设置值
代码:timer.c
/* timer.c */
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
int h = 0, m = 0, s = 0, ms = 0;
void sigalrm(int signum) {
printf("\r%02d:%02d:%02d.%03d", h, m, s, ms);
if (++ms == 1000) {
ms = 0;
if (++s == 60) {
s = 0;
if (++m == 60) {
m = 0;
++h;
}
}
}
}
void sigint(int signum) {
static int run = 1;
struct itimerval it = {};
if (!run) {
printf("\n");
h = m = s = ms = 0;
it.it_value.tv_usec = 1;
it.it_interval.tv_usec = 1000;
}
setitimer(ITIMER_REAL, &it, NULL);
run ^= 1;
}
int main(void) {
setbuf(stdout, NULL);
if (signal(SIGALRM, sigalrm) == SIG_ERR) {
perror("signal");
return -1;
}
if (signal(SIGINT, sigint) == SIG_ERR) {
perror("sigint");
return -1;
}
struct itimerval it = {{0, 1000}, {5, 0}};
if (setitimer(ITIMER_REAL, &it, NULL) == -1){
perror("setitimer");
return -1;
}
if (getitimer(ITIMER_REAL, &it) == -1) {
perror("getitimer");
return -1;
}
printf("初始间隔:%ld秒%ld微秒\n", it.it_value.tv_sec, it.it_value.tv_usec);
printf("重复间隔:%ld秒%ld微秒\n", it.it_interval.tv_sec, it.it_interval.tv_usec);
for (;;) pause();
return 0;
}