一文看懂linux 内核网络中 RPS/RFS 原理

news2024/12/26 1:00:33

1 自带 irqbalance 瓶颈

基于简单的中断负载均衡(如系统自带的irqbalance进程)可能会弄巧成拙。因为其并不识别网络流,只识别到这是一个数据包,不能识别到数据包的元组信息。

在多处理器系统的每个处理器都有单独的硬件高速缓存,如果其中一个 CPU 修改了自己的硬件高速缓存,它就必须该数据是否包含在其它 CPU 的硬件高速缓存,如果存在,必须通知其它 CPU 更新硬件高速缓存,这叫做 CPU 的 cache 一致性。所以为了降低 CPU 硬件高速缓存的刷新频率,需要把特征相似的数据包分配给同一个 CPU 处理。

另外在TCP的IP包的分段重组问题,一旦乱序就要重传,一个 linux 主机如果只是作为一台路由器的话,那么进入系统的一个TCP包的不同分段如果被不同的 cpu 处理并向一个网卡转发了,那么同步问题会很麻烦的,如果不做同步处理,那么很可能后面的段被一个 cpu 先发出去了,那最后接收方接收到乱序的包后就会请求重发,这样则还不如是一个 cpu 串行处理。

这个需要一套方案,需要弄清楚三个问题:

  • 哪个 CPU 在消耗这个网络数据
  • 哪个 CPU 在处理中断
  • 哪个 CPU 在做软中断

这个三个问题就是 RPS/RFS 需要去解决的。

2 RPS/RFS 数据结构以及更新函数

linux 底层的很多机密都藏在数据结构中,包括其面向对象的设计,所以先来看下这些数据结构。网卡的硬件接收队列 netdev_rx_queue(include/linux/netdevice.h)。

/* This structure contains an instance of an RX queue. */
struct netdev_rx_queue {
#ifdef CONFIG_RPS
	struct rps_map __rcu		*rps_map;
	struct rps_dev_flow_table __rcu	*rps_flow_table;
#endif
	struct kobject			kobj;
	struct net_device		*dev;
} ____cacheline_aligned_in_smp;

2.1 CPU 负载表 rps_map

存放解析结果的就是网卡硬件接收队列实例的 rps_map 成员, cpus数组用来记录配置文件中配置的参与报文分发处理的cpu的数组,而len成员就是cpus数组的长度。

/*
 * This structure holds an RPS map which can be of variable length.  The
 * map is an array of CPUs.
 */
struct rps_map {
	unsigned int len;
	struct rcu_head rcu;
	u16 cpus[0];
};

2.1.1 rps_map 解析函数(store_rps_map)

// net/core/net-sysfs.c
static ssize_t store_rps_map(struct netdev_rx_queue *queue,
			     const char *buf, size_t len)
{
	struct rps_map *old_map, *map;
	cpumask_var_t mask;
	int err, cpu, i;
	static DEFINE_MUTEX(rps_map_mutex);
 
	if (!capable(CAP_NET_ADMIN))
		return -EPERM;
 
	if (!alloc_cpumask_var(&mask, GFP_KERNEL))
		return -ENOMEM;
 
	err = bitmap_parse(buf, len, cpumask_bits(mask), nr_cpumask_bits);
	if (err) {
		free_cpumask_var(mask);
		return err;
	}
 
	map = kzalloc(max_t(unsigned int,
			    RPS_MAP_SIZE(cpumask_weight(mask)), L1_CACHE_BYTES),
		      GFP_KERNEL);
	if (!map) {
		free_cpumask_var(mask);
		return -ENOMEM;
	}
 
	i = 0;
	for_each_cpu_and(cpu, mask, cpu_online_mask)
		map->cpus[i++] = cpu;
 
	if (i) {
		map->len = i;
	} else {
		kfree(map);
		map = NULL;
	}
 
	mutex_lock(&rps_map_mutex);
	old_map = rcu_dereference_protected(queue->rps_map,
					    mutex_is_locked(&rps_map_mutex));
	rcu_assign_pointer(queue->rps_map, map);
 
	if (map)
		static_key_slow_inc(&rps_needed);
	if (old_map)
		static_key_slow_dec(&rps_needed);
 
	mutex_unlock(&rps_map_mutex);
 
	if (old_map)
		kfree_rcu(old_map, rcu);
 
	free_cpumask_var(mask);
	return len;
}
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

