在中断里不能sleep的原因:硬件中断是很宝贵的资源,想在中断里睡眠的话就得在大量的critical section中关闭中断才能避免race condition,而关闭硬件中断将会大大地增加中断响应的延迟,降低系统的反应速度,这是操作系统的用户所无法接受的。
一、在中断里不能sleep的原因
“为什么在中断里不能sleep”,即“为什么在Linux里,ISR被设计成不能睡眠”。sleep会导致call scheduler以选择另一个进程来运行。内核代码里有大量的critical section(临界区)。critical section本质上是一段会访问或操作共享资源的代码,例如:
static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{
struct fs_struct *fs = current->fs;
if (clone_flags & CLONE_FS) {
/* tsk->fs is already what we want */
spin_lock(&fs->lock);
if (fs->in_exec) {
spin_unlock(&fs->lock);
return -EAGAIN;
}
fs->users++;
spin_unlock(&fs->lock);
return 0;
}
tsk->fs = copy_fs_struct(fs);
if (!tsk->fs)
return -ENOMEM;
return 0;
}
在 critical section 里,是不能 call scheduler 的。因为已经有一个进程持有锁了,如果这时切换到另一个进程,较好的情况下是等待一段无法预测的时间后前一个进程会将锁释放出来,最坏的情况是死锁。硬件中断是随时可能发生的,即便内核执行的路径正处于 critical section 中。
如果想在 ISR 里支持 sleep,也就是支持 call scheduler 的话,那么所有的 critical section 都必须得禁用中断,否则硬件中断一旦来临系统就会出现 race condition,接下来大概率是死锁。
总结:
硬件中断是超级宝贵的资源,想在中断里睡眠的话就得在大量的 critical section 中关闭中断才能避免 race condition,而关闭硬件中断将会大大地增加中断响应的延迟,降低系统的反应速度,这是操作系统的用户所无法接受的,因此内核开发者采用的设计是在中断里不允许睡眠,并且 ISR 应尽快执行并返回以便系统里的进程继续运行。
二、ISR(中断服务程序)
1、概念
所谓中断是指当CPU正在处理某件事情的时候,外部发生的某一事件(如一个电平的变化,一个脉冲沿的发生或 定时器计数溢出等)请求CPU迅速去处理,于是CPU暂时中止当前的工作,转去处理所发生的事件。中断服务处理完该事件以后,再回到原来被中止的地方继续原来的工作。
中断是一种硬件机制,用于通知CPU有个异步事件发生了。中断一旦被系统识别,CPU则保存部分(或全部)现场(context),即部分(或全部) 寄存器的值,跳转到专门的 子程序,称为 中断服务程序(ISR)。 中断服务程序做事件处理,处理完成后执行任务调度,程序回到就绪态优先级较高的任务开始运行(对于可剥夺型内核)。
2、示例
错误示例:
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
正确示例:
void interrupt int60()
{
puts("This is an example");
}
3、注意事项
中断是嵌入式系统中重要组成部分,很多编译器开发商都让标准c支持中断,并引入关键字_interrupt。但是:
- ISR不能有返回值;
- ISR不能传递参数;
- ISR应该是短而高效的,在ISR中做浮点运算是不明智的;
- ISR中不应该有重入和性能上的问题,因此不应该使用pintf()函数。
- 裸奔的系统:硬件中断响应程序的运行插入时机是随机的,程序中不存在这样的调用语句:“value=interrupter( )”, 所以,即使有返回值也不知返回给谁。 同理,如果中断函数有形参,但因没有调用者,也就没有实参对形参赋值。所以,不可能有参数传递。裸奔系统中,中断程序由硬件触发执行。这意味着中断函数没有具体的调用者,所以,中断函数无法将值返回给任何对象。
- 非裸奔系统:操作系统需要进行各种调度安排,所以接管了中断的入、出口;另外,还增加了许多软件中断。这些中断函数的运行插入时机已经不再是随机了。一个中断申请发生后,其运行时机取决于操作系统的确定安排和调用。也就是说,有了调用者,所以可以有返回值和参数传递。
延伸阅读1:Linux的schedule()函数是什么
schedule() 是 linux 调度器中最重要的一个函数,就像 fork 函数一样优雅,它没有参数,没有返回值,却实现了内核中最重要的功能,当需要执行实际的调度时,直接调用 shedule(),进程就这样神奇地停止了,而另一个新的进程占据了 CPU。