进程调度

  • 一、进程分类
  • 二、task_struct与进程调度相关的成员
  • 三、进程创建时调度相关成员赋值
  • 四、优先级
  • 五、调度类
  • 六、调度实体
  • 七、调度策略
  • 八、调度类,实体,策略的关系
  • 九、运行队列
  • 十、schedule
  • 十一、scheduler_tick
  • 十二、进程唤醒
  • 十四、调度相关的系统调用
  • 十五、总结

进程调度即从一个进程切换到另一个进程,进程调度需要明确:
1、进程调度的时机,即什么时候进行进程调度;
2、选择哪个进程替换当前进程;
3、如何切换两个进程。

一、进程分类

按进程运行资源限制分类
1、I/O受限(I/O-bound):频繁访问IO,花费时间等待IO操作完成;
2、CPU受限(CPU-bound):需要在CPU计算上花费时间。

按进程功能分类
1、交换式进程(interactive process):进程与用户交互,花费时间等待鼠标,键盘等外设。这类进程的延迟不能太大,否则用户体验不好。典型交互式进程是shell,编辑器等;
2、批处理进程(batch process):无需与用户交互,在后台运行。这类进程响应时间不会太快。典型批处理进程是编译程序;
3、实时进程(real-time process):这种进程需要及时调度。典型实时进程有工业控制程序。

二、task_struct与进程调度相关的成员

截取Linux5.6.4中task_struct进程调度相关的成员:

	int				prio;                    /* 动态优先级 */int				static_prio;             /* 静态优先级 */int				normal_prio;             /* 普通优先级 */unsigned int			rt_priority;     /* 实时优先级 */const struct sched_class	*sched_class;/* 调度类 */struct sched_entity		se;              /* CFS调度实体 */struct sched_rt_entity		rt;          /* RT调度实体 */
#ifdef CONFIG_CGROUP_SCHEDstruct task_group		*sched_task_group;/* 组调度 */
#endifstruct sched_dl_entity		dl;          /* DL调度实体 */unsigned int			policy;          /* 调度策略 */int				nr_cpus_allowed;

三、进程创建时调度相关成员赋值

《深入Linux内核(进程篇)—进程创建与退出》介绍了进程创建的过程,_do_fork完成了进程的创建,进程描述符task_struct中关于进程调度的成员是在_do_fork调用的sched_fork中实现的。
sched_fork函数实现如下:
1、调用__sched_fork初始化子进程调度实体相关成员;
2、如果设置SCHED_RESET_ON_FORK策略,则强制设置子进程优先级及调度策略为普通进程。
3、SCHED_RESET_ON_FORK策略由系统调用sched_setscheduler实现,父进程设置该策略后,子进程也会继承,因而判断子进程sched_reset_on_fork标志即可。
4、设置进程调度类。
5、调用进程调度类p->sched_class->task_fork方法,只有CFS调度类实现了task_fork_fair方法,用于初始化子调度实体vruntime虚拟运行时间。

/** fork()/clone()-time setup:*/
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{unsigned long flags;/* 初始化调度实体 */__sched_fork(clone_flags, p);/** We mark the process as NEW here. This guarantees that* nobody will actually run it, and a signal or other external* event cannot wake it up and insert it on the runqueue either.*/p->state = TASK_NEW;/** Make sure we do not leak PI boosting priority to the child.*/p->prio = current->normal_prio;uclamp_fork(p);/** Revert to default priority/policy on fork if requested.*//* 设置SCHED_RESET_ON_FORK策略,则强制设置子进程优先级及调度策略为普通进程 */if (unlikely(p->sched_reset_on_fork)) {if (task_has_dl_policy(p) || task_has_rt_policy(p)) {p->policy = SCHED_NORMAL;p->static_prio = NICE_TO_PRIO(0);p->rt_priority = 0;} else if (PRIO_TO_NICE(p->static_prio) < 0)p->static_prio = NICE_TO_PRIO(0);p->prio = p->normal_prio = __normal_prio(p);set_load_weight(p, false);/** We don't need the reset flag anymore after the fork. It has* fulfilled its duty:*/p->sched_reset_on_fork = 0;}/* 设置进程调度类 */if (dl_prio(p->prio))return -EAGAIN;else if (rt_prio(p->prio))p->sched_class = &rt_sched_class; /* 实时调度类 */elsep->sched_class = &fair_sched_class; /* CFS调度类 */init_entity_runnable_average(&p->se);/** The child is not yet in the pid-hash so no cgroup attach races,* and the cgroup is pinned to this child due to cgroup_fork()* is ran before sched_fork().** Silence PROVE_RCU.*/raw_spin_lock_irqsave(&p->pi_lock, flags);/** We're setting the CPU for the first time, we don't migrate,* so use __set_task_cpu().*/__set_task_cpu(p, smp_processor_id());/* 调用进程调度类p->sched_class->task_fork方法,只有CFS调度类实现了task_fork_fair方法,用于初始化子进程调度实体vruntime虚拟运行时间。 */if (p->sched_class->task_fork)p->sched_class->task_fork(p);raw_spin_unlock_irqrestore(&p->pi_lock, flags);#ifdef CONFIG_SCHED_INFOif (likely(sched_info_on()))memset(&p->sched_info, 0, sizeof(p->sched_info));
#endif
#if defined(CONFIG_SMP)p->on_cpu = 0;
#endifinit_task_preempt_count(p);
#ifdef CONFIG_SMPplist_node_init(&p->pushable_tasks, MAX_PRIO);RB_CLEAR_NODE(&p->pushable_dl_tasks);
#endifreturn 0;
}

四、优先级

	int				prio;                    /* 动态优先级 */int				static_prio;             /* 静态优先级 */int				normal_prio;             /* 普通优先级 */unsigned int			rt_priority;     /* 实时优先级 */

内核使用0-139表示进程优先级,数值越低优先级越高。优先级0-99用于实时进程,100-139用于普通进程。
MAX_RT_PRIO宏是最大实时进程优先级,数值是100。
DEFAULT_PRIO宏是默认普通进程优先级,数值为120,即创建普通进程默认优先级是120,可以通过nice系统调用修改。
NICE的取值范围是-20到19,负值增大进程优先级,正值减小进程优先级。
实时进程的优先级范围是0…MAX_PRIO-1(0-99),普通进程优先级范围是MAX_RT_PRIO…MAX_PRIO-1(100-139)。