2.2 设备流表 rps_dev_flow_table

设备流表 rps_dev_flow_table (include/linux/netdevice.h)。mask 成员是类型为 struct rps_dev_flow 的数组的大小,也就是流表项的数目,通过配置文件 /sys/class/net/(dev)/queues/rx-(n)/rps_flow_cnt 进行指定的。当设置了配置文件,那么内核就会获取到数据,并初始化网卡硬件接收队列中的设备流表成员rps_flow_table(初始化过程函数 store_rps_dev_flow_table_cnt)。

/*
 * The rps_dev_flow_table structure contains a table of flow mappings.
 */
struct rps_dev_flow_table {
	unsigned int mask;
	struct rcu_head rcu;
	struct rps_dev_flow flows[0];
};

rps_dev_flow 类型的实例则主要包括存放着上次处理该流中报文的 cpu 以及所在 cpu 私有数据对象 softnet_data 的 input_pkt_queue 队列尾部索引的两个成员。

/*
 * The rps_dev_flow structure contains the mapping of a flow to a CPU, the
 * tail pointer for that CPU's input queue at the time of last enqueue, and
 * a hardware filter index.
 */
struct rps_dev_flow {
	u16 cpu;
	u16 filter;
	unsigned int last_qtail;
};

2.2.1 设备流表初始化函数 store_rps_dev_flow_table_cnt

 static ssize_t store_rps_dev_flow_table_cnt(struct netdev_rx_queue *queue,
					    const char *buf, size_t len)
{
	unsigned long mask, count;
	struct rps_dev_flow_table *table, *old_table;
	static DEFINE_SPINLOCK(rps_dev_flow_lock);
	int rc;
 
	if (!capable(CAP_NET_ADMIN))
		return -EPERM;
 
	rc = kstrtoul(buf, 0, &count);
	if (rc < 0)
		return rc;
 
	if (count) {
		mask = count - 1;
		/* mask = roundup_pow_of_two(count) - 1;
		 * without overflows...
		 */
		while ((mask | (mask >> 1)) != mask)
			mask |= (mask >> 1);
		/* On 64 bit arches, must check mask fits in table->mask (u32),
		 * and on 32bit arches, must check
		 * RPS_DEV_FLOW_TABLE_SIZE(mask + 1) doesn't overflow.
		 */
#if BITS_PER_LONG > 32
		if (mask > (unsigned long)(u32)mask)
			return -EINVAL;
#else
		if (mask > (ULONG_MAX - RPS_DEV_FLOW_TABLE_SIZE(1))
				/ sizeof(struct rps_dev_flow)) {
			/* Enforce a limit to prevent overflow */
			return -EINVAL;
		}
#endif
		table = vmalloc(RPS_DEV_FLOW_TABLE_SIZE(mask + 1));
		if (!table)
			return -ENOMEM;
 
		table->mask = mask;
		for (count = 0; count <= mask; count++)
			table->flows[count].cpu = RPS_NO_CPU;
	} else {
		table = NULL;
	}
 
	spin_lock(&rps_dev_flow_lock);
	old_table = rcu_dereference_protected(queue->rps_flow_table,
					      lockdep_is_held(&rps_dev_flow_lock));
	rcu_assign_pointer(queue->rps_flow_table, table);
	spin_unlock(&rps_dev_flow_lock);
 
	if (old_table)
		call_rcu(&old_table->rcu, rps_dev_flow_table_release);
 
	return len;
}

2.3 全局的数据流表(rps_sock_flow_table)

