欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

浅谈linux几种定时函数的使用

程序员文章站 2023-02-14 15:55:12
在程序开发过程中,我们时不时要用到一些定时器,通常如果时间精度要求不高,可以使用sleep,uslepp函数让进程睡眠一段时间来实现定时, 前者单位为秒(s),后者为微妙...

在程序开发过程中,我们时不时要用到一些定时器,通常如果时间精度要求不高,可以使用sleep,uslepp函数让进程睡眠一段时间来实现定时,

前者单位为秒(s),后者为微妙(us);但有时候我们又不想让进程睡眠阻塞在哪儿,我们需要进程正常执行,当到达规定的时间时再去执行相应的操作,

在linux下面我们一般使用alarm函数跟setitimer函数来实现定时功能;

下面对这两个函数进行详细分析:

(1)alarm函数

alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送sigalrm信号;

alarm函数原型如下:

unsigned int alarm(unsigned int seconds);
//seconds 为指定的秒数

所需头文件
  #include<unistd.h>

函数原型
  unsigned int alarm(unsigned int seconds)

函数参数
  seconds:指定秒数

函数返回值
  成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
  出错:-1

下面是alarm()函数的简单例子:

void sigalrm_fn(int sig)  
 
{ 
  printf("alarm!\n"); 
  alarm(2); 
  return; 
} 
 
int main(void) 
 
{ 
  signal(sigalrm, sigalrm_fn); //后面的函数必须是带int参数的
  alarm(1); 
  while(1)  
  pause(); 
 
}

(2)setitimer()函数

在linux下如果对定时要求不太精确的话,使用alarm()和signal()就行了,但是如果想要实现精度较高的定时功能的话,就要使用setitimer函数。

setitimer()为linux的api,并非c语言的standard library,setitimer()有两个功能,一是指定一段时间后,才执行某个function,二是每间格一段时间就执行某个function;

linux为每个任务安排了3个内部定时器:

itimer_real:实时定时器,不管进程在何种模式下运行(甚至在进程被挂起时),它总在计数。定时到达,向进程发送sigalrm信号。

itimer_virtual:这个不是实时定时器,当进程在用户模式(即程序执行时)计算进程执行的时间。定时到达后向该进程发送sigvtalrm信号。

itimer_prof:进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计数。定时到达产生sigprof信号。itimer_prof记录的时间比itimer_virtual多了进程调度所花的时间。

定时器在初始化是,被赋予一个初始值,随时间递减,递减至0后发出信号,同时恢复初始值。在任务中,我们可以一种或者全部三种定时器,但同一时刻同一类型的定时器只能使用一个。

setitimer函数原型如下:

#include <sys/time.h>

    int setitimer(int which, const struct itimerval *new_value,
           struct itimerval *old_value);

 timer values are defined by the following structures:

      struct itimerval {
        struct timeval it_interval; /* next value */
        struct timeval it_value;  /* current value */
      };

      struct timeval {
        time_t   tv_sec;     /* seconds */
        suseconds_t tv_usec;    /* microseconds */
      };

it_interval用来指定每隔多长时间执行任务, it_value用来保存当前时间离执行任务还有多长时间。比如说, 你指定it_interval为2秒(微秒为0),开始的时候我们把it_value的时间也设定为2秒(微秒为0),当过了一秒, it_value就减少一个为1, 再过1秒,则it_value又减少1,变为0,这个时候发出信号(告诉用户时间到了,可以执行任务了),并且系统自动把it_value的时间重置为 it_interval的值,即2秒,再重新计数

下面是setitimer简单实例:

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

void test_func()
{
  static count = 0;

  printf("count is %d\n", count++);
}

void init_sigaction()
{
  struct sigaction act;
     
  act.sa_handler = test_func; //设置处理信号的函数
  act.sa_flags = 0;

  sigemptyset(&act.sa_mask);
  sigaction(sigprof, &act, null);//时间到发送sigrof信号
}

void init_time()
{
  struct itimerval val;
     
  val.it_value.tv_sec = 1; //1秒后启用定时器
  val.it_value.tv_usec = 0;

  val.it_interval = val.it_value; //定时器间隔为1s

  setitimer(itimer_prof, &val, null);
}

