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。
下面是个小程序示例:
#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\n", 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\n", 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\n", "fuction", "time(usec)", "realtime", "reduce");
fprintf(stderr, "----------------------------------------------------\n");
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]\n", 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, "\t usleep %8u %8u %8d\n", 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, "\t nanousleep %8u not support\n", nDelay);
}
gettimeofday(&tvNow, NULL);
nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t nanosleep %8u %8u %8d\n", 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]\n", 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, "\t select %8u %8u %8d\n", 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]\n", 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, "\t pselect %8u %8u %8d\n", nDelay, nTimeTest,nReduce); fprintf (stderr, "--------------------------------\n"); } 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\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;
}
如果我们把37,38行代码打开,后面的都注释掉,则会第一次经过5s 输出recv语句,以后每次经过1s 就输出recv语句。而如上程
序所示的话,输出为:
huangcheng@ubuntu:~$ ./a.out
1 0 4 999924
即先是设定了闹钟,for了一个循环后重新设定闹钟,此次通过第三个参数返回上次时钟unsleep的时间,即本来再过oit这么多时间就会产生信号,通过getitimer也可以获得,但getitimer不会重新设置时钟。
UNIX环境高级编程——信号之kill、raise、killpg、alarm、pause、abort、sleep、usleep、nanosleep和setitimer函数的更多相关文章
- UNIX环境高级编程——信号
一.信号生命周期 从信号发送到信号处理函数的执行完毕. 对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生:信号在进 ...
- UNIX环境高级编程——信号基本概述和signal函数
一.为了理解信号,先从我们最熟悉的场景说起:1. 用户输入命令,在Shell下启动一个前台进程.2. 用户按下Ctrl-C,这个键盘输入产生一个硬件中断.3. 如果CPU当前正在执行这个进程的代码,则 ...
- Unix环境高级编程---信号
参考博客:http://blog.csdn.net/alex_my/article/details/39494129 1. 信号概念 何为信号? 信号是一种软中断,可以由以下情形触发: -1: 用户按 ...
- UNIX环境高级编程——信号(API)
一.信号在内核中的表示 实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending).进程可以选择阻塞(Block)某个信号.被阻塞的信号 ...
- UNIX环境高级编程——信号说明列表
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGK ...
- UNIX环境高级编程——sigqueue、sigsuspend函数
一.sigqueue函数 功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用. int sigqueue(pid_t pid, int sig, ...
- (八) 一起学 Unix 环境高级编程 (APUE) 之 信号
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- 《UNIX环境高级编程(第3版)》
<UNIX环境高级编程(第3版)> 基本信息 原书名:Advanced Programming in the UNIX Environment (3rd Edition) (Addison ...
- (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
随机推荐
- Mysql锁机制--乐观锁 & 悲观锁
Mysql 系列文章主页 =============== 从 这篇 文章中,我们知道 Mysql 并发事务会引起更新丢失问题,解决办法是锁.所以本文将对锁(乐观锁.悲观锁)进行分析. 第一部分 悲观锁 ...
- sqlserver批量更新数据
update t_hr_teadept set rjkm=b.yjkmfrom t_hr_teadept a inner join t_tr_bzxx_km b on a.bzh=b.bzh wher ...
- Python Selenium 之数据驱动测试
数据驱动模式的测试好处相比普通模式的测试就显而易见了吧!使用数据驱动的模式,可以根据业务分解测试数据,只需定义变量,使用外部或者自定义的数据使其参数化,从而避免了使用之前测试脚本中固定的数据.可以将测 ...
- 记录 Python3 安装 Scrapy 遇到的问题
开发环境:Windows 10 + Python 3 使用 pip 去安装 Scrapy, pip install scrapy , 报了一个错误. 原因:加 --user 的作用是显式指定安装在用 ...
- Python中capitalize()与title()的区别
capitalize()与title()都可以实现字符串首字母大写.主要区别在于:capitalize(): 字符串第一个字母大写title(): 字符串内的所有单词的首字母大写 例如: >&g ...
- 【python标准库模块二】random模块学习
random模块是用来生成随机数的模块 导入random模块 import random 生成一个0~1的随机数,浮点数 #随机生成一个0~1的随机数 print(random.random()) 生 ...
- A potentially dangerous Request.Form value was detected from the client问题处理
问题剖析: 用户在页面上提交表单到服务器时,服务器会检测到一些潜在的输入风险,例如使用富文本编辑器控件(RichTextBox.FreeTextBox.CuteEditor等)编辑的内容中包含有HTM ...
- spring-boot配置静态资源映射的坑:properties文件不能添加注释
如此博文所述,Spring Boot 对静态资源映射提供了默认配置 默认将 /** 所有访问映射到以下目录:classpath:/staticclasspath:/publicclasspath:/r ...
- Django中过期@cache_page中缓存的views数据
django的缓存系统中,cache_page 这个装饰器非常好用,只要添加一个装饰器就可以缓存views的响应内容,但是django没有提供过期这个views缓存数据的功能. @cache_page ...
- 多线程(五) Fork/Join框架介绍及实例讲解
什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过For ...