深入理解Linux内核之内核抢占

1.开场白

环境:

处理器架构:arm64

内核源码:linux-5.11

ubuntu版本:20.04.1

代码阅读工具:vim+ctags+cscope

我们或许经常听说过内核抢占,可是我们是否真正理解它呢?内核抢占和抢占式内核究竟有什么关系呢?抢占计数器究竟干什么用?... 本文我们就来好好讨论下,关于内核抢占的一些技术细节,力求让大家理解内核抢占。

注:本文主要关注CFS调度类。

2.内核抢占和抢占式内核

我们经常使用uname -a命令能看到“PREEMPT”的字样,没错,我们使用的是抢占式内核

# uname -a
Linux (none) 5.11.0-g08a3831f3ae1 #1 SMP PREEMPT Fri Apr 30 17:41:53 CST 2021 aarch64 GNU/Linux

那什么是抢占式内核呢? 

实际上,支持内核抢占的内核叫做抢占式内核,不支持内核抢占的内核叫做不可抢占式内核

那么问题又来了,什么是内核抢占呢?

我们都知道,拿周期性的tick来说:对于用户任务,当每个时钟中断到来后都会检查它的实际运行时间是否超过理想运行时间,或者运行队列中有没有优先级更高的进程,一般如果满足其中一个条件就会设置重新调度标志,然后在中断返回用户态的前夕发生调度,这是所谓的用户任务抢占。

但是如果处于一个内核态的任务正在运行,这个时候发生中断唤醒了一个高优先级的任务,那么这个被唤醒的任务能否被调度执行呢?

这个时候就会分两种情况分析,如果是抢占式内核那么高优先级任务就有可能抢占当前任务而调度执行(之所有是有可能是因为两者虚拟运行时间差值要大于抢占粒度才允许抢占),如果是不可抢占式内核那么不允许抢占,除非当前进程执行完或者主动发生调度高优先级进程该有机会被调度。也就是说,支持内核抢占的内核不仅允许在用户态的任务可以被抢占,处在内核态的任务也允许被抢占(请注意这里说的是内核态,因为用户空间任务可以通过系统调用等进入内核态),这样对于交互性或者低延迟的应用场景很友好,如手持设备和桌面应用,响应会很快。而对于服务器来说,它就对吞吐量要求较高,希望获得更多的cpu时间,而交互性或者低延迟都是次要的,所以被设计成不可抢占式内核。

下图给出非抢占式内核调度情况:

下图给出抢占式内核调度情况:

对比两个图可以发现:

采用抢占式内核调度的情况下,在中断中唤醒一个高优先级任务能够得到很好的响应。

关于抢占式内核还是不可抢占式内核的选择在源码的

kernel/Kconfig.preempt有所描述:

config PREEMPT_NONEbool "No Forced Preemption (Server)"help¦ This is the traditional Linux preemption model, geared towards¦ throughput. It will still provide good latencies most of the¦ time, but there are no guarantees and occasional longer delays¦ are possible.¦ Select this option if you are building a kernel for a server or¦ scientific/computation system, or if you want to maximize the¦ raw processing power of the kernel, irrespective of scheduling¦ latencies.config PREEMPTbool "Preemptible Kernel (Low-Latency Desktop)"depends on !ARCH_NO_PREEMPTselect PREEMPTIONselect UNINLINE_SPIN_UNLOCK if !ARCH_INLINE_SPIN_UNLOCKselect PREEMPT_DYNAMIC if HAVE_PREEMPT_DYNAMIChelp¦ This option reduces the latency of the kernel by making¦ all kernel code (that is not executing in a critical p)¦ preemptible.  This allows reaction to interactive events by¦ permitting a low priority process to be preempted involuntarily¦ even if it is in kernel mode executing a system call and would¦ otherwise not be about to reach a natural preemption point.¦ This allows applications to run more 'smoothly' even when the¦ system is under load, at the cost of slightly lower throughput¦ and a slight runtime overhead to kernel code.¦ Select this if you are building a kernel for a desktop or¦ embedded system with latency requirements in the milliseconds¦ range.

上面列举了两个编译选项一个是支持内核抢占一个是不支持内核抢占,其实还有PREEMPT_VOLUNTARY和PREEMPT_RT,前者会显式增加一些抢占点,后者用于支持实时性 。

3.重新调度标志和抢占计数器

