《LKD3粗读笔记》(11)定时器和时间管理

这篇具有很好参考价值的文章主要介绍了《LKD3粗读笔记》(11)定时器和时间管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、内核中的时间概念

  • 硬件为内核提供了一个系统定时器用以计算流逝的时间,该时钟在内核中可看成是一个电子时间资源,比如数字时钟或处理器频率等。
  • 系统定时器以某种频率自行触发(常被称为击中(hitting)或者射中(popping))时钟中断,该频率可以通过编程预定,称作节拍率(tick rate)。
  • 内核维护两种时间:墙上时间系统运行时间
  • 什么是墙上时间?
    墙上时间也就是实际时间,对用户空间的应用程序来说是最重要的。
  • 什么是系统运行时间?
    系统运行时间也就是自系统启动开始所经的时间,对用户空间和内核都很有用。
  • 这两种时间内核如何维护计算?
    通过预编的节拍率可以知道连续两次时钟中断的间隔时间(节拍(tick))。 这个间隔时间等于节拍率分之一(1/(tick rate))秒。
  • 利用时钟中断周期执行的任务有哪些?
    注意:有些工作随时钟频率反复执行,有些是n个时钟中断执行一次。
    • 更新系统运行时间
    • 更新实际时间
    • 在SMP上,均衡调度程序尽量使运行队列负载均衡
    • 检查当前进程是否用尽了自己的时间片
    • 运行超时的动态定时器
    • 更新资源消耗和处理器时间统计值
  • 言下之意,还有一些工作不是周期执行的,但依旧需要使用时间管理资源。

2、 节拍率:HZ

  • 系统定时器频率(节拍率)通过静态预处理定义,也就是HZ(赫兹),在asm/param.h文件中定义了这个值。与体系结构有关。
  • 例如:在x86体系结构中,系统定时器频率默认为100。因此,x86上时钟中断的频率就是100HZ,也就是说每秒时钟中断100次,即每10ms中断一次
  • HZ值不是一个固定不变的值,大多数体系结构的节拍率是可调的
  • 下面是各种体系结构与之对应的时钟中断频率
    《LKD3粗读笔记》(11)定时器和时间管理
  • 高HZ值的优势是什么?
    • 内核定时器能够以更高的频度更高的准确度运行;
    • 依赖定时值执行的系统调用,比如poll()select(),能够以更高的精度运行;
    • 提高进程抢占的准确度
  • 高HZ值的劣势是什么?
    • 系统负担变重,中断处理程序占用处理器时间越多,处理其他任务时间减少
    • 频繁打乱处理器高速缓存,增加耗电
  • 无节拍的OS
    • Linux内核支持“无节拍操作”选项。编译内核时设置了CONFIG_HZ配置选项,系统根据这个选项动态调度时钟中断,不是固定间隔。
    • 例如:如果50ms内无事可做,内核以50ms重新调度时钟中断。

3、jiffies

  • 全局变量jiffies用来记录自系统启动以来产生的节拍的总数。
  • jiffies定义于文件<linux/jiffies.h>中,存放jiffies类型数据的时候必须用无符号长整型(unsigned long):
    extern unsigned long volatile jiffies;
    
  • 将以秒为单位的时间转化为jiffies
    (seconds * HZ)
    
  • jiffies转化为以秒为单位的时间:
    (jiffies/HZ)
    
  • 设置将来的时间:
    unsigned long time_stamp = jiffies; 		/*现在*/
    unsigned long next_tick = jiffies+1;		/*从现在开始1个节拍*/
    unsigned long later = jiffies+5*HZ;			/*从现在开始5秒*/
    unsigned long fraction = jiffies + HZ / 10;	/*从现在开始1/10秒*/
    
  1. jiffies的内部表示
    • jiffies变量总是unsigned long类型,在32位上是32位,在64位上是64位。因为32位jiffies会溢出,所以需要引入64位的jiffies_64
    • jiffies_64定义在<linux/jiffies.h>中:
      extern u64 jiffies_64;
      jiffies = jiffies_64; //非常巧妙
      
      这样一来,在32位机器中,访问jiffies等价于访问jiffies_64的低32位;而在64位机器中,访问jiffies则等价于访问jiffies_64
    • 下面是jiffiesjiffies_64的划分,对照前一句话进行理解。
      《LKD3粗读笔记》(11)定时器和时间管理
  2. jiffies的回绕
    • 什么是回绕?
      和任何C整型一样,当jiffies变量的值超过它的最大存放范围后就会发生溢出。即:对于32位无符号长整型,最大取值为2^32-1。所以在溢出前,定时器节拍计数最大为4294967295。当节拍计数达到了最大值后还要继续增加,那jiffies的值会回绕到0
    • 内核提供如下宏来帮助比较节拍计数,以便能够正确处理节拍计数回绕情况,定义在文件<linux/jiffies.h>中。简化版如下:
      #define time_after(unknown,known) ((long)(known) - (long)(unknown) < 0)
      #define time_before(unknown,known) ((long)(unknown) - (long)(known) < 0)
      #define time_after_eq(unknown,known) ((long)(unknown) - (long)(known) >= 0)
      #define time_before_eq(unknown,known) ((long)(known) - (long)(unknown) >= 0)
      
      其中unknown参数通常是jiffiesknown参数是需要对比的值。
    • 一个回绕的例子
      unsigned long timeout = jiffies + HZ/2; /*0.5秒后超时*/
      //if(timeout > jiffies){//没有使用正确处理回绕的宏,那么就会发生错误
      if(time_before(jiffies,timeout)){ //使用了正确处理回绕的宏
      	/*没有超时,很好*/
      }else{
      	/*超时了,发生错误*/
      }
      
  3. 用户空间和HZ
    • 在Linux 2.6以前的版本中,改变内核的HZ值会影响用户空间的某些程序。
    • USER_HZ代表用户空间看到的HZ,用 jiffies_to_clock_t()HZ转换成USER_HZ表示的节拍数。

4、硬时钟和定时器

  1. RTC
    • 什么是实时时钟(RTC)?
      实时时钟是用来持久存放系统时间的设备,即便系统关闭后,它也可以靠主板上的微型电池提供的电力保持系统的计时
    • RTC对内核的作用是什么?
      当系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime变量中。
  2. 系统定时器
    • 系统定时器的根本思想是提供一种周期性触发中断机制
    • 有对晶振分频实现定时器的,也有衰减值定时的。
    • x86中主要采用可编程中断时钟(PIT),还有其他的时钟资源如本地APIC时钟和时间戳计数(TSC)等。

5、时钟中断处理程序

  • 时钟中断处理程序可以划分哪两个部分?
    体系结构相关体系结构无关部分。
  • 与体系结构相关的部分做哪些工作?
    与体系结构相关的部分作为系统定时器中断处理程序而注册到内核中,以便在产生时钟中断时,它能够相应的运行。处理程序的具体工作依赖于特定的体系结构,最少需要执行以下操作:
    • 获得xtime_lock锁,以便对访问jiffies_64和墙上时间xtime进行保护;
    • 需要时应答或重新设置系统时钟
    • 周期性地使用墙上时间更新实时时钟
    • 调用体系结构无关的时钟部分:tick_perodic()
  • tick_perodic()函数做什么工作?
    • jiffies_64变量增加1(这个操作即使是在32位体系结构上也是安全的,因为前面已经获得了xtime_lock锁);
    • 更新资源消耗的统计值,比如当前进程所消耗的系统时间用户时间
    • 执行已经到期的动态定时器
    • 执行sheduler_tick()函数(负责减少当前运行进程的时间片计数值并且设置need_resched标志);
    • 更新墙上时间,该时间存放在xtime变量中;
    • 计算平均负载值。
      注意:以上全部工作每1/HZ秒都要发生一次,可以联想STM32的中断服务程序

6、实际时间

  • 实际时间(墙上时间)的定义
    • 定义在文件kernel/time/timekeeping.c中:
      struct timespec xtime;
      
    • timespec数据结构定义在文件<linux/time.h>中,形式如下:
      struct timespec{
      	_kernel_time_t tv_sec;		/* 以s为单位,存放着自1970年1月1日(UTC)以来经过的时间,1970年1月1日被称为纪元。*/
      	long tv_nsec;				/* 记录自上一秒开始经过的ns数 */
      };
      
  • 读写xtime变量需要有哪些操作?
    • xtime首先需要申请一个seqlock锁:
      write_seqlock(&xtime_lock);
      /*更新xtime*/
      write_unseqlock(&xtime_lock);
      
    • xtime也要使用read_seqbegin()read_seqretry()函数
      /* 顺序锁中读锁来循环获取 xtime,直至读取过程中 xtime 没有被改变过 */
          do {
              seq = read_seqbegin(&xtime_lock);
      
              *ts = xtime;
              nsecs = timekeeping_get_ns();
      
              /* If arch requires, add in gettimeoffset() */
              nsecs += arch_gettimeoffset();
      
          } while (read_seqretry(&xtime_lock, seq));
      /* 省略 。。。。 */
      }
      
      该循环不断重复,如果发现循环期间有时间中断处理程序更新xtime,那么read_seqretry()函数就返回无效序列号,继续循环等待。
    • 关注一下使用顺序锁而非普通锁的原因。
      • 写时间的进程少,读时间的进程多。
      • 希望写进程优先于读进程(显而易见,写时间延迟的话会造成很大问题),而且不允许读者让写着饥饿。
  • 如何从用户空间得到墙上时间?
    从用户空间取得墙上时间的主要接口是gettimeofday(),在内核中对应的系统调用是sys_gettimeofday(),定义于kernel/time.c中。它实现的逻辑是:
    • 如果用户提供的tv参数非空,那么与体系结构相关的do_gettimeofday()函数将被调用;
    • 如果tz参数为空,该函数就把系统时区返回用户。
  • 用户如何设置当前时间?
    • 使用系统调用settimeofday()

