赵亚楠:LinuxCFS调度器原理、设计与内核实现(2023)

Linux阅码场 2023-02-20 08:00

整理一些 Linux 默认调度器 CFS 相关的东西。CFS、cgroup 等内核技术合力实现了进程的 CPU 资源限额(CPU 带宽控制),这是容器的基础之一。


  • 1 概念及关系

    • 1.3.1 CFS 存在的问题

    • 1.3.2 CONFIG_CFS_BANDWIDTH

    • 1.2.1 前提:CONFIG_CGROUPS

    • 1.2.2 前提:CONFIG_CGROUP_SCHED

    • 1.2.3 扩展:支持实时进程组(CONFIG_RT_GROUP_SCHED

    • 1.2.4 扩展:支持常规进程组(CONFIG_FAIR_GROUP_SCHED

    • 1.1 CFS:进程(task)的公平调度

    • 1.2 CFS 扩展

    • 1.3 常规进程组 CFS 再扩展:支持 CPU 带宽控制(限额)

    • 1.4 CFS BANDWITH 近几年改进

    • 1.5 小结:CFS 相关内核编译选项的关系

  • 2 CFS 相关设计

    • CPU throttle 是怎么来的

    • 上下文切换开销

    • 新配置项

    • 查看 k8s pod 的 CPU throttle 统计

    • 更多设计细节

    • 例子

    • 2.3.1 实时进程调度策略

    • 2.3.2 常规进程调度策略

    • 2.3.3 常规进程 SCHED_NORMAL 和实时进程 SCHED_RR 调度策略的区别

    • 2.3.4 查看或修改进程的调度属性

    • SCHED_FIFO

    • SCHED_RR

    • SCHED_NORMAL

    • SCHED_BATCH

    • SCHED_IDLE

    • 2.2.1 vruntime

    • 2.2.2 runqueue

    • 2.2.3 基于时序的红黑树

    • 2.1 设计目标和基本原理

    • 2.2 核心概念

    • 2.3 调度策略(scheduling policy)

    • 2.4 调度类(scheduling class)

    • 2.5 进程组调度器扩展(group scheduler extensions)

    • 2.6 CFS 配置项

    • 2.7 CPU 带宽控制设计(CONFIG_CFS_BANDWIDTH

    • 2.9 问题

  • 3 内核实现

    • 3.2.1 struct task_struct

    • 3.2.2 struct task_group

    • 3.2.3 struct sched_entity

    • 3.2.4 struct cfs_rq

    • 3.2.5 struct sched_class

    • 3.1 CFS 第一版实现

    • 3.2 核心数据结构

    • 3.3 CFS CPU 带宽控制实现

  • 4 使用

    • 4.1 模拟 throttle 场景

    • 4.2 k8s

  • 参考资料

1 概念及关系

首先理清几个概念和它们之间的关系。

1.1 CFS:进程(task)的公平调度

CFS(Completely Fair Scheduler)是 Linux 内置(也是目前默认)的一个内核调度器, 如名字所示,它实现了所谓的“完全公平”调度算法,将 CPU 资源均匀地分配给各进程( 在内核代码中称为“任务”,task)。 简单来说,如果一台机器有一个 CPU 多个(计算密集型)进程,那采用 CFS 调度器时,

  • 两个进程:每个进程会各占 50% CPU 时间;

  • 四个进程:每个进程会各占 25% CPU 时间;

这个很好理解。接下来看第二个概念。

1.2 CFS 扩展

最初的 CFS 管理的是单个任务(进程)的调度,给每个进程分配公平的 CPU 时间。 但很多情况下,进程会组织成进程组(task group)的形式, 用户希望先对进程组分配 CPU 份额,再在每个进程组里面实现公平调度

举个具体例子,多个用户使用同一台机器时,可能希望,

  • 首先按 user 公平(也可以不公平)分配 CPU;

  • 针对每个 user,再对其所有进程公平分配这个 user 的总 CPU 时间。

为此,CFS 引入了几项扩展,例如

  • 实时任务的组调度(RT group)

  • 常规进程的组调度(task group)

但实现这几个扩展是需要一些前提的。

1.2.1 前提:CONFIG_CGROUPS

要实现按进程组分配和管理 CPU 份额的功能,首先要能够控制(control) 进程组(task group)的资源限额, 这种技术在 Linux 内核中已经有了,叫控制组(control group),缩写是 cgroup。 (CONFIG_CGROUPS)。

  • cgroup 有两个版本,分别称为 cgroup v1 和 cgroup v2。这两个版本不兼容,现在默认都是用的 v1;

  • 有了 cgroup,调度器就能通过 cgroup 伪文件系统来管理进程组占用的资源(我们这里关心的是CPU 资源)了;

  • 更多信息见 Documentation/admin-guide/cgroup-v1/cgroups.rst。

1.2.2 前提:CONFIG_CGROUP_SCHED

cgroup 是按资源类型(cpu/memory/device/hugetlb/…)来做资源限额的,每种资源 类型会有一种对应的控制器(controller),有独立的开关。 控制进程或进程组能使用的 CPU 时间,对应的开关是 CONFIG_CGROUP_SCHED

至此,支持进程组级别资源控制的基础就具备了。接下来就是 CFS 扩展代码的实现, 添加对于 realtime/conventional task group 的支持。下面分别来看下。

1.2.3 扩展:支持实时进程组(CONFIG_RT_GROUP_SCHED

CONFIG_RT_GROUP_SCHED 支持对 real-time (SCHED_FIFO and SCHED_RR) 任务进行分组 CFS。

实时进程有严格的响应时间限制,不管机器的 load 有多高,都应该确保这些进程的响应实时性。 例子:内核中的 migration 进程,负责在不同 CPU 之间分发任务(进程负载均衡)。

$ ps -ef | grep migration
root 12 2 0 00:00:01 [migration/0]

1.2.4 扩展:支持常规进程组(CONFIG_FAIR_GROUP_SCHED

实时进程之外的进程就是所谓的常规进程,它们没有严格的响应时间限制, 当系统繁忙时,响应延迟就会增加。

在 cgroup 技术基础上上,再对 CFS 代码做一些增强,就能够支持进程组内的公平调度了。 这些增强代码是通过编译选项 CONFIG_FAIR_GROUP_SCHED 控制的。 支持对普通 CFS (SCHED_NORMAL, SCHED_BATCH) 任务进行分组。

至此,我们已经能对进程和进程组进行 CFS 调度。

1.3 常规进程组 CFS 再扩展:支持 CPU 带宽控制(限额)

1.3.1 CFS 存在的问题

CFS 自己也存在一些问题或限制:

  1. 某些情况下做不到真正的公平。

    CFS 本质上是会把 CPU 用满的(work-conserving)。具体来说,如果一个 CPU 上 有两个任务,理论上应该各占用 50% 的 CPU;但如果其中一个任务有很多 sleep/wait 时间, CFS 就会把多余的时间给到第二个进程,导致第二个进程实际使用的时间超过一半。

  2. 优先级高的进程仍然可能获得更大的时间片。

    内核中有两中调度类(scheduling class):SCHED_RT 和 SCHED_NORMAL,前者的优先级更大。 当一个 CPU 上有 RT 类型任务时,永远是它们先执行。优先级可以通过 nice(2) 控制。

  3. 无法设置 CPU 使用上限。

    CFS 只关注 CPU 平均分配,并不保证 CPU 时间(上下限)。 换句话说,CPU share/quota 只有相对意义,share 大的一定比 share 小的能分到更多 CPU,仅此而已。 进程越多,每个进程分到的 CPU 时间越少。 CPU 限额(上限)对按 CPU 时间计费的场景非常关键,例如公有云。


    图片来自 google paper [5]。注意:严格来说,这里的相对时间还只是在 SCHED_NORMAL 里的时间,不包括 SCHED_RT 进程占掉的 CPU 时间。

1.3.2 CONFIG_CFS_BANDWIDTH

严格来说,Linux 中的调度单位是线程(thread),因此在调度上下文中并没有进程(process)的概念。

基于以上原因,Google 提出了 CFS CPU 带宽控制(CFS bandwidth control)方案,并合并到了主线内核。 这里的“CPU 带宽”指的就是 CPU 份额,或者说的更清楚点,CPU 比例。 SCHED_RT 中其实已经有这个功能,这里指的是 SCHED_NORMAL 支持这个功能。

这个功能的好处主要是给服务器,不是给桌面电脑。 好处:

  • 能精确控制一个进程使用的 CPU 带宽上限;比如设置一个容器只能使用 0.2 CPU, 那它的总时间就不能超过这个比例,即使这个 CPU 非常空闲;

  • 对容量规划非常有用(例如 OpenStack 调度 VM,k8s 调度 pod);

  • 延迟更有保证;

1.4 CFS BANDWITH 近几年改进

  • burst 特性:允许借用前一个进程剩下的带宽。

1.5 小结:CFS 相关内核编译选项的关系

总结一下前面提到的 CFS 相关功能,它们的配置选项或依赖关系如下:

CONFIG_CGROUPS                     # 1. 是否支持 cgroup,下面进一步区分 cpu/memory/hugetlb/...
|-CONFIG_MEMCG # 1.1 是否支持 memory cgroup
|-CONFIG_BLK_CGROUP # 1.2 是否支持 blkio cgroup
|-CONFIG_CGROUP_SCHED # 1.3 是否支持 cpu cgroup
| |-CONFIG_RT_GROUP_SCHED # 1.3.1 是否支持 realtime scheduler cpu cgroup
| |-CONFIG_FAIR_GROUP_SCHED # 1.3.2 是否支持 cfs for task cgroup
| |-CONFIG_CFS_BANDWIDTH # 1.3.2.1 是否支持 cfs cpu bandwidth
|
|-CONFIG_CGROUP_PIDS # 1.4 是否支持 pid cgroup
|-CONFIG_CPUSETS # 1.5 是否支持 cpuset cgroup
|-CONFIG_CGROUP_DEVICE # 1.6 是否支持 device cgroup
|-CONFIG_CGROUP_CPUACCT # 1.7 是否支持 cpu,acc cgroup
|-... # 1.8 是否支持 ... cgroup

这些宏定义(编译开关)的层次关系在 init/Kconfig 中可以看出来,在父一级开关为 yes 的条件下,才会有子一级的开关。 例如,要启用 CFS CPU 带宽控制功能,就必须要有: CONFIG_CGROUPS=y && CONFIG_CGROUP_SCHED=y && CONFIG_FAIR_GROUP_SCHED=y && CONFIG_CFS_BANDWIDTH=y 这是本文接下来将重点关注的部分(1 -> 1.3 -> 1.3.2 -> 1.3.2.1)。

各开关的详细解释:

// init/Kconfig

menuconfig CGROUPS
bool "Control Group support"
help
This option adds support for grouping sets of processes together, for
use with process control subsystems such as Cpusets, CFS, memory controls or device isolation.
See
- Documentation/scheduler/sched-design-CFS.rst (CFS)
- Documentation/admin-guide/cgroup-v1/ (features for grouping, isolation and resource control)

if CGROUPS
config MEMCG
bool "Memory controller"
select PAGE_COUNTER
help
Provides control over the memory footprint of tasks in a cgroup.
config BLK_CGROUP
bool "IO controller"
depends on BLOCK
help
Generic block IO controller cgroup interface. This is the common
cgroup interface which should be used by various IO controlling policies.

Currently, CFQ IO scheduler uses it to recognize task groups and
control disk bandwidth allocation (proportional time slice allocation)
to such task groups. It is also used by bio throttling logic in
block layer to implement upper limit in IO rates on a device.

This option only enables generic Block IO controller infrastructure.
One needs to also enable actual IO controlling logic/policy. For
enabling proportional weight division of disk bandwidth in CFQ, set
CONFIG_BFQ_GROUP_IOSCHED=y; for enabling throttling policy, set
CONFIG_BLK_DEV_THROTTLING=y.

See Documentation/admin-guide/cgroup-v1/blkio-controller.rst for more information.

menuconfig CGROUP_SCHED
bool "CPU controller"
help
This feature lets CPU scheduler recognize task groups and control CPU
bandwidth allocation to such task groups. It uses cgroups to group tasks.
if CGROUP_SCHED
config FAIR_GROUP_SCHED
bool "Group scheduling for SCHED_OTHER"
depends on CGROUP_SCHED
default CGROUP_SCHED

config CFS_BANDWIDTH
bool "CPU bandwidth provisioning for FAIR_GROUP_SCHED"
depends on FAIR_GROUP_SCHED
help
This option allows users to define CPU bandwidth rates (limits) for
tasks running within the fair group scheduler. Groups with no limit
set are considered to be unconstrained and will run with no restriction.
See Documentation/scheduler/sched-bwc.rst for more information.
config RT_GROUP_SCHED
bool "Group scheduling for SCHED_RR/FIFO"
depends on CGROUP_SCHED
help
This feature lets you explicitly allocate real CPU bandwidth
to task groups. If enabled, it will also make it impossible to
schedule realtime tasks for non-root users until you allocate realtime bandwidth for them.
See Documentation/scheduler/sched-rt-group.rst for more information.
endif #CGROUP_SCHED

config CGROUP_PIDS
bool "PIDs controller"
config CPUSETS
bool "Cpuset controller"
help
This option will let you create and manage CPUSETs which
allow dynamically partitioning a system into sets of CPUs and
Memory Nodes and assigning tasks to run only within those sets.
This is primarily useful on large SMP or NUMA systems.
config CGROUP_DEVICE
bool "Device controller"
help
Provides a cgroup controller implementing whitelists for
devices which a process in the cgroup can mknod or open.
config CGROUP_CPUACCT
bool "Simple CPU accounting controller"
help
Provides a simple controller for monitoring the total CPU consumed by the tasks in a cgroup.
endif # CGROUPS

2 CFS 相关设计

2.1 设计目标和基本原理

CFS 早在 2007 年就合并到 Linux 内核(2.6.23) [1],取代了之前调度器中的 SCHED_OTHER 实现。 CFS 80% 的设计都可以总结为一句话:将真实 CPU 建模为一个“理想、精确的多任务 CPU”。 “理想多任务 CPU” 掌控 100% 物理资源,能精确地以相同速度并行执行多个进程, 每个任务的速度都是 1/nr_running

内核为每个 CPU 维护了一个可运行进程的队列(runqueue); CFS 有一个可配置的调度周期 sched_latency;接下来的基本调度过程:

  • CFS 根据当前可运行进程的数量 N,计算得到每个进程应该执行的时间 sched_latency/N;

  • 依次取出进程执行以上计算出的时间;

  • 如果 runqueue 有变化,再重新计算可执行时间。

2.2 核心概念

2.2.1 vruntime

在真实 CPU 上,任意时间只能运行一个任务;为了实现“公平”,CFS 引入了 “virtual runtime”(虚拟运行时间)的概念。

  • vruntime 表示进程真正在 CPU 上执行的时间,不包括任何形式的等待时间;

  • 注意机器一般都是多核的,因此 vruntime 是在多个 CPU 上执行时间的累加。

2.2.2 runqueue

刚才其实已经提到了,是每个 CPU 上的可运行进程队列,之前就已经存在,并不是 CFS 引入的。

2.2.3 基于时序的红黑树

哪个进程 vruntime 最小,说明累计执行时间最少,从“公平”的角度来说,就需要执行它。

  • CFS 用红黑树来组织这些进程(描述 runqueue),用 vruntime 做 key, 所以这是一个基于时序的红黑树(time-ordered rbtree), 所有 runnable 的进程都是用 p->se.vruntime 作为 key 来排序的。

  • CFS 每次取出最左边的进程(红黑树特性),执行完成后插入越来越右边,这样每个任务都有机会成为最左边的节点, 在一段确定是时间内总得得到 CPU 资源。

实际上 CFS 还维护了 min/max vruntime,

  • min vruntime 用途:新进程或重新回到 ready 状态的进程,用 vruntime=min_vruntime 来初始化,放到最左边;这对防止进程饥饿非常关键;

  • max vruntime 用途:限额?

查询复杂度:O(1) 插入复杂度 O(logN)

2.3 调度策略(scheduling policy)

2.3.1 实时进程调度策略

可运行的进程都放到了一个 runqueue(运行队列)的数据结构中,调度器根据调度策略从里面取出进程放到 CPU 上执行。 有两种调度策略:SCHED_RR 和 SCHED_FIFO。

SCHED_FIFO

很简单,先进先出。

进程在下面的条件下会放弃 CPU:

  1. 进程在等待,例如 IO 操作。当进程再回到 ready 状态时,它会被放到 runqueue 队尾。

  2. 进程通过 sched_yield yield(主动让出) CPU。进程立即进入 runqueue 队尾。

SCHED_RR

在这种调度策略中,runqueue 中的每个进程轮流获得时间片(quantum)。

调度策略:影响的是 runqueue 如何工作,每个进程能获得多少执行时间。

2.3.2 常规进程调度策略

CFS 实现了三种调度策略:

SCHED_NORMAL

历史上叫 SCHED_OTHER,适用于普通任务的调度。

SCHED_BATCH

适合批量任务。不像普通任务那样容易被抢占,因此每个任务运行的时间可以更长,缓存效率更高,但交互性变差。

SCHED_IDLE

This is even weaker than nice 19, but its not a true idle timer scheduler in order to avoid to get into priority inversion problems which would deadlock the machine.

2.3.3 常规进程 SCHED_NORMAL 和实时进程 SCHED_RR 调度策略的区别

二者都是按进程公平分配 CPU,计算好每个进程的执行时间(时长都一样),然后依次取出进程执行, 听起来有点像,但不一样:

 SCHED_RRSCHED_NORMAL
调度的进程类型实时进程普通进程
时间片静态,不依赖系统中的进程数量动态,根据系统中进程的数量会发生变化
下一个进程的选择从 runqueue 中按 RR 选下一个从红黑树中选 vruntime 最小的一个

2.3.4 查看或修改进程的调度属性

chrt 可查看或修改进程的调度属性

$ chrt --help
Show or change the real-time scheduling attributes of a process.
...

查看调度属性:

$ chrt -p 219027
pid 219027's current scheduling policy: SCHED_OTHER
pid 219027'
s current scheduling priority: 0

2.4 调度类(scheduling class)

CFS 引入了 “Scheduling Class” 概念,将调度策略封装到一个调度类型,使得 CFS 调度器核心代码不用处理调度策略细节。

这些调度类组成一个可扩展的调度模块层级(an extensible hierarchy of scheduler modules)。

2.5 进程组调度器扩展(group scheduler extensions)

CONFIG_FAIR_GROUP_SCHED 启用后,会为每个 task group 在对应的 cgroup 目录中创建一个名为 cpu.shares 的文件。

2.6 CFS 配置项

CFS 的时间粒度是 ns,并不依赖任何 jiffies 或 HZ 信息。 有一个可调优的配置,(需要在编译内核时打开 CONFIG_SCHED_DEBUG):

  • kernel < 5.15/proc/sys/kernel/sched_min_granularity_ns

  • kernel >= 5.15/sys/kernel/debug/sched/min_granularity_ns

可以通过调整这个参数来让调度器从 “desktop”(低延迟)到“server”(高并发)workloads。 默认配置适合的是 desktop workloads。

例子

下面是个例子,创建进程组,通过 cgroup 文件系统设置 CPU shares,

# 1. 挂载 cgroup 文件系统
$ mount -t tmpfs cgroup_root /sys/fs/cgroup
$ mkdir /sys/fs/cgroup/cpu # 创建 cpu 目录,用于控制 cpu 资源份额
$ mount -t cgroup -ocpu none /sys/fs/cgroup/cpu
$ cd /sys/fs/cgroup/cpu

# 2. 创建进程组对应的 cgroup 目录
$ mkdir multimedia # create "multimedia" group of tasks
$ mkdir browser # create "browser" group of tasks

# 3. 设置进程组的 CPU 份额: multimedia 可以比 browser 多用一倍
$ echo 2048 > multimedia/cpu.shares
$ echo 1024 > browser/cpu.shares

# 4. 启动浏览器进程 firefox 并放到 "browser" 进程组
$ firefox &
$ echo > browser/tasks

# 启动多媒体进程 gmplayer 并放到 "multimedia" 进程组
$ gmplayer &
$ echo > multimedia/tasks

2.7 CPU 带宽控制设计(CONFIG_CFS_BANDWIDTH

新配置项

给 cpu cgroup 引入了两个新配置项:

  • cpu.cfs_period_us: 周期(period),每个周期单独计算,周期结束之后状态(quota 等)清零;默认 100ms

  • cpu.cfs_quota_us: 在一个周期里的份额(quota),默认 5ms。

    最大 1s,最小 1ms:

      const u64 max_cfs_quota_period = 1 * NSEC_PER_SEC; /* 1s */
    const u64 min_cfs_quota_period = 1 * NSEC_PER_MSEC; /* 1ms */

此外还有一个统计输出:

  • cpu.stat:输出 throttling statistics

后来还引入了一个优化项:

  • cpu.cfs_burst_us: the maximum accumulated run-time。上一个进程没用完的份额,可以给下一个 CPU 用。

默认值:

cpu.cfs_period_us=100ms
cpu.cfs_quota_us=-1
cpu.cfs_burst_us=0 # 5.15+

查看 k8s pod 的 CPU throttle 统计

在一个 k8s node 上查看某个 pod 的 CPU throttle 统计

$ cat /sys/fs/cgroup/cpu/kubepods/pod/cpu.stat
nr_periods 1312889
nr_throttled 100714
throttled_time 22081774986248

更多设计细节

The skeleton of our approach is as follows:

  • We maintain a global pool (per-tg) pool of unassigned quota. Within it we track the bandwidth period, quota per period, and runtime remaining in the current period. As bandwidth is used within a period it is decremented from runtime. Runtime is currently synchronized using a spinlock, in the current implementation there’s no reason this couldn’t be done using atomic ops instead however the spinlock allows for a little more flexibility in experimentation with other schemes.

  • When a cfs_rq participating in a bandwidth constrained task_group executes it acquires time in sysctl_sched_cfs_bandwidth_slice (default currently 10ms) size chunks from the global pool, this synchronizes under rq->lock and is part of the update_curr path.

  • Throttled entities are dequeued, we protect against their re-introduction to the scheduling hierarchy via checking for a, per cfs_rq, throttled bit.

After received a slice, sched_entities in cfs_rq would start running, and keep applying after every slice is used up. If there are no more slices, which means the cfs_rq used up all the allowable quota in this period, it will be throttled, at the meanwhile tasks couldn’t run anymore. The throttling statistics could be checked with Cgroup cpu.stat. After this period, global quota pool will be refreshed and cfs_rq get out of throttled state, tasks continue running.

2.9 问题

CPU throttle 是怎么来的

多核情况下:可以在一个核上运行很长时间,也可以在多个核上运行很短时间。 后一种情况会导致进程在很短的时间内用完了全部 quota,在调度周期内剩下的时间里,只能等待。这就是 throttle(关闭阀门,节流)。

典型场景:多核,多线程(例如线程池)进程。

上下文切换开销

过载情况下,每个进程能分到的时间片很短,例如 1ms,导致大部分 CPU 开销都花在了上下文切换上。

上下文切换做的事情:

  1. 保存当前进程或线程的状态;

  2. 恢复下一个进程或线程的状态;

  3. 执行后一个进程。

如果一个时间片是 6ms,切换占 1ms,那我们还有5ms 可以执行; 如果 1.5ms,那只有 0.5ms 可以执行。

为了避免这个问题,引入 min_granularity;反过来,这也会影响 sched_latency 的选取。

3 内核实现

SCHED_FIFO/_RR are implemented in sched/rt.c and are as specified by POSIX.

sched/rt.c implements SCHED_FIFO and SCHED_RR semantics. It uses 100 runqueues (for all 100 RT priority levels, instead of 140 in the previous scheduler) and it needs no expired array.

3.1 CFS 第一版实现

见 [1],代码不太多。想快速了解整体实现的可以浏览一下。

3.2 核心数据结构

接下来的部分都是参考 5.10 代码。

介绍几个核心的数据结构。

3.2.1 struct task_struct

每个进程的结构体表示是 struct task

// include/linux/sched.h

struct task_struct {
struct thread_info thread_info;
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped: */

void *stack;
refcount_t usage;
unsigned int flags; /* Per task flags (PF_*), defined further below: */
unsigned int ptrace;

int on_cpu;
struct __call_single_node wake_entry;
unsigned int cpu; /* Current CPU: */
unsigned int wakee_flips;
unsigned long wakee_flip_decay_ts;
struct task_struct *last_wakee;

int recent_used_cpu;
int wake_cpu;
int on_rq;

int prio;
int static_prio;
int normal_prio;

const struct sched_class *sched_class;
struct sched_entity se; // schedule entity, including vruntime
#ifdef CONFIG_CGROUP_SCHED
struct task_group *sched_task_group;
#endif
...
};

其中有个调度相关的字段是 struct sched_entity se。可以是一个进程、一个进程组或一个用户。

3.2.2 struct task_group

同理,进程组中也有一个 struct sched_entity se 字段:

// https://github.com/torvalds/linux/blob/v5.10/kernel/sched/sched.h#L383

/* Task group related information */
struct task_group {
struct cgroup_subsys_state css;

#ifdef CONFIG_FAIR_GROUP_SCHED
struct sched_entity **se; /* schedulable entities of this group on each CPU */
struct cfs_rq **cfs_rq; /* runqueue "owned" by this group on each CPU */
unsigned long shares;

atomic_long_t load_avg ____cacheline_aligned;
#endif

#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity **rt_se;
struct rt_rq **rt_rq;

struct rt_bandwidth rt_bandwidth;
#endif

struct rcu_head rcu;
struct list_head list;

struct task_group *parent;
struct list_head siblings;
struct list_head children;

#ifdef CONFIG_SCHED_AUTOGROUP
struct autogroup *autogroup;
#endif

struct cfs_bandwidth cfs_bandwidth;

#ifdef CONFIG_UCLAMP_TASK_GROUP
/* The two decimal precision [%] value requested from user-space */
unsigned int uclamp_pct[UCLAMP_CNT];
/* Clamp values requested for a task group */
struct uclamp_se uclamp_req[UCLAMP_CNT];
/* Effective clamp values used for a task group */
struct uclamp_se uclamp[UCLAMP_CNT];
#endif

};

3.2.3 struct sched_entity

scheduling entities 通过一个红黑树组织到一起,根据 vruntime 排序,通过 cfs_rq 来管理.

所以 virtual runtime(vruntime)就定义在这个结构体里面,单位是 ns

// https://github.com/torvalds/linux/blob/v5.10/include/linux/sched.h#L451

struct sched_entity {
struct load_weight load; // For load-balancing
struct rb_node run_node;
struct list_head group_node;
unsigned int on_rq;

u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime; // unit: ns
u64 prev_sum_exec_runtime;

u64 nr_migrations;

struct sched_statistics statistics;

#ifdef CONFIG_FAIR_GROUP_SCHED
int depth;
struct sched_entity *parent;
struct cfs_rq *cfs_rq; // rq on which this entity is (to be) queued
struct cfs_rq *my_q; // rq "owned" by this entity/group
unsigned long runnable_weight; /* cached value of my_q->h_nr_running */
#endif

// Per entity load average tracking.
// Put into separate cache line so it does not collide with read-mostly values above.
struct sched_avg avg;
};

有了这个字段,就能精确地通过时间戳来保证一个任务应该获得的 “expected CPU time”。

前面已经提到,CFS 就是基于 p->se.vruntime 来选择(调度)进程的,逻辑非常简单: 永远选择 p->se.vruntime 最小(说明这个进程到目前为止累积执行的时间最少)的进程来运行。 CFS 会不断尝试均衡各进程的 CPU 时间,尽量接近“理想多任务 CPU”。

3.2.4 struct cfs_rq

// https://github.com/torvalds/linux/blob/v5.10/kernel/sched/sched.h#L518

/* CFS-related fields in a runqueue (rq) */
struct cfs_rq {
struct load_weight load;
unsigned int nr_running;
unsigned int h_nr_running; /* SCHED_{NORMAL,BATCH,IDLE} */
unsigned int idle_h_nr_running; /* SCHED_IDLE */

u64 exec_clock;
u64 min_vruntime;
u64 min_vruntime_copy;

struct rb_root_cached tasks_timeline; // rbtree

/*
* 'curr' points to currently running entity on this cfs_rq.
* It is set to NULL otherwise (i.e when none are currently running).
*/

struct sched_entity *curr;
struct sched_entity *next;
struct sched_entity *last;
struct sched_entity *skip;

/*
* CFS load tracking
*/

struct sched_avg avg;
#ifndef CONFIG_64BIT
u64 load_last_update_time_copy;
#endif
struct {
raw_spinlock_t lock ____cacheline_aligned;
int nr;
unsigned long load_avg;
unsigned long util_avg;
unsigned long runnable_avg;
} removed;

#ifdef CONFIG_FAIR_GROUP_SCHED
unsigned long tg_load_avg_contrib;
long propagate;
long prop_runnable_sum;

/*
* h_load = weight * f(tg)
*
* Where f(tg) is the recursive weight fraction assigned to
* this group.
*/

unsigned long h_load;
u64 last_h_load_update;
struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */

#ifdef CONFIG_FAIR_GROUP_SCHED
// the main per-CPU runqueue structure, which cfs_rq would attached to
struct rq *rq; /* CPU runqueue to which this cfs_rq is attached */

/*
* leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
* a hierarchy). Non-leaf lrqs hold other higher schedulable entities
* (like users, containers etc.)
*
* leaf_cfs_rq_list ties together list of leaf cfs_rq's in a CPU.
* This list is used during load balance.
*/

int on_list;
struct list_head leaf_cfs_rq_list;
struct task_group *tg; /* group that "owns" this runqueue */

#ifdef CONFIG_CFS_BANDWIDTH
int runtime_enabled;
s64 runtime_remaining;

u64 throttled_clock;
u64 throttled_clock_task;
u64 throttled_clock_task_time;
int throttled;
int throttle_count;
struct list_head throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif
/* CONFIG_FAIR_GROUP_SCHED */
};

其中的 CFS 红黑树:

// include/linux/rbtree.h

// Leftmost-cached rbtrees.
struct rb_root_cached {
struct rb_root rb_root;
struct rb_node *rb_leftmost;
};

在 cfs_rq 中声明了这个 rbtree 变量:

CFS 维护了 rq->cfs.min_vruntime 值,这是一个单调递增值,跟踪 runqueue 所有进程中最小的 vruntime。

  • 系统完成的总 work 量用 min_vruntime 表示,新激活的进程,会用这个值来初始化,放到 rbtree 尽量最左边;

  • runqueue 中的总 running 进程数量用 rq->cfs.load 表示,是 runqueue 中所有进程的 weights 总和;

总结一下 CFS 的工作原理:

  • runs a task a bit, 当进程被调度(或 scheduler tick 到来时),更新这个进程的 CPU usage: 这个进程使用的物理 CPU 时间会累加到 p->se.vruntime

  • 当 p->se.vruntime (加上一个很小的”granularity” distance,以避免过度调度进程,trash the cache)大到已经不是最左侧节点时, 新的最左侧节点就会被选中,当前进程被强占(current task is preempted)。

3.2.5 struct sched_class

Scheduling class 是通过 struct sched_class 实现的,

// kernel/sched/sched.h

struct sched_class {
int uclamp_enabled;

void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*yield_task) (struct rq *rq);
bool (*yield_to_task)(struct rq *rq, struct task_struct *p);

void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);

struct task_struct *(*pick_next_task)(struct rq *rq);

void (*put_prev_task)(struct rq *rq, struct task_struct *p);
void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);

int (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);
int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
void (*migrate_task_rq)(struct task_struct *p, int new_cpu);

void (*task_woken)(struct rq *this_rq, struct task_struct *task);

void (*set_cpus_allowed)(struct task_struct *p, const struct cpumask *newmask);

void (*rq_online)(struct rq *rq);
void (*rq_offline)(struct rq *rq);

void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
void (*task_fork)(struct task_struct *p);
void (*task_dead)(struct task_struct *p);

/*
* The switched_from() call is allowed to drop rq->lock, therefore we
* cannot assume the switched_from/switched_to pair is serliazed by
* rq->lock. They are however serialized by p->pi_lock.
*/

void (*switched_from)(struct rq *this_rq, struct task_struct *task);
void (*switched_to) (struct rq *this_rq, struct task_struct *task);
void (*prio_changed) (struct rq *this_rq, struct task_struct *task, int oldprio);

unsigned int (*get_rr_interval)(struct rq *rq, struct task_struct *task);

void (*update_curr)(struct rq *rq);

#define TASK_SET_GROUP 0
#define TASK_MOVE_GROUP 1

#ifdef CONFIG_FAIR_GROUP_SCHED
void (*task_change_group)(struct task_struct *p, int type);
#endif
} __aligned(STRUCT_ALIGNMENT); /* STRUCT_ALIGN(), vmlinux.lds.h */

初始化:

// kernel/sched/fair.c

/*
* All the scheduling class methods:
*/

const struct sched_class fair_sched_class
__section("__fair_sched_class") = {
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
.yield_task = yield_task_fair,
.yield_to_task = yield_to_task_fair,

.check_preempt_curr = check_preempt_wakeup,

.pick_next_task = __pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
.set_next_task = set_next_task_fair,

#ifdef CONFIG_SMP
.balance = balance_fair,
.select_task_rq = select_task_rq_fair,
.migrate_task_rq = migrate_task_rq_fair,

.rq_online = rq_online_fair,
.rq_offline = rq_offline_fair,

.task_dead = task_dead_fair,
.set_cpus_allowed = set_cpus_allowed_common,
#endif

.task_tick = task_tick_fair,
.task_fork = task_fork_fair,

.prio_changed = prio_changed_fair,
.switched_from = switched_from_fair,
.switched_to = switched_to_fair,

.get_rr_interval = get_rr_interval_fair,

.update_curr = update_curr_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED
.task_change_group = task_change_group_fair,
#endif

#ifdef CONFIG_UCLAMP_TASK
.uclamp_enabled = 1,
#endif
};

3.3 CFS CPU 带宽控制实现

内核文档:CFS bandwidth control。

$ cat /proc/sys/kernel/sched_cfs_bandwidth_slice_us
5000

$ sysctl -a | grep cfs_
kernel.sched_cfs_bandwidth_slice_us = 5000

第一版实现: https://github.com/torvalds/linux/commit/ab84d31e15502fb626169ba2663381e34bf965b2

sched: Introduce primitives to account for CFS bandwidth tracking
In this patch we introduce the notion of CFS bandwidth, partitioned into
globally unassigned bandwidth, and locally claimed bandwidth.

- The global bandwidth is per task_group, it represents a pool of unclaimed
bandwidth that cfs_rqs can allocate from.
- The local bandwidth is tracked per-cfs_rq, this represents allotments from
the global pool bandwidth assigned to a specific cpu.

Bandwidth is managed via cgroupfs, adding two new interfaces to the cpu subsystem:
- cpu.cfs_period_us : the bandwidth period in usecs
- cpu.cfs_quota_us : the cpu bandwidth (in usecs) that this tg will be allowed
to consume over period above.
  • cpu.cfs_period_us: 周期(period),每个周期单独计算,周期结束之后状态(quota 等)清零;默认 100ms

  • cpu.cfs_quota_us: 在一个周期里的份额(quota),默认 5ms。

    最大 1s,最小 1ms:

      const u64 max_cfs_quota_period = 1 * NSEC_PER_SEC; /* 1s */
    const u64 min_cfs_quota_period = 1 * NSEC_PER_MSEC; /* 1ms */
static int tg_set_cfs_bandwidth(struct task_group *tg, u64 period, u64 quota)
{
int i;
struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(tg);
static DEFINE_MUTEX(mutex);

if (tg == &root_task_group)
return -EINVAL;

/*
* Ensure we have at some amount of bandwidth every period. This is
* to prevent reaching a state of large arrears when throttled via
* entity_tick() resulting in prolonged exit starvation.
*/

if (quota < min_cfs_quota_period || period < min_cfs_quota_period)
return -EINVAL;

/*
* Likewise, bound things on the otherside by preventing insane quota
* periods. This also allows us to normalize in computing quota
* feasibility.
*/

if (period > max_cfs_quota_period)
return -EINVAL;

mutex_lock(&mutex);
raw_spin_lock_irq(&cfs_b->lock);
cfs_b->period = ns_to_ktime(period);
cfs_b->quota = quota;
raw_spin_unlock_irq(&cfs_b->lock);

for_each_possible_cpu(i) {
struct cfs_rq *cfs_rq = tg->cfs_rq[i];
struct rq *rq = rq_of(cfs_rq);

raw_spin_lock_irq(&rq->lock);
cfs_rq->runtime_enabled = quota != RUNTIME_INF;
cfs_rq->runtime_remaining = 0;
raw_spin_unlock_irq(&rq->lock);
}
mutex_unlock(&mutex);

return 0;
}

4 使用

4.1 模拟 throttle 场景

用 Overly aggressive CFS 中的程序测试 CFS CPU throttle:

package main

import (
"crypto/sha512"
"flag"
"log"
"syscall"
"time"
)

func main() {
sleep := flag.Duration("sleep", time.Second, "sleep between iterations")
interations := flag.Int("iterations", 100, "number of iterations")
flag.Parse()

time.Sleep(time.Second)

b := time.Now()
for i := 0; i < *interations; i++ {
s := time.Now()
burn(time.Millisecond * 5)
e := time.Now().Sub(s)

log.Printf("[%d] burn took %dms, real time so far: %dms, cpu time so far: %dms", i, ms(e), ms(time.Since(b)), ms(usage()))
time.Sleep(*sleep)
}
}

func ms(duration time.Duration) int {
return int(duration.Nanoseconds() / 1000 / 1000)
}

func burn(duration time.Duration) {
s := time.Now()

for {
sum := sha512.New()
sum.Write([]byte("banana"))
sum.Sum([]byte{})

if time.Since(s) > duration {
break
}
}
}

func usage() time.Duration {
r := syscall.Rusage{}
syscall.Getrusage(syscall.RUSAGE_SELF, &r)
return time.Duration(r.Stime.Nano() + r.Utime.Nano())
}

程序每个 iteration 会执行 5ms,然后 sleep 一段时间。

用 docker container 来执行,避免污染本机配置。

period=100ms,quota=5m,sleep=10ms 情况下,不会产生 throttle:

$ dk run --rm -it -v $(pwd):$(pwd) -w $(pwd) golang:1.19.4 go run cfs.go -iterations 20 -sleep 10ms
2023/02/05 09:50:35 [0] burn took 5ms, real time so far: 5ms, cpu time so far: 7ms
2023/02/05 09:50:35 [1] burn took 5ms, real time so far: 20ms, cpu time so far: 12ms
2023/02/05 09:50:35 [2] burn took 5ms, real time so far: 37ms, cpu time so far: 17ms
...
2023/02/05 09:50:35 [18] burn took 5ms, real time so far: 338ms, cpu time so far: 102ms
2023/02/05 09:50:35 [19] burn took 5ms, real time so far: 355ms, cpu time so far: 108ms

period=100ms,quota=25m,sleep=10ms 情况下,会产生 throttle:

$ dk run --rm -it --cpu-quota 25000 --cpu-period 100000 -v $(pwd):$(pwd) -w $(pwd) golang:1.19.4 go run cfs.go -iterations 20 -sleep 10ms
2023/02/05 09:48:38 [0] burn took 5ms, real time so far: 5ms, cpu time so far: 6ms
2023/02/05 09:48:38 [1] burn took 11ms, real time so far: 35ms, cpu time so far: 19ms
2023/02/05 09:48:38 [2] burn took 5ms, real time so far: 51ms, cpu time so far: 24ms
2023/02/05 09:48:38 [3] burn took 20ms, real time so far: 83ms, cpu time so far: 28ms
2023/02/05 09:48:38 [4] burn took 5ms, real time so far: 100ms, cpu time so far: 33ms
2023/02/05 09:48:38 [5] burn took 5ms, real time so far: 115ms, cpu time so far: 38ms
2023/02/05 09:48:38 [6] burn took 5ms, real time so far: 131ms, cpu time so far: 43ms
2023/02/05 09:48:38 [7] burn took 5ms, real time so far: 147ms, cpu time so far: 49ms
2023/02/05 09:48:38 [8] burn took 24ms, real time so far: 182ms, cpu time so far: 51ms
2023/02/05 09:48:38 [9] burn took 5ms, real time so far: 197ms, cpu time so far: 56ms
...

4.2 k8s

研究这些东西是为了更好理解一些容器问题。

TODO。

参考资料

  1. CFS 第一版实现, kernel/sched_fair.c,kernel v2.6.23, 2007

  2. CFS scheduler design (kernel 5.10), kernel doc, 2023

  3. The burstable CFS bandwidth controller, lwn.net, 2021

  4. CFS bandwidth control, lwn.net, 2011

  5. CPU bandwidth control for CFS, google, 2009

  6. CPU bandwidth control warmup, 2021

  7. Overly aggressive CFS, 2018

  8. Process Scheduling In Linux, 2021

Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论 (0)
  • 在当今汽车电子化和智能化快速发展的时代,车规级电子元器件的质量直接关系到汽车安全性能。三星作为全球领先的电子元器件制造商,其车规电容备受青睐。然而,选择一个靠谱的三星车规电容代理商至关重要。本文以行业领军企业北京贞光科技有限公司为例,深入剖析如何选择优质代理商。选择靠谱代理商的关键标准1. 授权资质与行业地位选择三星车规电容代理商首先要验证其授权资质及行业地位。北京贞光科技作为中国电子元器件行业的领军者,长期走在行业前沿,拥有完备的授权资质。公司专注于市场分销和整体布局,在电子元器件领域建立了卓
    贞光科技 2025-04-14 16:18 94浏览
  •   电磁干扰测试系统软件:深度剖析   电磁干扰(EMI)测试系统软件,是电子设备电磁兼容性(EMC)测试的核心工具,在通信、汽车、航空航天、医疗设备等众多领域广泛应用。它的核心功能涵盖信号采集、频谱分析、干扰定位、合规性评估以及报告生成,旨在保障设备在复杂电磁环境中稳定运行。下面从功能、技术原理、应用场景、主流软件及发展趋势这五个方面展开详细解析。   应用案例  软件开发可以来这里,这个首肌开始是幺乌扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照你的顺序组合可以找到。   目前
    华盛恒辉l58ll334744 2025-04-14 10:02 27浏览
  •     电气间隙是指两个带电体在空气中的最短距离。导体、电介质(空气),最短距离,就是这个术语的要素了。        (图源:TI)    电气间隙是由安装类别决定的,或者更本质地说,是瞬态过电压的最大值来决定的,而不是工作电压的高低。安装类别见协议标准第007篇,瞬态过电压另见协议标准第009篇。    实际设计中怎么确定电气间隙?可以按照CAT,工作电压和绝缘等级来定。 
    电子知识打边炉 2025-04-13 18:01 78浏览
  • 在制造业或任何高度依赖产品质量的行业里,QA(质量保证)经理和QC(质量控制)经理,几乎是最容易被外界混淆的一对角色。两者的分工虽清晰,但职责和目标往往高度交叉。因此,当我们谈到“谁更有可能升任质量总监”时,这并不是一个简单的职位比较问题,而更像是对两种思维方式、职业路径和管理视角的深度考察。QC经理,问题终结者QC经理的世界,是充满数据、样本和判定标准的世界。他们是产品出厂前的最后一道防线,手里握着的是批次报告、不合格品记录、纠正措施流程……QC经理更像是一位“问题终结者”,目标是把不合格扼杀
    优思学院 2025-04-14 12:09 56浏览
  • MASSAGE GUN 筋膜枪拆解 今天给车子做保养,厂家送了一个筋膜枪。产品拿在手里还是挺有分量的。标价108元。通过海鲜市场一搜索,几十元不等,而且还是爆款。不多说,我们就来看看里面用了什么料,到底值几个钱。外观篇 首先给它来个开箱照,从外观看,确实还是很精致,一点都不逊色品牌产品。 从箱中取出筋膜枪,沉甸甸的。附上产品的各方位视角 产品的全家福 我装上球头,使用了一番,还真不赖,有不同的敲击速度和根据力度调节不同的档位。拆解篇 拿出我的螺丝套装,对产品开始进行拆解,首先
    zhusx123 2025-04-13 16:52 72浏览
  • 时源芯微 专业EMC解决方案提供商  为EMC创造可能(适用于高频时钟电路,提升EMC性能与信号稳定性)一、设计目标抑制电源噪声:阻断高频干扰(如DC-DC开关噪声)传入晶振电源。降低时钟抖动:确保晶振输出信号纯净,减少相位噪声。通过EMC测试:减少晶振谐波辐射(如30MHz~1GHz频段)。二、滤波电路架构典型拓扑:电源输入 → 磁珠(FB) → 大电容(C1) + 高频电容(C2) → 晶振VDD1. 磁珠(Ferrite Bead)选型阻抗特性:在目标频段(如100MHz~1GH
    时源芯微 2025-04-14 14:53 58浏览
  • 软瓦格化 RISC-V 处理器集群可加速设计并降低风险作者:John Min John Min是Arteris的客户成功副总裁。他拥有丰富的架构专业知识,能够成功管理可定制和标准处理器在功耗、尺寸和性能方面的设计权衡。他的背景包括利用 ARC、MIPS、x86 和定制媒体处理器来设计 CPU SoC,尤其擅长基于微处理器的 SoC。RISC-V 指令集架构 (ISA) 以其强大的功能、灵活性、低采用成本和开源基础而闻名,正在经历各个细分市场的快速增长。这种多功能 ISA 支持汽车、航空航天、国防
    ArterisIP 2025-04-14 10:52 68浏览
  • 一、磁场发生设备‌电磁铁‌:由铁芯和线圈组成,通过调节电流大小可产生3T以下的磁场,广泛应用于工业及实验室场景(如电磁起重机)。‌亥姆霍兹线圈‌:由一对平行共轴线圈组成,可在线圈间产生均匀磁场(几高斯至几百高斯),适用于物理实验中的磁场效应研究。‌螺线管‌:通过螺旋线圈产生长圆柱形均匀磁场,电流与磁场呈线性关系,常用于磁性材料研究及电子束聚焦。‌超导磁体‌:采用超导材料线圈,在低温下可产生3-20T的强磁场,用于核磁共振研究等高精度科研领域。‌多极电磁铁‌:支持四极、六极、八极等多极磁场,适用于
    锦正茂科技 2025-04-14 13:29 53浏览
  • 在公共安全、工业调度、户外作业等场景中,对讲机作为关键通信工具,正面临从“功能单一化”向“智能融合化”的转型需求。WT2605C蓝牙语音芯片凭借双模蓝牙架构、高扩展存储方案与全场景音频处理能力,推动传统对讲机实现无屏化操控、专业级音频解码与蓝牙音箱功能融合,为行业用户打造更高效、更灵活、更低成本的通信解决方案。一、无屏化交互革命:BLE指令重构操作逻辑针对工业环境中对讲机操作复杂、屏幕易损的痛点,WT2605C通过双模蓝牙(BR/EDR+BLE)与AT指令集,实现全链路无屏控制:手机APP远程控
    广州唯创电子 2025-04-14 09:08 33浏览
  •   电磁干扰测试系统:电子设备电磁兼容性保障利器   北京华盛恒辉电磁干扰测试系统作为评估电子设备在电磁环境中电磁兼容性(EMC)的关键工具,主要用于检测与分析设备在电磁干扰环境下的性能表现,确保其符合相关标准,能够在实际应用中稳定运行。   应用案例   目前,已有多个电磁干扰测试系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁干扰测试系统。这些成功案例为电磁干扰测试系统的推广和应用提供了有力支持。   系统组成   电磁干扰测试系统一般由以下核心部分构成:  
    华盛恒辉l58ll334744 2025-04-14 10:40 39浏览
  • 亥姆霍兹线圈的应用领域‌物理学研究‌:在原子物理中,用于研究塞曼效应;在磁学研究中,用于测试磁性材料的磁滞回线等特性;还可用于研究电子荷质比等实验‌。‌工程与技术领域‌:用于电子设备校准和测试,提供标准磁场环境;在大型加速器中用于磁场校准;用于电磁干扰模拟实验,测试电子设备在不同磁场干扰下的性能‌。‌生物医学领域‌:研究生物磁场效应,如探索磁场对生物细胞的影响;在生物医学工程基础研究中,提供可控磁场环境‌。‌其他应用‌:作为磁场发生装置产生标准磁场;用于地球磁场的抵消与补偿、地磁环境模拟;还可用
    锦正茂科技 2025-04-14 10:41 51浏览
  •  亥姆霍兹线圈的制造材料选择需兼顾导电性、绝缘性、机械强度及磁场性能,具体分类如下:一、‌导线材料1、‌高纯度铜线:‌作为线圈绕制的核心材料,铜因其you异的导电性(电阻率低)和热稳定性成为shou选。漆包铜线通过表面绝缘漆层实现匝间绝缘,避免短路‌。2、‌其他导电材料‌ 铝线等材料可用于特定场景(如轻量化需求),但导电性和抗氧化性较铜略逊二、‌磁源材料‌1、‌永磁体‌如钕铁硼(NdFeB)或铁氧体,适用于无需外部电源的静态磁场生成,但磁场强度有限。2、‌电磁铁‌通过电流控制磁场强度,
    锦正茂科技 2025-04-14 10:22 32浏览
  •   高空 SAR 目标智能成像系统软件:多领域应用的前沿利器   高空 SAR(合成孔径雷达)目标智能成像系统软件,专门针对卫星、无人机等高空平台搭载的 SAR传感器数据,融合人工智能与图像处理技术,打造出的高效目标检测、识别及成像系统。此软件借助智能算法,显著提升 SAR图像分辨率、目标特征提取能力以及实时处理效率,为军事侦察、灾害监测、资源勘探等领域,提供关键技术支撑。   应用案例系统软件供应可以来这里,这个首肌开始是幺伍扒,中间是幺幺叁叁,最后一个是泗柒泗泗,按照数字顺序组合
    华盛恒辉l58ll334744 2025-04-14 16:09 96浏览
  • 你知道精益管理中的“看板”真正的意思吗?在很多人眼中,它不过是车间墙上的一块卡片、一张单子,甚至只是个用来控制物料的工具。但如果你读过大野耐一的《丰田生产方式》,你就会发现,看板的意义远不止于此。它其实是丰田精益思想的核心之一,是让工厂动起来的“神经系统”。这篇文章,我们就带你一起从这本书出发,重新认识“看板”的深层含义。一、使“看板”和台车结合使用  所谓“看板”就是指纸卡片。“看板”的重要作用之一,就是连接生产现场上道工序和下道工序的信息工具。  “看板”是“准时化”生产的重要手段,它总是要
    优思学院 2025-04-14 15:02 81浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