内核有些路径是不允许调度的,如原子上下文,那么这个时候如果唤醒一个高优先级的任务或者tick的时候检查可重新调度条件满足,那么高优先级的任务将不能马上得到执行,但是我又要标识一下需要重新调度,那么就需要设置重新调度标志,当返回到可调度上下文的时候(如开抢占),这个时候就会检查是否设置了这个标志来决定是否调用调度器来选择下一个任务来运行。

标识重新调度是设置:

//当前任务的task_struct的thread_info的flag
stsk->thread_info->flags设置TIF_NEED_RESCHED标志
#define TIF_NEED_RESCHED        1       /* rescheduling necessary */

内核的某些路径上设置了这个标志之后,将在最近的调度点发生调度(可能是最近开启抢占的时候,也可能是最近中断异常返回的时候)。

当前任务被设置了重新调度标志,只是表明不久的将来会发生调度,并不是马上发生调度,对于用户任务来说就是中断异常返回用户态的前夕发生调度,而对于处于内核态的任务来说,想要在内核态抢占当前进程,仅仅置位重新调度标志还不行,还需要判断当前进程的抢占计数器是否为0。

所有对于处于内核态的任务来说,抢占计数器对于重新调度至关重要,只要抢占计数器不为0,无论被唤醒的任务在紧急都不能获得调度器,我们来看看这个抢占计数器:

tsk->thread_info->preempt.count 

我们来看下对于arm64架构,抢占计数器的定义:

 24 struct thread_info {25         unsigned long           flags;          /* low level flags */29         union {30                 u64             preempt_count;  /* 0 => preemptible, <0 => bug */31                 struct {32 #ifdef CONFIG_CPU_BIG_ENDIAN33                         u32     need_resched;34                         u32     count;35 #else36                         u32     count;37                         u32     need_resched;38 #endif39                 } preempt;40         };45 };

可以发现它是一个共用体,内核某些路径使用preempt_count,有的是preempt,为何会使用这么奇怪的定义呢?因为一个成员可以表示两种状态:重新调度标志和抢占计数器的数值

当需要重新调度的时候会置位flags的TIF_NEED_RESCHED标志,与此同时会将preempt.need_resched清零。当检查thread_info 的preempt_count==0成立时,说明抢占计数器的数值为0且flags的TIF_NEED_RESCHED标志被置位,这个时候可以进程重新调度(如中断返回内核态前夕的检查)。

下面看下如何设置重新调度标志:

resched_curr   //kernel/sched/core.c613         if (cpu == smp_processor_id()) {   614                 set_tsk_need_resched(curr);615                 set_preempt_need_resched();616                 return;                    617         }    29 static inline void set_preempt_need_resched(void)   //arch/arm64/include/asm/preempt.h
30 {
31         current_thread_info()->preempt.need_resched = 0;
32 }

当内核的某个路径设置重新调度标志(如时钟中断tick时),会调用到resched_curr  来设置重新调度标志:可以看到除了设置任务的flags的TIF_NEED_RESCHED标志外,还设置了preempt.need_resched为0。

如何清除重新调度标志:

kernel/sched/core.c
__schedule    //主动调度或抢占式调度 都会调用到这5046         clear_tsk_need_resched(prev); 5047         clear_preempt_need_resched(); //arch/arm64/include/asm/preempt.h
34 static inline void clear_preempt_need_resched(void)      
35 {                
36         current_thread_info()->preempt.need_resched = 1;
37 }

可以看到在主调度器中,除了调用clear_tsk_need_resched来清除任务的flags的TIF_NEED_RESCHED标志外,会调用clear_preempt_need_resched来设置preempt.need_resched为1, 来清除重新调度。

下面为抢占计数器的各个域的表示:

0-7 表示抢占计数 ,8-15表示软中断计数, 16-19表示硬中断计数,20-23表示不可屏蔽中断计数。当进入不同的上下文时会设置响应的位域,表示在某个上下文中,当某个位域被设置,抢占计数器不为0,任务在内核态就不容许被抢占。

所以,抢占计数器有两个作用:一个是标识内核路径在某个原子上下文,一个是用来判断是否允许任务在内核态被抢占。

include/linux/preempt.h85 /*                                                                                    
86  * Macros to retrieve the current execution context:                                  
87  *                                                                                    
88  * in_nmi()             - We're in NMI context                                        
89  * in_hardirq()         - We're in hard IRQ context                                   
90  * in_serving_softirq() - We're in softirq context                                    
91  * in_task()            - We're in task context                                       
92  */                                                                                   
93 #define in_nmi()                (nmi_count())     //判断是否在 不可屏蔽中断上下文                                  
94 #define in_hardirq()            (hardirq_count())     //判断是否在硬中断上下文                                    
95 #define in_serving_softirq()    (softirq_count() & SOFTIRQ_OFFSET)        //判断是否在软中断上下文                
96 #define in_task()               (!(in_nmi() | in_hardirq() | in_serving_softirq()))  //判断是否在进程上下文 
97                                                                                       98 /*                                                                              99  * The following macros are deprecated and should not be used in new code:      
100  * in_irq()       - Obsolete version of in_hardirq()                            
101  * in_softirq()   - We have BH disabled, or are processing softirqs             
102  * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled        
103  */                                                                             
104 #define in_irq()                (hardirq_count())    //判断是否在硬中断上下文                           
105 #define in_softirq()            (softirq_count())    //判断是否在软中断上下文(关闭软中断或者在执行软中断)                              
106 #define in_interrupt()          (irq_count())      //判断是否在中断上下文(包括硬中断 软中断和不可屏蔽中断)                                //判断是否在原子上下文(抢占计数器不为0)
144 #define in_atomic()     (preempt_count() != 0)

4.内核抢占的调度时机

这里调度时机我将它细分为两种情况,一种是不进行调度的check点,一种是真正的抢占点(即是调用主调度器进行调度):

check点->

 tick的时候  :  

满足条件(任务使用完理想运行时间,运行时间大于最小抢占粒度且运行队列有优先级更高的任务) 时,设置TIF_NEED_RESCHED标志,最近的抢占点发生调度 。

唤醒抢占    : 

满足条件(唤醒的任务与当前任务的虚拟运行时间差值大于最小唤醒抢占粒度 ,唤醒的任务虚拟运行时间更小) 时, 设置TIF_NEED_RESCHED标志,最近的抢占点发生调度。

抢占点->

中断返回内核态  :    满足条件(重新调度标志置位且抢占计数器为0) 时, 抢占式调度 。

打开抢占的时候  : (如开抢占,开中断下半部,释放自旋锁) 满足条件(重新调度标志置位且抢占计数器为0)时,  抢占式调度。

开启软中断的时候  : 满足条件(重新调度标志置位且抢占计数器为0)时,  抢占式调度。

中断返回内核态是常规的抢占点,一般情况下即使没有其他中断产生,周期性的tick中断也会发生,  满足条件(重新调度标志置位且抢占计数器为0)时,当前任务就会被抢占。而在一些会发生多任务竟态的临界区中,我们需要关闭内核抢占,有的直接调用preempt_disable, 有的是间接调用preempt_disable(如申请自旋锁的临界区), 有的则是关闭软中断等,这些都会导致抢占计数器不为0,但是在这些临界区中如果中断唤醒了高优先级的任务,中断返回内核态的前夕是不能进行调度的,所以在这些临界区结束的时候会检查调度条件是否满足,如果满足进行抢占式调度,从而使得被唤醒的任务被及时的响应。一般,一些check点设置了当前任务的重新调度标志之后,如果抢占计数器为0,会在最近的抢占点发生调度(就是上面所说的三种情况)。还有需要注意的是:关抢占的临界区中,只是禁止了当前任务所在cpu的内核抢占,其他cpu依然可以进行内核抢占,如果这段临界区有可能被其他cpu访问到,可以直接使用自旋锁来保护。

4.1 check点

1) 时钟中断tick时:

kernel/sched/core.cscheduler_tick
->curr->sched_class->task_tick(rq, curr, 0)->task_tick_fair->entity_tick->check_preempt_tick->4374         if (delta_exec > ideal_runtime) {  //1.当前任务的实际运行时间大于理想运行时间4375                 resched_curr(rq_of(cfs_rq));   //设置重新调度标志4389         if (delta_exec < sysctl_sched_min_granularity) //当前任务的实际运行时间 小于 最小调度粒度吗?4390                 return;4398         if (delta > ideal_runtime)  //2.红黑树最左边的任务的虚拟运行时间和当前任务的虚拟运行时间的差值小于 理想运行时间4399                 resched_curr(rq_of(cfs_rq)); //设置重新调度标志

每个时钟tick到来时,会调用scheduler_tick来检查是否需要重新调度,以下两个条件有一个发生都会设置重新调度标志:

1.当前任务的实际运行时间大于理想运行时间(保证任务在一个调度周期内运行时间不会超过理想运行时间,防止“流氓”任务一直霸占cpu,通过周期性的时钟中断夺回处理器的使用权)。 

2.当前任务的实际运行时间大于最小调度粒度,且红黑树最左边的任务的虚拟运行时间和当前任务的虚拟运行时间的差值小于理想运行时间(红黑树中的高优先级的任务可以抢占当前任务)。

2)唤醒抢占:

在fork和正常的唤醒路径上:

fork路径:

kernel/fork.ckernel_clone
->wake_up_new_task(p)->check_preempt_curr(rq, p, WF_FORK)->rq->curr->sched_class->check_preempt_curr(rq, p, flags)->check_preempt_wakeup    //kernel/sched/fair.c-> 6994         if (wakeup_preempt_entity(se, pse) == 1) {   //唤醒的任务的虚拟运行时间和当前任务的虚拟运行时间差值小于最新唤醒抢占粒度转换的虚拟运行时间       6995                 /*                                                  6996                 ¦* Bias pick_next to pick the sched entity that is  6997                 ¦* triggering this preemption.                      6998                 ¦*/                                                 6999                 if (!next_buddy_marked)                             7000                         set_next_buddy(pse);                        7001                 goto preempt;                                       7002         }                                                           7003                                                                     7004         return;                                                     7005                                                                     7006 preempt:                                                            7007         resched_curr(rq);      //设置重新调度标志                                       

正常唤醒路径:

kernel/sched/core.c
wake_up_process
->try_to_wake_up->ttwu_queue->ttwu_do_activate->ttwu_do_wakeup->check_preempt_curr(rq, p, wake_flags)

无论是创建新任务或者是唤醒任务的时候,都有可能新唤醒的任务抢占当前任务,判断条件如下:唤醒的任务的虚拟运行时间和当前任务的虚拟运行时间差值小于最小唤醒抢占粒度转换的虚拟运行时间(唤醒的任务的虚拟运行时间更小)。

4.2 抢占点

上面介绍的都是check点,只是设置重新调度标志,并没有让抢占的任务运行,真正的抢占点是调用主调度器的时候

1)中断返回内核态

当开启内核抢占的时候,在中断返回内核态的前夕,会检查当前任务是否设置了重新调度标志且抢占计数器为0,如果都满足,进行抢占式调度。

arch/arm64/kernel/entry.Sel1_irq
->  671 #ifdef CONFIG_PREEMPTION                                                                   672         ldr     x24, [tsk, #TSK_TI_PREEMPT]     // get preempt count                       673 alternative_if ARM64_HAS_IRQ_PRIO_MASKING                                                  674         /*                                                                                 675         ¦* DA_F were cleared at start of handling. If anything is set in DAIF,             676         ¦* we come back from an NMI, so skip preemption                                    677         ¦*/                                                                                678         mrs     x0, daif                                                                   679         orr     x24, x24, x0                                                               680 alternative_else_nop_endif                                                                 681         cbnz    x24, 1f                         // preempt count != 0 || NMI return path   682         bl      arm64_preempt_schedule_irq      // irq en/disable is done inside           683 1:                                                                                         684 #endif                                                                                     

当发生中断时,会执行el1_irq来处理中断,

672行  来读取当前任务的thread_info.preempt_count 681行 判断thread_info.preempt_count是否为0,如果为0 则调用682  行的arm64_preempt_schedule_irq  进行抢占式调度(上一节已经分析过)。

下面看下抢占式调度:

arm64_preempt_schedule_irq
->preempt_schedule_irq->__schedule(true)  //调用主调度器进行抢占式调度

2)打开抢占的时候

