中断

中断分为上半部和下半部,中断上半部处理时间敏感任务和硬件相关工作,不能睡眠且不可被打断。中断下半部包括软中断、tasklet和工作队列,用于处理可延迟的任务。软中断在do_softirq中运行,不抢占其他软中断;tasklet串行执行,适合特定场景;工作队列则允许任务在内核线程中睡眠和重新调度。

中断


中断内部被细分成了两部分, 中断上半部, 中断下半部.

中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面4条:

  1. 如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
  2. 如果一个任务和硬件相关,将其放在中断处理程序中执行。
  3. 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
  4. 其他所有任务,考虑放在下半部去执行。

中断上半部

这里写图片描述

中断上下文

进程上下文是运行在内核态帮助进程在内核执行, 通过current宏关联当前的进程, 在内核态进程是可以睡眠的, 可以调用调度函数的, 但是中断上下文是不能睡眠的.

  1. 中断上半部主要就是快速的处理中断信号.
  2. 它有严格的时间限制, 因为中断会打断其他代码的执行.
  3. 中断上半部处理的时侯不会被打断, 不能睡眠, 所以不能在中断执行某些函数(并不是不能执行其他函数).
  4. 尽量将中断工作(处理等)交给中断下半部.

中断处理程序是被内核用来处理中断的, 同时他也是不可阻塞的, 检测到中断就会立即执行.

中断上半部运行的就是中断处理函数, 它主要是将中断交给下半部来处理, 同时在中断处理结束返回时标记它是什么类型的中断.比如 软中断代替用户态进程在内核态运行返回时, 中断调用run_local_times将其标记为软中断.

中断也有自己的中断栈, 大概1kb左右. 为了保存执行在中断的一些函数的数据, 指针.


中断下半部

因为中断上半部要达到即时响应, 将工作都交给了中断下半部.

下半部的实现是由以下构成的

  1. k);

    // 重复执行

  2. 软中断

  3. tasklets

  4. 工作队列

软中断

软中断是由编译时静态分配的. 产生后并不是马上可以执行,必须要等待内核的调度才能执行。可以并发运行在多个CPU上.

同样, 在函数调用的时侯, 经系统调用触发软中断, 陷入内核, 此时内核”代替进程”在内核空间运行.

当运行一个软中断处理程序的时侯, 他会执行 action函数执行中断调用, 其唯一的参数指向 softirq_action结构体, 软中断是在 do_softirq中运行.

软中断描述符

struct softirq_action
{ 
    void (*action)(struct softirq_action *);
};

软中断执行函数

do_softirq-->__do_softirq

软中断在 do_softirq() 的执行

u32 pending;
// 获取软中断
pending = local_softirq_pending();

// 如果有软中断, pending就不为0
if(pending)
{
    struct softirq_action *h;

    // 清除当前的软中断, 当前中断已经保存在pending中了
    set_softirq_pending(0);

    // h 指向当前softirq_vec 的第一项, 也就是第一个向量号
    h = softirq_vec;
    do{
        // 向量号为 1, 代表次信号开启, 需要被执行
        if(pending & 1)
            // action 执行当前向量号的中断
            h->action(h);

        /* 下面就是移动至下一个向量号, 直到 pending 没有要处理的中断信号了 */
        h++;
        pending >>= 1;
    }while(pending);
}
  1. 软中断是不会抢占其他软中断. 而唯一能够抢占中断的函数就是中断处理函数.
  2. 软中断的调用一般是由系统调用引起的, 用户态进程触发软中断而陷入内核, 然后进程上下文代替进程在内核态运行.

tasklet

tasklet 也是软中断的一种, 只是又有自己独特的不同于软中断的特点.

tasklet 的有特点

  1. 特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
  2. 多个不同类型的tasklet可以并行在多个CPU上。
  3. 软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行

tasklet 描述符

struct tasklet_struct
{
      //将多个tasklet链接成单向循环链表
      struct tasklet_struct *next;
      //TASKLET_STATE_SCHED(Tasklet is scheduled for execution)  TASKLET_STATE_RUN(Tasklet is running (SMP only))
      unsigned long state;
      //0:激活tasklet 非0:禁用tasklet
      atomic_t count;
      //用户自定义函数
      void (*func)(unsigned long); 
      // 函数入参
      unsigned long data; 
};

工作队列

工作队列可以把工作推后执行, 并将工作交给内核线程去执行, 是唯一一个可以在进程上下文中执行的中断, 所以他也是可睡眠, 可重新调度的.

工作队列可创建专门的工作者线程(内核线程)来处理需要推后的工作, 而所有的工作者线程都使用普通的内核线程实现的, 都要执行 work_thread 函数.

graph TD
工作队列 -- 创建 --> 工作者线程
工作者线程 --> 处理推迟的工作

workqueue_struct 表示所有给定类型的工作者线程, 而一个 cpu_workqueue_struct 表示一个工作者线程

struct workqueue_struct
{
  struct cpu_workqueue_struct cpu_wq[NR_CPUS];
  struct list_head list;
  const char *name;
  int sinqlethread;
  int freezeable;
  int rt;
};

工作队列将推后的工作放入一个链表中, 当工作者线程有空闲时侯才会从中执行链表中的工作, 不空闲那么将工作往等待链表中出入, 等待.

工作队列的执行

// 工作队列的执行
for( ; ; )
{
  // TASK_INTERRUPTIBLE : 工作队列设置为等待
  prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);

  // 如果工作队列是一个空链表, 那么就进行调度, 工作队列休眠
  if(list_empty(&cwq->worklist))
    shedule();

  // 工作队列链表非空, 就将其要执行的进行分离
  finish_wait(&cwq->more_work, &wait);
  // 执行上一步分离的工作.
  run_workqueue(cwq);
};

run_workqueue 部分实现

while(!list_empty(&cwq->worklist))
{
  struct work_struct *work;
  work_func_t f;
  void *data;

  // 执行工作
  work = list_entry(cwq->worklist.next, struct work_struct, entry);
  f = work->func;

  // 清除执行了的工作
  list_del_init(cwq->worklist.next);
  work_clear_pending(work);

  // 重复执行
  f(work);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值