在windows平台,我想写一个timer,然后传入一个我自定义的参数,我查找msdn,发现了SetTimer函数:
UINT_PTR SetTimer(
HWND hWnd, // 窗口句柄
UINT_PTR nIDEvent, // 定时器ID,多个定时器时,可以通过该ID判断是哪个定时器
UINT uElapse, // 时间间隔,单位为毫秒
TIMERPROC lpTimerFunc // 回调函数
);
这 就结束了吗?一个api就搞定了吗?我敢肯定,在linux下,的确就这么就搞定了,我只要说我需要一个定时器,那么它会给我一个api,或系统调用或 posix调用,我几乎不需要其他的东西,但是这是个爱把什么都搅在一起的windows,事情就不那么简单了,通过应用这个SetTimer,我甚至明白了这个操作系统为什么叫windows,看看它的第一个参数吧,一个窗口句柄。
我天真地写下了以下的代码:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
printf( "in the timer proc/n" );
//自动备份代码
}
DWORD CALLBACK AutoBakup( PVOID lpParam )
{
char * c = "hello";
UINT id=SetTimer(NULL,0,1000,TimerFunc);
while( 1 )
{
sleep(1000);
}
KillTimer(NULL,id);
return 0;
}
可是我错了,根本就没有进入回调函数,原来windows是消息驱动的,timer的触发需要一个代号为275(我的xp系统是275)的消息,当定时器到期的时候,系统会分发这个消息,然后在消息处理中调用我设置的回调函数,唉,晕倒,于是我修改了代码:
VOID CALLBACK TimerFunc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
printf( "in the timer proc/n" );
//自动备份代码
}
DWORD CALLBACK AutoBakup( PVOID lpParam )
{
MSG msg;
PeekMessage(&msg,NULL,WM_USER,WM_USER,PM_NOREMOVE);
UINT id=SetTimer(NULL,0,1000,TimerFunc);
while( ((bRet = GetMessage(&msg,NULL,0,0))!= 0)&& g_iRunTimer )
{
if(bRet==-1)
{
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
KillTimer(NULL,id);
return 0;
}
这下好使了,于是我要狂喜吗?拉倒吧,我都快哭了,就用个timer,值得这么费力吗?timer生效了,我怎么传参数啊?看看TimerProc的形参,根本没有什么能自定义,唯一的一个idEvent可以还只在窗口句柄有效时有意义,难道我需要整出一个窗口吗?我写的是一个控制台服务程序,继续搞笑!于是我想办法在msg上做文章,可是后来一想,别玩了,这可是在为公司写商业软件,不是自己玩的,搞笑进行中...
实际上往回调函数里传参数是不应该的,因为回调函数的意义就是操作系统或库函数在发生一些事情的时候调用的函数,类似“好莱坞法则”,不要call我,我会call你,而这个call的时机往往是不确定的,因此当时的环境就是不确定的,我们参数往往是具有确定性的,回调函数调用时机的环境复杂性往往不一定能保证我们参数行为的确定性,于是回调函数一般都是系统定义好的,也就是回调函数是谁调用谁定义,但是虽然是系统定义却是用户提供的,这是和普通库函数的 最大区别,普通库函数是谁被调用谁定义。
但是需求要求我必须传个参数进去,还好,windows没有说不让用全局变量或TLS,否则我就要去考古队找份新工作了。
嘲笑完了windows,我强烈想试试linux下同样的功能,timer的设置不必说,方法多了去了,而且没有可恶的类似于窗口句柄那样不相关的概念使 你困扰,发疯。关键还是第二个问题,怎么传参?linux下的定时器是用信号实现的,而信号处理函数也是没有办法传参,和windows一样,于是我想试试posix库函数,于是我想到了timer_create函数系列,还好,在网上找了一个现成的例子可以串一下:
引用(我有所更改):
#include
using namespace std;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SIGMYTIMER (SIGRTMAX)
int SetTimer(int nElaspe , int nMode = 1);
void TimerRoutine(int signo, siginfo_t* info, void* context);
int main()
{
struct sigaction sysact;
sigemptyset(&sysact.sa_mask);
sysact.sa_flags = SA_SIGINFO;
sysact.sa_sigaction = TimerRoutine ;
sigaction(SIGMYTIMER, &sysact, NULL);
SetTimer(500);
while(1)
{
sleep(1);
}
return 0;
}
timer_t IDList[20];
char *buf = "abcd";
int SetTimer(int nElaspe, int nMode)
{
struct sigevent evp;
static int nTimerIndex = 0;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGMYTIMER;
evp.sigev_value.sival_ptr = buf;
int nCreate = timer_create(CLOCK_REALTIME, &evp, &IDList[nTimerIndex]);
if (nCreate == 0) //success
{
struct itimerspec value;
struct itimerspec ovalue;
value.it_value.tv_sec = nElaspe / 1000;
value.it_value.tv_nsec = (nElaspe % 1000) * (1000 * 1000);
if (nMode == 1)
{
value.it_interval.tv_sec = value.it_value.tv_sec;
value.it_interval.tv_nsec = value.it_value.tv_nsec;
}
else
{
value.it_interval.tv_sec = 0;
value.it_interval.tv_nsec = 0;
}
timer_settime(IDList[nTimerIndex], 0, &value, &ovalue)
}
return 1;
}
void TimerRoutine(int signo, siginfo_t* info, void* context)
{
if (signo != SIGMYTIMER) return;
cout<<*(int*)(info->si_value.sival_ptr)<
}
于是打印出来了abcd,证明传参成功了,其实吧,TimerRoutine就是个简单的信号处理函数,关键就是他的第二个参数:
typedef struct siginfo {
int si_signo;
int si_errno;
int si_code;
union {
int _pad[SI_PAD_SIZE];
/* kill() */
struct {
pid_t _pid; /* sender's pid */
__ARCH_SI_UID_T _uid; /* sender's uid */
} _kill;
/* POSIX.1b timers */
struct {
timer_t _tid; /* timer id */
int _overrun; /* overrun count */
char _pad[sizeof( __ARCH_SI_UID_T) - sizeof(int)];
sigval_t _sigval; /* same as below */
int _sys_private; /* not to be passed to user */
} _timer;
/* POSIX.1b signals */
struct {
pid_t _pid; /* sender's pid */
__ARCH_SI_UID_T _uid; /* sender's uid */
sigval_t _sigval; //可以用作参数
} _rt;
/* SIGCHLD */
struct {
pid_t _pid; /* which child */
__ARCH_SI_UID_T _uid; /* sender's uid */
int _status; /* exit code */
clock_t _utime;
clock_t _stime;
} _sigchld;
/* SIGILL, SIGFPE, SIGSEGV, SIGBUS */
struct {
void __user *_addr; /* faulting insn/memory ref. */
} _sigfault;
/* SIGPOLL */
struct {
__ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */
int _fd;
} _sigpoll;
} _sifields;
} siginfo_t;
typedef union sigval {
int sival_int;
void __user *sival_ptr;
} sigval_t;
#define si_value _sifields._rt._sigval
#define si_int _sifields._rt._sigval.sival_int
#define si_ptr _sifields._rt._sigval.sival_ptr
晕了吧,反正我快晕了,但是我就是喜欢,正是这些琐碎又有条理的东西促使了linux的强大。其实这个结构是众多联合的组合,实际用到的只有一个,比如我的例子中就用到了 POSIX.1b signals ,就是:
· struct {
· pid_t _pid; /* sender's pid */
· __ARCH_SI_UID_T _uid; /* sender's uid */
· sigval_t _sigval; //可以用作参数
· } _rt;而这个结构是极其好理解的,这就是我们传递的参数结构,具体详情请查看posix标准吧,总结起来就是siginfo的功劳,那么我们发信号的时候能否直接把siginfo发给进程呢?答案是肯定的,看看:
long sys_rt_sigqueueinfo (int pid, int sig, siginfo_t *uinfo);
这个系统调用就可以。siginfo的si_code代表了信号的类型,具体不赘述了,现在看看内核在执行用户的信号处理时是怎么把siginfo传给用户的:
handle_signal--> setup_rt_frame -->copy_siginfo_to_user:
int copy_siginfo_to_user(siginfo_t __user *to, siginfo_t *from)
{
...
switch (from->si_code & __SI_MASK) {
case __SI_KILL:
err |= __put_user(from->si_pid, &to->si_pid);
err |= __put_user(from->si_uid, &to->si_uid);
break;
case __SI_TIMER: //检索si_code来确定类型
err |= __put_user(from->si_tid, &to->si_tid);
err |= __put_user(from->si_overrun, &to->si_overrun);
err |= __put_user(from->si_ptr, &to->si_ptr); //将参数给用户
break;
...
return err;
}
这 样用户的自定义参数就在信号处理的时候又返回给了用户,实际上linux实现了posix的语义,在内核里面专门实现了一套posix的timer,看来posix是考虑的相当周全的,linux并不自造轮子,实现posix比自己实现一套机制更好,最起码口碑会更好,windows令人烦就是因为它总自己实现一些怪怪的东西,很庞 大,让你目不暇接。于是就因为这个小小的timer我就会更加喜欢linux。
linux强大的信号机制使很多事情成为可能,信号是独立的一块,不像windows里面的apc,消息驱动等等概念杂糅在一起,一看就让人头晕,一研究就不可自拔,意志薄弱的人可能就从此进入微软开发大军了