sparc架构代码分析-NMI看门狗分析
性能监测功能能够统计软件运行过程中发生的各种事件,比如执行分支指令的次数,发生tlb miss的次数等,从而给软件的性能给出评判,它通常是被perf,oprofile等性能诊断工具所使用。
NMI看门狗能够周期性的产生非屏蔽中断并监听系统中是否CPU被锁住,如果有CPU被锁住,就打印出调试信息,这样即使系统被锁死,也能通过NMI 看门狗的调试信息诊断问题所在。
从软件意义上来看,性能监测和NMI看门狗是两个相互独立的模块,他们实现不同的功能,但是在T2 CPU并没有实现NMI 看门狗的硬件电路,即硬件上T2 CPU并没有支持NMI 看门狗,Linux是使用T2 CPU性能监测模块的溢出中断来实现NMI看门狗的,因此在T2 linux中,两者基于相同的硬件模块,所以这里把它们放在一起来讲。
T2 CPU的性能监听硬件介绍:
性能监测模块有两类寄存器,一类是控制寄存器PCR,一类是统计具体事件发生次数的计数寄存器PIC,T2的每个虚拟处理器上都有一个64位的PCR,两个32位PIC,分别称作PIC.h和PIC.l。
我们先来看PCR,它的各个域如下表所示:
位 | 域 | 值 | 描述 |
---|---|---|---|
63 | hold_ov1 | 0 | 如果写入PCR的数据中该域为0,则该数据中写入ov1域的数据有效,否则,该次写入操作对ov1域无效 |
62 | hold_ov0 | 0 | 如果写入PCR的数据中该域为0,则该数据中写入ov0域的数据有效,否则,该次写入操作对ov0域无效 |
61:32 | — | 0 | Reserved |
31 | ov1 | 0 | PIC.h溢出状态位,当PIC.h计数器溢出时,该位置1,直到软件清除该状态位。 |
30:27 | sl1 | 0 | 从PIC.h能够统计的16类事件中选择一类,如下表2所示 |
26:19 | mask1 | 0 | 控制sl1选择的那一类事件中统计其中哪一个或几个事件,如下表2所示 |
18 | ov0 | 0 | PIC.l溢出状态位,当PIC.l计数器溢出时,该位置1,直到软件清除该状态位。 |
17:14 | sl0 | 0 | 从PIC.l能够统计的16类事件中选择一类,如下表2所示 |
13:6 | mask0 | 0 | 控制sl0选择的那一类事件中统计其中哪一个或几个事件,如下表2所示 |
5:4 | toe | 0 | PIC溢出中断允许位,这个域有两位,分别控制PIC.h和PIC.l溢出中断 |
3 | ht | 0 | 如果该位为1,则会统计在hypervisor模式下执行时事件发生的次数 |
2 | ut | 0 | 如果该位为1,则会统计在noprivilged模式下执行时事件发生的次数 |
1 | st | 0 | 如果改位为1,则会统计在privilged模式下执行时事件发生的次数 |
0 | priv | 0 | 如果该位为1,则nonpriviled模式下的代码不能访问PIC寄存器 |
当PIC 溢出中断发生时,中断处理函数可以从ov1和ov0中的溢出中断状态来判断是PIC.h还是PIC.l发生了溢出。
需要指出的是PIC溢出中断其实就是interrupt_level_15 trap。
下表中是对PCR中sl域(sl1,sl0)和mask域(mask1,mask0)的解释,它们控制 PIC具体统计那些事件:
sl域 | mask域 | 事件 | 描述 |
---|---|---|---|
0 | — | All strands idle | 当一个物理核的所有8个虚拟处理器都处于空闲状态时,统计这个空闲状态经过的时钟节拍数 |
1 | — | — | Reserved |
2 | 0x01 | 完成的分支 | |
0x02 | 发生的分支 | ||
0x04 | FGU算数指令 | 包括FADD,FSUB,FCMP等指令 | |
0x08 | load指令 | ||
0x10 | store指令 | ||
0x20 | Sethi | 软件计数指令 | |
0x40 | 其他指令 | ||
0x80 | 原子指令 | 如LDSTUB/A, CASA/XA, SWAP/A等指令 | |
0x03到0xFF的其他值 | 同时统计上面的两个或多个事件 | 如0x3则同时统计完成的分支和发生的分支 | |
3 | 0x01 | Icache misses | 只统计一级cache |
0x02 | Dcache misses | 同时统计一级cache和二级cache | |
0x04 | — | 未定义 | |
0x08 | — | 未定义 | |
0x10 | 二级cache指令miss | ||
0x20 | 二级cache load miss | ||
其他值 | 同时统计上面的两个或多个事件 | ||
4 | 0x01 | — | Reserved |
0x02 | — | Reserved | |
0x04 | ITLB references to L2 | 当发生ITLBmiss时,ITLB hardware tablewalk访问L2 cache的次数 | |
0x08 | DTLB references to L2 | 同上 | |
0x10 | ITLB references to L2which miss in L2 | 当发生ITLBmiss时,ITLB hardware tablewalk访问L2 cache时发生的cache miss的次数 | |
0x20 | DTLB references to L2 which miss in L2 | 同上 | |
其他值 | 同时统计上面的两个或多个事件 | ||
5 | 0x01,0x02 | — | Reserved |
0x04 | CPU Load to PCX | Count CPU loads to L2 | |
0x08 | CPU I-fetch to PCX | Count I-fetches to L2 | |
0x10 | CPU Store to PCX | Count CPU stores to L2 | |
0x20 | MMU Load to PCX | Count MMU loads to L2 | |
其他值 | 同时统计上面的两个或多个事件 | ||
6-10 | — | — | Reserved |
11 | 0x04 | ITLB misses | Includes all misses (successful and unsuccessful tablewalks) |
0x08 | DTLB misses | Includes all misses (successful and unsuccessful tablewalks) | |
0x0C | TLB misses | Count both ITLB and DTLB misses, including successful andunsuccessful tablewalks. | |
12-15 | — | — | Reserved |
下从上面的两张表中就可以看出PCR的所有作用了,我们在来看PIC寄存器,PIC.h和PIC.l寄存器是完全一样的,他们都是一个32位的计数器,当它所统计的事件发生时(由PCR控制),计数器的值会自加1。当这两个寄存器发生溢出时,PCR寄存器的ov0或ov1状态位会置位,同时SOFTINT寄存器中的interrutp_level_15 trap状态位也会置位。
下T2 Linux性能监听及NMI 看门狗软件实现:
下在之前分析T2 Linux的代码时,我们总是避免讲解Linux 中体系结构无关的软件机制,这是因为之前的分析中我们讲解的T2体系结构相关代码是Linux相关软件机制的基础,Linux 相关的软件机制使用T2提供的硬件特性来实现相关的功能,比如Linux的中断机制是使用T2提供的中断能力来处理中断的。因此尽管也许我们并不了解Linux的标准中断机制,却仍然能够了解Linux的如何使用T2中断体系提供的硬件特性来处理中断的。但是这里我们却要不得不讲解Linux提供的一种软件机制:通知链机制。与之前所涉及的软件机制不同,它并不依赖T2提供的硬件特性,相反Linux中使用T2性能监测硬件特性的软件机制如perf,oprofile性能诊断工具以及NMI看门狗如果想要高效使用T2提供的性能监测硬件特性却必须借助于通知链机制。因此理解通知链机制是我们理解Linux如何使用T2性能监测硬件特性的基础。
下通知链:
下对通知链机制我们只打算讲解其工作原理而并不打算涉及代码细节,因为毕竟它毕竟不是我们的目的。
下在linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其它子系统产生的事件感兴趣。为了让某个子系统在发生某个事件时通知感兴趣的子系统,Linux内核引入了通知链技术。通知链只能够在内核的子系统之间使用,而不能够在内核和用户空间进行事件的通知。
下通知链有四种类型:
下原子通知链:通知链元素的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。
下可阻塞通知链:通知链元素的回调函数在进程上下文中运行,允许阻塞。对应的链表头:
struct blocking_notifier_head {
struct rw_semaphore rwsem;
struct notifier_block *head;
};
下原始通知链:对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。
下SRCU 通知链:可阻塞通知链的一种变体。
下通知链的核心结构:
struct notifier_block { int (*notifier_call)(struct notifier_block *, unsigned long, void *); struct notifier_block *next; int priority;
};
下其 中notifier_call是通知链要执行的函数指针,next用来连接其它的通知结构,priority是这个通知的优先级,同一条链上的 notifier_block{}是按优先级排列的。内核代码中一般把通知链命名为xxx_chain, xxx_nofitier_chain这种形式的变量名。
下通知链的运作机制包括两个角色:
下被通知者:对某一事件感兴趣一方。定义了当事件发生时,相应的处理函数,即回调函数。但需要事先将其注册到通知链中(被通知者注册的动作就是在通知链中增加一项)。
通知者:事件的通知者。当检测到某事件,或者本身产生事件时,通知所有对该事件感兴趣的一方事件发生。他定义了一个通知链,其中保存了每一个被通知者对事件的处理函数(回调函数)。通知这个过程实际上就是遍历通知链中的每一项,然后调用相应的事件处理函数。
通知链的工作包含以下步骤:
- 通知者定义通知链
- 被通知者向通知链中注册回调函数
- 当事件发生时,通知者发出通知(执行通知链中所有元素的回调函数),被通知者调用notifier_chain_register 函数注册回调函数,该函数按照优先级将回调函数加入到通知链中。
通知链的工作原理就讲解到这里了,后面的分析中会有很多地方用到通知连,至于有哪些通知链,到用到的时候再说。
T2 Linux中使用T2 性能检测硬件特性的有3个相对独立的子系统,他们分别是NMI看门狗,perf性能诊断工具和oprofile性能诊断工具,为此,对PCR和PIC的访问定义了公共的读写函数以供这3个子系统调用。
PCR寄存器的访问:
为PCR的访问定义了一个结构体:
struct pcr_ops {u64 (*read)(void);void (*write)(u64);}const struct pcr_ops *pcr_ops;EXPORT_SYMBOL_GPL(pcr_ops);
在我们的平台上(tlb_type==hypervisor),pcr_ops被初始化为n2_pcr_ops,定义如下:
static const struct pcr_ops n2_pcr_ops = {.read = direct_pcr_read,.write = n2_pcr_write,};
读PCR的函数 direct_pcr_read,
60static u64 direct_pcr_read(void)61 {62 u64 val;63 64 read_pcr(val);65 return val;66 }#define read_pcr(__p) __asm__ __volatile__("rd %%pcr, %0" : "=r" (__p))
可以看到 direct_pcr_read函数直接调用宏 read_pcr,该宏是内嵌汇编,它直接使用rd指令读取PCR寄存器的值。
写PCR的函数 n2_pcr_write,
78 static void n2_pcr_write(u64 val)79 {80 unsigned long ret;81 82 ret = sun4v_niagara2_setperf(HV_N2_PERF_SPARC_CTL, val);83 if (val != HV_EOK)84 write_pcr(val);85 }
#define write_pcr(__p) __asm__ __volatile__("wr %0, 0x0, %%pcr" : : "r" (__p))
n2_pcr_write函数先调用hypervisor服务写PCR寄存器,如果没有成功则调用宏 write_pcr写PCR寄存器,该宏跟 read_pcr差不多,就不多说了。
读写PIC寄存器的操作跟上面读写PCR的宏一样:
#define read_pic(__p) __asm__ __volatile__("rd %%pic, %0" : "=r" (__p))
#define write_pic(__p) \__asm__ __volatile__("ba,pt %%xcc, 99f\n\t" \".align 64\n" \"99:wr %0, 0x0, %%pic\n\t" \"rd %%pic, %%g0" : : "r" (__p))#define reset_pic() write_pic(0)
NMI 看门狗:
我们还是从初始化函数pcr_arch_init开始:
132 int __init pcr_arch_init(void)
133 {
134 int err = register_perf_hsvc();
135
136 if (err)
137 return err;
138
139 switch (tlb_type) {
140 case hypervisor:
141 pcr_ops = &n2_pcr_ops;
142 pcr_enable = PCR_N2_ENABLE;
143 picl_shift = 2;
144 break;
145
146 case cheetah:
147 case cheetah_plus:
148 pcr_ops = &direct_pcr_ops;
149 pcr_enable = PCR_SUN4U_ENABLE;
150 break;
151
152 case spitfire:
153 /* UltraSPARC-I/II and derivatives lack a profile
154 * counter overflow interrupt so we can’t make use of
155 * their hardware currently.
156 /
157 / fallthrough */
158 default:
159 err = -ENODEV;
160 goto out_unregister;
161 }
162
163 return nmi_init();
164
165 out_unregister:
166 unregister_perf_hsvc();
167 return err;
168 }
第134行, register_perf_hsvc函数就不细分析了,它的作用是请求hypervisor服务类型,基于sparc体系结构的CPU有多种型号,不同的型号器hypervisor服务是不一样的,这里就是请求设置我们所需要的hypervisor服务类型。
第139到161行的分支语句我们只看hypervisor的那个分支,第140行,把PCR寄存器的操作函数保存到变量,然后初始化了两个全局变量,至于这两个全局变量有什么作用,到用到的时候在解释吧。
第163行,看名字就知道nmi_init是真正初始化NMI看门狗的。我们来看这个函数:
254 int __init nmi_init(void)
255 {
256 int err;
257
258 on_each_cpu(start_nmi_watchdog, NULL, 1);
259
260 err = check_nmi_watchdog();
261 if (!err) {
262 err = register_reboot_notifier(&nmi_reboot_notifier);
263 if (err) {
264 on_each_cpu(stop_nmi_watchdog, NULL, 1);
265 atomic_set(&nmi_active, -1);
266 }
267 }
268 if (!err)
269 init_hw_perf_events();
270
271 return err;
272 }
第258行,on_each_cpu的函数在每个虚拟处理器上都调用函数 start_nmi_watchdog,我们来看这个函数:
215 void start_nmi_watchdog(void *unused)
216 {
217 __get_cpu_var(wd_enabled) = 1;
218 atomic_inc(&nmi_active);
219
220 pcr_ops->write(PCR_PIC_PRIV);
221 write_pic(picl_value(nmi_hz));
222
223 pcr_ops->write(pcr_enable);
224 }
第217行,wd_enabled变量是一个per_cpu变量,在T2上每个虚拟处理器上都有性能监测模块,相应的每个虚拟处理器上都有一个nmi看门狗,对每个nmi看门狗标志其是否被允许的变量 wd_enabled,这里是允许每个虚拟处理器上的nmi看门狗。
第218,nmi_active是一个原子操作,它的值代表了有多少个nmi看门狗处于active状态,这里为每初始化一个nmi看门狗,它都自加1。
第220行,写PCR寄存器,这里写入的值是 PCR_PIC_PRIV,实际上这次写入槽组是把PCR寄存器的位0置1,禁止nonpriviled模式代码访问PIC寄存器。
第221行,写PIC寄存器,是为PIC计数器设置一个初始值,以产生周期性的中断,中断的周期是nmi_hz,而nmi_hz被初始化为HZ,所以NMI看门狗中断的周期是HZ,我们看这个函数:
37 static inline u64 picl_value(unsigned int nmi_hz)38 {39 u32 delta = local_cpu_data().clock_tick / (nmi_hz << picl_shift);40 41 return ((u64)((0 - delta) & 0xffffffff)) << 32;42 }
第39行, local_cpu_data().clock_tick我们在分析setup_arch函数是见过,它里面保存的是CPU频率。而picl_shift定义的值是2,nmi_hz左移2位实际上就是nmi_hz乘以4。4实际上是代表T2虚拟处理器平均每4个tick执行一条指令,nmi中断的频率是nmi_hz,这每个中断周期经过的cpu tick数是cpu频率除以nmi_hz,这个值在除以4,则是每个中断周期执行的指令数保存在变量delta。因此可以看出以PIC计数器的溢出中断作为NMI中断的话,该PIC计数器统计的事件实际上是虚拟处理器执行的指令,我们后面会看到。
第41行,计算PIC寄存器的初始值,由于要统计delta个指令后产生溢出,所以PIC的初始值应该是0 - delta。PIC寄存器是一个64位寄存器,它实际上被分为两个32位寄存器,其中高32位是PIC.h,低32位是PIC.l。这里左移32位说明我们的NMI中断使用的是PIC.h寄存器的溢出中断作为中断源。
回到 start_nmi_watchdog函数,
第223行,这时初始化nmi wachdog的最后一步,通过前面的分析我们知道,它会通过写PCR寄存器允许PIC.h溢出中断,并且sl1域和mask1域设置程统计执行的指令数,这里写入的值是 pcr_enable,这个变量在之前初始化为 PCR_N2_ENABLE,该宏定义如下:
#define PCR_N2_ENABLE (PCR_PIC_PRIV | PCR_STRACE | PCR_UTRACE | \PCR_N2_TOE_OV1 | \(2 << PCR_N2_SL1_SHIFT) | \(0xff << PCR_N2_MASK1_SHIFT))
PCR_PIC_PRIV表示禁止nonpriviled模式代码写PIC寄存器, PCR_STRACE表示统计privileged模式下发生的事件, PCR_UTRACE表示统计nopriviled模式下发生的事件,PCR_N2_TOE_OV1 表示允许PIC.h溢出中断。(2 << PCR_N2_SL1_SHIFT)|(0xff << PCR_N2_MASK1_SHIFT))
是初始化sl1域与mask1域,其中sl1=2,mask1=0xff,刚好表示统计所有类型的cpu指令。
执行了 start_nmi_watchdog函数之后,一个nmi 看门狗就初始化完了,在每一次中断处理函数中,都会把PIC.h的值重新置为刚才计算出来的初始值,这样就会接连不断的产生周期为HZ的nmi中断了,在中断处理函数中会判断当前cpu是否已经被锁住,如果锁住,就打印调试信息。
回到nmi_init函数中,
第260行,之前已经启动nmi看门狗了,这里check_nmi_watchdog函数检查刚刚启动的nmi看门狗是否成功,这个函数由于对我们理解nmi看门狗并不重要,就不仔细分析了,它的检查方法实际上是等待能够产生20个nmi中断的时间,然后看在这其中产生的多少个nmi中断,如果产生的nmi中断的个数小于5个,则认为该nmi看门狗是有问题的,因此禁止该看门狗,即wd_enable变量置0,nmi_active变量减1。
第262行,这里就要用到我们前面提到的通知链机制了,Linux内核为一些实际预定义了一些通知链,比如当你执行halt等关机命令时,内核就会产生一个关机事件,而注册到关机通知链上的所有处理函数就会代表关心关机事件的所有子系统执行它们所需要进行的操作。同样对于reboot重启命令,也有这样的一个预订义的通知链,而重启事件刚好是nmi看门狗所关心的事件。register_reboot_notifier函数就是注册我们的nmi看门狗对重启事件的相应函数到重启通知链中去,我们来看nmi_reboot_notifier的定义:
static struct notifier_block nmi_reboot_notifier = {.notifier_call = nmi_shutdown,
};
从相应函数的名字 nmi_shutdown就知道,当系统重启时nmi看门狗的相应操作就是关掉所有的nmi看门狗:
244 static int nmi_shutdown(struct notifier_block *nb, unsigned long cmd, void *p)
245 {
246 on_each_cpu(stop_nmi_watchdog, NULL, 1);
247 return 0;
248 }
调用函数 stop_nmi_watchdog:
161 void stop_nmi_watchdog(void *unused)
162 {
163 pcr_ops->write(PCR_PIC_PRIV);
164 __get_cpu_var(wd_enabled) = 0;
165 atomic_dec(&nmi_active);
166 }
该函数实际上是把PCR寄存器清空,并把所有的 wd_enabled置0, nmi_active也置0。
我们在回到nmi_init函数中来,
第269行,init_hw_perf_events就与nmi看门狗没多大关系了,它是初始化使用T2性能监测硬件特性的另一个子系统:perf性能诊断工具,这个函数我们讲完nmi看门狗后再讲。
我们上面讲完了nmi看门狗的初始化,但是nmi看门狗的主要工作是在其中断处理函数中完成的,它会检测虚拟处理器是否处于锁住的状态。nmi看门狗的中断使用的是PIC溢出中断,实际上是interrupt_level_15 trap,在Linux的trap table中,该trap hander为:
tl0_irq15: TRAP_NMI_IRQ(perfctr_irq, 15)
宏 TRAP_NMI_IRQ就不分析了,主要工作是在 perfctr_irq函数中完成的,我们直接看这个函数:
93 notrace __kprobes void perfctr_irq(int irq, struct pt_regs *regs)94 {95 unsigned int sum, touched = 0;96 int cpu = smp_processor_id();97 98 clear_softint(1 << irq);99
100 local_cpu_data().__nmi_count++;
101
102 nmi_enter();
103
104 if (notify_die(DIE_NMI, "nmi", regs, 0,
105 pt_regs_trap_type(regs), SIGINT) == NOTIFY_STOP)
106 touched = 1;
107 else
108 pcr_ops->write(PCR_PIC_PRIV);
109
110 sum = kstat_irqs_cpu(0, cpu);
111 if (__get_cpu_var(nmi_touch)) {
112 __get_cpu_var(nmi_touch) = 0;
113 touched = 1;
114 }
115 if (!touched && __get_cpu_var(last_irq_sum) == sum) {
116 local_inc(&__get_cpu_var(alert_counter));
117 if (local_read(&__get_cpu_var(alert_counter)) == 30 * nmi_hz)
118 die_nmi("BUG: NMI Watchdog detected LOCKUP",
119 regs, panic_on_timeout);
120 } else {
121 __get_cpu_var(last_irq_sum) = sum;
122 local_set(&__get_cpu_var(alert_counter), 0);
123 }
124 if (__get_cpu_var(wd_enabled)) {
125 write_pic(picl_value(nmi_hz));
126 pcr_ops->write(pcr_enable);
127 }
128
129 nmi_exit();
130 }
第96行,得到发生nmi中断的cpu的cpuid
第98行,清除中断状态,以接收下一次中断
第100行,cpu data中有一个字段是用来记录该cpu上发生的nmi中断的次数的,这里更新它的值
第102行,进入nmi执行环境
第104行,这里又遇到了一个通知链,内核中预订义了一个通知链die_chain,不过这个通知链不是为了特定的事件定义的,该通知链可以同时处理多个不同的事件,该通知链的事件处理函数中有一个参数cmd标志了是什么事件触发了这个通知链,当某个子系统需要触发一个事件时,调用通知链的通知函数时以该事件类型为参数,这样关心该类型事件的子系统会处理该事件,而通知链中的其他处理函数则不做任何处理。系统中之所以预定义一个die_chain通知链,是因为很多事件,关心它的子系统只有一个或两个,为该事件单独定义一个通知链则显得比较浪费,因此可为这些花费不大的事件定义一个公共的通知链。
与前面分析中遇到的通知链机制不同,这里的我们是作为通知者而不是被通知者,notify_die函数则会触发一个DIE_NMI事件,关心该事件的子系统则会响应该事件。实际上关心该事件的子系统只有两个,分别是perf性能诊断工具和oprofile性能诊断工具,因为他们都是基于T2的性能监测硬件机制工作的,会关心PIC的溢出,因此会在溢出中断中对溢出进行处理。
这里需要说明的是,在T2 linux中,nmi看门狗,perf,和oprofile 3个子系统同时只能由一种处于工作状态,另外两种则处于禁止状态,这3者的优先级是perf > oprofile > nmi看门狗,当优先级低的子系统在处于工作状态时,优先级高的子系统可以打断优先级低的子系统的工作状态直到优先级高的子系统释放对性能检测硬件特性的使用。
oprofile和perf响应DIE_NMI事件的方式并不相同,它们的对该事件的响应方式在后面我们讲解oprofile和perf时会讲到,在讲解的过程中我们会说明为什么它们3者会有这种优先级排序。
第105行,如果notify_die函数的返回值为NOTIFY_STOP,则说明oprofile和perf中的一种处于工作状态,即说明nmi 看门狗处于挂起状态,因此中断处理函数中不会判断cpu是否处于锁住状态。
第106行,touched=1用以说明nmi看门狗处于挂起状态
第108行,清PCR寄存器,这样清了之后性能监测模块从硬件上就被禁止掉了,这条语句只有当nmi 看门狗,opofile和perf 三者都处于停止状态是才有意义。
第11行,kstat_irqs_cpu的作用是从当前cpu的kstat结构中取出0号中断发生的次数,放在sum变量中,在《Linux T2的时钟实现》的章节中,我们分析过0号中断实际上就是系统的时钟中断,每发生一次时钟中断,都会记录到cpu的kstat结构中。
这里之所以取出时钟中断发生的次数是因为nmi看门狗判断cpu处于锁住状态的条件就是:如果连续30秒中该cpu都没有发生过时钟中断,则认定该cpu处于锁定状态。因此每次NMI中断中都会取出当前时钟中断的次数last_irq_sum变量中以在下次NMI中断中进行比较判断时钟中断的次数有没有变化,如果30秒钟没有变化,则cpu处于锁定状态。
第111行,nmi_touch也是一个per_cpu变量,如果nmi_tocuch=0则标志当前cpu上的nmi看门狗处于挂起状态,除了oprofile和perf可以使nmi看门狗处于挂起状态外,内核没有调用touch_nmi_watchdog函数显示的启动nmi看门狗,则代表nmi看门狗仍然处于挂起状态(虽然这时可以周期性的产生nmi中断,但是中断处理函数中不会判断cpu是否处于锁定状态),touch_nmi_watchdog函数正式把该nmi看门狗的nmi_tocuch变量置1来显式的启动nmi看门狗的。如果处于挂起状态,则把touched变量置1。
第115行到123行的分支语句,如果nmi看门狗不是处于挂起状态,则判断cpu是否处于锁定状态,当然如果__get_cpu_var(last_irq_sum)!=sum,则说明在这一次的nmi中断和上一次的nmi中断之间发生了时钟中断,即时钟中断次数有变化,这时cpu就不是处于锁定状态,于是执行120行到123行的分支:
该分支把当前得到的时钟中断次数保存到last_irq_sum中,并把alert_counter置0。
否则,如果__get_cpu_var(last_irq_sum)==sum,则两次nmi中断中没有发生时钟中断,因此执行116行到119行的分支,把alert_counter自加1,如果下次nmi中断来时,时钟次数仍然没有变化则alert_counter再自加1,如果有时钟中断次数有变化则把alert_counter清0重新开始计算。
alert_counter表示连续没有发生时钟中断的nmi中断的次数,一次没发生时钟中断的nmi中断代表1/nmi_hz秒的时间没有发生时钟中断,如果alert_counter自加到30 * nmi_hz,则表示有30s种没有发生时钟中断,判定cpu处于锁定状态,执行die_nmi函数,我们看这个函数:
68 static void die_nmi(const char *str, struct pt_regs *regs, int do_panic)69 {70 if (notify_die(DIE_NMIWATCHDOG, str, regs, 0,71 pt_regs_trap_type(regs), SIGINT) == NOTIFY_STOP)72 return;73 74 console_verbose();75 bust_spinlocks(1);76 77 printk(KERN_EMERG "%s", str);78 printk(" on CPU%d, ip %08lx, registers:\n",79 smp_processor_id(), regs->tpc);80 show_regs(regs);81 dump_stack();82 83 bust_spinlocks(0);84 85 if (do_panic || panic_on_oops)86 panic("Non maskable interrupt");87 88 nmi_exit();89 local_irq_enable();90 do_exit(SIGBUS);91 }
第70行,notify_die函数前面见过,它是触发一个通知链事件,不过这里是DIE_NMIWATCHDOG事件,该事件表示NMI 看门狗探测到一个cpu锁定状态,关心该事件的子系统是内核调试工具kgdb,就不对该事件的处理做分析了。
第72行,如果kgdb对事件进行了响应处理,则nmi看门狗就不做处理了,直接返回,否则,就要对它做处理了。
第74行,console_verbose函数的作用是设置控制台的输出级别,这里是设置到最高级别,这样在之后用printk函数输出时,就会直接输出到屏幕上了。
第75行,bust_spinlocks函数清除一些spinlocks,一些spinlocks会阻止内核的一些调试信息输出到用户空间去,而我们这里正式这样做,因此先把他们清楚掉
第77行到82行,就是输出调试信息到屏幕上的语句,就不细分析了,它会输出被锁住的cpuid,tpc寄存器的值,struct pt_regs结构中保存的通用寄存器中的值,以及把内核栈的值都输出到屏幕上
第83行到90行,退出nmi执行环境
到这里nmi看门狗就分析完了。
Oprofile性能监测工具:
oprofile有两种模式,他们分别基于事件采样和基于时间采样。基于事件的采样需要CPU硬件上的事件计数器支持(即前面讲的PIC),基于时间的采样则不需要事件计数器的支持。T2 Linux的oprofile是基于时间采样的,它虽然不需要使用PIC来统计一些事件发生的次数,但却仍然使用T2的性能监测硬件,因为基于时间采样需要每个一段时间来周期性的采样,所以和nmi看门狗一样,它使用PIC溢出中断来产生周期性的中断。
oprofile体系结构相关代码在arch/sparc/oprofile/init.c文件中,我们来看其初始化函数:
72 int __init oprofile_arch_init(struct oprofile_operations *ops)73 {74 int ret = -ENODEV;75 76 #ifdef CONFIG_SPARC6477 ret = op_nmi_timer_init(ops);78 if (!ret)79 return ret;80 #endif81 82 return ret;83 }
这个函数调用 op_nmi_timer_init函数来进行真正的初始化:
59 static int op_nmi_timer_init(struct oprofile_operations *ops)60 {61 if (atomic_read(&nmi_active) <= 0)62 return -ENODEV;63 64 ops->start = timer_start;65 ops->stop = timer_stop;66 ops->cpu_type = "timer";67 printk(KERN_INFO "oprofile: Using perfctr NMI timer interrupt.\n");68 return 0;69 }
该函数实际上是初始化 struct oprofile_operations *ops变量,该变量是oprofile体系结构无关代码提供的,体系结构相关代码只要初始化好该结构进行了,体系结构无关代码需要时会调用该结构中的函数指针。该结构主要包含两个成员 start,和 stop,它的作用实际上是开始周期性中断和停止周期性的中断,一旦开始了周期性的中断,在中断处理函数中则会调用采样函数进行采样。
我们看开始周期中断的函数timer_start:
43 static int timer_start(void)44 {45 if (register_die_notifier(&profile_timer_exceptions_nb))46 return 1;47 nmi_adjust_hz(HZ);48 return 0;49 }
第45行,在die_chain通知链中注册一个通知处理函数,die通知链,我们在PIC溢出中断的中断处理函数中讲过,这里我们解释一下为什么oprofile的优先级高于nmi 看门狗,一旦oproflie 开始,它就会注册die_chain通知链处理函数,这时一旦PIC溢出中断处理函数中触发DIE_NMI事件,这里注册的通知链处理函数就会处理该事件,在PIC溢出中断处理函数的分析中我们知道如果DIE_NMI事件被处理,就不会判断CPU是否被锁住,就相当于nmi看门狗定时器被挂起。因此oprofile的优先级高于nmi看门狗。不过这时并没有调用stop_nmi_watchdog 函数来停止nmi看门狗,所以nmi看门狗的周期性中断仍然会产生周期性中断,而这正是oprofile所需要的。因此与其说oprofile是使用PIC溢出中断,不如说oprofile使用的是NMI 看门狗中断来工作的。那么为什么perf的优先级会高于oprofile呢,因为perf在开始之前会先调用stop_nmi_watchdog停止nmi 看门狗(我们后面会分析到)从而不会有周期性中断,这时oprofile也就不会工作,因此perf的优先级高于oprofile。
profile_timer_exceptions_nb结构中的通知链处理函数我们就不分析了,它就是判断事件是否为DIE_NMI,如果是就调用oprofile体系结构无关代码提供的采样函数来采样。如果不是则什么就不做。
第47行, nmi_adjust_hz调整nmi中断的频率,这个函数也不分析了,它会判断wd_enable是否为1,如果不为1显然oproflie是无法工作的(没有周期中断),返回错误。
timer_stop函数也不分析了,它会在die_chain通知链中删除刚刚注册的通知链处理函数。
perf性能诊断工具:
perf基于事件的采样可以采样硬件事件和软件事件,显然软件事件是体系结构无关的。体系结构相关代码只会关心硬件事件。
sparc架构代码分析-NMI看门狗分析相关推荐
- Android system server之WatchDog看门狗分析
android -- WatchDog看门狗分析 在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系 ...
- (实验6,实验7)单片机,STM32F4学习笔记,代码讲解【看门狗实验】【正点原子】【原创】
文章目录 其它文章链接,独家吐血整理 实验现象(实验六) 主程序(实验六) 独立看门狗初始化程序(实验六) 代码讲解(实验六) 实验现象(实验七) 主程序(实验七) 窗口看门狗初始化程序(实验七) 代 ...
- java实现看门狗_Watchdog看门狗分析
看门狗最初的意义是因为早期嵌入式设备上的程序经常跑飞(比如说电磁干扰等),所以专门设置了一个硬件看门狗,每隔一段时间,看门狗就去检查某个参数是不是被设置了,如果发现该参数被设置了,则判断为系统出错,然 ...
- SP706SEN外部看门狗分析
1.硬件电路图 2.引脚解析 1脚MR:手动复位,输入电压低于0.8v时触发复位脉冲,或主动低电平输入触发 2脚vcc:电源 3脚gnd:公共地 4脚pfi:电源故障输入,当电压低于1.25v时,pf ...
- C8051关闭看门狗汇编语言,汇编写启动代码之关看门狗
1 什么是看门狗? 看门狗(watch dog timer 看门狗定时器).大家想象这样一个场景:家门口有一只狗,这个狗定时会饿(譬如说2小时一饿),够饿了会胡乱咬死人.人进进出出要想保证安全必须提前 ...
- 《嵌入式 – GD32开发实战指南》第17章 看门狗
开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK GD32 有两个看门狗,一个是独立看门狗,另外一个是窗口看门狗,独立看门狗号称宠物狗,窗口看 ...
- 在多任务(RTOS)环境中使用看门狗
最近在SEGGER的博客上看到一篇有关在实时操作系统使用看门狗的文章.从一个失败的太空项目出发,分析了看门狗的作用及使用,自我感觉很有启发,特此翻译此文并推荐给各位同仁.为了阅读方便,有些航天领域名词 ...
- 第四天:关看门狗、设置栈、控制icache、重定位、链接脚本
1.汇编写启动代码:关看门狗 什么是看门狗? 看门狗(watch dog timer看门狗定时器),比如:家门口有一只狗,这个狗定时会饿(譬如两小时一饿),狗饿了就会胡乱咬人,人进进出出要想保证安全必 ...
- 【STM32】stm32独立看门狗(IWDG)
stm32独立看门狗(IWDG) 0x01 IWDG简介 0x02 IWDG主要性能 0x03 IWDG寄存器配置 0x01 IWDG简介 STM32F10xxx内置两个看门狗,(独立看门狗和窗口看门 ...
最新文章
- hihoCoder1040 矩形判断
- pytorch permute维度转换
- c语言输出11258循环,c/c++内存机制(一)(转)
- 雷军凌晨2点下班、刘强东睡4小时,这碗鸡汤程序员你必须干了
- Scikit-learn库中的数据预处理(一)
- 将Vim打造成Python快速开发环境(一)
- Scroll View 控件以Thumbnail的方式显示一个目录的全部图片,相似图片浏览器
- 百叶窗式的幻灯片切换效果原理
- Java Matcher源码学习记录
- 百度网盘图片直链的php解析代码
- w25qxx SPI读取数据出来为全FF
- Viterbi算法(维特比算法)
- php聊天室把数据存在缓存里,php聊天室信息存储的问题
- 【matlab】Simulink 微分模块的线性化问题
- 两张MD5值一样但实际不一样的图片
- 【WiFi】WiFi安全类型
- pandas之链式索引问题(chained indexing)
- adobe illustrator软件能做什么
- 软件测试周刊(第36期):为什么你要当程序员?
- 从 pdf 中提取表格信息、合并、解析、输出数据