开启抢占:

preempt_enable
->if (unlikely(preempt_count_dec_and_test())) \   //抢占计数器减一  为0__preempt_schedule(); \               ->preempt_schedule  //kernel/sched/core.c-> __schedule(true)  //调用主调度器进行抢占式调度

释放自旋锁:

spin_unlock
->raw_spin_unlock->__raw_spin_unlock->preempt_enable  //如上

3) 开启软中断

local_bh_enable
->__local_bh_enable_ip->preempt_check_resched->if (should_resched(0)) \     __preempt_schedule();->preempt_schedule-> __schedule(true)  //调用主调度器进行抢占式调度

其实,无论是主动进行调度还是抢占式调度都会调用__schedule,而__schedule是属于关抢占上下文,在调度期间不允许被抢占。

5.不可抢占内核的低延迟处理

下面我们来看下在没有开启内核抢占的内核中如何处理低延迟:

我们会看到在一些比较耗时的处理中如文件系统和内存回收的一些路径会调用cond_resched,它是干什么用呢:

下面是使用这个宏的例子:在内存回收路径中,会从不活跃的lru链表尾部取出一些页面回收隔离到page_list中,最终会调用到shrink_page_list:

mm/vmscan.c
shrink_page_list
->1084         while (!list_empty(page_list)) {...1091                 cond_resched();... //回收处理
}

