34

Linux时间子系统之:POSIX timer

 5 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI3NzA5MzUxNA%3D%3D&%3Bmid=2664606415&%3Bidx=1&%3Bsn=ca2fa6d35d432fd25927b1dbf701d890&%3Butm_source=tuicool&%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、前言

在用户空间接口函数文档中,我们描述了和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

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

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 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)这个成员用来标识进程。

(7)it这个成员是一个union类型的,用于描述和timer interval相关的信息,不同类型的timer选择使用不同的成员数据。alarm是和alarm timer相关的成员,具体可以参考alarm timer的文档。(mmtimer不知道用在什么场合,可能和Multimedia Timer相关)。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)

2、获取一个posix timer剩余时间的系统调用,代码如下:

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函数

对于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、信号的产生

在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  }

本文转自“蜗窝科技”


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK