8.7.3 Kernel Function Instrumentation

Предыдущая  Содержание  Следующая V*D*V

В последних двух разделах мы обсудили методы профилирования приложений пользовательского пространства. В этом разделе мы рассмотрим Kernel Function Instrumentation (KFI), инструмент для профилирования функций ядра. (Для профилирования ядра вы также можете использовать OProfile.)

KFI может быть использован для измерения времени, затраченного любой функцией ядра. Он основан на функциях контроля и возможностях профилирования GCC, используя флаг GCC -finstrument-functions. (* GCC ожидает, что пользователь определит две функции: __cyg_profile_func_enter и __cyg_profile_func_exit. Когда используется флаг -finstrument-functions, эти функции добавляются GCC на входе и выходе из функции, соответственно. KFI определяет эти две функции, в которых он собирает данные профилирования.) KFI доступен в виде патча ядра и набора утилит для ядер версии 2.4 и 2.6. Патч ядра добавляет поддержку для генерации данных профилировщика и утилиты, используемые для последующего анализа данных профилирования. Загрузите их с www.celinuxforum.org. Примените патч ядра и включите при сборке ядра опцию CONFIG_KFI. Во время конфигурации ядра вы также можете решить сделать KFI работающим статически, если вы хотите измерять время загрузки ядра и время, затраченное различными функциями ядра во время загрузки. Это достигается включением флага конфигурации KFI_STATIC_RUN. В этом разделе мы обсудим динамически работающий KFI; то есть мы собираем данные профилирования, когда ядро запущено и работает. Для шагов, которые необходимо сделать для работы статически, пожалуйста, следуйте инструкциям в файле README.kfi, который является частью пакета KFI.

 

Набор инструментальных средств KFI

 

Прежде чем перейти к использованию KFI, мы должны узнать о различных инструментах, которые являются частью набора инструментов KFI. Набор инструментов KFI состоит из трёх утилит: kfi, kfiresolve и kd. Триггеры kfi профилируют сбор данных в ядре. kfiresolve и kd изучают собранные данные и представляют их пользователю в удобном для чтения формате.

 

Вам необходима кросс-компиляция программы kfi для вашей целевой платформы. Командами kfi являются:

 

# ./kfi

Usage: ./kfi <cmds>

commands: new, start, stop, read [id], status [id], reset

 

new: начало новой сессии профилирования. Каждый сеанс опознаётся по идентификатору выполнения.

start: запуск профилирования.

stop: остановка профилирования.

read: чтение данных профилировщика из ядра.

status: проверка состояние вашего сеанса профилирования.

reset: сброс KFI.

 

Большую часть времени вы не хотите профилировать всё ядро целиком. Вы можете просто хотеть профилировать обработчики прерываний или какие-то специфические функции ядра. KFI обеспечивает фильтры для выборочного профилирования. Прежде чем начать сессию профилировщика, необходимо установить соответствующие фильтры. Чтобы настроить фильтры, необходимо модифицировать программу kfi. Исходный код программы kfi находится в файле kfi.c, а определения структур находится в kfi.h.

Каждый сеанс профилирования связан со struct kfi_run, которая содержит все необходимые детали сессии.

 

typedef struct kfi_run {

    ...

  struct kfi_filters filters;

    ...

} kfi_run_t;

 

Фильтры могут быть определены путём установки соответствующим образом полей структуры kfi_filters_t.

 

typedef struct kfi_filters {

  unsigned long min_delta;

  unsigned long max_delta;

  int no_ints;

  int only_ints;

 

  void** func_list;

  int func_list_size;

    ...

} kfi_filters_t;

 

Полями kfi_filters_t являются:

 

min_delta: не профилировать функции с временем выполнения меньшим, чем min_delta.

max_delta: не профилировать функции с временем выполнения большим, чем max_delta.

no_ints: не профилировать функции, которые выполняются в контексте прерываний.

only_ints: профилировать функции, которые выполняются в контексте прерываний.

func_list: профилировать функции, указанные в списке.

func_list_size: если задан func_list, то func_list_size содержит число записей в func_list.

 

myrun в kfi.c представляет собой структуру типа kfi_run_t.

 

struct kfi_run myrun = {

  0, 0,

  { 0 },

  { 0 },

  { 0 }, /* фильтры struct kfi_filters_t */

  myrunlog, MAX_RUN_LOG_ENTRIES, 0,

  0,

  NULL,

};

 

Как видите, все поля членов этого фильтра установлены в ноль. Вы должны изменить члены фильтров в соответствии с вашими потребностями. Например, предположим, что необходимо профилировать функцию ядра do_fork. Чтобы создать соответствующий фильтр, должны быть выполнены следующие шаги:

 

1.Откройте файл System.map вашего ядра и найдите do_fork.
 
....
c0119751 T do_fork
....
 

