2、操作系統-中斷


可屏蔽中斷和非屏蔽中斷區別

​ 按照是否可以 被屏蔽,可將中斷分為兩大類:不可屏蔽中斷(又叫非屏蔽中斷)和可屏蔽中斷。

不可屏蔽中斷源一旦提出請求,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 文件的內容,在我服務器上查看到的文件內容如下:

img

你可以看到,每一個 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 命令可以查看到,下面這個就是在我的服務器上查到軟中斷內核線程的結果:

img

可以發現,內核線程的名字外面都有有中括號,這說明 ps 無法獲取它們的命令行參數,所以一般來說,名字在中括號里到,都可以認為是內核線程。

而且,你可以看到有 4 個 ksoftirqd 內核線程,這是因為我這台服務器的 CPU 是 4 核心的,每個 CPU 核心都對應着一個內核線程。


如何定位軟中斷 CPU 使用率過高的問題?

要想知道當前的系統的軟中斷情況,我們可以使用 top 命令查看,下面是一台服務器上的 top 的數據:

img

上圖中的黃色部分 si,就是 CPU 在軟中斷上的使用率,而且可以發現,每個 CPU 使用率都不高,兩個 CPU 的使用率雖然只有 3% 和 4% 左右,但是都是用在軟中斷上了。

另外,也可以看到 CPU 使用率最高的進程也是軟中斷 ksoftirqd,因此可以認為此時系統的開銷主要來源於軟中斷。

如果要知道是哪種軟中斷類型導致的,我們可以使用 watch -d cat /proc/softirqs 命令查看每個軟中斷類型的中斷次數的變化速率。

img

一般對於網絡 I/O 比較高的 Web 服務器,NET_RX 網絡接收中斷的變化速率相比其他中斷類型快很多。

如果發現 NET_RX 網絡接收中斷次數的變化速率過快,接下里就可以使用 sar -n DEV 查看網卡的網絡包接收速率情況,然后分析是哪個網卡有大量的網絡包進來。

img

接着,在通過 tcpdump 抓包,分析這些包的來源,如果是非法的地址,可以考慮加防火牆,如果是正常流量,則要考慮硬件升級等。

總結

為了避免由於中斷處理程序執行時間過長,而影響正常進程的調度,Linux 將中斷處理程序分為上半部和下半部:

  • 上半部,對應硬中斷,由硬件觸發中斷,用來快速處理中斷;
  • 下半部,對應軟中斷,由內核觸發中斷,用來異步處理上半部未完成的工作;

Linux 中的軟中斷包括網絡收發、定時、調度、RCU 鎖等各種類型,可以通過查看 /proc/softirqs 來觀察軟中斷的累計中斷次數情況,如果要實時查看中斷次數的變化率,可以使用 watch -d cat /proc/softirqs 命令。

每一個 CPU 都有各自的軟中斷內核線程,我們還可以用 ps 命令來查看內核線程,一般名字在中括號里面到,都認為是內核線程。

如果在 top 命令發現,CPU 在軟中斷上的使用率比較高,而且 CPU 使用率最高的進程也是軟中斷 ksoftirqd 的時候,這種一般可以認為系統的開銷被軟中斷占據了。

這時我們就可以分析是哪種軟中斷類型導致的,一般來說都是因為網絡接收軟中斷導致的,如果是的話,可以用 sar 命令查看是哪個網卡的有大量的網絡包接收,再用 tcpdump 抓網絡包,做進一步分析該網絡包的源頭是不是非法地址,如果是就需要考慮防火牆增加規則,如果不是,則考慮硬件升級等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM