一文读懂TSC时钟: (x86_64/arm64)实现介绍和编程使用

Linux(16)之Time Stamp Counter

Author:Once Day Date:2023年5月30日

参考文档:

  • 4. Environment Abstraction Layer — Data Plane Development Kit 23.03.0 documentation (dpdk.org)
  • DPDK: lib/eal/include/generic/rte_cycles.h File Reference
  • 测量CPU的利器 - TSC (Time Stamp Counter) - 知乎 (zhihu.com)
  • 正确使用cpu提供的TSC - 知乎 (zhihu.com)
  • x86 TSC使用的那些坑 - 爱你一万年123 - 博客园 (cnblogs.com)
  • Time Stamp Counter (TSC) 知识点 - 简书 (jianshu.com)
  • 细说RDTSC的坑 – Dreamer Thinker Doer (wangkaixuan.tech)
  • 调整开发板arm的cpu频率_armv8 cpuinfo pll_思而后行之的博客-CSDN博客
  • Pitfalls of TSC usage | Oliver Yang
  • timestamp - Getting TSC rate from x86 kernel - Stack Overflow
  • linux - rdtsc accuracy across CPU cores - Stack Overflow
  • Porting x86架构的rdtsc函数到ARM64架构的方法 - 极术社区 - 连接开发者与智能计算生态 (aijishu.com)
  • Arm Architecture Reference Manual for A-profile architecture
  • 华为鲲鹏云 KBengine arm64编译问题实践报告-云社区-华为云 (huaweicloud.com)

文章目录

    • Linux(16)之Time Stamp Counter
        • 1. 概述
        • 2. DPDK的两种时间戳计时器(TSC,HPET)
        • 3. 时间戳计数器(TSC,Time Stamp Counter)详细总结
        • 4. TSC发展历史
        • 5. TSC多核时钟深入分析
        • 6. ARM架构的“TSC”时钟
        • 7. 实际代码演示
          • 7.1 x86_64架构DPDK获取TSC频率和cycle代码
          • 7.2 ARM64架构获取TSC(System Counter)

1. 概述

DPDK(数据包开发处理包,Data Plane Development Kit)是一套用于快速处理数据包的库和驱动程序。TSC(时间戳计数器,Time Stamp Counter)是一个高精度计时器,用于在CPU内核上测量时间。TSC是一个64位的寄存器,每个CPU内核都有一个TSC,它在每个时钟周期内递增。

在不同的CPU核上,TSC的周期可能相同,也可能不同。这取决于以下几个因素:

  1. 同步TSC:现代处理器实现了TSC的同步,即在所有内核上同时启动和递增TSC。这意味着在这些处理器上,所有内核上的TSC周期应该是相同的。要确定处理器是否支持同步TSC,可以检查处理器的规格文档,或者查询CPUID指令的相关字段。

  2. 动态调节频率(如Intel SpeedStep,AMD Cool’n’Quiet等):某些处理器可以根据负载动态调整CPU频率。这可能导致不同内核上的TSC以不同的速度增加,因为每个内核的运行频率可能不同。在这种情况下,不同内核上的TSC周期可能不同。要解决这个问题,可以将处理器设置为固定频率运行,或者使用不受动态频率调整影响的计时器,如HPET(高精度事件计时器,High Precision Event Timer)。

  3. 多处理器系统(如多个物理CPU的服务器):在具有多个物理处理器的系统中,每个处理器都有自己的TSC。这可能导致不同处理器上的内核具有不同的TSC周期。可以尝试使用软件技术,如RDTSC(读取时间戳计数器指令,Read Time Stamp Counter instruction)或者使用更适合多处理器系统的计时器,如HPET来解决这个问题。

总之,不同CPU核上的TSC周期可能相同,也可能不同。确定TSC周期是否相同通常需要考虑处理器架构、动态频率调整技术和多处理器系统等因素。在需要精确计时的场景中,可以使用其他计时器,如HPET,以避免潜在的问题。

2. DPDK的两种时间戳计时器(TSC,HPET)