#define MAX_NICE	19
#define MIN_NICE	-20
#define NICE_WIDTH	(MAX_NICE - MIN_NICE + 1)/** Priority of a process goes from 0..MAX_PRIO-1, valid RT* priority is 0..MAX_RT_PRIO-1, and SCHED_NORMAL/SCHED_BATCH* tasks are in the range MAX_RT_PRIO..MAX_PRIO-1. Priority* values are inverted: lower p->prio value means higher priority.** The MAX_USER_RT_PRIO value allows the actual maximum* RT priority to be separate from the value exported to* user-space.  This allows kernel threads to set their* priority to a value higher than any user task. Note:* MAX_RT_PRIO must not be smaller than MAX_USER_RT_PRIO.*/#define MAX_USER_RT_PRIO	100
#define MAX_RT_PRIO		MAX_USER_RT_PRIO#define MAX_PRIO		(MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO		(MAX_RT_PRIO + NICE_WIDTH / 2)

static_prio
进程的静态优先级,静态优先级不会随着时间改变,但是可以通过nice或sched_setscheduler系统调用修改。
normal_prio
基于static_prio和调度策略计算出来的优先级,do_fork进程时,子进程会继承父进程的normal_prio。
对于普通进程,normal_prio等于static_prio;对于实时进程,会根据rt_priority计算normal_prio。
prio
进程的动态优先级。
rt_priority
实时进程优先级

/** __normal_prio - return the priority that is based on the static prio*/
static inline int __normal_prio(struct task_struct *p)
{return p->static_prio;
}/** Calculate the expected normal priority: i.e. priority* without taking RT-inheritance into account. Might be* boosted by interactivity modifiers. Changes upon fork,* setprio syscalls, and whenever the interactivity* estimator recalculates.*/
static inline int normal_prio(struct task_struct *p)
{int prio;if (task_has_dl_policy(p))prio = MAX_DL_PRIO-1;else if (task_has_rt_policy(p))prio = MAX_RT_PRIO-1 - p->rt_priority;elseprio = __normal_prio(p);return prio;
}/** Calculate the current priority, i.e. the priority* taken into account by the scheduler. This value might* be boosted by RT tasks, or might be boosted by* interactivity modifiers. Will be RT if the task got* RT-boosted. If not then it returns p->normal_prio.*/
static int effective_prio(struct task_struct *p)
{p->normal_prio = normal_prio(p);/** If we are RT tasks or we were boosted to RT priority,* keep the priority unchanged. Otherwise, update priority* to the normal priority:*/if (!rt_prio(p->prio))return p->normal_prio;return p->prio;
}

五、调度类

内核提供5种调度类,用于提供完成调度的一些方法。
task_struct描述调度类的成员。

const struct sched_class	*sched_class;/* 调度类 */

内核共有5个调度类,依次为:

kenel/sched/stop_task.c
const struct sched_class stop_sched_class = {.next			= &dl_sched_class,……}
kenel/sched/deadline.c
const struct sched_class dl_sched_class = {.next			= &rt_sched_class,……}
kenel/sched/rt.c
const struct sched_class rt_sched_class = {.next			= &fair_sched_class,}
kenel/sched/fair.c
const struct sched_class fair_sched_class = {.next			= &idle_sched_class,}
kenel/sched/idle.c
const struct sched_class idle_sched_class = {/* .next is NULL */}

优先级高的调度类指向下一优先级调度类,这种优先级的关系可以从schdule()函数中选择下一个要执行的进程看出。

static void __sched notrace __schedule(bool preempt)
{struct task_struct *prev, *next;struct rq *rq;……next = pick_next_task(rq, prev, &rf); /* 选择切换后执行的进程 */……
}
/* 最高优先级调度类 */
#ifdef CONFIG_SMP
#define sched_class_highest (&stop_sched_class)
#else
#define sched_class_highest (&dl_sched_class)
#endif
/* 遍历调度类 */
#define for_class_range(class, _from, _to) \for (class = (_from); class != (_to); class = class->next)#define for_each_class(class) \for_class_range(class, sched_class_highest, NULL)/** Pick up the highest-prio task:*/
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{const struct sched_class *class;struct task_struct *p;……/* 按调度类优先级高低选择下一个执行的进程,高优先级调度类有要执行的进程,则直接返回该进程描述符,不再遍历下一个调度类 */for_each_class(class) {p = class->pick_next_task(rq);if (p)return p;}/* The idle class should always have a runnable task: */BUG();
}

可见各调度类优先级排序为:

stop_sched_class > dl_sched_class > rt_sched_class > fair_sched_class > idle_sched_class 

stop_sched_class调度类具有最高优先级,它可以抢占其他任何调度类调度实体且不被任何调度实体抢占。CPU总是能够选择一个进程执行,如果当前没有可执行的进程,则执行idle进程。当遍历到idle_sched_class调度类后,说明没有要进程要执行。idle_sched_class->pick_next_task(rq)会选择rq->idle进程执行,rq即运行队列。

struct task_struct *pick_next_task_idle(struct rq *rq)
{struct task_struct *next = rq->idle;set_next_task_idle(rq, next, true);return next; /* 返回idle进程描述符 */
}

内核调度类数据结构如下。

struct sched_class {const struct sched_class *next; /* 指向下一个调度类,高优先级指向低优先级 */#ifdef CONFIG_UCLAMP_TASKint uclamp_enabled;
#endif/* 进程加入运行队列 */void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);/* 进程退出运行队列 */void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);/* 进程主动让出CPU,进程退出运行队列后再加入运行队列尾 */void (*yield_task)   (struct rq *rq);bool (*yield_to_task)(struct rq *rq, struct task_struct *p, bool preempt);/* 检查当前进程是否可被新进程抢占 */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);#ifdef CONFIG_SMPint (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);/* 返回进程运行队列CPU number */ int  (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);/* 进程迁移到指定CPU,set_task_cpu调用 */void (*migrate_task_rq)(struct task_struct *p, int new_cpu);/* 进程唤醒 */void (*task_woken)(struct rq *this_rq, struct task_struct *task);/* 修改进程的CPU亲和力(affinity) */void (*set_cpus_allowed)(struct task_struct *p,const struct cpumask *newmask);/* 启动运行队列 */void (*rq_online)(struct rq *rq);/* 禁止运行队列 */void (*rq_offline)(struct rq *rq);
#endif/*scheduler_tick定时器调用,CFS调度算法调用update_curr计算vruntime*/void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);/* do_fork调用,CFS调度算法初始化vruntime */void (*task_fork)(struct task_struct *p);/* 进程切换时前一个进程状态为TASK_DEAD时调用 */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.*//* __sched_setscheduler调用check_class_changed,进程切换调度策略时触发切换调度类以及优先级的变化 */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);/* 系统调用sched_rr_get_interval,返回 round robin time,非RR调度类返回0 */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_SCHEDvoid (*task_change_group)(struct task_struct *p, int type);
#endif
}

