专题 10 时钟与信号

  1.  时钟
    1.  系统时间

系统时间是UNIX中最基本的时间形式,其他任何时钟形式都是从系统时间中转化而来的。UNIX中用于系统时间的函数如下:

#include <time.h>

time_t time(time_t *tloc);

double difftime(time_t time2, time_t time1);//获取系统时间差

/************读取系统时间****************/

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

int main()

{

time_t start, finish;

time(&start);

sleep(6);

time(&finish);

printf("start=[%d], finish=[%d], difftime=[%f]\n", start, finish, difftime(finish, start));

return 0;

}

  1.  本地时间

struct tm *localtime(const time_t *clock);

time_t mktime(struct tm *timeptr);

PS:函数mktime实现函数localtime的反功能,它返回tm结构对应的系统时间。

/************读取本地时间****************/

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <time.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;

}

  1.  信号
    1.  信号操作

函数signal更改信号的默认处理方式,函数kill或raise可以向进程发送信号。在实践应用中常见的信号处理有:1.忽略SIGINT等进程终止类信号,屏幕用户终止进程;2. 忽略或捕获子进程结束信号SIG(H)LD,释放进程表项,预防僵死进程;3. 捕获定时器信号,完成进程定时或并发操作; 4. 捕获自定义信号,完成进程特定操作。

  1.  信号忽略与捕获

/***********忽略信号*************/

#include <signal.h>

#include <stdio.h>

int main()

{

 signal(SIGINT, SIG_IGN);/*屏蔽信号SIGINT*/

 return 0;

}

/*************信号捕获****************/

#include <signal.h>

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

}

  1.  信号的显示发送

函数原型:

int kill(pid_t pid, int signo);

int raise(int signo);

函数kill发送信号signo到参数pid决定的特定进程中,其中参数pid的取值含义如下所示:

取值

含义

>0

发送信号signo到进程pid中

0

发送信号signo到与调用进程同组进程中

-1

发送信号signo到实际用户id等于调用进程的有效用户id的进程中

<-1

发送信号signo到进程id等于pid绝对值的进程中

kill函数调用成功返回0,否则返回-1.

raise函数向调用进程自身发送信号signo,成功时返回0,否则返回-1.

/***************发送信号******************/

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

void childfunc(int sig)

{

fprintf(stderr, "Get Sig\n");

exit(5);

}

int main()

{

pid_t pid;

int status;

if((pid=fork()) < 0)

 exit(1);

else 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;

}

  1.  定时器
    1.  普通定时器的设置

函数原型:

unsigned int alarm(unsigned int seconds);

函数alarm总能调用成功,它返回上次定时器剩余的定时时间,如果是第一次设置定时器,返回0.

在UNIX中使用普通定时器的三个步骤如下:

步骤1:设置捕获定时信号。

更改信号SIGALRM的默认处理机制,并设置捕获响应函数。

步骤2:定时。

在需要定时的代码处调用函数alarm即可。

步骤3:编写响应定时信号函数。

捕获定时信号函数模型如下代码所示:

void timefunc(int sig)

{

 ………………….

 signal(SIGALRM, timefunc);/*再次设置捕获定时信号*/

 alarm(1);/*再次定时*/

}

PS:由于在部分Linux版本中,信号的捕获只响应一次,故函数中需要增加信号捕获设置,函数alarm设置的定时器也只能定时一次,故函数中需要重新定时。

/***************函数alarm定时器实例**************/

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

{

signal(SIGALRM, timefunc);/*捕获定时信号*/

alarm(1);    /*定时开始*/

while(1);

return 0;

}

  1.  精确定时器的设置

函数原型:

int gettimer(int which, struct itimerval *value);

int settimer(int which, const struct itimerval *value, struct itimerval *ovalue);

参数which与定时器类型:

取值

含义

信号发送

TIMER_REAL

定时真实时间,与alarm类型相同

SIGALRM

TIMER_VIRT

定时进程在用户态下的实际执行时间

SIGVTALRM

TIMER_PROF

定时进程在用户态和核心态下的实际执行时间

SIGPROF

settimer函数设置的定时器不但可以计时到微秒,还能自动循环定时。结构itimerval描述了定时器的组成:

struct itimerval

{

 struct timerval it_interval; /*下次定时取值*/

 struct timerval it_value;  /*本次定时设置值*/

};

/****************精确定时器实例*********************/

#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;  /*定时1.5秒*/

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

}

  1.  全局跳转

函数原型:

int setjmp(jmp_buf env);//保存当前的堆栈环境