int main(int argc, char **argv)
{

  init_sigaction();
  init_time();

  while(1);

  return 0;
}

可以看出每个一秒输出一个count的值:

下面是运行结果:

[root@localhost 5th]# ./test
count is 0
count is 1
count is 2
count is 3
count is 4
count is 5
count is 6
count is 7
count is 8
count is 9

附录:

signal

1. 头文件
#include <signal.h>

2. 功能
设置某一信号的对应动作

3. 函数原型
void (*signal(int signum,void(* handler)(int)))(int);

分解来看:

typedef void (*sig_t) (int);
sig_t signal(int sig, sig_t func);

第一个参数是目标信号。func参数是一个指针,指向某个处理该信号的函数。这个处理信号函数带有一个int型参数,并应返回void。
func参数也可以设定为下面的一些值:
sig_ign: 如果func参数被设置为sig_ign,该信号将被忽略。
sig_dfl: 如果func参数被设置为sig_dfl,该信号会按照确定行为处理。

4. sig信号的可能类型

1) #define sighup 1 /* hangup */

sighup是unix系统管理员很常用的一个信号。许多后台服务进程在接受到该信号后将会重新读取它们的配置文件。然而,该信号的实际功能是通知进程它的控制终端被断开。缺省行为是终止进程。

2) #define sigint 2 /* interrupt */

对于unix使用者来说,sigint是另外一个常用的信号。许多shell的ctrl-c组合使得这个信号被大家所熟知。该信号的正式名字是中断信号。缺省行为是终止进程。

3) #define sigquit 3 /* quit */

sigquit信号被用于接收shell的ctrl-/组合。另外,它还用于告知进程退出。这是一个常用信号,用来通知应用程序从容的(译注:即在结束前执行一些退出动作)关闭。缺省行为是终止进程,并且创建一个核心转储。

4) #define sigill 4 /* illegal instr. (not reset when caught) */

如果正在执行的进程中包含非法指令,操作系统将向该进程发送sigill信号。如果你的程序使用了线程,或者pointer functions,那么可能的话可以尝试捕获该信号来协助调试。([color=red]注意:原文这句为:“if your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”。中间的两个use of use of,不知是原书排版的瑕疵还是我确实没有明白其意义;另外,偶经常听说functions pointer,对于pointer functions,google了一下,应该是fortran里面的东西,不管怎样,还真不知道,确切含义还请知道的兄弟斧正。[/color])缺省行为是终止进程,并且创建一个核心转储。

5) #define sigtrap 5 /* trace trap (not reset when caught) */

sigtrap这个信号是由posix标准定义的,用于调试目的。当被调试进程接收到该信号时,就意味着它到达了某一个调试断点。一旦这个信号被交付,被调试的进程就会停止,并且它的父进程将接到通知。缺省行为是终止进程,并且创建一个核心转储。

6) #define sigabrt 6 /* abort() */

sigabrt提供了一种在异常终止(abort)一个进程的同时创建一个核心转储的方法。然而如果该信号被捕获,并且信号处理句柄没有返回,那么进程不会终止。缺省行为是终止进程,并且创建一个核心转储。

7) #define sigfpe 8 /* floating point exception */

当进程发生一个浮点错误时,sigfpe信号被发送给该进程。对于那些处理复杂数学运算的程序,一般会建议你捕获该信号。缺省行为是终止进程,并且创建一个核心转储。

8) #define sigkill 9 /* kill (cannot be caught or ignored) */

sigkill是这些信号中最难对付的一个。正如你在它旁边的注释中看到的那样,这个信号不能被捕获或忽略。一旦该信号被交付给一个进程,那么这个进程就会终止。然而,会有一些极少数情况sigkill不会终止进程。这些罕见的情形在处理一个“非中断操作”(比如磁盘i/o)的时候发生。虽然这样的情形极少发生,然而一旦发生的话,会造成进程死锁。唯一结束进程的办法就只有重新启动了。缺省行为是终止进程。

9) #define sigbus 10 /* bus error */

如同它的名字暗示的那样,cpu检测到数据总线上的错误时将产生sigbus信号。当程序尝试去访问一个没有正确对齐的内存地址时就会产生该信号。缺省行为是终止进程,并且创建一个核心转储。