六、调度实体

task_struct关于调度实体的成员有三个:
CFS调度实体,用于普通进程;
RT调度实体,用于实时进程;
DL调度实体,用于Deadline进程。

	struct sched_entity		se;              /* CFS调度实体 */struct sched_rt_entity		rt;          /* RT调度实体 */struct sched_dl_entity		dl;          /* DL调度实体 */

之所以定义调度实体,是因为Linux调度的对象可以是进程,也可以是组调度(sched_task_group)。因而定义调度实体抽象被调度的对象。

七、调度策略

决定什么时候以怎样的方式选择一个新进程运行的规则就是所谓的调度策略(scheduling policy)。用户可以通过sched_setscheduler()系统调用设置进程调度策略。
Linux六种调度策略:

/** Scheduling policies*/
#define SCHED_NORMAL		0
#define SCHED_FIFO		1
#define SCHED_RR		2
#define SCHED_BATCH		3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE		5
#define SCHED_DEADLINE		6

SCHED_NORMAL
普通的分时进程调度策略,通过CFS调度器实现。
SCHED_FIFO
实时进程调度策略-先进先出。高优先级抢占低优先级任务,优先级相同则按照先进先出(先到先得)调度。通过RT调度器实现。
SCHED_RR
实时进程调度策略-时间片轮转。高优先级抢占低优先级任务,优先级相同则按照时间片轮转,时间片用完则放至队尾重新排队调度。通过RT调度器实现。
SCHED_BATCH
普通的分时进程调度策略,类似SCHED_NORMAL,根据动态优先级调度,通过CFS调度器实现。
SCHED_IDLE
普通的分时进程调度策略,CPU不能执行其他任务,才执行0号进程。
SCHED_DEADLINE
实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF) 调度算法。通过deadline调度器实现。

八、调度类,实体,策略的关系

按调度类优先级从高到底排列。

调度类 调度策略 调度实体 调度算法
stop_sched_class
dl_sched_class SCHED_DEADLINE sched_dl_entity EDF
rt_sched_class SCHED_RR/SCHED_FIFO sched_rt_entity RR/FIFO
fair_sched_class SCHED_NORMAL/SCHED_BATCH sched_entity CFS
idle_sched_class

九、运行队列

内核为每个CPU分配一个运行队列(runqueue)[每CPU变量],用于组织CPU上进程的运行。
this_rq()用于获取当前CPU运行队列。
task_rq()用于获取进程所在CPU运行队列。
cpu_curr()用于获取指定CPU运行队列当前运行的进程。

DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);#define cpu_rq(cpu)		(&per_cpu(runqueues, (cpu)))
#define this_rq()		this_cpu_ptr(&runqueues)
#define task_rq(p)		cpu_rq(task_cpu(p))
#define cpu_curr(cpu)		(cpu_rq(cpu)->curr)
#define raw_rq()		raw_cpu_ptr(&runqueues)

struct rq 数据结构描述CPU的通用运行队列,记录了一个运行队列所需要的全部信息,包括CFS调度运行队列struct cfs_rq,实时进程调度运行队列struct rt_rq和struct dl_rq,权重load信息,运行队列当前运行进程struct task_struct __rcu *curr,idle进程struct task_struct *idle等。

/** This is the main, per-CPU runqueue data structure.*/
struct rq {/* runqueue lock: */raw_spinlock_t		lock; /* 保护运行队列自旋锁 *//** nr_running and cpu_load should be in the same cacheline because* remote CPUs use both these fields when doing load calculation.*/unsigned int		nr_running; /* 运行队列可运行进程数 */…………unsigned long		nr_load_updates;u64			nr_switches; /* 进程切换次数 */#ifdef CONFIG_UCLAMP_TASK/* Utilization clamp values based on CPU's RUNNABLE tasks */struct uclamp_rq	uclamp[UCLAMP_CNT] ____cacheline_aligned;unsigned int		uclamp_flags;
#define UCLAMP_FLAG_IDLE 0x01
#endifstruct cfs_rq		cfs;/* CFS运行队列,用于普通进程 */struct rt_rq		rt;/* rt运行队列,用于实时进程 */struct dl_rq		dl;/* dl运行队列,用于deadline进程 */#ifdef CONFIG_FAIR_GROUP_SCHED/* list of leaf cfs_rq on this CPU: */struct list_head	leaf_cfs_rq_list;struct list_head	*tmp_alone_branch;
#endif /* CONFIG_FAIR_GROUP_SCHED *//** This is part of a global counter where only the total sum* over all CPUs matters. A task can increase this counter on* one CPU and if it got migrated afterwards it may decrease* it on another CPU. Always updated under the runqueue lock:*/unsigned long		nr_uninterruptible;struct task_struct __rcu	*curr;/* 当前正在运行的进程描述符 */struct task_struct	*idle;/*idle进程描述符 */struct task_struct	*stop;/*stop进程描述符 */unsigned long		next_balance;struct mm_struct	*prev_mm;/*进程切换是用于存放被替换进程内存描述符 */unsigned int		clock_update_flags;u64			clock;/* Ensure that all clocks are in the same cache line */u64			clock_task ____cacheline_aligned;u64			clock_pelt;unsigned long		lost_idle_time;atomic_t		nr_iowait;/*等待io操作结束的进程数*/#ifdef CONFIG_MEMBARRIERint membarrier_state;
#endif#ifdef CONFIG_SMPstruct root_domain		*rd;struct sched_domain __rcu	*sd;/*调度域*/unsigned long		cpu_capacity;unsigned long		cpu_capacity_orig;struct callback_head	*balance_callback;unsigned char		idle_balance;unsigned long		misfit_task_load;/* For active balancing */int			active_balance;int			push_cpu;struct cpu_stop_work	active_balance_work;/* CPU of this runqueue: */int			cpu;int			online;struct list_head cfs_tasks;struct sched_avg	avg_rt;struct sched_avg	avg_dl;
#ifdef CONFIG_HAVE_SCHED_AVG_IRQstruct sched_avg	avg_irq;
#endifu64			idle_stamp;u64			avg_idle;/* This is used to determine avg_idle's max value */u64			max_idle_balance_cost;
#endif#ifdef CONFIG_IRQ_TIME_ACCOUNTINGu64			prev_irq_time;
#endif
#ifdef CONFIG_PARAVIRTu64			prev_steal_time;
#endif
#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTINGu64			prev_steal_time_rq;
#endif/* calc_load related fields */unsigned long		calc_load_update;long			calc_load_active;#ifdef CONFIG_SCHED_HRTICK
#ifdef CONFIG_SMPint			hrtick_csd_pending;call_single_data_t	hrtick_csd;
#endifstruct hrtimer		hrtick_timer;
#endif#ifdef CONFIG_SCHEDSTATS/* latency stats */struct sched_info	rq_sched_info;unsigned long long	rq_cpu_time;/* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? *//* sys_sched_yield() stats */unsigned int		yld_count;/* schedule() stats */unsigned int		sched_count;unsigned int		sched_goidle;/* try_to_wake_up() stats */unsigned int		ttwu_count;unsigned int		ttwu_local;
#endif#ifdef CONFIG_SMPstruct llist_head	wake_list;
#endif#ifdef CONFIG_CPU_IDLE/* Must be inspected within a rcu lock section */struct cpuidle_state	*idle_state;
#endif
}

