http://www.zyfforlinux.cc/2014/10/20/%E4%BF%A1%E5%8F%B7%E6%93%8D%E4%BD%9C%E8%AF%A6%E8%A7%A3-%E4%B8%8A/
信号的应用
- 结合定时器和间隔计时器实现一些多任务
下面先简单的介绍下定时器和间隔计时器。- 定时器:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
非阻塞函数,定时时间到了就发送SIGALRM信号,如果在alarm之前已经调用了alarm并且时间没有到的话,那么此次返回之前alarm剩余的时间,否则返回0。
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, //计时方式 可以选择 ITIMER_REAL/ITIMER_VIRTUAL/ITIMER_PROF
const struct itimerval *new_value,//时间参数
struct itimerval *old_value //上一次设置的定时器
);
struct itimerval {
struct timeval it_interval; /* next value */ 间隔时间
struct timeval it_value; /* current value */ 延迟时间 //延时it_value后,第一次触发SIGALRM信号,之后每隔it_iterval时间后触发一次
};
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
间隔计数器,首先在it_value秒后会产生SIGALRM信号,以后每隔it_interval秒后都会产生SIGALARM信号
- 结合间隔计数器实现多任务案例:- 间隔计时器:
1
|
#include<stdio.h>
|
- 进程间通信 传递数据
struct sigaction
{
void (*sa_handle)(int); //信号处理函数的地址,或者是SIG_IGN SIG_DFL
void (*sa_sigaction)(int,siginfo_t*,void*);
sigset_t sa_mask; //要屏蔽的信号集
int sa_flags;//SA_SIGINFO
void (*sa_restorer)(void); //保留成员
}
其中void (*sa_handle)(int) 和 void (*sa_sigaction)(int,siginfo_t*,void*); 只选其中之一取决于flags,如果sa_flasg=0那么就是前者,相当于普通的信号处理函数
如果sa_flasg=SA_SIGINFO 那么就是后者.
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
#include <sys/types.h>
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int; //可以传递两种类型的数据,整型或一个指针
void *sival_ptr;
};
使用示列:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handle(int s,siginfo_t* info,void *d)
{
printf("接受到的数据:%d\n",info->si_int);
}
main()
{
struct sigaction act={0};
act.sa_sigaction=handle;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGINT);
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&act,0);
printf("pid:%d\n",getpid());
while(1);
}
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
main()
{
union sigval val;
val.sival_int=8888;
sigqueue(pid,SIGUSR1,val); //pid是上面程序输出的pid向上面这个程序发送带有数据的信号
}
程序输出:
[root@localhost apue]# ./a.out
pid:11214
接受到的数据:8888
int raise(int sig);
允许进程向自身发送信号
========================
http://blog.csdn.net/solstice/article/details/6173563
Linux 时间函数
Linux 的计时函数,用于获得当前时间:
- time(2) / time_t (秒)
- ftime(3) / struct timeb (毫秒)
- gettimeofday(2) / struct timeval (微秒)
- clock_gettime(2) / struct timespec (纳秒)
- gmtime / localtime / timegm / mktime / strftime / struct tm (这些与当前时间无关)
定时函数,用于让程序等待一段时间或安排计划任务:
- sleep
- alarm
- usleep
- nanosleep
- clock_nanosleep
- getitimer / setitimer
- timer_create / timer_settime / timer_gettime / timer_delete
- timerfd_create / timerfd_gettime / timerfd_settime
我的取舍如下:
- (计时)只使用 gettimeofday 来获取当前时间。
- (定时)只使用 timerfd_* 系列函数来处理定时。
gettimeofday 入选原因:(这也是 muduo::Timestamp class 的主要设计考虑)
- time 的精度太低,ftime 已被废弃,clock_gettime 精度最高,但是它系统调用的开销比 gettimeofday 大。
- 在 x86-64 平台上,gettimeofday 不是系统调用,而是在用户态实现的(搜 vsyscall),没有上下文切换和陷入内核的开销。
- gettimeofday 的分辨率 (resolution) 是 1 微秒,足以满足日常计时的需要。muduo::Timestamp 用一个 int64_t 来表示从 Epoch 到现在的微秒数,其范围可达上下 30 万年。
timerfd_* 入选的原因:
- sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免。(近期我会写一篇博客仔细讲讲“多线程、RAII、fork() 与信号”)
- nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
- getitimer 和 timer_create 也是用信号来 deliver 超时,在多线程程序中也会有麻烦。timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
- timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。我在一年前发表的《Linux 新增系统调用的启示》中也谈到这个想法,现在我把这个想法在 muduo 网络库中实现了。
- 传统的 Reactor 利用 select/poll/epoll 的 timeout 来实现定时功能,但 poll 和 epoll 的定时精度只有毫秒,远低于 timerfd_settime 的定时精度。
必须要说明,在 Linux 这种非实时多任务操作系统中,在用户态实现完全精确可控的计时和定时是做不到的,因为当前任务可能会被随时切换出去,这在 CPU 负载大的时候尤为明显。但是,我们的程序可以尽量提高时间精度,必要的时候通过控制 CPU 负载来提高时间操作的可靠性,在程序在 99.99% 的时候都是按预期执行的。这或许比换用实时操作系统并重新编写并测试代码要经济一些。
关于时间的精度(accuracy)问题我留到专题博客文章中讨论,它与分辨率(resolution)不完全是一回事儿。时间跳变和闰秒的影响与应对也不在此处展开讨论了。
在非阻塞服务端编程中,绝对不能用 sleep 或类似的办法来让程序原地停留等待,这会让程序失去响应,因为主事件循环被挂起了,无法处理 IO 事件。这就像在 Windows 编程中绝对不能在消息循环里执行耗时的代码一样,会让程序界面失去响应。