在DPDK中,时间戳计数器(TSC,Time Stamp Counter)和高精度事件计时器(HPET,High Precision Event Timer)是两种用于测量时间的方法。它们之间的使用关系可以从以下几个方面进行总结:

  1. 时间测量精度和性能

    • TSC:TSC是高精度的计时器,它在每个时钟周期内递增。由于TSC读取速度快,延迟低,因此在性能要求较高的场景中,DPDK可能优先使用TSC作为计时器。
    • HPET:HPET也是一种高精度计时器,但相对于TSC,它的读取速度较慢,延迟较高。然而,HPET在多处理器系统和动态频率调整场景下表现更加稳定,因此在这些情况下,DPDK可能会选择使用HPET作为计时器。
  2. TSC和HPET的选择

    • DPDK在启动时会自动检测并选择合适的计时器。首选TSC,因为它具有较高的性能。然而,如果检测到TSC在多核处理器、多处理器系统或动态频率调整场景下可能存在不稳定性,DPDK会退回到使用HPET作为计时器。
  3. 定时器API

    • DPDK提供了通用的定时器API,这些API抽象了底层的计时器实现(如TSC和HPET),使得DPDK应用程序可以在不关心底层计时器类型的情况下进行时间测量和调度。这意味着DPDK应用程序开发者不需要直接处理TSC和HPET之间的使用关系,而可以通过DPDK提供的API来实现所需的计时功能。

总之,在DPDK中,TSC和HPET是两种用于测量时间的方法。它们之间的使用关系主要取决于性能需求和特定场景下的稳定性。DPDK会自动选择合适的计时器,应用程序开发者可以通过DPDK提供的通用定时器API来实现计时功能,而无需直接处理TSC和HPET之间的关系。

3. 时间戳计数器(TSC,Time Stamp Counter)详细总结

(1)优点

  • 高精度:TSC是一个高精度计数器,每个CPU内核上的TSC寄存器在每个时钟周期内递增,因此可以提供非常精确的时间测量。
  • 低延迟:相比其他计时器(如HPET),TSC读取速度更快,延迟更低。
  • 广泛支持:绝大多数现代处理器支持TSC,使其成为一种通用的计时解决方案。

(2)缺点

  • 同步问题:在多核处理器和多处理器系统中,不同CPU核上的TSC可能不完全同步,导致时间测量不一致。
  • 动态频率调整:动态调节CPU频率(如Intel SpeedStep,AMD Cool’n’Quiet等)可能导致TSC以不同速度增加,影响精确度。
  • 虚拟化环境:在虚拟化环境中,TSC的行为可能受到虚拟机监视器(hypervisor)的影响,导致不准确的时间测量。

(3)使用方法

  • 读取TSC:可以通过执行RDTSC(读取时间戳计数器)指令来读取当前TSC值。在C/C++中,可以使用内联汇编或者使用编译器提供的内建函数(如__rdtsc())来读取TSC。
  • 计算时间差:通过在程序的不同点读取TSC,可以计算两个时间点之间的时钟周期数。然后,将时钟周期数除以CPU频率(单位为Hz),可以得到时间差(单位为秒)。

(4)注意事项

  • 确保同步:在使用TSC之前,应检查处理器是否支持同步TSC。可以查询CPUID指令的相关字段或处理器规格文档来获取这些信息。
  • 考虑动态频率调整:在受到动态频率调整影响的处理器上使用TSC时,应将处理器设置为固定频率运行,或使用不受动态频率调整影响的计时器(如HPET)。
  • 多处理器系统:在多处理器系统中,应使用适用于多处理器系统的计时器(如HPET),或使用软件技术来解决不同处理器上TSC不同步的问题。
  • 考虑到CPU乱序执行的问题,rdtsc需要配合cpuid或lfence指令,以保证计这一刻流水线已排空,即rdtsc要测量的指令已执行完。后来的CPU提供了rdtscp指令,相当于cpuid + rdtsc,但cpuid指令本身的执行周期有波动,而rdtscp指令的执行更稳定。
  • 多核系统:新的CPU支持了Invariant TSC特性,可以保证在默认情况下各核心看到的TSC是一致的,否则测量代码执行时不能调度至其它核心上。
  • 时序测量容易被干扰(线程调度、抢占、系统中断、虚拟化等),要求测量的指令序列尽量短,并且需要进行多次测量

