为什么不能在中断上半部休眠?
这是一个老生常谈的问题。
我们先简单说下什么是中断「因为最近在群里看到有人竟然不懂什么是中断」。中断是计算机里面非常核心的东西,我们可以跑OS,可以多任务运行都因为中断的存在。
假设你是一个CPU,你正在睡觉。
你突然觉得肚子疼,「这个事情就是一个中断你睡觉的事情」,然后你就去上厕所了。
然后你又回到床上睡觉。
你突然又觉得肚子饿了,「这个事情也是一个中断你睡觉的事情」,然后你就去吃了个汉堡。
中断可以认为是突发的事情,并且时间不是特别长,如果时间非常久,那就不能叫中断了,就叫主业了。
CPU怎么用中断做到多任务运行呢?
CPU不是普通的人,CPU比普通的人快得多得多。
就拿我手上最差的新唐单片机举例子,他的主频是24M,那执行一条指令周期的时间就是
1/24000000 = 41.6 纳秒
假设你现在有两个事情要做,玩王者荣耀和扫地
你在第一个时钟周期,拿着手机玩王者荣耀,第二个时钟周期扫地,第三个时钟周期玩王者荣耀,第四个时钟周期扫地……
这样,你会发现,CPU是一个闪电侠,可能比闪电侠还要快,根据这个方法,可以裂变出很多很多个人做很多很多件事情。
Linux 内核中断上文为什么不能休眠呢?
现在的Linux 内核中断是不能嵌套的,所以我们就只讨论单中断的问题。
进中断的第一件事情,就是关中断,这个是关所有中断。
然后就在中断里面干事情了,具体做什么事情,我也不知道。
做完事情然后就开中断,去做其他事情了。
如果休眠会引起什么呢?
比如这样的代码
static irqreturn_t tpd_eint_interrupt_handler(unsigned irq, struct irq_desc *desc)
{
TPD_DEBUG_PRINT_INT;
tpd_flag = 1;
/* enter EINT handler disable INT, make sure INT is disable when handle touch event including top/bottom half */
/* use _nosync to avoid deadlock */
spin_lock(&irq_lock);
if (tpd_irq_flag) {
tpd_irq_flag = 0;
disable_irq_nosync(tpd_touch_irq);
}
spin_unlock(&irq_lock);
ssleep(1);//系统会死掉
wake_up_interruptible(&waiter);
return IRQ_HANDLED;
}
这个代码的问题在ssleep(1)
上,ssleep(1)
是OS里面的休眠函数,这个函数会让任务休眠,休眠的意思就是让这个任务睡觉不执行,CPU去执行其他的任务。
因为CPU是按时间片执行的,等下一次调度到这个中断的时候,就有可能再获取一次spin_lock
自旋锁,两次获取锁的操作,肯定就引起系统挂壁了。
具体的可以去看看ssleep
的代码
/**
* msleep - sleep safely even with waitqueue interruptions
* @msecs: Time in milliseconds to sleep for
*/
void msleep(unsigned int msecs)
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1;
while (timeout)
timeout = schedule_timeout_uninterruptible(timeout);
}
EXPORT_SYMBOL(msleep);
系统宕机日志
[ 1.564767] <3>.(3)[139:kworker/u8:2]<>[tpd_irq_registration:484] Device Tree Tpd_irq_registration!
[ 1.566520] <3>.(3)[139:kworker/u8:2]gpiod_set_debounce: invalid GPIO
[ 1.567319] <3>.(3)[139:kworker/u8:2]<>[tpd_irq_registration:495] Device gt1x_int_type = 1!
[ 1.568484] <0>-(0)[0:swapper/0]BUG: scheduling while atomic: swapper/0/0/0x00010002
[ 1.568493] <0>.(3)[139:kworker/u8:2]<>[tpd_irq_registration:514] irq:119, debounce:0-0:
[ 1.570534] Modules linked in:
[ 1.570912] <0>-(0)[0:swapper/0]CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.4.146 #41
[ 1.571879] <0>-(0)[0:swapper/0]Hardware name: Generic DT based system
[ 1.572689] Backtrace:
[ 1.572989] <0>-(0)[0:swapper/0][] (dump_backtrace) from [] (show_stack+0x18/0x1c)
[ 1.574143] r6:60000193 r5:c1136bc8 r4:00000000 r3:dc8ba692
[ 1.574841] <0>-(0)[0:swapper/0][] (show_stack) from [] (dump_stack+0x94/0xa8)
[ 1.575950] <0>-(0)[0:swapper/0][] (dump_stack) from [] (__schedule_bug+0x58/0x6c)
[ 1.577104] r6:c1101ce8 r5:c1085b40 r4:00000000 r3:60000193
[ 1.577802] <0>-(0)[0:swapper/0][] (__schedule_bug) from [] (__schedule+0x59c/0x808)
[ 1.578978] r4:c110bcf8 r3:00010002
[ 1.579418] <0>-(0)[0:swapper/0][] (__schedule) from [] (schedule+0x54/0xc4)
[ 1.580507] r10:00000000 r9:c1102100 r8:dfedfb80 r7:c1103948
[ 1.581216] <0>-(0)[0:swapper/0][] (schedule) from [] (schedule_timeout+0x148/0x264)
[ 1.582391] r4:fffea370 r3:c1100000
[ 1.582831] <0>-(0)[0:swapper/0][] (schedule_timeout) from [] (msleep+0x34/0x40)
[ 1.583964] r10:c1100000 r9:00000000 r8:00000000 r7:00000077
[ 1.584673] <0>-(0)[0:swapper/0][] (msleep) from [] (tpd_eint_interrupt_handler+0x2c/0xd8)
[ 1.585913] r5:ced00000 r4:cec8d540
[ 1.586353] <0>-(0)[0:swapper/0][] (tpd_eint_interrupt_handler) from [] (handle_irq_event_percpu+0x6c/0x268)
[ 1.587787] r5:d0344ae0 r4:cec8d540
[ 1.588227] <0>-(0)[0:swapper/0][] (handle_irq_event_percpu) from [] (handle_irq_event+0x40/0x64)
[ 1.589542] r10:c0c487cc r9:00000077 r8:00000000 r7:00000000
[ 1.590251] <0>-(0)[0:swapper/0][] (handle_irq_event) from [] (handle_level_irq+0xb4/0x16c)
[ 1.591502] r6:d0344a90 r5:d0344ae0 r4:d0344a80 r3:00020002
[ 1.592201] <0>-(0)[0:swapper/0][] (handle_level_irq) from [] (generic_handle_irq+0x2c/0x3c)
[ 1.593462] r6:d030e6d0 r5:02000000 r4:00000019 r3:c019d2d4
[ 1.594161] <0>-(0)[0:swapper/0][] (generic_handle_irq) from [] (mtk_eint_irq_handler+0x288/0x36c)
[ 1.595486] <0>-(0)[0:swapper/0][] (mtk_eint_irq_handler) from [] (generic_handle_irq+0x2c/0x3c)
[ 1.596790] r10:00000001 r9:00000000 r8:00010001 r7:00000000
[ 1.597499] <0>-(0)[0:swapper/0][] (generic_handle_irq) from [] (__handle_domain_irq+0xc0/0x25c)
[ 1.598802] <0>-(0)[0:swapper/0][] (__handle_domain_irq) from [] (gic_handle_irq+0x50/0x94)
[ 1.600053] r9:e1003000 r8:e1002000 r7:c1101eb0 r6:e100200c
[ 1.600752] <0>-(0)[0:swapper/0][] (gic_handle_irq) from [] (__irq_svc+0x54/0x90)
[ 1.601893] <0>-(0)[0:swapper/0]Exception stack(0xc1101eb0 to 0xc1101ef8)
[ 1.602736] <0>-(0)[0:swapper/0]1ea0: 00000000 ffffffff 1ee35000 00000000
[ 1.603965] <0>-(0)[0:swapper/0]1ec0: c11ac9b4 cfbdf800 5cc9dbdf 00000000 5ccecf4c 00000000 00000001 c1101f44
[ 1.605192] <0>-(0)[0:swapper/0]1ee0: c1101f00 c1101f00 c08a2380 c08a2390 60000013 ffffffff
[ 1.606224] r9:00000000 r8:5ccecf4c r7:c1101ee4 r6:ffffffff
[ 1.606922] <0>-(0)[0:swapper/0][] (cpuidle_enter_state) from [] (cpuidle_enter+0x1c/0x20)
[ 1.608162] r10:c1103a1c r9:c1187308 r8:cfbdf800 r7:c11ac9b4
[ 1.608872] <0>-(0)[0:swapper/0][] (cpuidle_enter) from [] (cpu_startup_entry+0x1e0/0x3d8)
[ 1.610110] <0>-(0)[0:swapper/0][] (cpu_startup_entry) from [] (rest_init+0x90/0x94)
[ 1.611285] r7:ffffffff
[ 1.611596] <0>-(0)[0:swapper/0][] (rest_init) from [] (start_kernel+0x418/0x468)
[ 1.612739] r4:c11b1040 r3:dc8ba692
[ 1.613179] <0>-(0)[0:swapper/0]Backtrace aborted due to bad pc
[ 1.614040] <0>-(0)[0:swapper/0]------------[ cut here ]------------
[ 1.614816] <0>-(0)[0:swapper/0]Kernel BUG at c0155360 [verbose debug info unavailable]
[ 1.615807] <0>-(0)[0:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM
[ 1.616746] <0>-(0)[0:swapper/0][LY]rtc_mark_aee_kernel_panic!!!
[ 1.617551] disable aee kernel api
[ 1.617935] <0>-(0)[0:swapper/0]Kernel Offset: disabled
[ 1.618603] Modules linked in:
Linux 内核中断函数能不能执行malloc函数?
这个是之前已经发过的文章
内核里面分配内存的函数是kmalloc
,其他不废话,我直接测试了下面的代码
static irqreturn_t tpd_eint_interrupt_handler(unsigned irq, struct irq_desc *desc)
{
char *rbuff = NULL;
rbuff = kmalloc(1024, GFP_KERNEL);
TPD_DEBUG_PRINT_INT;
tpd_flag = 1;
/* enter EINT handler disable INT, make sure INT is disable when handle touch event including top/bottom half */
/* use _nosync to avoid deadlock */
spin_lock(&irq_lock);
if (tpd_irq_flag) {
tpd_irq_flag = 0;
disable_irq_nosync(tpd_touch_irq);
}
spin_unlock(&irq_lock);
ssleep(1);//系统会死掉
wake_up_interruptible(&waiter);
kfree(rbuff);
return IRQ_HANDLED;
}
系统是没有挂掉的,然后去看看kmalloc
的实现。
/**
* kmalloc - allocate memory
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate.
*
* kmalloc is the normal method of allocating memory
* for objects smaller than page size in the kernel.
*
* The @flags argument may be one of:
*
* %GFP_USER - Allocate memory on behalf of user. May sleep.
*
* %GFP_KERNEL - Allocate normal kernel ram. May sleep.
*
* %GFP_ATOMIC - Allocation will not sleep. May use emergency pools.
* For example, use this inside interrupt handlers.
*
* %GFP_HIGHUSER - Allocate pages from high memory.
*
* %GFP_NOIO - Do not do any I/O at all while trying to get memory.
*
* %GFP_NOFS - Do not make any fs calls while trying to get memory.
*
* %GFP_NOWAIT - Allocation will not sleep.
*
* %__GFP_THISNODE - Allocate node-local memory only.
*
* %GFP_DMA - Allocation suitable for DMA.
* Should only be used for kmalloc() caches. Otherwise, use a
* slab created with SLAB_DMA.
*
* Also it is possible to set different flags by OR'ing
* in one or more of the following additional @flags:
*
* %__GFP_COLD - Request cache-cold pages instead of
* trying to return cache-warm pages.
*
* %__GFP_HIGH - This allocation has high priority and may use emergency pools.
*
* %__GFP_NOFAIL - Indicate that this allocation is in no way allowed to fail
* (think twice before using).
*
* %__GFP_NORETRY - If memory is not immediately available,
* then give up at once.
*
* %__GFP_NOWARN - If allocation fails, don't issue any warnings.
*
* %__GFP_REPEAT - If allocation fails initially, try once more before failing.
*
* There are other flags available as well, but these are not intended
* for general use, and so are not documented here. For a full list of
* potential flags, always refer to linux/gfp.h.
*/
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
if (!(flags & GFP_DMA)) {
int index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
return kmem_cache_alloc_trace(kmalloc_caches[index],
flags, size);
}
#endif
}
return __kmalloc(size, flags);
}
代码里面没有看到引起调度的代码,但是注释里面有说会引起休眠。
这个函数跟微信微信公众号留言提到的gfp
一致。