netlib.narod.ru< Назад | Оглавление | Далее >

printk

Printk (строка 25836) представляет собой внутреннюю функцию поддержки журнала сообщений ядра. При генерации какого-либо сообщения, например, когда ядро обнаруживает несовместимость в своих структурах данных, функция printk вызывается для отображения соответствующей информации на системной консоли. Обращения к printk попадают под одну из следующих категорий:

Просмотрев упомянутые выше строки кода, несложно убедиться, что аргументы printk подобны аргументам printf: строка формата, за которой следует 0 или более аргументов. Строка формата может начинаться с последовательности символов в форме «<N>», где N — цифра, от 0 до 7 включительно. Цифра определяет уровень регистрации сообщения; сообщение будет выводиться только если этот уровень меньше текущего уровня, определенного для консоли (console_loglevel, строка 25650). Уровень для консоли можно снижать, тем самым отфильтровывая менее важные сообщения. Если в строке формата не задается ни одного уровня регистрации, сообщение будет выводиться всегда. (В настоящий момент уровень регистрации не должен обязательно присутствовать в строке формата — он отыскивается в форматированном тексте.)

Блок конструкций #define, начинающийся в строке 14946, присваивает имена специальным последовательностям, что упрощает использование printk. Так уж вышло, что уровни с 0 по 4 относятся к тем, которые я называю «аварийными ситуациями», уровни 5 и 6 — к «общей информации», а 7 — к «отладке».

Обратим свой взгляд на код.

printk

Цикл for, определенный в строке 25869, мог работать быстрее, если бы не объем работы, выполняемый над каждым символом. Небольшое ускорение можно получить за счет лишь однократного обновления logged_chars после завершения цикла. Однако мы должны попытаться достигнуть большего. Размер сообщения известен заранее, поэтому log_size и log_start не должны увеличиваться до конца цикла. Вот как простенько можно ускорить цикл:

do {
  static int wrapped = 0;
  const int x = wrapped
    ?  log_start
    :  log_size;
  const int lim = LOG_BUF_LEN - x;
  int n = buf_end - p;
  if (n >= lim)
    n = lim;

  memcpy (log_buf+x, p, n);
  p += n;

  if (log_size < LOG_BUF_LEN)
    log_size += n;
  else {
    wrapped = 1;
    log_start += n;
    log_start &= LOG_BUF_LEN - 1;
  }
} while (p < buf_end);

Не следует забывать, что цикл, как правило, выполняется один раз; большее количество выполнений цикла имеет место тогда, когда запись по достижении конца log_buf переходит на начало. Следовательно, log_size и log_buf обновляются только один раз (или два, если случается переход на начало).

Ускорение достигнуто, но мы не будем поступать таким образом по двум причинам. Во-первых, ядро имеет собственную версию memcpy, и мы должны быть четко уверены, что вызов memcpy никогда не приведет к возврату в printk. (Некоторые версии ядра определяют собственные более быстрые версии memcpy, поэтому они должны быть согласны с последним утверждением.) Если memcpy обращается к printk для выдачи уведомления об ошибке, есть риск попасть в бесконечный цикл.

Однако не это самая большая проблема. Самое печальное, что версия цикла, реализованная в ядре, отслеживает также символы новой строки, так что применение memcpy для копирования всего сообщения в log_buf оказывается некорректным — при появлении символа новой строки, он попросту «перепрыгивается».

Можно попытаться убить двух зайцев одним выстрелом. Приведенная ниже замена оказывается несколько медленнее рассмотренной ранее, однако она не противоречит семантике существующей версии ядра:

/* В разделе объявлений */
int n;
char * start;
static char * log = log_buf;
/* . . . */

for (start = p; p < buf_end; p++) {
  *log++ = *p;
  if (log >= (log_buf + LOG_BUF_LEN))
    log = log_buf;   /* Wrap. */
  if (*p == '\n') {
    line_feed = 1;
    break;
  }
}

/* p - start представляет количество копируемых символов */
n = p - start;
logged_chars += n;
/*
 * Задание для читателя:
 * Воспользуйтесь n для обновления log_size и log_start
 * (Это не так просто, как может показаться на первый взгляд)
 */

(Следует отметить, что оптимизатор gcc достаточно интеллектуален, чтобы определить, что выражение log_buf + LOG_BUF_LEN внутри цикла не изменяется, поэтому никакого выигрыша от выноса этого выражения за пределы цикла не будет.)

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


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz