Linux中断(tasklet,工作队列,内核线程的使用)

简介: Linux中断(tasklet,工作队列,内核线程的使用)

前言

本篇文章来讲解在Linux中tasklet,工作队列,内核线程的使用。

一、tasklet

tasklet在内核里面其实就是下面这样的一个结构体:

struct tasklet_struct
{
  struct tasklet_struct *next;
  unsigned long state;
  atomic_t count;
  void (*func)(unsigned long);
  unsigned long data;
};

下面是对 struct tasklet_struct 结构体成员的解释:

struct tasklet_struct *next: 链表中的下一个任务队列项。任务队列项通过 next 成员在链表中连接在一起,可以形成一个队列。

unsigned long state: 任务队列项的状态标志。用于跟踪任务队列项当前的状态,例如等待执行、已禁用等。

atomic_t count: 计数器,用于跟踪任务队列项的引用计数。当某个任务队列项被多个地方引用时,可以通过增加计数器来避免释放或重用。

void (*func)(unsigned long): 函数指针,指向任务队列项需要执行的函数。当任务队列项被调度执行时,内核将调用该函数来执行任务。

unsigned long data: 存储额外的数据,供任务队列项函数使用。可以将数据与任务队列项关联起来,以在执行函数时传递给函数。

相关的三个重要函数:

tasklet_init(struct tasklet_struct *tasklet, void (*func)(unsigned long), unsigned long data)

tasklet_init() 函数用于初始化一个任务队列项。它接受三个参数:

tasklet:指向要初始化的 struct tasklet_struct 对象的指针。

func:指向任务队列项的处理函数的指针。该函数将在任务队列项被调度执行时被调用。

data:传递给任务队列项处理函数的数据。

通过调用 tasklet_init(),可以为任务队列项设置适当的处理函数和数据。初始化的任务队列项是处于禁用状态的,需要通过调用 tasklet_schedule() 来启用它。

tasklet_kill(struct tasklet_struct *tasklet)

tasklet_kill() 函数用于停止一个正在运行或已经被调度的任务队列项。它接受一个参数:

tasklet:指向要停止的任务队列项的指针。

调用 tasklet_kill() 将会禁用任务队列项,并确保它不会被再次调度执行。这在你不再需要该任务队列项时非常有用,可以确保它不会再干扰系统的正常操作。

tasklet_schedule(struct tasklet_struct *tasklet)

tasklet_schedule() 函数用于将一个任务队列项添加到任务队列中以待执行。它接受一个参数:

tasklet:指向要调度的任务队列项的指针。

调用 tasklet_schedule() 将会启用被调度的任务队列项,并安排它在适当的时机执行。在软中断上下文中调用该函数会立即执行任务队列项的处理函数,而在其他上下文中,例如进程上下文,稍后会调度执行。

任务队列项的处理函数将在任务队列中的适当时机被调用,以更好地与操作系统的内核执行过程进行协调。

使用tasklet步骤:

1.先定义tasklet,需要使用时调用tasklet_schedule,驱动卸载前调用tasklet_kill。

2.tasklet_schedule只是把tasklet放入内核队列,它的func函数会在软件中断的执行过程中被调用。

注意:

tasklet_schedule只会把tasklet放入队列一次,调用完成后需要再次放入队列中。

tasklet内部机制:

tasklet属于TASKLET_SOFTIRQ软件中断。

入口函数为tasklet_action。

void __init softirq_init(void)
{
  int cpu;
  for_each_possible_cpu(cpu) {
    per_cpu(tasklet_vec, cpu).tail =
      &per_cpu(tasklet_vec, cpu).head;
    per_cpu(tasklet_hi_vec, cpu).tail =
      &per_cpu(tasklet_hi_vec, cpu).head;
  }
  open_softirq(TASKLET_SOFTIRQ, tasklet_action);
  open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

当驱动程序调用tasklet_schedule时,会设置tasklet的state为TASKLET_STATE_SCHED,并把它放入某个链表:

void __tasklet_schedule(struct tasklet_struct *t)
{
  unsigned long flags;
  local_irq_save(flags);
  t->next = NULL;
  *__this_cpu_read(tasklet_vec.tail) = t;
  __this_cpu_write(tasklet_vec.tail, &(t->next));
  raise_softirq_irqoff(TASKLET_SOFTIRQ);
  local_irq_restore(flags);
}

当发生硬件中断时,内核处理完硬件中断后,会处理软件中断。对于TASKLET_SOFTIRQ软件中断,会调用tasklet_action函数。

注意:

1.tasklet_schedule调度tasklet时,其中的函数并不会立刻执行,而只是把tasklet放入队列。

2.调用一次tasklet_schedule,只会导致tasklnet的函数被执行一次。

3.如果tasklet的函数尚未执行,多次调用tasklet_schedule也是无效的,只会放入队列一次。

tasklet_action函数:

这个函数会将队列中的tasklet逐个取出来执行:

static __latent_entropy void tasklet_action(struct softirq_action *a)
{
  struct tasklet_struct *list;
  local_irq_disable();
  list = __this_cpu_read(tasklet_vec.head);
  __this_cpu_write(tasklet_vec.head, NULL);
  __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
  local_irq_enable();
  while (list) {
    struct tasklet_struct *t = list;
    list = list->next;
    if (tasklet_trylock(t)) {
      if (!atomic_read(&t->count)) {
        if (!test_and_clear_bit(TASKLET_STATE_SCHED,
              &t->state))
          BUG();
        t->func(t->data);
        tasklet_unlock(t);
        continue;
      }
      tasklet_unlock(t);
    }
    local_irq_disable();
    t->next = NULL;
    *__this_cpu_read(tasklet_vec.tail) = t;
    __this_cpu_write(tasklet_vec.tail, &(t->next));
    __raise_softirq_irqoff(TASKLET_SOFTIRQ);
    local_irq_enable();
  }
}

二、工作队列

tasklet使用起来非常方便简单,但是tasklet依然是属于中断,在中断执行时,应用程序还是无法得到执行,这样就可能导致系统的卡顿。所以就有了工作队列。

工作队列在内核中的定义:

struct work_struct {
  atomic_long_t data;
  struct list_head entry;
  work_func_t func;
#ifdef CONFIG_LOCKDEP
  struct lockdep_map lockdep_map;
#endif
};

1.atomic_long_t data: 这是一个原子长整型变量,通常用于存储工作项相关的数据。原子型意味着对该变量的读取和写入是原子操作,可以在多线程环境下进行安全的并发访问。

2.struct list_head entry: 这是一个链表节点,用于将工作项(work_struct)添加到一个链表中。链表节点通常包含指向前一个节点和后一个节点的指针,以便在链表中进行插入、删除和遍历操作。

3.work_func_t func: 这是一个函数指针类型(work_func_t),用于指向执行工作项的函数。通过该函数指针,可以在工作队列中执行相应的操作或任务。

4.#ifdef CONFIG_LOCKDEP 和 struct lockdep_map lockdep_map: 这部分代码是一个预处理指令,用于条件编译。在特定的配置条件下(CONFIG_LOCKDEP宏定义为真),会包含struct lockdep_map类型的lockdep_map成员变量。lockdep_map用于在工作项中添加用于锁依赖分析的调试信息。

使用工作队列时,步骤如下:

① 构造一个work_struct结构体,里面有函数;

② 把这个work_struct结构体放入工作队列,内核线程就会运行work中的函数。

相关函数:

1.INIT_WORK:

INIT_WORK用于初始化一个工作项(struct work_struct)。其原型如下:

void INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));

