Linux中的alarm和setitimer定时器函数以及信号捕捉函数signal和sigaction

1.alarm函数:设置发送信号的闹钟

通过 man 2 alarm 查看alarm函数的详细信息。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

功能:设置定时器(闹钟)。从调用这个函数开始进行倒计时,倒计时结束之后(变为0),函数会给当前的进程发送SIGALARM信号。

参数:

seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。取消一个定时器,通过alarm(0)。

返回值: 之前没有定时器,返回0;之前有定时器,返回之前的定时器剩余的时间。

SIGALARM信号:

默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。

alarm(10);  -> 返回0

过了1秒

alarm(5);   -> 返回9

测试代码:

#include <stdio.h>
#include <unistd.h>

int main() {

    int seconds = alarm(5);
    printf("seconds = %d\n", seconds);  // 0

    sleep(2);
    seconds = alarm(2);    // 不阻塞
    printf("seconds = %d\n", seconds);  // 3

    while(1) {
    }  //alarm该函数是不阻塞的,如果不设置这个,这个程序就结束了,看不到SIGALARM信号的效果,还有两秒 才能看到

    return 0;
}

使用alarm函数测试电脑一秒钟能数多少个数

alarm和setitimer(ITIMER_PROF) 共享同一个定时器。即alarm的定时时间包含的是:用户+系统内核的运行时间

进行文件的IO操作会比较浪费时间,因此在执行的时候 ./alarm1 >> a.txt 写道这个文件中,向终端输出会消耗文件IO的时间。

此外需要说明的是,定时器与进程的状态无关,无论进程处于什么状态,alarm都会计时

测试代码:

#include <stdio.h>
#include <unistd.h>

int main() {    

    alarm(1);  //1秒后进程终止

    int i = 0;
    while(1) {
        printf("%i\n", i++);
    }

    return 0;
}

通过vim 打开a.txt,然后shift + G到最后一行,可以看到1秒钟在1000万左右,当然向文件中写入也会花费时间,真实的情况比这还要高。

2.setitimer函数

通过 man 2 setitimer 命令查看setitimer的详细描述

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,
                        struct itimerval *old_value);

这些系统调用提供了对间隔计时器的访问,也就是说,这些计时器最初在将来的某个时间点过期,之后(可选地)定期过期。当计时器过期时,将为调用进程生成一个信号,并将计时器重置为指定的间隔(如果间隔非零)。

功能:

设置定时器(闹钟)。可以替代alarm函数。精确到微妙us,并可以实现周期性定时

参数:

which: 定时器以什么时间定时

  • ITIMER_REAL  :这个计时器以真实时间(即挂钟)倒数。在每次到期时,生成一个SIGALRM信号。(常用,这个真实时间包括很多其他的处理时间)

  • ITIMER_VIRTUAL  :该计时器根据进程消耗的用户模式CPU时间进行倒计时。(该度量包括进程中所有线程所消耗的CPU时间。)在每次到期时,生成一个SIGVTALRM信号。

  • ITIMER_PROF  :这个计时器根据进程消耗的总(即用户和系统)CPU时间进行计数。(该度量包括进程中所有线程所消耗的CPU时间。)在每次到期时,生成一个SIGPROF信号。

new_value: 设置定时器的属性,是一个结构体类型

struct itimerval {      // 定时器的结构体
    struct timeval it_interval;  // 每个阶段的时间,即间隔时间 2
    struct timeval it_value;     // 延迟多长时间执行定时器  10
    };


struct timeval {        // 时间的结构体
    time_t      tv_sec;     //  秒数     
    suseconds_t tv_usec;    //  微秒    精确到微妙
    };

过10秒后,每个2秒定时一次

old_value :记录上一次的定时的时间参数,一般不使用,指定NULL

返回值:成功返回0, 失败返回-1并设置错误号

测试代码:实现过3秒后,每隔2秒钟定时一次

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct itimerval new_value;

    // 设置间隔的时间2秒
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;


    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");  //立马执行这个

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    getchar(); //获取键盘录入,什么也不做,否则还不到3秒程序就结束了
    //在获取键盘录入时,进程处于阻塞态,但是定时器与进程的状态无关,
    //无论进程处于什么状态,都会计时

    return 0;
}

我们发现,执行这个程序之后,3秒钟之后,这个程序就截止了,似乎没有每间隔2秒定时一次。原因是,当3秒钟之后,会向进程发送SIGALRM信号,进程收到这个信号后终止。因此,并没有执行每隔2秒定时一次这样的操作。这就需要信号捕捉函数了!!!捕捉这个3秒之后的信号,不让它杀死当前进程。

3.signal信号捕捉函数

通过man 2 signal 查看signal函数的详细描述

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

The  behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux.  Avoid its use: use sigaction(2) instead. (signal()的行为在不同的UNIX版本之间是不同的,在不同的Linux版本之间也是不同的。避免使用它:使用sigaction(2)代替。)

功能:简单来说就是 捕捉到某个信号,然后执行一些处理(可以是系统自带的,也可以是自定义的函数)

signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a  programmer-defined  function  (a  "signal  handler"). (将信号signum的处置设置为handler,它可以是SIG_IGN, SIG_DFL,或者是程序员定义的函数(“信号处理程序”)的地址。)

参数:

signum: 要捕捉的信号

handler: 捕捉到信号要如何处理

  • SIG_IGN : 忽略这个信号,什么也不做

  • SIG_DFL : 使用信号默认的行为,相当于没有捕捉

  • 自定义(回调函数):这个函数是由内核调用,程序员只负责写,捕捉到信号后如何去处理信号。

