linux中軟中斷和工作佇列的作用是實作中斷處理。軟中斷和工作隊列是中斷上下部機制中的下半部實現機制。軟中斷不能睡眠、不能阻塞、不能進程間切換,只能被硬體中斷打斷;而工作隊列可以睡眠,也能被阻塞,能夠在不同的進程間切換,以完成不同的工作。
本教學操作環境:linux5.9.8系統、Dell G3電腦。
linux中軟中斷和工作佇列的作用是實作中斷處理。
中斷是指在CPU正常運作期間,由於內部外部事件或由程序預先安排的事件引起的CPU暫時停止正在運行的程序,轉而為該內部或外部事件或預先安排的事件服務的程序中去,服務完畢後再返回去繼續運行被暫時中斷的程序。 Linux中通常分為外部中斷(又稱為硬體中斷)和內部中斷(又叫異常)。
在實位址模式中,CPU把記憶體中從0開始的1KB空間當作一個中斷向量表。表中的每一項佔4個位元組。但在保護模式中,有這4個位元組的表項構成的中斷向量表不滿足實際需求,於是根據反映模式切換的資訊和偏移量的足夠使得中斷向量表的表項由8個位元組組成,而中斷向量表也叫做了中斷描述符表(IDT)。在CPU中增加了一個用來描述中斷描述符表暫存器(IDTR),用來保存中斷描述符表的起始位址。
2.1 系統中斷號碼
由上述中斷定義可知,系統中斷向量表中共可保存256個中斷向量入口,即IDT中包含的256個中斷描述子(對應256個中斷向量)。
而0-31號中斷向量被intel公司保留用來處理例外事件,不能另作它用。對這0-31號中斷向量,作業系統只需提供異常的處理程序,當產生一個異常時,處理機就會自動把控制轉移到相應的處理程序的入口,運行相應的處理程序;而事實上,對於這32個處理異常的中斷向量,2.6版本的Linux只提供了0-17號中斷向量的處理程序,其對應處理程序請參考下表、中斷向量和例外事件對應表;也就是說,17-31號中斷向量是空著未用的。
#
中斷向量號 | #Linux的處理程序 | |
0 | 除法錯誤 | pide_error |
1 | #偵錯例外 | Debug |
2 | NMI中斷 | #Nmi |
##3 | 單字節,int 3 | #Int3 |
溢位 | Overflow | |
邊界監控中斷 | Bounds | |
無效操作碼 | Invalid_op | |
裝置不可用 | Device_not_available | |
#雙故障 | ||
Double_fault | #9 | ##協處理器段溢出 |
Coprocessor_segment_overrun | 10 | 無效TSS |
Incalid_tss | #11 | 缺失片段 |
#Segment_not_present | ||
#12 | 堆疊異常 | |
#Stack_segment | 13 | |
General_protection | 14 |
0-31號中斷向量已被保留,那麼剩下32-255共224個中斷向量可用。這224個中斷向量又是怎麼分配的呢? 2.6版本的Linux中,除了0x80 (SYSCALL_VECTOR)用作系統呼叫總入口之外,其他都用在外部硬體中斷來源上,其中包括可程式中斷控制器8259A的15個irq;事實上,當沒有定義CONFIG_X86_IO_APIC時,其他223(除0x80外)個中斷向量,只利用了從32號開始的15個,其它208個空著未用。
2.2 中斷請求
2.2.1 中斷請求概述
外部裝置當需要作業系統做相關的事情的時候,會產生對應的中斷。
設備透過對應的中斷線向中斷控制器發送高電平以產生中斷訊號,而作業系統則會從中斷控制器的狀態位元取得那根中斷線上產生的中斷。而且只有在設備在對某一條中斷線擁有控制權,才可以向這條中斷線上發送訊號。也由於現在的周邊越來越多,中斷線又是很寶貴的資源不可能被一一對應。因此在使用中斷線前,就得對對應的中斷線進行申請。無論採用共享中斷方式或獨佔一個中斷,申請過程都是先講所有的中斷線進行掃描,得出哪些沒有別佔用,從其中選擇一個作為該設備的IRQ。其次,透過中斷申請函數申請對應的IRQ。最後,根據申請結果查看中斷是否能夠被執行。
2.2.2 中斷相關結構
## 中斷中核心處理數據結構為irq_desc,它完整的描述了一條中斷線,Linux 2.6。22.6中原始碼如下。
irq_desc#定義在include/linux/irq.h中
/** * struct irq_desc - interrupt descriptor * * @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()] * @chip: low level interrupt hardware access * @msi_desc: MSI descriptor * @handler_data: per-IRQ data for the irq_chip methods * @chip_data: platform-specific per-chip private data for the chip * methods, to allow shared chip implementations * @action: the irq action chain * @status: status information * @depth: disable-depth, for nested irq_disable() calls * @wake_depth: enable depth, for multiple set_irq_wake() callers * @irq_count: stats field to detect stalled irqs * @irqs_unhandled: stats field for spurious unhandled interrupts * @lock: locking for SMP * @affinity: IRQ affinity on SMP * @cpu: cpu index useful for balancing * @pending_mask: pending rebalanced interrupts * @dir: /proc/irq/ procfs entry * @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP * @name: flow handler name for /proc/interrupts output */struct irq_desc { irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* IRQ action list */ unsigned int status; /* IRQ status */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned int irqs_unhandled; spinlock_t lock; #ifdef CONFIG_SMP cpumask_t affinity; unsigned int cpu;#endif#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE) cpumask_t pending_mask;#endif#ifdef CONFIG_PROC_FS struct proc_dir_entry *dir;#endif const char *name; } ____cacheline_internodealigned_in_smp;
irq_desc
其相關聯的幾個結構體如下:定義在include/linux/ interrupt.h中的中斷行動結構體:struct irqaction
struct irqaction { irq_handler_t handler; unsigned long flags; cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; };
/** * struct irq_chip - hardware interrupt chip descriptor * * @name: name for /proc/interrupts * @startup: start up the interrupt (defaults to ->enable if NULL) * @shutdown: shut down the interrupt (defaults to ->disable if NULL) * @enable: enable the interrupt (defaults to chip->unmask if NULL) * @disable: disable the interrupt (defaults to chip->mask if NULL) * @ack: start of a new interrupt * @mask: mask an interrupt source * @mask_ack: ack and mask an interrupt source * @unmask: unmask an interrupt source * @eoi: end of interrupt - chip level * @end: end of interrupt - flow level * @set_affinity: set the CPU affinity on SMP machines * @retrigger: resend an IRQ to the CPU * @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ * @set_wake: enable/disable power-management wake-on of an IRQ * * @release: release function solely used by UML * @typename: obsoleted by name, kept as migration helper */struct irq_chip { const char *name; unsigned int (*startup)(unsigned int irq); //中断开始 void (*shutdown)(unsigned int irq); //中断关闭 void (*enable)(unsigned int irq); //中断使能 void (*disable)(unsigned int irq); //中断禁用 void (*ack)(unsigned int irq); void (*mask)(unsigned int irq); void (*mask_ack)(unsigned int irq); void (*unmask)(unsigned int irq); void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsigned int irq, cpumask_t dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type); int (*set_wake)(unsigned int irq, unsigned int on); /* Currently used only by UML, might disappear one day.*/#ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id);#endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ const char *typename; };
2.2.3 中斷請求實作
我們期望讓中斷處理程序運作得快,並想讓它完成的工作量多,這兩個目標相互制約,如何解決——上下半部機制。
我們把中斷處理切成兩半。中斷處理程序是上半部-接受中斷,他就立刻開始執行,但只有做嚴格時限的工作。能夠被允許稍後完成的工作會推遲到下半部去,此後,在適當的時機,下半部會被開啟終端執行。上半部簡單快速,執行時禁止一些或全部中斷。
下半部稍後執行,而且執行期間可以回應所有的中斷。這種設計可以使系統處於中斷屏蔽狀態的時間盡可能的短,以此來提高系統的反應能力。上半部只有中斷處理程序機制,而下半部的實作有軟中斷實現,tasklet實作和工作佇列實作。
我們用網路卡來解釋這兩半。當網卡接受到資料包時,通知內核,觸發中斷,所謂的上半部就是,及時讀取資料包到內存,防止因為延遲導致遺失,這是很急迫的工作。讀到記憶體後,這些資料的處理不再緊迫,此時核心可以去執行中斷前執行的程序,而對網路封包的處理則交給下半部處理。
1) 如果一個任務對時間非常敏感,將其放在中斷處理程序中執行;
2) 如果一個任務和硬體有關,將其放在中斷處理程序中執行;
3) 如果一個任務要確保不被其他中斷打斷,將其放在中斷處理程序中執行;
# 4)其他所有任務,考慮放置在下半部執行。
軟中斷作為下半部機制的代表,是隨著SMP(share memory processor)的出現應運而生的,它也是tasklet實作的基礎(tasklet其實只是在軟中斷的基礎上添加了一定的機制)。軟中斷一般是“可延遲函數”的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是因為要滿足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,軟中斷執行中斷處理程序留給它去完成的剩餘任務,而且可以在多個CPU上並行執行,使得總的系統效率可以更高。它的特性包括:
a)產生後並不是馬上可以執行,必須要等待核心的調度才能執行。軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部)。
b)可以並發運行在多個CPU上(即使是相同類型的也可以)。所以軟中斷必須設計為可重入的函數(允許多個CPU同時操作),因此也需要使用自旋鎖來保護其資料結構。
tasklet是透過軟中斷實現的,所以它本身也是軟中斷。
軟中斷用輪詢的方式處理。假如正好是最後一種中斷,則必須循環完所有的中斷類型,才能最終執行對應的處理函數。顯然當年開發人員為了保證輪詢的效率,於是限制中斷個數為32個。
為了提高中斷處理數量,順道改進處理效率,於是產生了tasklet機制。
Tasklet採用無差別的佇列機制,有中斷時才執行,免去了循環查表之苦。 Tasklet作為一種新機制,顯然可以承擔更多的優點。正好這時候SMP越來越火紅了,因此又在tasklet中加入了SMP機制,保證同種中斷只能在一個cpu 上執行。在軟中斷時代,顯然沒有這種考慮。因此同一種軟中斷可以在兩個cpu上同時執行,很可能會造成衝突。
總結下tasklet的優點:
# (1)無類型數量限制;
## (
2)效率高,無須循環查表; (3)支援SMP機制;
# 它的特性如下: 1)一種特定類型的tasklet#只能運行在一個
CPU上,不能並行,只能串行執行。
23
)軟中斷是靜態分配的,在內核編譯好之後,就無法改變。但tasklet就靈活許多,可以在執行時改變(例如新增模組時)。
#####################下半部實作機制之工作佇列(work queue)############## # 上面我們介紹的可延遲函數運行在中斷上下文中(軟中斷的一個檢查點就是###do_IRQ###退出的時候),於是導致了一些問題:軟中斷不能睡眠、不能阻塞。由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或阻塞,將無法退出這種狀態,導致核心會整個僵死。但可阻塞函數不能用在中斷上下文中實現,必須運行在進程上下文中,例如存取磁碟資料塊的函數。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。 ################## 上面我們介紹的可延遲函數運行在中斷上下文中,於是導致了一些問題,說明它們不可掛起,也就是說軟中斷不能睡眠、不能阻塞,原因是由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或阻塞,將無法退出這種狀態,導致核心會整個僵死。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。而且由於是串行執行,因此只要有一個處理時間較長,就會導致其他中斷響應的延遲。為了完成這些不可能的任務,於是出現了工作佇列,它能夠在不同的進程間切換,以完成不同的工作。 ######如果推後執行的任務需要睡眠,那麼就選擇工作隊列,如果不需要睡眠,那麼就選擇軟中斷或tasklet。工作佇列能運行在行程上下文,它將工作託付給一個核心執行緒。工作隊列說白了就是一組核心線程,當作中斷守護線程來使用。多個中斷可以放在一個執行緒中,也可以每個中斷分配一個執行緒。我們用結構體workqueue_struct表示工作者線程,工作者線程是用內核線程實現的。而工作者執行緒是如何執行被推後的工作——有這樣一個鍊錶,它由結構體work_struct組成,而這個work_struct則描述了一個工作,一旦這個工作被執行完,對應的work_struct物件就從鍊錶上移去,當鍊錶上不再有物件時,工作者執行緒就會繼續休眠。因為工作隊列是線程,所以我們可以使用所有可以在線程中使用的方法。
linux 中的軟中斷和工作佇列的作用是實作中斷處理;它們是中斷上下部機制中的下半部實現機制。
1.軟體中斷一般是「可延遲函數」的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能進程間切換,軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部),可以並發的運行在多個CPU上。所以軟中斷必須設計成可重入的函數,因此也需要自旋鎖來保護其資料結構。
# 2.
工作佇列中的函數處在進程上下文中,它可以睡眠,也能被阻塞,能夠在不同的進程間切換,以完成不同的工作。
可延遲函數和工作佇列都無法存取使用者的進程空間,可延時函數在執行時不可能有任何正在執行的進程,工作佇列的函數有核心行程執行,他不能存取使用者空間位址。
相關推薦:《###Linux影片教學###》###以上是linux軟中斷和工作佇列的作用是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!