Linux信号(下)

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
信号处理是Linux系统中的一个重要概念,用于处理进程间通信和异常情况。信号是由操作系统或其他进程发送给进程的通知,用于通知进程发生了某个事件或异常情况。信号可以被进程捕获和处理,也可以被忽略或使用默认处理方式。 在Linux中,信号可以由多种情况触发,比如按下CTRL+C键产生的SIGINT信号,非法内存访问产生的信号,硬件故障产生的信号,以及环境切换等。进程可以通过调用signal函数来注册信号处理函数,以捕获和处理特定的信号。 signal函数的原型如下: ```c typedef void (*sighandler)(int); sighandler signal(int signum, sighandler handler); ``` 其中,signum是需要处理的信号编号,handler是信号的处理函数。处理函数可以是用户自定义的函数,也可以是预定义的常量SIG_IGN表示忽略该信号,或者SIG_DFL表示使用默认的信号处理方式。 在信号处理函数中,可以执行一些特定的操作来处理信号,比如打印日志、保存数据、发送信号给其他进程等。处理函数可以是空函数,表示仅仅捕获信号但不做任何处理。 需要注意的是,一个进程可以屏蔽掉大多数的信号,除了SIGSTOP和SIGKILL这两个信号是无法被屏蔽的。信号有优先级,当一个进程有多个未决信号时,内核将按照发送的顺序来递送信号。值越小的信号越先被递送。 在Linux中,可以通过编写信号处理程序来处理不同的信号,并根据需要执行特定的操作。通过信号处理,可以实现进程间通信、优雅地关闭进程或处理异常情况等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗里い着迷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值