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内核中管理中断的机制,以响应硬件事件或触发的事件,处理与中断相关的操作

总结

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


相关文章
|
7月前
|
安全 网络协议 Linux
深入理解Linux内核模块:加载机制、参数传递与实战开发
本文深入解析了Linux内核模块的加载机制、参数传递方式及实战开发技巧。内容涵盖模块基础概念、加载与卸载流程、生命周期管理、参数配置方法,并通过“Hello World”模块和字符设备驱动实例,带领读者逐步掌握模块开发技能。同时,介绍了调试手段、常见问题排查、开发规范及高级特性,如内核线程、模块间通信与性能优化策略。适合希望深入理解Linux内核机制、提升系统编程能力的技术人员阅读与实践。
680 1
|
7月前
|
Ubuntu Linux
Ubuntu 23.04 用上 Linux 6.2 内核,预计下放到 22.04 LTS 版本
Linux 6.2 带来了多项内容更新,修复了 AMD 锐龙处理器设备在启用 fTPM 后的运行卡顿问题,还增强了文件系统。
|
7月前
|
Ubuntu Linux
Ubuntu 23.10 现在由Linux内核6.3提供支持
如果你想在你的个人电脑上测试一下Ubuntu 23.10的最新开发快照,你可以从官方下载服务器下载最新的每日构建ISO。然而,请记住,这是一个预发布版本,所以不要在生产机器上使用或安装它。
|
7月前
|
传感器 监控 Ubuntu
10 月发布,Ubuntu 23.10 已升级到 Linux Kernel 6.3 内核
硬件方面,Linux 6.3 引入了在 HID 中引入了原生的 Steam Deck 控制器接口,允许罗技 G923 Xbox 版赛车方向盘在 Linux 上运行;改善 8BitDo Pro 2 有线控制器的行为;并为一系列华硕 Ryzen 主板添加传感器监控。
|
7月前
|
Ubuntu Linux
Ubuntu24.04LTS默认采用Linux 6.8内核,实验性版本可通过PPA获得
IT之家提醒,当下的 Ubuntu 23.10 也是一个“短期支持版本”,该版本将在今年 7 月终止支持,而今年 4 月推出的 Ubuntu 24.04 LTS 长期支持版本将获得 5 年的更新支持。
|
7月前
|
监控 Ubuntu Linux
什么Linux,Linux内核及Linux操作系统
上面只是简单的介绍了一下Linux操作系统的几个核心组件,其实Linux的整体架构要复杂的多。单纯从Linux内核的角度,它要管理CPU、内存、网卡、硬盘和输入输出等设备,因此内核本身分为进程调度,内存管理,虚拟文件系统,网络接口等4个核心子系统。
458 0
|
7月前
|
Web App开发 缓存 Rust
|
7月前
|
Ubuntu 安全 Linux
Ubuntu 发行版更新 Linux 内核,修复 17 个安全漏洞
本地攻击者可以利用上述漏洞,攻击 Ubuntu 22.10、Ubuntu 22.04、Ubuntu 20.04 LTS 发行版,导致拒绝服务(系统崩溃)或执行任意代码。
|
7月前
|
Ubuntu 机器人 物联网
Linux Ubuntu 22.04 LTS 测试版实时内核已可申请
请注意,在启用实时内核后您需要手动配置 grub 以恢复到原始内核。更多内容请参考: