1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        認(rèn)認(rèn)真真的聊聊"軟"中斷

        共 10190字,需瀏覽 21分鐘

         ·

        2021-08-27 20:23

        低并發(fā)編程
        戰(zhàn)略上藐視技術(shù),戰(zhàn)術(shù)上重視技術(shù)

        上一篇《認(rèn)認(rèn)真真聊聊中斷》,其實(shí)講的都是硬中斷,注意是硬中斷不是硬件中斷哦,硬中斷的概念更大。
        硬中斷包括中斷、異常以及 INT 指令這種軟件中斷,整個(gè)中斷機(jī)制是純硬件實(shí)現(xiàn)的邏輯,別管觸發(fā)它的是誰,所以通通叫硬中斷。
        當(dāng)然這里也要有軟件的配合,比如軟件需要提前把中斷向量表寫在內(nèi)存里,并通過 IDTR 寄存器告訴 CPU 它的起始位置在哪里。
        好了,這就是上一篇硬中斷的回顧了,如果上面這幾句總結(jié)你看著很困惑,那強(qiáng)烈建議你先把上面的文章看一遍。

        軟中斷與硬中斷很像





        軟中斷是純軟件實(shí)現(xiàn)的,宏觀效果看上去和中斷差不多的一種方式。
        什么叫宏觀效果呢?意思就是說,中斷在宏觀層面看來,就是打斷當(dāng)前正在運(yùn)行的程序,轉(zhuǎn)而去執(zhí)行中斷處理程序,執(zhí)行完之后再返回原程序
        從這個(gè)層面看,硬中斷可以達(dá)到這個(gè)效果,軟中斷也可以達(dá)到這個(gè)效果,所以說宏觀效果一樣。
        那微觀層面呢?就是我們需要了解的原理啦。
        硬中斷的微觀層面,就是 CPU 在每一個(gè)指令周期的最后,都會留一個(gè) CPU 周期去查看是否有中斷,如果有,就把中斷號取出,去中斷向量表中尋找中斷處理程序,然后跳過去。
        這個(gè)在上面那篇文章里講的很清楚啦。
        軟中斷的微觀層面,簡單說就是有一個(gè)單獨(dú)的守護(hù)進(jìn)程,不斷輪詢一組標(biāo)志位,如果哪個(gè)標(biāo)志位有值了,那去這個(gè)標(biāo)志位對應(yīng)的軟中斷向量表數(shù)組的相應(yīng)位置,找到軟中斷處理函數(shù),然后跳過去。
        你看,微觀層面其實(shí)也和硬中斷差不多。
        接下來我們具體說來看看,以 Linux-2.6.0 內(nèi)核為例,扒開它的外套。

        開啟內(nèi)核軟中斷處理的守護(hù)進(jìn)程





        不想之前有斷檔,我們直接從開機(jī)開始講起。
        知識都是想通的,學(xué)了從不會浪費(fèi),如果你對開機(jī)啟動流程有很直觀的了解,那這塊那就完全可以自己跟源碼知道內(nèi)核軟中斷處理線程是怎么從零到一開始的了。
        這個(gè)是我之前在講解自制操作系統(tǒng)時(shí)的圖,放在這里完全沒有問題,這就是 Linux 的啟動過程,文件名都一樣。
        唯一不同的是,我們這里內(nèi)核主方法叫 kernel_start,Linux-2.6.0 里叫 start_kernel,我也懶得改了。
        接下來看這個(gè)入口方法。
        asmlinkage void __init start_kernel(void) {
            ...
            trap_init();
            sched_init();
            time_init();
            ...
            rest_init();
        }
        省略了很多部分,但可以看出這個(gè)方法里就是各種初始化。
        接著看 rest_init() 這個(gè)方法。
        static void rest_init(void) {
            kernel_thread(init, NULL, CLONE_KERNEL);


        static int init(void * unused) {
            do_pre_smp_initcalls();
        }

        static void do_pre_smp_initcalls(void) {
            spawn_ksoftirqd();
        }
        看到一個(gè) spawn_ksoftirqd(),翻譯過來就是 spawn kernel soft irt daemon,開啟內(nèi)核軟中斷守護(hù)進(jìn)程,這名字太直觀了,都不用我講了!
        再往里跟。很長,但有用的信息很少。
        __init int spawn_ksoftirqd(void) {
            cpu_callback(&cpu_nfb, CPU_ONLINE, (void *)(long)smp_processor_id());
            register_cpu_notifier(&cpu_nfb);
            return 0;
        }

        static int __devinit cpu_callback(...) {
            kernel_thread(ksoftirqd, hcpu, CLONE_KERNEL);
        }

        static int ksoftirqd(void * __bind_cpu) {
            for (;;) {
                while (local_softirq_pending()) {
                    do_softirq();
                    cond_resched();
                }
            }
        }

        asmlinkage void do_softirq(void) {
            h = softirq_vec;
            pending = local_softirq_pending();
            do {
                if (pending & 1) {
                    h->action(h);
                h++;
                pending >>= 1;
            } while (pending);
        }
        前面的不用管,直接看最后一個(gè)方法,do_softirq(),這個(gè)方法展示了軟中斷處理守護(hù)進(jìn)程所做的事情的精髓,我給翻譯一下。
        // 這就是軟中斷處理函數(shù)表(軟中斷向量表)
        // 和硬中斷的中斷向量表一樣
        static struct softirq_action softirq_vec[32];

        asmlinkage void do_softirq(void) {
            // h = 軟中斷向量表起始地址指針
            h = softirq_vec;
            // 這個(gè)是軟中斷標(biāo)志位們,一次性拿到所有的軟中斷標(biāo)志位
            pending = local_softirq_pending();
            do {
                // 此時(shí)的軟中斷標(biāo)志位有值(說明有軟中斷)
                if (pending & 1) {
                    // 去對應(yīng)的軟中斷向量表執(zhí)行對應(yīng)的處理函數(shù)
                    h->action(h);
                // 軟中斷向量表指針向后移動
                h++;
                // 同時(shí)軟中斷處理標(biāo)志位也向后移動
                pending >>= 1;
            } while (pending);
        }
        這翻譯還沒看明白,那我來幾個(gè)圖你就懂了。
        首先 h 代表軟中斷向量表 softirq_vec,和硬中斷的中斷向量表的存在是一個(gè)目的,就是個(gè)數(shù)組嘛,然后里面的元素存儲著軟中斷處理程序的地址指針,在 action 中。

        然后 pending 代表軟中斷標(biāo)志位(們)
        這里完全由于 Linux 里用了好多 C 語言的宏定義搞得很繞,我先放出來,別擔(dān)心。
        typedef struct {
            unsigned int __softirq_pending;
            unsigned long idle_timestamp;
            unsigned int __nmi_count;   /* arch dependent */
            unsigned int apic_timer_irqs;   /* arch dependent */
        irq_cpustat_t;

        extern irq_cpustat_t irq_stat[];    /* defined in asm/hardirq.h */
        #define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
        #define __IRQ_STAT(cpu, member) ((void)(cpu), irq_stat[0].member)
        #define softirq_pending(cpu)  __IRQ_STAT((cpu), __softirq_pending)
        #define local_softirq_pending() softirq_pending(smp_processor_id())

        pending = local_softirq_pending();
        把這些宏定義都翻譯過來,再去掉多處理器的邏輯,就當(dāng)只有一個(gè)核心,就變得很簡單了。
        pending = irq_stat[0].__softirq_pending;
        它就是個(gè) int 值而已,32 位。
        回過頭看之前的,pending(軟中斷標(biāo)志位)h(軟中斷向量表)的向后移動的步長。
        // 軟中斷向量表指針向后移動
        h++;
        // 同時(shí)軟中斷處理標(biāo)志位也向后移動
        pending >>= 1;
        可以看出軟中斷標(biāo)志位的一位對應(yīng)著軟中斷向量表中的一個(gè)元素,這就不難理解為什么中斷向量表這個(gè)數(shù)組大小是 32 位了。

        好了,這樣這個(gè)內(nèi)核軟中斷處理這個(gè)守護(hù)進(jìn)程做的事,就完全搞懂了。
        就是不斷遍歷 pending 這個(gè)軟中斷標(biāo)志位的每一位,如果是 0 就忽略,如果是 1,那從上面的 h 軟中斷向量表中找到對應(yīng)的元素,然后執(zhí)行 action 方法,action 就對應(yīng)著不同的軟中斷處理函數(shù)。
        而且也能看到,內(nèi)核軟中斷處理守護(hù)進(jìn)程,在 Linux 啟動后,會自動跑起來,那也就代表了,軟中斷機(jī)制生效了。
        如果讓你使用這個(gè)內(nèi)核功能,做軟中斷的事情,那不難想象,很簡單。
        第一步,注冊軟中斷向量表,其實(shí)就是把軟中斷向量表的每個(gè) action 變量賦值,相當(dāng)于硬中斷中注冊中斷向量表的過程。
        第二步,觸發(fā)一個(gè)軟中斷,其實(shí)就是修改 pending 的某個(gè)標(biāo)志位,觸發(fā)一次軟中斷,相當(dāng)于硬中斷中由外部硬件、異常、或者 INT 指令來觸發(fā)硬中斷一樣。
        而實(shí)際上,Linux 就是這樣做的,和我們猜的一樣,我們一步步看。

        注冊軟中斷向量表





        就是給 softirq_vec 這個(gè)軟中斷向量表,也是一個(gè)數(shù)組,里面的每一個(gè)元素的 action 附上值,賦的就是軟中斷處理函數(shù)的函數(shù)地址。
        這代碼很容易就可以想到,太好寫了,就這樣唄。
        softirq_vec[0].action = NULL;
        softirq_vec[1].action = run_timer_softirq;
        softirq_vec[2].action = net_tx_action;
        ...
        softirq_vec[31].action = xxx;
        沒錯(cuò),就是這樣,不要以為 Linux 有啥神奇的操作,也是得這樣老老實(shí)實(shí)給他們賦值。
        比如,網(wǎng)絡(luò)子系統(tǒng)的初始化,有一步就需要注冊網(wǎng)絡(luò)的軟中斷處理函數(shù)。
        subsys_initcall(net_dev_init);

        static int __init net_dev_init(void) {
            ...
            // 網(wǎng)絡(luò)發(fā)包的處理函數(shù)
            open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
            // 網(wǎng)絡(luò)收包的處理函數(shù)
            open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
            ...
        }

        void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
        {
            softirq_vec[nr].data = data;
            // 簡直完全一樣
            softirq_vec[nr].action = action;
        }
        這和我們寫的不能說是相似,簡直完全是一樣呀,只是多包裝了一層函數(shù)叫 open_softirq 方便調(diào)用罷了。
        NET_TX_SOFTIRQ 這些是枚舉值,具體看這些枚舉也會發(fā)現(xiàn) Linux-2.6.0 中也不多。
        enum
        {
            HI_SOFTIRQ=0,
            TIMER_SOFTIRQ,
            NET_TX_SOFTIRQ,
            NET_RX_SOFTIRQ,
            SCSI_SOFTIRQ,
            TASKLET_SOFTIRQ
        };
        好奇翻了下 Linux-5.11,發(fā)現(xiàn)也不多
        enum
        {
            HI_SOFTIRQ=0,
            TIMER_SOFTIRQ,
            NET_TX_SOFTIRQ,
            NET_RX_SOFTIRQ,
            BLOCK_SOFTIRQ,
            IRQ_POLL_SOFTIRQ,
            TASKLET_SOFTIRQ,
            SCHED_SOFTIRQ,
            HRTIMER_SOFTIRQ,
            RCU_SOFTIRQ,
            NR_SOFTIRQS
        };

        觸發(fā)一次軟中斷





        同上,這代碼也很容易就可以想到,就這樣唄。
        你看,表示軟中斷標(biāo)志位的 p 不是這樣取值的么。
        pending = local_softirq_pending();
        取出來的是個(gè) 32 位的 int 值。
        那只需要local_softirq_pending() 對應(yīng)的標(biāo)志位改成 1 就觸發(fā)了軟中斷了,比如我們想觸發(fā)一個(gè) 2 號軟中斷,就像這樣。

        代碼這么寫就行了。

        local_softirq_pending() |= 1UL << 2;
        而 Linux 居然也是這么做的,我們看網(wǎng)絡(luò)數(shù)據(jù)包到來之后,有一段代碼。
        #define __raise_softirq_irqoff(nr) \
        do { local_softirq_pending() |= 1UL << (nr); } while (0)


        static inline void __netif_rx_schedule(struct net_device *dev) {
            list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
            // 發(fā)出軟中斷
            __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        }
        如果把 do while(0) 這種 C 語言宏定義的一種玩法去掉,其實(shí)就和我們的完全一樣了,這回可真的是完全一樣。
        static inline void __netif_rx_schedule(struct net_device *dev) {
            list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
            // 發(fā)出軟中斷
            local_softirq_pending() |= 1UL << (NET_RX_SOFTIRQ)
        }
        所以我之前總是說,當(dāng)你真的去接觸這個(gè)東西的時(shí)候,一個(gè)個(gè)細(xì)節(jié)逐步撥開后,會發(fā)現(xiàn)一點(diǎn)也不難,而且都是順理成章,和我們猜測的也一樣。

        總結(jié)



        軟中斷沒什么神奇的騷操作,就是一組一位一位的軟中斷標(biāo)志位,對應(yīng)著軟中斷向量表中一個(gè)一個(gè)的中斷處理函數(shù),然后有個(gè)內(nèi)核守護(hù)進(jìn)程不斷去循環(huán)判斷調(diào)用,而已。
        然后,由各個(gè)子系統(tǒng)調(diào)用 open_softirq 負(fù)責(zé)把軟中斷向量表附上值。
        再由各個(gè)需要觸發(fā)軟中斷的地方調(diào)用 raise_softirq_irqoff 修改中斷標(biāo)志位的值。
        后面的工作就交給內(nèi)核那個(gè)軟中斷守護(hù)進(jìn)程,去觸發(fā)這個(gè)軟中斷了,其實(shí)就是個(gè)遍歷并查找對應(yīng)函數(shù)的簡單過程。

        記住上面這張圖,就可以了。
        好了,上篇文章的硬中斷,和本篇文章的軟中斷,它們最基本的原理,和他們的異同點(diǎn),你整明白了么?
        軟中斷是 Linux 處理一個(gè)中斷的下半部的主要方式,比如 Linux 某網(wǎng)卡接收了一個(gè)數(shù)據(jù)包,此時(shí)會觸發(fā)一個(gè)硬中斷,由于處理數(shù)據(jù)包的過程比較耗時(shí),而硬中斷資源又非常寶貴,如果占著硬中斷函數(shù)不返回,會影響到其他硬中斷的相應(yīng)速度,比如點(diǎn)擊鼠標(biāo)、按下鍵盤等。
        所以一般 Linux 會把中斷分成上下兩半部分執(zhí)行,上半部分處理最簡單的邏輯,下半部分直接丟給一個(gè)軟中斷異步處理。
        比如網(wǎng)卡收到了一個(gè)數(shù)據(jù)包,假如這個(gè)網(wǎng)卡型號是 e1000,那對應(yīng)的硬中斷處理函數(shù)是,e1000_intr,我們看看它做了什么事情。
        static irqreturn_t e1000_intr(int irq, void *data, struct pt_regs *regs) {
           __netif_rx_schedule(netdev);
        }

        static inline void __netif_rx_schedule(struct net_device *dev) {
            list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
            __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        }
        看到?jīng)],后面直接 __raise_softirq_irqoff 丟給軟中斷就不管了。
        這個(gè)會在后面講內(nèi)核接受網(wǎng)絡(luò)包的全過程中詳細(xì)講解,本次的兩篇文章硬中斷和軟中斷,都是為之后的內(nèi)核收包做鋪墊,大家一定要把它們整明白了。
        所有復(fù)雜的技術(shù),都是由諸多簡單技術(shù)拼接起來的,所以,跟著我一步步來,沒問題,加油!
        瀏覽 75
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            三上悠亚免费电影网站 | 淫男乱女之小雄性事 | 国产精品午夜小视频观看 | 亚洲精品水蜜桃 | 国产夫妻久久 | 欧美日韩操逼视频 | 看操美女逼网 | 国内外免费激情视频 | 99久久精品免费看国产交换 | 日本二级黄免费在线观看 |