7、定时器

  • 什么是定时器?
    定时器也称作为动态定时器内核定时器,它是管理内核流逝时间的基础
  • 定时器有什么作用?
    例如:内核经常需要推后执行某些代码,而使用定时器就可以执行推后执行的工作。
  • 如何使用定时器?
    • 分为四步:1. 执行初始化工作;2. 设置超时时间;3. 指定超时发生执行的函数;4.激活定时器。
    • 注意:指定的函数会在定时器到期自动执行,定时器不周期运行,在超时后就自行撤销
  1. 使用定时器

    • 定时器的定义
      定时器结构由timer_list 表示,在linux/timer.h中。
      struct timer_list {
      	struct list_head entry; 		 /* 定时器链表的入口 */
      	unsigned long expires; 			 /* 以jiffies为单位的定时值 */
      	void (*function)(unsigned long); /* 定时器处理函数 */
      	unsigned long data; 			 /* 传递给处理函数的参数 */
      	struct tvec_t_base_s *base; 	 /* 定时器内部值,用户不要使用 */
      };
      
    • 定时器相关的接口
      与定时器相关的接口,声明在linux/timer.h,实现在kernel/timer.c, 需要注意的是,内核可能延误一个节拍才执行处理函数,所有任何硬实时任务都不能用它。
      • 定义定时器结构
        struct timer_list my_timer;
        
      • 初始化定时器数据结构的内部值
        init_timer(&my_timer);
        
      • 填充数据
        my_timer.expires = jiffies + delay; /* 定时器超时时间节拍数 */
        my_timer.data = 0; 					/* 该参数可使用户利用同一个处理函数注册多个定时器,不需要则传递0或其他任何值 */
        my_timer.function = my_function; 	/* 定时器超时处理函数 */
        
      • 激活定时器
        add_timer(&my_timer);
        
      • 更改定时器超时时间
        // 改变超时时间,如果定时器没被激活,则自动激活。之前未激活返回0 ,激活返回1
        mod_timer(&my_timer, jiffies + new_delay); /* 新的定时值 */
        
      • 在定时器超时之前删除定时器
        // 在还未超时时可以删除定时器,
        // 激活未激活的都行(未激活返回0,否则返回1),但是超时就不用了因为已经自动删除了
        del_timer(&my_timer); 	/* 能在中断上下文使用 */
        
        // 删除定时器存在潜在竞争条件,删除定时器时可能要等待其他处理器上运行的定时器处理程序都退出
        // 该函数不能在中断上下文中使用,因为它会造成睡眠。
        del_timer_sync(&my_timer); /* 不能在中断上下文中使用,但优先使用*/
        
  2. 定时器竞争条件

    • 定时器与当前执行代码是异步的,可能存在潜在的竞争条件。以删除定时器为例,最好使用del_timer_sync()而非del_timer()
    • 还要着重保护定时器中断处理程序中的共享数据
  3. 实现定时器

    • 什么时候执行定时器?
      内核在时钟中断发生后执行定时器,定时器作为软中断在下半部上下文中执行。
    • 执行定时器的步骤是什么?
      时钟中断处理程序会执行update_process_times(),然后调用run_local_timers()run_local_timers函数处理软中断TIMER_SOFTIRQ,从而在当前处理器上运行所有的超时定时器。
      void run_local_timers(void)
      {
      	hrtimer_run_queues();
      	raise_softirq(TIMER_SOFTIRQ); /* 执行定时器软中断 */
      	softlockup_tick();
      }
      
    • 搜索超时定时器的策略
      • 内核按照定时器的超时时间将定时器分为5组,超时时间相近的定时器为一组
      • 当定时器超时时间接近时,定时器将随组一起下移
    • 为什么需要上述策略?
      所有定时器以链表形式存放,寻找超时定时器而遍历链表是不明智的

