Linux调度域与调度组

引入调度域的讨论可以参考这篇文章。这篇笔记重点分析了内核调度域相关的数据结构以及内核用于构建调度域的代码实现,以此来加深对调度域的理解。调度域是调度器进行负载均衡的基础。

调度域拓扑层级

整个系统的调度域组成一个层级结构,内核设计了struct sched_domain_topology_level来描述一层调度域拓扑。

typedef const struct cpumask *(*sched_domain_mask_f)(int cpu);
typedef int (*sched_domain_flags_f)(void);struct sched_domain_topology_level {sched_domain_mask_f mask;sched_domain_flags_f sd_flags;int    flags;int    numa_level;struct sd_data      data;
#ifdef CONFIG_SCHED_DEBUGchar *name;
#endif
};
  • mask:该回调用于指定该层级的调度域的CPU掩码。
  • sd_flags:该回调用于获取该层级的调度域标记。
  • data:该层级的调度域对象,见下面单独分析。
  • name:层级名字,如MC、DIE,这些名字会在用户态的/proc/sys/kernel/sched_domain目录中体现。

系统的调度域拓扑用sched_domain_topology_level[]数组表示,保存在全局变量sched_domain_topology中,数组的每一个元素代表一个层级。default_topology是系统定义的默认调度域拓扑层级数组。各体系结构可以定义自己的调度域拓扑层级数组,然后通过set_sched_topology()函数替换该默认值。

// 不考虑超线程,默认的由MC(多核)和DIE(socket)两个层级组成,手机产品基本上只有这两个层级
static struct sched_domain_topology_level default_topology[] = {
#ifdef CONFIG_SCHED_SMT{ cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) }, // 支持超线程时开启
#endif
#ifdef CONFIG_SCHED_MC{ cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) },
#endif{ cpu_cpu_mask, SD_INIT_NAME(DIE) },{ NULL, },
};struct sched_domain_topology_level *sched_domain_topology = default_topology;// MC层级包含了属于同一个cluster的所有CPU
const struct cpumask *cpu_coregroup_mask(int cpu)
{return &cpu_topology[cpu].core_sibling;
}// DIE层级包含了系统所有的CPU(不考虑NUMA)
static inline const struct cpumask *cpu_cpu_mask(int cpu)
{return cpumask_of_node(cpu_to_node(cpu));
}

sd_data

该结构用于辅助定义调度域拓扑层级,包含了一个拓扑层级所有的调度域对象,这些对象每个CPU一份。由于Per-CPU变量也是动态分配的,所以类型是二级指针。此外,由于整个调度域拓扑层级一旦建立就基本不会再发生变化,每个CPU一份可以提高访问效率。

struct sd_data {struct sched_domain **__percpu sd;struct sched_group **__percpu sg;struct sched_group_capacity **__percpu sgc;
};

构建系统调度域拓扑结构时,会使用__sdt_alloc()为sd_data分配内存,对应的释放函数为__sdt_free()

