赞
踩
1.#define notrace _attribute_((no_instrument_function))
notrace如同GCC的-finstrument-functions() 参数的作用是在程序中加入hook,让它在每次进入和退出函数的时候分别调用这个函数
2.SMI:system manager interrupt interrupt. NMI:non-maskable interrupt
3.RTSJ: 实时规范(Real Time Specification For Java)
4.TCK:
5.APIC: 高级可编程序中断控制器(Advanced Programmable Interrupt Controller)
6.dump_stack():当内核出现比较严重的错误时,例如发生Oops错误或者内核认为系统运行状态异常,内核就会打印出当前进程的栈回溯信息,其中包含当前执行代码的位置以及相邻的指令、产生错误的原因、关键寄存器的值以及函数调用关系等信息,这些信息对于调试内核错误非常有用。
打印函数调用关系的函数就是dump_stack(),该函数不仅可以用在系统出问题的时候,我们在调试内核的时候,可以通过dump_stack()函数的打印信息更方便的了解内核代码执行流程。
7.barrier:要理解barrier,你首先需要理解文件系统日志功能。常用的文件系统使用日志功能来保证文件系统的完整性。该功能背后的思路很简单:在写入新的数据块到磁盘之前,会先将元数据写入日志。预先将元数据写入日志可以保证在写入真实数据前后一旦发生错误,日志功能能很容易地回滚到更改之前的状态。这个方法确保了不会发生文件系统崩溃的情况。
单独使用日志功能不能保证没有任何差错。现在的磁盘大都有大容量的缓存,数据不会立即写入到磁盘中,而是先写入到磁盘缓存中。到这一步,磁盘控制器就能更加高效地将其复制到磁盘中。这对性能来说是有好处的,但是对日志功能来说则相反。为了保证日志百分之百可靠,它必须绝对保证元数据在真实数据写入之前被预先写入。这就是我们要介绍文件系统barrier的原因。
我们很容易理解使用barrier的根本原因:barrier本身禁止在barrier之后写入数据,真实的数据块将在barrier被写入之前完全写入磁盘。使用barrier可以确保文件系统的完整性,因为barrier功能在EXT4文件系统中是默认启用的(除非你的操作系统更改了这个默认设置)。
8.PI:Product improve 优先级继承
9.cacheline:高速缓存块
10.JBD:Journaling Block Device–日志块设备层
11.BH:buff_head—不但用来映射页面里面的一个块,而且作为文件系统与块设备进行I/O交互的最小单位。
12.jiffies:全局变量jiffies用来记录自系统启动以来产生的节拍的总数。
13.HPET(High Precision Event Timer):俗称高精度定时器(到时了产生中断),最低时钟频率为10MHZ,而且定义了比较严格的精确度(间隔>=1毫秒的允许+-0.05%的误差,间隔<=100微妙的允许+-0.2%的误差)。
14.ACPI:表示高级配置和电源管理接口(Advanced Configuration and Power Management Interface)
15.cpuidle:在Linux kernel中,这种CPU的无所事事的状态,被称作idle状态,而cpuidle framework,就是为了管理这种状态。我们知道,Linux系统运行的基础是进程调度,而所有进程都不再运行时,称作cpu idle。但是,怎么判断这种状态呢?kernel采用了一个比较简单的方法:在init进程(系统的第一个进程)完成初始化任务之后,将其转变为idle进程,由于该进程的优先级是最低的,所以当idle进程被调度到时,则说明系统的其它进程不再运行了,也即CPU idle了。最终,由idle进程调用idle指令(这里为WFI),让CPU进入idle状态。“ARM WFI和WFE指令”中介绍过,WFI Wakeup events会把CPU从WFI状态唤醒,通常情况下,这些events是一些中断事件,因此CPU唤醒后会执行中断handler,在handler中会wakeup某些进程,在handler返回的时候进行调度,当没有其他进程需要调度执行的时候,调度器会恢复idle进程的执行,当然,idle进程不做什么,继续进入idle状态,等待下一次的wakeup。
16.Runtime PM机制:动态电源管理–每个设备(包括CPU)都处理好自身的电源管理工作,尽量以最低的能耗完成交代的任务,尽量在不需要工作的时候进入低功耗状态,尽量不和其它模块有过多耦合。
17.suspend机制:suspend(standby)模式的时候,各平台通用的电源管理代码会先把除了引导cpu以外的其他cpu都关掉,然后引导cpu自己再进入睡眠。当睡眠结束的时候,引导cpu会从进入睡眠的地方继续执行,而其他cpu则要重新启动。
18.NO_HZ:HZ, 它的值应该被设定为多少呢? 频率低了的话, 精度不够, 影响整个系统的实时性; 频率高虽然能提高精度和实时性, 但又会造成系统负担过重, 大量的时间被用来处理时钟中断服务程序. 而且由于使用场景的不同, HZ并没有一个通用的理想值, 内核的默认值也曾多次改变.
为了兼顾实时性和能耗, 内核从2.6.17开始引入了tickless的特性, 也被称为NO_HZ. 发展到现在(3.10之后, 目前3.12), 内核为此功能提供了三个选项:
CONFIG_HZ_PERIODIC, 每个节拍都不会被忽略.
CONFIG_NO_HZ_IDLE, 默认设置, 节拍在空闲的CPU上会被忽略.
CONFIG_NO_HZ_FULL, 节拍在空闲的或者只有一个可执行任务的CPU上会被忽略.
之所以默认启用是因为tickless有着立竿见影的好处, 省电和降低负载, 尤其是在移动, 虚拟化和高性能运算等环境下.
19.max_c-state: Limit the processor to a maximum C-state—CPU电源状态. ACPI定义系统处理器的电源状态,要么为活跃状态(正在执行),要么为睡眠状态(未执行) ACPI定义这样的逻辑在每个CPU的偏置上,即OS-PM通过转换来切换不同的处理器电源状态。
20.EXPORT_SYMBOL:标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
这里要和System.map做一下对比:
System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。
EXPORT_SYMBOL 的符号, 是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。而模块在加载过程中,其本质就是能动态连接到内核,如果在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接。
使用方法
第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名)
第二、在掉用该函数的模块中使用extern对之声明
第三、首先加载定义该函数的模块,再加载调用该函数的模块
21.dynticks:可以睡眠的rcu锁很大程度上有益于系统的实时性,因为不用禁用抢占了,该rcu锁巧妙的使用两个“阶段”来跟踪rcu的状态,内部维持着一个状态机,该状态机的其中两个状态需要所有cpu的确认,也就是说只有所有cpu都确认了之后,状态机才能向前推进,这就暴露出一个缺点,就是在no_hz启用的情况下,如果一个cpu不再接收时钟中断,那么它就没有机会执行确认,结果就是所有的和rcu相关的cpu需要等待很长一段时间才能推进rcu状态机,一直等到这个停掉时钟心跳的cpu重新开始心跳,怎么解决这个问题呢?内核中引入了一个叫做dynticks的机制,该机制很简单,就是当一个cpu处于心跳停止状态的时候可以直接跳过它,不需要它的确认,毕竟它上面是不可能操作read-rcu锁的.
22.CFS(完全公平调度算法): cfs定义了一种新的模型,它给cfs_rq(cfs的run queue)中的每一个进程安排一个虚拟时钟,vruntime。如果一个进程得以执行,随着时间的增长(也就是一个个tick的到来),其vruntime将不断增大。没有得到执行的进程vruntime不变。而调度器总是选择vruntime跑得最慢的那个进程来执行。这就是所谓的“完全公平”。为了区别不同优先级的进程,优先级高的进程vruntime增长得慢,以至于它可能得到更多的运行机会,CFS调度没有时间片的概念。在2.6.23中取代了RSDL/SD调度算法
23.TSC:在 Linux 内核中主要有两种类型的定时器。一类称为 timeout 类型,另一类称为 timer 类型。timeout 类型的定时器通常用于检测各种错误条件,例如用于检测网卡收发数据包是否会超时的定时器,IO 设备的读写是否会超时的定时器等等。通常情况下这些错误很少发生,因此,使用 timeout 类型的定时器一般在超时之前就会被移除,从而很少产生真正的函数调用和系统开销。总的来说,使用 timeout 类型的定时器产生的系统开销很小,它是下文提及的 timer wheel 通常使用的环境。此外,在使用 timeout 类型定时器的地方往往并不关心超时处理,因此超时精确与否,早 0.01 秒或者晚 0.01 秒并不十分重要,这在下文论述 deferrable timers 时会进一步介绍。timer 类型的定时器与 timeout 类型的定时器正相反,使用 timer 类型的定时器往往要求在精确的时钟条件下完成特定的事件,通常是周期性的并且依赖超时机制进行处理。例如设备驱动通常会定时读写设备来进行数据交互。如何 高效的管理 timer 类型的定时器对提高系统的处理效率十分重要,下文在介绍 hrtimer 时会有更加详细的论述。
内核需要进行时钟管理,离不开底层的硬件支持。在早期是通过 8253 芯片提供的 PIT(Programmable Interval Timer)来提供时钟,但是 PIT 的频率很低,只能提供最高 1ms 的时钟精度,由于 PIT 触发的中断速度太慢,会导致很大的时延,对于像音视频这类对时间精度要求更高的应用并不足够,会极大的影响用户体验。随着硬件平台的不断发展变化,陆续出 现了 TSC(Time Stamp Counter),HPET(High Precision Event Timer),ACPI PM Timer(ACPI Power Management Timer),CPU Local APIC Timer 等精度更高的时钟。这些时钟陆续被 linux 的时钟子系统所采纳,从而不断的提高 Linux 时钟子系统的性能和灵活性。这些不同的时钟会在下文不同的章节中分别进行介绍。在 Linux 2.6.16 之前,内核一直使用一种称为 timer wheel 的机制来管理时钟。这就是熟知的 kernel 一直采用的基于 HZ 的 timer 机制。
24.NICE:表示进程可被执行的优先级的修正数值。如前面所说,PRI值越小越快被执行,那么加入nice值后,将会使得PRI(进程优先级)变为:PRI(new)=PRI(old)+nice。这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。
到目前为止,更需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
25.futex:快速用户区互斥的简称,是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。
26.cpuidle:CPU Idle相关的软件架构可以分以下几种:
CPUIDLE core:CPUIdle的内核驱动,在kernel\drivers\cpuidle目录下。
CPUIDLE sysfs:这部分向用户层提供CPUIDLE的相关节点,以提供信息给用户。
在/sys/devices/system/cpu/cpuidle下可以查到当前的CPUIdle的驱动的名字和governor的种类。
在/sys/devices/system/cpu/cpu0/cpuidle下可以看到几种CPUIDLE状态,在相应的目录下有lantency,名字等信息。
CPUIdle govenor:根据latency,residency等,根据某种算法选择一个合适的CPUIDLE状态。
比如cpuidle governor里边的menu algorithm会根据之前的idle time的统计结果来预测接下来的idle time,并从中选择合适的idle state。
CPUIdle状态 以S5PC210为例,它有以下几种CPUIdle状态
NORMAL : IDLE (Wait For Interrupt)
AFTR : ARM Off and TOP Running
LPA : Low Power Audio
DEEP-STOP
SLEEP
27.menu governor:在当前的kernel中,有2个governor,分别为ladder和menu,ladder在periodic timer tick system(周期性定时器系统)中使用,menu在tickless system(定时器中断不会按照固定的周期产生中断,根据需要)中使用。以menu governor为例,进一步理解cpuidle framework中governor的概念,并学习governor的实现方法。现在主流的系统,出于电源管理的考量,大多都是tickless system。另外,menu governor会利用pm qos framework,在选择策略中加入延迟容忍度(Latency tolerance)的考量
1)切换的代价
进入C state的目的,是节省功耗,但CPU在C state和normal state之间切换,是要付出功耗上面的代价的。这最终会体现在idle state的target_residency字段上。
idle driver在注册idle state时,要非常明确state切换的代价,基于该代价,CPU必须在idle state中停留超过一定的时间(target_residency)才是划算的。
因此governor在选择C state时,需要预测出CPU将要在C state中的停留时间,并和备选idle state的target_residency字段比较,选取满足“停留时间 > target_residency”的state。
2)系统的延迟容忍程度
备选的的C state中,功耗和退出延迟是一对不可调和的矛盾,电源管理的目标,是在保证延迟在系统可接受的范围内的情况下,尽可能的节省功耗。
idle driver在注册idle state时,会提供两个信息:CPU在某个state下的功耗(power_usage)和退出该state的延迟(exit_latency)。那么如果知道系统当前所能容忍的延迟(简称latency_req),就可以在所有exit_latency小于latency_req的state中,选取功耗最小的那个。
因此,governor算法就转换为获取系统当前的latency_req,而这正是pm qos的特长。
28.ladder governor:governor的主要职责,是根据系统的运行情况,选择一个合适idle state(在kernel的标准术语中,也称作C state)。一个移动CPU有多个空闲状态可以进入来节省电源。A mobile CPU has multiple idle states it can go into, to save power when not doing anything. The states that save more power generally take more time to get in and out of. These two source files describe two different methods for picking the best state to enter.
If a too-deep state is entered immediately, then it will be awoken too soon to justify the higher transition cost. Conversely, if a too-shallow state is selected, then potential power savings will be missed.
The ladder governor enters the lightest state first, and will only move on to the next deeper state if a sleep was long enough, as defined by some measure. It would first go to sleep in state 1, then 2, then 3, and so on, until either the deepest available state is reached, or the CPU is restarted after too short a time. In this case, the governor will pick state 1 next time.
The menu governor does not necessarily follow this progression, but can jump into a deeper state immediately, if it determines that this is likely to be worthwhile.
29.per-CPU:2.6内核上一个新的特性就是per-CPU变量。顾名思义,就是每个处理器上有此变量的一个副本。per-CPU的最大优点就是,对它的访问几乎不需要锁,因为每个CPU都在自己的副本上工作。tasklet、timer_list等机制都使用了per-CPU技术。
30. oneshot timer和periodic timer:实现细粒度定时器的另一种方法就是使定时器运行在单触发模式(one-shot mode)。这与周期模式(periodic mode)运行 的定时器不同,周期模式运行的定时器,只要对它进行一次初始化操作,以后定时器就会周期的产生中断,不再需要额外的对定时器进行编程操作;而当定时器运行 于单触发模式下时,每当定时器产生一次中断后就不再运行,系统再根据当前任务对时间的要求计算出定时器下一次应该产生中断的时间间隔,然后再对定时器进行 编程,使它能在系统要求的将来某一时刻产生中断。在单触发模式下,定时器的定时精度能达到微秒级。不过需要注意的是,由于每次中断后都要计算下一次中断的 时间,而且还要对定时器进行编程,这两个操作会降低系统的性能。
one-shot timer不仅仅有local apic可以提供,更重要的是由HPET提供,而HPET(High Precision Event Timer)则是实时系统用的计时机制。
31.Linux 死锁检测模块 Lockdep:死锁是指多个进程(线程)因为长久等待已被其他进程占有的的资源而陷入阻塞的一种状态。当等待的资源一直得不到释放,死锁会一直持续下去。死锁一旦发生,程序本身是解决不了的,只能依靠外部力量使得程序恢复运行,例如重启,开门狗复位等。
Linux 提供了检测死锁的机制,主要分为 D 状态死锁和 R 状态死锁。
D 状态死锁进程等待 I/O 资源无法得到满足,长时间(系统默认配置 120 秒)处于 TASK_UNINTERRUPTIBLE 睡眠状态,这种状态下进程不响应异步信号(包括 kill -9)。如:进程与外设硬件的交互(如 read),通常使用这种状态来保证进程与设备的交互过程不被打断,否则设备可能处于不可控的状态。对于这种死锁的检测 Linux 提供的是 hung task 机制,MTK 也提供 hang detect 机制来检测 Android 系统 hang 机问题。触发该问题成因比较复杂多样,可能因为 synchronized_irq、mutex lock、内存不足等。D 状态死锁只是局部多进程间互锁,一般来说只是 hang 机、冻屏,机器某些功能没法使用,但不会导致没喂狗,而被狗咬死。
R 状态死锁进程长时间(系统默认配置 60 秒)处于 TASK_RUNNING 状态垄断 CPU 而不发生切换,一般情况下是进程关抢占或关中断后长时候执行任务、死循环,此时往往会导致多 CPU 间互锁,整个系统无法正常调度,导致喂狗线程无法执行,无法喂狗而最终看门狗复位的重启。该问题多为原子操作,spinlock 等 CPU 间并发操作处理不当造成。本文所介绍的 Lockdep 死锁检测工具检测的死锁类型就是 R 状态死锁。
常见错误
AA: 重复上锁
ABBA: 曾经使用 AB 顺序上锁,又使用 BA 上锁
ABBCCA: 这种类型是 ABBA 的扩展。AB 顺序 , AB 顺序,CA 顺序。这种锁人工很难发现。
32.Linux 时间管理:
1.1 用于时间管理的对象
时钟源设备(clock-source device)
系统中可以提供一定精度的计时设备都可以作为时钟源设备。如x86构架里的TSC、HPET、ACPI PM-Timer、PIT等。但是不同的时钟源提供的时钟精度是不一样的。像TSC、HPET等时钟源既支持高精度模式(high-resolution mode)也支持低精度模式(low-resolution mode),而PIT只能支持低精度模式。此外,时钟源的计时都是单调递增的(monotonically),如果时钟源的计时出现翻转(即返回到0值),很容易造成计时错误,内核的一个patch(commit id: ff69f2)就是处理这类问题的一个很好示例。时钟源作为系统时钟的提供者,在可靠并且可用的前提下精度越高越好。在 Linux 中不同的时钟源有不同的rating,有更高 rating的时钟源会优先被系统使用,如图 2 所示。
时钟事件设备(clock-event device)
系统中可以触发one-shot(单次)或者周期性中断的设备都可以作为时钟事件设备。如 HPET、CPU Local APIC Timer等。HPET比较特别,它既可以做时钟源设备也可以做时钟事件设备。时钟事件设备的类型分为全局和per-CPU两种类型。全局的时钟事件设备虽然附属于某一个特定的CPU上,但是完成的是系统相关的工作,例如完成系统的tick更新;per-CPU 的时钟事件设备主要完成 Local CPU 上的一些功能,例如对在当前 CPU 上运行进程的时间统计,profile,设置 Local CPU 上的下一次事件中断等。和时钟源设备的实现类似,时钟事件设备也通过 rating 来区分优先级关系。
tick device
Tick device提供时钟事件的连续流,各个事件定期触发。Tick device其实是时钟事件设备的一个 wrapper,因此tick device也有one-shot和周期性这两种中断触发模式。每注册一个时钟事件设备,这个设备会自动被注册为一个tick device。全局的tick device用来更新诸如jiffies这样的全局信息,per-CPU的tick device则用来更新每个CPU相关的特定信息。
Broadcast
Broadcast的出现是为了应对这样一种情况:假定CPU使用Local APIC Timer作为 per-CPU的tick device,但是某些特定的CPU如 Intel的Westmere之前的CPU)在进入C3+ 的状态时Local APIC Timer也会同时停止工作,进入睡眠状态。在这种情形下broadcast可以替代Local APIC Timer继续完成统计进程的执行时间等有关操作。本质上broadcast是发送一个IPI(Inter-processor interrupt)中断给其他所有的CPU,当目标CPU收到这个IPI中断后就会调用原先Local APIC Timer正常工作时的中断处理函数,从而实现了同样的功能。目前主要在 x86 以及 MIPS 下会用到 broadcast 功能(补充:在ARM Cortex-A9上也可以使用)。
1.2 Timekeeping & GTOD (Generic Time-of-Day)
Timekeeping(可以理解为时间测量或者计时)是内核时间管理的一个核心组成部分。没有 Timekeeping,就无法更新系统时间,维持系统“心跳”。 GTOD 是一个通用的框架,用来实现诸如设置系统时间do_gettimeofday或者修改系统时间do_settimeofday等工作。这些功能的实现都依赖于系统的clocksource设备。
为了实现以上功能,linux 实现了多种与时间相关但用于不同目的的数据结构。
Ø struct timespec
struct timespec {
__kernel_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
timespec 精度是纳秒。它用来保存从00:00:00 GMT, 1 January 1970开始经过的时间。内核使用全局变量xtime来记录这一信息,这就是通常所说的“Wall Time”或者“Real Time”。与此对应的是“System Time”。System Time是一个单调递增的时间,每次系统启动时从0开始计时。
Ø struct timeval
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
timeval精度是微秒。timeval主要用来指定一段时间间隔。
Ø ktime
typedef union {
s64 tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
struct {
# ifdef __BIG_ENDIAN
s32 sec, nsec;
# else
s32 nsec, sec;
# endif
} tv;
#endif
}ktime_t;
ktime_t是hrtimer主要使用的时间结构。无论使用哪种体系结构,ktime_t始终保持64bit的精度,并且考虑了大小端的影响。
Ø cycle_t;
typedef u64 cycle_t;
cycle_t 是从时钟源设备中读取的时钟类型。
为了管理这些不同的时间结构,Linux 实现了一系列辅助函数来完成相互间的转换。
ktime_to_timespec,ktime_to_timeval,ktime_to_ns/ktime_to_us,反过来有诸如 ns_to_ktime 等类似的函数。
timeval_to_ns,timespec_to_ns,反过来有诸如 ns_to_timeval 等类似的函数。
timeval_to_jiffies,timespec_to_jiffies,msecs_to_jiffies, usecs_to_jiffies, clock_t_to_jiffies 反过来有诸如 ns_to_timeval 等类似的函数。
clocksource_cyc2ns / cyclecounter_cyc2ns
时钟源设备和时钟事件设备的引入,将原本放在各个体系结构中重复实现的冗余代码封装到各自的抽象层中,这样做不但消除了原来timer wheel与内核其他模块的紧耦合性,更重要的是系统可以在运行状态动态更换时钟源设备和时钟事件设备而不影响系统正常使用,譬如当 CPU 由于睡眠要关闭当前使用的时钟源设备或者时钟事件设备时系统可以平滑的切换到其他仍处于工作状态的设备上。Timekeeping/GTOD在使用时钟源设备的基础上也采用类似的封装实现了体系结构的无关性和通用性。hrtimer则可以通过timekeeping提供的接口完成定时器的更新,通过时钟事件设备提供的事件机制,完成对timer的管理。在图2 中还有一个重要的模块就是tick device的抽象,尤其是dynamic tick。Dynamic tick的出现是为了能在系统空闲时通过停止tick的运行以达到降低CPU功耗的目的。使用dynamic tick的系统,只有在有实际工作时才会产生tick,否则tick是处于停止状态。
33. lockstat:
34. proc 文件系统:Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。下面列出的这些文件或子文件夹,并不是都是在你的系统中存在,这取决于你的内核配置和装载的模块。另外,在/proc下还有三个很重要的目录:net,scsi和sys。 Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则scsi 目录不存在。
35. soft lockup和hard lockup:在Linux kernel里,有一个debug选项LOCKUP_DETECTOR。
使能它可以打开kernel中的soft lockup和hard lockup探测。
这两个东西到底有什么用处那?
首先,soft/hard lockup的实现在kernel/watchdog.c中,主体涉及到了3个东西:kernel线程,时钟中断,NMI中断(不可屏蔽中断)。
这3个东西具有不一样的优先级,依次是kernel线程 < 时钟中断 < NMI中断。
而正是用到了他们之间优先级的区别,所以才可以调试系统运行中的两种问题:
1. 抢占被长时间关闭而导致进程无法调度(soft lockup)
2. 中断被长时间关闭而导致更严重的问题(hard lockup)
36. dump_stack():在内核中代码调用过程难以跟踪,上下文关系复杂,确实让人头痛调用dump_stack()就会打印当前cpu的堆栈的调用函数了。如此,一目了然的就能看到当前上下文环境,调用关系了
37.CFS(完全公平调度算法):cfs定义了一种新的模型,它给cfs_rq(cfs的run queue)中的每一个进程安排一个虚拟时钟,vruntime。如果一个进程得以执行,随着时间的增长(也就是一个个tick的到来),其vruntime将不断增大。没有得到执行的进程vruntime不变。而调度器总是选择vruntime跑得最慢的那个进程来执行。这就是所谓的“完全公平”。为了区别不同优先级的进程,优先级高的进程vruntime增长得慢,以至于它可能得到更多的运行机会。
CFS的思想就是让每个调度实体(没有组调度的情形下就是进程,以后就说进程了)的vruntime互相追赶,而每个调度实体的vruntime增加速度不同,权重越大的增加的越慢,这样就能获得更多的cpu执行时间。
再补充一下权重的来源,权重跟进程nice值之间有一一对应的关系,可以通过全局数组prio_to_weight来转换,nice值越大,权重越低。
第一个是调度实体sched_entity,它代表一个调度单位,在组调度关闭的时候可以把他等同为进程。每一个task_struct中都有一个sched_ entity,进程的vruntime和权重都保存在这个结构中。那么所有的sched_entity怎么组织在一起呢?红黑树。所有的sched_entity以vruntime为key(实际上是以vruntime-min_vruntime为key,是为了防止溢出,反正结果是一样的)插入到红黑树中,同时缓存树的最左侧节点,也就是vruntime最小的节点,这样可以迅速选中vruntime最小的进程。
注意只有等待CPU的就绪态进程在这棵树上,睡眠进程和正在运行的进程都不在树上。
38.vdso与vsyscall:vdso的全称是虚拟动态共享库(virtual dynamic shared library),而vsyscall的全称是虚拟系统调用(virtual system call)先来看vdso与vsyscall的出现原因:由于进行系统调用时,操作系统要由用户态切换到内核态,而这一操作是非常浪费时间的操作,无论采用早期的int 0x80/iret中断,还是sysenter/sysexit指令,再到syscall/sysexit指令。另一方面,某些系统调用并不会向内核提交参数,而仅仅只是从内核里请求读取某个数据,例如gettimeofday(),内核在处理这部分系统调用时可以把系统当前时间写在一个固定的位置,而应用程序直接从该位置简单读取即可,无需发起系统调用。内核与用户态程序之间进行数据交互的方法就是mmap。但由于vsyscall采用固定地址映射的方式,所以存在一定的安全隐患,这一方式便被vdso所改进,vdso的随机映射在一定程度上缓解了安全威胁。虽然有了vdso,但从历史兼容性上来讲,vsyscall不能就此完全抛弃,否则将导致一些陈旧的(特别是静态连接的)应用程序无法执行,因此现在在我的3.19内核上,将同时看到vdso和vsyscal。
39.基数树(radix tree):是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。IDR机制的API函数在lib/idr.c中实现,这里不加分析。
linux radix树最广泛的用途是用于内存管理,结构address_space通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在lib/radix-tree.c中实现。
40.:一些内核调用可以用来方便标记bug,提供断言并输出信息。最常用的两个是BUG()和BUG_ON()。当被调用的时候,它们会引发oops,导致栈的回溯和错误信息的打印。为什么这些声明会导致 oops跟硬件的体系结构是相关的。大部分体系结构把BUG()和BUG_ON()定义成某种非法操作,这样自然会产生需要的oops。你可以把这些调用当作断言使用,想要断言某种情况不该发生 :
if (bad_thing)
BUG();
或者使用更好的形式:
BUG_ON(bad_thing);
而WARN_ ON 则是调用dump_stack,打印堆栈信息,不会OOPS
41.APIC–高级中断控制器:APIC虽号称现代,但也出现10几年了,PC机市场总是很晚才能接触到新的技术,前面说了,我的T42用的还是PIC呢。APIC相较于PIC来说,最大的优点是能适用于MP平台,当然,管脚多是它另一个优点。APIC由两部分组成,一个称为LAPIC(Local APIC,本地高级中断控制器),一个称为IOAPIC(I/O APCI,I/O高级中断控制器)。前者位于CPU中,在MP平台,每个CPU都有一个自己的LAPIC。后者通常位于南桥上,像PIC一样,连接各个产生中断的设备。在一个典型的具有多个处理器的PC平台,通常有一个IOAPIC和多个LAPIC,它们相互配合,形成一个中断的分发网络。
42._ASSEMBLY_ :某些常量宏会同时出现被c和asm引用,而c与asm在对立即数符号的处理上是不同的。asm中通过指令来区分其操作数是有符号还是无符号,而不是通过操作数。而c中是通过变量的属性,而不是通过操作符。c中如要指名常量有无符号,必须为常量添加后缀,而asm则通过使用不同的指令来指明。如此,当一个常量被c和asm同时包含时,必须作不同的处理。故AFLAGS中将添加一项D__ASSEMBLY__,来告知预处理器此时是asm。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。