该表中包含了数据流期望被处理的 CPU,是当前处理流中报文的应用程序所在的 CPU。全局 socket 流表会在调 recvmsg,sendmsg (inet_accept(), inet_recvmsg(), inet_sendmsg(), inet_sendpage() and tcp_splice_read())被设置更新。最终调用函数 rps_record_sock_flow 来更新 ents 数组的。

/*
 * The rps_sock_flow_table contains mappings of flows to the last CPU
 * on which they were processed by the application (set in recvmsg).
 * Each entry is a 32bit value. Upper part is the high-order bits
 * of flow hash, lower part is CPU number.
 * rps_cpu_mask is used to partition the space, depending on number of
 * possible CPUs : rps_cpu_mask = roundup_pow_of_two(nr_cpu_ids) - 1
 * For example, if 64 CPUs are possible, rps_cpu_mask = 0x3f,
 * meaning we use 32-6=26 bits for the hash.
 */
struct rps_sock_flow_table {
	u32	mask;
 
	u32	ents[0] ____cacheline_aligned_in_smp;
};

mask 成员存放的就是 ents 这个数组的大小,通过配置文件 /proc/sys/net/core/rps_sock_flow_entries 的方式指定的

2.3.1 全局流表更新函数 ( rps_record_sock_flow )

每次用户程序读取数据包都会更新 rps_sock_flow_table 表,保证其中的 CPU号是最新的。

// include/linux/netdevice.h
static inline void rps_record_sock_flow(struct rps_sock_flow_table *table,
					u32 hash)
{
	if (table && hash) {
		unsigned int index = hash & table->mask;
		u32 val = hash & ~rps_cpu_mask;
 
		/* We only give a hint, preemption can change CPU under us */
		val |= raw_smp_processor_id();
 
		if (table->ents[index] != val)
			table->ents[index] = val;
	}
}

3 RPS 工作流程  

rps 和 rfs 的工作都是在软中断上下文中执行,因为该阶段处理工作是和进程无关的,又和底层硬件剥离了。能够实现网络协议栈软中断的负载均衡。

rps 实现的总流程如下:将数据包加入其它CPU的接收队列,其它CPU将会在自己的软中断中执行 process_backlog,process_backlog 将会接收队列中的所有数据包,并调用 __netif_receive_skb() 执行后续工作。

Linux是通过配置文件的方式指定哪些cpu核参与到报文的分发处理,配置文件存放的路径是:/sys/class/net/(dev)/queues/rx-(n)/rps_cpus。设置好该配置文件之后,内核就会去获取该配置文件的内容,然后根据解析的结果生成一个用于参与报文分发处理的cpu列表,这样当收到报文之后,就可以建立起 hash-cpu 的映射关系了,解析函数为store_rps_map,结果存放在 rps_map 中。

rps 会根据数据包的hash值(报文hash值,可以由网卡计算得到,也可以是由软件计算得到,具体的计算也因报文协议不同而有所差异,如tcp报文的hash值是根据四元组信息,即源ip、源端口、目的ip和目的端口进行hash计算得到的)来选择CPU,选目标cpu的动作具体的实现函数是get_rps_cpu,其定义在net/core/dev.c文件中, 实现从cpu列表中获取核号的:

staticint get_rps_cpu(struct net_device *dev, struct sk_buff *skb, structrps_dev_flow **rflowp)

rps 是单纯用报文的hash值来分发报文,而不关注处理该流中报文的应用程序所在的cpu。

那么何时调用函数get_rps_cpu呢?

支持 NAPI 接口的驱动而言,在上半部主要就是将设备加入到 cpu 的私有数据 softnet_data 的待轮询设备列表中,下半部主要就是调用poll回调函数从网卡缓冲区中获取报文,然后向上给协议栈。

函数 get_rps_cpu 会被 netif_rcv_skb 调用,获取到用于分发处理报文的目标 cpu,如果目标 cpu 有效,则会调用 enqueue_to_backlog()(net/core/dev.c )函数,尝试将报文加入到cpu私有数据对象 softnet_data 的 input_pkt_queue 队列中。

