<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          認(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 指令這種軟件中斷,整個中斷機(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í)行完之后再返回原程序
          從這個層面看,硬中斷可以達(dá)到這個效果,軟中斷也可以達(dá)到這個效果,所以說宏觀效果一樣。
          那微觀層面呢?就是我們需要了解的原理啦。
          硬中斷的微觀層面,就是 CPU 在每一個指令周期的最后,都會留一個 CPU 周期去查看是否有中斷,如果有,就把中斷號取出,去中斷向量表中尋找中斷處理程序,然后跳過去。
          這個在上面那篇文章里講的很清楚啦。
          軟中斷的微觀層面,簡單說就是有一個單獨(dú)的守護(hù)進(jìn)程,不斷輪詢一組標(biāo)志位,如果哪個標(biāo)志位有值了,那去這個標(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)核軟中斷處理線程是怎么從零到一開始的了。
          這個是我之前在講解自制操作系統(tǒng)時的圖,放在這里完全沒有問題,這就是 Linux 的啟動過程,文件名都一樣。
          唯一不同的是,我們這里內(nèi)核主方法叫 kernel_start,Linux-2.6.0 里叫 start_kernel,我也懶得改了。
          接下來看這個入口方法。
          asmlinkage void __init start_kernel(void) {
              ...
              trap_init();
              sched_init();
              time_init();
              ...
              rest_init();
          }
          省略了很多部分,但可以看出這個方法里就是各種初始化。
          接著看 rest_init() 這個方法。
          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();
          }
          看到一個 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);
          }
          前面的不用管,直接看最后一個方法,do_softirq(),這個方法展示了軟中斷處理守護(hù)進(jìn)程所做的事情的精髓,我給翻譯一下。
          // 這就是軟中斷處理函數(shù)表(軟中斷向量表)
          // 和硬中斷的中斷向量表一樣
          static struct softirq_action softirq_vec[32];

          asmlinkage void do_softirq(void) {
              // h = 軟中斷向量表起始地址指針
              h = softirq_vec;
              // 這個是軟中斷標(biāo)志位們,一次性拿到所有的軟中斷標(biāo)志位
              pending = local_softirq_pending();
              do {
                  // 此時的軟中斷標(biāo)志位有值(說明有軟中斷)
                  if (pending & 1) {
                      // 去對應(yīng)的軟中斷向量表執(zhí)行對應(yīng)的處理函數(shù)
                      h->action(h);
                  // 軟中斷向量表指針向后移動
                  h++;
                  // 同時軟中斷處理標(biāo)志位也向后移動
                  pending >>= 1;
              } while (pending);
          }
          這翻譯還沒看明白,那我來幾個圖你就懂了。
          首先 h 代表軟中斷向量表 softirq_vec,和硬中斷的中斷向量表的存在是一個目的,就是個數(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)只有一個核心,就變得很簡單了。
          pending = irq_stat[0].__softirq_pending;
          它就是個 int 值而已,32 位。
          回過頭看之前的,pending(軟中斷標(biāo)志位)h(軟中斷向量表)的向后移動的步長。
          // 軟中斷向量表指針向后移動
          h++;
          // 同時軟中斷處理標(biāo)志位也向后移動
          pending >>= 1;
          可以看出軟中斷標(biāo)志位的一位對應(yīng)著軟中斷向量表中的一個元素,這就不難理解為什么中斷向量表這個數(shù)組大小是 32 位了。

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

          注冊軟中斷向量表





          就是給 softirq_vec 這個軟中斷向量表,也是一個數(shù)組,里面的每一個元素的 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;
          沒錯,就是這樣,不要以為 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();
          取出來的是個 32 位的 int 值。
          那只需要local_softirq_pending() 對應(yīng)的標(biāo)志位改成 1 就觸發(fā)了軟中斷了,比如我們想觸發(fā)一個 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)你真的去接觸這個東西的時候,一個個細(xì)節(jié)逐步撥開后,會發(fā)現(xiàn)一點(diǎn)也不難,而且都是順理成章,和我們猜測的也一樣。

          總結(jié)



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

          記住上面這張圖,就可以了。
          好了,上篇文章的硬中斷,和本篇文章的軟中斷,它們最基本的原理,和他們的異同點(diǎn),你整明白了么?
          軟中斷是 Linux 處理一個中斷的下半部的主要方式,比如 Linux 某網(wǎng)卡接收了一個數(shù)據(jù)包,此時會觸發(fā)一個硬中斷,由于處理數(shù)據(jù)包的過程比較耗時,而硬中斷資源又非常寶貴,如果占著硬中斷函數(shù)不返回,會影響到其他硬中斷的相應(yīng)速度,比如點(diǎn)擊鼠標(biāo)、按下鍵盤等。
          所以一般 Linux 會把中斷分成上下兩半部分執(zhí)行,上半部分處理最簡單的邏輯,下半部分直接丟給一個軟中斷異步處理。
          比如網(wǎng)卡收到了一個數(shù)據(jù)包,假如這個網(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 丟給軟中斷就不管了。
          這個會在后面講內(nèi)核接受網(wǎng)絡(luò)包的全過程中詳細(xì)講解,本次的兩篇文章硬中斷和軟中斷,都是為之后的內(nèi)核收包做鋪墊,大家一定要把它們整明白了。
          所有復(fù)雜的技術(shù),都是由諸多簡單技術(shù)拼接起來的,所以,跟著我一步步來,沒問題,加油!
          瀏覽 75
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产美女丝袜足交视频 | 欧美成人一级 | 免费观看黄色在线视频 | 一区二区精品视频尤酸乳 | 免费看欧美三级片 |