kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Idle进程的切换过程

http://blog.chinaunix.net/uid-27767798-id-3577069.html

每个cpu都有自己的运行队列,如果当前cpu上运行的任务都已经dequeue出运行队列,而且idle_balance也没有移动到当前运行队列的任务,那么schedule函数中,按照rt ,cfs,idle这三种调度方式顺序,寻找各自的运行任务,那么如果rt和cfs都未找到运行任务,那么最后会调用idle schedule的idle进程,作为schedule函数调度的下一个任务。

kernel/sched.c 中的schedule()函数中的片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {   
	//state大于0代表prev也就是当前运行的任务不是running状态,并且没有标记 PREEMPT_ACTIVE,就表示当前的运行的任务没有必要停留在运行队列中了
	if (unlikely(signal_pending_state(prev->state, prev)))  //如果当前进程标记了状态是TASK_INTERRUPTIBLE,并且还有信号未处理,那么没有必要从运行队列中移除这个进程
		prev->state = TASK_RUNNING;
	else
		deactivate_task(rq, prev, DEQUEUE_SLEEP);        //从运行队列中移除这个进程
	switch_count = &prev->nvcsw;
}

pre_schedule(rq, prev);

if (unlikely(!rq->nr_running)) //如果当前运行队列没有进程可以运行了,就balance其他运行队列的任务到当前运行队列,这里balance的具体过程暂时不说
	idle_balance(cpu, rq);

put_prev_task(rq, prev);
next = pick_next_task(rq);     //按照rt,cfs,idle优先级的顺序挑选进程,如果在rt和cfs中都没有找到能够运行的任务,那么当前cpu会切换到idle进程。

这里 PREEMPT_ACTIVE是个标志位,由于进程由于系统调用或者中断异常返回到用户态之前,都要判断是否可以被抢占,会首先判断preempt_count,等于0的时候表示没有禁止抢占,然后再去判断是否标记了need_resched,如果标记了,在去调用schedule函数,如果在某些时候禁止了抢占,禁止了一次就要preempt_count加1。可以肯定的一点是进程的state和是否在运行队列的因果关系并不是十分同步的,修改了进程的状态后,可能还需要做一些其他的工作才去调用schedule函数。引用一下其他人的例子。

1
2
3
4
5
6
for (;;) {
   prepare_to_wait(&wq, &__wait,TASK_UNINTERRUPTIBLE);
   if (condition)
	 break;
   schedule();
}

可以看出在修改了进程的state之后,并不会立刻调用schedule函数,即使立刻调用了schedule函数,也不能保证在schedule函数之前的禁止抢占开启之前有其他的抢占动作。毕竟修改进程的state和从运行队列中移除任务不是一行代码(机器码)就能搞定的事情。所以如果在修改了进程的状态之后和schedule函数禁止抢占之前有抢占动作(可能是中断异常返回),如果这个时候进程被其他进程抢占,这个时候把当前进程移除运行队列,那么这个进程将永远没有机会运行后面的代码。所以这个时候在抢占的过程之前将preempt_count标记PREEMPT_ACTIVE,这样抢占中调用schedule函数将不会从当前运行队列中移除当前进程,这样才有前面分析schedule函数代码,有判断进程state同时判断preempt_count未标记PREEMPT_ACTIVE的情况。

在当前进程被移除出运行队列之前还需要判断是否有挂起的信号需要处理,如果当前进程的状态是TASK_INTERRUPTIBLE或者TASK_WAKEKILL的时候,如果还有信号未处理,那么当前进程就不需要被移除运行队列,并且将state置为running。

1
2
3
4
5
6
7
8
9
static inline int signal_pending_state(long state, struct task_struct *p)
{
	if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL))) //首先判断状态不是这两个可以处理信号的状态就直接返回0,后面的逻辑不考虑了
		return 0;
	if (!signal_pending(p))             //如果没有信号挂起就不继续了
		return 0;

	return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p); //如果有信号
}
说下 put_prev_task的逻辑,按照道理说应该是rt,cfs,idle的顺序寻找待运行态的任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pick_next_task(struct rq *rq)
{
	const struct sched_class *class;
	struct task_struct *p;

	/*
	 * Optimization: we know that if all tasks are in
	 * the fair class we can call that function directly:
	 */
	//这里注释的意思都能看懂,如果rq中的cfs队列的运行个数和rq中的运行个数相同,直接调用cfs中 的pick函数,因为默认的调度策略是cfs。
	if (likely(rq->nr_running == rq->cfs.nr_running)) {
		p = fair_sched_class.pick_next_task(rq);
		if (likely(p))
		return p;
	}

	//这里 sched_class_highest就是rt_sched_class,所以前面没有选择出任务,那么从rt开始挑选任务,直到idle
	class = sched_class_highest;
      for ( ; ; ) {
		p = class->pick_next_task(rq);
		if (p)
			return p;
		/*
		 * Will never be NULL as the idle class always
		 * returns a non-NULL p:
		 */
		class = class->next;
	}
}

