软中断amp;taskletamp;工作队列
軟中斷
軟中斷的分配時靜態的(即在編譯時定義),而tasklet的分配和初始化能夠在執行時進行。
軟中斷(即便是同一種類型的軟中斷)能夠并發地運行在多個CPU上。
因此,軟中斷是可重入函數并且必須明白地使用自旋鎖保護其數據結構。
tasklet不必操心這些問題。由于內核對tasklet的運行進行了更加嚴格的控制。
同樣類型的tasklet總是被串行運行。
換句話說就是:不能在兩個CPU上同一時候執行同樣類型的tasklet。可是,類型不同的tasklet能夠在幾個CPU上并發執行。
tasklet的串行化使tasklet函數不必是可重入的
軟中斷 ? ? ? ? ? 下標 ? ?說明
HI_SOFTIRQ ? ? ?0 ? ? ?處理高優先級的tasklet
TIMER_SOFTIRQ ? 1 ? ? ?和時鐘中斷相關的tasklet
NET_TX_SOFTIRQ ?2 ? ? ?把數據包傳送到網卡
NET_RX_SOFTIRQ ?3 ? ? ?從網卡接收數據包
SCSI_SOFTIRQ ? ?4 ? ? ?SCSI命令的后臺中斷處理
TASKLET_SOFTIRQ 5 ? ? ?處理常規tasklet
thread_info->preempt_count
preempt_count字段
位 ? ? ? ? ? ? ?描寫敘述
0~7 ? ? ? ? ? ? 搶占計數器(max value=255)
8~15 ? ? ? ? ? ?軟中斷計數器(max value=255)
16~27 ? ? ? ? ? 硬中斷計數器(max value=255)
28 ? ? ? ? ? ? ?PREEMPT_ACTIVE標志
第一個計數器記錄顯式禁用本地CPU內核搶占的次數。值等于0表示同意內核搶占。
第二個計數器表示可延遲函數被禁用的程度(值為0表示可延遲函數處于激活狀態)。
第三個計數器表示在本地CPU上中斷處理程序的嵌套數。
處理軟中斷
open_softirq() ? ? 函數處理軟中斷的初始化
raise_softirq() ? ?函數用來激活軟中斷
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(); }void raise_softirq(unsigned int nr) {unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags); }
raise_softirq()函數運行以下的操作:
1、運行local_irq_save宏以保存eflags寄存器IF標志狀態并禁用本地CPU上的中斷。
2、把軟中斷標記為掛起狀態。這是通過設置本地CPU的軟中斷掩碼中與下標nr相關的位來實現的
3、假設in_interrupt()產生為1的值,則跳轉到5。
這樣的情況說明:要么已經在中斷上下文中調用了raise_softirq()。要么當前禁用了軟中斷。
4、否則,就在須要的時候去調用wakeup_softirqd()以喚醒本地CPU的ksoftirqd內核線程。
5、運行local_irq_restore宏。恢復在第1步保存的IF標志的狀態。
在下面幾種情況周期性地檢查是否有軟中斷要運行:
a、當內核調用local_bh_enable()函數激活本地CPU的軟中斷時
b、當do_IRQ()完畢了I/O中斷的處理時或調用irq_exit()宏時
c、假設系統使用I/O APIC。則當smp_apic_timer_interrupt()函數處理完本地定時器中斷時
d、在多處理器系統中。當CPU處理完被CALL_FUNCTION_VECTOR處理器間中斷所觸發的函數時
e、當一個特殊的ksoftirqd/n內核線程被喚醒時
ksoftirqd內核線程
每一個ksoftirqd/n內核線程都執行ksoftirqd()函數,該函數實際上執行下列的循環:
static int ksoftirqd(void * __bind_cpu) {set_user_nice(current, 19);current->flags |= PF_NOFREEZE;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();if (cpu_is_offline((long)__bind_cpu))goto wait_to_die;do_softirq();preempt_enable();cond_resched();}set_current_state(TASK_INTERRUPTIBLE);}__set_current_state(TASK_RUNNING);return 0;wait_to_die:preempt_enable();/* Wait for kthread_stop */set_current_state(TASK_INTERRUPTIBLE);while (!kthread_should_stop()) {schedule();set_current_state(TASK_INTERRUPTIBLE);}__set_current_state(TASK_RUNNING);return 0; } 當內核線程被喚醒時,就檢查local_softirq_pending()中的軟中斷位掩碼并在必要時調用do_softirq()。假設沒有掛起的軟中斷。函數把當前進程狀態置為TASK_INTERRUPTIBLE,隨后。假設當前進程須要(當前thread_info的TIF_NEED_RESCHED標志被設置)就調用con_resched()函數來實現進程切換。
tasklet
tasklet是I/O驅動程序中實現可延遲函數的首選方法。
tasklet建立在兩個叫做HI_SOFTIRQ和TASKLET_SOFTIRQ的軟中斷之上。
幾個tasklet能夠與同一個軟中斷相關聯,每一個tasklet運行自己的函數。
struct tasklet_struct {struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data; }; struct tasklet_head {struct tasklet_struct *head;struct tasklet_struct **tail; }; static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);tasklet描寫敘述符的字段
next ? ? 指向鏈表中下一個描寫敘述符的指針
state ? ?tasklet的狀態
count ? ?鎖計數器
func ? ? 指向tasklet函數的指針
data ? ? 一個無符號長整數。能夠由tasklet函數使用
tasklet描寫敘述的state字段含有兩個標志:
TASKLET_STATE_SCHED
該標志被設置時,表示tasklet是掛起的。也意味著tasklet描寫敘述符被插入到tasklet_vec和tasklet_hi_vec數組的當中一個鏈表
TASKLET_STATE_RUN
該標志被設置時。表示tasklet正在被執行;在單處理器系統上不使用這個標志,由于沒有必要檢查特定的tasklet是否在執行
為了激活tasklet,依據tasklet的優先級,調用tasklet_schedule()或tasklet_hi_schedule()函數。
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(); }void __tasklet_schedule(struct tasklet_struct *t) {unsigned long flags;local_irq_save(flags);t->next = NULL;*__get_cpu_var(tasklet_vec).tail = t;__get_cpu_var(tasklet_vec).tail = &(t->next);raise_softirq_irqoff(TASKLET_SOFTIRQ);local_irq_restore(flags); } static inline void tasklet_schedule(struct tasklet_struct *t) {if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))__tasklet_schedule(t); } 這兩個函數很類似,當中每一個運行下列操作:
1、檢查TASKLET_STATE_SCHED標志;假設設置則返回
2、調用local_irq_save保存IF標志的狀態并禁用本地中斷
3、在tasklet_vec或者tasklet_hi_vec指向的鏈表的起始處添加tasklet描寫敘述符
4、調用raise_softirq_irqoff()激活TASKLET_SOFTIRQ或HI_SOFTIRQ類型的軟中斷
5、調用local_irq_restore恢復IF標志的狀態
工作隊列
可延遲函數和工作隊列很相似。它們的差別在于:可延遲函數執行在中斷上下文,而工作隊列中的函數執行在進程上下文中。執行可堵塞函數(比如:須要訪問磁盤數據塊的函數)的唯一方式是在進程上下文中執行。由于,在中斷上下文中不可能發生進程切換。可延遲函數和工作隊列中的函數都不能訪問進程的用戶態地址空間。
struct cpu_workqueue_struct {spinlock_t lock;long remove_sequence; /* Least-recently added (next to run) */long insert_sequence; /* Next to add */struct list_head worklist;wait_queue_head_t more_work;wait_queue_head_t work_done;struct workqueue_struct *wq;task_t *thread;int run_depth; /* Detect run_workqueue() recursion depth */ } ____cacheline_aligned;/** The externally visible workqueue abstraction is an array of* per-CPU workqueues:*/ struct workqueue_struct {struct cpu_workqueue_struct cpu_wq[NR_CPUS];const char *name;struct list_head list; /* Empty if single thread */ };cpu_workqueue_struct類型的描寫敘述符,字段描寫敘述例如以下:
lock ? ? ? ? ? ? 保護該數據結構的自旋鎖
remove_sequence ?flush_workqueue()使用的序列號
insert_sequence ?flush_workqueue()使用的序列號
worklist ? ? ? ? 掛起鏈表的頭節點
more_work ? ? ? ?等待隊列。當中的工作者線程因等待很多其它的工作而處于睡眠狀態
work_done ? ? ? ?等待隊列,當中的進程因為等待工作隊列被刷新而處于睡眠狀態
wq ? ? ? ? ? ? ? 指向workqueue_struct結構的指針,當中包括該描寫敘述符
thread ? ? ? ? ? 指向結構中工作線程的進程描寫敘述符指針
run_depth ? ? ? ?run_workqueue()當前的運行深度
struct work_struct {unsigned long pending;struct list_head entry;void (*func)(void *);void *data;void *wq_data;struct timer_list timer; };pending ? ? ? 假設函數已經在工作隊列鏈表中。該字段值設為1,否則設為0
entry ? ? ? ? 指向掛起函數鏈表前一個或后一個元素的指針
func ? ? ? ? ?掛起函數的地址
data ? ? ? ? ?傳遞給掛起函數的參數,是一個指針
wq_data ? ? ? 一般是指向cpu_workqueue_struct描寫敘述符的父節點的指針
timer ? ? ? ? 用于延遲掛起函數運行的軟定時器
工作隊列函數
create_workqueue() ?函數接收一個字符串作為參數,返回新創建工作隊列的workqueue_struct描寫敘述符的地址。該函數還創建n個工作者線程,并根 ? ? ? ? ? ? ? ? ? ? 據傳遞給函數的字符串為工作者線程命名,如foo/0,foo/1等等
destory_workqueue() 函數撤銷工作隊列
queue_work() ? ? ? ?把函數插入工作隊列,該函數主要運行以下步驟:
1、檢查要插入的函數是否已經在工作隊列中,假設是就結束
2、把work_struct描寫敘述符加到工作隊列鏈表中。然后把work->pending置為1
3、假設工作者線程在本地CPU的cpu_workqueue_struct描寫敘述符的more_work等待隊列上睡眠。該函數喚醒這個線程
每一個工作線程在worker_thread()函數內部不斷地運行循環操作,因而。線程在絕大多數時間里處于睡眠狀態并等待某些工作被插入隊列。
工作線程一旦被喚醒就調用run_workqueue()函數,該函數從工作者線程的工作隊列鏈表中刪除全部work_struct描寫敘述符并運行對應的掛起函數。
因為工作隊列函數能夠堵塞。因此,能夠讓工作者線程睡眠,甚至能夠讓它遷移到還有一個CPU上恢復運行
flush_workqueue()函數接收workqueue_struct描寫敘述符的地址,而且在工作隊列中的全部掛起函數結束之前使調用進程一直處于堵塞狀態。
總結
以上是生活随笔為你收集整理的软中断amp;taskletamp;工作队列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker 容器中“TERM envi
- 下一篇: hdoj1428 -- 漫步校园 (记忆