可以看到对于page_list中的每一个被隔离的候选回收页,在处理之前都会调用到cond_resched来主动判断是否需要重新调度。

下面我们来看下cond_resched这个宏实现:

include/linux/sched.h1868 /*
1869  * cond_resched() and cond_resched_lock(): latency reduction via
1870  * explicit rescheduling in places that are safe. The return
1871  * value indicates whether a reschedule was done in fact.
1872  * cond_resched_lock() will drop the spinlock before scheduling,
1873  */
1874 #ifndef CONFIG_PREEMPTION
1875 extern int _cond_resched(void);
1876 #else
1877 static inline int _cond_resched(void) { return 0; }
1878 #endif
1879 
1880 #define cond_resched() ({                       \
1881         ___might_sleep(__FILE__, __LINE__, 0);  \
1882          _cond_resched();                    \
1883 })

我们可以很清楚的看到,抢占式内核中(CONFIG_PREEMPTION=y)cond_resched宏的_cond_resched为空,并没有主动判断重新调度的功能,只有非抢占式内核才会调用_cond_resched来执行主动检查可抢占性。

下面我们来看下_cond_resched:

6671 #ifndef CONFIG_PREEMPTION
6672 int __sched _cond_resched(void)
6673 {
6674         if (should_resched(0)) {   //判断抢占计数器是否为0
6675                 preempt_schedule_common();  //进行抢占式调度
6676                 return 1;
6677         }
6678         rcu_all_qs();
6679         return 0;
6680 }
6681 EXPORT_SYMBOL(_cond_resched);
6682 #endif

会主动检查抢占计数器是否为0(实际上抢占计数器是否为0且当前任务被设置了重新调度标志),则进行抢占式调度。

实际上,对于非抢占式内核来说,在内核的很多地方,特别是文件系统操作和内存管理相关的一些耗时路径中,都已经被内核开发者识别出来,并使用cond_resched来减小延迟(感兴趣的小伙伴可以通过grep和wc -l命令来查看一下)。

6.自愿内核抢占

内核抢占模型有一种叫做自愿内核抢占模型(CONFIG_PREEMPT_VOLUNTARY=y),可以使得内核开发者在进行耗时操作的时候,主动检查是否需要发生抢占式调度,这个和上一节差不多。

config PREEMPT_VOLUNTARYbool "Voluntary Kernel Preemption (Desktop)"depends on !ARCH_NO_PREEMPThelp¦ This option reduces the latency of the kernel by adding more¦ "explicit preemption points" to the kernel code. These new¦ preemption points have been selected to reduce the maximum¦ latency of rescheduling, providing faster application reactions,¦ at the cost of slightly lower throughput.¦ This allows reaction to interactive events by allowing a¦ low priority process to voluntarily preempt itself even if it¦ is in kernel mode executing a system call. This allows¦ applications to run more 'smoothly' even when the system is¦ under load.¦ Select this if you are building a kernel for a desktop system.

使用might_resched

83 #ifdef CONFIG_PREEMPT_VOLUNTARY
84 extern int _cond_resched(void);
85 # define might_resched() _cond_resched()
86 #else
87 # define might_resched() do { } while (0)
88 #endif

发现只有CONFIG_PREEMPT_VOLUNTARY=y时,might_resched才有效,否则为空。

可以惊奇的发现,当搜索might_resched在内核中使用的使用的时候,并没有看见有任何地方在使用,猜想是因为大多数耗时的内核路径,都已经使用cond_resched来进行检查是否具备调度时机。

