十、时钟与信号
获取时钟
UNIX的时间系统存在一个基点, 就是格林威治时间1970年1月1日凌晨0点0分0秒, 也是传说中UNIX的生日.
UNIX中存在三种格式的时间:
(1) 系统时间. UNIX从出生到现在的秒数, 表现为一个time_t类型的变量
(2) 高分辨率时间. 精确到微秒的时间, 表现为一个timeval结构的变量
(3) 日历时间. 以'年、月、日、时、分、秒'结构表示的时间, 表现为tm结构.
系统时间. 它是UNIX中最基本的时间形式. 用于系统时间的函数如下:
#include <time.h>
time_t time(time_t *tloc);
double difftime(time_t time2, time_t time1);
函数difftime获取两次time调用返回的系统时间差.
秒数往往很难读懂, UNIX中更改系统时间为日历时间的函数如下:
#include <time.h>
struct tm *localtime(const time_t *clock);
time_t mktime(struct tm*timeptr);
函数localtime转换系统时间, clock为当地时间, 并以tm结构返回.
函数mktime实现函数localtime的反功能.
下面给出一个打印本地时间的例子
[bill@billstone Unix_study]$ cat time1.c
#include <time.h>
#include <stdio.h>
int main()
{
struct tm when;
time_t now;
time(&now);
when = *localtime(&now);
printf("now=[%d] [%04d %02d %02d %02d:%02d:%02d]\n", now, \
when.tm_year+1900, when.tm_mon+1, when.tm_mday, \
when.tm_hour, when.tm_min, when.tm_sec);
return 0;
}
[bill@billstone Unix_study]$ make time1
cc time1.c -o time1
[bill@billstone Unix_study]$ ./time1
now=[1239927129] [2009 04 17 08:12:09]
[bill@billstone Unix_study]$
信号的概念
信号是传送给进程的事件通知, 它可以完成进程间异步事件的通信.
导致信号产生的原因很多, 但总体说来有三种可能:
(1) 程序错误. 当硬件出现异常, 除数为0或者软件非法访问等情况时发生.
(2) 外部事件. 当定时器到达, 用户按健中断或者进程调用abort等信号发送函数时方生.
(3) 显式请求. 当进程调用kill, raise等信号发送函数或者用户执行shell命令kill传递信号时发生.
同样的, 当进程收到信号时有三种处理方式:
(1) 系统默认. 系统针对不同的信号有不同的默认处理方式.
(2) 忽略信号. 信号收到后, 立即丢弃. 注意信号SIGSTOP和SIGKILL不能忽略.
(3) 捕获信号. 进程接收信号, 并调用自定义的代码响应之.
信号操作
函数signal设置对信号的操作动作,原型如下:
#include <signal.h>
void (*signal (int sig, void (*f) (int()) (int);
这是个复杂的函数原型, 不果可以分开看:
typedef void (*func)(int);
func signal(int sig, func f);
其中, func参数有三种选择:SIG_DFL(恢复信号默认处理机制), SIG_IGN(忽略信号处理)和函数地址(调用信号捕获函数执行处理).
首先看一个忽略终止信号SIGINT的例子.
[bill@billstone Unix_study]$ cat sig1.c
#include <signal.h>
#include <stdio.h>
int main()
{
signal(SIGINT, SIG_IGN);
sleep(10); // 睡眠10秒
return 0;
}
[bill@billstone Unix_study]$ make sig1
cc sig1.c -o sig1
[bill@billstone Unix_study]$ ./sig1
[bill@billstone Unix_study]$
在程序运行的10秒内,即使你键入Ctrl+C中断命令, 进程也不退出.
再看一个捕获自定义信号的例子.
[bill@billstone Unix_study]$ cat sig2.c
#include <signal.h>
#include <stdio.h>
int usr1 = 0, usr2 = 0;
void func(int);
int main()
{
signal(SIGUSR1, func);
signal(SIGUSR2, func);
for(;;)
sleep(1); // 死循环, 方便运行观察
return 0;
}
void func(int sig){
if(sig == SIGUSR1)
usr1++;
if(sig == SIGUSR2)
usr2++;
fprintf(stderr, "SIGUSR1[%d], SIGUSR2[%d]\n", usr1, usr2);
signal(SIGUSR1, func);
signal(SIGUSR2, func);
}
在后台运行, 结果如下:
[bill@billstone Unix_study]$ make sig2
cc sig2.c -o sig2
[bill@billstone Unix_study]$ ./sig2& // 后台运行
[2] 13822
[bill@billstone Unix_study]$ kill -USR1 13822 // 发送信号SIGUSR1
SIGUSR1[1], SIGUSR2[0]
[bill@billstone Unix_study]$ kill -USR2 13822 // 发送信号SIGUSR2
SIGUSR1[1], SIGUSR2[1]
[bill@billstone Unix_study]$ kill -USR2 13822 // 发送信号SIGUSR2
SIGUSR1[1], SIGUSR2[2]
[bill@billstone Unix_study]$ kill -9 13822 // 发送信号SIGSTOP, 杀死进程
[bill@billstone Unix_study]$
[2]+ 已杀死 ./sig2
[bill@billstone Unix_study]$
UNIX应用程序可以向进程显式发送任意信号, 原型如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
看一个发送和捕获SIGTERM终止信号的例子
[bill@billstone Unix_study]$ cat sig3.c
#include <signal.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
void childfunc(int sig){
fprintf(stderr, "Get Sig\n");
}
int main()
{
pid_t pid;
int status;
assert((pid = fork()) >= 0);
if(pid == 0){
signal(SIGTERM, childfunc);
sleep(30);
exit(0);
}
fprintf(stderr, "Parent [%d] Fork child pid=[%d]\n", getpid(), pid);
sleep(1);
kill(pid, SIGTERM);
wait(&status);
fprintf(stderr, "Kill child pid=[%d], exit status[%d]\n", pid, status>>8);
return 0;
}
[bill@billstone Unix_study]$ make sig3
cc sig3.c -o sig3
[bill@billstone Unix_study]$ ./sig3
Parent [13898] Fork child pid=[13899]
Get Sig
Kill child pid=[13899], exit status[0]
[bill@billstone Unix_study]$
定时器
UNIX下定时器可以分为普通的定时器和精确的定时器.
普通定时器通过alarm函数实现, 它的精度是秒, 而且每调用一次alarm函数只能产生一次定时操作, 如果需要反复定时, 就要多次调用alarm. 调用fork后, 子进程中的定时器将被取消, 但调用exec后, 定时器仍然有效.
在UNIX中使用普通定时器需要三个步骤:
(1) 调用signal函数设置捕获定时信号
(2) 调用函数alarm定时.
(3) 编写响应定时信号函数.
下面是一个定时器的例子, 每隔1秒向进程发送定时信号,用户可键入Ctrl+C或Delete结束程序.
[bill@billstone Unix_study]$ cat time2.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int n = 0;
void timefunc(int sig){
fprintf(stderr, "Alarm %d\n", n++);
signal(SIGALRM, timefunc);
alarm(1);
}
int main()
{
int status;
signal(SIGALRM, timefunc);
alarm(1);
while(1);
return 0;
}
[bill@billstone Unix_study]$ make time2
cc time2.c -o time2
[bill@billstone Unix_study]$ ./time2
Alarm 0
Alarm 1
Alarm 2
// 按Ctrl+C结束
[bill@billstone Unix_study]$
函数alarm设置的定时器只能精确到秒, 而下面函数理论上可以精确到毫秒:
#include <sys/select.h>
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval value, struct itimerval *ovalue);
函数setitimer可以提供三种定时器, 它们相互独立, 任意一个定时完成都将发送定时信号到进程, 并且重新计时. 参数which确定了定时器的类型:
(1) ITIMER_REAL. 定时真实时间, 与alarm类型相同. 对应信号为SIGALRM.
(2) ITIMER_VIRT. 定时进程在用户态下的实际执行时间. 对应信号为SIGVTALRM.
(3) ITIMER_PROF. 定时进程在用户态和核心态下的实际执行时间. 对应信号为SIGPROF.
在一个UNIX进程中, 不能同时使用alarm和ITIMER_REAL类定时器.
结构itimerval描述了定时器的组成:
struct itimerval{
struct timeval it_interval;
struct timeval it_value;
}
结构成员it_value指定首次定时的时间, 结构成员it_interval指定下次定时的时间. 定时器工作时, 先将it_value的时间值减到0, 发送一个信号, 再将it_vale赋值为it_interval的值, 重新开始定时, 如此反复. 如果it_value值被设置为0, 则定时器停止定时.
结构timeval秒数了一个精确到微秒的时间:
struct timeval{
long tv_sec;
long tv_usec;
}
下面设计了一个精确定时器的例子, 进程每1.5秒发送定时信号SIGPROF, 用户可键入Ctrl+C或Delete结束程序.
[bill@billstone Unix_study]$ cat time3.c
#include <sys/select.h>
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int n = 0;
void timefunc(int sig){
fprintf(stderr, "ITIMER_PROF[%d]\n", n++);
signal(SIGPROF, timefunc);
}
int main()
{
struct itimerval value;
value.it_value.tv_sec = 1;
value.it_value.tv_usec = 500000;
value.it_interval.tv_sec = 1;
value.it_interval.tv_usec = 500000;
signal(SIGPROF, timefunc);
setitimer(ITIMER_PROF, &value, NULL);
while(1);
return 0;
}
[bill@billstone Unix_study]$ make time3
cc time3.c -o time3
[bill@billstone Unix_study]$ ./time3
ITIMER_PROF[0]
ITIMER_PROF[1]
ITIMER_PROF[2]
[bill@billstone Unix_study]$