(5)常见问题

  • TSC不同步:多核处理器和多处理器系统中,不同CPU核上的TSC可能不完全同步。可以尝试使用软件技术进行校正,或使用其他计时器(如HPET)。
  • 动态频率调整:动态调节CPU频率可能导致TSC以不同速度增加。可以将处理器设置为固定频率运行,或使用不受动态频率调整影响的计时器(如HPET)。
  • 虚拟化环境:虚拟机中的TSC可能受到虚拟机监视器(hypervisor)的影响。在虚拟化环境中,建议使用虚拟化友好的计时器,如虚拟机监视器提供的虚拟化时钟(如KVM中的kvm-clock)。

4. TSC发展历史

参考文档:

  • 正确使用cpu提供的TSC - 知乎 (zhihu.com)
  • Pitfalls of TSC usage | Oliver Yang

最早CPU提供的TSC有很多弊端:

  • 频率受cpu频率影响,进入C-state的某些深度级别甚至会停止工作(不再跳动)
  • SMP架构下core间不同步,意味着一个core上的tsc与其它core上不一样,并且跳变的频率也不同。

后来Intel进行了增强(在CPU特性标识里面可以查看):

  • constant_tsc:含义是以固定的频率跳动,与cpu当前的频率无关。
  • nonstop_tsc:进入C-State也不会停止跳动。

基于这2个特性组合,称为 invariant tsc,即tsc是以理想中的恒定频率跳动,符合对时钟的假设。

SMP架构下不同步的问题,有内核来进行判定:

  • Linux 内核启动时,探测tsc是否同步,采用尝试校准多个核心上的tsc以相同的频率和起始值启动运行。
  • 通过写入MSR寄存器值来设置tsc的特性,需要cpu支持,目前仅仅intel的cpu才可能被认为是多核同步的

TSC的频率有以下的获取方式:

  • 通过CPUID中的一些寄存器值来计算,较新的cpu可以。
  • 通过读取MSR寄存器的值来计算,需要跟进不同的CPU model来读取不同的寄存器。
  • 通过读取内核export的符号 tsc_khz

如下是读取内核符号:

bpftrace -e 'BEGIN { printf("%u\n", *kaddr("tsc_khz")); exit(); }'

此外,内核计算和调整后的tsc freqency和经过硬件寄存器计算出来的不一定相同,因为内核会进行calibrate。

5. TSC多核时钟深入分析

参考文档:

  • 细说RDTSC的坑 – Dreamer Thinker Doer (wangkaixuan.tech)
  • linux - rdtsc accuracy across CPU cores - Stack Overflow
  • Porting x86架构的rdtsc函数到ARM64架构的方法 - 极术社区 - 连接开发者与智能计算生态 (aijishu.com)
  • x86 TSC使用的那些坑 - 爱你一万年123 - 博客园 (cnblogs.com)

在同一处理器的多个核心之间,以及不同处理器的不同核心之间,rdtsc的结果是否是同步的呢?如果不同步,那么取时的结果就不能用来相互比较。

关于这点,Intel的官方手册没有明说,如下:

The time stamp counter in newer processors may support an enhancement, referred to as invariant TSC. Processor’s support for invariant TSC is indicated by CPUID.80000007H:EDX[8].
The invariant TSC will run at a constant rate in all ACPI P-, C-. and T-states. This is the architectural behavior moving forward. On processors with invariant TSC support, the OS may use the TSC for wall clock timer services (instead of ACPI or HPET timers). TSC reads are much more efficient and do not incur the overhead associated with a ring transition or access to a platform resource.

只是说TSC能够在CPU处于任何(电源)状态下都能保证以标称速率递增,并没有明确说明TSC能够在多核甚至多处理器的情况下保持同步