8、延迟执行

  • 内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外,还有其他方法推迟执行任务。这种延迟常用于等待硬件完成某些工作,而且等待时间非常短。 如重新设置以太网卡模式需要等待2ms
  • 内核提供多种延迟方法处理各种延迟要求,有些在延迟任务时挂起处理器,防止处理器执行任何实际工作;有些不会挂起处理器,所以也不能确保延迟代码能够在指定的延迟时间运行。
  1. 忙等待
    • 最简单不理想的方法
      延迟节拍的整数倍或者精确度要求不高的延时。例如:等待10个节拍,处理器原地旋转。
      unsigned long timeout = jiffies + 10; /* ten ticks */
      while (time_before(jiffies, timeout))
      	;
      
    • 更好的方法
      在代码等待时,允许内核重新调度其他任务。由于需要调度程序,不能在中断上下文中使用,只能在进程上下文中使用。例如:
      unsigned long delay = jiffies + 5*HZ;
      while (time_before(jiffies, delay))
      	cond_resched();  // cond_resched将调度一个新程序投入运行,但需要设置完need_resched标志后才可生效
      					 // 换句话说,系统中存在更重要的任务需要运行。
      
    • 延迟执行不应该持有锁时禁止中断时发生。
  2. 短延迟
    • 什么情况下使用短延迟?
      有时内核代码(通常时驱动程序)需要很短延迟而却要精确,多发生在与硬件同步时。大多小于1ms,因此不能用jiffies。内核提供三个可以处理msusns级别的延迟函数,定义在linux/delay.hasm/delay.h中。
      void udelay(unsigned long usecs)
      void ndelay(unsigned long nsecs)
      void mdelay(unsigned long msecs)
      
    • 什么是BogoMIPS
      BogoMIPS这一名字取自bogus(伪的)和MIPS(Million Instructions Per Second)。取该名字的原因是:它记录的并不是机器性能而是在给定时间内忙循环执行的次数,处理器在空闲时速度有多快。该值存放在变量loops_per_jiffy中,可以从文件/proc/cpuinfo中读到。该变量可以提供精确延迟需要进行的循环数。
  3. schedule_timeout()
  • 更理想的方法是使用schedule_timeout()函数,该方法会让需要延迟执行的任务睡眠到指定的延时时间耗尽后再重新运行

  • 睡眠时间不能保证等于指定的延时时间,只能尽量接近指定时间

  • 在调用shcedule_timeout时,任务必须设置状态为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。例如:文章来源地址https://www.toymoban.com/news/detail-434895.html

    /* 将任务设置为可中断睡眠状态 */
    set_current_state(TASK_INTERRUPTIBLE);
    /* 小睡一会,"s"秒后唤醒 */
    schedule_timeout(s*HZ);
    
    1. schedule_timeout()的实现
      signed long schedule_timeout(signed long timeout)
      {
          time_t timer;//创建了一个定时器timer
          unsigned long expire;
      
          switch(timeout)
          {
          case MAX_SCHEDULE_TIMEOUT://任务无限期休眠,不允许!
              schedule();
              goto out;
          default:
              if(timeout < 0)//超时时间设置小于0,不允许!
              {
                  printk(KERN_ERR "schedule_timeout: wrong timeout "
                      "value %lx from %p\n", timeout,
                      __builtin_return_address(0));
                  current->state = TASK_RUNNING;
                  goto out;
              }
          }
          
          expire = timeout + jiffies;
      
          init_timer(&timer);
          timer.expires = expires;
          timer.data = (unsigned long)current;
          timer.function = process_timeout;//设置超时执行函数process_timeout()
      
          add_timer(&timer);//激活定时器
          schedule();//调用schedule()
          del_timer_sync(&timer);//任务提前被唤醒,定时器被撤销
      
          timeout = expire - jiffies;//剩余的时间
      
      out:
          return timeout < 0 ? 0 : timeout;
      }
      
    2. 设置超时时间,在等待队列上睡眠。
      等待队列中的任务可能既在等待一个特定事件到来,又在等待一个特定时间到期(取决于两者的速度)。在这种情况下,代码可以简单地使用schedule_timeout()函数代替schedule()函数。

