Linux 内核学习(7) - 时间系统

Linux Kernel的时钟

Jiffies

  • 背景:从时钟中断说起

    • Linux系统有一个时钟中断,被称为tick,用来做进程调度,周期为Hz
    • Jiffies,就是tick的计数,每一个时钟中断发生时被加1
    • Jiffies可以用来做计时,以系统启动时间为起点,粒度为一个tick
  • 为了尽可能减少jiffies环绕,jiffies定义为64位,在32位上读写就不是一个原子操作。用顺序锁来避免竞争

    • 读侧

      u64 get_jiffies_64(void) {
          unsigned long seq;
          u64 ret;
          do {
              seq = read_seqbegin(&jiffies_lock);
              ret = jiffies_64;
          } while (read_seqretry(&jiffies_lock, req));
          return ret;
      }
      
    • 更新侧

      void xtime_update(unsigned long ticks) {
          write_seqlock(&jiffies_lock);
          do_timer(ticks);
          write_sequnlock(&jiffies_lock);
          update_wall_time();
      }
      
  • 顺序锁

    • 类似于读写锁,用于读很频繁写较少的情况

    • 用于读者用来获得一致的数据

    • 更新端需要同步多个写者

    • 特别需要注意的是,读端需要自行注意数据的有效性。比如访问一个指针,读写锁无法保护指针的合法性

    • API

      • 初始化:SEQCOUNT_DEP_MAP_INIT(), seqcount_init()

      • 读者:

        do {
            x = read_seqcount_begin(lock);
            read_something();
        } while(read_seqcount_retry(lock, x));
        
      • 写者:

        write_seqcount_begin(lock);
        write_something();
        write_seqcount_end(lock);
        
      • 更新侧自带锁同步的API

        • 带有spinlock: seqlock_init(),seqlock_init(),read_seqretry(),write_seqlock(),write_sequnlock()
        • 带有spinlock_bh:写者API变成write_seqlock_bh()/write_sequnlock_bh()
        • 带有spinlock_irq
    • 顺序锁的实现:简化版本

      write_seqcount_begin(lock) {
          lock->sequence++; // 开始写变成奇数
          smb_wmb();  // 写屏障(疑似smp_wmb?)
      }
      
      write_seqcount_end(lock) {
          smb_wmb();
          return lock->sequence++; // 写结束后变成偶数
      }
      
      read_seqcount_begin(lock) {
          repeat:
              ret = ACCESS_ONCE(lock->sequence);
              if (ret & 1) { // 如果是奇数表示正在写
                  cpu_relax(); // 就让CPU等待
                  goto repeat;
              }
              smb_rmb();
              return ret;
      }
      
      read_seqcount_retry(lock, begin) {
          smb_rmb();
          return lock->sequence != begin;
      }
      
  • tickless

    • CONFIG_NO_HZ_IDLE
      如果CPU是空闲的,则不在此CPU上产生tick,主要目的是省电
    • CONFIG_NO_HZ_FULL
      如果CPU上只有一个任务,则不需要时钟中断来打断这个CPU,因为没有其他的任务需要调度,用于HPC(高性能计算)的场景。

墙上时间 xtime

  • xtime表示现实生活中的时间,实际计时之用
    • gettimeofday();
    • 文件系统访问时间戳
  • 从cmos中得到初始值,每个tick也将此值更新
  • ntp也会更新时间

定时器

timer

  • 普通定时器
  • APIs
    • 初始化
      • TIMER_INITIALIZER(_function, _expires, _data)
      • setup_timer(timer, fn, data)
    • 更新timer到期时间
      • mod_timer(timer, expires)
    • 把timer加到运行队列
      • add_timer(timer)
    • 撤销timer
      • del_timer(timer)
      • 撤销timer并等待callback运行完成 del_timer_sync()