十、schedule

schedule是完成进程调度的入口函数,其调用__schedule完成调度。__schedule是调度器核心函数。
触发__schedule调度的时机有如下几种情况:

  1. 阻塞操作:进程调用mutex, semaphore, waitqueue等;
  2. 中断返回及系统调用返回用户空间时,会检查TIF_NEED_RESCHED标志,设置TIF_NEED_RESCHED标志,则产生调度。该标志在scheduler_tick定时器中可能被设置。
  3. 将要被唤醒的进程不会马上调用schedule,而是被添加到运行队列(run-queue),并设置TIF_NEED_RESCHED标志位。被唤醒进程调度时机与内核是否开启CONFIG_PREEMPTION有关。
    如果内核开启抢占(CONFIG_PREEMPTION=y):
    1)如果唤醒动作发生在系统调用或者异常处理上下文(in syscall or exception context),则在下一次preempt_enable()检查是否需要抢占调度;
    2)如果唤醒动作发生在硬中断处理上下文(in IRQ context),则硬中断返回前会检查是否抢占调度。
    如果内核没有开启抢占(CONFIG_PREEMPTION未设置),则调度发生在:
    1)当前进程主动调用cond_resched();
    2)当前进程主动调用schedule();
    3)系统调用或者异常处理返回用户空间;
    4)中断处理返回用户空间。

中断处理完成返回与中断处理完成返回用户空间是两个不同的概念,前者每次中断返回都会检查是否抢占调度,无论中断发生在内核空间还是用户空间;后者只有中断发生在用户空间才会检查。

/** __schedule() is the main scheduler function.** The main means of driving the scheduler and thus entering this function are:**   1. Explicit blocking: mutex, semaphore, waitqueue, etc.**   2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return*      paths. For example, see arch/x86/entry_64.S.**      To drive preemption between tasks, the scheduler sets the flag in timer*      interrupt handler scheduler_tick().**   3. Wakeups don't really cause entry into schedule(). They add a*      task to the run-queue and that's it.**      Now, if the new task added to the run-queue preempts the current*      task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets*      called on the nearest possible occasion:**       - If the kernel is preemptible (CONFIG_PREEMPTION=y):**         - in syscall or exception context, at the next outmost*           preempt_enable(). (this might be as soon as the wake_up()'s*           spin_unlock()!)**         - in IRQ context, return from interrupt-handler to*           preemptible context**       - If the kernel is not preemptible (CONFIG_PREEMPTION is not set)*         then at the next:**          - cond_resched() call*          - explicit schedule() call*          - return from syscall or exception to user-space*          - return from interrupt-handler to user-space** WARNING: must be called with preemption disabled!*/

__schedule函数的实现:
1、找到将被切换的进程描述符prev,即运行队列里记录的当前进程。
2、找到将切换的进程描述符next,即调用pick_next_task选择一个切换的进程,调用调度类提供的pick_next_task方法实现,在调度类一节中已说明。
3、调用context_switch切换到next进程。

static void __sched notrace __schedule(bool preempt)
{struct task_struct *prev, *next;unsigned long *switch_count;struct rq_flags rf;struct rq *rq;int cpu;cpu = smp_processor_id();rq = cpu_rq(cpu);prev = rq->curr; /* prev是当前运行的进程 */schedule_debug(prev, preempt);if (sched_feat(HRTICK))hrtick_clear(rq);local_irq_disable();rcu_note_context_switch(preempt);/** Make sure that signal_pending_state()->signal_pending() below* can't be reordered with __set_current_state(TASK_INTERRUPTIBLE)* done by the caller to avoid the race with signal_wake_up().** The membarrier system call requires a full memory barrier* after coming from user-space, before storing to rq->curr.*/rq_lock(rq, &rf);smp_mb__after_spinlock();/* Promote REQ to ACT */rq->clock_update_flags <<= 1;update_rq_clock(rq);switch_count = &prev->nivcsw;if (!preempt && prev->state) {if (signal_pending_state(prev->state, prev)) {prev->state = TASK_RUNNING;} else {deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);if (prev->in_iowait) {atomic_inc(&rq->nr_iowait);delayacct_blkio_start();}}switch_count = &prev->nvcsw;}/* 调用调度类pick_next_task方法选择一个切换的进程 */next = pick_next_task(rq, prev, &rf);clear_tsk_need_resched(prev);clear_preempt_need_resched();/* 当前进程与切换到的进程不是同一个进程,则调用context_switch */if (likely(prev != next)) {rq->nr_switches++;/* 运行队列进程切换统计加1 *//** RCU users of rcu_dereference(rq->curr) may not see* changes to task_struct made by pick_next_task().*/RCU_INIT_POINTER(rq->curr, next);/** The membarrier system call requires each architecture* to have a full memory barrier after updating* rq->curr, before returning to user-space.** Here are the schemes providing that barrier on the* various architectures:* - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC.*   switch_mm() rely on membarrier_arch_switch_mm() on PowerPC.* - finish_lock_switch() for weakly-ordered*   architectures where spin_unlock is a full barrier,* - switch_to() for arm64 (weakly-ordered, spin_unlock*   is a RELEASE barrier),*/++*switch_count;trace_sched_switch(preempt, prev, next);/* Also unlocks the rq: *//* 调用context_switch完成进程切换 */rq = context_switch(rq, prev, next, &rf);} else {rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);rq_unlock_irq(rq, &rf);}balance_callback(rq);
}

进程切换由两部分组成:

  1. 切换页全局目录安装一个新的地址空间;
  2. 切换内核态堆栈及硬件上下文。

