Linux kernel 笔记 (53)——为什么“interrupt handler”不能被抢占?

Interrupt handler会复用当前被中断taskkernel stack,它并不是一个真正的task,也不拥有task_struct。因此一旦被调度出去,就无法再被调度回来继续执行。所以interrupt handler不允许被抢占。

参考资料:
Why can’t you sleep in an interrupt handler in the Linux kernel? Is this true of all OS kernels?
Why kernel code/thread executing in interrupt context cannot sleep?;
Are there any difference between “kernel preemption” and “interrupt”?;
Why can not processes switch in atomic context?

 

Linux kernel 笔记 (52)——使用“spinlock”的进程不能被抢占

以下摘自这封邮件

A process cannot be preempted nor sleep while holding a spinlock due spinlocks behavior. If a process grabs a spinlock and goes to sleep before releasing it. A second process (or an interrupt handler) that to grab the spinlock will busy wait. On an uniprocessor machine the second process will lock the CPU not allowing the first process to wake up and release the spinlock so the second process can continue, it is basically a deadlock.

This happens since grabbing an spinlocks also disables interrupts and this is required to synchronize threads with interrupt handlers.

当一个task获得spinlock以后,它不能被抢占(比如调用sleep)。因为如果这时有另外一个task也想获得这个spinlock,在UP系统上,这个task就会一直占据CPU,并且不停地尝试获得锁。而第一个task没有机会重新执行来释放锁,这就造成“死锁”。Interrupt handler也是同样道理。

 

Linux kernel 笔记 (51)——”atomic context”

以下摘自这篇文章

Kernel code generally runs in one of two fundamental contexts. Process context reigns when the kernel is running directly on behalf of a (usually) user-space process; the code which implements system calls is one example. When the kernel is running in process context, it is allowed to go to sleep if necessary. But when the kernel is running in atomic context, things like sleeping are not allowed. Code which handles hardware and software interrupts is one obvious example of atomic context.

There is more to it than that, though: any kernel function moves into atomic context the moment it acquires a spinlock. Given the way spinlocks are implemented, going to sleep while holding one would be a fatal error; if some other kernel function tried to acquire the same lock, the system would almost certainly deadlock forever.

“Deadlocking forever” tends not to appear on users’ wishlists for the kernel, so the kernel developers go out of their way to avoid that situation. To that end, code which is running in atomic context carefully follows a number of rules, including (1) no access to user space, and, crucially, (2) no sleeping. Problems can result, though, when a particular kernel function does not know which context it might be invoked in. The classic example is kmalloc() and friends, which take an explicit argument (GFPKERNEL or GFPATOMIC) specifying whether sleeping is possible or not.

处理中断代码属于atomic context,必须遵守下面的原则:
a)不能访问user space
b)不能sleep

 

Linux kernel 笔记 (50)——”context switch”和”mode switch”

以下内容摘自stackoverflow上的这个帖子

At a high level, there are two separate mechanisms to understand. The first is the kernel entry/exit mechanism: this switches a single running thread from running usermode code to running kernel code in the context of that thread, and back again. The second is the context switch mechanism itself, which switches in kernel mode from running in the context of one thread to another.

So, when Thread A calls sched_yield() and is replaced by Thread B, what happens is:

Thread A enters the kernel, changing from user mode to kernel mode;
Thread A in the kernel context-switches to Thread B in the kernel;
Thread B exits the kernel, changing from kernel mode back to user mode.

Each user thread has both a user-mode stack and a kernel-mode stack. When a thread enters the kernel, the current value of the user-mode stack (SS:ESP) and instruction pointer (CS:EIP) are saved to the thread’s kernel-mode stack, and the CPU switches to the kernel-mode stack – with the int $80 syscall mechanism, this is done by the CPU itself. The remaining register values and flags are then also saved to the kernel stack.

When a thread returns from the kernel to user-mode, the register values and flags are popped from the kernel-mode stack, then the user-mode stack and instruction pointer values are restored from the saved values on the kernel-mode stack.

When a thread context-switches, it calls into the scheduler (the scheduler does not run as a separate thread – it always runs in the context of the current thread). The scheduler code selects a process to run next, and calls the switchto() function. This function essentially just switches the kernel stacks – it saves the current value of the stack pointer into the TCB for the current thread (called struct taskstruct in Linux), and loads a previously-saved stack pointer from the TCB for the next thread. At this point it also saves and restores some other thread state that isn’t usually used by the kernel – things like floating point/SSE registers.

So you can see that the core user-mode state of a thread isn’t saved and restored at context-switch time – it’s saved and restored to the thread’s kernel stack when you enter and leave the kernel. The context-switch code doesn’t have to worry about clobbering the user-mode register values – those are already safely saved away in the kernel stack by that point.

总结如下:
mode switch”是一个运行的taskuser-mode切换到kernel-mode,或者切换回来。而“context switch”一定发生在kernel mode,进行task的切换。

每个user task有一个user-mode stack和一个kernel-mode stack,当从user-mode切换到kernel-mode时,寄存器的值要保存到kernel-mode stack,反之,从kernel-mode切换回user-mode时,要把寄存器的值恢复出来。

进行“context switch”时,scheduler将当前kernel-mode stack中的值保存在task_struct中,并把下一个将要运行tasktask_struct值恢复到kernel-mode stack中。这样,从kernel-mode返回到user-mode,就会运行另外一个task