10) #define sigsegv 11 /* segmentation violation */

sigsegv是另一个c/c++程序员很熟悉的信号。当程序没有权利访问一个受保护的内存地址时,或者访问无效的虚拟内存地址(脏指针,dirty pointers,译注:由于没有和后备存储器中内容进行同步而造成。关于野指针,可以参见http://en.wikipedia.org/wiki/wild_pointer 的解释。)时,会产生这个信号。缺省行为是终止进程,并且创建一个核心转储。

11) #define sigsys 12 /* non-existent system call invoked */

sigsys信号会在进程执行一个不存在的系统调用时被交付。操作系统会交付该信号,并且进程会被终止。缺省行为是终止进程,并且创建一个核心转储。

12) #define sigpipe 13 /* write on a pipe with no one to read it */

管道的作用就像电话一样,允许进程之间的通信。如果进程尝试对管道执行写操作,然而管道的另一边却没有回应者时,操作系统会将sigpipe信号交付给这个讨厌的进程(这里就是那个打算写入的进程)。缺省行为是终止进程。

13) #define sigalrm 14 /* alarm clock */

在进程的计时器到期的时候,sigalrm信号会被交付(delivered)给进程。这些计时器由本章后面将会提及
的setitimer和alarm调用设置。缺省行为是终止进程。

14) #define sigterm 15 /* software termination signal from kill */

sigterm信号被发送给进程,通知该进程是时候终止了,并且在终止之前做一些清理活动。sigterm信号是unix的kill命令发送的缺省信号,同时也是操作系统关闭时向进程发送的缺省信号。缺省行为是终止进程。

15) #define sigurg 16 /* urgent condition on io channel */

在进程已打开的套接字上发生某些情况时,sigurg将被发送给该进程。如果进程不捕获这个信号的话,那么将被丢弃。缺省行为是丢弃这个信号。

16) #define sigstop 17 /* sendable stop signal not from tty */

本信号不能被捕获或忽略。一旦进程接收到sigstop信号,它会立即停止(stop),直到接收到另一个sigcont
信号为止。缺省行为是停止进程,直到接收到一个sigcont信号为止。

17) #define sigtstp 18 /* stop signal from tty */

sigstp与sigstop类似,它们的区别在于sigstp信号可以被捕获或忽略。当shell从键盘接收到ctrl-z的时候就会交付(deliver)这个信号给进程。缺省行为是停止进程,直到接收到一个sigcont信号为止。

18) #define sigcont 19 /* continue a stopped process */

sigcont也是一个有意思的信号。如前所述,当进程停止的时候,这个信号用来告诉进程恢复运行。该信号的有趣的地方在于:它不能被忽略或阻塞,但可以被捕获。这样做很有意义:因为进程大概不愿意忽略或阻塞sigcont信号,否则,如果进程接收到sigstop或sigstp的时候该怎么办?缺省行为是丢弃该信号。

19) #define sigchld 20 /* to parent on child stop or exit */

sigchld是由berkeley unix引入的,并且比srv 4 unix上的实现有更好的接口。(如果信号是一个没有追溯能力的过程(not a retroactive process),那么bsd的sigchid信号实现会比较好。在system v unix的实现中,如果进程要求捕获该信号,操作系统会检查是否存在有任何未完成的子进程(这些子进程是已经退出exit)的子进程,并且在等待调用wait的父进程收集它们的状态)。如果子进程退出的时候附带有一些终止信息(terminating information),那么信号处理句柄就会被调用。所以,仅仅要求捕获这个信号会导致信号处理句柄被调用(译注:即是上面说的“信号的追溯能力”),而这是却一种相当混乱的状况。)一旦一个进程的子进程状态发生改变,sigchld信号就会被发送给该进程。就像我在前面章节提到的,父进程虽然可以fork出子进程,但没有必要等待子进程退出。一般来说这是不太好的,因为这样的话,一旦进程退出就可能会变成一个僵尸进程。可是如果父进程捕获sigchld信号的话,它就可以使用wait系列调用中的某一个去收集子进程状态,或者判断发生了什么事情。当发送sigstop,sigstp或sigconf信号给子进程时,sigchld信号也会被发送给父进程。缺省行为是丢弃该信号。