在Linux内核启动时,对TSC(时间戳计数器,Time Stamp Counter)时钟进行处理的过程可分为以下几个步骤

  1. 检测TSC特性:内核首先使用CPUID指令来检测CPU是否支持TSC。如果CPU支持TSC,内核将继续检查其他TSC相关特性,例如:

    • 是否支持不变TSC(Invariant TSC):不变TSC在所有内核和处理器之间同步,且不受CPU频率和电源管理事件影响。
    • 是否支持恒定TSC(Constant TSC):恒定TSC在所有内核和处理器之间同步,但可能受到CPU频率调整的影响。
  2. 校准TSC:为了将TSC时钟周期转换为实际时间,内核需要知道CPU的时钟频率。在启动期间,内核将校准TSC,以便将其与实际时间对齐。校准过程通常涉及在一定时间间隔内计算TSC增量,然后根据这些增量推断出CPU时钟频率。

  3. 选择时钟源:Linux内核支持多种时钟源,例如TSC、HPET(高精度事件计时器,High Precision Event Timer)和ACPI Power Management Timer。在启动时,内核将根据可用时钟源的精度和性能选择最佳时钟源。如果TSC具有恒定或不变特性,并且表现出良好的性能和精度,内核可能会将其设置为默认时钟源。

  4. 同步多处理器系统中的TSC:在多处理器系统中,内核需要确保所有处理器上的TSC是同步的。内核将使用特定的同步算法(如校准时钟偏移)来尽量确保不同处理器上的TSC值保持一致。然而,这种同步并非总是完美的,因此在多处理器系统中使用TSC时需要小心。

  5. 初始化调度时钟:在内核启动过程中,它还需要初始化调度时钟,该时钟用于内核调度器来决定何时运行进程和线程。如果TSC被选为默认时钟源,内核将使用TSC来初始化和维护调度时钟。

内核检查TSC是否同步代码如下(X86结构):

/** Make an educated guess if the TSC is trustworthy and synchronized* over all CPUs.*/
int unsynchronized_tsc(void)
{if (!boot_cpu_has(X86_FEATURE_TSC) || tsc_unstable)return 1;#ifdef CONFIG_SMPif (apic_is_clustered_box())return 1;
#endifif (boot_cpu_has(X86_FEATURE_CONSTANT_TSC))return 0;if (tsc_clocksource_reliable)return 0;/** Intel systems are normally all synchronized.* Exceptions must mark TSC as unstable:*/if (boot_cpu_data.x86_vendor != X86_VENDOR_INTEL) {/* assume multi socket systems are not synchronized: */if (num_possible_cpus() > 1)return 1;}return 0;
}

从这段代码可以获取下面的信息:

  • 如果你的cpuinfo里有constant_tsc的flag,那么无论在同一CPU不同核心之间,还是在不同CPU的不同核心之间,TSC都是同步的,可以随便用。
  • 如果你用的是Intel的CPU,但是cpuinfo里没有constant_tsc的flag,那么在同一处理器的不同核心之间,TSC仍然是同步的,但是不同CPU的不同核心之间不同步,尽量不要用。
  • 在Intel CPU下还有一个注释“assume multi socket systems are not synchronized”,即在多处理器系统上,不同CPU(处理器、socket、NUMA节点)之间的TSC是不同步的。

Non-intel x86 platform has different stories. Current Linux kernel treats all non-intel SMP system as non-sync TSC system. See unsynchronized_tsc code in tsc.c. LKML also has the AMD documents.

非英特尔x86平台有不同的情况。当前Linux内核将所有非intel SMP系统视为非同步TSC系统

6. ARM架构的“TSC”时钟

参考文档:

  • Porting x86架构的rdtsc函数到ARM64架构的方法 - 极术社区 - 连接开发者与智能计算生态 (aijishu.com)
  • Arm Architecture Reference Manual for A-profile architecture
  • 华为鲲鹏云 KBengine arm64编译问题实践报告-云社区-华为云 (huaweicloud.com)
  • 【ARMv8】通用定时器总结_arm system counter_从善若水的博客-CSDN博客

ARM64架构(也称为ARMv8-A架构)引入了一种称为系统计数器(System Counter)的新组件,用于提供一个单调递增的计时器,以便在ARM64系统中实现精确的时间测量和调度。系统计数器在ARM64架构中相当重要,因为它为操作系统和应用程序提供了一个稳定、可靠的计时器。

在这里插入图片描述

