可屏蔽中斷和非屏蔽中斷區別
按照是否可以 被屏蔽,可將中斷分為兩大類:不可屏蔽中斷(又叫非屏蔽中斷)和可屏蔽中斷。
不可屏蔽中斷源一旦提出請求,cpu必須無條件響應,而對於可屏蔽中斷源的請求,cpu可以響應,也可以不響應。cup一般設置兩根中斷請求輸入線:可屏蔽中斷請求INTR(Interrupt Require)和不可屏蔽中斷請求NMI(Nonmaskable Interrupt)。對於可屏蔽中斷,除了受本身的屏蔽位的控制外,還都要受一個總的控制,即CPU標志寄存器中的中斷允許標志位IF(Interrupt Flag)的控制,IF位為1,可以得到CPU的響應,否則,得不到響應。IF位可以有用戶控制,指令STI或Turbo c的Enable()函數,將IF位置1(開中斷),指令CLI或Turbo_c 的Disable()函數,將IF位清0(關中斷)。
典型的非屏蔽中斷源的例子是電源掉電,一旦出現,必須立即無條件地響應,否則進行其他任何工作都是沒有意義的。典型的可屏蔽中斷源的例子是打印機中斷,CPU對打印機中斷請求的響應可以快一些,也可以慢一些,因為讓打印機等待兒是完全可以的。
中斷是什么?
先來看看什么是中斷?在計算機中,中斷是系統用來響應硬件設備請求的一種機制,操作系統收到硬件的中斷請求,會打斷正在執行的進程,然后調用內核中的中斷處理程序來響應請求。
中斷首先是處理器提供的一種響應外設請求的機制。一個外設通過產生一種電信號通知中斷控制器,中斷控制器再向處理器發送相應的信號。處理器檢測到了這個信號后就會打斷自己當前正在做的工作,轉而去處理這次中斷(所以才叫中斷)。當然在轉去處理中斷和中斷返回時都有保護現場和返回現場的操作 ,這里不贅述,我在下面問題的回答里解釋過一些,可以參考:
不同的設備會對應不同的中斷號,不同的中斷也會有不同的中斷處理函數,中斷處理函數一般在設備驅動注冊時一同注冊,這樣一來哪個設備有了事件就能產生對應的中斷,並找到對應的中斷處理程序來執行了。
一個硬件中斷的大致過程描述是下面這樣(非絕對,依具體情況而定,意會一下即可):
+---------+ 產生中斷 +----------+ 通知 +-----+
| 硬件設備 | -------------> | 中斷控制器 | -------> | CPU |
+---------+ +----------+ +-----+
|
V
[中斷內核]
|
V
[是否存在中斷處理程序?] <--- do_IRQ()
|
+-------+-------+
|Y N|
V |
handle_IRQ_event |
| |
V |
執行中斷處理程序 |
| V
+----------> irq_exit ----> 恢復現場 .....
上面的解釋可能過於學術了,容易雲里霧里,我就舉個生活中取外賣的例子。
小林中午搬完磚,肚子餓了,點了份白切雞外賣。雖然平台上會顯示配送進度,但是我也不能一直傻傻地盯着呀,時間很寶貴,當然得去干別的事情,等外賣到了配送員會通過「電話」通知我,電話響了,我就會停下手中地事情,去拿外賣。
這里的打電話,其實就是對應計算機里的中斷,沒接到電話的時候,我可以做其他的事情,只有接到了電話,也就是發生中斷,我才會停下當前的事情,去進行另一個事情,也就是拿外賣。
從這個例子,我們可以知道,中斷是一種異步的事件處理機制,可以提高系統的並發處理能力。
操作系統收到了中斷請求,會打斷其他進程的運行,所以中斷請求的響應程序,也就是中斷處理程序,要盡可能快的執行完,這樣可以減少對正常進程運行調度地影響。
而且,中斷處理程序在響應中斷時,可能還會「臨時關閉中斷」,這意味着,如果當前中斷處理程序沒有執行完之前,系統中其他的中斷請求都無法被響應,也就說中斷有可能會丟失,所以中斷處理程序要短且快。
還是回到外賣的例子,小林到了晚上又點起了外賣,這次為了犒勞自己,共點了兩份外賣,一份小龍蝦和一份奶茶,並且是由不同地配送員來配送,那么問題來了,當第一份外賣送到時,第一位配送員給我打了長長的電話,說了一些雜七雜八的事情,比如給個好評等等,但如果這時第二位配送員也想給我打電話。
很明顯,這時第二位配送員因為我在通話中(相當於關閉了中斷響應),自然就無法打通我的電話,他可能嘗試了幾次后就走掉了(相當於丟失了一次中斷)
什么是軟中斷?
前面我們也提到了,中斷請求的處理程序應該要短且快,這樣才能減少對正常進程運行調度地影響,而且中斷處理程序可能會暫時關閉中斷,這時如果中斷處理程序執行時間過長,可能在還未執行完中斷處理程序前,會丟失當前其他設備的中斷請求。
在中斷處理時CPU沒法處理其它事物,
那 Linux 系統為了解決中斷處理程序執行過長和中斷丟失的問題,將中斷過程分成了兩個階段,分別是「上半部和下半部分」。
- 上半部用來快速處理中斷,一般會暫時關閉中斷請求,主要負責處理跟硬件緊密相關或者時間敏感的事情。
- 下半部用來延遲處理上半部未完成的工作,一般以「內核線程」的方式運行。
前面的外賣例子,由於第一個配送員長時間跟我通話,則導致第二位配送員無法撥通我的電話,其實當我接到第一位配送員的電話,可以告訴配送員說我現在下樓,剩下的事情,等我們見面再說(上半部),然后就可以掛斷電話,到樓下后,在拿外賣,以及跟配送員說其他的事情(下半部)。
這樣,第一位配送員就不會占用我手機太多時間,當第二位配送員正好過來時,會有很大幾率撥通我的電話。
再舉一個計算機中的例子,常見的網卡接收網絡包的例子。
於網卡來說,如果每次網卡收包時中斷的時間都過長,那很可能造成丟包的可能性。( 因為中斷處理程序在處理中斷請求的時候,會臨時關閉中斷。。也就是此時如果網卡受到其他包時候,控制器無法繼續向cpu發送其他中斷信號)
當然我們不能完全避免丟包的可能性,以太包的傳輸是沒有100%保證的,所以網絡才有協議棧,通過高層的協議來保證連續數據傳輸的數據完整性(比如在協議發現丟包時要求重傳)。
但是即使有協議保證,那我們也不能肆無忌憚的使用中斷,中斷的時間越短越好,盡快放開處理器,讓它可以去響應下次中斷甚至進行調度工作。
對於網卡收包來說:
網卡收到網絡包后,會通過硬件中斷通知內核有新的數據到了,於是內核就會調用對應的中斷處理程序來響應網卡發出的中斷信號,為了讓中斷的處理事件的事件越短越好,我們將中斷處理事件分成了上下兩部分:
上半部:網卡收到數據包,通知內核數據包到了,中斷處理將只要把網卡的數據讀到內存中,然后更新一下硬件寄存器的狀態,比如把狀態更新為表示數據已經讀到內存中的狀態值。
下半部:軟中斷就是下半部使用的一種機制,它通過軟件模仿硬件中斷的處理過程,但是和硬件沒有關系,單純的通過軟件達到一種異步處理的方式。
接上半部,內核會觸發一個軟中斷,把一些處理比較耗時且復雜的事情,交給「軟中斷處理程序」去做,也就是中斷的下半部,其主要是需要從內存中找到網絡數據,再按照網絡協議棧,對網絡數據進行逐層解析和處理,最后把數據送給應用程序。即解析處理數據包的工作則可以放到下半部去執行。
所以,中斷處理程序的上部分和下半部可以理解為:
- 上半部直接處理硬件請求,也就是硬中斷,主要是負責耗時短的工作,特點是快速執行;
- 下半部是由內核觸發,也就說軟中斷,主要是負責上半部未完成的工作,通常都是耗時比較長的事情,特點是延遲執行;
還有一個區別,硬中斷(上半部)是會打斷 CPU 正在執行的任務,然后立即執行中斷處理程序,而軟中斷(下半部)是以內核線程的方式執行,並且每一個 CPU 都對應一個軟中斷內核線程,名字通常為「ksoftirqd/CPU 編號」,比如 0 號 CPU 對應的軟中斷內核線程的名字是 ksoftirqd/0
其它下半部的處理機制還包括tasklet,工作隊列等。依據所處理的場合不同,選擇不同的機制,網卡收包一般使用軟中斷。對應NET_RX_SOFTIRQ這個軟中斷,軟中斷不只是包括硬件設備中斷處理程序的下半部,一些內核自定義事件也屬於軟中斷,比如內核調度等、RCU 鎖(內核里常用的一種鎖)等。
網卡收包的中斷過程
注意:之前提過
硬中斷(上半部)是會打斷 CPU 正在執行的任務,然后立即執行中斷處理程序,
軟中斷(下半部)是以內核線程的方式執行,並且每一個 CPU 都對應一個軟中斷內核線程,名字通常為「ksoftirqd/CPU 編號」,比如 0 號 CPU 對應的軟中斷內核線程的名字是 ksoftirqd/0
1、網卡硬中斷的注冊
一個網卡收到數據包,它首先要做的事情就是通知處理器進行中斷處理。 其實就是通知cpu去執行設備驅動程序中的中斷處理程序代碼
不同的外設有不同的中斷和中斷處理函數,所以要研究網卡的中斷我們得以一個具體的網卡驅動為例,比如e1000。其模塊初始化函數就是:
static int __init e1000_init_module(void)
{
int ret;
pr_info("%s - version %s\n", e1000_driver_string, e1000_driver_version);
pr_info("%s\n", e1000_copyright);
ret = pci_register_driver(&e1000_driver);
...
...
return ret;
}
static int __init e1000_init_module(void)
{
int ret;
pr_info("%s - version %s\n", e1000_driver_string, e1000_driver_version);
pr_info("%s\n", e1000_copyright);
ret = pci_register_driver(&e1000_driver);
...
...
return ret;
}
其中e1000_driver這個結構體是一個關鍵,它的賦值如下:
static struct pci_driver e1000_driver = {
.name = e1000_driver_name,
.id_table = e1000_pci_tbl,
.probe = e1000_probe,
.remove = e1000_remove,
.driver = {
.pm = &e1000_pm_ops,
},
.shutdown = e1000_shutdown,
.err_handler = &e1000_err_handler
};
其中很主要的一個方法就是.probe方法,也就是e1000_probe():
/**
* e1000_probe - Device Initialization Routine
* @pdev: PCI device information struct
* @ent: entry in e1000_pci_tbl
*
* Returns 0 on success, negative on failure
*
* e1000_probe initializes an adapter identified by a pci_dev structure.
* The OS initialization, configuring of the adapter private structure,
* and a hardware reset occur.
**/
static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
...
...
netdev->netdev_ops = &e1000_netdev_ops;
e1000_set_ethtool_ops(netdev);
...
...
}
這個函數很長,我們不都列出來,這是e1000主要的初始化函數,即使從注釋都能看出來。我們留意其注冊了netdev的netdev_ops,用的是e1000_netdev_ops這個結構體:
static const struct net_device_ops e1000_netdev_ops = {
.ndo_open = e1000_open,
.ndo_stop = e1000_close,
.ndo_start_xmit = e1000_xmit_frame,
.ndo_set_rx_mode = e1000_set_rx_mode,
.ndo_set_mac_address = e1000_set_mac,
.ndo_tx_timeout = e1000_tx_timeout,
...
...
};
這個e1000的方法集里有一個重要的方法,e1000_open,我們要說的中斷的注冊就從這里開始:
/**
* e1000_open - Called when a network interface is made active
* @netdev: network interface device structure
*
* Returns 0 on success, negative value on failure
*
* The open entry point is called when a network interface is made
* active by the system (IFF_UP). At this point all resources needed
* for transmit and receive operations are allocated, the interrupt
* handler is registered with the OS, the watchdog task is started,
* and the stack is notified that the interface is ready.
**/
int e1000_open(struct net_device *netdev)
{
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
...
...
err = e1000_request_irq(adapter);
...
}
e1000在這里注冊了中斷:
static int e1000_request_irq(struct e1000_adapter *adapter){ struct net_device *netdev = adapter->netdev; irq_handler_t handler = e1000_intr; int irq_flags = IRQF_SHARED; int err; err = request_irq(adapter->pdev->irq, handler, irq_flags, netdev->name,......}
中斷處理程序是寫在設備驅動中的
如上所示,這個被注冊到中斷處理程序的硬中斷處理函數,也就是handler,就是e1000_intr()。我們不展開這個中斷處理函數看了,我們知道中斷處理函數在這里被注冊了,在網絡包來的時候會觸發這個中斷函數。
到這里,網卡硬中斷的注冊完成
2、注冊軟中斷
上面我們看到了網卡硬中斷的注冊,我們下面看一下軟中斷處理的注冊。我們在一開始提到了網卡收包時使用的軟中斷是NET_RX_SOFTIRQ,我們就在內核中查找這個關鍵字,看看這個注冊的位置在哪。踏破鐵鞋無覓處,得來全不費工夫,原來這個注冊的位置在這里:
/* * Initialize the DEV module. At boot time this walks the device list and * unhooks any devices that fail to initialise (normally hardware not * present) and leaves us with a valid list of present and active devices. * */...static int __init net_dev_init(void){...... // open_softirq()函數就是注冊軟中斷用的函數,向它指定軟中斷號NET_RX_SOFTIRQ和軟中斷處理函數 net_rx_action()就可 // 以完成注冊了。 open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action);......}
3、從硬中斷到軟中斷
現在一個網絡包來了,會產生中斷,會執行do_IRQ。關於do_IRQ的實現有很多,不同硬件對中斷的處理都會有所不同,但一個基本的執行思路就是:
void __irq_entry do_IRQ(unsigned int irq) //do_IRQ[98] void do_IRQ(struct pt_regs *regs, int irq){ irq_enter();//*** arch/sh/kernel/irq.c: |do_IRQ[185] asmlinkage __irq_entry int do_IRQ(unsigned //int irq, struct pt_regs *regs) generic_handle_irq(irq); irq_exit(); }
我們沒必要都展開,讓我們專注我們的問題。do_IRQ會執行上面e1000_intr這個中斷處理函數,這個中斷處理是屬於上半部的處理,在do_IRQ的結尾會調用irq_exit(),這是軟中斷和中斷銜接的一個地方。我們重點說一下這里。
void irq_exit(void){ __irq_exit_rcu(); rcu_irq_exit(); /* must be last! */ lockdep_hardirq_exit();}static inline void __irq_exit_rcu(void){#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED local_irq_disable();#else lockdep_assert_irqs_disabled();#endif account_irq_exit_time(current); preempt_count_sub(HARDIRQ_OFFSET); if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); tick_irq_exit();}
在irq_exit()的第一步就是一個local_irq_disable(),也就是說禁止了中斷,不再響應中斷。因為下面要處理所有標記為要處理的軟中斷,關中斷是因為后面要清除這些軟中斷,將CPU軟中斷的位圖中置位的位清零,這需要關中斷,防止其它進程對位圖的修改造成干擾。??
然后preempt_count_sub(HARDIRQ_OFFSET),硬中斷的計數減1,表示當前的硬中斷到這里就結束了。但是如果當前的中斷是嵌套在其它中斷里的話,這次減1后不會計數清0,如果當前只有這一個中斷的話,這次減1后計數會清0。注意這很重要。
因為接下來一步判斷!in_interrupt() && local_softirq_pending(),第一個!in_interrupt()就是通過計數來判斷當前是否還處於中斷上下文中,如果當前還有未完成的中斷,則直接退出當前中斷 tick_irq_exit();。未完成的中斷即后半部的執行在后續適當的時機再進行,這個“適當的時機”比如ksoftirqd守護進程的調度,或者下次中斷到此正好不在中斷上下文的時候等情況。
我們現在假設當前中斷結束后沒有其它中斷了,也就是不在中斷上下文了,且當前CPU有等待處理的軟中斷,即local_softirq_pending()也為真。那么執行invoke_softirq()。
static inline void invoke_softirq(void){ if (ksoftirqd_running(local_softirq_pending())) return; if (!force_irqthreads) {#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK /* * We can safely execute softirq on the current stack if * it is the irq stack, because it should be near empty * at this stage. */ __do_softirq();#else /* * Otherwise, irq_exit() is called on the task stack that can * be potentially deep already. So call softirq in its own stack * to prevent from any overrun. */ do_softirq_own_stack();#endif } else { wakeup_softirqd(); }}
這個函數的邏輯很簡單,首先如果ksoftirqd正在被執行,那么我們不想處理被pending的軟中斷,交給ksoftirqd線程來處理,這里直接退出。
如果ksoftirqd沒有正在運行,那么判斷force_irqthreads,也就是判斷是否配置了CONFIG_IRQ_FORCED_THREADING,是否要求強制將軟中斷處理都交給ksoftirqd線程。因為這里明顯要在中斷處理退出的最后階段處理軟中斷,但是也可以讓ksoftirqd來后續處理。如果設置了force_irqthreads,則不再執行__do_softirq(),轉而執行wakeup_softirqd()來喚醒ksoftirqd線程,將其加入可運行隊列,然后退出。
如果沒有設置force_irqthreads,那么就執行__do_softirq():
asmlinkage __visible void __softirq_entry __do_softirq(void){...... pending = local_softirq_pending(); account_irq_enter_time(current); __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); in_hardirq = lockdep_softirq_start();restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0); local_irq_enable(); h = softirq_vec; while ((softirq_bit = ffs(pending))) {...... } if (__this_cpu_read(ksoftirqd) == current) rcu_softirq_qs(); local_irq_disable(); pending = local_softirq_pending(); if (pending) { if (time_before(jiffies, end) && !need_resched() && --max_restart) goto restart; wakeup_softirqd(); } lockdep_softirq_end(in_hardirq); account_irq_exit_time(current); __local_bh_enable(SOFTIRQ_OFFSET); WARN_ON_ONCE(in_interrupt()); current_restore_flags(old_flags, PF_MEMALLOC);}
注意在函數開始時就先執行了一個__local_bh_disable_ip(RET_IP, SOFTIRQ_OFFSET),表示當前要處理軟中斷了,在這種情況下是不允許睡眠的,也就是不能進程調度。這點很重要,也很容易混淆,加上前面我們說的irq_exit()開頭的local_irq_disable(),所以當前處在一個既禁止硬中斷,又禁止軟中斷,不能睡眠不能調度的狀態。很多人就容易將這種狀態歸類為“中斷上下文”,我個人認為是不對的。從上面in_interrupt函數的定義來看,是否處於中斷上下文和preempt_count對於中斷的計數有關:
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \ | NMI_MASK))#define in_interrupt() (irq_count())
和是否禁止了中斷沒有直接的關系。雖然中斷上下文應該不允許睡眠和調度,但是不能睡眠和調度的時候不等於in_interrupt,比如spin_lock的時候也是不能睡眠的(這是目前我個人觀點)。但是很多程序員之所以容易一概而論,是因為對於內核程序員來講,判斷自己所編程的位置是否可以睡眠和調度是最被關心的,所以禁用了中斷后不能調度和睡眠就很容易被歸類為在中斷上下文,實際上我個人認為這應該算一個誤解,或者說是“變相擴展”后的說辭。一切還要看我們對中斷上下文這個概念的界定,如果像in_interrupt那樣界定,那關不關中斷和是否處於中斷上下文就沒有直接的關系。
下面在__do_softirq開始處理軟中斷(執行每一個待處理的軟中斷的action)前還有一個很關鍵的地方,就是local_irq_enable(),這就打開了硬件中斷,然后后面的軟中斷處理可以在允許中斷的情況下執行。注意這時候__local_bh_disable_ip(RET_IP, SOFTIRQ_OFFSET)仍然有效,睡眠仍然是不允許的。
到這里我們可以看到,內核是盡量做到能允許中斷就盡量允許,能允許調度就盡量允許,因為無情的禁止是對CPU資源最大的浪費,也是對外設中斷的不負責。否則長期處於禁止中斷的情況下,網卡大量丟包將是難免的,而這也將是制約成網卡實際速率的瓶頸。
系統里有哪些軟中斷?
在 Linux 系統里,我們可以通過查看 /proc/softirqs
的 內容來知曉「軟中斷」的運行情況,以及 /proc/interrupts
的 內容來知曉「硬中斷」的運行情況。
接下來,就來簡單的解析下 /proc/softirqs
文件的內容,在我服務器上查看到的文件內容如下:
你可以看到,每一個 CPU 都有自己對應的不同類型軟中斷的累計運行次數,有 3 點需要注意下。
第一點,要注意第一列的內容,它是代表着軟中斷的類型,在我的系統里,軟中斷包括了 10 個類型,分別對應不同的工作類型,比如 NET_RX
表示網絡接收中斷,NET_TX
表示網絡發送中斷、TIMER
表示定時中斷、RCU
表示 RCU 鎖中斷、SCHED
表示內核調度中斷。
第二點,要注意同一種類型的軟中斷在不同 CPU 的分布情況,正常情況下,同一種中斷在不同 CPU 上的累計次數相差不多,比如我的系統里,NET_RX
在 CPU0 、CPU1、CPU2、CPU3 上的中斷次數基本是同一個數量級,相差不多。
第三點,這些數值是系統運行以來的累計中斷次數,數值的大小沒什么參考意義,但是系統的中斷次數的變化速率才是我們要關注的,我們可以使用 watch -d cat /proc/softirqs
命令查看中斷次數的變化速率。
前面提到過,軟中斷是以內核線程的方式執行的,我們可以用 ps
命令可以查看到,下面這個就是在我的服務器上查到軟中斷內核線程的結果:
可以發現,內核線程的名字外面都有有中括號,這說明 ps 無法獲取它們的命令行參數,所以一般來說,名字在中括號里到,都可以認為是內核線程。
而且,你可以看到有 4 個 ksoftirqd
內核線程,這是因為我這台服務器的 CPU 是 4 核心的,每個 CPU 核心都對應着一個內核線程。
如何定位軟中斷 CPU 使用率過高的問題?
要想知道當前的系統的軟中斷情況,我們可以使用 top
命令查看,下面是一台服務器上的 top 的數據:
上圖中的黃色部分 si
,就是 CPU 在軟中斷上的使用率,而且可以發現,每個 CPU 使用率都不高,兩個 CPU 的使用率雖然只有 3% 和 4% 左右,但是都是用在軟中斷上了。
另外,也可以看到 CPU 使用率最高的進程也是軟中斷 ksoftirqd
,因此可以認為此時系統的開銷主要來源於軟中斷。
如果要知道是哪種軟中斷類型導致的,我們可以使用 watch -d cat /proc/softirqs
命令查看每個軟中斷類型的中斷次數的變化速率。
一般對於網絡 I/O 比較高的 Web 服務器,NET_RX
網絡接收中斷的變化速率相比其他中斷類型快很多。
如果發現 NET_RX
網絡接收中斷次數的變化速率過快,接下里就可以使用 sar -n DEV
查看網卡的網絡包接收速率情況,然后分析是哪個網卡有大量的網絡包進來。
接着,在通過 tcpdump
抓包,分析這些包的來源,如果是非法的地址,可以考慮加防火牆,如果是正常流量,則要考慮硬件升級等。
總結
為了避免由於中斷處理程序執行時間過長,而影響正常進程的調度,Linux 將中斷處理程序分為上半部和下半部:
- 上半部,對應硬中斷,由硬件觸發中斷,用來快速處理中斷;
- 下半部,對應軟中斷,由內核觸發中斷,用來異步處理上半部未完成的工作;
Linux 中的軟中斷包括網絡收發、定時、調度、RCU 鎖等各種類型,可以通過查看 /proc/softirqs 來觀察軟中斷的累計中斷次數情況,如果要實時查看中斷次數的變化率,可以使用 watch -d cat /proc/softirqs 命令。
每一個 CPU 都有各自的軟中斷內核線程,我們還可以用 ps 命令來查看內核線程,一般名字在中括號里面到,都認為是內核線程。
如果在 top 命令發現,CPU 在軟中斷上的使用率比較高,而且 CPU 使用率最高的進程也是軟中斷 ksoftirqd 的時候,這種一般可以認為系統的開銷被軟中斷占據了。
這時我們就可以分析是哪種軟中斷類型導致的,一般來說都是因為網絡接收軟中斷導致的,如果是的話,可以用 sar 命令查看是哪個網卡的有大量的網絡包接收,再用 tcpdump 抓網絡包,做進一步分析該網絡包的源頭是不是非法地址,如果是就需要考慮防火牆增加規則,如果不是,則考慮硬件升級等。