回调函数:(类似于C++中的仿函数)

需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义

不是程序员调用,而是当信号产生,由内核调用

函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。

返回值:

成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL

失败,返回SIG_ERR,设置错误号

 SIGKILL   SIGSTOP不能被捕捉,不能被忽略。(SIGKILL是强制杀死进程,不能捕捉它,如果一旦捕捉并设置为SIG_IGN,那么这个进程就结束不了了)

测试代码:过3秒以后,每隔2秒钟定时一次

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    // 注册信号捕捉
    // signal(SIGALRM, SIG_IGN);
    // signal(SIGALRM, SIG_DFL);
    // void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。
    signal(SIGALRM, myalarm);  //信号捕捉函数要提前设置好,就相当于打猎一样,要提前设置好陷阱。

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    getchar();

    return 0;
}

3秒钟之后,捕捉到了SIGALRM信号,然后每隔2秒,再次捕捉到SIGALRM信号。

4.sigaction信号捕捉函数

通过man 2 sigaction查看sigaction函数的具体信息

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:

The  sigaction()  system  call  is used to change the action taken by a process on receipt of a specific signal.  (See signal(7) for  an  overview of signals.) sigaction()系统调用用于更改进程在接收到特定信号时所采取的操作。(信号的概述见信号(7)。) 【检查或者改变信号的处理,实现信号捕捉】

参数:

signum : 需要捕捉的信号的编号或者宏值(信号的名称);signum  specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.

If act is non-NULL, the new action for signal signum is installed  from act.  If oldact is non-NULL, the previous action is saved in oldact.如果act为非null,则从act安装信号signum的新动作。如果oldact为非null,则先前的操作保存在oldact中。

act :捕捉到信号之后的处理动作

oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL

返回值:成功0,失败-1

参数 act 和 oldact是结构体类型

The sigaction structure is defined as something like:

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

struct sigaction {

        // 函数指针,指向的函数就是信号捕捉到之后的处理函数

        /*

        sa_handler specifies the action to be associated with signum and may be SIG_DFL          for  the  default  action, SIG_IGN to ignore this signal, or a pointer to a signal handling         function.  This function receives the signal number as its only argument.sa_handler

        指定与signum相关联的操作,默认操作可以是SIG_DFL,忽略该信号的SIG_IGN,或者指向信号处理函数的指针。这个函数接收信号号作为它的唯一参数。

        */

        void     (*sa_handler)(int);

        // 不常用

        void     (*sa_sigaction)(int, siginfo_t *, void *);

        // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。

        sigset_t   sa_mask;


 

        // 使用哪一个信号处理对捕捉到的信号进行处理

        // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction

        //On  some  architectures  a  union  is  involved:  do not assign to both sa_handler and sa_sigaction.在某些体系结构中涉及到联合:不要同时分配给sa_handler和sa_sigaction。

        int        sa_flags;


 

        // 被废弃掉了

        void     (*sa_restorer)(void);

    };

测试代码:过3秒以后,每隔2秒钟定时一次

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct sigaction act;
    
    act.sa_handler = myalarm;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集,全0
   
    // 注册信号捕捉,捕捉SIGALRM信号,输出myalarm中的信号
    sigaction(SIGALRM, &act, NULL);

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    // getchar();
    while(1);

    return 0;
}

通过act.sa_mask设置SIGINT信号阻塞,这样程序就不能通过ctrl c停止了:

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct sigaction act;
    
    act.sa_handler = myalarm;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集,全0

    sigaddset(&act.sa_mask, SIGINT);  
    // 修改内核中的阻塞信号集
    sigprocmask(SIG_BLOCK, &act.sa_mask, NULL);
    //设置在信号捕捉函数执行过程中,临时阻塞SIGINT信号。
    //这样的话ctrl c就停止不了进程了
   



   
    // 注册信号捕捉,捕捉SIGALRM信号,输出myalarm中的信号
    sigaction(SIGALRM, &act, NULL);

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    // getchar();
    while(1);

    return 0;
}

  • 36
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
setitimer函数Linux实现高精度定时器的系统调用。它用于设置一个定时器,在指定的时间间隔内周期性地触发一个定时信号函数原型如下: ```c int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); ``` 其,which参数指定了定时器的类型。常用的取值有: - ITIMER_REAL:使用系统的真实时间来计算定时器间隔,与系统的墙钟相关。 - ITIMER_VIRTUAL:使用进程的虚拟时间来计算定时器间隔,即进程在用户态运行的时间。 - ITIMER_PROF:使用进程在用户态和内核态运行的总时间来计算定时器间隔。 new_value参数是一个itimerval结构体指针,用于指定定时器的间隔和初始启动时间。该结构体定义如下: ```c struct itimerval { struct timeval it_interval; // 定时器的间隔 struct timeval it_value; // 定时器的初始启动时间 }; ``` 其,timeval结构体定义如下: ```c struct timeval { time_t tv_sec; // 秒数 suseconds_t tv_usec; // 微秒数 }; ``` old_value参数是一个itimerval结构体指针,用于保存之前的定时器设置。 在使用setitimer函数之前,需要通过signal函数注册一个信号处理函数,用于处理定时器触发的信号。一般常用的信号SIGALRM。 使用setitimer函数可以实现一些需要高精度定时的场景,比如周期性地刷新屏幕、定时采集传感器数据等。定时器的间隔可通过设置it_interval参数来调整,定时器的初始启动时间可通过设置it_value参数来设置。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值