Linux时间子系统之(六):POSIX timer
Notes:首先讲解了POSIX timer的标识(唯一识别)、POSIX Timer的组织(管理POSIX Timer)、内核中如何抽象POSIX Timer;然后分析了POSIX timer相关系统调用(timer_create/timer_gettime/timer_getoverrun/timer_settime/timer_delete);再后重点分析了real time timer的callback函数。POSIX timer超时有三种方式(SIGEV_SIGNAL、SIGEV_NONE和SIGEV_THREAD),重点分析了信号触发方式。
原文地址:Linux时间子系统之(六):POSIX timer
一、前言
在用户空间接口函数文档中,我们描述了和POSIX timer相关的操作,主要包括创建一个timer、设定timer、获取timer的状态、获取timer overrun的信息、删除timer。本文将沿着这些用户空间的接口定义来看看内核态的实现。虽然POSIX timer可以基于各种不同的clock创建,本文主要描述real time clock相关的timer。
本文第二章描述了POSIX timer的基本原理,第三章描述系统调用的具体实现,第四章主要讲real time clock的timer callback函数的实现,第五章介绍了timer超期后,内核如何处理信号。
二、基本概念和工作原理
1、如何标识POSIX timer
Notes:获取POSIX timer的ID,保证进程内唯一的一个32为整数。
POSIX.1b interval timer(后面的文章中简称POSIX timer)是用来替代传统的interval timer的,posix timer一个重要的改进是进程可以创建更多(而不是3个)timer,既然可以创建多个timer,那么就存在标识问题,我们用timer ID来标识一个具体的posix timer。这个timer ID也作为一个handler参数在用户空间和内核空间之间传递。
posix timer是一种资源,它隶属于某一个进程,。对于kernel,我们会用timer ID来标识一个POSIX timer,而这个ID是由进程自己管理和分配的。在进程控制块(struct task_struct )中有一个struct signal_struct *signal的成员,用来管理和signal相关的控制数据。timer的处理和信号的发送是有关系的,因此也放到该数据结构中:
……
int posix_timer_id;
……
一个进程在fork的时候,posix_timer_id会被设定为0,因此,对于一个进程而言,其timer ID从0开始分配,随后会依次加一,达到最大值后会从0开始。由此可见,timer ID不是一个全局唯一标识符,只是能保证在一个进程内,其ID是唯一的。实际timer ID的分配算法可以参考posix_timer_add函数,如下:
static int posix_timer_add(struct k_itimer *timer)
{
struct signal_struct *sig = current->signal;
int first_free_id = sig->posix_timer_id;----------------(1)
struct hlist_head *head;
int ret = -ENOENT;do {-------------------------------(2)
spin_lock(&hash_lock);
head = &posix_timers_hashtable[hash(sig, sig->posix_timer_id)];----(3)
if (!__posix_timers_find(head, sig, sig->posix_timer_id)) {--------(4)
hlist_add_head_rcu(&timer->t_hash, head);
ret = sig->posix_timer_id;
}
if (++sig->posix_timer_id < 0)--------------------(5)
sig->posix_timer_id = 0;
if ((sig->posix_timer_id == first_free_id) && (ret == -ENOENT))------(6)
ret = -EAGAIN;
spin_unlock(&hash_lock);
} while (ret == -ENOENT);
return ret;
}
(1)sig->posix_timer_id中记录了上一次分配的ID+1,该值被认为是下一个可以使用的free ID(当然,这个假设不一定成立,但是有很大的机会),也就是本次scan free timer ID的起点位置。
(2)do while是一个循环过程,如果选定的timer ID不是free的,我们还需要++sig->posix_timer_id,以便看看下一个timer ID是否是free的,这个过程不断的循环执行,直到找到一个free的timer ID,或者出错退出循环。一旦找到free的timer ID,则将该posix timer插入哈希表。
(3)根据分配的timer ID和该进程的signal descriptor的地址,找到该posix timer的hash链表头
(4)看看该进程中是否已经有了该timer ID的posix timer存在,如果没有,那么timer ID分配完成
(5)否则,看看下一个timer ID的情况。如果溢出(超过了INT_MAX),那么从0开始搜索
(6)如果scan了一圈还是没有找到free timer ID,那么就出错返回。
2、如何组织POSIX timer
Notes:两种组织方式全局hash表posix_timers_hashtable和进程管理的task->signal->posix_timers。
static DEFINE_HASHTABLE(posix_timers_hashtable, 9);
static DEFINE_SPINLOCK(hash_lock);
随着系统启动和运行,各个进程会不断的创建属于自己的POSIX timer,这些timer被放到了一个全局的hash表中,也就是posix_timers_hashtable。该table共计有512个入口,每个入口都是一个POSIX timer链表头的指针。每一个系统中的POSIX timer都会根据其hash key放入到其中一个入口中(挂入链表)。具体hash key的计算方法是:
static int hash(struct signal_struct *sig, unsigned int nr)
{
return hash_32(hash32_ptr(sig) ^ nr, HASH_BITS(posix_timers_hashtable));
}
计算key考虑的factor包括timer ID值和进程signal descriptor的地址。
hash_lock是包含全局POSIX timer的锁,每次访问该资源的时候需要使用该锁进行保护。
除了作为一个全局资源来管理的hash table,每个进程也会管理自己分配和释放的timer资源,当然,这也是通过链表进行管理的,链表头在该进程signal descriptor的posix_timers成员中:
struct task_struct {
struct signal_struct {
struct list_head posix_timers;
}
}
一旦进程创建了一个timer,那么就会挂入posix_timers的链表中。
3、如何抽象POSIX timer
在内核中用struct k_itimer 来描述一个POSIX timer:
struct k_itimer {
struct list_head list; --------------------------(1)
struct hlist_node t_hash;
spinlock_t it_lock; -----保护本数据结构的spin lock
clockid_t it_clock;----------------------------(2)
timer_t it_id;
int it_overrun; -----------------------------(3)
int it_overrun_last;
int it_requeue_pending; -------------------------(4)
#define REQUEUE_PENDING 1
int it_sigev_notify; ----------------------------(5)
struct signal_struct *it_signal; ----该timer对应的signal descriptor
union { ---------------------------------(6)
struct pid *it_pid; /* pid of process to send signal to */
struct task_struct *it_process; /* for clock_nanosleep */
};
struct sigqueue *sigq; ---超期后,该sigquue成员会挂入signal pending队列
union { ---------------------------------(7)
struct {
struct hrtimer timer;
ktime_t interval;
} real;
struct cpu_timer_list cpu;
struct {
unsigned int clock;
unsigned int node;
unsigned long incr;
unsigned long expires;
} mmtimer;
struct {
struct alarm alarmtimer;
ktime_t interval;
} alarm;
struct rcu_head rcu;
} it;
};
(1)这两个成员都是和POSIX timer的组织有关。t_hash是链接入全局hash table的节点,而list成员是和进程管理自己创建和释放timer的链表相关。
(2)这两个成员描述了POSIX timer的基本信息的。任何一个timer都是基于clock而构建的,it_clock说明该timer是以系统中哪一个clock为标准来计算超时时间。it_id描述了该timer的ID,在一个进程中唯一标识该timer。
(3)理解这两个成员首先对timer overrun的概念要理解。对overrun的解释我们可以用信号异步通知的例子来描述(创建进程执行callback函数也是一样的)。假设我们当一个POSIX timer超期后,会发送信号给进程,但是也有可能该信号当前被mask而导致signal handler不会调度执行(当然也有其他的场景导致overrun,这里就不描述了)。这样,我们当然想知道这种timer的overrun的次数。假设一个timer设定超期时间是1秒,那当timer超期后,会产生一个pending的signal,但是由于种种原因,在3秒后,信号被进程捕获到,调用signal handler,这时候overrun的次数就是2次。用户空间可以通过timer_getoverrun来获取这个overrun的次数。
根据POSIX标准,当信号被递交给进程后,timer_getoverrun才会返回该timer ID的overrun count,因此在kernel中需要两个成员,只有信号还没有递交给进程,it_overrun就会不断的累积,一旦完成递交,it_overrun会保存在it_overrun_last成员中,而自己会被清除,准备进行下一次overrun count的计数。因此,实际上timer_getoverrun函数实际上是获取it_overrun_last的数据,代码如下:
SYSCALL_DEFINE1(timer_getoverrun, timer_t, timer_id)
{
……overrun = timr->it_overrun_last;
……return overrun;
}
(4)it_requeue_pending标识了该timer对应信号挂入signal pending的状态。该flag的LSB bit标识该signal已经挂入signal pending队列,其他的bit作为信号的私有数据。下面的代码会更详细的描述。
(5)it_sigev_notify成员说明了timer超期后如何异步通知该进程(线程)。定义如下:
#define SIGEV_SIGNAL 0 -----使用向进程发送信号的方式来通知
#define SIGEV_NONE 1 ------没有异步通知事件,用户空间的程序用轮询的方法
#define SIGEV_THREAD 2 ----异步通知的方式是创建一个新线程来执行callback函数
#define SIGEV_THREAD_ID 4 -----使用向指定线程发送信号的方式来通知
(6)这个成员用来标识进程。Notes:分为进程和线程两种类型。
(7)it这个成员是一个union类型的,用于描述和timer interval相关的信息,不同类型的timer选择使用不同的成员数据。alarm是和alarm timer相关的成员,具体可以参考alarm timer的文档。(mmtimer不知道用在什么场合,可能和Multimedia Timer相关Notes:driver/char/mmtimer.c)。real用于real time clock的场景。real time clock的timer是构建在高精度timer上的(timer成员),而interval则描述该timer的mode,如果是one shot类型的,interval等于0,否则interval描述周期性触发timer的时间间隔。更详细的内容会在本文后面的小节中描述。
三、和POSIX timer相关的系统调用
1、创建timer的系统调用。具体代码如下:
SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock,
struct sigevent __user *, timer_event_spec,
timer_t __user *, created_timer_id)
{
struct k_clock *kc = clockid_to_kclock(which_clock);--根据clock ID获取内核中的struct k_clock
struct k_itimer *new_timer;
int error, new_timer_id;
sigevent_t event;
int it_id_set = IT_ID_NOT_SET;new_timer = alloc_posix_timer();-----分配一个POSIX timer,所有成员被初始化为0
spin_lock_init(&new_timer->it_lock);
new_timer_id = posix_timer_add(new_timer);-----------(1)it_id_set = IT_ID_SET;
new_timer->it_id = (timer_t) new_timer_id;
new_timer->it_clock = which_clock;
new_timer->it_overrun = -1; -------------------(2)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));--------(3)
rcu_read_unlock();
} else {
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));----------(4)
}new_timer->it_sigev_notify = event.sigev_notify;
new_timer->sigq->info.si_signo = event.sigev_signo; --信号ID
new_timer->sigq->info.si_value = event.sigev_value;
new_timer->sigq->info.si_tid = new_timer->it_id; ---信号发送的目的地线程ID
new_timer->sigq->info.si_code = SI_TIMER; -------------(5)if (copy_to_user(created_timer_id,
&new_timer_id, sizeof (new_timer_id))) {-------------(6)
error = -EFAULT;
goto out;
}error = kc->timer_create(new_timer);------调用具体clock的create timer函数
spin_lock_irq(¤t->sighand->siglock);
new_timer->it_signal = current->signal;
list_add(&new_timer->list, ¤t->signal->posix_timers);-------(7)
spin_unlock_irq(¤t->sighand->siglock);return 0;
}
(1)将该timer加入到全局的哈希表中。当然,在加入之前,要分配一个timer ID,内核要确保该timer ID是在本进程内能唯一标识该timer。
(2)初始化该posix timer,设定timer ID,clock ID以及overrun的值。it_id_set这个变量主要用于出错处理,如果其值等于IT_ID_SET,说明已经完成插入全局的哈希表的操作,那么其后的出错处理要有从全局的哈希表中摘除该timer的操作(注意:上面的代码省略了出错处理,有兴趣的读者可以自行阅读)。
(3)good_sigevent这个函数主要是用来进行参数检查。用户空间的程序可以通过sigevent_t的数据结构来控制timer超期之后的行为。例如可以向某一个指定的线程(不是进程)发送信号(sigev_notify设定SIGEV_THREAD_ID并且设定SIGEV_SIGNAL),当然这时候要传递thread ID的信息。内核会根据这个thread ID来寻找对应的struct task_struct,如果找不到,那么说明用户空间传递的参数有问题。如果该thread ID对应的struct task_struct的确存在,那么还需要该thread ID对应的thread和当前thread属于同一个进程。此外,一旦程序打算用signal通知的方式来进行timer超期通知,那么传入的sigev_signo参数必须是一个有效的signal ID。如果这些检查通过,那么good_sigevent返回适当的pid信息。这里有两种场景,一种是指定thread ID,另外一种是发送给当前进程(实际上是返回当前的线程组leader)
(4)如果用户空间的程序没有指定sigevent_t的参数,那么内核的缺省行为是发送SIGALRM给调用线程所属的线程组leader。
(5)初始化信号发送相关的数据结构。SI_TIMER用来标识该信号是由于posix timer而产生的。
(6)将分配的timer ID 拷贝回用户空间
(7)建立posix timer和当前进程signal descriptor的关系(所有线程共享一个signal descriptor)
Notes:如果(3)中从用户空间获取sigevent和it_pid成功,或者(4)中使用缺省值,(5)就将这些值赋给k_itimer,其中si_code为SI_TIMER表示为内核Timer超时发送的信号。
2、获取一个posix timer剩余时间的系统调用,代码如下:
Notes:由timer_id通过lock_timer得到内核定时器k_itimer,由k_itimer的clock类型通过clockid_to_klock得到k_clock,k_clock的成员函数和系统调用对应,此处为timer_get。
SYSCALL_DEFINE2(timer_gettime, timer_t, timer_id,
struct itimerspec __user *, setting)
{
struct itimerspec cur_setting;
struct k_itimer *timr;
struct k_clock *kc;
unsigned long flags;
int ret = 0;timr = lock_timer(timer_id, &flags);--------根据timer ID找到对应的posix timer
kc = clockid_to_kclock(timr->it_clock);------根据clock ID获取内核中的struct k_clock
if (WARN_ON_ONCE(!kc || !kc->timer_get))
ret = -EINVAL;
else
kc->timer_get(timr, &cur_setting); ------调用具体clock的get timer函数unlock_timer(timr, flags);
if (!ret && copy_to_user(setting, &cur_setting, sizeof (cur_setting))) --将结果copy到用户空间
return -EFAULT;return ret;
}
3、timer_getoverrun、timer_settime和timer_delete
这三个系统调用都非常简单,这里就不细述了,有兴趣的读者可以自行阅读。
四、real time clock的timer callback函数
Notes:POSIX三种Timer类型对应的底层技术:hrtimer、cpu_timer_list、alarm。
对于real time base的那些clock(CLOCK_REALTIME、CLOCK_MONOTONIC等),其timer相关的函数都是构建在一个高精度timer的基础上,这个高精度timer就是posix timer中的it.real.timer成员。
1、common_timer_create,代码如下:
static int common_timer_create(struct k_itimer *new_timer)
{
hrtimer_init(&new_timer->it.real.timer, new_timer->it_clock, 0);
return 0;
}
代码很简单,就是初始化了一个高精度timer而已。具体高精度timer的内容可以参考本站其他文档。
2、common_timer_set,代码如下:
common_timer_set(struct k_itimer *timr, int flags,
struct itimerspec *new_setting, struct itimerspec *old_setting)
{
struct hrtimer *timer = &timr->it.real.timer;---获取该posix timer对应的高精度timer
enum hrtimer_mode mode;if (old_setting)
common_timer_get(timr, old_setting); ----获取旧的timer设定,参考下节描述timr->it.real.interval.tv64 = 0; -------初始化interval设定
if (hrtimer_try_to_cancel(timer) < 0)----马上就要进行新的设定了,当然要停掉该高精度timer
return TIMER_RETRY;timr->it_requeue_pending = (timr->it_requeue_pending + 2) &
~REQUEUE_PENDING;
timr->it_overrun_last = 0; ----------------------------(1)if (!new_setting->it_value.tv_sec && !new_setting->it_value.tv_nsec)
return 0; ----如果新设定的时间值等于0的话,那么该函数仅仅是停掉timer并获取old value。mode = flags & TIMER_ABSTIME ? HRTIMER_MODE_ABS : HRTIMER_MODE_REL; --(2)
hrtimer_init(&timr->it.real.timer, timr->it_clock, mode);
timr->it.real.timer.function = posix_timer_fn; -----高精度timer的mode,callback函数设定hrtimer_set_expires(timer, timespec_to_ktime(new_setting->it_value)); --超期时间设定
timr->it.real.interval = timespec_to_ktime(new_setting->it_interval); ----------(3)
if (((timr->it_sigev_notify & ~SIGEV_THREAD_ID) == SIGEV_NONE)) { --------(4)
if (mode == HRTIMER_MODE_REL) {
hrtimer_add_expires(timer, timer->base->get_time());
}
return 0;
}hrtimer_start_expires(timer, mode); ----启动高精度timer
return 0;
}
(1)it_overrun_last实际上是和timer_getoverrun的调用有关。在一个timer触发后到异步通知完成之间可能会产生overrun,但是,一旦重新调用timer_settime之后,上次的overrun count要被清除。it_requeue_pending状态flag中的信号私有数据加一(这个私有数据是[31:1],因此代码中加2),并且清除pending flag。
(2)这里的代码都是对该posix timer对应的高精度timer进行各种设定。该timer的callback函数会在下一章分析
(3)设置interval的值,通过该值可以设定周期性timer,用户空间传入的参数是timespec,需转换成ktime的时间格式
(4)对于轮询类型的posix timer,我们并不会真正启动该timer(插入到高精度timer的红黑树中),而是仅仅为那些设定相对事件的timer配置正确的超期时间值。
3、common_timer_get,代码如下:
static void common_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting)
{
ktime_t now, remaining, iv;
struct hrtimer *timer = &timr->it.real.timer;memset(cur_setting, 0, sizeof(struct itimerspec));
iv = timr->it.real.interval; ---获取该posix timer对应的timer period值
if (iv.tv64)---------------------------------(1)
cur_setting->it_interval = ktime_to_timespec(iv);---interval timer需返回timer period
else if (!hrtimer_active(timer) &&
(timr->it_sigev_notify & ~SIGEV_THREAD_ID) != SIGEV_NONE)-------(2)
return;now = timer->base->get_time(); -----------------------(3)
if (iv.tv64 && (timr->it_requeue_pending & REQUEUE_PENDING ||
(timr->it_sigev_notify & ~SIGEV_THREAD_ID) == SIGEV_NONE))
timr->it_overrun += (unsigned int) hrtimer_forward(timer, now, iv); --------(4)remaining = ktime_sub(hrtimer_get_expires(timer), now); ---计算剩余时间
if (remaining.tv64 <= 0) { --已经超期
if ((timr->it_sigev_notify & ~SIGEV_THREAD_ID) != SIGEV_NONE)
cur_setting->it_value.tv_nsec = 1; --------------------(5)
} else
cur_setting->it_value = ktime_to_timespec(remaining); ---返回剩余时间信息
}
(1)posix timer的时间设定用struct itimerspec表示:
struct itimerspec {
struct timespec it_interval; /* timer period */
struct timespec it_value; /* timer expiration */
};
如果it_interval等于0的话,那么说明该posix timer是一个one shot类型的timer。如果非零的话,则说明该timer是一个periodic timer(或者称之为interval timer),it_interval定义了周期性触发的时间值。这个timer period值对应内核struct k_itimer中的it.real.interval成员。
(2)如果是one shot类型的timer,it_interval返回0值就OK了,我们只需要设定it_value值。对于通过信号进行异步通知的posix timer,如果对应的高精度timer已经不是active状态了,那么it_value值也是0,表示该timer已经触发了。
(3)获取当前时间点的值。不论timer当初是如何设定的:相对或者绝对,it_value总是返回相对于当前时间点的值,因此这里需要获取当前时间点的值。
(4)对于一个周期性触发的timer,并且设定SIGEV_NONE,实际上,该timer是不会触发的,都是用户程序自己调用timer_gettime来轮询情况,因此在get time函数中处理超期后,再次设定高精度timer的任务,同时计算overrun次数。
如果periodic timer设定信号异步通知的方式,那么在信号pending到信号投递到进程这段时间内,虽然由于各种情况可能导致这段时间很长,按理periodic timer应该多次触发,但是实际上,信号只有在投递到进程后才会再次restart高精度timer,因此在信号pending期间,如果用户调用了timer_gettime,也需要自己处理timer的超期以及overrun。
(5)TODO。
4、common_timer_del。比较简单,不再赘述。
五、和posix timer相关的信号处理
1、发送什么信号?发向哪一个进程或者线程?
用户空间的程序可以通过timer_create函数来创建timer,在创建timer的时候就设定了异步通知的方式(SIGEV_SIGNAL、SIGEV_NONE和SIGEV_THREAD),SIGEV_NONE方式比较简单,没有异步通知,用户空间的程序自己需要调用timer_gettime来轮询是否超期。SIGEV_THREAD则是创建一个线程来执行callback函数。我们这一章的场景主要描述的就是设定为SIGEV_SIGNAL方式,也就是timer超期后,发送信号来异步通知。缺省是发送给创建timer的进程,当然,也可以设定SIGEV_THREAD_ID的标识,发给一个该进程内的特定的线程。
一个指定进程的timer超期后,产生的信号会挂入该进程(线程)pending队列,需要注意的是:在任意的时刻,特定timer的信号只会挂入一次,也就是说,该信号产生到该信号被投递到进程之间,如果timer又一次超期触发了,这时候,signal pending队列不会再次挂入信号(即便该signal是一个real-time signal),只会增加overrun的次数。
2、信号的产生
Notes:POSIX hrtimer的超时函数是posix_timer_fn,posix_timer_fn-->posix_timer_event-->send_sigqueue,send_sigqueue进行具体的操作。
在set timer函数中,内核会设定高精度timer的超期回调函数为posix_timer_fn,代码如下:
static enum hrtimer_restart posix_timer_fn(struct hrtimer *timer)
{
struct k_itimer *timr;
unsigned long flags;
int si_private = 0;
enum hrtimer_restart ret = HRTIMER_NORESTART; -----------(1)timr = container_of(timer, struct k_itimer, it.real.timer);-----------(2)
spin_lock_irqsave(&timr->it_lock, flags);if (timr->it.real.interval.tv64 != 0)
si_private = ++timr->it_requeue_pending; ---------------(3)if (posix_timer_event(timr, si_private)) { -----------------(4)
如果该signal的handler设定是ignor,那么需要对interval类型的timer做特别处理
}
}unlock_timer(timr, flags);
return ret;
}
(1)高精度timer的超期callback函数的返回值标识了是否需要再次将该timer挂入队列,以便可以再次触发timer。对于one shot类型的,需要返回HRTIMER_NORESTART,对于periodic timer,需要返回HRTIMER_RESTART。缺省设定不再次start该timer。
(2)POSIX timer对应的高精度timer是嵌入到k_itimer数据结构中的,通过container_of可以获取该高精度timer对应的那个k_itimer数据。
(3)对于one shot类型的timer,不存在signal requeue的问题。对于周期性timer,有可能会有overrun的问题,这时候,需要传递一个signal的私有数据,以便在queue signal的时候进行标识。++timr->it_requeue_pending用来标记该timer处于pending状态(加一就是将LSB设定为1)
(4)具体将信号挂入进程(线程)signal pending队列的操作在posix_timer_event函数中,该函数会调用send_sigqueue函数进行具体操作。如下:
int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
{……ret = 0;
if (unlikely(!list_empty(&q->list))) {--------是否已经挂入signal pending队列?
q->info.si_overrun++;------------如果是,那么增加overrun counter就OK了
return ret;
}
q->info.si_overrun = 0; ------首次挂入signal pending队列,初始化overrun counter等于0
pending = group ? &t->signal->shared_pending : &t->pending;-挂入进程的还是线程的pending队列
list_add_tail(&q->list, &pending->list);----挂入pending队列
sigaddset(&pending->signal, sig);------设定具体哪一个signal pending
complete_signal(sig, t, group);-------设定TIF_SIGPENDING标记
……
}
如果信号已经正确的产生了,挂入进程或者线程的signal pending队列(也有可能是仅仅增加overrun的计数),或者处理过程中发生了错误,posix_timer_event返回False,这时候整个处理就结束了。如果返回TRUE,说明该signal被进程ignor了。这时候需要一些特殊的处理。
相信大家已经注意到了,default的情况下,该高精度timer的callback返回HRTIMER_NORESTART,即便是periodic timer也是如此,难道periodic timer不需要restart高精度timer吗?当然需要,只不过不是在这里,在投递信号的时候会处理的,具体可以参考dequeue_signal的处理。然而,如果一个periodic timer的信号处理是ignor类型的,那么信号是不会挂入pending队列的,这时候不会有信号的投递,不会调用dequeue_signal,这时候则需要在这个callback函数中处理的。这时候会设定下一个超期时间,并返回HRTIMER_RESTART,让高精度timer有机会重新挂入高精度timer的红黑树中。
3、信号投递到进程
timer超期后会产生一个信号(配置了SIGEV_SIGNAL),这个信号虽然产生了,但是具体在什么时间点被投递到进程并执行signal处理函数呢?在ARM中断处理过程文档中,我们给出了一个场景(另外一个场景是系统调用返回用户空间,这里略过不表,思路是类似的),在返回用户空间之前,中断处理代码会检查struct thread_info中的flag标记,看看是否有_TIF_WORK_MASK的设定:
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)
如果任何一个bit有设定,那么就会调用do_work_pending来处理,如果设定了_TIF_SIGPENDING,那么就调用do_signal来处理信号,属于当前进程的pending signal会被一一处理,首先调用dequeue_signal,从队列中取出信号,然后调用signal handler执行。相关的dequeue_signal代码如下:
int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
{……
if ((info->si_code & __SI_MASK) == __SI_TIMER && info->si_sys_private) {
spin_unlock(&tsk->sighand->siglock);
do_schedule_next_timer(info);
spin_lock(&tsk->sighand->siglock);
}
return signr;
}
如果你想通过发生信号的方式进行异步通知,那么必须要设定si_code为SI_TIMER。对于real time的clock,do_schedule_next_timer函数会调用schedule_next_timer来处理periodic timer的restart:
static void schedule_next_timer(struct k_itimer *timr)
{
struct hrtimer *timer = &timr->it.real.timer;if (timr->it.real.interval.tv64 == 0)---one shot类型的,直接退出
return;timr->it_overrun += (unsigned int) hrtimer_forward(timer,---设定下次超期时间并计算overrun次数
timer->base->get_time(),
timr->it.real.interval);timr->it_overrun_last = timr->it_overrun;---保存该timer的overrun次数
timr->it_overrun = -1;----为下次初始化overrun
++timr->it_requeue_pending;------清除pending标记并增加信号私有数据域
hrtimer_restart(timer);----restart该timer
}
原创文章,转发请注明出处。蜗窝科技
http://www.wowotech.net/timer_subsystem/posix-timer.html
Linux时间子系统之(六):POSIX timer的更多相关文章
- Linux时间子系统(十七) ARM generic timer驱动代码分析
一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单 ...
- Linux时间子系统(十六) clockevent
一.clock event控制的通用逻辑 1.产生clock event的设备 各种系统的timer硬件形形色色,不过在general clock event device layer,struct ...
- Linux时间子系统之(五):POSIX Clock
专题文档汇总目录 Notes: 本章主要介绍了若干种类的静态时钟,这些时钟都可以通过k_clock表示,注册到posix_clocks中.这些都是静态时钟,可以分为三大类:各种REALTIME时钟.带 ...
- Linux时间子系统之(十六):clockevent
专题文档汇总目录 Notes:介绍struct clocke_event_device及其功能feature.模式:触发event接口clockevents_program_event:clockev ...
- Linux时间子系统之(十七):ARM generic timer驱动代码分析
专题文档汇总目录 Notes:ARM平台Clock/Timer架构:System counter.Timer以及两者之间关系:Per cpu timer通过CP15访问,System counter通 ...
- Linux时间子系统专题汇总
关于Linux时间子系统有两个系列文章讲的非常好,分别是WowoTech和DroidPhone. 还有两本书分别是介绍: Linux用户空间时间子系统<Linux/UNIX系统编程手册>的 ...
- Linux时间子系统之(二):软件架构
专题文档汇总目录 Notes:从框架上讲解了时间子系统,从底向上包括CPU Local TImer.Global Counter.Clock Souce/Clock Events模块管理.Tick D ...
- Linux时间子系统之(三):用户空间接口函数
专题文档汇总目录 Notes:用户空间时间相关接口函数: 类型 API 精度 说明 时间 time stime time_t 精度为秒级 逐渐要被淘汰.需要定义__ARCH_WANT_SYS_TIME ...
- Linux时间子系统之(四):timekeeping
专题文档汇总目录 Notes:timekeeping模块的狠心数据结构是timekeeper,它维护了系统不同类型时钟的时间值,并且介绍了获取不同类型时钟时间的函数. clocksource切换通过c ...
随机推荐
- saiku应用的调试
ubuntu下解压saiku包后使用: 运行.sh命令(.bat是windows命令).运行时注意权限.可以先chmod a+x *.sh 提示,catali?.sh出错. 这是tomcat的一个文件 ...
- Linux变量键盘读取、数组与声明: read, array, declare
[root@www ~]# read [-pt] variable 选项与参数: -p :后面可以接提示字符! -t :后面可以接等待的『秒数!』这个比较有趣-不会一直等待使用者啦! 范例一:让用户由 ...
- cocoa编程第4版 8.5 挑战1 解答
看似简单,其实也很简单,但开始思路想错了:还上网查了一下,有网友说是将Array Controller的Keys中的personName改为personName.length,好像完全不起作用. 后来 ...
- 实现去哪儿来回机票选择的view
最近有个控件是实现和去哪儿和阿里旅行的app的选择日历效果,反编译没有效果的情况下我自己实现了个,大致的原理是: 上面是产品需要实现的效果,我看了下不就是一个ListView+gridView就能实现 ...
- C语言算法---求鞍点
题目:有一个3X4矩阵,要求输出其鞍点(行列均最大的值),以及它的行号和列号. int a[3][4] = {{123,94,-10,218}, {3 ...
- LeetCode刷题之合并排序链表
合并两个有序链表并返回一个新的列表.新列表应该由连接在一起的节点前两个列表 给定实例:Input: 1->2->4, 1->3->4Output: 1->1->2- ...
- 学习Selenium遇到的问题和解决方案
问题1:IE驱动位数问题,未安装对应的IE,打不开IE浏览器(已解决20180323) 使用Selenium启动IE浏览器的时候,报错,报错信息如下 org.openqa.selenium.remot ...
- js定义数组的方法
1.定义时赋值 var mycars=new Array("a","b","c") 2.new一个数组对象 var mycars=new A ...
- Android流媒体开发之路二:NDK开发Android端RTMP直播推流程序
NDK开发Android端RTMP直播推流程序 经过一番折腾,成功把RTMP直播推流代码,通过NDK交叉编译的方式,移植到了Android下,从而实现了Android端采集摄像头和麦克缝数据,然后进行 ...
- 每天几分钟跟小猫学前端之node系列:用node实现最简单的爬虫
先来段求分小视频: https://www.iesdouyin.com/share/video/6550631947750608142/?region=CN&mid=6550632036246 ...