数据对象 softnet_data 中有 backlog 的成员,该成员类型为 struct napi_struct,在函数 enqueue_to_backlog() 中,会将 backlog 加入到 cpu 的待轮询设备列表中,并触发软中断,在软中断处理函数 net_rx_action() 中会依次遍历待轮询设备列表中的设备,并调用设备注册的 poll 回调函数来进行报文处理。

从 input_pkt_queue 队列该队列中取出报文,然后调用 __netif_receive_skb() 上报文送至协议栈进行后续处理。

如果没有rps的处理流程(现在一般均采用的是NAPI收包方式),软中断处理函数net_rx_action()会调用网卡驱动注册的poll回调函数从网卡中获取到报文数据后就将报文数据上送至协议栈。

对于有rps的处理流程,软中断处理函数 net_rx_action() 会调用网卡驱动注册的 poll 回调函数从网卡中获取到报文数据后,暂时不直接送至协议栈,而是选择一个目标cpu,将报文放到该cpu私有数据对象 softnet_data 的 input_pkt_queue 队列中,待对列 input_pkt_queue 满了之后,就将该cpu对应的backlog设备对象加入到该cpu的待轮询设备列表中,并触发软中断,软中断处理函数轮询到 backlog 设备对象后,调用poll回调函数 process_backlog() 从 input_pkt_queue 队列中取出报文,再上送至协议栈。

如下图左边是没有使用 rps,右边是使用 rps。

4 RFS 工作流程 

RFS 在 RPS 上的改进,通过RPS可以把同一流的数据包分发给同一个 CPU 核来处理,但是有可能发给该数据流分发的 CPU 核和执行处理该数据流的应用程序的CPU核不是同一个。

当用户态处理报文的 cpu 和内核处理报文软中断的 cpu 不同的时候,就会导致 cpu 的缓存不命中。而 rfs 就是用来处理这种情况的,rfs 的目标是通过指派处理报文的应用程序所在的 cpu 来在内核态处理报文,以此来增加 cpu 的缓存命中率。rfs 和 rps 的主要差别就是在选取分发处理报文的目标cpu上。

rfs 实现指派处理报文的应用程序所在的cpu来在内核态处理报文主要是依靠两个流表来实现的:

  • 一个是设备流表 rps_dev_flow_table记录的是上次在内核态处理该流中报文的cpu
  • 一个是全局的socket流表 rps_sock_flow_table记录报文期望被处理的目标cpu

4.1 负载均衡策略 get_rps_cpu()【核心】

/*
 * get_rps_cpu is called from netif_receive_skb and returns the target
 * CPU from the RPS map of the receiving queue for a given skb.
 * rcu_read_lock must be held on entry.
 */
static int get_rps_cpu(struct net_device *dev, struct sk_buff *skb,
		       struct rps_dev_flow **rflowp)
{
	const struct rps_sock_flow_table *sock_flow_table;
	struct netdev_rx_queue *rxqueue = dev->_rx;
	struct rps_dev_flow_table *flow_table;
	struct rps_map *map;
	int cpu = -1;
	u32 tcpu;
	u32 hash;
 
	if (skb_rx_queue_recorded(skb)) {
		u16 index = skb_get_rx_queue(skb);//获取网卡的rx队列
 
		if (unlikely(index >= dev->real_num_rx_queues)) {
			WARN_ONCE(dev->real_num_rx_queues > 1,
				  "%s received packet on queue %u, but number "
				  "of RX queues is %u\n",
				  dev->name, index, dev->real_num_rx_queues);
			goto done;
		}
		rxqueue += index;
	}
 
	/* Avoid computing hash if RFS/RPS is not active for this rxqueue */
 
	flow_table = rcu_dereference(rxqueue->rps_flow_table);
    
    // RPS 逻辑
	map = rcu_dereference(rxqueue->rps_map);
	if (!flow_table && !map)
		goto done;
 
