linux系统编程之信号(二):一些信号发送函数和不同精度的睡眠

一、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。下面是个小程序示例:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#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会被信号打断,返回unslept的时间
    }
    while (n > 0);

    return 0;
}

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
}

/* raise(sig) 等价于 kill(getpid(), sig) 给自己发送信号 */

程序中注册信号在fork之前,故子进程也会继承,在子进程中对进程组发送了信号,故信号处理函数会被调用两次:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./kill  recv a sig=10 recv a sig=10 simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ 

因为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, abort 函数

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

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

示例程序:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#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\n", sig);
    alarm(1); // 间接递归调用handler
}

输出测试:

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./alarm  recv a sig=14 recv a sig=14 recv a sig=14 recv a sig=14 recv a sig=14

....................

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

#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);

3、setitimer函数

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

示例程序如下:

/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#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\n", 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\n", (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;
}

如果我们把43,44行代码打开,后面的都注释掉,则会第一次经过5s 输出recv语句,以后每次经过1s 就输出recv语句。而如上程序所示的话,输出为simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./setitimer  1 0 4 999924

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

参考:《APUE》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯数据库技术

玩转MyRocks/RocksDB--STATISTICS与后台线程篇

1672
来自专栏用户2442861的专栏

linux下libevent的安装和使用例子:数据回显

http://blog.csdn.net/fall221/article/details/9045353 (安装)

1352
来自专栏哲学驱动设计

CQRS讨论

今天和同事一起讨论了CQRS(Command Query Responsibility Segregation),过程中,我产生了一些疑问,先记录在这里,以后有...

1667
来自专栏杨建荣的学习笔记

缓慢的update语句性能分析(r6笔记第61天)

最近处理一个问题的时候,先是收到DB time升高的报警,然后查看DB time的情况发现,已经有近1000%的负载了。 ? 带着好奇心想看看到底是什么样的...

2685
来自专栏杨建荣的学习笔记

同样的sql执行结果不同的原因分析 (r4笔记第27天)

今天开发的同事问我一个问题,说有一个sql语句,在weblogic的日志中执行没有结果,但是手动拷贝数据到客户端执行,却能够查到。这种奇怪的问题一下子就能引起我...

2838
来自专栏微信公众号:Java团长

《阿里巴巴Java工作手册》学习笔记

最近浏览了一下阿里巴巴的Java开发手册,感觉内容确实非常的赞,发现了不少自己在编程中的误区,因此决定通过成文牢固掌握,文中将选取个人认为比较重要的部分进行描述...

661
来自专栏为数不多的Android技巧

请不要滥用SharedPreference

SharedPreference是Android上一种非常易用的轻量级存储方式,由于其API及其友好,得到了很多很多开发者的青睐。但是,SharedPrefer...

934
来自专栏技术栈大杂烩

Linux: linux 匿名管道

相信很多在linux平台工作的童鞋, 都很熟悉管道符 '|', 通过它, 我们能够很灵活的将几种不同的命令协同起来完成一件任务.就好像下面的命令:

982
来自专栏西安-晁州

GraphQL介绍&使用nestjs构建GraphQL查询服务

GraphQL介绍&使用nestjs构建GraphQL查询服务(文章底部附demo地址) GraphQL一种用为你 API 而生的查询语言。出自于Faceboo...

4119
来自专栏Java学习之路

Hibernate学习---Configuration,Session,SessionFactory Configuration:SessionFactory:Session:

上一节我们讲到了Hibernate的测试,并且给出了测试代码,刚开始看见这个测试代码的同学估计是一头雾水把,所以这一节我们来讲一下测试代码。 本节主要内容: ...

2836

扫码关注云+社区