Linux内核软中断

令狐葱dennis 2021-05-04 14:43:33
linux 操作系统 博客园 内核 中断


1 软中断概述

软中断是实现中断下半部的一种手段,与2.5以前版本的下半段机制不同。软中断可以同时运行在不同的CPU上。

1.1 软中断的表示

内核中用结构体softirq_action表示一个软中断。软中断是一组静态定义的接口,有32个。但是内核(2.6.34)中只实现了10个。可用的软中断的个数用NR_SOFTIRQ表示,NR_SOFTIRQ=10,软中断的类型用一个枚举体表示。这里需要注意的是,32个软中断体现在位掩码是unsigned int 类型。

static struct softirq_action softirq_vec[NR_SOFTIRQS] ;
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
};
struct softirq_action
{
void (*action)(struct softirq_action *);
};

2 软中断相关的数据结构

2.1 thread_info的preempt_count字段

preempt_count 是一个32位的int型,共分为5个字段
这里写图片描述
宏in_interrupt检测软中断计数器 硬中断计数器 和 NMI掩码,只要这三个字段任意一个字段不为0,就表示进程处于中断上下文。

#define in_irq() (hardirq_count())
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())

2.2 pending位掩码

每个CPU上都有一个irq_stat结构,irq_stat中的__softirq_pending是一个32位的掩码,为1表示该软中断已经激活,正等待处理。为0表示软中断被禁止。在do_irq中被使用。
内核使用local_softirq_pending得到当前CPU上的位掩码

#define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)
#define set_softirq_pending(x) percpu_write(irq_stat.__softirq_pending, (x))
#define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))
irq_cpustat_t irq_stat[NR_CPUS]
typedef struct {
...
unsigned int __softirq_pending;
...
}irq_cpustat_t;

2.3 软中断栈

进程的内核栈的大小根据编译时选项不同,可以是4K或者8K。如果是8K堆栈,中断,异常和软中断(softirq) 共享这个堆栈。如果选择4K堆栈,则内核堆栈 硬中断堆栈 软中断堆栈各自使用一个4K空间。关于软中断堆栈,后面在软中断处理时再详细说明。

#ifdef CONFIG_4KSTACKS
static DEFINE_PER_CPU_PAGE_ALIGNED(union irq_ctx, softirq_stack);
union irq_ctx {
struct thread_info tinfo;
u32 stack[THREAD_SIZE/sizeof(u32)];
} __attribute__((aligned(PAGE_SIZE)));

3 软中断的初始化

内核使用open_softirq初始化一个软中断,nr是代表软中断类型的常量,action指向一个软中断处理函数

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}

4 软中断的触发(raise softirq)

触发就是将位掩码pending的相应位 置1的过程。内核使用raise_softirq完成触发软中断,nr是要触发的软中断类型。值的注意的是,中断的触发 发生关闭硬中断的情况下。

触发软中断的过程中,如果该进程未处于中断上下文,说明当前进程处于进程上下文中,那么我们直接调用wakeup_softirqd调度ksoftirqd即可。

反之,如果当前处于中断上下文中或软中断被禁止使用,那么就不必调度内核线程,在中断处理后期irq_exit中,会调用invoke_softirq()处理软中断。
实际的工作是交给or_softirq_pending(1UL << (nr)); 完成的,该函数通过位操作将指定为和pending相加。

这里写图片描述

void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
* 如果不在中断上下文中
*/
if (!in_interrupt())
wakeup_softirqd();
}
#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); }

5. 软中断的处理

5.1 处理软中断的时机

1 在do_irq的末期(irq_exit)

如果当前进程没有处于中断上下文中并且本地CPU上还有没有处理的软中断,那么就调用invoke_softirq()处理软中断。

#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq()
#else
# define invoke_softirq() do_softirq()
#endif
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
rcu_irq_exit();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
}
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq()
#else
# define invoke_softirq() do_softirq()
#endif

程序5-1 irq_exit

2 当软中断被重复触发超过10次时,内核会调用wakeup_softirqd()唤醒内核线程ksoftirqd去处理软中断。

5.2 软中断的处理

1 do_softirq

根据内核堆栈大小,有两种do_softirq,一种是通用do_irq,另一种是架构相关的do_irq(arch/x86/kernel/irq_32.c)。

通用的do_irq的工作流程
1 判断当前是否处于硬中断上下文中或者软中断被禁用,如果是那么直接返回
2 保存Eflags 然后关闭本地硬件中断
3 获取本地CPU的位掩码pending 如果有待处理的软中断就调用__do_irq
4 从__do_irq返回恢复Eflags