	skb_reset_network_header(skb);
	hash = skb_get_hash(skb);
	if (!hash)
		goto done;
 
	sock_flow_table = rcu_dereference(rps_sock_flow_table);
    
    // RFS 逻辑
	if (flow_table && sock_flow_table) {
		struct rps_dev_flow *rflow;
		u32 next_cpu;
		u32 ident;
 
		/* First check into global flow table if there is a match */
		ident = sock_flow_table->ents[hash & sock_flow_table->mask];
		if ((ident ^ hash) & ~rps_cpu_mask)
			goto try_rps;
 
		next_cpu = ident & rps_cpu_mask;
 
		/* OK, now we know there is a match,
		 * we can look at the local (per receive queue) flow table
		 */
		rflow = &flow_table->flows[hash & flow_table->mask];
		tcpu = rflow->cpu;
 
		/*
		 * If the desired CPU (where last recvmsg was done) is
		 * different from current CPU (one in the rx-queue flow
		 * table entry), switch if one of the following holds:
		 *   - Current CPU is unset (>= nr_cpu_ids).
		 *   - Current CPU is offline.
		 *   - The current CPU's queue tail has advanced beyond the
		 *     last packet that was enqueued using this table entry.
		 *     This guarantees that all previous packets for the flow
		 *     have been dequeued, thus preserving in order delivery.
		 */
		if (unlikely(tcpu != next_cpu) &&
		    (tcpu >= nr_cpu_ids || !cpu_online(tcpu) ||
		     ((int)(per_cpu(softnet_data, tcpu).input_queue_head -
		      rflow->last_qtail)) >= 0)) {
			tcpu = next_cpu;
			rflow = set_rps_cpu(dev, skb, rflow, next_cpu);
		}
 
		if (tcpu < nr_cpu_ids && cpu_online(tcpu)) {
			*rflowp = rflow;
			cpu = tcpu;
			goto done;
		}
	}
 
try_rps:
 
	if (map) { //RPS 逻辑
		tcpu = map->cpus[reciprocal_scale(hash, map->len)];
		if (cpu_online(tcpu)) {
			cpu = tcpu;
			goto done;
		}
	}
 
done:
	return cpu;
}

rfs 的负载均衡策略通过判断报文的 hash 值(流 hash 值)所对应的两个流表(设备流表和全局socket流表)中的记录的cpu是否相同来实施的。

1、如果当前CPU表(设备流表)对应表项未设置或者当前CPU表对应表项映射的 CPU 核处于离线状态,那么使用期望CPU表(全局流表)对应表项映射的CPU核。

2、如果当前CPU表对应表项映射的CPU核和期望CPU表对应表项映射的CPU核为同一个,就使用这一个核。

3、如果当前CPU表对应表项映射的CPU核和期望CPU表对应表项映射的CPU核不为同一个:

  • a) 如果同一流的前一段数据包未处理完,必须使用当前CPU表对应表项映射的CPU核,避免乱序。
  • b) 如果同一流的前一段数据包已经处理完,那么则可以使用期望CPU表对应表项映射的CPU核。

4、如果 设备流表和 全局流表 均为设置,则使用 rps 策略中的CPU流表。

5、如果 rps表 ,设备流表、全局流表均为设置,返回无效 cpu_index(-1)。

5 RPS/RFS 配置方法

在大于等于2.6.35版本的Linux kernel上可以直接使用,默认都是关闭的。

RPS设置:

RPS指定哪些接收队列需要通过rps平均到配置的cpu列表上。

/sys/class/net/(dev)/queues/rx-(n)/rps_cpus

RFS设置:

每个队列的数据流表总数可以通过下面的参数来设置:

该值设置成rps_sock_flow_entries/N,其中表示设备的接收队列数量。

/sys/class/net/(dev)/queues/rx-(n)/rps_flow_cnt

全局数据流表(rps_sock_flow_table)的总数,红帽是建议设置成 32768,一般设置成最大并发链接数量