系统计数器具有以下主要特点:

  1. 64位单调递增计数器:系统计数器是一个64位宽度的寄存器,它在每个时钟周期内递增。由于它是单调递增的,因此不会受到任何系统事件的影响,如电源管理事件或处理器休眠状态。

  2. 全局同步:系统计数器在所有处理器核心和处理器之间保持全局同步。这意味着,在多处理器系统中,不需要额外的同步机制以确保计数器的一致性。

  3. 基于架构的访问:ARM64架构提供了一组指令,以便操作系统和应用程序可以直接访问系统计数器。这些指令包括:

    • CNTFRQ_EL0:用于读取系统计数器的频率,以便将计数器值转换为实际时间。
    • CNTPCT_EL0:用于读取系统计数器的当前计数值。
  4. 异常级别访问控制:ARM64架构中的异常级别(EL)机制允许操作系统控制应用程序和其他操作系统组件对系统计数器的访问。例如,操作系统可以允许用户级别应用程序(在EL0运行)访问系统计数器,也可以将其限制在内核级别(在EL1或更高级别运行)。

  5. 虚拟化支持:ARM64架构还为虚拟化环境提供了系统计数器支持。在虚拟化环境中,宿主操作系统可以为每个虚拟机配置虚拟系统计数器,从而使它们能够使用类似于物理计数器的计时功能。

Linux内核在ARM64架构上使用该定时器来实现"TSC时钟"

u64 rdtsc(void)
{u64 val;/** According to ARM DDI 0487F.c, from Armv8.0 to Armv8.5 inclusive, the* system counter is at least 56 bits wide; from Armv8.6, the counter* must be 64 bits wide.  So the system counter could be less than 64* bits wide and it is attributed with the flag 'cap_user_time_short'* is true.*/asm volatile("mrs %0, cntvct_el0" : "=r" (val));return val;
}

system counter的精度一般不会超过100MHz,一般是达不到CPU cycle级别的精度。

因此还可以借助PMU系列寄存器中的PMCCNTR_EL0(需要内核开启使能),读取此寄存器就可以知道当前CPU已运行了多少cycle

7. 实际代码演示

7.1 x86_64架构DPDK获取TSC频率和cycle代码
  • timestamp - Getting TSC rate from x86 kernel - Stack Overflow

最简单的方法是通过dmesg消息来获取:

onceday->~:# dmesg  |grep tsc
[    0.000001] tsc: Detected 2995.199 MHz processor

其频率也和lscpu里面的BogoMIPS有关,是其二分之一:

BogoMIPS:            5990.39

其次也可以通过代码来获取,下面代码源自dpdk

