Linux中断子系统原理及应用总结

软中断

软中断简介

软中断一般而言在对时间要求较高的地方使用,常见的如网络
其分为八种类型

API

步骤函数参数意义
1open_softirqint nr, void(* action)(struct softirq_action *)注册nr类型的软中断,处理函数为action
2actionstruct softirq_action *中断处理函数
3raise_softirqint nr触发nr类型的软中断,设为挂起状态,下次周期运行

其中在软中断运行时,允许响应中断,也可被其他cpu再次激活软中断,两个同时运行,所以处理函数必须是可重入函数,唯一不响应中断的时候就是在触发中断时raise_softirq,在设挂起后响应中断

tasklet

tasklet 简介

tasklet建立在软中断上,但是对比直接使用软中断来说,tasklet有以下几点区别

  • 一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行
  • 多个不同类型的tasklet可以并行在多个CPU上
  • 软中断是静态分配的,tasklet可以动态改变

简单的说软中断可并发在cpu上运行,所以需要多考虑并发的同步和互斥
tasklet杜绝了同一个任务并发运行,但是可以不同的tasklet在不同的cpu上可以同时运行


struct tasklet_struct
{
    struct tasklet_struct *next;    //链表中的下一个tasklet
    unsigned long state;            //tasklet的状态
    atomic_t count;                    //引用计数器
    void (*func)(unsigned long);    //tasklet处理函数
    unsigned long data;                //tasklet处理函数的传参
};

其中taskelet状态

TASKLET_STATE_SCHED //tasklet已被挂起,尚未运行

TASKLET_STATE_RUN //tasklet正在运行

0

count为tasklet引用计数器

0 //使能tasklet,此状态下被设置挂起时,才可执行

1 //失能tasklet

tasklet API

步骤函数参数意义
1DECLARE_TASKLETname,func,data创建一个引用计数器为0的tasklet
1DECLARE_TASKLET_DISABLEDname,func,data创建一个引用计数器为1的tasklet
2tasklet_initstruct tasklet_struct *t,void (*func)(unsigned long), unsigned long data赋值一个名为t的tasklet,其调度函数为func,data为调度函数的传参,count为0
3funcunsigned long实现调度函数
4tasklet_schedulestruct tasklet_struct *t将名为t的tasklet标记挂起
5tasklet_disablestruct tasklet_struct *t失能名为t的tasklet
6tasklet_enablestruct tasklet_struct *t使能名为t的tasklet,与tasklet_disable成对使用
7tasklet_killstruct tasklet_struct *t将名为t的tasklet休眠,该函数可能会休眠

ksoftirqd

软中段(包括tasklet)是人为申请触发的,故而肯定会出现时而爆满时而空闲的状况,为了提高内核效率就必然会有一套动态效率控制的方法

ksoftirqd
为了解决软中断动态性数量而造成的内核效率问题,在每个处理器都有线程ksoftirqd,它在软中断爆满时唤醒,此线程优先级为最低,达到不抢占其他任务,却肯定会执行的效果

示例场景:
当从中断返回时触发软中段执行中断下半部,在执行软中断时又可以重复触发自身,形成循环,无限占用内核,当ksoftirqd出现时,触发的软中断优先级被调到最低,在执行其他的重要任务后才执行软中断,避免了内核被一直占用的问题

workqueue

均以2.6.19以后的内核代码为例