/proc/sys/net/core/rps_sock_flow_entries

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

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

相关文章

黑马程序员软件测试实战项目

Ego微商 “Ego微商”微信小程序应用&#xff0c;主要针对于有特色的食品类商品线上零售。通过微信平台的大流量入口&#xff0c;在一定程度上升高了特色食品的影响力&#xff0c;同时借助微信的模板消息快速推送更新的商品&#xff0c;实现轻量级应用的C2C或者是B2C的线上销售…

《论文阅读》BALM: Bundle Adjustment for Lidar Mapping

留个笔记自用 BALM: Bundle Adjustment for Lidar Mapping 做什么 首先是最基础的&#xff0c;Structure-from-Motion&#xff08;SFM&#xff09;&#xff0c;SFM可以简单翻译成运动估计&#xff0c;是一种基于dui8序列图片进行三维重建的算法。简单来说就是是从运动中不同…

12月2日第壹简报,星期五,农历十一月初九

12月2日第壹简报&#xff0c;星期五&#xff0c;农历十一月初九1. 银保监会&#xff1a;2023年1月起在北京、上海、江苏、浙江、福建、广东等10个省市开展商业养老金业务试点。2. 国家首批未来产业科技园试点名单出炉&#xff1a;空天科技未来产业科技园、未来能源与智能机器人…

2022-12-02 编译Android平台OpenCV,用到读取视频时报错:AMediaXXX

文章目录编译Android平台OpenCV&#xff0c;用到读取视频时报错&#xff1a;解决参考编译Android平台OpenCV&#xff0c;用到读取视频时报错&#xff1a; ld: error: undefined symbol: AMediaExtractor_new ld: error: undefined symbol: AMediaExtractor_setDataSourceFd ld…

PyQt5的安装

0. 准备工作 Anaconda3-5.2.0-Windows-x86_64pycharm-professional-2018.2.4PyQt5 5.8.1 1. 如何正确安装PyQt5&#xff1f; 1.1 安装PyQt5 pip install PyQt5 -i https://pypi.douban.com/simple- i表示指定安装源&#xff0c;表示国内源 https://pypi.douban.com/simple …

创建一个SpringCloud项目

文章目录1.首先在**SpringCloud官网**中查看依赖版本号2.创建主Maven项目&#xff1a;在pom文件中引入依赖3.再在这个Maven项目中创建子模块&#xff08;子模块也是Maven&#xff09;(1)创建一个数据库db01和表dept(2)创建实体类dept&#xff08;注意&#xff1a;**每个实体类都…

导包问题解决--ImportError: DLL load failed while importing _path: 找不到指定的模块

一、问题反馈 在运行某个Python程序时&#xff0c;需要导入numpy和matplotlib包如下&#xff1a; import numpy as np import matplotlib.pyplot as plt运行程序时会报错“ImportError: DLL load failed while importing _path: 找不到指定的模块”&#xff1a; 二、问题解决…

信号发生器的电路构成及工作原理

一、信号发生器的电路构成 信号发生器的电路组成有多种形式&#xff0c;一般包括以下几个环节: 基本波形产生电路:波形产生可以由RC振荡器、文丘里电桥振荡器或压控振荡器产生。 波形转换电路:基本波形由正弦波、方波、三角波经过矩形波整形电路、正弦波整形电路、三角波整形电…

经众多Nature文章使用认证!艾美捷抗酒石酸酸性磷酸酶TRAP染色试剂盒

抗酒石酸酸性磷酸酶&#xff08;TRAP&#xff0c;tartrate-resistant acid phosphatase&#xff09;为破骨细胞的标志酶&#xff0c;特异地分布于破骨细胞中&#xff0c;为破骨细胞所特有。通常作为鉴别破骨细胞的重要标志物&#xff0c;使破骨细胞呈红色。Kamiya艾美捷抗酒石酸…

Java单表实现评论回复功能