/* SPDX-License-Identifier: BSD-3-Clause* Copyright(c) 2017 Intel Corporation*/
#include <stdio.h>
#include <stdint.h>#include <fcntl.h>
#include <unistd.h>
#include <cpuid.h>static unsigned int rte_cpu_get_model(uint32_t fam_mod_step)
{uint32_t family, model, ext_model;family = (fam_mod_step >> 8) & 0xf;model  = (fam_mod_step >> 4) & 0xf;if (family == 6 || family == 15) {ext_model = (fam_mod_step >> 16) & 0xf;model += (ext_model << 4);}return model;
}static int32_t rdmsr(int msr, uint64_t *val)
{int fd;int ret;fd = open("/dev/cpu/0/msr", O_RDONLY);if (fd < 0)return fd;ret = pread(fd, val, sizeof(uint64_t), msr);close(fd);return ret;
}static uint32_t check_model_wsm_nhm(uint8_t model)
{switch (model) {/* Westmere */case 0x25:case 0x2C:case 0x2F:/* Nehalem */case 0x1E:case 0x1F:case 0x1A:case 0x2E:return 1;}return 0;
}static uint32_t check_model_gdm_dnv(uint8_t model)
{switch (model) {/* Goldmont */case 0x5C:/* Denverton */case 0x5F:return 1;}return 0;
}uint64_t get_tsc_freq_arch(void)
{uint64_t tsc_hz = 0;uint32_t a, b, c, d, maxleaf;uint8_t  mult, model;int32_t  ret;/** Time Stamp Counter and Nominal Core Crystal Clock* Information Leaf*/maxleaf = __get_cpuid_max(0, NULL);printf("maxleaf: %d\n", maxleaf);if (maxleaf >= 0x15) {__cpuid(0x15, a, b, c, d);/* EBX : TSC/Crystal ratio, ECX : Crystal Hz */if (b && c)return c * (b / a);}__cpuid(0x1, a, b, c, d);model = rte_cpu_get_model(a);printf("model: %d\n", model);if (check_model_wsm_nhm(model))mult = 133;else if ((c & bit_AVX) || check_model_gdm_dnv(model))mult = 100;elsereturn 0;printf("mult: %d\n", mult);ret = rdmsr(0xCE, &tsc_hz);if (ret < 0)return 0;return ((tsc_hz >> 8) & 0xff) * mult * 1E6;
}/** C extension macro for environments lacking C11 features. */
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L
#define RTE_STD_C11 __extension__
#else
#define RTE_STD_C11
#endifuint64_t rte_rdtsc(void)
{union {uint64_t tsc_64;// RTE_STD_C11struct {uint32_t lo_32;uint32_t hi_32;};} tsc;#ifdef RTE_LIBRTE_EAL_VMWARE_TSC_MAP_SUPPORTif (unlikely(rte_cycles_vmware_tsc_map)) {/* ecx = 0x10000 corresponds to the physical TSC for VMware */asm volatile("rdpmc" : "=a"(tsc.lo_32), "=d"(tsc.hi_32) : "c"(0x10000));return tsc.tsc_64;}
#endifasm volatile("rdtsc" : "=a"(tsc.lo_32), "=d"(tsc.hi_32));return tsc.tsc_64;
}uint64_t rte_get_tsc_cycles(void)
{return rte_rdtsc();
}/*** Macro to align a value to the multiple of given value. The resultant* value will be of the same type as the first parameter and will be no lower* than the first parameter.*/
#define RTE_ALIGN_MUL_CEIL(v, mul) \((((v) + (typeof(v))(mul)-1) / ((typeof(v))(mul))) * (typeof(v))(mul))/*** Macro to align a value to the multiple of given value. The resultant* value will be of the same type as the first parameter and will be no higher* than the first parameter.*/
#define RTE_ALIGN_MUL_FLOOR(v, mul) (((v) / ((typeof(v))(mul))) * (typeof(v))(mul))/*** Macro to align value to the nearest multiple of the given value.* The resultant value might be greater than or less than the first parameter* whichever difference is the lowest.*/
#define RTE_ALIGN_MUL_NEAR(v, mul)                     \({                                                 \typeof(v) ceil  = RTE_ALIGN_MUL_CEIL(v, mul);  \typeof(v) floor = RTE_ALIGN_MUL_FLOOR(v, mul); \(ceil - (v)) > ((v)-floor) ? floor : ceil;     \})#define CYC_PER_10MHZ 1E7int main(void)
{uint64_t start, end, mhz;uint64_t tsc_hz = get_tsc_freq_arch();printf("tsc_hz: %lu\n", tsc_hz);start = rte_get_tsc_cycles();sleep(1);end = rte_get_tsc_cycles();printf("start_clock: %lu\n", start);printf("end_clock: %lu\n", end);printf("diff_clock: %lu\n", end - start);/* Round up to 10Mhz. 1E7 ~ 10Mhz */mhz = (end - start);mhz = RTE_ALIGN_MUL_NEAR(mhz, CYC_PER_10MHZ);printf("mhz: %lu Mhz\n", (uint64_t)(mhz/1E6));return 0;
}

编译运行后,输出如下(获取频率失败,只能走手动测量):

ubuntu->performance:$ ./tsc.out 
maxleaf: 13
model: 94
mult: 100
tsc_hz: 0
start_clock: 30482663960076192
end_clock: 30482687903183604
diff_clock: 23943107412
mhz: 2390 Mhz
7.2 ARM64架构获取TSC(System Counter)

获取cycle值和频率比较方便,直接使用汇编读取其值。

/** Read generic counter frequency */
static uint64_t __rte_arm64_cntfrq(void)
{uint64_t freq;asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));return freq;
}/** Read generic counter */
static uint64_t __rte_arm64_cntvct(void)
{uint64_t tsc;asm volatile("mrs %0, cntvct_el0" : "=r"(tsc));return tsc;
}
d_clock: 30482687903183604
diff_clock: 23943107412
mhz: 2390 Mhz

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

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