该函数用于初始化一个工作项,并指定要执行的回调函数(func)。回调函数将在调用schedule_work函数时被调度执行。初始化后的工作项可以被添加到工作队列中,以便在合适的时机执行相关操作。

2.schedule_work:

schedule_work用于将一个工作项添加到工作队列,以便在合适的时机执行工作项的回调函数。其原型如下:

int schedule_work(struct work_struct *work);

该函数将指定的工作项添加到工作队列,工作队列会按照一定的调度机制在合适的时机执行工作项。工作项的回调函数将在调度时被执行。这样可以将一些延后执行的任务或者需要在工作队列上下文中执行的任务安排在合适的时机执行。

注意:schedule_work函数是异步的,意味着它只是将工作项添加到队列,然后立即返回,并不会等待工作项的执行完成。

三、内核线程

有了工作队列后就可以把耗时的任务放入工作队列中进行处理了,但是当有多个work时前面没有执行完的work就会影响到后面work的执行,这样的效率还是不高,那么这个时候就需要使用中断的线程化处理了。

使用这个机制需要使用到下面这个函数:

request_threaded_irq:

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                         irq_handler_t thread_fn, unsigned long irqflags,
                         const char *devname, void *dev_id);

该函数用于请求指定中断号(irq)的中断,并将中断处理函数(handler)注册到该中断上。当中断事件发生时,处理函数会被调用。

这个函数具有以下参数:

irq:要请求的中断号。

handler:中断处理函数,即中断发生时要调用的函数。它通常是一个标准的中断处理函数,接收中断相关的参数。

thread_fn:中断线程函数,当中断处理函数返回IRQ_WAKE_THREAD时会调用。它通常用于执行耗时操作,以便避免中断处理函数的执行时间过长。

irqflags:中断标志,用于指定中断的行为,例如共享中断、触发类型等。

devname:设备名称,用于标识请求中断的设备。

dev_id:设备ID,用于传递设备相关的数据给中断处理函数。

request_threaded_irq的工作流程如下:

1.检查中断号的有效性和是否已被占用。如果中断号无效或已被占用,函数会返回错误。

2.创建一个中断描述符(struct irq_desc)并初始化相应的字段。

3.将中断处理函数和中断线程函数注册到中断描述符的对应字段上。

4.配置中断描述符的中断行为,如中断类型、中断共享等。

5.将中断描述符添加到全局中断控制器中断描述符数组中,以便系统可以管理该中断。

6.启用中断,允许中断事件触发中断处理函数的调用。

在请求中断成功后,中断事件发生时,中断控制器会检测到对应的中断号,然后调用已注册的中断处理函数或中断线程函数(如果应用)。中断处理函数是在中断上下文中执行的,因此需要遵循中断上下文的要求,尽量做到快速和非阻塞。如果中断处理函数在处理中需要较长时间,可以调用wake_up_process函数唤醒中断线程函数,以便执行更复杂的工作。

总结而言,request_threaded_irq函数用于请求指定中断,并将中断处理函数注册到该中断上。它提供了一种在Linux内核中管理中断的机制,以响应硬件事件或触发的事件,处理与中断相关的操作

总结

本篇文章就讲解到这里,需要大家好好理解并进行实践。


相关文章
|
21天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
60 4
|
25天前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
43 6
|
10天前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
32 1
|
15天前
|
算法 Linux 开发者
Linux内核中的锁机制:保障并发控制的艺术####
本文深入探讨了Linux操作系统内核中实现的多种锁机制,包括自旋锁、互斥锁、读写锁等,旨在揭示这些同步原语如何高效地解决资源竞争问题,保证系统的稳定性和性能。通过分析不同锁机制的工作原理及应用场景,本文为开发者提供了在高并发环境下进行有效并发控制的实用指南。 ####
|
23天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
57 9
|
22天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
38 6
|
23天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
39 5
|
23天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
24天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
23天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。