void longjmp(jmp_buf env, int val);//恢复保存在env中的堆栈信息,并使程序跳转到env中保存的位置处重新执行。

在UNIX中使用全局跳转的步骤如下:

  1.  定义jmp_buf变量,如:

#include <setjmp.h>

jmp_buf env;

  1.  在需要程序重复执行的位置调用setjmp,如:

i = setjmp(env);

  1.  在调用函数setjmp后增加判断代码,确认程序执行次数,避免产生死循环。
  2.  在程序回退的位置调用longjmp。

/***************跳转语句实例********************/

#include <unistd.h>

#include <stdio.h>

#include <setjmp.h>

int j = 0;

jmp_buf env;

int main()

{

int i, k=0;

i = setjmp(env);

printf("setjmp=[%d];j=[%d];k=[%d]\n", i, j++, k++);

if(j > 2)

 exit(0);

sleep(1);

longjmp(env, 0);

return 0;

}

ps:程序运行的过程中没有出现想要的结果(k应该一直为0),可能Linux对这个函数的支持不同?

  1.  单进程I/O超时处理

处理UNIX中I/O超时的方式有终端方式、信号跳转方式和多路复用方式等多种。

5.1 终端I/O超时方式

利用ioctl函数,设置文件描述符对应的标准输入文件属性为“接收输入后立刻返回,如果无输入则10秒钟后定时返回”模式。

/**************终端越野模式*****************/

#include <unistd.h>

#include <stdio.h>

#include <termio.h>

#include <fcntl.h>

int main()

{

struct termio old, new;

char c = 0;

ioctl(0, TCGETA, &old);

new = old;

new.c_lflag &= ~ICANON;

new.c_cc[VMIN] = 0;

new.c_cc[VTIME] = 30;

ioctl(0, TCSETA, &new);

if(read(0, &c, 1) != 1)

 printf("time out\n");

else

 printf("\n%d\n", c);

ioctl(0, TCSETA, &old);

return 0;

}

  1.  信号与跳转I/O超时方式

在read函数前调用setjmp保存堆栈数据并使用alarm设定3秒定时器。倘若输入超时则必然接收定时信号,此时只需让longjmp函数从setjmp处返回,并跳过read语句即可。

/*********************信号跳转方式超时处理**********************/

#include <setjmp.h>

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

int timeout = 0;

jmp_buf env;

void timefunc(int sig)

{

timeout = 1;

longjmp(env, 1);

}

int main()

{

char c;

signal(SIGALRM, timefunc);

setjmp(env);

if(timeout == 0)  /*正常处理*/

{

 alarm(3);

 read(0, &c, 1);

 alarm(0);

 printf("\n%d\n", c);

}

else     /*打印超时*/

 printf("timeout\n");

return 0;

}

  1.  多路复用I/O超时方式

一个进程可能同时打开多个文件,UNIX中函数select可以同时监控多个文件描述符的输入输出,进程将一直阻塞,直到越野或者产生I/O为止,通知进程读取或发送数据。函数select的原型如下:

int select(int nfds, fd_set *readfs, fd_set *writeds, fd_set *exceptionds, struct timeval *timeout);

FD_CLR(int fd, fd_set *fdset);

FD_ISSET(int fd, fd_set *fdset);

FD_SET(int fd, fd_set *fdset);

FD_ZERO(fd_set *fdset);

PS: nfds为最大描述符编号+1, 当不知道监控的最大描述符编号时,可以使用常数FD_SETSIZE。

timout的含义:为0时,函数不等待, 立即返回。为NULL时,函数永远等待,直到有文件描述符就绪。

/************多路利用方式超时处理******************/

#include <stdio.h>

#include <sys/types.h>

#include <sys/times.h>

#include <sys/select.h>

int main()

{

struct timeval timeout;

fd_set readfds;

int i;

char c;

timeout.tv_sec = 3;

timeout.tv_usec = 0;

FD_ZERO(&readfds);

FD_SET(0, &readfds);                           /*监控文件0(标准输入)*/

i = select(1, &readfds, NULL, NULL, &timeout);

if(i > 0)          /*有输入*/

{

 read(0, &c, 1);

 printf("\n%d\n", c);

}

else if(i == 0)         /*超时*/

 printf("\ntimeout\n");

else

 printf("\nerror\n");

return 0;

}

PS:当参数readfds, writefds和exceptfds都为NULL,函数select退化成可精确到微秒级的sleep调用,如下:

select(0, NULL, NULL, NULL, &timeout);



你可能感兴趣的:(unix,struct,kill,null,终端,Signal)