到了这里,关于《LKD3粗读笔记》(11)定时器和时间管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单片机学习 11-中断系统(定时器中断+外部中断)

    ​ 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的,中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一,是我们学习单片机必须要掌握的。很多初学者被困在中断中,学了很久仍然不知道中断究竟是

    2024年02月05日
    浏览(51)
  • STM32速成笔记—定时器

    🎀 文章作者:二土电子 🌸 关注文末公众号获取其他资料和工程文件! 🐸 期待大家一起学习交流! 关于什么是定时器,简单来讲,就是是用来定时的。STM32F103ZET6有两个基本定时器TIM6和TIM7,四个通用定时器TIM2~TIM5和两个高级定时器TIM1,TIM8。每一个定时器都是完全独立的

    2024年02月09日
    浏览(53)
  • 学习笔记|定时器|STC中断|定时器时间计算|STC32G单片机视频开发教程(冲哥)|第十一集:定时器的作用和意义

    什么是定时器:定时器-与非网 上节课的一段代码: TimeCount++然后一个延时1毫秒,每运行1ms,变量就会加一。 系统已经运行了多少个毫秒。 实际使用时的代码如下, 判断按键有沿有按下的时候,我们等待按键松开,还有一个while循环。 如果没有松开,会一直死在这一行。所以,

    2024年02月09日
    浏览(65)
  • DSP28335学习笔记:定时器中断

    F28335的CPU定时器有3个且均为32位,分别是Timer0、Timer1、Timer2, 其中 Timer2 是为操作系统 DSP/BIOS 保留的,当未移植操作系统时,可用来做普 通的定时器。这三个定时器的中断信号分别为 TINT0,TINT1,TINT2,分别对应于中断向量 INT1,INT13,INT14。 一、F28335定时器介绍(CPU定时器)

    2024年04月10日
    浏览(46)
  • 【STM32笔记】STM32的定时器开发基础(二)(基于STM32CubeMX实现定时器中断)

      传统STM32外部中断 的设计步骤:  (1)将GPIO初始化为输入端口。  (2)配置相关I/O引脚与中断线的映射关系。  (3)设置该I/O引脚对印的中断触发条件。  (4)配置NVIC,并使能中断。  (5)编写中断服务函数。   基于STM32CubeMX的外部中断 设计步骤  (1)在STM3

    2024年02月20日
    浏览(62)
  • STM32笔记----5、TIM定时器

    时基单元:由自动重装载寄存器、预分频器、计数器组成。 来自RCC的TIMxCLK:一般是系统的主频,72MHz。 预分频器(16位):对进来的频率进行分频,写0,不分频,输出72MHz。写1,2分频,输出36MHz。以此类推。 计数器(16位):对预分频器后的计数时钟进行计数。 自动重装载

    2024年01月22日
    浏览(78)
  • 【STM32】学习笔记(TIM定时器)

    定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发

    2024年02月09日
    浏览(43)
  • 江科大stm32视频学习笔记——TIM定时中断&定时器外部时钟

    目录 一、TIM(Timer)定时器简介  1.1 定时器类型 摘要 1.1.1 基本定时器 1.1.2 通用定时器 1.1.3 高级定时器  1.2 定时中断基本结构 1.2.1 结构框图 1.2.2 时序图 二、定时器定时中断定时器外部时钟 2.1 内部时钟闹钟代码 2.1.1 Timer.c 2.1.2 Buzzer.c加入间隔发声函数 2.1.3 main.c 2.1.4 实验视频

    2024年01月23日
    浏览(64)
  • GD32系列笔记六:定时器Timer

    目录 一、定时器的作用 二、定时器介绍 三、定时器配置 1. 用作封装延时函数,提高程序实时性; 2. 测试某段代码的执行时间; 3. 一些外设的核心,如PWM输入捕获、输出比较等。 1.时钟树 2.结构图(基本定时器为例)  TIMER_CK就是CK_TIMER    3. 工作原理               1. 

    2024年02月13日
    浏览(43)
  • stm32——hal库学习笔记(定时器)

    使用纯软件(CPU死等)的方式实现定时(延时)功能 使用精准的时基,通过硬件的方式,实现定时功能 递增计数模式实例说明 中心对齐模式实例说明 TIM6 和TIM7 控制寄存器 1(TIMx_CR1) TIM6 和TIM7 DMA/中断使能寄存器(TIMx_DIER) TIM6 和TIM7 状态寄存器(TIMx_SR) TIM6 和TIM7 计数器(TIMx_CNT)

    2024年02月21日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包