asmlinkage void do_softirq(void)
{
//用来保存位掩码的局部变量
__u32 pending;
//保存Eflags寄存器的局部变量
unsigned long flags;
//如果do_softirq在中断上下文中被调用 或 软中断被禁止使用 那么不处理软中断
//直接返回
if (in_interrupt())
return;
//将Eflags保存到flags中 然后关硬件中断
local_irq_save(flags);
//获取本地CPU上的位掩码
pending = local_softirq_pending();
if (pending)
__do_softirq();
//将flags
local_irq_restore(flags);
}

x86_32架构下使用4K软中断堆栈的do_softirq处理流程
1 类似于通用的do_softirq,如果在中断上下文中或者软中断被禁止使用就立即返回。然后关外部中断
2 如果本地CPU上存在待处理的软中断就开始对软中断堆栈的处理,关键是令isp指向软中断堆栈的栈底。然后在软中断栈上调用call_on_stack。call_on_stack是一段内联汇编,其主要目的是完成从内核栈到软中断栈的切换。先将esp保存到ebx中,使用call指令跳转到__do_softirq子例程,子例程返回时再恢复esp。

asmlinkage void do_softirq(void)
{
unsigned long flags;
struct thread_info *curctx;
union irq_ctx *irqctx;
u32 *isp;
if (in_interrupt())
return;
local_irq_save(flags);
if (local_softirq_pending()) {
//curctx指向当前进程的thread_info结构
curctx = current_thread_info();
//irqctx包含一个软中断堆和thread_info结构
irqctx = __get_cpu_var(softirq_ctx);
//触发硬中断和软中断是同一个进程所以将threadinfo的task指针统一
irqctx->tinfo.task = curctx->task;
//从内核堆栈切换到软中断堆栈 需要保存内核堆栈的栈指针寄存器内容
irqctx->tinfo.previous_esp = current_stack_pointer;
/* build the stack frame on the softirq stack */
//isp指向软中断栈底
isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
//在软中断堆栈上调用__do_softirq
call_on_stack(__do_softirq, isp);
}
local_irq_restore(flags);
}
static void call_on_stack(void *func, void *stack)
{
//call *%%edi 间接绝对近调用 偏移地址存储在edi寄存器中
//指令执行时 先将eip入栈 然后将edi-->eip
//先交换ebx and esp
//然后调用__do_softirq
//然后将ebx-->esp 恢复xchgl之前的esp
//输出约束将ebx --> stack
asm volatile("xchgl %%ebx,%%esp \n"
"call *%%edi \n"
"movl %%ebx,%%esp \n"
: "=b" (stack)
: "0" (stack),
"D"(func)
: "memory", "cc", "edx", "ecx", "eax");
}

2 __do_softirq

软中断的处理实际上是由 __do_softirq完成,整体的思路是遍历pending,如果某一位不为空表示本地CPU上有待处理的软中断,然出调用软中断的处理函数。
开始处理软中断前,内核要调用__local_bh_disable(通过将preempt_count的软中断计数器加1)关闭下半部。如前所说,处理软中断的时机不止一个,内核要保证在本地CPU上软中断的处理是串行的。
另外在处理软中断的循环结束时,内核还要检测是否有重复触发的软中断。先调用local_softirq_pending()获取位掩码pending,然后根据pending继续处理软中断,不过这种重复处理不能超过10次(MAX_SOFTIRQ_RESTART),一旦超过10次,内核就会唤醒ksoftirqd

