<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>

          一文看懂 Linux 性能分析|perf 源碼實(shí)現(xiàn)

          共 13902字,需瀏覽 28分鐘

           ·

          2022-10-15 00:35

          我們?cè)凇?a style="box-sizing: border-box;color: rgb(239, 112, 96);background-color: initial;outline: none;cursor: pointer;transition: color 0.3s ease 0s;touch-action: manipulation;overflow-wrap: break-word;font-weight: bold;border-bottom: 1px solid rgb(239, 112, 96);" data-linktype="2">一文看懂Linux性能分析|perf 原理》一文中介紹過(guò),perf 是基于采樣來(lái)對(duì)程序進(jìn)行分析的。采樣的步驟如下:

          • 通過(guò)設(shè)置一個(gè)定時(shí)器,定時(shí)器的觸發(fā)時(shí)間可以由用戶設(shè)定。

          • 定時(shí)器被觸發(fā)后,將會(huì)調(diào)用采集函數(shù)收集當(dāng)前運(yùn)行環(huán)境的數(shù)據(jù)(如當(dāng)前正在執(zhí)行的進(jìn)程和函數(shù)等)。

          • 將采集到的數(shù)據(jù)寫入到一個(gè)環(huán)形緩沖區(qū)(ring buffer)中。

          • 應(yīng)用層可以通過(guò)內(nèi)存映射來(lái)讀取環(huán)形緩沖區(qū)中的采樣數(shù)據(jù)。

          上述步驟如下圖所示:

          接下來(lái),我們將會(huì)介紹 perf 在 Linux 內(nèi)核中的實(shí)現(xiàn)。

          事件

          perf 是基于事件進(jìn)行采樣的,上面所說(shuō)的定時(shí)器就是其中一種事件,被稱為:CPU時(shí)鐘事件。除了 CPU 時(shí)鐘事件外,perf 還支持多種事件,如:

          • 上下文切換事件:當(dāng)調(diào)度器切換進(jìn)程時(shí)觸發(fā)。

          • 缺頁(yè)異常事件:當(dāng)進(jìn)程訪問(wèn)還沒(méi)有映射到物理內(nèi)存的虛擬內(nèi)存地址時(shí)觸發(fā)。

          • CPU遷移事件:當(dāng)進(jìn)程從一個(gè) CPU 遷移到另一個(gè) CPU 時(shí)觸發(fā)。

          • ...

          由于 perf 支持的事件眾多,所以本文只挑選 CPU時(shí)鐘事件 進(jìn)行分析。

          1. perf_event 結(jié)構(gòu)體

          Linux 內(nèi)核使用 perf_event 結(jié)構(gòu)體來(lái)描述一個(gè)事件(如 CPU 時(shí)鐘事件),其定義如下(由于 perf_event 結(jié)構(gòu)體過(guò)于龐大,所以對(duì)其進(jìn)行簡(jiǎn)化):

          struct perf_event {
              ...
              struct list_head                event_entry;
              const struct pmu                *pmu;
              enum perf_event_active_state    state;
              atomic64_t                      count;  // 事件被觸發(fā)的次數(shù)
              ...
              struct perf_event_attr          attr;   // 事件的屬性(由用戶提供)
              struct hw_perf_event            hw;
              struct perf_event_context       *ctx;   // 事件所屬的上下文
              ...
          };

          我們現(xiàn)在只需關(guān)注其中的兩個(gè)成員變量:count 和 ctx。

          • count:表示事件被觸發(fā)的次數(shù)。

          • ctx:表示當(dāng)前事件所屬的上下文。

          count 成員變量容易理解,所以就不作詳細(xì)介紹了。我們注意到 ctx 成員變量的類型為 perf_event_context 結(jié)構(gòu),那么這個(gè)結(jié)構(gòu)代表什么?

          2. perf_event_context 結(jié)構(gòu)體

          因?yàn)橐粋€(gè)進(jìn)程可以同時(shí)分析多種事件,所以就使用 perf_event_context 結(jié)構(gòu)來(lái)記錄屬于進(jìn)程的所有事件。我們來(lái)看看 perf_event_context 結(jié)構(gòu)的定義,如下所示:

          struct perf_event_context {
              ...
              struct list_head            event_list; // 連接所有屬于當(dāng)前上下文的事件
              int                         nr_events;  // 屬于當(dāng)前上下文的所有事件的總數(shù)
              ...
              struct task_struct          *task;      // 當(dāng)前上下文屬于的進(jìn)程
              ...
          };

          我們對(duì) perf_event_context 結(jié)構(gòu)進(jìn)行了簡(jiǎn)化,下面介紹一下各個(gè)成員的作用:

          • event_list:連接所有屬于當(dāng)前上下文的事件。

          • nr_events:屬于當(dāng)前上下文的所有事件的總數(shù)。

          • task:當(dāng)前上下文所屬的進(jìn)程。

          perf_event_context 結(jié)構(gòu)通過(guò) event_list 字段把所有屬于本上下文的事件連接起來(lái),如下圖所示:

          另外,在進(jìn)程描述結(jié)構(gòu)體 task_struct 中,有個(gè)指向 perf_event_context 結(jié)構(gòu)的指針。如下所示:

          struct task_struct {
              ...
              struct perf_event_context *perf_event_ctxp;
              ...
          };

          這樣,內(nèi)核就能通過(guò)進(jìn)程描述結(jié)構(gòu)體的 perf_event_ctxp 成員,來(lái)獲取屬于此進(jìn)程的事件列表。

          3. pmu 結(jié)構(gòu)體

          前面我們說(shuō)過(guò) perf 支持多種事件,而不同的事件應(yīng)該有不同的啟用和禁用動(dòng)作。為了讓不同的事件有不同的啟用和禁用動(dòng)作,所以內(nèi)核定義了 pmu 結(jié)構(gòu)。其定義如下:

          struct pmu {
              int (*enable)   (struct perf_event *event);
              void (*disable) (struct perf_event *event);
              void (*read)    (struct perf_event *event);
              ...
          };

          下面介紹一下各個(gè)字段的作用:

          • enable:?jiǎn)⒂檬录?/section>
          • disable:禁用事件。
          • read:事件被觸發(fā)時(shí)的回調(diào)。

          perf_event 結(jié)構(gòu)的 pmu 成員是一個(gè)指向 pmu 結(jié)構(gòu)的指針。如果當(dāng)前事件是個(gè) CPU 時(shí)鐘事件時(shí),pmu 成員將會(huì)指向 perf_ops_cpu_clock 變量。

          我們來(lái)看看 perf_ops_cpu_clock 變量的定義:

          static const struct pmu perf_ops_cpu_clock = {
              .enable  = cpu_clock_perf_event_enable,
              .disable = cpu_clock_perf_event_disable,
              .read    = cpu_clock_perf_event_read,
          };

          也就是說(shuō):

          • 當(dāng)要啟用一個(gè) CPU 時(shí)鐘事件時(shí),內(nèi)核將會(huì)調(diào)用 cpu_clock_perf_event_enable() 函數(shù)來(lái)啟用這個(gè)事件。
          • 當(dāng)要禁用一個(gè) CPU 時(shí)鐘事件時(shí),內(nèi)核將會(huì)調(diào)用 cpu_clock_perf_event_disable() 函數(shù)來(lái)禁用這個(gè)事件。
          • 當(dāng)事件被觸發(fā)時(shí),內(nèi)核將會(huì)調(diào)用 cpu_clock_perf_event_read() 函數(shù)來(lái)進(jìn)行特定的動(dòng)作。

          啟用事件

          前面說(shuō)過(guò),當(dāng)要啟用一個(gè) CPU 時(shí)鐘事件時(shí),內(nèi)核會(huì)調(diào)用 cpu_clock_perf_event_enable() 函數(shù)來(lái)啟用它。我們來(lái)看看 cpu_clock_perf_event_enable() 函數(shù)的實(shí)現(xiàn),代碼如下:

          static int
          cpu_clock_perf_event_enable(struct perf_event *event)
          {
              ...
              perf_swevent_start_hrtimer(event);

              return 0;
          }

          從上面代碼可以看出,cpu_clock_perf_event_enable() 函數(shù)實(shí)際上調(diào)用了 perf_swevent_start_hrtimer() 函數(shù)來(lái)進(jìn)行初始化工作。我們?cè)賮?lái)看看 perf_swevent_start_hrtimer() 函數(shù)的實(shí)現(xiàn):

          static void
          perf_swevent_start_hrtimer(struct perf_event *event)
          {
              struct hw_perf_event *hwc = &event->hw;

              // 1. 初始化一個(gè)定時(shí)器,定時(shí)器的回調(diào)函數(shù)為:perf_swevent_hrtimer()
              hrtimer_init(&hwc->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
              hwc->hrtimer.function = perf_swevent_hrtimer;

              if (hwc->sample_period) {
                  ...

                  // 2. 啟動(dòng)定時(shí)器
                  __hrtimer_start_range_ns(&hwc->hrtimer, ns_to_ktime(period), 0,
                                           HRTIMER_MODE_REL, 0);
              }
          }

          從上面的代碼可知,perf_swevent_start_hrtimer() 函數(shù)主要完成兩件事情:

          • 初始化一個(gè)定時(shí)器,定時(shí)器的回調(diào)函數(shù)為:perf_swevent_hrtimer()。
          • 啟動(dòng)定時(shí)器。

          這個(gè)定時(shí)器結(jié)構(gòu)保存在 perf_event 結(jié)構(gòu)的 hwc 成員中,我們?cè)谝院蟮奈恼轮袑?huì)介紹 Linux 高精度定時(shí)器的實(shí)現(xiàn)。

          當(dāng)定時(shí)器被觸發(fā)時(shí),內(nèi)核將會(huì)調(diào)用 perf_swevent_hrtimer() 函數(shù)來(lái)處理事件。我們?cè)賮?lái)分析一下 perf_swevent_hrtimer() 函數(shù)的實(shí)現(xiàn):

          static enum hrtimer_restart 
          perf_swevent_hrtimer(struct hrtimer *hrtimer)
          {
              enum hrtimer_restart ret = HRTIMER_RESTART;
              struct perf_sample_data data;
              struct pt_regs *regs;
              struct perf_event *event;
              u64 period;

              // 獲取當(dāng)前定時(shí)器所屬的事件對(duì)象
              event = container_of(hrtimer, struct perf_event, hw.hrtimer);

              // 前面說(shuō)過(guò),如果是CPU時(shí)鐘事件,將會(huì)調(diào)用 cpu_clock_perf_event_read() 函數(shù)
              event->pmu->read(event);

              data.addr = 0;
              // 獲取定時(shí)器被觸發(fā)時(shí)所有寄存器的值
              regs = get_irq_regs();

              ...
              if (regs) {
                  if (!(event->attr.exclude_idle && current->pid == 0)) {
                      // 最重要的地方:對(duì)數(shù)據(jù)進(jìn)行采樣
                      if (perf_event_overflow(event, 0, &data, regs))
                          ret = HRTIMER_NORESTART;
                  }
              }
              ...
              return ret;
          }

          perf_swevent_hrtimer() 函數(shù)最重要的操作就是:調(diào)用 perf_event_overflow() 函數(shù)對(duì)數(shù)據(jù)進(jìn)行采樣與收集。perf_event_overflow() 函數(shù)在后面將會(huì)介紹,我們暫時(shí)跳過(guò)。

          那什么時(shí)候會(huì)啟用事件呢?答案就是:進(jìn)程被調(diào)度到 CPU 運(yùn)行時(shí)。調(diào)用鏈如下:

          schedule()
          └→ context_switch()
          └→ finish_task_switch()
          └→ perf_event_task_sched_in()
          └→ __perf_event_sched_in()
          └→ group_sched_in()
          └→ event_sched_in()
          └→ event->pmu->enable()
          └→ cpu_clock_perf_event_enable()

          內(nèi)核通過(guò)調(diào)用 schedule() 函數(shù)來(lái)完成調(diào)度工作。從上面的調(diào)用鏈可知,當(dāng)進(jìn)程選中被調(diào)度到 CPU 運(yùn)行時(shí),最終會(huì)調(diào)用 cpu_clock_perf_event_enable() 函數(shù)來(lái)啟用這個(gè) CPU 時(shí)鐘事件。

          啟用事件的過(guò)程如下圖所示:

          所以,當(dāng)進(jìn)程被選中并且被調(diào)度運(yùn)行時(shí),內(nèi)核會(huì)啟用屬于此進(jìn)程的 perf 事件。不難看出,當(dāng)進(jìn)程被調(diào)度出 CPU 時(shí)(停止運(yùn)行),內(nèi)核會(huì)禁用屬于此進(jìn)程的 perf 事件。

          數(shù)據(jù)采樣

          最后,我們來(lái)看看 perf 是怎么進(jìn)行數(shù)據(jù)采樣的。

          通過(guò)上面的分析,我們知道 perf 最終會(huì)調(diào)用 perf_event_overflow() 函數(shù)來(lái)進(jìn)行數(shù)據(jù)采樣。所以我們來(lái)看看 perf_event_overflow() 函數(shù)的實(shí)現(xiàn),代碼如下:

          int
          perf_event_overflow(struct perf_event *event, int nmi,
                              struct perf_sample_data *data,
                              struct pt_regs *regs)

          {
              return __perf_event_overflow(event, nmi, 1, data, regs);
          }

          可以看出,perf_event_overflow() 函數(shù)只是對(duì) __perf_event_overflow() 函數(shù)的封裝。我們接著來(lái)分析 __perf_event_overflow() 函數(shù)的實(shí)現(xiàn):

          static int
          __perf_event_overflow(struct perf_event *event, int nmi, int throttle,
                                struct perf_sample_data *data, struct pt_regs *regs)
          {
              ...
              perf_event_output(event, nmi, data, regs);

              return ret;
          }

          從上面代碼可知,__perf_event_overflow() 會(huì)調(diào)用 perf_event_output() 函數(shù)來(lái)進(jìn)行數(shù)據(jù)采樣。perf_event_output() 函數(shù)的實(shí)現(xiàn)如下:

          static void
          perf_event_output(struct perf_event *event, int nmi,
                            struct perf_sample_data *data,
                            struct pt_regs *regs)

          {
              struct perf_output_handle handle;
              struct perf_event_header header;

              // 進(jìn)行數(shù)據(jù)采樣,并且把采樣到的數(shù)據(jù)保存到data變量中
              perf_prepare_sample(&header, data, event, regs);
              ...

              // 把采樣到的數(shù)據(jù)保存到環(huán)形緩沖區(qū)中
              perf_output_sample(&handle, &header, data, event);
              ...
          }

          perf_event_output() 函數(shù)會(huì)進(jìn)行兩個(gè)操作:

          • 調(diào)用 perf_prepare_sample() 函數(shù)進(jìn)行數(shù)據(jù)采樣,并且把采樣到的數(shù)據(jù)保存到 data 變量中。
          • 調(diào)用 perf_output_sample() 函數(shù)把采樣到的數(shù)據(jù)保存到環(huán)形緩沖區(qū)中。

          我們來(lái)看看 perf 是怎么把采樣到的數(shù)據(jù)保存到環(huán)形緩沖區(qū)的:

          void
          perf_output_sample(struct perf_output_handle *handle,
                             struct perf_event_header *header,
                             struct perf_sample_data *data,
                             struct perf_event *event)

          {
              u64 sample_type = data->type;
              ...

              // 1. 保存當(dāng)前IP寄存器地址(用于獲取正在執(zhí)行的函數(shù))
              if (sample_type & PERF_SAMPLE_IP)
                  perf_output_put(handle, data->ip);

              // 2. 保存當(dāng)前進(jìn)程ID
              if (sample_type & PERF_SAMPLE_TID)
                  perf_output_put(handle, data->tid_entry);

              // 3. 保存當(dāng)前時(shí)間
              if (sample_type & PERF_SAMPLE_TIME)
                  perf_output_put(handle, data->time);
              ...

              // n. 保存函數(shù)的調(diào)用鏈
              if (sample_type & PERF_SAMPLE_CALLCHAIN) {
                  if (data->callchain) {
                      int size = 1;

                      if (data->callchain)
                          size += data->callchain->nr;

                      size *= sizeof(u64);

                      perf_output_copy(handle, data->callchain, size);
                  } else {
                      u64 nr = 0;
                      perf_output_put(handle, nr);
                  }
              }
              ...
          }

          perf_output_sample() 通過(guò)調(diào)用 perf_output_put() 函數(shù)把用戶感興趣的數(shù)據(jù)保存到環(huán)形緩沖區(qū)中。

          用戶感興趣的數(shù)據(jù)是在創(chuàng)建事件時(shí)指定的,例如,如果我們對(duì)函數(shù)的調(diào)用鏈感興趣,那么可以在創(chuàng)建事件時(shí)指定 PERF_SAMPLE_CALLCHAIN 標(biāo)志位。

          perf 事件可以通過(guò) pref_event_open() 系統(tǒng)調(diào)用來(lái)創(chuàng)建,關(guān)于 pref_event_open() 系統(tǒng)調(diào)用的使用,讀者可以自行參考相關(guān)的資料。

          當(dāng) perf 把采樣的數(shù)據(jù)保存到環(huán)形緩沖區(qū)后,用戶就可以通過(guò) mmap() 系統(tǒng)調(diào)用把環(huán)形緩沖區(qū)的數(shù)據(jù)映射到用戶態(tài)的虛擬內(nèi)存地址來(lái)進(jìn)行讀取。由于本文只關(guān)心數(shù)據(jù)采樣部分,所以 perf 的其他實(shí)現(xiàn)細(xì)節(jié)可以參考 perf 的源代碼。

          數(shù)據(jù)采樣的流程如下圖所示:

          總結(jié)

          本文主要介紹了 perf 的 CPU 時(shí)鐘事件的實(shí)現(xiàn)原理,另外 perf 除了需要內(nèi)核支持外,還需要用戶態(tài)應(yīng)用程序支持,例如:把采樣到的原始數(shù)據(jù)生成可視化的數(shù)據(jù)或者使用圖形化表現(xiàn)出來(lái)。

          當(dāng)然,本文主要是介紹 perf 在內(nèi)核中的實(shí)現(xiàn),用戶態(tài)的程序可以參考 Linux 源碼 tools/perf 目錄下的源代碼。

          當(dāng)然,perf 是非常復(fù)雜的,本文也忽略了很多細(xì)節(jié)(如果把所有細(xì)節(jié)都闡明,那么篇幅將會(huì)非常長(zhǎng)),所以讀者如果有什么疑問(wèn)也可以留言討論。


          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  免费国产无码电影 | 操骚逼视频免费试看 | 手机无码视频在线观看 | 五月天婷婷激情网 | 91麻豆产精品久久久久久 |