Linux电源管理——CPUidle Framework
目录
前言
一、CPU idle
二、cpuidle framework 相关概念
三、cpuidle core 数据结构
3.1、cpuidle_state
3.2、cpuidle_driver
3.3、cpuidle_device
3.4、cpuidle_governor
四、cpuidle driver初始化流程
4.1、cpuidle driver 初始化方式
4.2、drv->states[0]的初始化
4.3、其它drv->states的初始化
4.4 register cpuidle driver/device
4.5、psci_enter_idle_state
五、cpuidle governor
5.1、governor 的分类和作用
5.2、代码分析
References
Linux Version:linux-6.1
前言
随着现在操作系统和芯片设计越来越复杂,产品的应用场景不同对cpu idle的要求也会不一样,有的产品可能要求 cpu idle时的功耗要低,但对退出 idle 时的延迟要求并不高,反之有的产品可能对系统恢复的时间(idle退出延迟)有很高的要求,但对功耗并没有要求,为了更好的平衡idle的功耗和延迟,所以就有了cpuidle framework,cpuidle framework 会根据系统的不同应用场景而选择不同 idle state,即本文主要就是针对 cpuidle framework 中相关的数据结构和相关函数进行分析,为后面分析 suspend to idle 打下基础。
一、CPU idle
在Linux系统中,CPU主要处理两类程序:一类是进程/线程,另一类是各种中断/异常的处理程序。为了节省电量,当系统中的 CPU 在处理完中断程序或没有进程任务需要运行时,该CPU就会进入一种空闲状态(idle state),即让 CPU 停止从内存获取指令。在空闲状态下,系统将消耗非常少的电量。
在ARM64架构中,当CPU Idle会调用WFI
指令(wait for interrupt
)让 CPU 进入低功耗状态,当有中断触发时,CPU又会恢复回来。
二、cpuidle framework 相关概念
一般情况下,在 CPU 进入空闲状态之前可能会有多种不同的空闲状态可以使用,所以此时需要找到当前系统最合适的一种,并让 CPU 进入该特定的空闲状态。而idle状态又会根据不同的省电程度而分成不同的idle等级,等级越深则省电越优,但是系统恢复越慢,反之如果想要系统恢复得比较快,则省电效果可能不太理想,因此需要结合用户的实际需求,选择进入哪个等级的idle状态。但是这些 idle 状态是怎样初始化的?系统中有哪些 idle 状态?是通过什么方式进行选择最合适一种 idle 状态的?这就是 cpuidle framework 的作用了。
cpuidle framework
通过向上给Scheduler/Sysfs
提供调用的接口,向下对应不同架构的CPU,屏蔽硬件层并抽象出调用接口,这也是 linux 系统常用的一种方法了。
下面是 cpuidle framework 的一张整体框架图,如下:
cpuidle core:cpuidle framework 框架的核心,向上层用户空间和 idle task 提供调用接口,如cpuidle_idle_call、call_cpuidle 等函数,向下层 cpuidle drivers/governors 提供统一的注册接口,比如 cpuidle_register_driver/cpuidle_unregister_driver,cpuidle_register_governor、cpuidle_switch_governor 函数等。
cpuidle drivers:负责每一种 idle state 的具体实现,向上为 cpuidle core 提供调用接口,向 cpuidle_governor 提供每一个 idle state 的一些基本信息,比如该 idle 的 “退出延迟“, “idle 功耗“ 等。不同的平台 cpuidle drivers 的实现方式不同,arm 架构中是在 cpuidle-arm.c,而 arm64 则是在 cpuidle-psci.c 文件中实现。
cpuidle governors:在之前就提到了,为了适应系统的不同使用场景会为CPU设置多种不同层级的idle state,这些idle state的主要区别主要有两点,一是idle时的功耗,这会体现出该idle state 是否真正达到了节省电量的目的,二是idle退出延迟,这会体现出该idle state resume 时的时间长短。在 cpuidle driver 中初始化每种idle state之后,它会把这些信息告诉 cpuidle governor,由governor根据系统具体的应用场景,决定要选用哪种idle状态。
简单的说就是当调度器发现没有Task在运行状态时,就会切换到Idle进程,此时通过cpuidle_idle_call
接口调到cpuidle framework
,cpuidle framework
会根据当前系统的情况选择合适的策略来决定进入哪种idle状态,最终回调到底层的平台实现。
三、cpuidle core 数据结构
cpuidle core
抽象出了三个数据结构:cpuidle device
用于描述CPU核,cpuidle driver
是针对CPU核的驱动而cpuidle governor
则主要根据cpuidle的device和driver状态来选择策略。
3.1、cpuidle_state
cpuidle core使用struct cpuidle_state结构体抽象cpu idle 的层级,如下:
// include/linux/cpuidle.h
struct cpuidle_state {char name[CPUIDLE_NAME_LEN];char desc[CPUIDLE_DESC_LEN];s64 exit_latency_ns;s64 target_residency_ns;unsigned int flags;unsigned int exit_latency; /* in US */int power_usage; /* in mW */unsigned int target_residency; /* in US */int (*enter) (struct cpuidle_device *dev,struct cpuidle_driver *drv,int index);int (*enter_dead) (struct cpuidle_device *dev, int index);/** CPUs execute ->enter_s2idle with the local tick or entire timekeeping* suspended, so it must not re-enable interrupts at any point (even* temporarily) or attempt to change states of clock event devices.** This callback may point to the same function as ->enter if all of* the above requirements are met by it.*/int (*enter_s2idle)(struct cpuidle_device *dev,struct cpuidle_driver *drv,int index);
};
name / desc:该idle state 的名字和描述
exit_latency_ns:退出该idle state 所需的延迟时间,单位是 ns。该变量决定了CPU从 idle 状态切换到运行状态的效率
target_residency_ns:期望CPU在该 idle 状态下停留的时间,单位是 ns
flags:idle state的特性标志
exit_latency:退出该 idle 状态所需的延迟时间,单位是 us
power_usage:CPU在该 idle 状态下的功耗,单位是 mW
target_residency:目标驻留时间,单位是 us
enter:CPU进入该idle状态的入口
enter_dead:通常用于深度休眠或关闭电源的场景
enter_s2idle:用于将CPU进入s2idle状态。如果 enter 函数满足和 enter_s2idle 函数一样的 idle 要求,则 enter_s2idle 可以指向和 enter 相同的函数。
cpuidle state的主要功能为定义一些关于 idle 的功耗、退出延迟等相关变量,并提供了进入该 idle state的回调函数。
3.2、cpuidle_driver
cpuidle core使用struct cpuidle_driver抽象cpuidle驱动,如下:
// include/linux/cpuidle.h
struct cpuidle_driver {const char *name;struct module *owner;/* used by the cpuidle framework to setup the broadcast timer */unsigned int bctimer:1;/* states array must be ordered in decreasing power consumption */struct cpuidle_state states[CPUIDLE_STATE_MAX];int state_count;int safe_state_index;/* the driver handles the cpus in cpumask */struct cpumask *cpumask;/* preferred governor to switch at register time */const char *governor;ANDROID_KABI_RESERVE(1);
};
name / owner:驱动名称和 驱动所有者
bctimer:1:广播计时器(broadcast timer),允许CPU在空闲状态下同步唤醒,以减少功耗
states:idle state 数组,包含了CPU idle driver支持的所有空闲状态
state_count:states 数组元素个数,即该 CPU 所支持的所有 idle state 个数
cpumask: 一个struct cpumask结构体类型的bit map指针,说明该driver支持哪些cpu core
governor:指定了在注册时首选的 governor
cpuidle_driver 结构体最主要是定义CPU所支持的cpuidle state
cpuidle_driver 的注册,销毁相关的函数如下:
// drivers/cpuidle/driver.c
int cpuidle_register_driver(struct cpuidle_driver *drv) //注册一个 cpuidle_driver
void cpuidle_unregister_driver(struct cpuidle_driver *drv) //注销一个 cpuidle_driver
struct cpuidle_driver *cpuidle_get_driver(void) //获取一个和当前 CPU 绑定的 cpuidle_driver
3.3、cpuidle_device
cpuidle device是一个虚拟设备,负责描述CPU和实现cpuidle相关的逻辑,linux kernel 使用struct cpuidle_device抽象cpuidle device
// include/linux/cpuidle.h
struct cpuidle_device {unsigned int registered:1;unsigned int enabled:1;unsigned int poll_time_limit:1;unsigned int cpu;ktime_t next_hrtimer;int last_state_idx;u64 last_residency_ns;u64 poll_limit_ns;u64 forced_idle_latency_limit_ns;struct cpuidle_state_usage states_usage[CPUIDLE_STATE_MAX];struct cpuidle_state_kobj *kobjs[CPUIDLE_STATE_MAX];struct cpuidle_driver_kobj *kobj_driver;struct cpuidle_device_kobj *kobj_dev;struct list_head device_list;#ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLEDcpumask_t coupled_cpus;struct cpuidle_coupled *coupled;
#endifANDROID_KABI_RESERVE(1);
};
registered:1:表示该idle devices 是否已经被注册
enabled:1:表示该idle devices 是否已经被启动
poll_time_limit:1:指示是否有限制轮询时间
cpu:表示这个cpuidle_device结构体关联的CPU编号
last_state_idx:上一次进入的CPU idle state 的索引
last_residency_ns:上一次在该CPU idle state 下停留的时间,单位为ns
poll_limit_ns:轮询限制时间
forced_idle_latency_limit_ns:强制空闲延迟限制时间
states_usage:记录了该设备的每个idle state的统计信息
kobjs/kobj_driver/kobj_dev:为用户空间创建 sysfs 接口
device_list:将cpuidle_device结构体添加到一个全局链表中
cpuidle_device 的注册,销毁相关的函数如下:
// drivers/cpuidle/cpuidle.c
int cpuidle_register_device(struct cpuidle_device *dev) //注册一个 cpuidle_device
void cpuidle_unregister_device(struct cpuidle_device *dev)//注销一个 cpuidle_device
void cpuidle_disable_device(struct cpuidle_device *dev) //禁用 cpuidle_device
int cpuidle_enable_device(struct cpuidle_device *dev) //开启 cpuidle_device
3.4、cpuidle_governor
cpuidle core使用struct cpuidle_governor结构抽象cpuidle governor,如下:
// include/linux/cpuidle.h
struct cpuidle_governor {char name[CPUIDLE_NAME_LEN];struct list_head governor_list;unsigned int rating;int (*enable) (struct cpuidle_driver *drv,struct cpuidle_device *dev);void (*disable) (struct cpuidle_driver *drv,struct cpuidle_device *dev);int (*select) (struct cpuidle_driver *drv,struct cpuidle_device *dev,bool *stop_tick);void (*reflect) (struct cpuidle_device *dev, int index);
};
name:governor的名字
governor_list:将该governor添加到一个全局的governors链表中
rating:该 CPU idle governor 的等级或评分,一般内核会根据rating来选择最合适的策略。
enable/disable:governor的enable/disable回调函数
select:结合当前系统的运行状况和各个idle state的特点,选择一个合适的 state,会返回该 state 的 idx 索引
reflect:更新governor,系统上一次的idle state是哪个,即 dev->last_state_idx = index
cpuidle_governor 的注册,销毁相关的函数如下:
// drivers/cpuidle/governor.c
int cpuidle_register_governor(struct cpuidle_governor *gov) //注册一个 governor
int cpuidle_switch_governor(struct cpuidle_governor *gov) //更改 governor
struct cpuidle_governor *cpuidle_find_governor(const char *str) //通过指定名称查找 governor
四、cpuidle driver初始化流程
4.1、cpuidle driver 初始化方式
本小节主要是对ARM64平台下的cpuidle driver进行分析,包括 cpuidle_state中的 idle state 初始化流程,因为 arm64 架构的 cpuidle _driver 是通过 psci 实现的,所以 cpuidle driver 文件在cpuidle-psci.c 文件中,如下:
// drivers/cpuidle/cpuidle-psci.c
static int __init psci_idle_init(void)
{struct platform_device *pdev;int ret;
// 注册驱动ret = platform_driver_register(&psci_cpuidle_driver);if (ret)return ret;pdev = platform_device_register_simple("psci-cpuidle", -1, NULL, 0);if (IS_ERR(pdev)) {platform_driver_unregister(&psci_cpuidle_driver);return PTR_ERR(pdev);}return 0;
}
device_initcall(psci_idle_init);
psci_cpuidle_driver 结构体:
static struct platform_driver psci_cpuidle_driver = {.probe = psci_cpuidle_probe,.driver = {.name = "psci-cpuidle",},
};
通过 psci_idle_init 函数可以看出在 arm64 架构中 cpuidle driver 中的初始化其实是通过平台总线进行匹配并开始初始化的,psci_cpuidle_probe 如下:
/** psci_idle_probe - Initializes PSCI cpuidle driver** Initializes PSCI cpuidle driver for all CPUs, if any CPU fails* to register cpuidle driver then rollback to cancel all CPUs* registration.*/
static int psci_cpuidle_probe(struct platform_device *pdev)
{int cpu, ret;struct cpuidle_driver *drv;struct cpuidle_device *dev;
// 遍历每一个可用的CPUfor_each_possible_cpu(cpu) {// 为每一个CPU初始化 cpuidle_driverret = psci_idle_init_cpu(&pdev->dev, cpu);if (ret)goto out_fail;}psci_idle_init_cpuhp();return 0;out_fail:while (--cpu >= 0) {dev = per_cpu(cpuidle_devices, cpu);drv = cpuidle_get_cpu_driver(dev);cpuidle_unregister(drv);psci_cpu_deinit_idle(cpu);}return ret;
}
psci_idle_init_cpu 函数如下:
static int psci_idle_init_cpu(struct device *dev, int cpu)
{struct cpuidle_driver *drv;struct device_node *cpu_node;const char *enable_method;int ret = 0;cpu_node = of_cpu_device_node_get(cpu);if (!cpu_node)return -ENODEV;/** Check whether the enable-method for the cpu is PSCI, fail* if it is not.*/enable_method = of_get_property(cpu_node, "enable-method", NULL);if (!enable_method || (strcmp(enable_method, "psci")))ret = -ENODEV;of_node_put(cpu_node);if (ret)return ret;drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);if (!drv)return -ENOMEM;drv->name = "psci_idle";drv->owner = THIS_MODULE;drv->cpumask = (struct cpumask *)cpumask_of(cpu);/** PSCI idle states relies on architectural WFI to be represented as* state index 0.*/drv->states[0].enter = psci_enter_idle_state;drv->states[0].exit_latency = 1;drv->states[0].target_residency = 1;drv->states[0].power_usage = UINT_MAX;strcpy(drv->states[0].name, "WFI");strcpy(drv->states[0].desc, "ARM WFI");/** If no DT idle states are detected (ret == 0) let the driver* initialization fail accordingly since there is no reason to* initialize the idle driver if only wfi is supported, the* default archictectural back-end already executes wfi* on idle entry.*/ret = dt_init_idle_driver(drv, psci_idle_state_match, 1);if (ret <= 0)return ret ? : -ENODEV;/** Initialize PSCI idle states.*/ret = psci_cpu_init_idle(dev, drv, cpu, ret);if (ret) {pr_err("CPU %d failed to PSCI idle\n", cpu);return ret;}ret = cpuidle_register(drv, NULL);if (ret)goto deinit;cpuidle_cooling_register(drv);return 0;
deinit:psci_cpu_deinit_idle(cpu);return ret;
}
psci_idle_init_cpu 函数首先会将设备树中的 cpu 节点通过 of_cpu_device_node_get 函数读取到 device_node *cpu_node 变量中,并通过 of_get_property 函数获取该 CPU 的启动方式,看是否为 PSCI,如果不是则返回错误。
然后对 cpuidle_driver 分配空间并初始化一些基本的变量,初始化 cpuidle_driver drv->name = “psci_idle”,这将以 sysfs 的形式体现在 /sys/devices/system/cpu/cpuidle/current_driver 文件中如下所示,然后还提供了默认的 WFI 状态,即 idle state[0],如果有其它 idle state,则需要通过DTS进行配置。
# cat /sys/devices/system/cpu/cpuidle/current_driver
psci_idle
4.2、drv->states[0]的初始化
在初始化完 cpuidle driver 的一些基本信息之后,会对 idle state 进行初始化,cpuidle state[0] 初始化如下:
/*
* PSCI idle states relies on architectural WFI to be represented as
* state index 0.
*/
drv->states[0].enter = psci_enter_idle_state;
drv->states[0].exit_latency = 1;
drv->states[0].target_residency = 1;
drv->states[0].power_usage = UINT_MAX;
strcpy(drv->states[0].name, "WFI");
strcpy(drv->states[0].desc, "ARM WFI");
这里将states[0].enter 回调初始化为 psci_enter_idle_state 函数,将 idle state[0].exit_latency 和 states[0].target_residency 都初始化为 1 (us),states[0].power_usage 初始化为 uint 类型的最大值 UINT_MAX,但这些值都只是一个相对值,因为对于其它的所有 idle state 的 power_usage 都不应该比 UINT_MAX 还要大了,其它 idle state 的 exit_latency 和 target_residency 都不应该比 1 还小了,因为 exit_latency 和 target_residency 是 unsigned int 类型的,这也是为了适应系统中各种 idle state 而设计,因为cpuidle_driver 不可能知道每一种 arm64 平台的 idle state 所有信息, 在实际应用中,这些值应该根据具体的硬件和系统需求进行调整和优化。
4.3、其它drv->states的初始化
初始化完 idle state[0]之后如果还有 idle state 需要初始化则从 state[1]开始需要通过 DTS 进行配置,如下:
/** If no DT idle states are detected (ret == 0) let the driver* initialization fail accordingly since there is no reason to* initialize the idle driver if only wfi is supported, the* default archictectural back-end already executes wfi* on idle entry.*/ret = dt_init_idle_driver(drv, psci_idle_state_match, 1);if (ret <= 0)return ret ? : -ENODEV;
通过注释可以看出如果没有在 DT 中检测到 idle state 即 (ret == 0),则驱动程序初始化将应该失败,因为如果仅支持 wfi,则没有理由初始化 idle driver 程序,因为架构已经有了默认的在空闲执行的 WFI。也就是说使用 cpuidle framework 时必须要通过 DTS 配置 idle state,否则 cpuidle_driver 会初始化失败。
dt_init_idle_driver 函数第二个参数 psci_idle_state_match 就是用来匹配 DTS 中的 state 节点的,三个参数表示 state的第几项,1表示从 DTS 初始化的 idle state 从 state[1]开始。psci_idle_state_match 如下:
static const struct of_device_id psci_idle_state_match[] = {{ .compatible = "arm,idle-state",.data = psci_enter_idle_state },{ },
};
在分析 dt_init_idle_driver 函数之前先了解一下 cpuidle state 的 DTS 节点长什么样,如下是以 rockchip 平台的 rk3399平台为例进行分析:
#arch/arm64/boot/dts/rockchip/rk3399.dtsi
cpus {#address-cells = <2>;#size-cells = <0>;cpu-map {cluster0 {core0 {cpu = <&cpu_l0>;};......};......};cpu_l0: cpu@0 {device_type = "cpu";compatible = "arm,cortex-a53";reg = <0x0 0x0>;enable-method = "psci";......cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>;};......idle-states {entry-method = "psci";CPU_SLEEP: cpu-sleep {compatible = "arm,idle-state";local-timer-stop;arm,psci-suspend-param = <0x0010000>;entry-latency-us = <120>;exit-latency-us = <250>;min-residency-us = <900>;};......};
};
通过上面的设备树文件可以看出该 CPU 的启动方式(enable-method)为 PSCI,在每个cpu 节点中,通过 cpu-idle-states 字段,指定该CPU所支持的idle states,比如这个CPU支持的 idle state 为 CPU_SLEEP 和 CLUSTER_SLEEP 两种,下面主要对 CPU_SLEEP state 进行分析,如下:
CPU_SLEEP: cpu-sleep {compatible = "arm,idle-state";local-timer-stop;arm,psci-suspend-param = <0x0010000>;entry-latency-us = <120>;exit-latency-us = <250>;min-residency-us = <900>;
};
idle state 的 compatible 属性都是 "arm,idle-state" ,这其实是为了个 psci_idle_state_match 结构体进行匹配,因为该结构体中的 compatible = "arm,idle-state" ,当想使用该 DTS node 时,compatible 需要相互匹配,具体细节后面再分析,entry-latency-us、exit-latency-us、min-residency-us 定义了该 idle state 的几个重要信息,后续会通过 dt_init_idle_driver 函数进行解析,并将数据保存到 cpuidle_state 的 idle_state 数组中。
结合上面对 DTS 的分析,下面对 dt_init_idle_driver函数进行简要分析,dt_init_idle_driver 函数如下:
// drivers/cpuidle/dt_idle_states.c
/*** dt_init_idle_driver() - Parse the DT idle states and initialize the* idle driver states array* @drv: Pointer to CPU idle driver to be initialized* @matches: Array of of_device_id match structures to search in for* compatible idle state nodes. The data pointer for each valid* struct of_device_id entry in the matches array must point to* a function with the following signature, that corresponds to* the CPUidle state enter function signature:** int (*)(struct cpuidle_device *dev,* struct cpuidle_driver *drv,* int index);** @start_idx: First idle state index to be initialized** If DT idle states are detected and are valid the state count and states* array entries in the cpuidle driver are initialized accordingly starting* from index start_idx.** Return: number of valid DT idle states parsed, <0 on failure*/
int dt_init_idle_driver(struct cpuidle_driver *drv,const struct of_device_id *matches,unsigned int start_idx)
{struct cpuidle_state *idle_state;struct device_node *state_node, *cpu_node;const struct of_device_id *match_id;int i, err = 0;const cpumask_t *cpumask;unsigned int state_idx = start_idx;if (state_idx >= CPUIDLE_STATE_MAX)return -EINVAL;/** We get the idle states for the first logical cpu in the* driver mask (or cpu_possible_mask if the driver cpumask is not set)* and we check through idle_state_valid() if they are uniform* across CPUs, otherwise we hit a firmware misconfiguration.*/cpumask = drv->cpumask ? : cpu_possible_mask;cpu_node = of_cpu_device_node_get(cpumask_first(cpumask));for (i = 0; ; i++) {state_node = of_get_cpu_state_node(cpu_node, i);if (!state_node)break;
// 检查一个设备节点state_node是否与给定的 matches 节点匹配,即 .compatible 是否相同match_id = of_match_node(matches, state_node);if (!match_id) {err = -ENODEV;break;}if (!of_device_is_available(state_node)) {of_node_put(state_node);continue;}if (!idle_state_valid(state_node, i, cpumask)) {pr_warn("%pOF idle state not valid, bailing out\n",state_node);err = -EINVAL;break;}if (state_idx == CPUIDLE_STATE_MAX) { // state_index 最大值pr_warn("State index reached static CPU idle driver states array size\n");break;}idle_state = &drv->states[state_idx++];err = init_state_node(idle_state, match_id, state_node);if (err) {pr_err("Parsing idle state node %pOF failed with err %d\n",state_node, err);err = -EINVAL;break;}of_node_put(state_node);}of_node_put(state_node);of_node_put(cpu_node);if (err)return err;/** Update the driver state count only if some valid DT idle states* were detected*/if (i)drv->state_count = state_idx;/** Return the number of present and valid DT idle states, which can* also be 0 on platforms with missing DT idle states or legacy DT* configuration predating the DT idle states bindings.*/return state_idx - start_idx;
}
EXPORT_SYMBOL_GPL(dt_init_idle_driver);
函数首先会将 start_idx 赋值给 state_idx ,作为 idle state 的索引,然后通过 of_cpu_device_node_get 和 of_get_cpu_state_node 函数获取该 CPU 的idle state node ,并调用 of_match_node 函数进行匹配,of_match_node 函数第一个参数为 matches 即传递进来的 psci_idle_state_match 结构体,第二个参数为 CPU 的idle state node,即看该结构体中的 compatible 成员和 DTS 中CPU 的idle state 节点的 compatible 属性是否匹配(compatible = "arm,idle-state"),如果匹配则继续往下。
idle_state = &drv->states[state_idx++];
这一段代码是在初始化 idle state 数组,state_idx = 1,所以是从 state[1] 开始初始化。
现在 DTS 中的 idle state node 已经找到了,现在就需要解析 node 中的数据,并初始化 idle_state 了,init_state_node 函数就是解析 DTS 并初始化 idle_state 的如下:
static int init_state_node(struct cpuidle_state *idle_state,const struct of_device_id *match_id,struct device_node *state_node)
{int err;const char *desc;/** CPUidle drivers are expected to initialize the const void *data* pointer of the passed in struct of_device_id array to the idle* state enter function.*/idle_state->enter = match_id->data;/** Since this is not a "coupled" state, it's safe to assume interrupts* won't be enabled when it exits allowing the tick to be frozen* safely. So enter() can be also enter_s2idle() callback.*/idle_state->enter_s2idle = match_id->data;err = of_property_read_u32(state_node, "wakeup-latency-us",&idle_state->exit_latency);if (err) {u32 entry_latency, exit_latency;err = of_property_read_u32(state_node, "entry-latency-us",&entry_latency);if (err) {pr_debug(" * %pOF missing entry-latency-us property\n",state_node);return -EINVAL;}err = of_property_read_u32(state_node, "exit-latency-us",&exit_latency);if (err) {pr_debug(" * %pOF missing exit-latency-us property\n",state_node);return -EINVAL;}/** If wakeup-latency-us is missing, default to entry+exit* latencies as defined in idle states bindings*/idle_state->exit_latency = entry_latency + exit_latency;}err = of_property_read_u32(state_node, "min-residency-us",&idle_state->target_residency);if (err) {pr_debug(" * %pOF missing min-residency-us property\n",state_node);return -EINVAL;}err = of_property_read_string(state_node, "idle-state-name", &desc);if (err)desc = state_node->name;idle_state->flags = 0;if (of_property_read_bool(state_node, "local-timer-stop"))idle_state->flags |= CPUIDLE_FLAG_TIMER_STOP;/** TODO:* replace with kstrdup and pointer assignment when name* and desc become string pointers*/strncpy(idle_state->name, state_node->name, CPUIDLE_NAME_LEN - 1);strncpy(idle_state->desc, desc, CPUIDLE_DESC_LEN - 1);return 0;
}
初始化 idle_state 的 enter 和 enter_s2idle 回调:
idle_state->enter = match_id->data;
idle_state->enter_s2idle = match_id->data;
这里 enter 和 enter_s2idle 指向的函数都是 match_id->data,也就是 psci_idle_state_match 中的 data 成员,即 psci_enter_idle_state 函数,和 state[0].enter 指向的函数相同。
然后就是解析 DTS 中关于 idle state 的各种信息,包括 entry-latency-us、exit-latency-us、min-residency-us等,并初始化 idle_state->exit_latency = entry_latency + exit_latency。
最后再将初始化 idle_state 的其它参数,包括 idle_state->flags、idle_state->name、idle_state->desc,如果一切顺利,init_state_node 函数将返回 0 。
当 init_state_node 成功返回 0 时,回到 dt_init_idle_driver 函数中还会更新 drv->state_count ,并返回通过 DTS 初始化了多少个 cpu idle state 如下:
/** Update the driver state count only if some valid DT idle states* were detected*/
if (i)drv->state_count = state_idx;
/** Return the number of present and valid DT idle states, which can* also be 0 on platforms with missing DT idle states or legacy DT* configuration predating the DT idle states bindings.*/
return state_idx - start_idx; // 如果 DTS 只初始化了一个 idle state 则 state_idx - start_idx = 2 - 1
4.4 register cpuidle driver/device
回到 psci_idle_init_cpu 函数中 dt_init_idle_driver 函数返回的就是通过 DTS 初始化的 idle state 的个数,当通过 DTS 初始化了一个 idle state 时,返回 1,当没有通过 DTS 初始化idle state或者初始化失败时, idle state 返回 0 , psci_idle_init_cpu 函数直接返回,cpuidle_driver 会初始化失败,这也就对前面的注释做了验证,如下代码段:
ret = dt_init_idle_driver(drv, psci_idle_state_match, 1);
if (ret <= 0)return ret ? : -ENODEV;
当 dt_init_idle_driver 函数成功返回时,最后会调用 cpuidle_register 对 cpuidle_driver 进行注册,并且还会在 cpuidle_register 函数中对 cpuidle_device 进行相应初始化和注册,如下:
cpuidle_register-> struct cpuidle_device *device;-> cpuidle_register_driver // 注册 cpuidle_driver-> device = &per_cpu(cpuidle_dev, cpu);-> device->cpu = cpu;...-> cpuidle_register_device // 注册 cpuidle_device
4.5、psci_enter_idle_state
psci_enter_idle_state 函数是进入cpuidle状态的入口函数,即 idle_state->enter,如下:
psci_enter_idle_state-> CPU_PM_CPU_IDLE_ENTER_PARAM(psci_cpu_suspend_enter, idx, state);-> __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, state, 0)
__CPU_PM_CPU_IDLE_ENTER 宏定义如下:
#define __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, \idx, \state, \is_retention) \
({ \int __ret = 0; \\if (!idx) { \cpu_do_idle(); \return idx; \} \\if (!is_retention) \__ret = cpu_pm_enter(); \if (!__ret) { \__ret = low_level_idle_enter(state); \if (!is_retention) \cpu_pm_exit(); \} \\__ret ? -1 : idx; \
})
可以看到当 idx = 0 ,即cpuidle_state states[0]时,会调用默认的 idle 函数 cpu_do_idle,然后直接返回了,cpu_do_idle 函数如下:
// arch/arm64/kernel/idle.c
/** cpu_do_idle()** Idle the processor (wait for interrupt).** If the CPU supports priority masking we must do additional work to* ensure that interrupts are not masked at the PMR (because the core will* not wake up if we block the wake up signal in the interrupt controller).*/
void noinstr cpu_do_idle(void)
{struct arm_cpuidle_irq_context context;arm_cpuidle_save_irq_context(&context);dsb(sy);wfi(); // 直接调用 wfi()函数等待中断arm_cpuidle_restore_irq_context(&context);
}
当 idx 不等于 0 时就会调用到 psci_cpu_suspend_enter 函数,如下:
int psci_cpu_suspend_enter(u32 state)
{int ret;if (!psci_power_state_loses_context(state)) {struct arm_cpuidle_irq_context context;arm_cpuidle_save_irq_context(&context);ret = psci_ops.cpu_suspend(state, 0);arm_cpuidle_restore_irq_context(&context);} else {ret = cpu_suspend(state, psci_suspend_finisher);}return ret;
}
cpu_suspend函数:
/** cpu_suspend** arg: argument to pass to the finisher function* fn: finisher function pointer**/
int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
{int ret = 0;unsigned long flags;struct sleep_stack_data state;struct arm_cpuidle_irq_context context;/* Report any MTE async fault before going to suspend */mte_suspend_enter();/** From this point debug exceptions are disabled to prevent* updates to mdscr register (saved and restored along with* general purpose registers) from kernel debuggers.*/flags = local_daif_save();/** Function graph tracer state gets inconsistent when the kernel* calls functions that never return (aka suspend finishers) hence* disable graph tracing during their execution.*/pause_graph_tracing();/** Switch to using DAIF.IF instead of PMR in order to reliably* resume if we're using pseudo-NMIs.*/arm_cpuidle_save_irq_context(&context);if (__cpu_suspend_enter(&state)) {/* Call the suspend finisher */ret = fn(arg);/** Never gets here, unless the suspend finisher fails.* Successful cpu_suspend() should return from cpu_resume(),* returning through this code path is considered an error* If the return value is set to 0 force ret = -EOPNOTSUPP* to make sure a proper error condition is propagated*/if (!ret)ret = -EOPNOTSUPP;} else {RCU_NONIDLE(__cpu_suspend_exit());}arm_cpuidle_restore_irq_context(&context);unpause_graph_tracing();/** Restore pstate flags. OS lock and mdscr have been already* restored, so from this point onwards, debugging is fully* reenabled if it was enabled when core started shutdown.*/local_daif_restore(flags);return ret;
}
在 cpu_suspend 函数中首先会通过__cpu_suspend_enter函数保存当前系统的状态,然后会回调 psci_suspend_finisher 函数,如下:
static noinstr int psci_suspend_finisher(unsigned long state)
{u32 power_state = state;phys_addr_t pa_cpu_resume;pa_cpu_resume = __pa_symbol_nodebug((unsigned long)cpu_resume);return psci_ops.cpu_suspend(power_state, pa_cpu_resume);
}
psci_suspend_finisher 函数中会调用底层的 psci_ops中的cpu_suspend回调,然后通过 smc 陷入 ATF 进行后续的工作 。
五、cpuidle governor
5.1、governor 的分类和作用
本文以menu governor为例,简单介绍cpuidle framework中的governor,在 linux-6.1 版本中有3 个 governor 分别为 ladder、menu、teo ,并且当前使用的 menu 如下:
# cat /sys/devices/system/cpu/cpuidle/available_governors
ladder menu teo
# cat /sys/devices/system/cpu/cpuidle/current_governor
menu
通过前面的简单了解,知道 governor 的主要作用是根据系统目前的运行情况,根据具体的算法选择一个合适idle state,在选择时一般有两点需要考虑,一是要考虑切换到 idle state 停留的时间所节省的功耗需要大于状态切换时所产生的功耗,通过 cpuidle_state 中的 target_residency 来体现,即该进入该 idle state 所停留的时间应该是大于 target_residency 的,不然并不能达到节省的效果。二是要考虑退出时的延迟大小,当idle state 在满足系统功耗之后会尽量选择延迟低的 idle state。
5.2、代码分析
// drivers/cpuidle/governors/menu.c
static struct cpuidle_governor menu_governor = {.name = "menu",.rating = 20,.enable = menu_enable_device,.select = menu_select,.reflect = menu_reflect,
};/*** init_menu - initializes the governor*/
static int __init init_menu(void)
{return cpuidle_register_governor(&menu_governor);
}
governor name 可以通过 /sys/devices/system/cpu/cpuidle 文件夹下的相关governor文件体现,并初始化了 enable、select、reflect 三个回调函数。
5.2.1、menu_enable_device
menu_enable_device 函数主要是做启动的前期工作
/*** menu_enable_device - scans a CPU's states and does setup* @drv: cpuidle driver* @dev: the CPU*/
static int menu_enable_device(struct cpuidle_driver *drv,struct cpuidle_device *dev)
{struct menu_device *data = &per_cpu(menu_devices, dev->cpu);int i;memset(data, 0, sizeof(struct menu_device));/** if the correction factor is 0 (eg first time init or cpu hotplug* etc), we actually want to start out with a unity factor.*/for(i = 0; i < BUCKETS; i++)data->correction_factor[i] = RESOLUTION * DECAY;return 0;
}
5.2.2、menu_select
/*** menu_update - attempts to guess what happened after entry* @drv: cpuidle driver containing state data* @dev: the CPU*/
static void menu_update(struct cpuidle_driver *drv, struct cpuidle_device *dev)
{struct menu_device *data = this_cpu_ptr(&menu_devices);int last_idx = dev->last_state_idx;struct cpuidle_state *target = &drv->states[last_idx];u64 measured_ns;unsigned int new_factor;/** Try to figure out how much time passed between entry to low* power state and occurrence of the wakeup event.** If the entered idle state didn't support residency measurements,* we use them anyway if they are short, and if long,* truncate to the whole expected time.** Any measured amount of time will include the exit latency.* Since we are interested in when the wakeup begun, not when it* was completed, we must subtract the exit latency. However, if* the measured amount of time is less than the exit latency,* assume the state was never reached and the exit latency is 0.*/if (data->tick_wakeup && data->next_timer_ns > TICK_NSEC) {/** The nohz code said that there wouldn't be any events within* the tick boundary (if the tick was stopped), but the idle* duration predictor had a differing opinion. Since the CPU* was woken up by a tick (that wasn't stopped after all), the* predictor was not quite right, so assume that the CPU could* have been idle long (but not forever) to help the idle* duration predictor do a better job next time.*/measured_ns = 9 * MAX_INTERESTING / 10;} else if ((drv->states[last_idx].flags & CPUIDLE_FLAG_POLLING) &&dev->poll_time_limit) {/** The CPU exited the "polling" state due to a time limit, so* the idle duration prediction leading to the selection of that* state was inaccurate. If a better prediction had been made,* the CPU might have been woken up from idle by the next timer.* Assume that to be the case.*/measured_ns = data->next_timer_ns;} else {/* measured value */measured_ns = dev->last_residency_ns;/* Deduct exit latency */if (measured_ns > 2 * target->exit_latency_ns)measured_ns -= target->exit_latency_ns;elsemeasured_ns /= 2;}/* Make sure our coefficients do not exceed unity */if (measured_ns > data->next_timer_ns)measured_ns = data->next_timer_ns;/* Update our correction ratio */new_factor = data->correction_factor[data->bucket];new_factor -= new_factor / DECAY;if (data->next_timer_ns > 0 && measured_ns < MAX_INTERESTING)new_factor += div64_u64(RESOLUTION * measured_ns,data->next_timer_ns);else/** we were idle so long that we count it as a perfect* prediction*/new_factor += RESOLUTION;/** We don't want 0 as factor; we always want at least* a tiny bit of estimated time. Fortunately, due to rounding,* new_factor will stay nonzero regardless of measured_us values* and the compiler can eliminate this test as long as DECAY > 1.*/if (DECAY == 1 && unlikely(new_factor == 0))new_factor = 1;data->correction_factor[data->bucket] = new_factor;/* update the repeating-pattern data */data->intervals[data->interval_ptr++] = ktime_to_us(measured_ns);if (data->interval_ptr >= INTERVALS)data->interval_ptr = 0;
}/*** menu_select - selects the next idle state to enter* @drv: cpuidle driver containing state data* @dev: the CPU* @stop_tick: indication on whether or not to stop the tick*/
static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,bool *stop_tick)
{struct menu_device *data = this_cpu_ptr(&menu_devices);s64 latency_req = cpuidle_governor_latency_req(dev->cpu);......if (data->needs_update) {menu_update(drv, dev); // 更新 munu governordata->needs_update = 0; // 该标志位会在 menu_reflect 函数中置位}....../** Don't stop the tick if the selected state is a polling one or if the* expected idle duration is shorter than the tick period length.*/if (((drv->states[idx].flags & CPUIDLE_FLAG_POLLING) ||predicted_ns < TICK_NSEC) && !tick_nohz_tick_stopped()) {*stop_tick = false;if (idx > 0 && drv->states[idx].target_residency_ns > delta_tick) {/** The tick is not going to be stopped and the target* residency of the state to be returned is not within* the time until the next timer event including the* tick, so try to correct that.*/for (i = idx - 1; i >= 0; i--) {if (dev->states_usage[i].disable)continue;idx = i;if (drv->states[i].target_residency_ns <= delta_tick)break;}}}return idx;
}
menu_select 会根据系统当前的运行情况,选择一个合适的 idle state,并返回相应 state 的 idx,并且更新 governor 也是在该函数中通过 menu_update 函数实现的,但为什么从 idle state 返回时需要更新 munu governor 呢? 其主要原因是为了下一次选择 idle state 的准确性,它根据CPU 进入和退出 idle state 的时间,来更新校正因子(correction_factor),以改进CPU idle 时间的预测准确性,提高电源管理效率。这里具体是怎么选择 idle state 的将不再具体分析。
5.2.3、menu_reflect
/*** menu_reflect - records that data structures need update* @dev: the CPU* @index: the index of actual entered state** NOTE: it's important to be fast here because this operation will add to* the overall exit latency.*/
static void menu_reflect(struct cpuidle_device *dev, int index)
{struct menu_device *data = this_cpu_ptr(&menu_devices);dev->last_state_idx = index;data->needs_update = 1;data->tick_wakeup = tick_nohz_idle_got_tick();
}
menu_reflect 主要是通过 data->last_state_idx记录了上次进入 idle state 的 idx 索引值,和置位data->needs_update,实现具体更新状态的代码是放到 menu_select 函数中去了,因为该函数的调用是在退出 idle state 之前调用的,所以这里速度很重要,要尽量的快,不然会增加 idle 整体的 exit latency。
References
[1] CPU Idle Time Management — The Linux Kernel documentation
[2] http://www.wowotech.net/pm_subsystem/cpuidle_overview.html
[3] http://www.wowotech.net/pm_subsystem/cpuidle_arm64.html
[4] https://blog.csdn.net/weixin_48185168/article/details/133576463
[5] https://zhuanlan.zhihu.com/p/539722367
[6] https://blog.csdn.net/youthcowboy/article/details/135348079
[7] https://m.elecfans.com/article/2313635.html
[8] https://zhuanlan.zhihu.com/p/572644843
[9] https://www.cnblogs.com/LoyenWang/p/11379937.html
相关文章:
Linux电源管理——CPUidle Framework
目录 前言 一、CPU idle 二、cpuidle framework 相关概念 三、cpuidle core 数据结构 3.1、cpuidle_state 3.2、cpuidle_driver 3.3、cpuidle_device 3.4、cpuidle_governor 四、cpuidle driver初始化流程 4.1、cpuidle driver 初始化方式 4.2、drv->states[0]的初…...
【黑灰产】假钱包推广套路
假钱包推广产业链研究 市面上钱包的主要推广方式: 1,竞价(搜索引擎),误导客户为真正官方钱包从而完成下载使用 优点:精准,客户大 缺点:竞价户容易挂,投资大 2࿰…...
联想java开发面试题及参考答案
IP 协议是哪一层的? IP 协议(Internet Protocol)属于网络层协议。 网络层主要负责将数据从源节点传输到目标节点,它在整个网络通信体系中起到了承上启下的关键作用。在分层网络模型中,下层(如数据链路层)为网络层提供物理链路的连接和帧传输服务。数据链路层关注的是在相…...
C# 继承(接口)
接口 如果一个类派生与一个接口,它就会执行某些函数。并不是所有的面向对象语言都支持接口。 熟悉COM的开发人员应注意,尽管在概念上C#接口类似于COM接口,但他们是不筒的,底层的结构不筒。比如,C#接口并不派生于IUnko…...
FPGA的 基本结构(Xilinx 公司Virtex-II 系列FPGA )
以Xilinx 公司Virtex-II 系列FPGA 为例,其基本结构由下图所示。它是主要由两大部分组成:可编程输入/输出(Programmable I/Os)部分和内部可配置(Configurable Logic)部分。 可编程输入/输出(I/Os…...
妙用编辑器:把EverEdit打造成一个编程学习小环境
1 妙用编辑器:把EverEdit打造成一个编程学习小环境 1.1 应用场景 最近在学习Python语言,由于只是学习和练习,代码规模很小,不想惊动PyCharm、VSCode、WingIDE这些重型武器,只想轻快的敲些代码,记事本虽好&…...
ELK日志分析实战宝典之ElasticSearch从入门到服务器部署与应用
目录 ELK工作原理展示图 一、ElasticSearch介绍(数据搜索和分析) 1.1、特点 1.2、数据组织方式 1.3、特点和优势 1.3.1、分布式架构 1.3.2、强大的搜索功能 1.3.3、数据处理与分析 1.3.4、多数据类型支持 1.3.5、易用性与生态系统 1.3.6、高性…...
【学习笔记】理解深度学习和机器学习的数学基础:数值计算
深度学习作为人工智能领域的一个重要分支,其算法的实现和优化离不开数值计算。数值计算在深度学习中扮演着至关重要的角色,它涉及到如何在计算机上高效、准确地解决数学问题。本文将介绍深度学习中数值计算的一些关键概念和挑战,以及如何应对…...
【Java回顾】Day5 并发基础|并发关键字|JUC全局观|JUC原子类
JUC全称java.util.concurrent 处理并发的工具包(线程管理、同步、协调) 一.并发基础 多线程要解决什么问题?本质是什么? CPU、内存、I/O的速度是有极大差异的,为了合理利用CPU的高性能,平衡三者的速度差异,解决办法…...
VSCODE使用Echarts组件库(不是vue)
第一步打开Echarts官网 Examples - Apache ECharts 第二步随便点击一个图形点击我圈的按钮 第三步...
DNS解析域名简记
域名通常是由: 权威域名.顶级域名.根域名组成的。 从左往右,级别依次升高,这和外国人从小范围到大范围的说话习惯相关。(我们自己是更习惯先说大范围再说小范围,如XX省XX市XX区XX路) DNS解析域名时,会先查…...
选择器css
1.a标签选择 // 选中所具有herf 的元素 [herf] {color: skyblue; } // 选中所具有herfhttps://fanyi.youdao.com/ 的元素 [herf$"youdao.com"] {color:pink; } // 按此顺序书写 link visited hover active // 未访问状态 a:link {color:orange } // 访问状态 a…...
9.4 visualStudio 2022 配置 cuda 和 torch (c++)
一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客,作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为: 2.vs…...
ASP.NET Core 实现微服务 -- Polly 服务降级熔断
在我们实施微服务之后,服务间的调用变的异常频繁。多个服务之间可能是互相依赖的关系。某个服务出现故障或者是服务间的网络出现故障都会造成服务调用的失败,进而影响到某个业务服务处理失败。某一个服务调用失败轻则造成当前相关业务无法处理࿱…...
2_CSS3 背景 --[CSS3 进阶之路]
CSS3 中的背景属性提供了许多强大的功能来增强网页设计,包括但不限于多背景图像、渐变、背景大小控制等。以下是一些关键的 CSS3 背景属性及其用法示例。 1. 多重背景图像 CSS3 允许你为一个元素设置多个背景图像。这些图像按照它们在 background-image 属性中定义…...
于交错的路径间:分支结构与逻辑判断的思维协奏
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。* 这一节内容很多,文章字数达到了史无前例的一万一,我们要来学习分支与循环结构中…...
升级 CentOS 7.x 系统内核到 4.4 版本
问题描述 在 CentOS 7.x 系统中,默认内核版本是 3.10.x,这个版本可能会带来一些与 Docker 和 Kubernetes 兼容性的问题,导致系统性能不稳定或功能异常。为了提高系统的稳定性和兼容性,建议升级到更高版本的内核,例如 …...
MySQL数据导出导入
一、数据导出 1.导出全库备份到本地的目录 mysqldump -u$USER -p$PASSWD -h127.0.0.1 -P3306 --routines--default-character-setutf8 --lock-all-tables --add-drop-database -A >db.all.sql 2.导出指定库到本地的目录(例如mysql库) mysqldump -u$USER -p$PASSWD -h127.…...
【机器学习:八、逻辑回归】
逻辑回归(Logistic Regression) 1. 逻辑回归的引出 在现实世界中,许多问题都涉及到分类任务。例如: 判断一封邮件是否为垃圾邮件;预测某人是否会患某种疾病;确定图片中是否包含某种特定物体。 这些问题…...
uniapp使用sm4加密
安装:npm install sm-crypto --save 1、在utils下新建crypto.js文件 // sm4 加密 export function encryption(params) {const SM4 require("sm-crypto").sm4const key 0123456789abcdeffedcba9876543212; // 提供的密钥const iv fedcba9876543210012…...
【STM32-学习笔记-1-】GPIO
文章目录 GPIOⅠ、GPIO函数Ⅱ、GPIO_InitTypeDef结构体参数①、GPIO_Mode②、GPIO_Pin③、GPIO_Speed GPIO Ⅰ、GPIO函数 // 将指定的GPIO端口寄存器重置为默认值 void GPIO_DeInit(GPIO_TypeDef* GPIOx);// 将GPIO的备用功能寄存器重置为默认值 void GPIO_AFIODeInit(void);…...
C#中的运算符和类--06
目录 一.运算符 1.赋值运算符 2.算数运算符 3.关系运算符 4.逻辑运算符 5.位运算符 6.三元运算符 7.空合并运算符 8.其他运算符 二.类 1.普通类 2.静态类 3.抽象类 4.密封类 5.部分类 6.泛型类 7.嵌套类 8.记录类 9.接口 一.运算符 1.赋值运算符 定义:赋值…...
【微服务】面试 2、服务雪崩
服务雪崩概念 主要内容:在微服务项目中,微服务间存在远程调用。若某一服务(如服务 d)出现故障,调用它的服务(如服务 a)会失败。若调用方持续向故障服务发起请求,由于服务连接数有限且…...
“深入浅出”系列之QT:(6)如何在一个项目中调用另一个项目
在Qt中,如果想在一个项目中调用另一个项目,这通常意味着想要在一个CMake构建的项目中集成或依赖另一个CMake构建的项目。 1.子模块或子目录方式: 如果另一个项目可以作为一个子模块或子目录包含在当前项目中,可以使用add_sub…...
计算机网络—地址与子网(IPv4)相关知识总结
前言 为了更加清楚的了解该相关知识,下面是发现的一些宝藏博主的博客。 彻底搞懂网络地址、广播地址、主机地址、网关、子网掩码、网络号、主机号 - lipga - 博客园 IP地址(分类)、子网掩码、网络号、主机号、子网号_网络号,主机号,子网号…...
计算机网络 (36)TCP可靠传输的实现
前言 TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP通过多种机制实现可靠传输,这些机制主要包括连接管理、序列号和确认应答机制、重传机制、流量控制、拥塞控制等。 一、连接管理 TCP使用三次握手࿰…...
SQL从入门到实战-2
高级语句 窗口函数 排序窗口函数 例题二十九 select yr,party,votes, rank() over (PARTITION BY yr ORDER BY votes desc) as pson from ge where constituency S14000021 order by party,yr 偏移分析函数 例题三十 select name,date_format(whn,%Y-%m-%d) data, confi…...
基于python的网页表格数据下载--转excel
基于 Python 的网页表格数据爬取与下载:以维基百科为例 目录 基于 Python 的网页表格数据爬取与下载:以维基百科为例1. 背景介绍2. 工具与环境3. 操作步骤1. 获取网页内容2. 定位表格元素3. 表格变身 Pandas DataFrame4. 检查数据,收工!5. 进阶玩法与优化6. 完整代码4. 结果…...
用户界面的UML建模13
􀂄 Concrete Presentation Model 包中所包含的是,在Environment 包中与表示层框架模式中的《apm》类相对应的那些类。 8 结论 本文使用了一个图书馆系统的案例,来论述了关于用户界面的建模。通过使用统一建模语言来对应用系统进行建模&…...
[Python学习日记-75] 计算机基础与网络
[Python学习日记-75] 计算机基础与网络 简介 计算机基础 什么是网络编程 计算机网络 简介 本篇主要介绍的计算机基础是浓缩的,这是因为我们主要学习的是 Python,而 Python 主要是为了开发应用程序的,并不会用它来开发操作系统和嵌入式程序…...
【机器学习:六、特征工程】
1. 特征工程背景意义 在机器学习中,特征工程是模型成功的关键之一。无论算法多么先进,其性能都很大程度上依赖于输入数据的质量。特征工程是指对原始数据进行处理,以创建更适合算法的特征的过程。这一过程在以下方面具有重要意义:…...
webpack打包要义
webpack基本 Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。它的工作原理可以概括为以下几个核心步骤: 1. 入口起点(Entry) Webpack 从配置文件中指定的入口文件(Entry Point)开始,分析应用…...
Mybatis——Mybatis开发经验总结
摘要 本文主要介绍了MyBatis框架的设计与通用性,阐述了其作为Java持久化框架的亮点,包括精良的架构设计、丰富的扩展点以及易用性和可靠性。同时,对比了常见持久层框架,分析了MyBatis在关系型数据库交互中的优势。此外࿰…...
013:深度学习之神经网络
本文为合集收录,欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 深度学习是机器学习中重要的一个学科分支,它的特点就在于需要构建多层且“深度”的神经网络。 人们在探索人工智能初期,就曾设想构建一个用数学方式…...
Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser)
Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser) 思路字符串替换器 思路 模板变量替换无非是寻找出字符串(模板)中的特殊标记,用对应的变量进行字符串替换。 提到变量替换,大家第一能…...
蓝桥杯备考:数据结构之栈 和 stack
目录 栈的概念以及栈的实现 STL 的stack 栈和stack的算法题 栈的模板题 栈的算法题之有效的括号 验证栈序列 后缀表达式 括号匹配 栈的概念以及栈的实现 栈是一种只允许在一端进行插入和删除的线性表 空栈:没有任何元素 入栈:插入元素消息 出…...
Lambda离线实时分治架构深度解析与实战
一、引言 在大数据技术日新月异的今天,Lambda架构作为一种经典的数据处理模型,在应对大规模数据应用方面展现出了强大的能力。它整合了离线批处理和实时流处理,为需要同时处理批量和实时数据的应用场景提供了成熟的解决方案。本文将对Lambda…...
Vue.js组件开发,AI时代的前端新玩法
AI可不只是写写小说、聊聊天,现在它的触角已经伸到了程序员的代码世界里。特别是前端开发,很多人都在尝试用ChatGPT或者类似的AI工具来写代码,甚至直接生成Vue.js组件。有些人感叹,"写代码的时代是不是要结束了?&…...
标定 3
标定场景与对应的方式 标定板标定主要应用场景: (1)无法获取到执行机构物理坐标值,比如相机固定,执行机构为传送带等 (2)相机存在畸变等非线性标定情况,需要进行畸变校正 (3)标定单像素精度 (4)获取两个相机之间的坐标系关系 标定板操作步骤: (1)确定好拍…...
电商项目-基于ElasticSearch实现商品搜索功能(三)
本系列文章主要介绍基于 Spring Data Elasticsearch 实现商品搜索的后端代码,介绍代码逻辑和代码实现。 主要实现功能:根据搜索关键字查询、条件筛选、规格过滤、价格区间搜索、搜索查询分页、搜索查询排序、高亮查询。 主要应用技术:canal,…...
【51单片机】03 蜂鸣器-播放音乐
蜂鸣器-播放音乐 一、原理介绍1.硬件电路 二、练习1.让蜂鸣器发声2.尝试演奏小星星 一、原理介绍 蜂鸣器分为有源蜂鸣器、无源蜂鸣器两种。 有源蜂鸣器:施加合适的电压之后就会发出特定频率的声音 无源蜂鸣器:需要提供特定频率的声音信号,才能…...
MySQL 架构
MySQL架构 MySQL8.0服务器是由连接池、服务管理⼯具和公共组件、NoSQL接⼝、SQL接⼝、解析器、优化 器、缓存、存储引擎、⽂件系统组成。MySQL还为各种编程语⾔提供了⼀套⽤于外部程序访问服务器的连接器。整体架构图如下所⽰: MySQL Connectors:为使⽤…...
XML 解析器:深入解析与高效应用
XML 解析器:深入解析与高效应用 引言 XML(可扩展标记语言)作为一种重要的数据交换格式,被广泛应用于各种系统和平台中。为了有效地处理和解析XML数据,XML解析器发挥着至关重要的作用。本文将深入探讨XML解析器的原理…...
LabVIEW设计 IIR 滤波器
这是一个设计 IIR 滤波器的 LabVIEW 程序框图,其功能主要是用于设计滤波器并计算其频率响应,但它并不直接对输入的波形进行实时滤波,而是提供一个滤波器的频率响应分析工具。 以下是框图中各部分的详细解释: 1. 主要模块功能说明 …...
基于改进粒子群优化的无人机最优能耗路径规划
目录 1. Introduction2. Preliminaries2.1. Particle Swarm Optimization Algorithm2.2. Deep Deterministic Policy Gradient2.3. Calculation of the Total Output Power of the Quadcopter Battery 3.OptimalEnergyConsumptionPathPlanningBasedonPSO-DDPG3.1.ProblemModell…...
AI刷题-数列推进计算任务、数组中的幸运数问题
目录 一、数列推进计算任务 问题描述 测试样例 解题思路: 问题理解 数据结构选择 算法步骤 优化思路 最终代码: 运行结果: 二、数组中的幸运数问题 问题描述 测试样例 解题思路: 问题理解 数据结构选择 算法步…...
微服务的配置共享
1.什么是微服务的配置共享 微服务架构中,配置共享是一个重要环节,它有助于提升服务间的协同效率和数据一致性。以下是对微服务配置共享的详细阐述: 1.1.配置共享的概念 配置共享是指在微服务架构中,将某些通用或全局的配置信息…...
【计算机网络】窥探计网全貌:说说计算机网络体系结构?
标签难度考察频率综合题⭐⭐⭐60% 这个问题在计算机网络知识体系中是一个比较重要的问题,只有完整地了解计算机网络的体系结构才能清晰地认识网络的运行原理。 在回答这个问题时,笔者认为有几个比较重要的点: 首先一定要分清楚前置条件&am…...
【MySQL】DATEDIFF()函数使用
DATEDIFF 函数用于计算两个日期之间的差值,以天为单位 DATEDIFF 函数返回一个整数,表示 date1 和 date2 之间的天数。如果 date1 在 date2 之前,结果为负数;如果在 date2 之后,结果为正数;如果相等…...
计算机网络学习笔记
第1课 绪论、传输介质 【知识点回顾】 两种导线可以减小电磁干扰: 双绞线(分为非屏蔽双绞线、屏蔽双绞线)(RJ-45用)同轴电缆(短距离使用)网络通信的基本单位:位(bit&…...