context_switch实现了上述两部分内容。
1、通过进程描述符next->mm是否为空判断当前进程是否是内核线程,因为内核线程的内存描述符mm_struct *mm总是为空,详见《深入Linux内核(进程篇)—进程描述》内存描述一节。
2、如果是内核线程则借用prev进程的active_mm,对于用户进程,active_mm == mm,对于内核线程,mm == NULL,active_mm==prev->active_mm。
3、如果prev->mm不为空,则说明prev是用户进程,调用mmgrab增加mm->mm_count引用计数。
4、对于内核线程,会启动懒惰TLB模式。懒惰TLB模式是为了减少无用的TLB刷新,关于TLB的内容详见《深入Linux内核(内存篇)-内存管理》TLB一节。enter_lazy_tlb与体系结构相关。
5、如果是用户进程则调用switch_mm_irqs_off完成用户地址空间切换,switch_mm_irqs_off(或switch_mm)与体系结构相关。
6、调用switch_to完成内核态堆栈及硬件上下文切换,switch_to与体系结构相关。
7、switch_to执行完成后,next进程获得CPU使用权,prev进程进入睡眠状态。
8、调用finish_task_switch,如果prev是内核线程,则调用mmdrop减少内存描述符引用计数。如果引用计数为0,则释放与页表相关的所有描述符和虚拟内存。

/** context_switch - switch to the new MM and the new thread's register state.*/
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next, struct rq_flags *rf)
{prepare_task_switch(rq, prev, next);/** For paravirt, this is coupled with an exit in switch_to to* combine the page table reload and the switch backend into* one hypercall.*/arch_start_context_switch(prev);/** kernel -> kernel   lazy + transfer active*   user -> kernel   lazy + mmgrab() active** kernel ->   user   switch + mmdrop() active*   user ->   user   switch*/if (!next->mm) {                                // to kernelenter_lazy_tlb(prev->active_mm, next);next->active_mm = prev->active_mm;if (prev->mm)                           // from usermmgrab(prev->active_mm);elseprev->active_mm = NULL;} else {                                        // to usermembarrier_switch_mm(rq, prev->active_mm, next->mm);/** sys_membarrier() requires an smp_mb() between setting* rq->curr / membarrier_switch_mm() and returning to userspace.** The below provides this either through switch_mm(), or in* case 'prev->active_mm == next->mm' through* finish_task_switch()'s mmdrop().*//* 调用switch_mm_irqs_off完成用户地址空间切换 */switch_mm_irqs_off(prev->active_mm, next->mm, next);if (!prev->mm) {                        // from kernel/* will mmdrop() in finish_task_switch(). */rq->prev_mm = prev->active_mm;prev->active_mm = NULL;}}rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);prepare_lock_switch(rq, next, rf);/* Here we just switch the register state and the stack. *//* 调用switch_to完成内核态堆栈及硬件上下文切换 */switch_to(prev, next, prev);barrier();return finish_task_switch(prev);
}

十一、scheduler_tick

schdule一节中介绍调度时机与TIF_NEED_RESCHED标志密不可分,而TIF_NEED_RESCHED标志的设置是由scheduler_tick定时器中断完成的。

To drive preemption between tasks, the scheduler sets the flag in timerinterrupt handler scheduler_tick().

scheduler_tick由定时器以HZ频率周期调用。

/** This function gets called by the timer code, with HZ frequency.* We call it with interrupts disabled.*/
void scheduler_tick(void)
{int cpu = smp_processor_id();struct rq *rq = cpu_rq(cpu);struct task_struct *curr = rq->curr;struct rq_flags rf;sched_clock_tick();rq_lock(rq, &rf);/* 更新当前CPU运行队列rq中的时钟计数clock和click_task */update_rq_clock(rq);/* 调度类task_tick方法,对于CFS和RR调度策略,TIF_NEED_RESCHED标志即在该方法中设置,其他调度策略不设置TIF_NEED_RESCHED */curr->sched_class->task_tick(rq, curr, 0);calc_global_load_tick(rq);psi_task_tick(rq);rq_unlock(rq, &rf);perf_event_task_tick();#ifdef CONFIG_SMPrq->idle_balance = idle_cpu(cpu);trigger_load_balance(rq);
#endif
}

十二、进程唤醒

进程睡眠状态下,可以被动等待被调度(进程切换时在运行队列中被选为next),也可以被其他进程调用wake_up_process唤醒。
wake_up_process其实调用的是try_to_wake_up。第二个入参为TASK_NORMAL(TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE),即wake_up_process唤醒处于睡眠的进程。

int wake_up_process(struct task_struct *p)
{return try_to_wake_up(p, TASK_NORMAL, 0);
}

try_to_wake_up入参分别为:

  • task_struct *p 被唤醒进程的进程描述符;
  • unsigned int state 可以被唤醒进程的状态掩码;
  • int wake_flags 唤醒标志:WF_SYNC,WF_FORK和WF_MIGRATED。

try_to_wake_up返回值为:

  • int success 被唤醒进程的状态是否发生变化,1为成功唤醒进程,0为没有唤醒进程;

try_to_wake_up实现如下。

static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{unsigned long flags;int cpu, success = 0;preempt_disable();/* 禁止抢占 */if (p == current) {/** We're waking current, this means 'p->on_rq' and 'task_cpu(p)* == smp_processor_id()'. Together this means we can special* case the whole 'p->on_rq && ttwu_remote()' case below* without taking any locks.** In particular:*  - we rely on Program-Order guarantees for all the ordering,*  - we're serialized against set_special_state() by virtue of*    it disabling IRQs (this allows not taking ->pi_lock).*/if (!(p->state & state))goto out;success = 1;cpu = task_cpu(p);trace_sched_waking(p);p->state = TASK_RUNNING;trace_sched_wakeup(p);goto out;}/** If we are going to wake up a thread waiting for CONDITION we* need to ensure that CONDITION=1 done by the caller can not be* reordered with p->state check below. This pairs with mb() in* set_current_state() the waiting thread does.*/raw_spin_lock_irqsave(&p->pi_lock, flags);smp_mb__after_spinlock();if (!(p->state & state))goto unlock; /* 如果唤醒进程的状态与可被唤醒的状态不匹配,则走退出流程 */trace_sched_waking(p);/* We're going to change ->state: */success = 1;/* 将返回值设置为1,即进程状态发生变化 */cpu = task_cpu(p);/* 进程运行的cpu *//** Ensure we load p->on_rq _after_ p->state, otherwise it would* be possible to, falsely, observe p->on_rq == 0 and get stuck* in smp_cond_load_acquire() below.** sched_ttwu_pending()			try_to_wake_up()*   STORE p->on_rq = 1			  LOAD p->state*   UNLOCK rq->lock** __schedule() (switch to task 'p')*   LOCK rq->lock			  smp_rmb();*   smp_mb__after_spinlock();*   UNLOCK rq->lock** [task p]*   STORE p->state = UNINTERRUPTIBLE	  LOAD p->on_rq** Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in* __schedule().  See the comment for smp_mb__after_spinlock().*/smp_rmb();if (p->on_rq && ttwu_remote(p, wake_flags))goto unlock;#ifdef CONFIG_SMP/** Ensure we load p->on_cpu _after_ p->on_rq, otherwise it would be* possible to, falsely, observe p->on_cpu == 0.** One must be running (->on_cpu == 1) in order to remove oneself* from the runqueue.** __schedule() (switch to task 'p')	try_to_wake_up()*   STORE p->on_cpu = 1		  LOAD p->on_rq*   UNLOCK rq->lock** __schedule() (put 'p' to sleep)*   LOCK rq->lock			  smp_rmb();*   smp_mb__after_spinlock();*   STORE p->on_rq = 0			  LOAD p->on_cpu** Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in* __schedule().  See the comment for smp_mb__after_spinlock().*/smp_rmb();/** If the owning (remote) CPU is still in the middle of schedule() with* this task as prev, wait until its done referencing the task.** Pairs with the smp_store_release() in finish_task().** This ensures that tasks getting woken will be fully ordered against* their previous state and preserve Program Order.*/smp_cond_load_acquire(&p->on_cpu, !VAL);p->sched_contributes_to_load = !!task_contributes_to_load(p);p->state = TASK_WAKING;if (p->in_iowait) {delayacct_blkio_end(p);atomic_dec(&task_rq(p)->nr_iowait);}/* 为进程p选择一个cpu */cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);if (task_cpu(p) != cpu) {/* 选择的cpu与当前cpu不同,则进程迁移 */wake_flags |= WF_MIGRATED;psi_ttwu_dequeue(p);set_task_cpu(p, cpu);}#else /* CONFIG_SMP */if (p->in_iowait) {delayacct_blkio_end(p);atomic_dec(&task_rq(p)->nr_iowait);}#endif /* CONFIG_SMP */ttwu_queue(p, cpu, wake_flags);/* 将进程添加到运行队列 */
unlock:raw_spin_unlock_irqrestore(&p->pi_lock, flags);
out:if (success)ttwu_stat(p, cpu, wake_flags);preempt_enable();/* 使能抢占 */return success;
}

唤醒进程将进程添加到运行队列是调用ttwu_queue,再调用ttwu_do_activate实现的。

static void ttwu_queue(struct task_struct *p, int cpu, int wake_flags)
{struct rq *rq = cpu_rq(cpu);struct rq_flags rf;#if defined(CONFIG_SMP)if (sched_feat(TTWU_QUEUE) && !cpus_share_cache(smp_processor_id(), cpu)) {sched_clock_cpu(cpu); /* Sync clocks across CPUs */ttwu_queue_remote(p, cpu, wake_flags);/* 远程唤醒进程,核间通信 */return;}
#endifrq_lock(rq, &rf);update_rq_clock(rq);ttwu_do_activate(rq, p, wake_flags, &rf);/* 进程入队 */rq_unlock(rq, &rf);
}

ttwu_do_activate实现如下。
1、调用activate_task完成enqueue_task入队操作;
2、调用ttwu_do_wakeup检测当前进程是否可以被抢占,并将唤醒进程的状态设置为TASK_RUNNING。

static void
ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags,struct rq_flags *rf)
{int en_flags = ENQUEUE_WAKEUP | ENQUEUE_NOCLOCK;lockdep_assert_held(&rq->lock);#ifdef CONFIG_SMPif (p->sched_contributes_to_load)rq->nr_uninterruptible--;if (wake_flags & WF_MIGRATED)en_flags |= ENQUEUE_MIGRATED;
#endif/* 调用enqueue_task将进程加入对应调度类的运行队列 */activate_task(rq, p, en_flags);/* 检测当前是否可以抢占,并将唤醒进程的状态设置为TASK_RUNNING */ttwu_do_wakeup(rq, p, wake_flags, rf);
}

