积极参与网站信息建设工作,个人网页代码html个人网页完整代码,access 可以做网站不,湖南seo优化公司文章目录中断概述Linux内核中断软中断相关代码解析软中断结构体软中断类型软中断两种触发方式函数__do_softirq解析定时器的软中断实现解析定时器相关代码总结Linux版本#xff1a;linux-3.18.24.x
中断概述
中断要求 快进快出#xff0c;提高执行效率#xff0c;…
文章目录中断概述Linux内核中断软中断相关代码解析软中断结构体软中断类型软中断两种触发方式函数__do_softirq解析定时器的软中断实现解析定时器相关代码总结Linux版本linux-3.18.24.x
中断概述
中断要求 快进快出提高执行效率尽量少做和硬件相关或数据处理等耗时繁琐的操作。
中断注意事项 中断会关闭调度中断优先级高于任何任务的优先级。长时间中断处理会影响系统的效率继而影响其他系统任务进行系统任务超时引起不可预料的后果。 中断处理程序中不可以睡眠不可以使用引起睡眠的函数如ssleep、msleep、kmalloc、copy_to_user、copy_from_user也不能使用可以引起睡眠的锁metux锁可以使用自旋锁。 Linux内核中断
Linux中断 中有两个“术语”“中断上半部” “中断下半部”又称 硬中断软中断
中断上半部分硬中断特征
对时间要求高与硬件相关不能被中断打断这时会在所有处理器上屏蔽当前中断线如果这个中断处理是SA_INTERRUPT标记的那么所有的本地中断都会全局的被屏蔽掉。
结合串口接收数据中断程序来了解硬中断涉及函数static irqreturn_t imx_rxint(int irq, void *dev_id)
imx_rxint- spin_lock_irqsave(sport-port.lock, flags); // 保存中断状态禁止本地中断并获取自旋锁- while (readl(sport-port.membase USR2) USR2_RDR) { // 读取寄存器数据sport-port.icount.rx; // 记录接收数量}- rx sport-port.read_status_mask; // 判断接收数据是否正常if (rx URXD_BRK)flg TTY_BREAK;else if (rx URXD_PRERR)flg TTY_PARITY;else if (rx URXD_FRMERR)flg TTY_FRAME;if (rx URXD_OVRRUN)flg TTY_OVERRUN;- tty_insert_flip_char(port, rx, flg); // 数据放入tty中- spin_unlock_irqrestore(sport-port.lock, flags); // 恢复中断状态启动本地中断释放自旋锁- tty_flip_buffer_push(port); // 将终端翻转缓冲区的推入队列到行规则通知tty有数据来了可以看出串口中断中只有数据读取和校验没有过多的繁琐操作。剩下的数据处理之类的操作在队列里进行。有关串口数据的来龙去脉可看TTY-UART驱动框架解析、 UART用户层与驱动层调用关系解析I、UART用户层与驱动层调用关系解析 (II)、UART用户层与驱动层调用关系解析 (III)
中断下半部分软中断特征
会恢复响应所有中断对时间要求不高可以延迟执行可以被其他中断打断
这就使系统处于中断屏蔽状态的时间尽可能的短了中断响应能力自然也就高了。 中断下半部分是需要系统的软中断来保证的。软中断也叫可延迟函数tasklet就是软中断基础上实现的。 软中断相关代码解析
软中断结构体
软中断使用struct softirq_action结构体管理软件中断的注册和激活操作
struct softirq_action
{void (*action)(struct softirq_action *);
};软中断类型
软中断类型提供10中软中断当然软中断允许用户添加但是内核开发者希望用户尽可能使用基于软中断的tasklet形式实现
// include/linux/interrupt.h
enum
{HI_SOFTIRQ0,/* 优先级高的tasklets */TIMER_SOFTIRQ,/* 定时器的下半部 */NET_TX_SOFTIRQ,/* 发送网络数据包 */NET_RX_SOFTIRQ,/* 接收网络数据包 */BLOCK_SOFTIRQ,/* BLOCK装置 */BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,/* 正常优先级的tasklets */SCHED_SOFTIRQ,/* 调度程序 */HRTIMER_SOFTIRQ,/* 高分辨率定时器 */RCU_SOFTIRQ, /* RCU锁定 */NR_SOFTIRQS /* 10 */
};以上的软中断都会有一个对应的回调函数保存在…softirq_vec
// kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;const char * const softirq_to_name[NR_SOFTIRQS] {HI, TIMER, NET_TX, NET_RX, BLOCK, BLOCK_IOPOLL,TASKLET, SCHED, HRTIMER, RCU
};void open_softirq(int nr, void (*action)(struct softirq_action *))
{softirq_vec[nr].action action;
}软中断两种触发方式
方式一 调用apiraise_softirq
定时器中断处理函数中调用该函数启动定时器软中断
// kernel/softirq.c
void raise_softirq(unsigned int nr)
{unsigned long flags;local_irq_save(flags);- arch_local_irq_restore(ARCH_IRQ_DISABLED); // 关闭本地CPU中断raise_softirq_irqoff(nr); // 修改软中断标志位local_irq_restore(flags);
}inline void raise_softirq_irqoff(unsigned int nr)
{__raise_softirq_irqoff(nr);- or_softirq_pending(1UL nr);- this_cpu_or(irq_stat.__softirq_pending, (x)) // 保存触发软中断标志位if (!in_interrupt()) // 判断系统当前是否在中断里wakeup_softirqd(); // 如果不在中断唤醒软中断线程。在线程里执行软中断的回调。如果在中断里什么都不做等中断退出再处理
}__softirq_pending标志保存每个软中断当前状态。__softirq_pending字段中的每一个bit对应着某一个软中断某个bit被置位说明有相应的软中断等待处理。每个CPU都有一份表用来记录。这个表的定义
typedef struct {unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);内核的其它代码主动调用raise_softirq。若这时正好不是在中断上下文中线程将被唤醒。 线程最后也要调用__do_softirq线程函数run_ksoftirqd函数。
static void run_ksoftirqd(unsigned int cpu)
{local_irq_disable();if (local_softirq_pending()) {__do_softirq(); // 最终调用__do_softirqlocal_irq_enable();cond_resched();preempt_disable();rcu_note_context_switch(cpu);preempt_enable();return;}local_irq_enable();
}static struct smp_hotplug_thread softirq_threads {.store ksoftirqd,.thread_should_run ksoftirqd_should_run,.thread_fn run_ksoftirqd, // 唤醒线程执行回调函数.thread_comm ksoftirqd/%u,
};static __init int spawn_ksoftirqd(void)
{register_cpu_notifier(cpu_nfb);BUG_ON(smpboot_register_percpu_thread(softirq_threads)); // 注册并创建线程return 0;
}
early_initcall(spawn_ksoftirqd); // 系统启动后自动执行方法二 在irq_exit执行中执行回调函数
void irq_exit(void)
{
...preempt_count_sub(HARDIRQ_OFFSET);if (!in_interrupt() local_softirq_pending()) // 保证不在中断里并且通过local_softirq_pending判断当前CPU有无等待的软中断invoke_softirq(); // 如果满足以上条件最终执行__do_softirq();
...
}函数__do_softirq解析
可以看出以上两种触发方式最终都会调用到__do_softirq函数来看下在这个函数里的操作…
asmlinkage __visible void __do_softirq(void)
{...pending local_softirq_pending(); // 取出pending的状态__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); // 禁止软中断主要是为了防止和软中断守护进程发生竞争restart:set_softirq_pending(0); // 清除所有的软中断待决标志local_irq_enable(); // 打开本地CPU中断// 循环执行待决软中断的回调函数h softirq_vec;while ((softirq_bit ffs(pending))) {unsigned int vec_nr;int prev_count;h softirq_bit - 1;vec_nr h - softirq_vec;prev_count preempt_count();kstat_incr_softirqs_this_cpu(vec_nr);trace_softirq_entry(vec_nr);h-action(h); // 处理回调函数这函数就是最开始提到软中断表中对应函数trace_softirq_exit(vec_nr);if (unlikely(prev_count ! preempt_count())) {pr_err(huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n,vec_nr, softirq_to_name[vec_nr], h-action,prev_count, preempt_count());preempt_count_set(prev_count);}h;pending softirq_bit;}rcu_bh_qs();local_irq_disable();// 如果上面的循环完毕发现新的软中断被触发pending local_softirq_pending();if (pending) {if (time_before(jiffies, end) !need_resched() --max_restart)goto restart; // 重新启动循环wakeup_softirqd(); // 如果满足没有新的软中断产生或者循环max_restart10唤醒守护进程剩下交给守护进程}lockdep_softirq_end(in_hardirq);account_irq_exit_time(current);__local_bh_enable(SOFTIRQ_OFFSET); // 退出前恢复软中断WARN_ON_ONCE(in_interrupt());tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}定时器的软中断实现解析
linux 定时器是依赖于TIMER_SOFTIRQ软中断实现的。 当系统硬件中断退出后软件会遍历中断的使能位并执行相关软中断回调函数。
内核启动开始中有软中断的初始化
// init/main.c
start_kernel- init_timers(); // 初始化定时器- open_softirq(TIMER_SOFTIRQ, run_timer_softirq); // 开启定时器软中断当定时器中断触发软中断后会通过__do_softirq函数调用run_timer_softirq函数
static void run_timer_softirq(struct softirq_action *h)
{struct tvec_base *base __this_cpu_read(tvec_bases); // 获取本地CPU参数该中断只在这个CPU有效hrtimer_run_pending();if (time_after_eq(jiffies, base-timer_jiffies))__run_timers(base);- base-running_timer timer; // 找出超时的定时器timer赋给base-running_timer- detach_expired_timer(timer, base); // 删除这个定时器...- spin_unlock(base-lock);- call_timer_fn(timer, fn, data); // 调用回调函数- spin_lock(base-lock);...
}定时器通过周期tick的事件处理程序触发软中断这里周期tick事件涉及到clockeven后续文章详细解析内核代码
// kernel/time/tick-common.c
tick_handle_periodic // 周期tick的事件处理程序- tick_periodic(cpu);- update_process_times(user_mode(get_irq_regs()));- run_local_timers();- raise_softirq(TIMER_SOFTIRQ); // 触发软中断定时器相关代码
上述提到在软中断中会从 struct tvec_base *base 处理超时的定时器并执行对应的处理函数。这里介绍下定时器函数如何添加到base中的。
// kernel/time/timer.c
// 添加定时器 添加定时器是将一个定时器挂到所在链表中
void add_timer(struct timer_list *timer)
{BUG_ON(timer_pending(timer));mod_timer(timer, timer-expires);- __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}
EXPORT_SYMBOL(add_timer);static inline int
__mod_timer(struct timer_list *timer, unsigned long expires,bool pending_only, int pinned)
{struct tvec_base *base, *new_base;unsigned long flags;int ret 0 , cpu;timer_stats_timer_set_start_info(timer);BUG_ON(!timer-function);// 获取tvec_base变量base为PerCPU变量给base加锁base lock_timer_base(timer, flags);// 判断定时器是否存在。如果timer已经存在则删除此处的操作是在加锁状态先的因此和softirq里面不会重入ret detach_if_pending(timer, base, false);if (!ret pending_only)goto out_unlock;debug_activate(timer, expires);cpu get_nohz_timer_target(pinned);new_base per_cpu(tvec_bases, cpu);// 判断定时器是否属于当前CPUif (base ! new_base) {// 不能修改一个正在运行的timer的base否则del_timer_sync会出问题if (likely(base-running_timer ! timer)) {/* See the comment in lock_timer_base() */timer_set_base(timer, NULL);spin_unlock(base-lock);base new_base;spin_lock(base-lock);timer_set_base(timer, base);}}timer-expires expires;// 将timer加入本地CPU base中internal_add_timer(base, timer);out_unlock:spin_unlock_irqrestore(base-lock, flags); // 释放锁恢复中断return ret;
}小结 1、同一个timer只能被提交一次,但是同一个timer是有可能在不同的cpu上同时运行的。 2、CPU0注册一个timer并已经在CPU0上运行了CPU1也注册了这个timer因为运行的timer已经在链表上删除了且没有获取base-lock因此lock_timer_base和timer_pending都可以向下执行并注册成功。
// 删除定时器
int del_timer(struct timer_list *timer)
{struct tvec_base *base;unsigned long flags;int ret 0;debug_assert_init(timer);timer_stats_timer_clear_start_info(timer);if (timer_pending(timer)) { // 判断一个定时器是否存在在链表中但是此定时器有可能正在运行base lock_timer_base(timer, flags);ret detach_if_pending(timer, base, true);- detach_timer(timer, clear_pending); // 删除spin_unlock_irqrestore(base-lock, flags);}return ret;
}
EXPORT_SYMBOL(del_timer);del_timer_sync和mod_timer函数实现目的是一样的。
// 判断当前定时器是否在其他CPU上运行如果运行等待其运行完成后删除此定时器
int del_timer_sync(struct timer_list *timer)
{...for (;;) {int ret try_to_del_timer_sync(timer);- base lock_timer_base(timer, flags);if (base-running_timer ! timer) { //判断当前timer是否在运行如果在运行则等待timer_stats_timer_clear_start_info(timer);ret detach_if_pending(timer, base, true); // 删除成功返回1}- spin_unlock_irqrestore(base-lock, flags);if (ret 0)return ret;cpu_relax();}
}
EXPORT_SYMBOL(del_timer_sync);总结
1、软中断是一组静态定义的下半部接口可以在所有处理器上同时执行即使两个类型相同也可以。但一个软中断不会抢占另一个软中断唯一可以抢占软中断的是硬中断。
2、软中断是指令触发产生的而硬中断是由外设引发的。
3、硬中断处理程序要确保它能快速地完成任务这样程序执行时才不会等待较长时间称为上半部。
4、软中断处理硬中断未完成的工作是一种推后执行的机制属于下半部。
5、硬中断和软中断都可以抢占或者称为中断异常最典型的是系统调用但是异常不能抢占硬中断和软中断。
6、硬中断和软中断只要是中断上下文执行的时候都不允许内核抢占换句话说中断上下文中永远不允许进程切换。在执行过程中不能休眠不能阻塞一旦休眠或者阻塞则系统直接挂死。