В современных десктопных и (в особенности) мобильных процессорах применяется целый рад энергосберегающих технологий: ODCM, CxE, EIST и др. Сегодня нас будет интересовать, пожалуй, самая высокоуровневая из них: гибкое управление частотой и напряжением процессорного ядра во время работы — Cool 'n' Quiet, PowerNow! у AMD и Enhanced SpeedStep (EIST) у Intel.
Чаще всего пользователю компьютера или ноутбука достаточно просто включить (чаще — не выключать) поддержку той или иной технологии в BIOS и/или операционной системе — никакой тонкой настройки обычно не предусмотрено, хотя, как показывает практика, она может оказаться весьма полезной. В этой статье я расскажу о том, как можно управлять рабочим напряжением ядра процессора из операционной системы (на примере Intel Pentium M и FreeBSD), и зачем это может понадобиться.
Несмотря на большое количество руководств, редко где встречается обстоятельное описание технологии Enhanced SpeedStep с точки зрения операционной системы (а не конечного пользователя), особенно на русском языке, поэтому значительная часть статьи посвящена деталям реализации и носит в некоторой степени теоретический характер.
Надеюсь, статья окажется полезной не только пользователям FreeBSD: мы также немного коснемся GNU/Linux, Windows и Mac OS X. Впрочем, в данном случае конкретная операционная система имеет второстепенное значение.
В прошлом году я проапгрейдил процессор в своем стареньком ноутбуке: поставил Pentium M 780 вместо штатного 735-го, добил до максимума, так сказать. Ноут стал больше греться под нагрузкой (за счет возросшего на 10 Вт тепловыделения); я не особо обращал на это внимание (разве что на всякий случай почистил и смазал кулер), но в один прекрасный день, во время длительной компиляции компьютер… просто выключился (температура таки-достигла критических ста градусов). Я вывел значение системной переменной hw.acpi.thermal.tz0.temperature
в трей, чтобы понаблюдать за температурой и, если что, вовремя прервать «тяжелую» задачу. Но через какое-то время я потерял бдительность (температура всегда оставалась в пределах нормы), и все повторилось. В этот момент я решил, что больше не хочу ни постоянно опасаться аварийного выключения во время длительной нагрузки CPU и держать руку на Ctrl-C, ни насиловать процессор.
Обычно изменение штатного напряжения подразумевает его повышение с целью обеспечить стабильную работу процессора при разгоне (т.е. на повышенной частоте). Грубо говоря, каждому значению напряжения соответствует некоторый диапазон частот, на которых он может работать, и задача оверклокера — найти максимальную частоту, на которой процессор еще не «глючит». В нашем случае задача стоит в некотором смысле симметричная: для известной частоты (точнее, как мы вскоре выясним, набора частот) найти наименьшее напряжение, обеспечивающее стабильную работу CPU. Понижать же рабочую частоту не хочется, чтобы не потерять в производительности — ноут и так уже далеко не топовый. Кроме того, понижать напряжение выгоднее.
Немного теории
Как известно, тепловыделение процессора пропорционально его емкости, частоте и квадрату напряжения (кому интересно, почему это так, могут попробовать вывести зависимость самостоятельно, рассмотрев процессор как набор элементарных CMOS-инверторов (логических отрицателей), либо сходить по ссылкам: раз, два, три).
Современные мобильные процессоры могут потреблять до 50-70 Вт, которые в итоге рассеивают в тепло. Это очень много (вспомните лампы накаливания), особенно для ноутбука, который в автономном режиме под нагрузкой будет «кушать» аккумулятор как та свинья апельсины. В условиях ограниченного пространства тепло, скорее всего, придется отводить активно, а это означает дополнительный расход энергии на вращение вентилятора кулера (возможно, нескольких).
Естественно, такое положение дел никого не устраивало, и производители процессоров стали думать, как бы оптимизировать энергопотребление (и, соответственно, теплоотдачу), а заодно и предотвратить процессор от перегрева. Интересующимся рекомендую к прочтению ряд замечательных статей Дмитрия Беседина, а я тем временем перейду непосредственно к делу.
Немного истории
Впервые технология SpeedStep (версия 1.1) появилась во втором поколении третьих пентиумов (производимые по .18 мкм техпроцессу мобильные Coppermine для ноутбуков, 2000 г.), которые в зависимости от нагрузки или источника питания компьютера — сеть или аккумулятор — могли переключаться между высокой и низкой частотами за счет переменного множителя. В экономном режиме процессор потреблял примерно вдвое меньше энергии.
С переходом на .13 мкм техпроцесс технология получает номер версии 2.1 и становится «улучшенной» (enhanced) — теперь процессор умеет понижать не только частоту, но и напряжение. Версия 2.2 — адаптация для архитектуры NetBurst, а к третьей версии (платформа Centrino) технология станет официально называться Enhanced Intel SpeedStep (EIST).
Версия 3.1 (2003 г.) впервые применяется в первом и втором поколениях процессоров Pentium M (ядра Banias и Dothan). Частота варьировалась (сначала — лишь переключалась между двумя значениями) от 40% до 100% от базовой, с шагом 100 МГц (для Banias) или 133 МГц (для Dothan; наш случай). Одновременно Intel вводит динамическое управление емкостью кэша второго уровня (L2), что позволяет еще лучше оптимизировать энергопотребление. Версия 3.2 (Enhanced EIST) — адаптация для многоядерных процессоров с общим L2-кэшем. (Небольшой FAQ от Intel по технологии SpeedStep.)
Теперь, вместо того, чтобы слепо следовать многочисленным howto и туториалам, скачаем pdf'ку и попробуем разобраться в принципе работы EST (я буду дальше использовать эту аббревиатуру, т.к. она универсальнее и короче).
Как работает EST
Итак, EST позволяет управлять производительностью и энергопотреблением процессора, причем динамически, во время его работы. В отличие от более ранних реализаций, которые требовали аппаратной поддержки (в чипсете) для изменения рабочих параметров процессора, EST позволяет программно, т.е. средствами BIOS или операционной системы, изменять множитель (отношение частоты процессора к частоте шины) и напряжение ядра (Vcc) в зависимости от типа источника питания компьютера, температурного режима CPU или настроек (политики) ОС.
Во время работы процессор находится в одном нескольких состояний (power states): T (throttle), S (sleep), C (idle), P (performance), переключаясь между ними по определенным правилам (с. 386 спецификации ACPI 5.0).