20) #define sigttin 21 /* to readers pgrp upon background tty read */

当一个后台进程尝试进行一个读操作时,sigttin信号被发送给该进程。进程将会阻塞直到接收到sigcont信号为止。缺省行为是停止进程,直到接收到sigcont信号。

21) #define sigttou 22 /* like ttin if (tp->t_local&ltostop) */

sigttou信号与sigttin很相似,不同之处在于sigttou信号是由于后台进程尝试对一个设置了tostop属性的tty执行写操作时才会产生。然而,如果tty没有设置这个属性,sigttou就不会被发送。缺省行为是停止进程,直到接收到sigcont信号。

22) #define sigio 23 /* input/output possible signal */

如果进程在一个文件描述符上有i/o操作的话,sigio信号将被发送给这个进程。进程可以通过fcntl调用来设置。缺省行为是丢弃该信号。

23) #define sigxcpu 24 /* exceeded cpu time limit */

如果一旦进程超出了它可以使用的cpu限制(cpu limit),sigxcpu信号就被发送给它。这个限制可以使用随后讨论的setrlimit设置。缺省行为是终止进程。

24) #define sigxfsz 25 /* exceeded file size limit */

如果一旦进程超出了它可以使用的文件大小限制,sigxfsz信号就被发送给它。稍后我们会继续讨论这个信号。缺省行为是终止进程。

25) #define sigvtalrm 26 /* virtual time alarm */

如果一旦进程超过了它设定的虚拟计时器计数时,sigvtalrm信号就被发送给它。缺省行为是终止进程。

26) #define sigprof 27 /* profiling time alarm */

当设置了计时器时,sigprof是另一个将会发送给进程的信号。缺省行为是终止进程。

27) #define sigwinch 28 /* window size changes */

当进程调整了终端的行或列时(比如增大你的xterm的尺寸),sigwinch信号被发送给该进程。缺省行为是丢弃该信号。

28) #define sigusr1 29 /* user defined signal 1 */

29) #define sigusr2 30 /* user defined signal 2 */

sigusr1和sigusr2这两个信号被设计为用户指定。它们可以被设定来完成你的任何需要。换句话说,操作系统没有任何行为与这两个信号关联。缺省行为是终止进程。(译注:按原文的意思翻译出来似乎这两句话有点矛盾。)

5. 例子

5.1. linux下的ctrl+c在windows下的实现

linux下通常的做法:

signal(sigint, sigfunc); // 设置信号
void sigfunc(int signo)
  {
   ... //处理信号相关的操作
  }

以下是linux下的ctrl+c在windows下的实现

#include <stdio.h>
  #include <windows.h>
  static is_loop = 1;
  // 捕获控制台 ctrl+c 事件的函数
  bool ctrlhandler( dword fdwctrltype )
  {
   switch (fdwctrltype)
   {
   /* handle the ctrl-c signal. */
   case ctrl_c_event:
     printf("ctrl_c_event \n");
     break;
   case ctrl_close_event:
     printf("ctrl_close_event \n");
     break;
   case ctrl_break_event:
     printf("ctrl_break_event \n");
     break;
   case ctrl_logoff_event:
     printf("ctrl_logoff_event \n");
     break;
   case ctrl_shutdown_event:
     printf("ctrl_shutdown_event \n");
     break;
   default:
     return false;
   }
   is_loop = 0;
   return (true);
  }

  int main(int argc, char *argv[])
  {
   printf("set console ctrl handler\n");
   setconsolectrlhandler((phandler_routine)ctrlhandler, true);
   while (is_loop);
   return 0;
  }

5.2.linux下的ctrl+c在windows下的实现二

#include <stdio.h>
  #include <windows.h>
  #define contrl_c_handle() signal(3, exit)
  int main(int argc, char *argv[])
  {
   printf("set console ctrl handler\n");
   contrl_c_handle();
   while (1);
   system("pause");
   return 0;
  }

以上就是小编为大家带来的浅谈linux几种定时函数的使用全部内容了,希望大家多多支持~