除了wake_up_process唤醒进程外,fork的子进程也会在do_fork流程中被唤醒,do_fork中的唤醒是调用wake_up_new_task实现的。
1、将进程状态设置为TASK_RUNNING;
2、调用activate_task将进程加入对应调度类的运行队列;
3、调用check_preempt_curr检测当前是否可以抢占;

/*1. wake_up_new_task - wake up a newly created task for the first time.2.  3. This function will do some initial scheduler statistics housekeeping4. that must be done for every newly created context, then puts the task5. on the runqueue and wakes it.*/
void wake_up_new_task(struct task_struct *p)
{struct rq_flags rf;struct rq *rq;raw_spin_lock_irqsave(&p->pi_lock, rf.flags);p->state = TASK_RUNNING;/*将进程状态设置为TASK_RUNNING*/
#ifdef CONFIG_SMP/** Fork balancing, do it here and not earlier because:*  - cpus_ptr can change in the fork path*  - any previously selected CPU might disappear through hotplug** Use __set_task_cpu() to avoid calling sched_class::migrate_task_rq,* as we're not fully set-up yet.*/p->recent_used_cpu = task_cpu(p);__set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
#endifrq = __task_rq_lock(p, &rf);update_rq_clock(rq);post_init_entity_util_avg(p);/* 调用enqueue_task将进程加入对应调度类的运行队列 */activate_task(rq, p, ENQUEUE_NOCLOCK);trace_sched_wakeup_new(p);check_preempt_curr(rq, p, WF_FORK);/* 检测当前是否可以抢占 */
#ifdef CONFIG_SMPif (p->sched_class->task_woken) {/** Nothing relies on rq->lock after this, so its fine to* drop it.*/rq_unpin_lock(rq, &rf);p->sched_class->task_woken(rq, p);rq_repin_lock(rq, &rf);}
#endiftask_rq_unlock(rq, p, &rf);
}

check_preempt_curr检测当前是否可以抢占的原则:

  1. 如果抢占进程与当前进程属于同一调度类,则调用调度类check_preempt_curr方法检查当前进程是否可以抢占;
  2. 抢占进程与当前进程不是同一调度类,则按照调度类的优先级判别。

具体实现如下:

