《Linux 内核设计与实现》11. 定时器和时间管理

news2024/12/21 22:05:59

文章目录

    • 内核中时间的概念
    • 节拍率:HZ
      • 理想的 HZ 值
      • 高 HZ 的优势
      • 高 HZ 的劣势
    • jiffies
      • jiffies 的内部表示
      • jiffies 的回绕
      • 用户空间和 HZ
    • 硬时钟和定时器
      • 实时时钟
      • 系统定时器
    • 时钟中断处理程序
    • 实际时间
    • 定时器
      • 使用定时器
      • 定时器竞争条件
      • 实现定时器
    • 延迟执行
      • 忙等待
      • 短延迟
      • schedule_timeout()

内核中时间的概念

系统定时器是一种可编程芯片,它能以固定频率产生中断,这种频率称作节拍率(tick rate)。节拍率是可以自定义的,因此通过连续两次时钟中断可以知道时钟中断间隔的时间,这个间隔时间称作节拍(tick),它等于节拍率分之一,即 1 / ( t i c k   r a t e ) 1/(tick\ rate) 1/(tick rate) 秒。

通过节拍来计数墙上时间和系统运行时间:

  • 墙上时间:真实时间。
  • 系统运行时间:自系统启动以来所运行的时间。

利用时钟中断周期执行的工作:

image-20230425085049554

节拍率:HZ

系统定时器频率(节拍率)是可自定义的,即 HZ。一个周期为 1/HZ 秒,即产生时钟中断的间隔时间。例如:100HZ,在处理器上每秒时钟中断 100 次(1/100,即每 10ms 产生一次)。

image-20230425085123169

理想的 HZ 值

提高节拍率意味着时钟中断产生的更加频繁,所以中断处理程序也会频繁执行,好处如下:

  • 更高的时钟中断解析度(resolution)可提高时间驱动事件的解析度。
  • 提高了时间驱动事件的准确度。

高 HZ 的优势

更高的时钟中断频率和更高的准确度又会带来如下优点:

image-20230425142807619

高 HZ 的劣势

节拍率越高,意味着时钟中断频率越高,也就意味着系统负担越重。

因为节拍率越高,处理器就被时钟中断处理程序占用了大量的时间。这样不仅减少了处理器处理其他的工作,还频繁地打乱处理器高速缓存并增加耗电。

现代计算机系统上,始终频率为 1000HZ 不会导致难以接受的负担,并不会对系统性能造成较大的影响。

无节拍的 OS:Linux 提供 CONFIG_HZ 配置选项,系统根据这个选项动态调度时钟中断。并非是固定节拍率,而是按需动态调度和重新设置。如:下一个时钟频率设置为 3ms,之后若 50ms 内都无事可做,内核将以 50ms 重新调度时钟中断。其优点:减少开销、省电。

jiffies

全局变量 jiffies 用来记录自系统启动以来产生的节拍的总数。每次触发时钟中断后,该值都会增加。因为一秒内时钟中断的次数等于 HZ,所以 jiffies 一秒内增加的值也就为 HZ。系统运行时间以秒为单位,就等于 jiffies/HZ。

路径:include\linux\jiffies.h

以秒为单位转 jiffies:(seconds * HZ)
jiffies 转换为以秒为单位的时间:(jiffies / HZ)

jiffies 的内部表示

jiffies 是无符号长整型,在 32 位,时钟频率为 100HZ 的情况下,497 天会溢出,1000HZ 的情况下,49.7 天就会溢出。64 位别指望溢出。

为了使 32 位系统和 64 位系统上的 jiffies 互相兼容,因此在 64 位 jiffies 中只使用低 32 位。

arch\x86\kernel\vmlinux.lds.S

#ifdef CONFIG_X86_32
OUTPUT_ARCH(i386)
ENTRY(phys_startup_32)
jiffies = jiffies_64;
#else
OUTPUT_ARCH(i386:x86-64)
ENTRY(phys_startup_64)
jiffies_64 = jiffies;
#endif

image-20230425085201644

访问 jiffies 的代码仅会读取 jiffies_64 的低 32 位。

获取整个 64 位的方法:

  • get_jiffies_64()
  • 直接读取 jiffies 变量

在 64 位系统上,jiffies_64 和 jiffies 指向的是同一个变量。

jiffies 的回绕

jiffies 达到最大值后,若持续增加,会绕回到 0。如果设置后 timeout(=jiffies+delay) 后,jiffies 绕回到 0,那么此时 jiffies 必然小于 timeout,而执行条件是 jiffies >= timeout,解决方案如下:

