linux内核中的typecheck函数

本文介绍了一个名为typecheck的宏定义,它用于检查指定类型与变量的实际类型是否一致。通过宏展开过程,展示了如何利用C语言扩展特性typeof来实现类型检查,并解释了其工作原理。

最近在查看linux代码时看到了一个typecheck函数,刚开始不是很明白,具体代码如下:

#define typecheck(type,x) \
({      type __dummy; \
        typeof(x) __dummy2; \
        (void)(&__dummy == &__dummy2); \
        1; \
})

这是一个宏,它是检查type和x的数据类型是否一样,比如我们这样使用

int a;
typecheck(double,a);
//宏展开后是一个表达式
({
      double  __dummy;
      typeof(a) __dummy2;
      (void)(&__dummy == &__dummy2);
      1;
})
//typeof关键字是C语言中的一个扩展,用于识别出某个表达式或类型的具体。
   typeof(int *) a,b;
            等价于:
            int *a,*b;
 //后面就是取两个变量的地址,然后比较,因为__dummy是double类型而__dummy2是int类型,取地址后是两种不同类型的指针,不同类型的指针不能比较,会出现编译错误,这种方式实现了类型的检查。
1;
//这个是表达式最后的值。如果整个表达式没有错误,那么最后的1就是整个表达式的值。
//总结:整个宏定义其实是一个表达式,不是函数,无所谓返回值,这个值是这个表达式的值,复合表达式,取最后一个表达式的值,作为复杂表达式的值。所以这里整个表达式的值就是最后一个表达式的值,也就是1
<think>我们正在分析Linux内核5.17中与jiffies相关的函数和实现。根据用户提供的引用和之前的问题,我们知道jiffies是一个全局变量,记录系统启动以来的时钟滴答数。每次时钟中断(timerinterrupt)发生时,jiffies会增加1。此外,我们还知道jiffies在系统启动时被初始化为一个负数(例如-300*HZ),以避免在系统启动后不久就发生溢出。同时,内核还提供了jiffies_64(64位版本)来避免溢出问题。在Linux内核5.17中,与jiffies相关的函数和实现主要涉及以下几个方面:1.**jiffies的定义和初始化**:jiffies和jiffies_64的定义位置,以及初始化过程。2.**更新jiffies**:时钟中断处理函数中如何更新jiffies。3.**jiffies的读取**:如何安全地读取jiffies(考虑可能的溢出和并行访问)。4.**时间比较宏**:提供了一组宏(如time_after,time_before等)来安全地比较时间,即使发生回绕(32位jiffies溢出)也能正确处理。5.**jiffies与时间转换**:将jiffies转换为时间(毫秒、秒等)的函数。我们将逐一分析这些方面。###1.jiffies的定义和初始化在Linux内核中,jiffies和jiffies_64的定义通常在`kernel/time/timer.c`或`include/linux/jiffies.h`中。在`include/linux/jiffies.h`中,我们可以看到:```cexternunsignedlongvolatile__jiffy_datajiffies;externu64__jiffy_datajiffies_64;```初始化在`kernel/time/timer.c`中。在系统启动时,会调用`init_timers`函数,其中会初始化jiffies相关的数据。具体初始化jiffies的值在启动过程中设置,例如在`start_kernel`函数中调用`tick_init`和`init_timer_cpus`等函数,但实际设置jiffies初始值的地方在时钟源初始化过程中。在5.17内核中,jiffies的初始化可能在`timer_init`函数中(针对特定架构)或者由通用时间初始化代码完成。例如,在x86架构中,初始值设置为`INITIAL_JIFFIES`,其定义为:```c#defineINITIAL_JIFFIES((unsignedlong)(unsignedint)(-300*HZ))```这样,jiffies在启动时是一个负数,经过300秒(5分钟)后才变为0,从而避免在启动后不久就发生溢出。###2.更新jiffiesjiffies的更新发生在时钟中断处理函数中。在Linux内核中,时钟中断处理函数通常是`timer_interrupt`(特定架构)或者通用层函数。在5.17内核中,通用框架下,时钟中断的处理会调用`tick_handle_periodic`。在`tick_handle_periodic`中,会调用`tick_periodic`函数,该函数位于`kernel/time/tick-common.c`:```cvoidtick_periodic(intcpu){if(tick_do_timer_cpu==cpu){write_seqlock(&jiffies_lock);//记录下一个jiffies_64的值jiffies_64+=1;//更新jiffies(32位,取jiffies_64的低32位)write_sequnlock(&jiffies_lock);update_wall_time();}update_process_times(user_mode(get_irq_regs()));profile_tick(CPU_PROFILING);}```注意:这里我们只关注jiffies的更新。实际上,`jiffies_64`每次增加1,而`jiffies`是`jiffies_64`的低32位。由于使用了顺序锁(`jiffies_lock`)来保护,因此读取jiffies时需要使用安全的方式。###3.读取jiffies由于jiffies是32位,可能会溢出,而且更新时使用了锁,因此内核提供了一组函数和宏来安全读取jiffies。在`include/linux/jiffies.h`中,提供了`get_jiffies_64`函数来获取64位的jiffies值:```cu64get_jiffies_64(void);```其实现(在`kernel/time/timer.c`)使用顺序锁来确保读取的一致性:```cu64get_jiffies_64(void){unsignedintseq;u64ret;do{seq=read_seqbegin(&jiffies_lock);ret=jiffies_64;}while(read_seqretry(&jiffies_lock,seq));returnret;}```对于32位的jiffies,可以直接读取,但要注意溢出问题。因此,内核提供了一组宏来安全比较时间。###4.时间比较宏为了避免jiffies的溢出导致比较错误,内核提供了以下宏(在`include/linux/jiffies.h`):```c#definetime_after(a,b)\(typecheck(unsignedlong,a)&&\typecheck(unsignedlong,b)&&\((long)((b)-(a))<0))#definetime_before(a,b)time_after(b,a)#definetime_after_eq(a,b)\(typecheck(unsignedlong,a)&&\typecheck(unsignedlong,b)&&\((long)((a)-(b))>=0))#definetime_before_eq(a,b)time_after_eq(b,a)```这些宏将两个无符号长整数转换为有符号长整数,然后判断差值。这样即使发生回绕(即a在b之后发生了溢出),也能正确判断先后。###5.jiffies与时间转换内核提供了将jiffies转换为毫秒、微秒等时间单位的函数。例如:```cunsignedintjiffies_to_msecs(constunsignedlongj);unsignedintjiffies_to_usecs(constunsignedlongj);```这些函数在`kernel/time/time.c`中实现。它们通过乘以转换因子(HZ)的倒数来实现转换。例如:```cunsignedintjiffies_to_msecs(constunsignedlongj){return(j*(1000/HZ));}```注意:由于1000/HZ可能不是整数,所以实际实现中使用了更复杂的计算以避免精度丢失。###总结在Linux内核5.17中,jiffies作为记录系统启动以来时钟滴答次数的全局变量,其更新、读取和比较都有相应的机制确保正确性。主要函数和实现包括:-定义和初始化:jiffies和jiffies_64的定义,以及启动时的初始化(设置为负数)。-更新:在时钟中断处理函数中,通过顺序锁保护更新jiffies_64,同时更新jiffies(32位)。-读取:使用`get_jiffies_64`安全读取64位值,或者直接读取jiffies(32位)但使用时间比较宏来避免溢出问题。-时间比较:提供一组宏(如time_after,time_before)来安全比较时间。-时间转换:提供函数将jiffies转换为毫秒、微秒等时间单位。这些机制共同保证了内核时间管理的正确性和健壮性。§§相关问题§§1.Linux内核中如何避免jiffies溢出导致的时间比较错误?2.在SMP系统中,更新jiffies时如何保证数据一致性?3.如何将jiffies转换为实际时间(例如毫秒)?转换精度如何保证?4.在64位系统上,为什么还需要32位的jiffies变量?5.如果系统时钟频率(HZ)设置为100,那么一个jiffies代表多少毫秒?</think>### Linux Kernel 5.17 Jiffies 相关函数与实现分析 在 Linux 5.17 内核中,`jiffies` 是时间子系统的核心概念,记录了系统启动后的时钟滴答次数。以下是关键实现细节: #### 1. **jiffies 定义与初始化** - **位置**:`include/linux/jiffies.h` - **实现**: ```c extern unsigned long volatile __jiffy_data jiffies; extern u64 __jiffy_data jiffies_64; ``` - **初始化**:系统启动时在 `start_kernel()` 中设置: ```c jiffies = INITIAL_JIFFIES; // 通常为 -300*HZ jiffies_64 = INITIAL_JIFFIES; ``` 这种负值初始化(如 -300*HZ)使 jiffies 在系统启动 5 分钟后才首次溢出[^1]。 #### 2. **jiffies 更新机制** - **更新函数**:`do_timer()` **位置**:`kernel/time/timekeeping.c` **作用**:每次时钟中断(timer interrupt)调用: ```c void do_timer(unsigned long ticks) { jiffies_64 += ticks; update_wall_time(); } ``` 实际更新由 `tick_periodic()` 触发,频率由 `CONFIG_HZ` 决定(默认 250/1000Hz)[^3]。 #### 3. **防溢出时间比较宏** - **宏定义**:`include/linux/jiffies.h` ```c #define time_after(a,b) ((long)((b) - (a)) < 0) #define time_before(a,b) time_after(b,a) ``` **原理**:利用有符号数减法处理无符号数回绕: > 当 `a = 0xFFFFFFFF`, `b = 0x00000001` 时: > `(long)(b - a) = (long)(2) > 0` → `time_after(a,b)=false` > 正确处理了 jiffies 溢出场景[^1]。 #### 4. **64 位扩展 jiffies_64** - **作用**:避免 32 位 jiffies 的溢出问题(约 497 天溢出) - **读取函数**:`get_jiffies_64()` ```c u64 get_jiffies_64(void) { unsigned long seq; u64 ret; do { seq = read_seqbegin(&jiffies_lock); ret = jiffies_64; } while (read_seqretry(&jiffies_lock, seq)); return ret; } ``` 使用顺序锁(seqlock)确保 SMP 下的数据一致性。 #### 5. **jiffies 时间转换函数** - **转换函数**:`kernel/time/time.c` ```c unsigned int jiffies_to_msecs(const unsigned long j) { return (j * (1000 / HZ)) % 1000; } ``` 提供 `jiffies_to_usecs()`, `msecs_to_jiffies()` 等转换函数,精度取决于 $HZ$ 值: | HZ 值 | 单个 jiffy 时间 | 最大计时范围 (32-bit) | |-------|----------------|----------------------| | 100 | 10ms | 497 天 | | 250 | 4ms | 198 天 | | 1000 | 1ms | 49.7 天 | #### 6. **jiffies 与定时器** - **定时器回调**:`timer_list` 结构使用 jiffies ```c struct timer_list { unsigned long expires; // 基于 jiffies 的到期时间 void (*function)(struct timer_list *); }; ``` - **添加定时器**:`mod_timer()` 基于当前 jiffies 计算到期时间: ```c expires = jiffies + msecs_to_jiffies(timeout); ``` #### 7. **精度问题与优化** - **精度限制**:低 `HZ` 值导致最小时间粒度变粗(如 HZ=100 时最小间隔 10ms) - **优化措施**: - 启用 `CONFIG_HIGH_RES_TIMERS` 使用高精度定时器 - 动态时钟模式(`CONFIG_NO_HZ`)减少中断频率 - 时钟源选择优化(如优先 TSC) --- ### 典型问题场景 1. **定时器漂移** 当 `HZ` 设置过低时,频繁短周期定时器会出现累积误差: ```math \text{误差率} = \frac{\text{实际执行间隔} - \text{预期间隔}}{\text{预期间隔}} \times 100\% ``` 2. **NMI 中断干扰** 长时间 NMI 处理会延迟时钟中断,导致 jiffies 更新延迟。 3. **虚拟化环境** VM 暂停/恢复导致 jiffies 与实际时间脱节(需 KVM 时钟补偿)[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值