2.Определите func_list.
 
void *func_list[] = {0xc0119751};
 

3.Модифицируйте члены фильтра myrun.
 
struct kfi_run myrun = {
 ...
{ 0,0,0,0,func_list,1 },
 ...
};
 

4.Перекомпилируйте kfi.c и сгенерируйте kfi.

 

Аналогичным образом могут быть установлены и другие фильтры. Сгенерированные профилировщиком данные могут быть исследованы с помощью двух сценариев на языке Phython, kfiresolve и kd. Мы обсуждаем их использование в следующем разделе.

 

Использование KFI

 

В этом разделе мы обсудим с помощью примера динамическую работу KFI. Мы профилируем функцию ядра do_fork. Чтобы создать фильтры на базе функций, сделаны все изменения, упомянутые в предыдущем разделе. Работа происходит с ядром версии 2.6.8.1 с включённым KFI на машине P4 2.8 ГГц. На пример программы:

 

/* sample.c */

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

 

int main(int argc, char *argv[]){

  int i;

  printf("Pid = %d\n", getpid());

  for (i = 0; i < 2; i++){

    if (fork() == 0)

      exit(0);

  }

}

 

Шаги следующие:

 

1.Создаём узел устройства:
 
mknod /dev/kfi c 10 51

 

2.Чтобы по-новый запустить KFI, сбрасываем kfi.
 
./kfi reset
 

3.Создаём новую сессию KFI.
 
# ./kfi new
new run created, id = 0
 
Заметим, что если при выполнении этой команды вы получаете ошибку выделения памяти, укажите для MAX_RUN_LOG_ENTRIES в kfi.h несколько меньшее число.
 

4.Запускаем профилирование.
 
# ./kfi start
runid 0 started
 

5.Запускаем приложение.
 
# ./sample
Pid = 4050

 

6.Останавливаем профилирование.
 
./kfi stop
runid 0 stopped
 

7.Читаем данные профилировщика из ядра.
 
./kfi read 0 > sample.log
 

8.Выполняем заключительную обработку данных.
 
./kfiresolve.py sample.log /boot/System.map > sample.out

 

Результат работы профилировщика содержит файл sample.out. Все временные интервалы измеряются с точностью до микросекунды. В выводе, показанном в Распечатке 8.12, Entry это время входа в функцию, а Delta - время, затраченное на  выполнение функции. В выходных данных записи, соответствующие PID 4095, получены от нашего приложения. Две другие записи, соответствующие PID 3982, получены от оболочки.

Для анализа данных профилировщика вы также можете использовать kd.

 

# ./kd sample.out

Function                  Count Time     Average  Local

------------------------- ----- -------- -------- --------

do_fork                    4      195       48      195

 

Перенос KFI на другую платформу

 

KFI не зависит от архитектуры. Ядром KFI является функция kfi_readclock, которая возвращает текущее время в микросекундах.

 

static inline unsigned long __noinstrument kfi_readclock(void)

{

  unsigned long long t;

  t = sched_clock();

  /* переводим в микросекунды */

  do_div(t,1000);

  return (unsigned long)t;

}

 

kfi_readclock вызывает sched_clock, которая обычно определена в arch/<your-arch>/kernel/time.c.

 

/*

 * Часы планировщика — возвращают текущее время в наносекундах.

 */

unsigned long long sched_clock(void)

{

  return (unsigned long long)jiffies * (1000000000 / HZ);

}

 

Вышеописанная реализация sched_clock не зависит от архитектуры и возвращает значение, основанное на тиках. На многих встраиваемых платформах точность тиков составляет 10 мс. Таким образом, чтобы иметь лучшую точность результатов профилирования, вы должны предоставить лучшую sched_clock на основе какого-либо источника аппаратного времени, с точностью по крайней мере до микросекунды. Например, для процессоров x86 sched_clock реализуется с помощью счётчика времени с поддержкой TSC.

 

unsigned long long sched_clock(void)

{

  unsigned long long this_offset;

 

  .....

 

  /* Читаем счётчик времени */

  rdtscll(this_offset);

 

  /* Возвращаем значение в нс */

  return cycles_2_ns(this_offset);

}

 

Если вы не хотите использовать sched_clock как источник синхронизации, в качестве альтернативы вы можете написать свой собственный вариант kfi_readclock. Одна такая реализация включена в патч для PPC.

 

static inline unsigned long kfi_readclock(void)

{

  unsigned long lo, hi, hi2;

  unsigned long long ticks;

  do {

    hi = get_tbu();

    lo = get_tbl();

    hi2 = get_tbu();

  } while (hi2 != hi);

  ticks = ((unsigned long long) hi << 32) | lo;

  return (unsigned long)((ticks>>CLOCK_SHIFT) &

                                       0xffffffff);

}

 

Предыдущая  Содержание  Следующая