image-20230425085656592

/*
 *	These inlines deal with timer wrapping correctly. You are 
 *	strongly encouraged to use them
 *	1. Because people otherwise forget
 *	2. Because if the timer wrap changes in future you won't have to
 *	   alter your driver code.
 *
 * time_after(a,b) returns true if the time a is after time b.
 *
 * Do this with "<0" and ">=0" to only test the sign of the result. A
 * good compiler would generate better code (and a really good compiler
 * wouldn't care). Gcc is currently neither.
 */
#define time_after(a,b)		\
	(typecheck(unsigned long, a) && \
	 typecheck(unsigned long, b) && \
	 ((long)(b) - (long)(a) < 0))
#define time_before(a,b)	time_after(b,a)

#define time_after_eq(a,b)	\
	(typecheck(unsigned long, a) && \
	 typecheck(unsigned long, b) && \
	 ((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b)	time_after_eq(b,a)

用户空间和 HZ

内核以节拍数或秒的形式给用户空间导出这个值,应用程序依赖于这个值。若随意改变内核中的 HZ 值,而不及时更新用户空间的 HZ,则会给用户空间中的某些程序造成些异常结果。

USER_HZ 代表用户空间所用到的 HZ 值。jiffies_to_clock_t() 将一个由 HZ 表示的节拍计数转换成一个由 USER_HZ 表示的节拍计数。

HZ 和 USER_HZ 是整数倍:

return x / (HZ / USER_HZ);

jiffies_to_clock_t() 在 kernel/time.c

硬时钟和定时器

实时时钟

实时时钟 RTC 是用来持久存放系统时间的设备,即便系统关闭后,它也能依靠 CMOS 保持系统计时。

当系统启动时,内核通过读取 RTC 来初始化墙上时间,该时间存放在 xtime 变量中。通常内核不会在系统启动后再读取 xtime 变量,但有些体系结构会周期性地将当前时间值存回 RTC 中。

实时时钟最主要的作用是:启动时初始化 xtime 变量。

系统定时器

系统定时器提供了一种周期性触发中断的机制。实现方式:

  • 衰减测量器
  • 可编程中断时钟(PIT):内核启动时对 PIT 初始化,使其能够以 HZ/秒的频率产生时钟中断。

x86 中其它的时钟资源包括:本地 APIC 时钟和时间戳计数(TSC)等。

时钟中断处理程序

时钟中断处理程序划分为两个部分:体系结构相关的、体系结构无关的。

与体系结构相关的:

image-20230425092146219

与体系结构无关的:

image-20230425092204447

实际时间

当前实际时间定义在:kernel/time/timekeeping.c

struct timespec xtime;

timespec 数据结构定义在 include/linux/time.h 中

struct timespec {
	__kernel_time_t	tv_sec; /* seconds, 1970/1/1 起 */
	long		tv_nsec;    /* nanoseconds 记录自上一秒开始经过的 ns 数 */
};

更新 xtime 需要一个 seq 锁:

write_seqlock(&xtime_lock);
// change xtime...
write_sequnlock(&xtime_lock);

读取 xtime 时同理需要 read_seqbegin() 和 read_seqretry()。

image-20230425093358041

该循环不断重复,直到读者确认读取数据时没有写操作介入。若发现循环中有时钟中断处理程序更新了 xtime,那么 read_seqretry() 返回无效序列号,继续循环等待。

从用户空间得到墙上时间:gettimeofday(),对应内核系统调用 sys_gettimeofday()。

image-20230425100552360

定时器

路径:

  • include/linux/timer.h
  • kernel/timer.c

使用定时器

struct timer_list {
	struct list_head entry; // 定时器链表的入口
	unsigned long expires; // 以 jiffies 为单位的定时值
	void (*function)(unsigned long); // 定时器处理函数
	unsigned long data; // 传给处理函数的参数
	struct tvec_base *base; // 定时器内部值,用户不要使用
};

使用定时器需要注意:

  • 不能用定时器来执行任何硬实时任务。

定时器竞争条件

由于定时器和当前代码是异步的,因此存在潜在的竞争条件。因此决不能用下面的程序来代替 mod_timer()。

del_timer(my_timer);
my_timer->expires = jiffies + new_delay;
add_timer(my_timer);

其次,一般情况下,应该使用 del_timer_sync() 代替 del_timer(),因为无法确定在删除定时器时,它是否在其它处理器上运行。

由于内核异步执行中断处理程序,所以应该要保护好定时器中断处理程序中的共享数据。

实现定时器

内核正在时钟中断发生后执行定时器,定时器作为软中断在下半部上下文中执行。具体的,时钟中断处理程序会执行 update_process_time() 函数,该函数调用 run_local_timer():

image-20230425103817872

TIMER_SOFTIRQ 对应 run_timer_softirq() 软中断处理函数。

定时器都以链表形式存放在一起,但为了寻找一个定时器而遍历整个链表是不明智的。将超时时间排序也是不明智的。为了提高搜索效率,内核将定时器按超时时间划分为 5 组。当定时器超时时间接近时,定时器随组一起下降。采用分组定时器的方法可以在执行软中断的多数情况下,确保内核尽可能减少搜索超时定时器所带来的负担。

延迟执行

忙等待

  • 实现方式:循环,直到超过 timeout 为止。

    unsigned long timeout = jiffies + 10;
    while(time_before(jiffies, timeout));
    
  • 上面会导致处理器一直处于循环等待中。我们应该在代码中等待,即异步等待:

    unsigned long delay = jiffies + 5 * HZ;
    while(time_before(jiffies, delay)) cond_resched();
    

    cond_resched() 将调入一个新程序投入运行。注意,该函数需要调度程序,因此不可在中断上下文中被调用。

延迟执行不管在哪种情况下,都不应该在持有锁时或禁止中断时发生。

短延迟

  • 短延迟,等待时间往往小于 1ms。即便是 1000HZ,它的节拍间隔都有 1ms。

  • 内核提供 us、ns、ms 级别的延迟函数:

    // include/linux/delay.h
    static inline void ndelay(unsigned long x) {
    	udelay(DIV_ROUND_UP(x, 1000));
    }
    // arch/xtensa/include/asm/delay.h
    /* For SMP/NUMA systems, change boot_cpu_data to something like
     * local_cpu_data->... where local_cpu_data points to the current
     * cpu. */
    static __inline__ void udelay (unsigned long usecs) {
    	unsigned long start = xtensa_get_ccount();
    	unsigned long cycles = usecs * (loops_per_jiffy / (1000000UL / HZ));
    	/* Note: all variables are unsigned (can wrap around)! */
    	while (((unsigned long)xtensa_get_ccount()) - start < cycles)
    		;
    }
    // include/linux/delay.h
    #define mdelay(n) (\
    	(__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : \
    	({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
    

udelay() 依靠执行循环得到延迟效果,而 mdelay() 由依靠 udelay() 实现。因为内核知道处理器 1 秒内能执行多少次循环,所以 udelay() 仅需要根据指定的延迟时间在 1 秒中占的比例,就能决定需要循环多少次可以达到推迟时间。

通常,超过 1ms 延迟的别用 udelay()。

schedule_timeout()

  • 该函数让需要延迟执行的任务睡眠到指定延迟时间后再重新投入运行,即进入运行队列。

  • 用法

    // 将任务设置为可中断睡眠状态
    set_current_state(TASK_INTERRUPTIBLE);
    // 睡眠 s 秒
    schedule_timeout(s * HZ);
    
  • schedule_timeout() 的实现

    /**
     * schedule_timeout - sleep until timeout
     * @timeout: timeout value in jiffies
     *
     * Make the current task sleep until @timeout jiffies have
     * elapsed. The routine will return immediately unless
     * the current task state has been set (see set_current_state()).
     *
     * You can set the task state as follows -
     *
     * %TASK_UNINTERRUPTIBLE - at least @timeout jiffies are guaranteed to
     * pass before the routine returns. The routine will return 0
     *
     * %TASK_INTERRUPTIBLE - the routine may return early if a signal is
     * delivered to the current task. In this case the remaining time
     * in jiffies will be returned, or 0 if the timer expired in time
     *
     * The current task state is guaranteed to be TASK_RUNNING when this
     * routine returns.
     *
     * Specifying a @timeout value of %MAX_SCHEDULE_TIMEOUT will schedule
     * the CPU away without a bound on the timeout. In this case the return
     * value will be %MAX_SCHEDULE_TIMEOUT.
     *
     * In all cases the return value is guaranteed to be non-negative.
     */
    signed long __sched schedule_timeout(signed long timeout) {
    	struct timer_list timer;
    	unsigned long expire;
    
    	switch (timeout)
    	{
    	case MAX_SCHEDULE_TIMEOUT:
    		/*
    		 * These two special cases are useful to be comfortable
    		 * in the caller. Nothing more. We could take
    		 * MAX_SCHEDULE_TIMEOUT from one of the negative value
    		 * but I' d like to return a valid offset (>=0) to allow
    		 * the caller to do everything it want with the retval.
    		 */
    		schedule();
    		goto out;
    	default:
    		/*
    		 * Another bit of PARANOID. Note that the retval will be
    		 * 0 since no piece of kernel is supposed to do a check
    		 * for a negative retval of schedule_timeout() (since it
    		 * should never happens anyway). You just have the printk()
    		 * that will tell you if something is gone wrong and where.
    		 */
    		if (timeout < 0) {
    			printk(KERN_ERR "schedule_timeout: wrong timeout "
    				"value %lx\n", timeout);
    			dump_stack();
    			current->state = TASK_RUNNING;
    			goto out;
    		}
    	}
    
    	expire = timeout + jiffies;
    
    	setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
    	__mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
    	schedule();
    	del_singleshot_timer_sync(&timer);
    
    	/* Remove the timer from the object tracker */
    	destroy_timer_on_stack(&timer);
    
    	timeout = expire - jiffies;
    
     out:
    	return timeout < 0 ? 0 : timeout;
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/505164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跨境商城APP开发需要注意的问题

随着全球化的趋势&#xff0c;跨境电商发展迅猛&#xff0c;越来越多的企业开始进军跨境市场。而跨境商城APP已经成为跨境电商非常重要的一部分。在开发跨境商城APP时&#xff0c;需要注意以下问题&#xff1a; 1.多语言支持 跨境商城APP需要支持不同国家和地区的语言&#x…

在基于Android以及Jetson TK平台上如何写32位的Thumb-2指令

由于Android以及Jetson TK的编译工具链中的汇编器仍然不支持大部分的32位Thumb-2指令&#xff0c;比如 add.w&#xff0c;因此我们只能通过手工写机器指令码来实现想要的指令。下面我将简单地介绍如何在ARM GCC汇编器中手工去写机器指令码。 对于GCC或Clang的汇编器&#xff0…

es6 学习笔记-1

学习视频&#xff1a;尚硅谷Web前端ES6教程&#xff0c;涵盖ES6-ES11_哔哩哔哩_bilibili 一、介绍 ES&#xff1a;全称为EcmaScript,是脚本语言的规范 ECMAScript&#xff1a; 由Ecma国际通过ECMA-262标准化的脚本程序设计语言。 es6兼容性&#xff1a;ECMAScript 6 compa…

adb logcat 保存日志文件到本地

指令 adb logcat > logcat.log例如&#xff1a;例如&#xff1a;adb logcat > D:\logcat.log 注意window中直接输入可能会出现log文件打开显示乱码问题&#xff1b; 请打开cmd检查 输入 chcp 如图 查看结果 如果不是65001 则 执行 chcp 65001 之后执行 例如&#x…

antd 中日期组件添加左侧日期范围选择

一、产品需求 产品有这样一个需求&#xff0c; 在实时的日期组件左侧添加一个快捷时间范围选择&#xff0c;并且选择后&#xff0c;窗口不会自动关闭。 大致样式长这样&#xff1a; 二、需求拆解 拆解一下这个需求&#xff0c;需要满足三个要点&#xff1a; ① 快捷时间范围…

Linux学习之Shell(一)

Shell概述 1&#xff09;Linux提供的Shell解析器有 [xiaominghadoop101 ~]$ cat /etc/shells /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bin/bash /usr/sbin/nologin /bin/tcsh /bin/csh2&#xff09;bash和sh的关系 [xiaominghadoop101 bin]$ ll | grep bash -rwxr…

LinkFlow CDP洞察能力升级,结合订单开启营销新趋势

4月26日&#xff0c;悠易科技LinkFlow在春季产品发布会上对其洞察产品能力进行了升级。 在技术赋能以人为本的营销5.0阶段&#xff0c;伴随技术的发展&#xff0c;消费者很容易接触到不同的产品和服务&#xff0c;也可以很方便的通过社交网络以及各种社群找到跟自己有相同兴趣…

第四十一章 Unity 输入框 (Input Field) UI

本章节我们学习输入框 (Input Field)&#xff0c;它可以帮助我们获取用户的输入。我们点击菜单栏“GameObject”->“UI”->“Input Field”&#xff0c;我们调整一下它的位置&#xff0c;效果如下 我们在层次面板中发现&#xff0c;这个InputField UI元素包含两个子元素&…

PMP项目管理-[第十章]沟通管理

沟通管理知识体系&#xff1a; 规划沟通管理&#xff1a; 10.1 沟通维度划分 10.2 核心概念 定义&#xff1a;通过沟通活动(如会议和演讲)&#xff0c;或以工件的方式(如电子邮件、社交媒体、项目报告或项目文档)等各种可能的方式来发送或接受消息 在项目沟通中&#xff0c;需要…

聊聊并发编程的12种业务场景

前言 并发编程是一项非常重要的技术&#xff0c;无论在面试&#xff0c;还是工作中出现的频率非常高。 并发编程说白了就是多线程编程&#xff0c;但多线程一定比单线程效率更高&#xff1f; 答&#xff1a;不一定&#xff0c;要看具体业务场景。 毕竟如果使用了多线程&…

用DevExpress WinForms富文本编辑器,集成高级文本编辑功能(一)

DevExpress WinForm富文本编辑器&#xff08;RTF编辑器&#xff09;控件允许用户将高级文本编辑功能集成到下一个WinForms项目中&#xff0c;它包括全面的文本格式选项、支持邮件合并&#xff0c;并附带了丰富的终端用户选项集&#xff0c;因此可以轻松交付受Microsoft word启发…

Linux本地套接字通信

1. 本地套接字 socket API原本为网络通信而设计&#xff0c;后来在其基础上扩展出本地套接字机制用于本地进程间通信。 本地套接字为全双工通信方式。 2. 本地套接字的使用 本地套接字通信步骤 &#xff08;1&#xff09;创建本地socket 本地套接字使用文件来标识&#xff0c;…

【JUC基础】04. Lock锁

1、前言 java.util.concurrent.locks为锁定和等待条件提供一个框架的接口和类&#xff0c;说白了就是锁所在的包。 2、什么是Lock Lock是一种锁机制&#xff0c;比同步块&#xff08;synchronized block&#xff09;更加灵活&#xff0c;同时也更加复杂的线程同步机制。在JDK…

Node.js,多环境配置

目录 1、多环境简介 2、多环境配置 3、命令运行 1、多环境简介 在前端项目的开发过程中&#xff0c;我们需要把项目发布到不同服务器环境中&#xff0c;例如&#xff0c;测试&#xff0c;生产&#xff0c;开发&#xff0c;预生产等环境。在这个我们需要对不同的环境设置不同…

函数的运用

函数的运用 一、函数的定义二、简单函数实验两个数字的运算&#xff1a;调用位置变量函数变量的作用范围 三、函数的递归阶乘递归目录 四、函数库 一、函数的定义 shell函数是经常使用的&#xff0c;因为有些命令序列是需要反复调用执行的&#xff0c;将命令序列按格式写在一起…

三分钟教你Mac下安装VmWare虚拟机

大数据课程课前环境准备&#xff1a;mac中安装三台linux服务器 一、课前准备 准备一台内存最少8G&#xff08;建议16G&#xff09;、cpu i7 4核的电脑 二、课堂主题 安装虚拟化软件VMware准备3台linux虚拟机 三、课堂目标 完成mac下3个虚拟机的安装 四、知识要点 文档说…

洗稿用什么软件-洗稿软件免费

洗稿文章的主要优势 洗稿文章的主要优势在于提高文章的质量和效率。以下是洗稿文章的几个主要优势&#xff1a; 优化结构和语言 洗稿可以删除冗余、无用和重复的内容&#xff0c;同时对文章的结构和语言进行优化&#xff0c;提高文章的可读性和吸引力。这可以使文章更加专业…

探索自然语言处理领域的最新进展与挑战

自然语言处理&#xff08;NLP&#xff09;是人工智能领域中最受关注的领域之一&#xff0c;它涉及计算机和人类语言之间的交互。NLP的应用范围非常广泛&#xff0c;包括机器翻译、语音识别、文本分类、情感分析等等。本文将介绍NLP的基本概念和入门知识&#xff0c;以帮助初学者…

一键安装k8s脚本

服务器配置 节点(华为云服务器)配置master 2vCPUs | 4GiB | s6.large.2 CentOS 7.8 64bit node1 2vCPUs | 8GiB | s6.large.4 CentOS 7.8 64bit node2 2vCPUs | 8GiB | s6.large.4 CentOS 7.8 64bit 1.master节点安装脚本&#xff1a;install_k8s_master.sh。 sh文件上传到…

Windows基于Docker安装Elasticsearch和 kibana笔记

Windows基于Docker安装Elasticsearch和 kibana笔记 一、Windows安装Docker1、Windows安装Docker要求2、Docker安装 二、基于Docker安装Elasticsearch1、安装Elasticsearch2、Elasticsearch的XPACK验证2.1、什么是Xpack2.2、Xpack相关安全配置介绍2.2.1、xpack.security.enabled…