Каждый процессор, присутствующий в системе, должен быть описан в DSDT, чаще всего в пространстве имен _PR
, и обычно предоставляет ряд методов, через которые реализуется взаимодействие с операционной системой (драйвером PM) и которые описывают возможности процессора (_PDC
, _PPC
), поддерживаемые состояния (_CST
, _TSS
, _PSS
) и управление ими (_PTC
, _PCT
). Нужные значения для каждого CPU определяются BIOS'ом материнской платы, который заполняет соответствующие таблицы ACPI (с. 11 pdf'ки) при загрузке машины.
EST управляет работой процессора в P-состоянии (P-state), оно-то и будет нас интересовать. Так, Pentium M поддерживает шесть P-состояний (см. рис. 1.1 и таб. 1.6 pdf'ки), отличающихся напряжением и частотой:

В общем случае, когда процессор заранее неизвестен, единственным более-менее надежным (и рекомендуемым Intel) методом работы с ним является ACPI. С конкретным процессором можно взаимодействовать напрямую, минуя ACPI — через регистры MSR (Model-Specific Register), с которыми можно работать непосредственно из командной строки: начиная с версии 7.2, во FreeBSD для этого используется утилита cpucontrol(8)
.
Узнать, поддерживает ли ваш процессор EST, можно, взглянув на 16-й бит в регистре IA_32_MISC_ENABLE
(0x1A0), он должен быть установлен:
# kldload cpuctl
# cpucontrol -m 0x1a0 /dev/cpuctl0 | (read _ msr hi lo ; echo $((lo >> 16 & 1)))
1
Аналогичная команда для GNU/Linux (потребуется пакет msr-tools):
# modprobe msr
# echo $((`rdmsr -c 0x1a0` >> 16 & 1))
1
Переход между состояниями происходит при записи в регистр IA32_PERF_CTL
(0x199). Узнать текущий режим работы можно, прочитав регистр IA32_PERF_STATUS
(0x198), который обновляется динамически (таб. 1.4 pdf'ки). В дальнейшем префикс IA32_ я буду для краткости опускать.
Попробуем для начала прочитать текущее значение PERF_STATUS
:
# cpucontrol -m 0x198 /dev/cpuctl0
MSR 0x198: 0x0612112b 0x06000c20
Из документации следует, что текущее состояние кодируется в нижних 16 битах (если выполнить команду несколько раз, их значение может меняться — это означает, что EST работает). Если посмотреть внимательнее на остальные биты, в них тоже явно не мусор. Погуглив, можно выяснить, что же они означают.
Структура регистра PERF_STATUS
Данные, читаемые из PERF_STATUS
, представляются следующей структурой (положим, что данные хранятся как little-endian):
struct msr_perf_status {
unsigned A : 16; /* Current PSV */
unsigned B : 8; /* Status flags */
unsigned C : 8; /* Minimum multiplier */
unsigned D : 16; /* Maximum PSV */
unsigned E : 16; /* Power-on PSV */
};
A, D, и E — это 16-битные, так называемые Performance State Values (PSV), их структуру мы рассмотрим ниже. A — это текущее значение PSV, D — максимальное (зависит от процессора), E — значение на старте системы (при включении). Текущее значение (A), очевидно, меняется при изменении режима работы, максимальное (D) обычно остается постоянным, стартовое значение (E) не меняется: как правило, оно равно максимальному значению для десктопов и серверов, но минимальному для мобильных CPU. C — это минимальный множитель, для процессоров Intel почти всегда равен шести. B содержит значение некоторых флагов, например, при наступлении событий EST или THERM (т.е. в момент изменения P-состояния или перегрева процессора, соответственно).
Теперь, когда мы знаем назначение всех 64 бит регистра PERF_STATUS
, мы можем расшифровать прочитанное выше слово: 0x0612112b 0x06000c20 ⇒ PSV на старте 0x0612, максимальное значение 0x112b, минимальный множитель 6 (как и ожидалось), флаги сброшены, текущее значение PSV = 0x0c20. Что именно означают эти 16 бит?
Структура Performance State Value (PSV)
Знать и понимать, что из себя представляет PSV, очень важно, ведь именно в таком виде задаются режимы работы процессора.
struct psv {
unsigned vid : 6; /* Voltage Identifier */
unsigned _reserved1 : 2;
unsigned freq : 5; /* Frequency Identifier */
unsigned _reserved2 : 1;
unsigned nibr : 1; /* Non-integer bus ratio */
unsigned slfm : 1; /* Dynamic FSB frequency (Super-LFM) */
};
Dynamic FSB frequency switching указывает пропускать каждый второй такт FSB, т.е. вдвое понижать рабочую частоту; эта возможность впервые реализована в процессорах Core 2 Duo (ядро Merom) и нас не касается, как и Non-integer bus ratio — специальный режим, поддерживаемый некоторыми процессорами, позволяющий, как следует из названия, более тонко управлять их частотой.
К собственно технологии EST имеют отношения два поля — идентификаторы частоты (Frequency Identifier, Fid), который численно равен множителю, и напряжения (Voltage Identifier, Vid), который соответствует уровню напряжения (он же обычно и наименее документирован).
Идентификатор напряжения (Voltage Identifier)
Intel весьма неохотно раскрывает информацию (обычно требуется подписать NDA) о том, как именно кодируется идентификатор напряжения для каждого процессора. Но для большинства популярных CPU, к счастью, эта формула известна; в частности, для нашего Pentium M (и многих других): Vcc = Vid0 + (Vid × Vstep), где Vcc — текущее (действительное) напряжение, Vid0 — базовое напряжение (когда Vid == 0), Vstep — шаг. Таблица для некоторых популярных процессоров (все значения в милливольтах):
Процессор | Vid0 | Vstep | Vboot | Vmin | Vmax |
---|---|---|---|---|---|
Pentium M | 700,0 | 16,0 | xxxx,x | xxx,x | xxxx,x |
E6000, E4000 | 825,0 | 12,5 | 1100,0 | 850,0 | 1500,0 |
E8000, E7000 | 825,0 | 12,5 | 1100,0 | 850,0 | 1362,5 |
X9000 | 712,5 | 12,5 | 1200,0 | 800,0 | 1325,0 |
T9000 | 712,5 | 12,5 | 1200,0 | 750,0 | 1300,0 |
P9000, P8000 | 712,5 | 12,5 | 1200,0 | 750,0 | 1300,0 |
Q9000D, Q8000D | 825,0 | 12,5 | 1100,0 | 850,0 | 1362,5 |
Q9000M | 712,5 | 12,5 | 1200,0 | 850,0 | 1300,0 |
Множитель (т.е. Fid) записывается в PSV сдвинутым на 8 бит влево, младшие шесть бит занимает Vid. Т.к. в нашем случае остальными битами можно пренебречь, то PSV, частота процессора, системной шины и физическое напряжение связаны простой формулой (для Pentium M):

Теперь рассмотрим регистр управления (PERF_CTL
). Запись в него должна производиться следующим образом: считывается текущее значение (64-битное слово целиком), в нем изменяются нужные биты, и записывается обратно в регистр (т.н. read-modify-write).
Структура регистра PERF_CTL
struct msr_perf_ctl {
unsigned psv : 16; /* Requested PSV */
unsigned reserved1 : 16;
unsigned ida_diseng : 1; /* IDA disengage */
unsigned reserved2 : 31;
};
IDA (Intel Dynamic Acceleration) disengage-бит позволяет временно отключать адаптивное (opportunistic) управление частотой (на процессорах Intel Core 2 Duo T7700 и более поздних, — опять же, нас не интересует). Младшие 16 бит (PSV) — режим, в который мы «просим» перейти процессор.
Таблица _PSS
Таблица _PSS
представляет собой массив состояний (Package в терминологии ACPI), либо метод, который возвращает такой массив; каждое состояние (P-state), в свою очередь определяется следующей структурой:
struct Pstate {
unsigned CoreFrequency; /* Core CPU operating frequency, MHz */
unsigned Power; /* Maximum power dissipation, mW */
unsigned Latency; /* Worst-case latency of CPU unavailability during transition, µs */
unsigned BusMasterLatency; /* Worst-case latency while Bus Masters are unable to access memory, µs */
unsigned Control; /* Value to be written to the PERF_CTL to switch to this state */
unsigned Status; /* Value (should be equal to the one read from PERF_STATUS) */
};
Таким образом, каждое P-состояние характеризуется какой-то рабочей частотой ядра, максимальной рассеиваемой мощностью, транзитными задержками (фактически это время перехода между состояниями, в течении которых недоступны CPU и память), наконец, самое интересное: PSV, которое соответствует данному состоянию и которое надо записать в PERF_CTL
, чтобы в это состояние перейти (Control). Чтобы убедиться, что процессор успешно перешел в новое состояние, можно прочитать регистр PERF_STATUS
и сравнить со значением, записанным в поле Status.
EST-драйвер операционной системы «знает» про некоторые процессоры, т.е. сумеет ими управлять и без поддержки ACPI. Но это редкость, особенно в наши дни (хотя для undervolting'а на Linux, где-то до версии 2.6.20, надо было патчить таблицы в драйвере, и еще в 2011 г. этот метод был весьма распространен).
Стоит отметить, что EST-драйвер может работать даже в случае отсутствия таблицы _PSS
и неизвестного процессора, т.к. максимальное и минимальное значения можно узнать из PERF_STATUS
(при этом, очевидно, число P-состояний вырождается в два).
Довольно теории. Что с этим всем делать?
Теперь, когда мы знаем 1) назначение всех битов в нужных словах MSR, 2) как именно кодируется PSV для нашего процессора, и 3) где в DSDT искать нужные настройки, самое время составить таблицу частот и напряжений по умолчанию. Сдампим DSDT и поищем там таблицу _PSS
. Для Pentium M 780 она должна выглядеть примерно так:
Name (_PSS, Package (0x06) { // Всего определено 6 состояний (P-states)
Package (0x06) {
0x000008DB, // 2267 MHz (cf. Fid × FSB clock)
0x00006978, // 27000 mW
0x0000000A, // 10 µs (соответствует спецификации)
0x0000000A, // 10 µs
0x0000112B, // 0x11 = 17 (множитель, Fid), 0x2b = 43 (Vid)
0x0000112B
},
Package (0x06) {
0x0000074B, // 1867 MHz (82% от максимальной)
0x000059D8, // 23000 mW
0x0000000A,
0x0000000A,
0x00000E25, // Fid = 14, Vid = 37
0x00000E25
},
Package (0x06) {
0x00000640, // 1600 MHz (71% от максимальной)
0x00005208, // 21000 mW
0x0000000A,
0x0000000A,
0x00000C20, // Fid = 12, Vid = 32
0x00000C20
},
Package (0x06) {
0x00000535, // 1333 MHz (59% от максимальной)
0x00004650, // 18000 mW
0x0000000A,
0x0000000A,
0x00000A1C, // Fid = 10, Vid = 28
0x00000A1C
},
Package (0x06) {
0x0000042B, // 1067 MHz (47% от максимальной)
0x00003E80, // 16000 mW
0x0000000A,
0x0000000A,
0x00000817, // Fid = 8, Vid = 23
0x00000817
},
Package (0x06) {
0x00000320, // 800 MHz (35% от максимальной)
0x000032C8, // 13000 mW
0x0000000A,
0x0000000A,
0x00000612, // Fid = 6, Vid = 18
0x00000612
}
})
Итак, мы знаем дефолтные Vid для каждого P-уровня: 43, 37, 32, 28, 23, 18, что соответствует напряжениям от 1388 mV до 988 mV. Суть undervolting'а в том, что наверняка эти напряжения несколько выше, чем реально необходимо для устойчивой работы. Попробуем определить «границы дозволенного».
Я написал для этого простой shell-скрипт, который постепенно понижает Vid и выполняет несложный цикл (демон powerd(8)
перед этим, разумеется, необходимо прибить). Таким образом я определил напряжения, позволяющие процессору хотя бы не виснуть, затем прогнал несколько раз тест Super Pi и пересборку ядра; уже позже я поднял значение Vid для двух максимальных частот на один пункт, иначе gcc изредка вылетал из-за illegal instruction. В результате всех экспериментов в течении нескольких дней получился такой набор “стабильных” Vid: 30, 18, 12, 7, 2, 0.
Анализ результатов
Теперь, когда мы эмпирически определили минимальные безопасные напряжения, интересно сравнить их с исходными:
Частота (множитель) | Vidold | Vidnew | Изменение Vcc |
---|---|---|---|
2267 (17) | 43 | 30 | -15% |
1867 (14) | 37 | 18 | -24% |
1600 (12) | 32 | 12 | -26% |
1333 (10) | 28 | 7 | -29% |
1067 (8) | 23 | 2 | -31% |
800 (6) | 18 | 0 | -29% |
Понижение максимального напряжения даже на 15% принесло довольно ощутимые результаты: длительная нагрузка не только не приводит больше к перегреву процессора и аварийному отключению, температура вообще теперь почти никогда не превышает 80°. Прогнозируемое время работы от аккумулятора, судя по acpiconf -i 0
, увеличилось с 1 ч. 40 м. до 2 ч. 25 м. (Не ахти как много, но литий-ионные элементы со временем «устают», а аккумулятор я не менял с момента покупки ноута лет семь тому назад.)
Теперь надо сделать так, чтобы настройки применялись автоматически. Можно, например, модифицировать драйвер cpufreq(4)
, чтобы значения PSV брались из собственной таблицы, а не через ACPI. Но это неудобно уже хотя бы тем, что придется каждый раз патчить драйвер при обновлении системы, да и вообще — больше похоже на грязный хак, чем на решение. Можно, наверное, еще пропатчить powerd(8)
, что плохо примерно по тем же причинам. Можно просто запускать скрипт, понижая напряжение прямой записью в MSR (что, собственно, я и делал для определения «стабильных» напряжений), но тогда придется помнить о и самостоятельно обрабатывать переходы между состояниями (не только P-states, вообще любыми, например, при выходе ноутбука из сна). Тоже не дело.
Если мы получаем значения PSV через ACPI, то логичнее всего изменить именно таблицу _PSS
в DSDT. К счастью, BIOS для этого ковырять не придется: FreeBSD умеет загружать DSDT из файла (про модификацию DSDT я уже как-то писал, поэтому сейчас подробно на этом останавливаться не буду). Заменяем нужные поля в DSDT, компилируем новый AML-файл (байткод ACPI) и прописываем в : loader.conf
acpi_dsdt_load="YES"
acpi_dsdt_name="/root/undervolt.aml"
Вот, в общем, и все. Единственное, не забудьте закомментировать эти две строчки в , если будете менять процессор. loader.conf
Даже если вы не собираетесь понижать штатные напряжения, умение настраивать управление состояниями процессора (не только P-states) может пригодиться. Ведь нередко бывает, что «кривой» BIOS заполняет таблицы неверно, не полностью, или не заполняет их вовсе (например потому, что стоит целерон, а производитель официально не предусматривает его замену). В этом случае вам придется проделать всю работу самостоятельно. Обратите внимание, что добавить одну лишь таблицу _PSS
может оказаться недостаточно; так, C-states задаются таблицей _CST
, и кроме того, может потребоваться описать сами процедуры управления (Performance Control, _PCT
). К счастью, это несложно и довольно подробно, с примерами, описано в 8-й главе спецификации ACPI.
Undervolting в GNU/Linux
По правде говоря, сначала я думал, что мне достаточно будет прочитать Gentoo Undervolting Guide и просто адаптировать его для FreeBSD. Это оказалось не так-то просто, ибо документ на поверку оказался на редкость бестолковым (что вообще-то странно для Gentoo Wiki). К сожалению, на их новом сайте я ничего похожего не нашел, пришлось довольствоваться старой копией; и хотя я понимаю, что это руководство во многом потеряло актуальность, я все же его немного покритикую. :-)
Мне почему-то сразу, без объявления войны, предлагается патчить ядро (во FreeBSD, на минуточку, нам вообще никакой системный код модифицировать не пришлось). Забивать во внутренности драйвера или записывать в какие-то init-скрипты значения неких «безопасных» напряжений, непонятно кем и каким образом полученные, из специальной таблицы (в которой Pentium M 780 издевательски представлен строкой, состоящей из одних вопросительных знаков). Следовать советам, среди которых есть написанные людьми, которые явно вообще не понимают, о чем говорят. А главное, совершенно неясно, почему и как именно эти магические замены одних цифр на другие работают; не предлагается способа «потрогать» EST, прежде чем что-то патчить и пересобирать ядро, ни разу не упоминаются регистры MSR и работа с ними из командной строки. Не рассматривается модификация таблиц ACPI как альтернативный и более предпочтительный вариант.
Еще более лаконично выглядит страница Arch Wiki. Вот эта строчка доставляет особенно:
# echo 34 26 18 12 8 5 > /sys/devices/system/cpu/cpu0/cpufreq/phc_vids
Так и просятся лостовские «4, 8, 15, 16, 23, 42» (правда, в обратном порядке, что несколько портит шутку).
Пожалуй, самое толковое описание всего процесса для Linux у Пата Эрлея, ссылку на которое я давал выше.
Undervolting в Windows и Mac OS X
Про Windows особо говорить смысла нет; есть и софт, и обсуждения на форумах; поэтому лишь пара ссылок.
Макось довольно плотно взаимодействует с (и рассчитывает на корректную работу) ACPI, и модификация таблиц — один из основных методов ее настройки под конкретное железо. Поэтому первое, что приходит в голову — точно так же сдампить и пропатчить свою DSDT. Альтернативный метод: google://IntelEnhancedSpeedStep.kext
, например, раз, два, три.
Еще одна «чудесная» утилита (к счастью, уже устаревшая) предлагает купить за $10 возможность менять напряжение и частоту. :-)
Что еще почитать
Для FreeBSD: тема на форуме, а также небезызвестное обсуждение в рассылке; исходное письмо Саши Мотина для удобства викифицировано. Для Linux можно начать с неплохой статьи в ArchWiki.
Для тех, кто хочет углубиться в тему, кроме официальной документации производителей процессоров и приведенных в тесте ссылок, вот здесь — отличная подборка материалов (исследовательских статей, презентаций) по широкому кругу вопросов управления энергопотреблением (осторожно, Comic Sans).
Автор: danfe