您的位置:首页 > 运维架构 > Linux

Linux内核学习笔记五——中断推后处理机制

2016-04-15 15:26 681 查看


一 中断

硬件通过中断与操作系统进行通信,通过对硬件驱动程序处注册中断处理程序,快速响应硬件的中断。

硬件中断优先级很高,打断当前正在执行的程序。有两种情况:

  硬件中断在中断处理程序中处理

  硬件中断延后再进行处理

  这个具体硬件相关,在中断处理程序中处理,打断了当前正在执行的程序;所有中断都将被屏蔽;如果占用时间太长不合适,

造成系统交互性,反应能力都会受到影响。 需要在其中判断平衡:

如果一个任务对时间非常敏感,将其放在中断处理程序中执行;

如果一个人和和硬件相关,将其放在中断处理程序中执行;

如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行;

其余情况考虑延后机制中执行——下半部。


二 中断推后执行机制—— 软中断

软中断是在编译期间静态分配的,在程序执行前将软中断假如到表中。

下面看一下这个过程:

加入软中断类型:

Linux3.5.3代码:

enum
{
HI_SOFTIRQ=0,

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

BLOCK_SOFTIRQ,

BLOCK_IOPOLL_SOFTIRQ,

TASKLET_SOFTIRQ,

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ,

RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS

};


//软中断表:

static struct softirq_action softirq_vec[NR_SOFTIRQS]

//软中断结构体

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


注册软中断处理函数:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
//关联表中对应类型
  softirq_vec[nr].action = action;
}


触发软中断:

void raise_softirq(unsigned int nr)
{
unsigned long flags;

//停止但保存中断标志
local_irq_save(flags);

//将相应软中断挂起状态
raise_softirq_irqoff(nr);

//恢复中断
local_irq_restore(flags);
}


执行软中断:

void irq_exit(void)
{
  invoke_softirq();  //do_softirq();
}

void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;

//获取CPU软中断状态标志位 32位代表最多32个软中断
pending = local_softirq_pending();

restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
do {
//被触发则执行软中断处理程序
if (pending & 1) {
          h->action(h);
}

//下一个软中断
h++;

//下一个软中断状态标志位
pending >>= 1;
} while (pending);

local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;

if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
    __local_bh_enable(SOFTIRQ_OFFSET);
}


软中断的基本结构如下图表示:

    



三 中断推后执行机制——tasklet

软中断中表中有一种类型是:TASKLET_SOFTIRQ

Tasklet就是利用软中断实现中断推后处理机制。通常使用较多的是tasklet而不是软中断。

Tasklet数据结构:

struct tasklet_struct
{
//链表中下一个tasklet
struct tasklet_struct *next;

//tasklet状态
unsigned long state;

//引用计数器
atomic_t count;

//tasklet处理函数
void (*func)(unsigned long);

//处理函数参数
    unsigned long data;

};


state:

enum
{
  TASKLET_STATE_SCHED,   /* Tasklet is scheduled for execution */
  TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};


count:为0允许激活执行

声明tasklet:可以动态或者静态方式

静态:

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }


动态:

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}


  同时需要编写tasklet处理函数。

调度tasklet:

void tasklet_hi_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处理程序:

继续看上面调度tasklet程序执行:

inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
if (!in_interrupt())
wakeup_softirqd();
}

//使用ksoftirqd内核线程来处理
static void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}


Ksoftirqd内核线程:

软中断才被触发频率很高,在处理过程中还会重新触发软中断;执行会导致用户空间进程无法获得处理时间处于饥饿状态;

对重新触发的软中断立即处理,会导致占据处理时间过长;不进行立即处理不合适;

对此解决方法:

  l 只要还有被触发并等待处理和过程中重新触发的软中断的软中断,本次执行就要负责处理;软中断立即处理,用户空间得不到执行时间。

  l 不处理过程中触发的软中断,放到下一个中断执行时机时处理。软中断得不到立即处理,系统空闲时造成不合理;保证用户空间得到执行时间。

两种方式有存在问题,只能在这其中采取这种的方式:

内核使用线程处理软中断,线程优先级较低,可以被抢占;能够保证软中断被处理,也能保证用户空间程序得到执行时间。

每个CPU上有存在这样一个线程:ksoftirqd/0或者ksoftirqd/1……

static __init int spawn_ksoftirqd(void)
{
void *cpu = (void *)(long)smp_processor_id();
int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);
……
return 0;
}

early_initcall(spawn_ksoftirqd);
static int __cpuinit cpu_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
int hotcpu = (unsigned long)hcpu;
struct task_struct *p;

switch (action) {
case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
p = kthread_create_on_node(run_ksoftirqd,
hcpu,
cpu_to_node(hotcpu),
"ksoftirqd/%d", hotcpu);
kthread_bind(p, hotcpu);
per_cpu(ksoftirqd, hotcpu) = p;
break;
……
}



四 中断推后执行机制——工作队列

工作队列(work queue)通过内核线程将中断下半部分程序推后执行到线程中执行,工作队列可以创建线程来处理相应任务。

工作队列创建的线程为工作者线程:worker thread;系统提供默认的线程来处理工作者队列。

  

      


工作者线程数据结构:

struct workqueue_struct {
unsigned int        flags;            /* W: WQ_* flags */
union {
struct cpu_workqueue_struct __percpu       *pcpu;
struct cpu_workqueue_struct         *single;
unsigned long                           v;
} cpu_wq;                          /* I: cwq's */
struct list_head   list;        /* W: list of all workqueues */
……
}


CPU工作队列数据结构:

struct cpu_workqueue_struct {

//每个CPU工作队列信息
struct global_cwq      *gcwq;

//每个CPU工作队列
struct workqueue_struct *wq;
……
};


工作数据结构:

struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;

};


声明工作队列:

静态:

#define DECLARE_WORK(n, f)                             \
struct work_struct n = __WORK_INITIALIZER(n, f)


动态:

#define INIT_WORK(_work, _func)                                   \
do {                                           \
__INIT_WORK((_work), (_func), 0);              \
} while (0)


需要编写工作队列处理函数:

typedef void (*work_func_t)(struct work_struct *work);


调度工作队列:

int schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
int queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
ret = queue_work_on(get_cpu(), wq, work);
}


唤醒工作者队列线程处理。

执行工作者队列处理程序:

static int worker_thread(void *__worker)
{
do {
struct work_struct *work =
list_first_entry(&gcwq->worklist,
struct work_struct, entry);
process_one_work(worker, work);
} while (keep_working(gcwq));

}


可以创建新的工作者队列和线程来处理。

平衡是个很关键的问题!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: