参考链接:https://zhuanlan.zhihu.com/p/527405217
中断上半部::硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境。完成整个中断处理任务中的一小部分
中断下半部:执行在内核空间的中断服务程序。计算时间比较长的数据处理,但是什么样的任务应该放到下半部来执行,需要开发者决定。
那下半部具体在什么时候执行呢?
这个没有确定的时间点,一般是从硬件中断返回后的某一个时间点内会被执行。下半部执行的关键点是允许响应所有的中断,是一个开中断的环境。 下半部的中断常见的包括软中断(不可睡眠)
、tasklet(不可睡眠)
、工作队列(可睡眠)
。
软中断:
目前驱动中只有块设备和网络子系统使用了软中断,我们使用一般用tasklet机制。enum { HI_SOFTIRQ=0, //优先级为0,是最高优先级的软中断类型 TIMER_SOFTIRQ, //优先级为1,用于定时器的软中断 NET_TX_SOFTIRQ, //优先就为2,用于发送网络数据包的软中断 NET_RX_SOFTIRQ, //优先级为3,用于接收网络数据包的软中断 BLOCK_SOFTIRQ, //优先级为4,用于块设备的软中断 IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ, //优先级为6,专门为tasklet机制准备的软中断 SCHED_SOFTIRQ, //优先级为7,进程调度以及负载均衡 HRTIMER_SOFTIRQ, //优先级为8,高精度定时器 RCU_SOFTIRQ, //优先级为9,专门为RCU服务的软中断 NR_SOFTIRQS };
struct softirq_action 用来描述软中断;软中断的描述符数组:
softirq_desc[];
void open_softirq(int nr, void (*action)(struct softirq_action *))
tasklet:
tasklet
由tasklet_struct
数据结构来描述,是软中断的一种:
struct tasklet_struct { struct tasklet_struct *next; //多个tasklet串成一个链表
unsigned long state; //有两个值。TASKLET_STATE_SCHER表示tasklet已经被调度,TASKLET_STATE_RUN表示tasklet正在运行中
atomic_t count; //为0表示tasklet处于激活状态;不为0表示tasklet被禁止,不允许执行
void (*func)(unsigned long); //tasklet处理程序
unsigned long data; //传递参数给tasklet处理函数
}; enum { TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */ TASKLET_STATE_RUN /* Tasklet is running (SMP only) */ };
tasklet的使用:
一次tasklet_schedule()后不会马上执行,要等到软中断被执行时才有机会运行tasklet。 tasklet挂入到哪个CPU的tasklet_vet链表,那么就由该CPU的软中断来执行。(每个CPU会维护两个tasklet链表,一个普通优先级的tasklet_vec,另一个用于高优先级的tasklet_hi_vec)
1.先定义tasklet:
a.静态定义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 }
//DECLARE_TASKLET把count初始化为0,表示tasklet处于激活状态,而DECLARE_TASKLET_DISABLED相反,把count初始化为1,表示tasklet处于关闭状态
b.动态定义tasklet:
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; } EXPORT_SYMBOL(tasklet_init);
2.调度tasklet
static inline void tasklet_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); }
//设置tasklet_struct->state成员为TASKLET_STATE_SCHED标志位,返回旧的state的值,如果返回true,那么说明这个tasklet已经在tasklet链表中,不需要再重新挂入,如果返回false,说明这个tasklet还没挂入到tasklet链表中,使用__tasklet_schedule函数把该tasklet挂入链表中。
实例:
//request_threaded_irq(irq, handler, NULL, flags, name, dev); rv = request_irq(SGI_UART_VECTOR, scdrv_event_interrupt, IRQF_SHARED, "system controller events", event_sd); //中断SGI_UART_VECTOR,当这个硬件中断发生时,就会调用到上半部的中断处理函数,scdrv_event_interrupt ...... static irqreturn_t scdrv_event_interrupt(int irq, void *subch_data) { struct subch_data_s *sd = subch_data; unsigned long flags; int status; spin_lock_irqsave(&sd->sd_rlock, flags); status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch); if ((status > 0) && (status & SAL_IROUTER_INTR_RECV)) { tasklet_schedule(&sn_sysctl_event); } spin_unlock_irqrestore(&sd->sd_rlock, flags); return IRQ_HANDLED; } DECLARE_TASKLET(sn_sysctl_event, scdrv_event, 0);//在该上半部的处理函数中,会调用到tasklet_schedule函数来执行下半部的操作,这个tasklet最终回调到的函数是scdrv_event()函数。tasklet是串行执行的。一个tasklet在tasklet_schedule()时会绑定某个CPU的tasklet_vec链表,它必须要在改CPU上执行完tasklet的回调函数才会和该CPU松绑
这是为什么?
0 static __latent_entropy void tasklet_action(struct softirq_action *a) 1 { 2 struct tasklet_struct *list; 3 unsigned long long ts; 4 5 local_irq_disable(); 6 list = __this_cpu_read(tasklet_vec.head); 7 __this_cpu_write(tasklet_vec.head, NULL); 8 __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head)); 9 local_irq_enable(); 10 11 while (list) { 12 struct tasklet_struct *t = list; 13 14 list = list->next; 15 16 if (tasklet_trylock(t)) { 17 if (!atomic_read(&t->count)) { 18 if (!test_and_clear_bit(TASKLET_STATE_SCHED, 19 &t->state)) 20 BUG(); 21 check_start_time(ts); 22 t->func(t->data); 23 check_process_time("tasklet %ps", ts, t->func); 24 tasklet_unlock(t); 25 continue; 26 } 27 tasklet_unlock(t); 28 } 29 30 local_irq_disable(); 31 t->next = NULL; 32 *__this_cpu_read(tasklet_vec.tail) = t; 33 __this_cpu_write(tasklet_vec.tail, &(t->next)); 34 __raise_softirq_irqoff(TASKLET_SOFTIRQ); 35 local_irq_enable(); 36 } 37 }
tasklet_schedule去调度一个tasklet的时候,就会进入到tasklet_action的处理函数。注意看16~28行,tasklet_trylock()函数是一个锁,如果目前访问的tasklet已经处于RUNNING状态,也就是它被设置了TASKLET_STATE_RUN的标志位,那么tasklet_trylock是会返回一个false,表示这个tasklet已经被其他CPU调度,正处于执行状态, 那么这一轮的tasklet就会跳过该tasklet。这样做的目的就是为了保证同一个tasklet只能在一个CPU上运行。
我们假设正常情况下,是(a)设备A首先触发了硬件中断,然后调用了snsc_event.c中的中断处理函数,然后它也(b)正常进入了tasklet的软中断处理中,在CPU0上处理,但是它还在处理下半部的时候,(c)突然设备B也产生了一个中断,那么(d)CPU0就会暂停tasklet的处理,转去执行设备B的硬件中断处理。如果这时候,(e)设备A又再次发生了中断,因为CPU0正忙着,所以中断控制器中的CPU Interface模块(f)把这个中断请求发送给了CPU1,假设CPU1很快处理完了硬件中断并开始处理该tasklet,发现tasklet_schedule()函数中发现并没有设置TASKLET_STATE_SCHED标志位(因为在CPU0执行tasklet回调函数的时候,就已经把这个标志位清楚了),所以CPU1会认为这个tasklet是一个新的tasklet,然后(g)CPU1把这个tasklet加入到自己的tasklet_vec链表中,然后等到它tasklet_trylock执行到的时候,发现(h)拿不到锁(因为CPU0中这个tasklet还没处理完,还没释放锁呢),所以CPU1就跳过了这次处理,得等到CPU0处理完第一个tasklet的时候,CPU1下一次软中断执行才会继续执行该tasklet。
进程上下文
(1)进程上文:其是指进程由用户态切换到内核态是需要保存用户态时cpu寄存器中的值,进程状态以及堆栈上的内容,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
(2)进程下文:其是指切换到内核态后执行的程序,即进程运行在内核空间的部分。
软中断和tasklet:都是运行在中断上下文中,拥有比较高的优先级
工作队列机制: 把work交给一个内核线程来执行,它总是运行在进程上下文中
工作队列利用进程上下文来执行中断下半部操作,因此工作队列允许重新调度和睡眠。工作队列没有软中断和tasklet那么霸道,CPU不用每次都得先服务完它再去服务其他进程了。
工作队列的使用
我们虽然可以自己创建新的工作队列,但是在Linux内核中,它推荐驱动开发者使用默认的workqueue,而不是新创建workqueue,要使用系统默认的workqueue,首先需要初始化一个work,内核提供了相应的宏 INIT_WORK()
标签:优先级,struct,中断,TASKLET,tasklet,机制,上下文,data From: https://www.cnblogs.com/yyg-cn/p/17165827.html