• Linux时间子系统之四:Timer在用户和内核空间流程


    用户空间应用中创建一个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 ptlsysdepsunixsysvlinux imer_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);--------------------------------修改方法
    ...
    }
  • 相关阅读:
    有关 PHP 和 MySQL 时区的一点总结
    PHP CLI模式下的多进程应用
    Linux编程之:五个常见PHP数据库问题
    用php定制404错误页面 并发信通知管理员
    配置PHP站点安全综合教程
    新手必看的PHP学习入门的一些基础知识
    彻底杜绝PHP的session cookie错误
    专家预言:PHP将比Java更受开发人员欢迎
    PHP企业级应用之WebService续篇
    清除 数据库 日志 以 Db_Test 为例
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/7929611.html
Copyright © 2020-2023  润新知