为什么不能在中断上半部休眠?

嵌入式Linux

共 9014字,需浏览 19分钟

 ·

2021-02-01 22:45

这是一个老生常谈的问题。

我们先简单说下什么是中断「因为最近在群里看到有人竟然不懂什么是中断」。中断是计算机里面非常核心的东西,我们可以跑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函数?

这个是之前已经发过的文章

可以在中断服务程序执行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一致。




推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~


浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报