Java单表实现评论回复功能1.简介2.功能实现图3.数据库设计4.实体类5.实现思路6.功能实现6.1 Sql入手6.2 业务实现7.前端实现8.最终成果1.简介 最近在写毕业设计的时候发现需要实现一个评论功能&#xff0c;然后看了一下掘金和csdn的评论区&#xff0c;如何实现评论功能&#xf…

【已解决】nginx x-cache: MISS

nginx x-cache: MISS 今天在使用nginx的时候发生了巨无语的一件事&#xff0c;明明我已经配置了代理缓存proxy_cache&#xff0c;但是一直未生效&#xff0c;于是我不断进行排错、nginx -s reload&#xff0c;问题始终没有解决。后来我尝试在另一台服务器上使用相同的配置&…

Docker的数据管理(数据卷、容器互联)

Docker的数据管理Docker的数据管理&#xff08;数据卷、容器互联&#xff09;一、数据卷&#xff08;容器与宿主机之间数据共享&#xff09;创建数据卷容器写入数据宿主机写入数据容器只有读的权限二、数据卷容器&#xff08;容器与容器之间数据共享&#xff09;创建两个数据卷…

yocs_velocity_smoother速度平滑库知识

一.C &#xff08;1&#xff09;nth_element()用法 头文件&#xff1a;#include<algorithm> nth_element:在数组或容器中将第几大或小的元素放到该放的位置上。&#xff08;默认第几小&#xff0c;可以用cmp自定义为第几大&#xff09; 如&#xff1a;nth_element(a,…

一篇文章搞懂:词法作用域、动态作用域、回调函数及闭包

前言 把以前一直只限于知道&#xff0c;却不清晰理解的这几个概念完完整整地梳理了一番。内容参考自wiki页面&#xff0c;然后加上自己一些理解。 词法作用域和动态作用域 不管什么语言&#xff0c;我们总要学习作用域(或生命周期)的概念&#xff0c;比如常见的称呼&#xf…

Flutter ーー logger 组件记录日志

Flutter ーー logger 组件记录日志 原文 https://medium.com/simbu/flutter-logging-with-logger-6227308ca199 前言 是时候添加一些日志记录了&#xff0c;我希望能够检查发生的网络请求和关键操作&#xff0c;在应用程序和后端之间的交互变得越来越复杂的情况下&#xff0c;给…

vue2自定义指令及钩子函数

目录​​​​​​​ 一、自定义指令的生命周期 二、局部自定义指令 三、全局自定义指令 1.定义自定义指令 2. index.js install方法安装自定义指令 3. main.js 全局挂载自定义指令 4.使用全局自定义指令 一、自定义指令的生命周期 在Vue中&#xff0c;自定义指令的生命…

晶振不仅仅是可以振荡就够了

晶振相当于人的心脏&#xff0c;能跳动&#xff0c;整个系统才是“活的”。晶振常见有有源晶振、无源晶振。有源晶振比较贵&#xff0c;但是需要外围电路少&#xff0c;供个电就能工作。无源晶振价格便宜&#xff0c;匹配电路复杂些。以无源晶振进行分析&#xff0c;以下是无源…

华为IdeaHub与华为云会议:软硬结合,天生一对

伴随数字化进程&#xff0c;传统视频会议终端存在着配置成本高昂、操作繁琐、组网复杂、需专业维护等问题&#xff0c;已无法满足企业数字化转型的需求&#xff0c;而会议软件使用便捷&#xff0c;但仍存在着与设备兼容性差、画面清晰度低、稳定性及信息安全等问题。面对市场上…

【疑难攻关】——文件包含漏洞

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

线性回归实现原理

一.定义 回归是监督学习的一个重要问题&#xff0c;回归用于预测输入变量和输出变量之间的关系&#xff0c;特别是当输入变量的值发生变化时&#xff0c;输出变量的值也随之发生变化。回归模型正是表示从输入变量到输出变量之间映射的函数 回归的目的是预测数组型的目标值。 …