workqueue简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhApkuij-1596530329712)(http://sswiki.sigmastar.com.tw:8090/download/attachments/1225160/workqueue.png?version=1&modificationDate=1596528398356&api=v2)]

先看看关键的数据结构:

  • work_struct:工作队列调度的最小单位,work item;

  • workqueue_struct:工作队列,work item都挂入到工作队列中;

  • worker:work item的处理者,每个worker对应一个内核线程;

  • worker_pool:worker池(内核线程池),是一个共享资源池,提供不同的worker来对work item进行处理;

  • pool_workqueue:充当桥梁纽带的作用,用于连接workqueue和worker_pool,建立链接关系;

struct work_struct {
  atomic_long_t data;     //低比特存放状态位,高比特存放worker_pool的ID或者pool_workqueue的指针
  struct list_head entry; //用于添加到其他队列上
  work_func_t func;       //工作任务的处理函数,在内核线程中回调
#ifdef CONFIG_LOCKDEP
  struct lockdep_map lockdep_map;
#endif
};

其中,data

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhR9USAR-1596530329714)(http://sswiki.sigmastar.com.tw:8090/download/attachments/1225160/work_struct-data.png?version=2&modificationDate=1596528634253&api=v2)]+

struct delayed_work {
    struct work_struct work;
    struct timer_list timer;

    /* target workqueue and CPU ->timer uses to queue ->work */
    struct workqueue_struct *wq;
    int cpu;
};
struct workqueue_struct {
 struct list_head pwqs;  /* WR: all pwqs of this wq */   //所有的pool_workqueue都添加到本链表中
 struct list_head list;  /* PR: list of all workqueues */    //用于将工作队列添加到全局链表workqueues中
 
 struct list_head maydays; /* MD: pwqs requesting rescue */    //rescue状态下的pool_workqueue添加到本链表中
 struct worker  *rescuer; /* I: rescue worker */  //rescuer内核线程,用于处理内存紧张时创建工作线程失败的情况
 
 struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
 
 char   name[WQ_NAME_LEN]; /* I: workqueue name */
 
 /* hot fields used during command issue, aligned to cacheline */
 unsigned int  flags ____cacheline_aligned; /* WQ: WQ_* flags */
 struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */     //Per-CPU都创建pool_workqueue
 struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */    //Per-Node创建pool_workqueue
    ...
};

其中workqueue可分两种

默认的工作者线程

自创工作者线程

一般而言工作队列会创建一个默认的内核线程也称为工作者线程worker thread

  • 名为events/n ,n为处理器编号
  • 每个处理器对应一个线程,比如0处理器为events/0

你也可以自己新开辟一个线程,一般用默认线程

默认工作者线程API:

步骤函数参数意义
1DECLARE_WORKname,void(func)(void)创建一个名为name的woke_struct,处理函数为func,函数参数为data
2funcvoid *data实现处理函数
3schedule_workstruct work_struct *work将work标记为挂起
4schedule_delayed_workstruct delayed_work *work,unsigned long delay延时delay后将work标记为挂起
5flush_scheduled_workvoid等待工作队列项全部执行完毕,期间休眠状态
6cancel_delayed_workstruct delayed_work *work取消延时的work

自创线程API:

步骤函数参数意义
1create_workqueueconst char *name
2INIT_WORKstruct work_struct *work,void(func)(void)赋值一个work,处理函数为func,函数参数为work_struct中的data
3INIT_DELAYED_WORKstruct delayed_work *work, void(func)(void)
4queue_workstruct workqueue_struct*wq,struct work_struct *work将work在wq中标记为挂起
5queue_delayed_workstruct workqueue_struct*wq,struct delayed_work *work,unsigned long delay延时delay后将work在wq标记为挂起
6flush_workqueuestruct workqueue_struct*wq等待wq工作项全部执行完毕,期间休眠状态
7destroy_workqueuestruct workqueue_struct*wq释放创建的工作队列

下半部机制的抉择

软中断、tasklet、工作队列三者如何选择

软中断、tasklet基本相近,tasklet基于软中断实现,如果对并发有要求可以使用软中断,否则一般而言都使用接口简单的tasklet

工作队列不同与两者,其使用内核线程完成,工作在进程上下文,最大的特点就是可以睡眠,但是开销较大

所以总结而言

  • 需要睡眠选工作队列
  • 不需要睡眠但是需要并发选软中断
  • 不需要睡眠也不需要并发选tasklet
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值