从每个调度类的代码的最后可以看出这个next关系

sched_rt.c中:

1
2
static const struct sched_class rt_sched_class = {
.next = &fair_sched_class,

sched_fair.c中:

1
2
static const struct sched_class fair_sched_class = {
.next = &idle_sched_class,

那么可以试想如果rt和cfs都没有可以运行的任务,那么最后就是调用idle的pick_next_task函数

sched_idletask.c:

1
2
3
4
5
6
static struct task_struct *pick_next_task_idle(struct rq *rq)
{
	schedstat_inc(rq, sched_goidle);
	calc_load_account_idle(rq);
	return rq->idle;    //可以看到就是返回rq中idle进程。
}

这idle进程在启动start_kernel函数的时候调用init_idle函数的时候,把当前进程(0号进程)置为每个rq的idle上。

kernel/sched.c:5415

1
rq->curr = rq->idle = idle;

这里idle就是调用start_kernel函数的进程,就是0号进程。

0号进程在fork完init进程等之后,进入cpu_idle函数,大概的逻辑是for循环调用hlt指令,每次hlt返回后,调用schedule函数,具体的流程现在还没太看懂,可以看到的是在具体的逻辑在default_idle函数中,调用了safe_halt函数

1
2
3
4
static inline void native_safe_halt(void)
{
	asm volatile("sti; hlt": : :"memory");
}

关于hlt指令的作用是:引用wiki百科

In the x86 computer architecture, HLT (halt) is an assembly language instruction which halts the CPU until the next external interrupt is fired.[1] Interrupts are signals sent by hardware devices to the CPU alerting it that an event occurred to which it should react. For example, hardware timers send interrupts to the CPU at regular intervals.

The HLT instruction is executed by the operating system when there is no immediate work to be done, and the system enters its idle state. In Windows NT, for example, this instruction is run in the “System Idle Process”.

可以看到注释的意思是,hlt指令使得cpu挂起,直到有中断产生这个时候cpu重新开始运行。所以时钟中断会唤醒正在hlt中的cpu,让它调用schedule函数,检测是否有新的任务在rq中,如果有的话切换到新的任务,否则继续执行hlt,cpu继续挂起。

参考文章
1.http://blog.csdn.net/dog250/article/details/5303547

2.http://en.wikipedia.org/wiki/HLT

NMI 看门狗

http://blog.csdn.net/arethe/article/details/6153143

[X86和X86-64体系结构均支持NMI看门狗]

你的系统是不是会经常被锁住(Lock up)?直至解锁,系统不再响应键盘?你希望帮助我们解决类似的问题吗?如果你对所有的问题都回答“yes”,那么此文档正是为你而写。

在很多X86/X86-64结构的硬件上,我们都可以使用一种被称为“看门狗NMI中断”的机制。(NMI:Non Maskable Interrupt. 这种中断即使在系统被锁住时,也能被响应)。这种机制可以被用来调试内核锁住现象。通过周期性地执行NMI中断,内核能够监测到是否有CPU被锁住。当有处理器被锁住时,打印调试信息。

为了使用NMI看门狗,首先需要在内核中支持APIC。对于SMP内核,APIC的相关支持已自动地被编译进内核。对于UP内核,需要在内核配置中使能CONFIG_X86_UP_APIC (Processor type and features -> Local APIC support on uniprocessors) 或 CONFIG_X86_UP_IOAPIC (Processor type and features -> IO-APIC support on uniprocessors)。在没有IO-APIC的单处理器系统中,配置CONFIG_X86_UP_APIC。在有IO-APIC的单处理器系统中,则需配置CONFIG_X86_UP_IOAPIC。[注意:某些与内核调试相关选项可能会禁用NMI看门狗。如:Kernel Stack Meter或Kernel Tracer]。

对于X86-64系统,APIC已被编进内核。

使用本地APIC(nmi_watchdog=2)时,需要占用第一个性能寄存器,因而此寄存器不能再被另作它用(如高精度的性能分析)。Oprofile与perfctr的驱动已自动地禁用了本地APIC的NMI看门狗。

可以通过启动参数“nmi_watchdog=N”使能NMI看门狗。即在lilo.conf的相关项中添加如下语句:

1
  append=”nmi_watchdog=1”

对于具有IO-APIC的SMP与UP机器,设置nmi_watchdog=1。对于没有IO-APIC的UP机器,设置nmi_watchdog=2,但仅在某些处理器上可以起作用。如果有疑问,在用nmi_watchdog=1启动后,再查看/proc/interrupts文件中的NMI项,如果该项为0,那么便用nmi_watchdog=2重新启动,并再次检查NMI项。如果还是0,问题就比较严重了,你的处理器很可能不支持NMI。

“锁住(Lockup)”是指如下的情况:如果系统中的任何一个CPU不能处理周期性的本地时钟中断,并持续5秒钟以上,那么NMI的处理函数将产生一个oops并杀死当前进程。这是一种“可控崩溃”(Controlled Crash,所谓可控,是指发生崩溃时,能够输出内核信息),可以用此机制来调试“锁住”现象。那么,无论什么时候发生“锁住”,5秒钟之后都会自动地输出oops。如果内核没有输出信息,说明此时发生的崩溃过于严重(如:hardware-wise),以至于NMI中断都无法被响应,或者此次崩溃使得内核无法打印信息。

在使用本地APIC时要注意,NMI中断被触发的频率依赖于系统的当前负载。由于缺乏更好的时钟源,本地APIC中的NMI看门狗使用的是“有效周期(Cycle unhalted,这个词的翻译似乎不太确切,如果某位朋友有更佳的建议,请告知在下。)”事件。也许你已经猜到了,当CPU处于halted(空等)状态时,该时钟是不计数的。处理器处于空闲状态的时候,常出现这样的情况。如果你的系统在被锁住时,执行的不是hlt指令,看门狗中断很快就会被触发,因为每个时钟周期都会发生“有效周期”事件。如果不幸,处理器在被锁住时,执行的恰是“hlt”指令,那么“有效周期”事件永远都不会发生,看门狗自然也不会被触发。这是本地APIC看门狗的缺陷,在倒霉的时候,永远不会进行时钟计数。而I/O APIC中的看门狗由于采用外部时钟进行驱动,便不存在这个缺陷。但是,它的NMI频率非常高,会显著地影响系统的性能。

X86的nmi_watchdog在默认情况下是禁用的,因此你需要在系统启动的时候使能它。

在系统运行期间,可以禁用NMI看门狗,只要向文件“/proc/sys/kernel/nmi_watchdog”中写“0”即可。向该文件写“1”,将重新使能看门狗。即使如此,你仍然需要在启动时使用参数“nmi_watchdog=”。

注意:在2.4.2-ac18之前的内核中,X86 SMP平台会无条件地使能NMI-oopser。


www.2cto.com/kf/201311/260704.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//  使能hard lockup探测
//  调用路径:watchdog_enable->watchdog_nmi_enable
//  函数任务:
//      1.初始化hard lockup检测事件
//          2.hard lockup阈值为10s
//      2.向performance monitoring子系统注册hard lockup检测事件
//      3.使能hard lockup检测事件
//  注:
//      performance monitoring,x86中的硬件设备,当cpu clock经过了指定个周期后发出一个NMI中断。
1.1 static int watchdog_nmi_enable(unsigned int cpu)
{
	//hard lockup事件
	struct perf_event_attr *wd_attr;
	struct perf_event *event = per_cpu(watchdog_ev, cpu);
	....
	wd_attr = &wd_hw_attr;
	//hard lockup检测周期,10s
	wd_attr->sample_period = hw_nmi_get_sample_period(watchdog_thresh);
	//向performance monitoring注册hard lockup检测事件
	event = perf_event_create_kernel_counter(wd_attr, cpu, NULL, watchdog_overflow_callback, NULL);
	....
	//使能hard lockup的检测
	per_cpu(watchdog_ev, cpu) = event;
	perf_event_enable(per_cpu(watchdog_ev, cpu));
	return 0;
}
 
//  换算hard lockup检测周期到cpu频率
1.2 u64 hw_nmi_get_sample_period(int watchdog_thresh)
{
	return (u64)(cpu_khz) * 1000 * watchdog_thresh;
}
 
//  hard lockup检测事件发生时的nmi回调函数
//  函数任务:
//      1.判断是否发生了hard lockup
//          1.1 dump hard lockup信息
1.3 static void watchdog_overflow_callback(struct perf_event *event,
	 struct perf_sample_data *data,
	 struct pt_regs *regs)
{
	//判断是否发生hard lockup
	if (is_hardlockup()) {
		int this_cpu = smp_processor_id();
 
		//打印hard lockup信息
		if (hardlockup_panic)
			panic("Watchdog detected hard LOCKUP on cpu %d", this_cpu);
		else
			WARN(1, "Watchdog detected hard LOCKUP on cpu %d", this_cpu);
 
		return;
	}
	return;
}
 
//  判断是否发生hard lockup
//  注:
//      如果时钟中断在指定阈值范围内为运行,核心认为可屏蔽中断被屏蔽时间过长
1.4 static int is_hardlockup(void)
{
	//获取watchdog timer的运行次数
	unsigned long hrint = __this_cpu_read(hrtimer_interrupts);
	//在一个hard lockup检测时间阈值内,如果watchdog timer未运行,说明cpu中断被屏蔽时间超过阈值
	if (__this_cpu_read(hrtimer_interrupts_saved) == hrint)
		return 1;
	//记录watchdog timer运行的次数
	__this_cpu_write(hrtimer_interrupts_saved, hrint);
	return 0;
}
 
//  关闭hard lockup检测机制
//  函数任务:
//      1.向performance monitoring子系统注销hard lockup检测控制块
//      2.清空per-cpu hard lockup检测控制块
//      3.释放hard lock检测控制块
2.1 static void watchdog_nmi_disable(unsigned int cpu)
{
	struct perf_event *event = per_cpu(watchdog_ev, cpu);
	if (event) {
		//向performance monitoring子系统注销hard lockup检测控制块
		perf_event_disable(event);
		//清空per-cpu hard lockup检测控制块
		per_cpu(watchdog_ev, cpu) = NULL;
		//释放hard lock检测控制块
		perf_event_release_kernel(event);
	}
	return;
}

中断,进程

blog.chinaunix.net/uid-20806345-id-3203602.html

中断不是进程,不受内核调度器的管辖。在系统处理进程的过程中,对于某个cpu来说,如果有内部中断或外部中断到来,则切换到中断处理程序,切换首先要将进程由用户态要切到进程的内核态,然后再将cpu切换到中断态,待处理完中断返回进程的内核态,再返回进程的用户态,如果中断时进程刚好处于内核态中不用由用户态切到内核态了。
中断处理时是不分优先级的,处理中断的过程中如果有任意中断到来,都会抢占当前的中断处理过程。所以对于要及时响应的中断,需要通过关中断来屏蔽其他中断。通常所说的中断优先级是指中断控制器端的优先级,当有多个中断触发时,首先选择优先级高的中断发出请求给处理器。中断优先级只是对中断控制器而言的,所有的中断对cpu来说都是一样的,没有优先级高低之分。
关中断是关闭所有的外部可屏蔽中断,和优先级没有关系,如果在某中断处理程序中关中断,则不会被任何可屏蔽中断抢占,但是会被任意的不可屏蔽中断抢占。关中断是中断处理程序可选的。

bbs.chinaunix.net/thread-2306027-1-8.html

软中断做的是一些可延迟的费时间的事,当然不能在中断里执行了。
__do_softirq代码,可以看到在执行可延迟函数第一件事就是开中断。但在开始之前,禁用了下半部中断(__local_bh_disable)。这样就算被中断了,返回内核时也不会被抢占,还是执行这里的代码。也不会被调度。
那么这样的后果就是软中断上下文里的会一直执行下去,直到到达了限定次数,然后唤醒守护进程。
因为软中断切换了栈,不再使用进程上下文,那么如果在软中断上下文直接或简洁调用了shedule,那么只有死翘翘了!!因为schedule调度回来的时候是依赖进程内核栈的thread_info。

内核抢占点之一就是中断返回的时候检查是否可以抢占,检查的内容之一就是preempt_count是否等于0,因为禁用了下半部中断,那么肯定就不会等于0的,所以不会被抢占。也就是说返回的时候不会发生调度。

个人理解 中断上下文 最大的特征 禁掉了某种中断(硬中断和软中断),所以导致 不能阻塞。
softirq 有可能在两种方式下被调用,一是中断处理程序退出时,开放硬件中断之后,会去调用do_softirq()。 do_softirq()会禁掉后半部抢占,并且现在执行流使用的是被中断的进程的栈,所以无法阻塞。
softirq的另一种调用方式是ksoftirq内核线程,同样do_softirq()被调用,后半部中断被禁掉,同样禁止阻塞。
工作队列,可以被任何中断或者软中断中断,运行在进程上下文,有自己的栈,可以阻塞。

看一下__do_softirq()的代码,新的硬中断确实可能触发更高优先级的软中断,但是这个软中断并不会在被中断的软中断之前得到执行,软中断始终是顺序执行的。从代码看来,新一批的软中断,无论优先级多高,也得等到前一批的软中断被处理完成之后才能得到处理。而优先级只能帮助软中断在对应的批次中优先得到处理。