static int __sdt_alloc(const struct cpumask *cpu_map)
{struct sched_domain_topology_level *tl;int j;for_each_sd_topology(tl) { // 遍历所有的拓扑层级,为每一个tl->sd_data分配空间struct sd_data *sdd = &tl->data;// 分配调度域指针对象,这些指针每个CPU一份sdd->sd = alloc_percpu(struct sched_domain *);sdd->sg = alloc_percpu(struct sched_group *);sdd->sgc = alloc_percpu(struct sched_group_capacity *);// 为每个CPU分配这三个调度域对象for_each_cpu(j, cpu_map) {struct sched_domain *sd;struct sched_group *sg;struct sched_group_capacity *sgc;sd = kzalloc_node(sizeof(struct sched_domain) + cpumask_size(),GFP_KERNEL, cpu_to_node(j));*per_cpu_ptr(sdd->sd, j) = sd;sg = kzalloc_node(sizeof(struct sched_group) + cpumask_size(),GFP_KERNEL, cpu_to_node(j));sg->next = sg;*per_cpu_ptr(sdd->sg, j) = sg;sgc = kzalloc_node(sizeof(struct sched_group_capacity) + cpumask_size(),GFP_KERNEL, cpu_to_node(j));*per_cpu_ptr(sdd->sgc, j) = sgc;}}return 0;
}

调度域对象

有3个调度域对象:

  1. struct sched_domain:调度域代表可以共享属性和调度参数的一组CPU。每个CPU在每一个调度域拓扑层级中都有一个shced_domain对象。
  2. struct sched_group:调度域可以由一个或多个调度组构成,每个调度组也代表可以共享属性和调度参数的一组CPU,属于同一个调度域的调度组的集合组成了调度域代表的CPU。调度域进行负载均衡的目的就是要保证其内部各个调度组之间的负载均衡。
  3. struct sched_capacity:包含了调度组的captacity内容,和调度组管理的CPU的能力强相关。

调度域: sched_domain

struct sched_domain {// 组成调度域拓扑层级struct sched_domain *parent;    /* top domain must be null terminated */struct sched_domain *child;    /* bottom domain must be null terminated */struct sched_group *groups;    /* the balancing groups of the domain */// 暂时忽略诸多调度参数 
...int level; // 代表的调度域拓扑等级,MC为0,其它依次加1#ifdef CONFIG_SCHED_DEBUGchar *name;
#endifunion {void *private; /* used during construction */struct rcu_head rcu; /* used during destruction */};unsigned int span_weight; // 该调度域包含的CPU个数和CPU掩码unsigned long span[0];
};
  1. parent、child、groups:组成了调度域拓扑等级。parent指针指向更高层的sched_domain对象,最低层的sched_domain对象(如MC层)的该指针为NULL。child则是和parent相反。groups指向了属于该调度域的所有调度组对象链表,见sched_group部分的介绍。
  2. private:指向所属的sched_domain_topology_level.sd_data。
  3. span_weight、span:包含了该调度域包含的CPU信息,span数组在构建系统调度域拓扑结构时根据sched_domain_topology中定义的系统调度域拓扑等级描述动态分配。

构建调度域: build_sched_domain()

sched_domain对象的分配是在__sdt_alloc()函数中完成的,但是其大多数字段的设置以及系统拓扑层级结构中sched_domain之间的父子关系是在build_sched_domain()函数中完成的。

// 构建cpu上tl层级的sched_domain对象(tl->sd_data.sd[cpu]),其包含的CPU为cpu_map,child为其下一级
struct sched_domain *build_sched_domain(struct sched_domain_topology_level *tl,const struct cpumask *cpu_map, struct sched_domain_attr *attr,struct sched_domain *child, int cpu)
{struct sched_domain *sd = sd_init(tl, cpu); // 初始化该sched_domain中的各字段if (!sd)return child;// 设置sched_domain的CPU掩码,即span数组cpumask_and(sched_domain_span(sd), cpu_map, tl->mask(cpu));if (child) {        sd->level = child->level + 1;sched_domain_level_max = max(sched_domain_level_max, sd->level);child->parent = sd; // 相邻层级的sched_domain对象之间建立父子关系sd->child = child;// 检查确保低层级的sched_domain的CPU掩码必须是高层级的sched_domain的CPU掩码的子集if (!cpumask_subset(sched_domain_span(child),sched_domain_span(sd))) {#ifdef CONFIG_SCHED_DEBUGpr_err("     the %s domain not a subset of the %s domain\n",child->name, sd->name);
#endifcpumask_or(sched_domain_span(sd),sched_domain_span(sd),sched_domain_span(child));}}set_domain_attribute(sd, attr);return sd;
}

如上可见,sched_domain对象的大部分字段都是在sd_init()函数中设置的,下面我们先忽略其中调度策略参数的设置,这部分内容在负载均衡相关的笔记中再来分析(结合负载均衡上下文更容易理解)。

static struct sched_domain *
sd_init(struct sched_domain_topology_level *tl, int cpu)
{// 找到要初始化的sched_domain对象struct sched_domain *sd = *per_cpu_ptr(tl->data.sd, cpu);int sd_weight, sd_flags = 0;*sd = (struct sched_domain) {
...
#ifdef CONFIG_SCHED_DEBUG.name = tl->name,
#endif};
...sd->private = &tl->data;// private指向sched_domain_topology_level.sd_datareturn sd;
}

调度组: sched_group

调度域进一步可以划分为若干个调度组,这些调度组的CPU的并集就是调度域的CPU集合。

struct sched_group {struct sched_group *next;    /* Must be a circular list */atomic_t ref;unsigned int group_weight; // cpumask中CPU的个数struct sched_group_capacity *sgc;// 调度组的CPU掩码unsigned long cpumask[0];
};
  1. next:同一个调度域下的多个调度组用该指针组成一个单向循环链表,链表的表头结点为sched_domain.groups指针。
  2. ref:同一个sched_group对象可能被多个CPU上的sched_domain对象引用,所以需要一个引用计数。
  3. sgc:用来描述该调度组的capacity,见下面介绍。
  4. group_weight、cpumask:该调度组包含了哪些CPU和CPU的个数。

构建调度组: build_sched_groups()

__sdt_alloc()函数中完成sched_group对象的分配,每个CPU在每个调度域拓扑层级都分配了一个sched_group对象,但是这些对象并不会全部被使用。在构建系统调度域拓扑时,会调用build_sched_groups()函数为指定CPU的sched_domain对象构建其所有的sched_group对象。

static int build_sched_groups(struct sched_domain *sd, int cpu)
{struct sched_group *first = NULL, *last = NULL;struct sd_data *sdd = sd->private;const struct cpumask *span = sched_domain_span(sd); // span为该调度域的CPU集合struct cpumask *covered;int i;// 为调度域对象sdd->sd_data.sd[cpu]确定调度组,将确定的调度组对象及其sgc指针// 保存在sdd->sd_data.groups[cpu]中,该函数会修改sd->groups的指向get_group(cpu, sdd, &sd->groups);atomic_inc(&sd->groups->ref);// 后面的逻辑只有在处理调度域对象的第一个CPU时才会执行,// 对于MC层就是每个cluster的第一个CPU,对于DIE层就是整个系统的第一个CPUif (cpu != cpumask_first(span))return 0;lockdep_assert_held(&sched_domains_mutex);covered = sched_domains_tmpmask;cpumask_clear(covered);for_each_cpu(i, span) {// 检查调度域内的每个CPU,判断其是否可以作为一个独立的sched_groupstruct sched_group *sg;int group, j;if (cpumask_test_cpu(i, covered))continue;group = get_group(i, sdd, &sg);cpumask_setall(sched_group_mask(sg)); // 这个逻辑非常奇怪,为什么要设置所有的CPU到调度组?// 找到那些属于同一个sched_group的CPU,将其设置到调度组对象的CPU掩码中for_each_cpu(j, span) {if (get_group(j, sdd, NULL) != group)continue;cpumask_set_cpu(j, covered);cpumask_set_cpu(j, sched_group_cpus(sg));}// 将同一个调度域对象下面的若干个调度组对象通过sched_group.next指针组织成单向循环链表if (!first)first = sg;if (last)last->next = sg;last = sg;}last->next = first;return 0;
}// 为调度域对象sdd->sd[cpu]确定调度组,将确定的调度组对象及其sgc指针保存在sg中
static int get_group(int cpu, struct sd_data *sdd, struct sched_group **sg)
{struct sched_domain *sd = *per_cpu_ptr(sdd->sd, cpu);struct sched_domain *child = sd->child;if (child)cpu = cpumask_first(sched_domain_span(child));if (sg) {*sg = *per_cpu_ptr(sdd->sg, cpu);(*sg)->sgc = *per_cpu_ptr(sdd->sgc, cpu);atomic_set(&(*sg)->sgc->ref, 1); /* for claim_allocations */}return cpu;
}

get_group()函数非常关键,该函数对第一层拓扑和更高层拓扑的逻辑不同。对于第一层拓扑,其child为NULL,这时传入的cpu不会发生变化,这样调度域对象sdd->sd[cpu]关联的调度组对象就是该CPU的调度组对象。对于更高层拓扑,其child不是NULL,这时调度组对象来自其第一层拓扑的第一个CPU对应的对象。这样的get_group()函数实现的效果见下面"构建系统调度域拓扑"中第二组for循环后的示意图。

调度组能力: sched_group_capacity

每个sched_group对象都关联一个sched_group_capacity对象来描述调度组的能力。

struct sched_group_capacity {atomic_t ref; // 同sched_group中的ref// 调度组的capacity    unsigned int capacity, capacity_orig;unsigned long next_update;
...unsigned long cpumask[0]; /* iteration mask */
};

初始化调度组能力: init_sched_groups_capacity()

在构建系统调度域拓扑过程中,会调用该函数将初始化指定调度域的所有调度组能力。调度组的能力和调度组中CPU的capacity强相关,这里不详细展开,在CPU能力相关笔记中再详细分析。

static void init_sched_groups_capacity(int cpu, struct sched_domain *sd)
{struct sched_group *sg = sd->groups;// 为该调度域的所有调度组计算其管理的CPU个数do {sg->group_weight = cpumask_weight(sched_group_cpus(sg));sg = sg->next;} while (sg != sd->groups);// 只有调度组中的第一个CPU才需要执行计算capacity的过程,因为其他CPU都会共享该sgc if (cpu != group_balance_cpu(sg))return;update_group_capacity(sd, cpu); // 计算capacityatomic_set(&sg->sgc->nr_busy_cpus, sg->group_weight);
}

根域: root_domain

根域定义了一些全局信息,所有CPU共用一个根域对象。初始时系统会定义一个def_root_domain作为系统根域,后续整个调度域拓扑结构建立时会重新建立一个根域对象来替换默认的根域对象。

/** By default the system creates a single root-domain with all cpus as* members (mimicking the global state we have today).*/
struct root_domain def_root_domain;struct root_domain {atomic_t refcount;
...struct rcu_head rcu;cpumask_var_t span;cpumask_var_t online;
};// 最终系统拓扑层级中的调度域和根域对象会关联到rq中
struct rq {
...
#ifdef CONFIG_SMPstruct root_domain *rd;struct sched_domain *sd;
#endif
}

cpu_attach_domain()

系统调度域拓扑建立完毕后会调用该函数将每个CPU上的调度域对象(第一级)和根域对象保存到CPU运行队列rq中。

static void
cpu_attach_domain(struct sched_domain *sd, struct root_domain *rd, int cpu)
{struct rq *rq = cpu_rq(cpu);struct sched_domain *tmp;// 遍历系统调度域拓扑结构,去掉那些对调度没有意义的荣誉层级(如相邻两个层级的调度域管理的CPU相同)for (tmp = sd; tmp; ) {struct sched_domain *parent = tmp->parent;if (!parent)break;if (sd_parent_degenerate(tmp, parent)) {tmp->parent = parent->parent;if (parent->parent)parent->parent->child = tmp;if (parent->flags & SD_PREFER_SIBLING)tmp->flags |= SD_PREFER_SIBLING;destroy_sched_domain(parent, cpu);} elsetmp = tmp->parent;}if (sd && sd_degenerate(sd)) {tmp = sd;sd = sd->parent;destroy_sched_domain(tmp, cpu);if (sd)sd->child = NULL;}// 将sd和rd指针保存到rq->sd和rq->rd中rq_attach_root(rq, rd);tmp = rq->sd;rcu_assign_pointer(rq->sd, sd);destroy_sched_domains(tmp, cpu);update_top_cache_domain(cpu);
}

构建系统调度域拓扑

在开机过程中,会调用init_sched_domains()函数根据定义的系统调度域拓扑层级结构构建系统调度域拓扑。

// 传入的cpu_map为cpu_active_mask
static int init_sched_domains(const struct cpumask *cpu_map)
{int err;arch_update_cpu_topology(); // 构建系统调度域拓扑之前让体系结构更新一次自己的cpu_mapndoms_cur = 1;doms_cur = alloc_sched_domains(ndoms_cur);if (!doms_cur)doms_cur = &fallback_doms;cpumask_andnot(doms_cur[0], cpu_map, cpu_isolated_map); // 去掉隔离的CPUerr = build_sched_domains(doms_cur[0], NULL); // 构建系统调度域拓扑结构// 系统调度域拓扑构建完毕,根据结果创建/proc/sys/kernel/domain目录register_sched_domain_sysctl();return err;
}

核心的系统调度域拓扑建立由build_sched_domains()函数完成。

// 该临时数据结构用来保存分配的调度域对象
struct s_data {struct sched_domain ** __percpu sd;struct root_domain    *rd;
};static int build_sched_domains(const struct cpumask *cpu_map,struct sched_domain_attr *attr)
{enum s_alloc alloc_state;struct sched_domain *sd;struct s_data d;int i, ret = -ENOMEM;// 为调度域拓扑层级数组sched_domain_topology分配调度域对象alloc_state = __visit_domain_allocation_hell(&d, cpu_map);if (alloc_state != sa_rootdomain)goto error;// 为每个CPU都建立一个sched_domain对象组成的层级结构(通过sched_domain的parent和child字段)for_each_cpu(i, cpu_map) {struct sched_domain_topology_level *tl;sd = NULL;for_each_sd_topology(tl) { // 拓扑层级从低到高遍历,sd为child// 每构建一层sched_domain对象都是下一层的childsd = build_sched_domain(tl, cpu_map, attr, sd, i);// 保存每个CPU的最低层次sched_domain对象到临时变量d.sd中,后面有用处if (tl == sched_domain_topology)*per_cpu_ptr(d.sd, i) = sd;if (tl->flags & SDTL_OVERLAP || sched_feat(FORCE_SD_OVERLAP))sd->flags |= SD_OVERLAP;// 构建的拓扑层级最高层次只需要覆盖所有active_cpu_mask即可,这样可以防止数组sched_domain_topology// 定义多余的高层次拓扑层级,比如在DIE上面再定义一层,但是DIE层已经可以覆盖系统所有CPU了if (cpumask_equal(cpu_map, sched_domain_span(sd)))break;}}// 为每个CPU的各层sched_domain对象构建调度组对象for_each_cpu(i, cpu_map) {for (sd = *per_cpu_ptr(d.sd, i); sd; sd = sd->parent) {// 由低到高为每一层调度域拓扑层级的sched_domain对象构建调度组对象sd->span_weight = cpumask_weight(sched_domain_span(sd));if (sd->flags & SD_OVERLAP) {// 调度域中各调度组的CPU存在重叠的情况,我们不考虑if (build_overlap_sched_groups(sd, i))goto error;} else {// 为CPU i上的调度域对象sd构建调度组对象if (build_sched_groups(sd, i))goto error;}}}// 1. 为每个CPU的各层sched_domain对象中的sched_group对象设置capacity。// 2. 标记系统调度域拓扑中不需要的调度域对象,后续根据标记结果释放。for (i = nr_cpumask_bits-1; i >= 0; i--) {if (!cpumask_test_cpu(i, cpu_map))continue;for (sd = *per_cpu_ptr(d.sd, i); sd; sd = sd->parent) {claim_allocations(i, sd);init_sched_groups_capacity(i, sd);}}// 将调度域对象和根域对象保存到CPU运行队列中rcu_read_lock();for_each_cpu(i, cpu_map) {sd = *per_cpu_ptr(d.sd, i);cpu_attach_domain(sd, d.rd, i);}rcu_read_unlock();ret = 0;
error:__free_domain_allocs(&d, alloc_state, cpu_map);return ret;
}
  • __visit_domain_allocation_hell()函数只负责调度对象的分配。下图是6小核+2大核的系统在该函数调用之后,系统调度域拓扑层级结构中的调度域对象分配示意图。

  • 第一组for循环中,通过build_sched_domain()函数为每个CPU都建立一个sched_domain对象组成的层级结构(通过sched_domain的parent和child字段)。下图是6小核+2大核的系统在该组for循环之后,系统调度域拓扑层级结构中的调度域对象关系示意图。

  • 第二组for循环中,通过build_sched_groups()函数为每个CPU的各层sched_domain对象构建调度组对象(保存到sched_domain的groups字段中)。下图是6小核+2大核的系统在该组for循环之后,系统调度域拓扑层级结构中的调度域对象关系示意图。

上图中置灰部分表示这些对象没有关联到拓扑结构中。在MC层,每个CPU为一个调度组,同一个cluster内的调度组组成该层的调度域。在DIE层,每个cluster内的CPU为一个调度组,多个cluster的调度组组成该层的调度域。

  • 第三组for循环中,通过claim_allocations()函数将系统调度域拓扑中不需要的调度域对象标记为NULL(上图中置灰部分调度域对象),这样最后的__free_domain_allocs()函数根据标记结果释放这些对象。通过init_sched_groups_capacity()函数为所有的sched_group对象初始化capacity。
  • 第四组for循环中,通过cpu_attach_domain()函数将调度域对象和根域对象保存到CPU运行队列中。
  • 最后,通过__free_domain_allocs()函数释放那些不需要的调度域对象。下图是6小核+2大核的系统在多余的调度域对象被释放之后的示意图。

__visit_domain_allocation_hell()

该函数为系统调度域拓扑层级数组sched_domain_topology分配调度域对象。

static enum s_alloc __visit_domain_allocation_hell(struct s_data *d,const struct cpumask *cpu_map)
{memset(d, 0, sizeof(*d));// 为所有的sched_domain_topology_level.sd_data分配对象if (__sdt_alloc(cpu_map))return sa_sd_storage;// 该临时的Per-CPU变量用于的后续流程,用于指向__sdt_alloc()中分配的调度对象d->sd = alloc_percpu(struct sched_domain *);if (!d->sd)return sa_sd_storage;// 分配并初始化根域对象d->rd = alloc_rootdomain();if (!d->rd)return sa_sd;return sa_rootdomain;
}

claim_allocations()

将调度域sd中使用到的相关调度域对象指针标记为NULL,这样最后的__free_domain_allocs()函数就可以通过判断非NULL指针来将那些未使用到的调度域对象进行回收。需要注意的是这里将tl->sdd.xxx指针设置为NULL并不会导致内存泄漏,因为这些对象的指针在build_sched_domains()函数的临时变量d中还保存了一份,而且最后会通过cpu_attach_domain()函数将sd指针保存到CPU运行队列中。

static void claim_allocations(int cpu, struct sched_domain *sd)
{struct sd_data *sdd = sd->private;WARN_ON_ONCE(*per_cpu_ptr(sdd->sd, cpu) != sd);*per_cpu_ptr(sdd->sd, cpu) = NULL;if (atomic_read(&(*per_cpu_ptr(sdd->sg, cpu))->ref))*per_cpu_ptr(sdd->sg, cpu) = NULL;if (atomic_read(&(*per_cpu_ptr(sdd->sgc, cpu))->ref))*per_cpu_ptr(sdd->sgc, cpu) = NULL;
}

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

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

相关文章

上海亚商投顾:沪指冲高回落 短剧、地产股集体走强

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 三大指数早盘冲高,创业板指盘初涨超1%,午后则集体下行翻绿,北证50一度大涨…

MyBatis:关联查询

MyBatis 前言关联查询附懒加载对象为集合时的关联查询 前言 在 MyBatis:配置文件 文章中,最后介绍了可以使用 select 标签的 resultMap 属性实现关联查询,下面简单示例 关联查询 首先,先创建 association_role 和 association_…

Postgresql常用命令函数

1、string_agg()函数 1.1用法: string_agg(expression, delimiter),参数类型(text, text) or (bytea, bytea),返回类型和参数类型一致,第一个参数是字段名,第二个参数是样式,比如,或者#分隔。 1.2实战: SELECT * FR…

深入解析数据结构与算法之堆

文章目录 🥦引言:🥦什么是堆🥦大顶堆与小顶堆🧄大顶堆(Max Heap)🧄小顶堆(Min Heap) 🥦堆的表示🧄数组表示:🧄…

设计模式总结-笔记

一个目标:管理变化,提供复用! 两种手段:分解vs.抽象 八大原则: 依赖倒置原则(DIP) 开放封闭原则(OCP) 单一职责原则(SRP) Liskov替换原则&a…

C/C++内存管理(1):C/C++内存分布,C++内存管理方式

一、C/C内存分布 1.1 1.2 二、C内存管理方式 C可以通过操作符new和delete进行动态内存管理。 2.1 new和delete操作内置类型 int main() {int* p1 new int;// 注意区分p2和p3int* p2 new int(10);// 对*p2进行初始化 10int* p3 new int[10];// p3 指向一块40个字节的int类…

C#,数值计算——插值和外推,PolCoef的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// polynomial coefficients from polynomial values /// </summary> public class PolCoef { public PolCoef() { } /// <summary>…

【Linux】:共享内存

共享内存 一.原理二.创建共享内存1.shmget2.写一个共享内存代码 三.进行通信1.各种接口2.各接口使用代码3.一次简单的通信四.共享内存的特点 一.原理 直接原理 共享内存顾名思义就是共同使用的一块空间。 很明显操作系统需要对这块内存进行管理&#xff0c;那么就避免不了先描…

Python pip 镜像源设置指南

文章目录 Python pip 镜像源设置指南前言安装单个包使用PyPI镜像使用镜像升级 pip设为默认pip镜像结语 Python pip 镜像源设置指南 前言 平时在使用 pip 安装一些包的时候速度非常慢,本文介绍如何在 Python3 下设置 PyPI 设置镜像源,本文以给 Python3 设置清华 镜像源举例. …

2023.11.20使用flask做一个简单图片浏览器

2023.11.20使用flask做一个简单图片浏览器 功能&#xff1a; &#xff08;1&#xff09;输入指定路径&#xff0c;打开文件夹 &#xff08;2&#xff09;判断文件格式为图片 &#xff08;3&#xff09;在前端进行预览 &#xff08;4&#xff09;使用bootstrap进行简单美化 ma…

win11,引导项管理

1&#xff0c;打开cmd,输入msconfig 2,进入引导选项卡 3&#xff0c;删除不需要的引导项

【CSH 入门基础 9 -- 输出 csh 脚本中每一句命令】

文章目录 输出csh脚本中每一句命令 输出csh脚本中每一句命令 在 csh 或 tcsh 脚本中&#xff0c;如果你想要输出脚本中的每一句执行&#xff0c;你可以在脚本的开头使用 -v&#xff08;verbose&#xff09;选项。这个选项会使得 shell 在执行命令前先打印出来。 要在脚本中使…

蓝桥杯每日一题2023.11.21

题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 思路&#xff1a; 1.去重排序将其进行预处理 2.用gcd得到最简比值 3.用gcd_sub分别计算分子、分母的指数最大公约数 #include<bits/stdc.h> using namespace std; const int N 110; typedef long long ll; ll…

前端性能优化之LightHouse

优质博文&#xff1a;IT-BLOG-CN 一、LightHouse环境搭建 LightHouse是一款由Google开发的开源工具&#xff0c;用于评估Web应用程序的性能和质量。可以将其看作是一个Chrome扩展程序运行&#xff0c;或从命令行运行。为LightHouse提供一个需要审查的网址&#xff0c;它将针对…

V100 GPU服务器安装CUDNN教程

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Go语言常用命令详解(三)

文章目录 前言常用命令go get示例参数说明 go install示例参数说明 go list示例 go mod示例参数说明 go work基本用法示例 go tool示例 go version示例 go vet示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命…

智能座舱架构与芯片- (14) 测试篇 上

一、 验证平台概要 1.1 测试软件方法论 “软件定义汽车” 的时代&#xff0c;软件在整车制造中的重要性日渐凸显。但不同于其他行业的软件开发&#xff0c;汽车行业有自己独特的软件开发要求。首先是需求严谨、需求层次复杂、需要通过专业的工具进行管理&#xff1b;其次开发…

数据中台之用户画像

用户画像应用领域较为广泛,适合于各个产品周期,从新用户的引流到潜在用户的挖掘、 从老用户 的培养到流失用户的回流等。通过挖掘用户兴趣、偏好、人口统计特征,可以 直接 作用于提升营销精准 度、推荐匹配度,最终提升产品服务和企业利润。还包括广告投放、产品布局和行业报…

从0开始学习JavaScript--深入理解JavaScript的async/await

JavaScript的异步编程在过去经历了回调地狱、Promise的引入&#xff0c;而今&#xff0c;通过async/await&#xff0c;让我们获得了更加优雅、可读性更高的异步编程方式。本文将深入探讨async/await的概念、用法&#xff0c;并通过丰富的示例代码展示其在实际应用中的威力。 理…

JVM垃圾收集器

什么是垃圾收集器 Java虚拟机的垃圾收集器是内存回收的具体实现&#xff0c;主要用于回收不再使用的对象&#xff0c;释放内存空间。Java虚拟机提供了多种垃圾收集器&#xff0c;不同的垃圾收集器适用于不同的场景和需求。 在C中&#xff0c;对象所占的内存在程序结束运行之前…