hrtimer

  • 基于高精度的外部硬件

  • 提供高精度的延迟,纳秒级

  • 基本APIs

    • 初始化:hrtimer_init(timer, which_clock, mode)
      which_clock: 定义定时器所使用的时钟

      • CLOCK_MONOTONIC: 单调递增的时钟
      • CLOCK_REALTIME: 墙上时钟,在系统时间更改时,此时间可能倒退

      mode:

      • HRTIMER_MODE_ABS 提供的超时时间是绝对值
      • HRTIMER_MODE_REL 提供的超时时间是相对值
    • 指定hrtimer的回调函数

      • hrtimer->function()
      • 如果该函数返回HRTIMER_RESTART表示需要再次被触发,否则返回HRTIMER_NORESTART
    • 启动hrtimer

      • hrtimer_start(timer, tim, mode)

        tim 指定的时间

        mode

        • HRTIMER_MODE_ABS 相对时间
        • HRTIMER_MODE_REL 相对时间
    • 取消hrtimer

      • hrtimer_cancel(hrtimer)
  • 实现

    • 在每个CPU上面,每个时钟都有一个红黑树,每个hrtimer都按照超时时间为key插入到该树
    • 在hrtimer初始化的时候,将该hrtimer关联到对应的时钟上 (__hrtimer_init(struct hrtimer *timer, clockid_t clock_id, ..))
    • hrtimer start的时候,先将相对时间转换为绝对时间,然后插入到红黑树中
    • 如果新插入的hrtimer是最左边的节点,即它的超时时间是最小的,配置clock device的触发事件为最低的超时时间
    • hrtimer到期后的处理

itimer

  • interval timer,间隔定时器,用于用户空间的高精度定时

  • itimer有3个时钟,分别为

    • ITIMER_REAL: 真实时钟
    • ITIMER_VIRTUAL: 进程实际运行时间计时
    • ITIMER_PROF:进程实际运行时间+进程在内核态的运行时间
  • 触发信号

    • ITIMER_REAL:触发SIGALRM
    • ITIMER_VIRTUAL:触发SIGVTALRM
    • ITIMER_PROF:触发SIGPROF
  • 用户空间接口(封装在glibc中)

    • 设置itimer

      int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
          
      struct itimerval {
          struct timeval it_interval; // next value
          struct timeval it_value;    // current value
      };
      
    • 查看itimer

      int getitimer(int which, struct itimerval *curr_value)
      // 获得指定时钟上的itimer信息
      
  • 实现原理

    • ITIMER_REAL

      • 对于ITIMER_REAL,kernel中直接用hrtimer来实现
        在每次fork一个新进程的时候,会初始化real_timer

      • setitimer系统调用处理的时候,启用该timer (调用hrtimer_start)

        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);
        } else
            tsk->signal->it_real_incr.tv64 = 0;
        
      • 超时后的处理

        /*
         * 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);
        
        	kill_pid_info(SIGALRM, SEND_SIG_PRIV, sig->leader_pid);
        
        	return HRTIMER_NORESTART;
        }
        
      • 重启itimer

        if (unlikely(signr == SIGALRM)) {
            struct hrtimer *tmr = &tsk->signal->real_timer;
        
            if (!hrtimer_is_queued(tmr) &&
                tsk->signal->it_real_incr.tv64 != 0) {
                hrtimer_forward(tmr, tmr->base->get_time(),
                                tsk->signal->it_real_incr);
                hrtimer_restart(tmr);
            }
        }
        
        • hrtimer_forward() 语句用来计算相对于当前时钟的绝对超时时间
        • hrtimer_restart() 重启计时器,以ABS的方式
    • ITIMER_VIRTUALITIMER_PROF

      • 在每一个tick发生的时候都对当前进程进行统计,如果tick发生时,进程运行在用户空间(CPL==3),则更新utime,否则更新systime。ITIMER_PROF统计的是utime+systime

      • 在tick发生时的处理

        static void tick_periodic(int cpu)
        {
        	if (tick_do_timer_cpu == cpu) {
        		write_seqlock(&xtime_lock);
        
        		/* Keep track of the next tick event */
        		tick_next_period = ktime_add(tick_next_period, tick_period);
        
        		do_timer(1);
        		write_sequnlock(&xtime_lock);
        	}
        
        	update_process_times(user_mode(get_irq_regs()));
        	profile_tick(CPU_PROFILING);
        }
        
      • 判断是否在用户空间:user_mode(get_irq_regs(),它根据压入堆栈的CS判断

      • account_user_time()为例

        void account_user_time(struct task_struct *p, cputime_t cputime, cputime_t cputime_scaled) {
            ...
        }
        
      • 超时的判断 void check_process_timers(tsk, firing)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值