void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
{const struct sched_class *class;/* 抢占进程与当前进程属于同一调度类,则调用调度类check_preempt_curr方法检查当前进程是否可以抢占 */if (p->sched_class == rq->curr->sched_class) {rq->curr->sched_class->check_preempt_curr(rq, p, flags);} else {/* 抢占进程与当前进程不是同一调度类,则按照调度类的优先级判别 */for_each_class(class) {/* 按优先级从高到低遍历调度类 *//* 匹配到当前进程,则说明当前进程调度类优先级高于抢占进程,即不可抢占 */if (class == rq->curr->sched_class)break;/* 匹配到抢占进程,则说明抢占进程调度类优先级高于当前进程,即可抢占 */if (class == p->sched_class) {resched_curr(rq);/* 触发延时调度,抢占CPU */break;}}}/** A queue event has occurred, and we're going to schedule.  In* this case, we can save a useless back to back clock update.*/if (task_on_rq_queued(rq->curr) && test_tsk_need_resched(rq->curr))rq_clock_skip_update(rq);
}

十四、调度相关的系统调用

sched_setscheduler

系统调用 作用
nice 改变进程优先级
get_priority SCHED_DEADLINE
sched_yield 当前进程进入运行队列尾让出CPU,执行schdule
sched_get_priority_min 返回实时进程最小优先级,根据调度策略区分
sched_get_priority_max 返回实时进程最大优先级,根据调度策略区分
sched_getscheduler 根据进程PID获取进程调度策略
sched_setscheduler 改变进程调度策略
sched_rr_get_interval 获取进程的时间片
sched_setparam 与sched_setscheduler类似,但是不修改调度策略
sched_getparam 获取进程的rt_priority
sched_getaffinity 获取进程亲和力
sched_setaffinity 设置进程亲和力

十五、总结

现在可以回答开篇提到进程调度需要明确的三个问题:

  1. 进程切换的时机:
    进程切换的入口函数就是schedule(),凡是触发schedlue(函数主体由__schedule完成)的操作都会导致进程切换。
    触发__schedule调度的时机有如下几种情况:

    • 阻塞操作:进程调用mutex, semaphore, waitqueue等;
    • 中断返回及系统调用返回用户空间时,会检查TIF_NEED_RESCHED标志,设置TIF_NEED_RESCHED标志,则产生调度。该标志在scheduler_tick定时器中可能被设置。
    • 将要被唤醒的进程不会马上调用schedule,而是被添加到运行队列(run-queue),并设置TIF_NEED_RESCHED标志位。被唤醒进程调度时机与内核是否开启CONFIG_PREEMPTION有关。
    • 如果内核开启抢占(CONFIG_PREEMPTION=y):
      1)如果唤醒动作发生在系统调用或者异常处理上下文(in syscall or exception context),则在下一次preempt_enable()检查是否需要抢占调度;
      2)如果唤醒动作发生在硬中断处理上下文(in IRQ context),则硬中断返回前会检查是否抢占调度。
    • 如果内核没有开启抢占(CONFIG_PREEMPTION未设置),则调度发生在:
      1)当前进程主动调用cond_resched();
      2)当前进程主动调用schedule();
      3)系统调用或者异常处理返回用户空间;
      4)中断处理返回用户空间。
  2. 选择哪个进程替换当前进程:
    schedule()函数通过调用pick_next_task选择一个进程替换当前进程。pick_next_task通过调用调度类中提供的pick_next_task方法实现next进程的选择。内核选择调度类时会首选CFS调度类,因为Linux中一般情况下都是普通进程。如果系统中存在其他调度类调度实体,则依调度类的优先级从高到低选择next进程。

struct task_struct *(*pick_next_task)(struct rq *rq);
  1. 切换两个进程:
    结合前两个问题,解决了schdule调用时机和选择替换的进程的问题后,就满足了完成进程切换的条件。当前进程是被替换的进程,记录在运行队列rq->curr成员中,__schedule调用context_switch完成进程切换。context_switch实现了切换进程的地址空间以及切换内核态堆栈及硬件上下文。

本文内核版本为Linux5.6.4。

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