7.总结

本文讲解了内核抢占的方方面面,非抢占式内核主要用于服务器等对吞吐量要求较高的场景,而抢占式内核主要用于嵌入式设备和桌面等对响应要求较高的场景。

内核抢占的调度时机主要从check点和抢占点两个角度去分析:

check点是在合适的时机(如时钟中断tick时或者任务唤醒的时候)判断是否需要重新调度任务,如果需要设置重新调度标志(need_resched),并没有马上进行调度,然后在最近的抢占点发生调度;而抢占点是真正调用主调度器发生调度的时机,一般会在中断返回内核态或者重新开启内核抢占等情况下发生。最后,我们又分析了非抢占式内核如何进行低延迟处理已经自愿抢占式内核如何实现自愿式抢占。


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

嵌入式Linux

微信扫描二维码,关注我的公众号

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

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

相关文章

Python+Selenium学习笔记10 - send_keys上传文件

在火狐浏览器上传文件 上传前&#xff0c;同一个HTML文件在火狐和Edge浏览器显示有些不同 这是Firefox浏览器的显示 这是Edge浏览器 上传后 1 # coding utf-82 3 from selenium import webdriver4 import os5 import time6 7 dr webdriver.Firefox()8 file_path "file:…

不错,又有东西可以领!顺带开发个炫彩灯

什么是涂鸦Arduino SDK?Arduino 是全球最流行的开源硬件平台&#xff0c;涂鸦官方推出的 Arduino 开发驱动库&#xff0c;使用任意 Arduino 开发板涂鸦通用模组即可快速实现设备联网&#xff0c;开发属于自己的 IoT 项目。本次实战营适合你吗?没基础 —— Arduino 开发需要掌…

GNS3从入门到精通

GNS3是一款优秀的具有图形化界面的模拟器。可以运行在多平台上&#xff08;Windows&#xff0c;Linux&#xff0c;MacOS等&#xff09;。其最大的特点就是搭建拓扑极其简单&#xff0c;且支持保存startup-config&#xff0c;供下次实验中继续导入使用&#xff0c;而且所有设备导…

Android App优化之ANR详解

引言 背景:Android App优化, 要怎么做?Android App优化之性能分析工具Android App优化之提升你的App启动速度之理论基础Android App优化之提升你的App启动速度之实例挑战Android App优化之Layout怎么摆Android App优化之ANR详解Android App优化之消除卡顿Android App优化之内存…

面试官让你用C语言实现大数相乘,慌吗?

在之前的笔试题解析里面&#xff0c;我写了大数相加的问题&#xff0c;这里再剖析一个大数相乘&#xff0c;顾名思义&#xff0c;大数相乘就是这个数已经大到最大的数据类型都没有办法保存了。我们看看最大的数据类型可以保存多大的数据。#include "stdio.h" #includ…

每周分享之cookie详解

本章从JS方向讲解cookie的使用。&#xff08;实质上后端代码也是差不多用法&#xff0c;无非读取和设置两块&#xff09; 基本用法&#xff1a;document.cookie"usernamepengpeng"; 修改的时候也是这句&#xff0c;重新赋值即可。 一般的&#xff0c;cookie是记域名的…

每天都用,但是你一定不知道麦克风的灵敏度是什么

我们讨论音频器件的时候&#xff0c;就不得不去讨论灵敏度&#xff0c;麦克风、喇叭、蜂鸣器这些器件都会涉及到灵敏度。灵敏度体现的是输出和输入的关系&#xff0c;因为和声音有关&#xff0c;我们必须要知道声压&#xff0c;理解灵敏度、测量灵敏度&#xff0c;并从中选择合…

邓总的vim配置,需要的自己拿走~

我比较喜欢直接用source insight看代码&#xff0c;不过邓总很喜欢用vim&#xff0c;今天特意让他整理了他的vim 配置&#xff0c;喜欢的同学可以自行下载。在公众号后台回复「vim」获取下载链接VIM 配置查看本机 VIM cscope ctagsvim Ubuntu自带&#xff0c;cscope 、ctags…

html嵌套html解决办法(object/object)

后台管理系统多用到了页面嵌套页面的场景&#xff0c;下面是我在工作中解决的方法&#xff0c;利用<object></object>的data属性&#xff0c;下面试w3c的介绍&#xff1a; data 属性用于指定供对象处理的数据文件的 URL。 该属性的值是文件的 URL&#xff0c;该 UR…

