• UNIX环境高级编程——信号之kill、raise、killpg、alarm、pause、abort、sleep、usleep、nanosleep和setitimer函数


    一、kill, raise, killpg 函数


    int kill(pid_t pid, int sig);
    int raise(int sig);
    int killpg(int pgrp, int sig);

    kill命令是调用kill函数实现的,kill函数可以给一个指定的进程或进程组发送指定的信号,其中kill 函数的pid 参数取值不同表示不同含义,具体可man 一下。raise函数可以给当前进程发送指定的信号(自己给自己发信号)killpg 函数可以给进程组发生信号。这三个函数都是成功返回0,错误返回-1。

    kill()可以用来送参数sig指定的信号给参数pid指定的进程。参数pid有几种情况:
    pid>0 将信号传给进程识别码为pid 的进程。
    pid=0 将信号传给和当前进程相同进程组的所有进程
    pid=-1 将信号广播传送给系统内所有的进程
    pid<0 将信号传给进程组识别码为pid绝对值的所有进程

    下面是个小程序示例:

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<signal.h>
    
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)
    
    void handler(int sig);
    
    int main(int argc, char *argv[])
    {
        if (signal(SIGUSR1, handler) == SIG_ERR)
            ERR_EXIT("signal error");
        pid_t pid = fork();
        if (pid == -1)
            ERR_EXIT("fork error");
    
        if (pid == 0)
        {
            /*
                pid = getpgrp(); // 得到进程组pid
                kill(-pid, SIGUSR1); //向进程组发送信号
            */
            killpg(getpgrp(), SIGUSR1);
            exit(EXIT_SUCCESS); // 子进程处理完信号才退出
        }
    
        int n = 5;
        do
        {
            n = sleep(n); // sleep会被信号打断,返回unsleep的时间
        }
        while (n > 0);
    
        return 0;
    }
    
    void handler(int sig)
    {
        printf("recv a sig=%d
    ", sig);
    }
    
    /* raise(sig) 等价于 kill(getpid(), sig) 给自己发送信号 */
         程序中注册信号在fork之前,故子进程也会继承,在子进程中对进程组发送了信号,故信号处理函数会被调用两次:

    huangcheng@ubuntu:~$ ./a.out
    recv a sig=10
    recv a sig=10
         因为sleep 函数会被信号处理函数打断(RETURN VALUE: Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted  by  a signal handler.),如果我们想让其睡够5s, 则可以用一个while循环判断其返回值。这里需要注意的是输出两次recv之后继续睡眠的时间是不一定的,也有可能是5s,即信号处理函数在调用sleep之前已经被调用(子进程先被系统调度执行),sleep未被中断。也表明一点:只要接收到信号,信号处理函数可以在任意某个时刻被调用,不仅仅只在进程主动调用sleep, pause等函数(让cpu去调度运行其他程序)的时候,cpu一直都在进行进程的调度,进行用户空间和内核空间的切换, 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先就会处理PCB中记录的信号。

    二、alarm、pause、abort 函数


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

    调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

    示例程序:

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<signal.h>
    
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)
    
    void handler(int sig);
    
    int main(int argc, char *argv[])
    {
        if (signal(SIGALRM, handler) == SIG_ERR)
            ERR_EXIT("signal error");
    
        alarm(1); //过了n秒会产生SIGALRM信号
        for (; ;)
            pause();
    
        return 0;
    }
    
    void handler(int sig)
    {
        printf("recv a sig=%d
    ", sig);
        alarm(1); // 间接递归调用handler
    }
    输出测试:
    huangcheng@ubuntu:~$ ./a.out
    recv a sig=14
    recv a sig=14
    recv a sig=14
    recv a sig=14
    recv a sig=14
    ....................

         即每隔1s就会发送一个SIGALRM信号,其实alarm函数时间到时只发送一次信号,我们在信号处理函数中再次调用alarm函数,造成不断的信号发送。

         pause函数使调用进程挂起直至捕捉到一个信号:

    #include <unistd.h>
    int pause(void);
         只有执行了一个信号处理程序并从其返回时,pause才返回。


    #include <stdlib.h>
    void abort(void);

    abort函数使当前进程接收到SIGABRT信号而异常终止。就像exit函数一样,abort函数总是会成功的,所以没有返回值。


    三、setitimer 和不同精度的睡眠

    1、首先来看三种不同的时间结构,如下:

    time_t; /* seconds */
    struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
    };
    struct timespec {
    time_t tv_sec;        /* seconds */
    long   tv_nsec;       /* nanoseconds */
    };
    microseconds就是微秒, nanoseconds就是纳秒。

    2、三种不同精度的睡眠

    unsigned int sleep(unsigned int seconds);
    int usleep(useconds_t usec);
    int nanosleep(const struct timespec *req, struct timespec *rem);

    sleep:单位为秒,1秒

    usleep:单位为微秒,1/1000 秒

    nanosleep:单位为毫微秒,也就是纳秒,1/1000 000 000 秒

    如下几个Linux下的微秒级别的定时器:

    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    #include<sys/time.h>
    #include<errno.h>
    #include<string.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/select.h>
    
    
    int main(int argc, char **argv)
    {
    	unsigned int nTimeTestSec = 0;
    	unsigned int nTimeTest = 0;
    	struct timeval tvBegin;
    	struct timeval tvNow;
    	int ret = 0;
    	unsigned int nDelay = 0;
    	struct timeval tv;
    	int fd = 1;
    	int i = 0;
    	struct timespec req;
    
    	unsigned int delay[20] = 
    		{500000, 100000, 50000, 10000, 1000, 900, 500, 100, 10, 1, 0};
    	int nReduce = 0; //误差
    
    	fprintf(stderr, "%19s%12s%12s%12s
    ", "fuction", "time(usec)", "realtime", "reduce");
    	fprintf(stderr, "----------------------------------------------------
    ");
    	for (i = 0; i < 20; i++)
    	{
    		if (delay[i] <= 0)
    			break;
    		nDelay = delay[i];
    		//test sleep
    		gettimeofday(&tvBegin, NULL);
    		ret = usleep(nDelay);
    		if(ret == -1)
    		{
    			fprintf(stderr, "usleep error, errno=%d [%s]
    ", errno, strerror(errno));
    		}
    		gettimeofday(&tvNow, NULL);
    		nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
    		nReduce = nTimeTest - nDelay;
    
    		 fprintf (stderr, "	 usleep       %8u   %8u   %8d
    ", nDelay, nTimeTest,nReduce);
    
    		 //test nanosleep
    		 req.tv_sec = nDelay/1000000;
    		 req.tv_nsec = (nDelay%1000000) * 1000;
    
    		 gettimeofday(&tvBegin, NULL);
    		 ret = nanosleep(&req, NULL);
    		 if (-1 == ret)
    		 {
    		 	fprintf (stderr, "	 nanousleep   %8u	not support
    ", nDelay);
    		 }
    		 gettimeofday(&tvNow, NULL);
    		 nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
    		 nReduce = nTimeTest - nDelay;
    		 fprintf (stderr, "	 nanosleep    %8u   %8u   %8d
    ", nDelay, nTimeTest,nReduce);
    
    		 //test select
    		 tv.tv_sec = 0;
    		 tv.tv_usec = nDelay;
    
    		 gettimeofday(&tvBegin, NULL);
    		 ret = select(0, NULL, NULL, NULL, &tv);
    		 if (-1 == ret)
    		 {
    		 	fprintf(stderr, "select error. errno = %d [%s]
    ", errno, strerror(errno));
    		 }
    
    		 gettimeofday(&tvNow, NULL);
    		 nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
    		 nReduce = nTimeTest - nDelay;
    		 fprintf (stderr, "	 select       %8u   %8u   %8d
    ", nDelay, nTimeTest,nReduce);
    
    		 //pselcet
    		 req.tv_sec = nDelay/1000000;
    		 req.tv_nsec = (nDelay%1000000) * 1000;
    
    		 gettimeofday(&tvBegin, NULL);
    		 ret = pselect(0, NULL, NULL, NULL, &req, NULL);
    		 if (-1 == ret)
    		 {
    		 	fprintf(stderr, "select error. errno = %d [%s]
    ", errno, strerror(errno));
    		 }
    
    		 gettimeofday(&tvNow, NULL);
    		 nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
    		 nReduce = nTimeTest - nDelay;
    		 fprintf (stderr, "	 pselect      %8u   %8u   %8d
    ", nDelay, nTimeTest,nReduce);
    
    		 fprintf (stderr, "--------------------------------
    ");
    
    	}
    	
    	return 0;
    }
         我们在对精度要求较高的情况下使用select()作为定时器,最大的好处就是不会影响信号处理,线程安全,而且精度能得到保证。在这个实验中,当时间延时时间较长时,select和pselect表现较差,当时间小于1毫秒时,他们的精确度便提高了,表现与usleep、nanosleep不相上下,有时精度甚至超过后者。


    usleep()有有很大的问题:

    (1)在一些平台下不是线程安全,如HP-UX以及Linux
    (2)usleep()会影响信号
    (3)在很多平台,如HP-UX以及某些Linux下,当参数的值必须小于1 * 1000 * 1000也就是1秒,否则该函数会报错,并且立即返回。大部分平台的帮助文档已经明确说了,该函数是已经被舍弃的函数。

         Linux下短延时推荐使用select函数,因为准确.

    3、setitimer函数

    包含头文件<sys/time.h> 
    功能setitimer()比alarm功能强大,会间歇性产生时钟,支持3种类型的定时器。
    原型:int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
    参数:第一个参数which指定定时器类型;第二个参数是结构体itimerval的一个实例;第三个参数若不为空则返回先前定时unsleep的时间。
    返回值:成功返回0,失败返回-1。


    参数 which的取值:

    ITIMER_REAL:经过指定的时间后,内核将发送SIGALRM信号给本进程 
    ITIMER_VIRTUAL :程序在用户空间执行指定的时间后,内核将发送SIGVTALRM信号给本进程 
    ITIMER_PROF :进程在用户空间执行和内核空间执行时,时间计数都会减少,通常与ITIMER_VIRTUAL共用,代表进程在用户空间与内核空间中运行指定时间后,内核将发送SIGPROF信号给本进程。

    itimerval结构体:

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

         it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。

    timeval 结构体:

    struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* microseconds*/
    };


    示例程序如下:

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<signal.h>
    #include<sys/time.h>
    
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)
    
    void handler(int sig)
    {
        printf("recv a sig=%d
    ", sig);
    }
    
    int main(int argc, char *argv[])
    {
        if (signal(SIGALRM, handler) == SIG_ERR)
            ERR_EXIT("signal error");
    
        struct timeval  tv_interval = {1, 0}; //以后每次延时1s
        struct timeval tv_value = {5, 0};//第一次延时5s
        struct itimerval it;
        /* Timers decrement from it_value to zero, generate a signal, and reset to it_interval */
        it.it_interval = tv_interval;
        /*  The element it_value is set to the amount of time remaining on the timer */
        it.it_value = tv_value;
        setitimer(ITIMER_REAL, &it, NULL); //间歇性地产生时钟
        /*
            for (; ;)
                pause();
        */
        int i;
        for (i = 0; i < 10000; i++) ;
        struct itimerval oit;
        // 上面循环后也许定时时间还没到,重新设置时钟,并将先前时钟剩余值通过oit传出
        setitimer(ITIMER_REAL, &it, &oit);
        /*  getitimer(ITIMER_REAL, &oit); */
        printf("%d %d %d %d
    ", (int)oit.it_interval.tv_sec, (int)oit.it_interval.tv_usec,
               (int)oit.it_value.tv_sec, (int)oit.it_value.tv_usec);
    
        return 0;
    }

         如果我们把37,38行代码打开,后面的都注释掉,则会第一次经过5s 输出recv语句,以后每次经过1s 就输出recv语句。而如上程

    序所示的话,输出为:

    huangcheng@ubuntu:~$ ./a.out 
    1 0 4 999924

       即先是设定了闹钟,for了一个循环后重新设定闹钟,此次通过第三个参数返回上次时钟unsleep的时间,即本来再过oit这么多时间就会产生信号,通过getitimer也可以获得,但getitimer不会重新设置时钟。








  • 相关阅读:
    fedora20安装hadoop-2.5.1
    超简单fedora20(linux)下JDK1.8的安装
    解决A program file was not specified in the launch configuration.问题
    java中的四则运算
    spring util命名空间
    java中常用的数据加密算法
    C语言实现栈
    百度ueditor富文本编辑器的使用
    C#进程管理程序实现
    ABP领域层-仓储
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6172800.html
Copyright © 2020-2023  润新知