#define MAX_SOFTIRQ_RESTART 10
asmlinkage void __do_softirq(void)
{
// softirq_action表示一个软中断
struct softirq_action *h;
// 局部变量pending 保存待处理软中断位图
__u32 pending;
// 软中断的重启次数
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
//获取本地CPU上所有待处理的软中断
pending = local_softirq_pending();
account_system_vtime(current);
//关闭下半部中断
__local_bh_disable((unsigned long)__builtin_return_address(0));
lockdep_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
//pending已经保存了所有带出软中断的状态 所以将pending bitmask clear
set_softirq_pending(0);
// 开中断
local_irq_enable();
//h指向第一类软中断
h = softirq_vec;
do {
//先处理第一类软中断
if (pending & 1) {
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(h - softirq_vec);
trace_softirq_entry(h, softirq_vec);
//调用软中断处理程序
h->action(h);
trace_softirq_exit(h, softirq_vec);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
softirq_to_name[h - softirq_vec],
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
//关闭本地CPU上的硬中断
local_irq_disable();
//获取位掩码
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
//将软中断计数器加1,激活软中断
_local_bh_enable();
}

6 ksoftirqd内核线程

6.1 ksoftirqd

在内核处理类似NET_RX_SOFTIRQ的软中断时,如果有大量等待处理的数据包。就会不断的调用
__raise_softirq_irqoff(NET_RX_SOFTIRQ)重复触发软中断NET_RX_SOFTIRQ。这样做会导致用户进程的” 饥饿问题 “ (长时间无法获得CPU)。
针对这种问题内核使用内核线程ksoftirqd去处理自行触发次数超过10次的软中断。

6.2 ksoftirqd的实现

static int ksoftirqd(void * __bind_cpu)
{
//ksoftirqd的优先级最低(nice = 19)
set_user_nice(current, 19);
//将ksoftirqd设置为不可冻结
current->flags |= PF_NOFREEZE;
//设置ksoftirqd为可中断状态
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
//如果没有待处理的软中断则 调度别的进程
if (!local_softirq_pending())
schedule();
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
//关闭内核抢占
preempt_disable();
//处理软中断
do_softirq();
//开启内核抢占
preempt_enable();
//cond_resched()的目的是提高系统实时性, 主动放弃cpu供优先级更高的任务使用
cond_resched();
}
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}

程序6-1 ksoftirqd主功能函数

out:
local_irq_enable();
return;
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;

程序6-2 net_rx_action

void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __get_cpu_var(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}

程序6-3 wakeup_softirqd

还未解决的问题

set_current_state和 __set_current_state的区别
PF_NOFREEZE
cond_resched()
为什么在do_softirq开始处理软中断时要关闭硬件中断

参考

ULK
http://blog.csdn.net/hardy_2009/article/details/7383729 关于内核栈和中断栈的说明

版权声明
本文为[令狐葱dennis]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/dennis-wong/p/14729418.html

  1. ASP调用SDK微信分享好友、朋友圈
  2. ASP calls SDK wechat to share friends and circle of friends
  3. SpringCloud(六)Bus消息总线
  4. 详解JavaScript中的正则表达式
  5. Springcloud (6) bus message bus
  6. Explain regular expressions in JavaScript
  7. Java 响应式关系数据库连接了解一下
  8. Java14它真的来了, 真是尾气都吃不到了
  9. 视频:使用Docker搭建RabbitMQ环境
  10. Java responsive relational database connection
  11. Java14 it's really coming. I can't eat the exhaust
  12. Video: building rabbitmq environment with docker
  13. SpringCloud(六)Bus消息总线
  14. 详解JavaScript中的正则表达式
  15. Springcloud (6) bus message bus
  16. Explain regular expressions in JavaScript
  17. Docker实战:用docker-compose搭建Laravel开发环境
  18. Docker: building laravel development environment with docker compose
  19. 求助,JAVA如何获取系统当前所有进程
  20. 有人用过JMeter或用HttpUnit写过测试吗????
  21. Help, Java how to get all the current processes of the system
  22. Has anyone ever used JMeter or written tests in httpUnit????
  23. Living in a mountain village in late spring
  24. Partridge day, spring of HKUST
  25. JavaScript异步编程4——Promise错误处理
  26. 海康摄像SDK开发笔记(一):海康威视网络摄像头SDK介绍与模块功能
  27. JavaScript asynchronous programming 4 -- promise error handling
  28. Haikang video SDK development notes (1): introduction and module functions of Hikvision webcam SDK
  29. JOP:用于FPGA的嵌入式实时系统中的Java优化处理器内核
  30. Spring Boot源码:使用MongoDB MongoTemplate公开REST在几分钟内实现CRUD功能
  31. Spring Boot应用程序事件教程 - reflectoring
  32. 带有Resilience4j断路器的Spring云网关 - rome
  33. 经验分享:Apache Kafka的缺点与陷阱 - Emil Koutanov
  34. 通过Spring Boot Webflux实现Reactor Kafka
  35. 从Java 8升级到Java 11应该注意的问题
  36. Jop: Java optimized processor core for FPGA embedded real time system
  37. Spring boot source code: use mongodb mongotemplate to open rest to realize crud function in a few minutes
  38. Spring boot application event tutorial - reflecting
  39. Spring cloud gateway with resilience4j circuit breaker - ROM
  40. Experience sharing: shortcomings and pitfalls of Apache Kafka - Emil koutanov
  41. Realization of reactor Kafka through spring boot Webflux
  42. RPC框架设计----Socket与I/0模型
  43. Problems in upgrading from Java 8 to Java 11
  44. RPC framework design -- socket and I / 0 model
  45. RPC框架设计----I/0模型
  46. RPC framework design: I / 0 model
  47. RPC框架设计----NIO编程缓冲区Buffer
  48. RPC框架设计----NIO编程缓冲区Buffer
  49. RPC framework design -- NiO programming buffer
  50. RPC framework design -- NiO programming buffer
  51. Java多线程基础
  52. Java multithreading Foundation
  53. 码农飞升记-00-Java发展历程
  54. Development history of coder-00-java
  55. 码农飞升记-00-Java发展历程
  56. Development history of coder-00-java
  57. Spring and Autumn Moon
  58. Node.js与Spring Boot比较? - Ryan Gleason
  59. Spring WebFlux的明显陷阱 - ŁukaszKyć
  60. Spring创始人Rod大叔对YAML的真实想法