相关文章

反射之使用自定义注解并处理自定义注解

注解&#xff1a;说起注解初学者可能不太明白&#xff0c;annotation是jdk1.5 引入的新特性&#xff0c;中文名叫注解&#xff0c;它提供了一种安全的类似注释的机制&#xff0c;用来将任何的信息或元数据&#xff08;metadata&#xff09;与程序元素&#xff08;类、方法、成员…

java自定义注解解析及自定义注解

jdk1.5之后提供了注解&#xff08;Annotation&#xff09;这一种语法。其主要作用是编译检查&#xff08;比如override&#xff09;和代码分析&#xff08;通过代码中添加注解&#xff0c;利用注解解析器对添加了注解的代码进行分析&#xff0c;获取想要的结果&#xff0c;一般…

自定义注解开发

自定义注解的语法要求&#xff1a; Target({ElementType.METHOD,ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Inherited Documented public interface Description {String desc();String author();int age() default 18; } 首先我们要明确这不是一个接口&#x…

自定义注解(上)自定义注解的定义和检查

什么是注解&#xff1f; 注解是接口的一种变型&#xff0c;定义一组属性&#xff0c;让类、方法或属性用标记的方式调用这些属性。不仅是带有一些属性和值&#xff0c;某些注解带有一些特殊的功能。 如单元测试Test&#xff0c;可以让方法不依赖主函数单独运行&#xff0c;右…

自定义注解详解

文章引用 深入理解Java&#xff1a;注解&#xff08;Annotation&#xff09;自定义注解入门 自定义注解详细介绍 说在最前 文章忽略特性的一些基本概念 注解的本质是反射 1. 自定义注解 注解其实就是一种标记&#xff0c;可以在程序代码中的关键节点&#xff08;类、方法、…

查看 HTTP 请求的数据.

文章结构 如果是 GET 请求如果是 POST 请求方法1&#xff1a;DEBUG 窗口&#xff08;**爽、超级爽、吴迪爽**&#xff09;&#xff1a;方法2&#xff1a;写方法读取流中数据&#xff08;繁琐&#xff0c;难用&#xff09;&#xff1a; 我们可能会碰到 MVC 拿不到前端的参数&…

基于Html5的在线资料库的设计与实现(asp.NET,SQLServer)

在线资料库系统采用.NET开发平台进行开发&#xff0c;开发工具采用Microsoft Visual Studio 2010集成开发环境&#xff0c;后台编程语言采用C#编程语言来进行编程开发&#xff0c;数据库我们采用当下流行的SQL Server 2008数据库管理系统来存放平台中的数据信息&#xff0c;整个…

【软硬件测试】测试经验:软硬件结合测试要点

目录 一、应用行业 二、测试要点 三、硬件测试 &#xff08;1&#xff09;测试含义 &#xff08;2&#xff09;测试方法 &#xff08;3&#xff09;相关链接 四、结合测试 &#xff08;1&#xff09;测试含义 &#xff08;2&#xff09;测试工具 &#xff08;3&am…

【ros/ros2】LCN及ros2节点的LCN改写

文章目录 序言1. ros2两种节点类型2. LCN是什么3. LCN状态转换4. LCN状态转换要做的事5. LCN节点功能划分6. ros2节点的LCN改写 序言 背景&#xff1a;ros2节点改写为lifecycle node节点 1. ros2两种节点类型 Node&#xff1a;和ros1中一样的节点基类LifecycleNode&#xff…

桶排序 — 计数排序和基数排序

计数排序 int类型数组&#xff0c;其中存的是员工的年龄。比如说16 - 150。对于这样的数据来讲&#xff0c;数据状况是受限的。此时如果将数组从小到大进行排序&#xff0c;该如果实现&#xff1f; 这个实现很简单&#xff0c;实现一个统计数组范围从 0 ~ 150&#xff0c;遍历原…

816墨盒计算机无法与,816墨盒怎么加墨 816墨盒加墨方法及注意问题【详解】

导语&#xff1a;随着时代的快速发展&#xff0c;人们生活水平的不断提高&#xff0c;打印机在我们日常生活中的应用也变得非常广泛&#xff0c;利用打印机打印文件&#xff0c;还有一些重要的材料&#xff0c;方便了人们的生活&#xff0c;给人们的生活提供了很大的便利&#…

打印机 检测到用过的耗材或者赝品耗材

检测到用过的耗材或者赝品耗材 大家好&#xff0c;今天续着给大家分享下惠普的803/805墨盒加墨应该注意的事项&#xff0c;先预习&#xff0c;加墨就没那么多困惑了~ 加墨后打印白线条、溅墨怎么办&#xff1f; ①先用温水浸泡打印头约30秒&#xff08;注意不要泡到芯片&…

打印机墨盒问题

因为打印机墨盒属于耗材&#xff0c;容易损坏&#xff0c;从而造成打印机没法打印。对于家用打印机来说&#xff0c;一个打印机也就三四百块钱&#xff0c;然后换一个新墨盒就得花掉一百左右&#xff0c;心里感觉贼不爽&#xff0c;墨盒那么小一个&#xff0c;居然要那么贵&…

墨盒 连供漏墨恒压问题

你提出了一个连供压力平衡原理的问题。 连供形状各式各样&#xff0c;但基本原理都是相同的。 以红色为例&#xff1a; 如上图。打印机静止时&#xff0c;墨水室的墨水重力&#xff0c;等于墨水室上方因为空气变稀薄后产生的负压。墨水不会流动。实现了压力的静平衡。 打印机工…

【Java 并发编程】深入理解 AQS - AbstractQueuedSynchronizer

深入理解 AQS - AbstractQueuedSynchronizer 1. AQS1.1 什么是 AQS1.2 AQS 具备的特性 2. AQS 原理解析2.1 AQS 原理概述2.1.1 什么是 CLH 锁2.1.2 AQS 中的队列 2.2 AQS 共享资源的方式&#xff1a;独占式和共享式2.2.1 Exclusive&#xff08;独占式&#xff09;2.2.2 Share&a…

JVM学习笔记(中)

1、垃圾回收算法 标记清除法 特点&#xff1a; 速度较快会产生内存碎片 注意&#xff1a;这里的清除并不是真正意义上的清除&#xff0c;即每个字节都清0&#xff0c;而是记录一下被清除的对象的起始和结束的地址&#xff0c;当下一次分配给一个新对象时&#xff0c;新对象…

《Java并发编程实战》课程笔记(四)

互斥锁 原子性问题到底该如何解决呢&#xff1f; “同一时刻只有一个线程执行”这个条件非常重要&#xff0c;我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的&#xff0c;那么&#xff0c;无论是单核 CPU 还是多核 CPU&#xff0c;就都能保证原子性了。 锁模型 …

RK3588平台开发系列讲解(驱动基础篇)设备树常用 of 函数

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、查找节点的 of 函数二、获取属性值的 of 函数三、实验示例3.1、查找的节点代码3.2、获取属性内容代码沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 设备树描述了设备的详细信息,这些信息包括数字类型的…

chatgpt赋能python:Python中-1的用法介绍

Python中-1的用法介绍 什么是-1&#xff1f; 在Python中&#xff0c;-1是一个特殊的索引值&#xff0c;它表示从序列的末尾开始向前数1个元素。这在对于列表、字符串、元组等序列类型进行操作时非常有用。 如何使用-1&#xff1f; 假设我们有一个列表&#xff1a; l [1, …

SpringBoot框架理解

1 SpringBoot入门 1.2 什么是SpringBoot 1 官网的解释 ​ Spring在官方首页是这么说的&#xff1a;说使用SpringBoot可以构造任何东西&#xff0c;SpringBoot是构造所有基于Spring的应用程序的起点,SpringBoot在于通过最少的配置为你启动程序。 2 我的理解 SpringBoot是Sp…