用戶空間應用中創建一個Timer(alarm/setitimer/POSIX Timer等等),然后程序繼續執行;
內核進入創建/設置Timer系統調用,開始計時,在超時后通過何種方式通知用戶空間;
用戶空間又是如何執行回調函數的。
下面就着重這個流程,梳理一下Timer周期中用戶空間和內核空間涉及到的相關模塊。
1. 總體框架
關注的Timer(alarm/setitimer/POSIX Timer),都在libc/librt/libphtread中定義。librt是POSIX.1b Realtime擴展的實現,這其中就包括POSIX Timer。
其中alarm/setitimer都調用libc,POSIX Timer調用librt/libpthread。
總體框架如下:
❶應用調用庫通過系統調用創建Timer,同時自身注冊信號處理函數。
❷庫提供通用接口,轉換成系統調用。
❸內核Timer相關系統調用(setitimer/timer_create),通過hrtimer創建相應的定時器,在超時后調用hrtimer超時函數發送signal給用戶空間進程。
❹用戶空間進程在收到信號之后,執行對應的信號處理函數。
至此,Timer一個閉環完成。
下面分alarm/setitimer和POSIX Timer兩種類型的Timer,來介紹其流程。
2. alarm/setitimer流程
linux/common/alarm.c中實現了alarm,可以看到和setitimer相同的接口。
#ifdef __NR_alarm _syscall1(unsigned int, alarm, unsigned int, seconds) #else #include <sys/time.h> unsigned int alarm(unsigned int seconds) { ... if (setitimer(ITIMER_REAL, &new, &old) < 0) { return 0; } ... } #endif
即使定義了alarm系統調用,在內核中alarm和setitimer也是調用相同的do_setitimer。所以這兩個API在內核的實現是一致的。
SYSCALL_DEFINE1(alarm, unsigned int, seconds) { return alarm_setitimer(seconds); } unsigned int alarm_setitimer(unsigned int seconds) { ... do_setitimer(ITIMER_REAL, &it_new, &it_old); ... } SYSCALL_DEFINE3(setitimer, int, which, struct itimerval __user *, value, struct itimerval __user *, ovalue) { ...
error = do_setitimer(which, &set_buffer, ovalue ? &get_buffer : NULL); ...
}
所以研究do_setitimer就可以分析這兩個API的內核實現。
int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue) { ... switch (which) { case ITIMER_REAL: again: spin_lock_irq(&tsk->sighand->siglock); timer = &tsk->signal->real_timer;---------------------------------這里是task_struct結構體中的real_timer這個hrtimer。所以alarm/setitimer一個進程/線程空間中只能存在一個。 if (ovalue) { ovalue->it_value = itimer_get_remtime(timer); ovalue->it_interval = ktime_to_timeval(tsk->signal->it_real_incr); } /* We are sharing ->siglock with it_real_fn() */ if (hrtimer_try_to_cancel(timer) < 0) { spin_unlock_irq(&tsk->sighand->siglock); goto again; } expires = timeval_to_ktime(value->it_value); if (expires.tv64 != 0) { tsk->signal->it_real_incr = timeval_to_ktime(value->it_interval); hrtimer_start(timer, expires, HRTIMER_MODE_REL);--------------啟動alarm/setitimer對應的hrtimer。 } else tsk->signal->it_real_incr.tv64 = 0; trace_itimer_state(ITIMER_REAL, value, 0); spin_unlock_irq(&tsk->sighand->siglock); break; ... } return 0; }
那么real_timer這個hrtimer的處理函數在何時初始化的呢?
可以看出在進程創建的時候,已經初始化了real_timer。對應的超時函數是it_real_fn,發送SIGALRM信號給對應的進程。然后用戶空間執行SIGALRM處理函數。
do_fork--> copy_process--> copy_signal--> static int copy_signal(unsigned long clone_flags, struct task_struct *tsk) { ... hrtimer_init(&sig->real_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);--------初始化real_timer定時器。 sig->real_timer.function = it_real_fn; ... } /* * The timer is automagically restarted, when interval != 0 */ enum hrtimer_restart it_real_fn(struct hrtimer *timer) { struct signal_struct *sig = container_of(timer, struct signal_struct, real_timer); trace_itimer_expire(ITIMER_REAL, sig->leader_pid, 0); kill_pid_info(SIGALRM, SEND_SIG_PRIV, sig->leader_pid);--------------------發送SIGALRM信號。 return HRTIMER_NORESTART; }
3. POSIX Timer流程
POSIX Timer在libpthread\nptl\sysdeps\unix\sysv\linux\timer_create.c中創建。
int timer_create ( clockid_t clock_id, struct sigevent *evp, timer_t *timerid) { # undef timer_create # ifndef __ASSUME_POSIX_TIMERS if (__no_posix_timers >= 0) # endif { ... kernel_timer_t ktimerid; int retval = INLINE_SYSCALL (timer_create, 3, syscall_clockid, evp,-------------通過timer_create系統調用創建Timer。 &ktimerid); ... } else { # ifndef __ASSUME_POSIX_TIMERS ... if (__no_posix_timers > 0) # endif { /* Create the helper thread. */ pthread_once (&__helper_once, __start_helper_thread);----------------------單獨創建線程來處理,一次初始化。 if (__helper_tid == 0) { /* No resources to start the helper thread. */ __set_errno (EAGAIN); return -1; } ... res = INTERNAL_SYSCALL (timer_create, err, 3,------------------------------通過timer_create系統調用來創建Timer。 syscall_clockid, &sev, &newp->ktimerid); ... } } } # ifndef __ASSUME_POSIX_TIMERS /* Compatibility code. */ return compat_timer_create (clock_id, evp, timerid); # endif }
__start_helper_thread-->
timer_helper_thread-->
timer_sigev_thread-----------------------------------------------------------此線程在定時器超時后,才會創建。
thrfunc------------------------------------------------------------------調用用戶提供的超時回調函數。
再來看看內核中的實現,這里主要看common_timer_create和alarm_timer_create兩種類型。
SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock, struct sigevent __user *, timer_event_spec, timer_t __user *, created_timer_id) { ...
if (timer_event_spec) { if (copy_from_user(&event, timer_event_spec, sizeof (event))) { error = -EFAULT; goto out; } rcu_read_lock(); new_timer->it_pid = get_pid(good_sigevent(&event));------------如果用戶提供了sigevent,獲取pid。 rcu_read_unlock(); if (!new_timer->it_pid) { error = -EINVAL; goto out; } } else { memset(&event.sigev_value, 0, sizeof(event.sigev_value)); event.sigev_notify = SIGEV_SIGNAL; event.sigev_signo = SIGALRM; event.sigev_value.sival_int = new_timer->it_id; new_timer->it_pid = get_pid(task_tgid(current));--------------如果沒有提供sigevent,使用默認的SIGALRM。 } new_timer->it_sigev_notify = event.sigev_notify; new_timer->sigq->info.si_signo = event.sigev_signo; new_timer->sigq->info.si_value = event.sigev_value; new_timer->sigq->info.si_tid = new_timer->it_id; new_timer->sigq->info.si_code = SI_TIMER; if (copy_to_user(created_timer_id,--------------------------------返回timer_id給用戶空間 &new_timer_id, sizeof (new_timer_id))) { error = -EFAULT; goto out; } error = kc->timer_create(new_timer);-----------------------------------------調用common_timer_create或者alarm_timer_create創建定時器 if (error) goto out; ...
} static int common_timer_create(struct k_itimer *new_timer) { hrtimer_init(&new_timer->it.real.timer, new_timer->it_clock, 0);-------------初始化hrtimer return 0; }
那么超時函數在哪里設置的呢?
static int common_timer_set(struct k_itimer *timr, int flags, struct itimerspec *new_setting, struct itimerspec *old_setting) { ...
mode = flags & TIMER_ABSTIME ? HRTIMER_MODE_ABS : HRTIMER_MODE_REL; hrtimer_init(&timr->it.real.timer, timr->it_clock, mode);-------------------重新初始化hrtimer timr->it.real.timer.function = posix_timer_fn;------------------------------hrtimer回調函數 hrtimer_set_expires(timer, timespec_to_ktime(new_setting->it_value)); ... }
在回調函數中,進行了超時處理。
static enum hrtimer_restart posix_timer_fn(struct hrtimer *timer) { ... if (posix_timer_event(timr, si_private)) {----------------------------------發送信號 ... } } ... } int posix_timer_event(struct k_itimer *timr, int si_private) { ... timr->sigq->info.si_sys_private = si_private; rcu_read_lock(); task = pid_task(timr->it_pid, PIDTYPE_PID);-------------------------------根據pid獲取task實體 if (task) { shared = !(timr->it_sigev_notify & SIGEV_THREAD_ID); ret = send_sigqueue(timr->sigq, task, shared);------------------------將當前信號隊列發送到對應的用戶空間對應的task,進程在收到信號后進行相應處理 } rcu_read_unlock(); /* If we failed to send the signal the timer stops. */ return ret > 0; }
那么如果是Alarm類型的Timer,情況如何呢?
static int alarm_timer_create(struct k_itimer *new_timer) { ... alarm_init(&new_timer->it.alarm.alarmtimer, type, alarm_handle_timer);-----------初始化alarmtimer,回調函數是alarm_handle_timer ... } static enum alarmtimer_restart alarm_handle_timer(struct alarm *alarm, ktime_t now) { ... if ((ptr->it_sigev_notify & ~SIGEV_THREAD_ID) != SIGEV_NONE) { if (posix_timer_event(ptr, 0) != 0)------------------------------------------和其他POSIX Timer一樣發送signal信號 ptr->it_overrun++; } ... } static int alarm_timer_set(struct k_itimer *timr, int flags, struct itimerspec *new_setting, struct itimerspec *old_setting) { ... alarm_start(&timr->it.alarm.alarmtimer, exp);-------------------------------------啟動AlarmTimer ... }
4. 總結
所以無論是alarm/setitimer,還是POSIX Timer都是通過發送signal來通知用戶應用。只是用戶空間處理消息的方式有所不同。
由於Timer經過庫的封裝,不光要看內核,還需要研究庫對API進行了何種封裝。才能更好的了解其行為。
也由於庫的種類(lig/glib/ulib等)和版本千差萬別,所以也需要引起重視。
一個關於libpthread引起的POSIX Timer執行異常情況。
描述:在一個進程中創建三個SIGEV_THREAD類型POSIX Timer,但是超時只執行一個回調函數。其他兩個沒有被調用。
問題分析:SIGCANCEL這個信號導致,timer_create創建helper thread失敗。Timer超時后,回調函數也不會被執行。
解決方法:
void attribute_hidden __start_helper_thread (void) { ... sigset_t ss; sigset_t oss; sigfillset (&ss); /*__sigaddset (&ss, SIGCANCEL); - already done by sigfillset */
__sigaddset (&ss, SIGCANCEL);--------------------------------修改方法
...
}