原来,我有这样期望

我妈小时候一直跟我们讲他们村里的一个人&#xff0c;我妈说他们家特有钱&#xff0c;他们家的粮仓里装满了花生和大米&#xff0c;而让他们变得这么富有的原因是—勤劳和省。我还上小学那几年&#xff0c;水稻的产量很低「袁隆平的杂交水稻是在后来几年才普及的」&#xff0c;…

详解SMS下OSD2008

百忙有闲来和大家分享一下接着上篇的SMS的一个应用—OSD&#xff0c;长话短说&#xff0c;开始吧&#xff01;老规矩&#xff0c;先看看拓扑图在来构思下要做的步骤&#xff0c;一 、SMS-OSD前的装备工作 二 、安装OSD组件 三 、创建捕获光盘 四 、捕获模板系统镜像 五 、创建、…

一文读懂 | 进程怎么绑定 CPU

昨天在群里有朋友问&#xff1a;把进程绑定到某个 CPU 上运行是怎么实现的。首先&#xff0c;我们先来了解下将进程与 CPU 进行绑定的好处。进程绑定 CPU 的好处&#xff1a;在多核 CPU 结构中&#xff0c;每个核心有各自的L1、L2缓存&#xff0c;而L3缓存是共用的。如果一个进…

Spark天堂之门解密

本课主题 什么是 Spark 的天堂之门Spark 天堂之门到底在那里Spark 天堂之门源码鉴赏引言 Spark 天堂之门就是SparkContext&#xff0c;这篇文章会从 SparkContext 创建3大核心对象 TaskSchedulerImpl、DAGScheduler 和 SchedulerBackend 开始到注册给 Master 这个过程中的源码鉴…

C语言,使用union了解内存

今天一个读者朋友给我发的一段代码&#xff0c;这段代码让他有了疑惑。代码如下&#xff1a;#include "stdio.h" int main() {typedef union{short i;char j[2];}DATA;DATA a;a.j[0] 10;a.j[1] 1;printf("%x\n",a.i);return 0; }他的几个测试代码以及输出…

我做技术的这十年,我不做技术的这一年~

我和明哥认识是因为之前他在群里跟我们分享一件事情&#xff0c;当时因为明哥相信网上认识的一个朋友&#xff0c;说是要一起开发一个项目&#xff0c;结果他被骗了几万块钱。然后聊着聊着&#xff0c;我觉得明哥太实诚了&#xff0c;后面继续接触&#xff0c;知道他做的一些决…

Oracle Golden Gate概要

Oracle GoldenGate简介 Oracle Golden Gate用于源数据库与目标数据库的数据复制备份&#xff1b;可以在异构的环境(各种操作系统和数据库)之间实现数据亚秒级的实时复制备份&#xff1b;以及可以在实时数据仓库、数据同步、集中/分发、容灾、数 据库升级和迁移等多个场景下应用…

android 音频加载hal so调试

1. 整个加载流程图 2. 加载hal so的代码位置 2.1 在audiopolicymanager中的加载位置 diff --git a/frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp index 632290a9…

鸿蒙的路还很长

这是昨晚看到我的老领导发的和鸿蒙有关的文章&#xff0c;我在下面评论了&#xff0c;作为科技自媒体屌丝本屌&#xff0c;我昨晚也是看了鸿蒙2.0的发布会&#xff0c;也有一些观点。鸿蒙OS是什么&#xff1f;鸿蒙os是一个操作系统&#xff0c;而且是面向智能终端的&#xff0c…

有关Accordion组件的研究——Silverlight学习笔记[27]

Accordion组件在开发中常用于信息的分类显示。本文将为大家介绍该组件的特性以及通过一个实例讲述该组件的基本运用。组件所在命名控件&#xff1a;System.Windows.Controls组件常用方法&#xff1a;SelectAll&#xff1a;选择所有位于Accordion组件中的Accordion项。&#xff…

螺旋格式输出数据

螺旋格式输出数据 问题&#xff1a;(问答题) 编程输出以下格式的数据。 When i0 1 When i1 7 8 96 1 25 4 3 When i2 21 22 23 24 2520 7 8 9 1019 6 1 2 1118 5 4 3 1217 16 15 14 13 1、Python&#xff1a; def format_…