相关文章

  1. Establishing SSL connection without server‘s identity verification is not recommended

    在启动SpringBoot项目的时候,控制台出现了如下警告,虽然不影响开发,但是每次启动看到这种提示还是很烦的:WARN: Establishing SSL connection without servers identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SS…...

    2024/4/25 3:27:58
  2. 微信小程序使用表单组件实现用户信息搜集问卷调查案例

    input输入框。该组件是原生组件,使用时请注意相关限制属性类型默认值必填说明最低版本valuestring是输入框的初始内容1.0.0typestringtext否input 的类型1.0.0passwordbooleanfalse否是否是密码类型1.0.0placeholderstring是输入框为空时占位符1.0.0placeholder-stylestring是…...

    2024/4/27 23:52:45
  3. 做了一段时间软件测试,感觉在浪费时间,怎么办?

    初入职场 第一份工作是测试点工。在外包公司。加班多,然后辞职了。 自我规划 第二份工作还是测试。想去转开发,但一下找不到测试转开发的下家,就继续做测试。做完工作后,就去找资料学习、请教,帮师傅做事,接触到了很多东西。 实现职位 自那开始,平时就是一边工作一边学习…...

    2024/4/27 16:36:55
  4. WebRTC音视频录制实战 七、第二节 录制音视频实战

    这一节我来实际操练一下,看看具体如何通过MediaRecorder来录制 音视频数据。在开发之前我们将整个流程给大家梳理一下,首先我们需要加几个标签,第一个标签是video标签,也就是说当我们开启录制之后,通过第二个video标签将录制的视频播放出来,所以我们要加第二个video标签 …...

    2024/4/27 19:22:10
  5. 数学之美第三章(统计语言模型)

    数学之美——统计语言模型假定S表示某一个有意义的句子,由一连串特定顺序排列的词w1,w2…wn组成,这里n是句子的长度。现在,我们想知道S在文本中出现的可能性,也就是数学上所说的S的概率P(S)。 因此,需要有个模型来估算。既然S = w1,w2…,wn,那么不妨把P(S)展开表示: P(w1, …...

    2024/4/27 21:07:07
  6. Java零基础学习实践系列(四十四):什么是继承?

    四十四:什么是继承?上一节详细介绍了封装,本节开始介绍继承.1.理论整理继承的本质是对某一类的抽象,从而实现对显示世界更好的建模.extends 的意思是"扩展".子类是父类的扩展. Java中只有单继承,没有多继承. 继承是类与类之间的一种关系,除此之外类之间的关系还有依…...

    2024/4/27 16:52:50
  7. 恢复空格(DP)

    哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!"已经变成了"iresetthecomputeritstilldidntboot"。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一…...

    2024/4/27 18:04:53
  8. Anchor-free之CenterNet

    anchor-base VS Anchor-freeAnchor-base存在的问题:•与锚点框相关超参 (scale、aspect ratio、IoU Threshold) 会较明显的影响最终预测效果;•预置的锚点大小、比例在检测差异较大物体时不够灵活;•大量的锚点会导致运算复杂度增大,产生的参数较多;•容易导致训练时negat…...

    2024/4/27 17:14:38
  9. C语言链表

    C语言链表 #include “linklist.h” STU_INFO_T *linklist_init() { STU_INFO_T *p_head = NULL; STU_INFO_T *p_tmp = NULL; STU_INFO_T *p_cur = NULL; p_head = (STU_INFO_T *)malloc(sizeof(STU_INFO_T));if (p_head == NULL) {printf("<%s:%d>memory request e…...

    2024/4/27 16:57:56
  10. 建群网培PMP每日一练2020-7-9

    大家好,今天有些晚,建群网培PMP给大家带了10道综合题,希望大家的正确率能达到70-80%,加油。建群网培PMP,OnePass。1、公司要求能够对一个 200,000 米, 每天生产 10,000 单位的工业厂房施工。 该工厂将设在未经利用的地产中, 该地产尚不具备公共设施、 进出道路和其他基础…...

    2024/4/27 20:52:04
  11. springboot源码解读-启动时做了什么工作

    拿到完全限定名,然后去实例化自定义spring factories也会被调用...

    2024/4/27 17:15:02
  12. DPDK基础库LPM

    DPDK中LPM(Longest Prefix Match)的实现,使用了DIR-24-8算法的一个变种,实际上就是用空间换时间。其由一个224大小的表,和256(RTE_LPM_TBL8_NUM_GROUPS)个大小为28的表组成。前者叫做tbl24,可使用IP地址的前24位进行索引。后者叫做tbl8,可使用IP地址的后8位进行索引。…...

    2024/4/27 14:37:32
  13. vue 自定义指令

    自定义指令:在vue中,除了可以使用它提供的内置指令,还可以自己定义一些指令 自定义指令用于不可避免要操作dom元素时,把它放在自定义指令中 注册自定义指令: ①包括全局注册和局部注册 全局注册在任何组件中都可以使用 局部注册只能在当前组件中使用 如果在多个组件中同时…...

    2024/4/27 17:19:13
  14. MyBatis学习(4)——缓存机制、MBG-逆向工程

    1. 缓存机制 1.1概述 MyBatis 包含一个强大的查询缓存特性,可以方便地配置和定制。缓存可极大提升查询效率。 MyBatis系统中默认定义了一级缓存和二级缓存,是一个HashMap,能保存查询出的一些数据默认情况下,只有一级缓存(SqlSession/线程 级别的缓存,也称为本地缓存)开启…...

    2024/4/27 20:27:23
  15. Synchronized原理解析(字节码文件)

    Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:普通同步方法,锁是当前实例对象静态同步方法,锁是当前类的class对象同步方法块,锁是括号里面的对象 Moniter为了解决线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程…...

    2024/4/27 20:02:13
  16. Javascript实现桶排序

    Js实现桶排序://桶排序//1、创建桶//2、遍历数据,向对应编号的桶灌水//3、把所有有水的桶的编号取出var arr = [3, 1, 9, 45, 2, 5];document.write("原数组:"+ arr +<br/>);// console.log(arr);function bucketSort(arr) {var buck = []; //创建桶// c…...

    2024/4/27 15:54:41
  17. vue---component2---组件传参2 之 子传父-事件派发emit和自定义事件(父组件向子组件传、子组件向父组件传、非相关组件之间传参)

    一、自定义事件<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>自定义事件</title>&…...

    2024/4/27 15:26:53
  18. 一文搞定 Flink Job 提交全流程

    前言 前面,我们已经分析了 一文搞定 Flink 消费消息的全流程 、写给大忙人看的 Flink Window原理 还有 一文搞定 Flink Checkpoint Barrier 全流程 等等,接下来也该回归到最初始的时候,Flink Job 是如何提交的。 正文 我们知道 Flink 总共有两种提交模式:本地模式和远程模式…...

    2024/4/27 13:56:25
  19. MT-DNN模型阅读笔记

    本文参考博客https://blog.csdn.net/ljp1919/article/details/90269059https://blog.csdn.net/Magical_Bubble/article/details/89517709本文主要记录了阅读论文《Multi-Task Deep Neural Networks for Natural Language Understanding》,MT-DNN是多任务深度神经网络。本文融合…...

    2024/4/27 18:17:40
  20. Spring 注解开发之 @ComponentScan

    这次介绍一下 Spring 中比较重要的一个注解 @ComponentScan。 本文的组织结构如下:先看一下该注解取代了配置文件中的哪些配置; 再总览该注解有哪些属性值; 最后讲解一下重要的属性值。Spring 版本 5.1.2.RELEASE一、XML 配置 @component 注解取代了配置文件中的如下配置: …...

    2024/4/27 16:47:40

最新文章

  1. android layout 的文件夹可以创建子文件夹吗

    android layout 的文件夹可以创建子文件夹吗?因为layout文件夹中的文件一多管理起来就很麻烦&#xff0c;如果可以分类管理起来就会轻松许多。 Android layout的文件夹可以创建子文件夹。在Android Studio中&#xff0c;你可以通过在layout文件夹下直接创建子文件夹的方式来实…...

    2024/4/28 11:22:32
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 磁盘管理与文件管理

    文章目录 一、磁盘结构二、MBR与磁盘分区分区的优势与缺点分区的方式文件系统分区工具挂载与解挂载 一、磁盘结构 1.硬盘结构 硬盘分类&#xff1a; 1.机械硬盘&#xff1a;靠磁头转动找数据 慢 便宜 2.固态硬盘&#xff1a;靠芯片去找数据 快 贵 硬盘的数据结构&#xff1a;…...

    2024/4/23 6:16:19
  4. 策略模式图

    策略模式 小小的图解 主要的三个角色 Strategy—抽象策略角色ConcreateStrategy—具体策略角色Context—上下文角色 封装了对具体策略的调用可以使用set的依赖注入也可以使用构造方法 核心是上下文角色 只要调用上下文角色就行&#xff0c;实现解耦 策略 工厂 将上下文角…...

    2024/4/25 20:06:50
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/28 3:28:32
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/28 1:22:35
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57