<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://localhost:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://localhost:4000/" rel="alternate" type="text/html" hreflang="ru" /><updated>2026-04-29T15:14:41+03:00</updated><id>http://localhost:4000/feed.xml</id><title type="html">РаботаДом</title><subtitle>Это простой сайт о моей жизни и работе в стиле WEB 1.0, то есть пишу о чем хочу :) </subtitle><author><name>Oleg Larkin</name></author><entry><title type="html">Жизнь без CubeMX: Первая прошивка для STM32, имея под рукой только блокнот</title><link href="http://localhost:4000/habr/2026/04/23/zhizn-bez-cubemx-pervaya-proshivka-dlya-stm32-imeya-pod-rukoj-tolko-bloknot.html" rel="alternate" type="text/html" title="Жизнь без CubeMX: Первая прошивка для STM32, имея под рукой только блокнот" /><published>2026-04-23T00:00:00+03:00</published><updated>2026-04-23T00:00:00+03:00</updated><id>http://localhost:4000/habr/2026/04/23/zhizn-bez-cubemx-pervaya-proshivka-dlya-stm32-imeya-pod-rukoj-tolko-bloknot</id><content type="html" xml:base="http://localhost:4000/habr/2026/04/23/zhizn-bez-cubemx-pervaya-proshivka-dlya-stm32-imeya-pod-rukoj-tolko-bloknot.html"><![CDATA[<p>Когда микроконтроллер получает питание или выходит из аппаратного сброса, выполнение программы начинается задолго до входа в <code class="language-plaintext highlighter-rouge">main()</code>. Сначала ядро Cortex-M3 загружает начальный указатель стека, затем берёт адрес обработчика сброса из векторной таблицы и только после этого запускает startup-код.</p>

<p>В минимальном bare-metal проекте без HAL и без CubeMX вся эта цепочка видна почти по шагам. Именно поэтому такой проект хорошо подходит для первого глубокого знакомства со STM32: становится понятно, что происходит в памяти, как работает линкер, зачем нужен startup и почему обычный C-код не может стартовать “сам по себе”.</p>

<p>В этой статье собирается минимальный проект с нуля:</p>

<ul>
  <li>
    <p>собственный linker script;</p>
  </li>
  <li>
    <p>startup-файл;</p>
  </li>
  <li>
    <p>ручная инициализация <code class="language-plaintext highlighter-rouge">.data</code> и <code class="language-plaintext highlighter-rouge">.bss</code>;</p>
  </li>
  <li>
    <p>настройка GPIO;</p>
  </li>
  <li>
    <p>управление встроенным светодиодом на PC13;</p>
  </li>
  <li>
    <p>запуск аппаратного таймера TIM2.</p>
  </li>
</ul>

<p>Цель здесь не просто заставить мигать светодиод. Цель — увидеть, как микроконтроллер стартует на самом низком уровне и как связаны между собой память, регистры и исполняемый код. <strong>Статья ориентирована на начинающих разработчиков.</strong> В проекте использовался микроконтроллер по цене чашки кофе <strong>STM32F103C8T6.</strong></p>

<h3 id="что-получится-в-итоге">Что получится в итоге</h3>

<p>После сборки и прошивки микроконтроллер будет выполнять простую последовательность:</p>

<ol>
  <li>
    <p>включать светодиод на PC13;</p>
  </li>
  <li>
    <p>ждать 1 секунду;</p>
  </li>
  <li>
    <p>выключать светодиод;</p>
  </li>
  <li>
    <p>снова ждать 1 секунду.</p>
  </li>
</ol>

<h3 id="структура-проекта">Структура проекта</h3>

<p>Сразу учимся разделять код на несколько слоёв:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>full_program_from_scratch/
├── inc/
│   ├── config.h
│   ├── GPIO.h
│   ├── LED.h
│   ├── main.h
│   └── Timers.h
├── src/
│   ├── config.c
│   ├── GPIO.c
│   ├── LED.c
│   ├── main.c
│   └── Timers.c
├── startup/
│   └── startup.c
├── myLinker.ld
├── firmware.elf
└── firmware.bin
</code></pre></div></div>

<h3 id="базовая-идея-bare-metal-подхода">Базовая идея bare-metal подхода</h3>

<p>В bare-metal-проекте очень часто работа с периферией сводится к прямой записи в регистры по фиксированному адресу. Самый простой вид такой записи выглядит так:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*(volatile uint32_t*)0x40021018 |= (1 &lt;&lt; 4);
</code></pre></div></div>

<p>Разберём эту строку по частям.</p>

<h4 id="volatile">volatile</h4>

<p>Ключевое слово <code class="language-plaintext highlighter-rouge">volatile</code> запрещает компилятору “умные” оптимизации вокруг этой переменной. Для обычной памяти это не всегда нужно, а для регистров периферии — обязательно. Компилятор может запомнить предыдущее значение регистра, и при следующем обращении к нему выдать пользователю то самое старое состояние. При этом регистр может измениться аппаратно, вне контроля программы, поэтому каждое обращение должно реально выполняться.</p>

<h4 id="uint32_t">uint32_t</h4>

<p>Регистр STM32F1 обычно имеет ширину 32 бита, поэтому используется именно <code class="language-plaintext highlighter-rouge">uint32_t</code>.</p>

<h4 id="0x40021018">0x40021018</h4>

<p>Это адрес регистра <code class="language-plaintext highlighter-rouge">RCC_APB2ENR</code>. Через него включается тактирование периферии на шине APB2.</p>

<h4 id="1-4">(1 « 4)</h4>

<p>Бит 4 соответствует порту GPIOC. Пока этот бит не установлен, периферия GPIOC формально существует в адресном пространстве, но не получает clock и не работает.</p>

<p>Такой способ записи выглядит грубовато, зато он очень хорошо показывает сам принцип: код не вызывает “магическую функцию библиотеки”, а меняет конкретный бит в конкретном регистре микроконтроллера.</p>

<p>Позже этот стиль можно сделать читабельнее, если описывать регистры через структуру. Для таймера TIM2 именно так и сделано: вместо голых адресов используется типизированный указатель <code class="language-plaintext highlighter-rouge">TIM2</code>.</p>

<h2 id="linker-script-карта-памяти-проекта">Linker script: карта памяти проекта</h2>

<p>Linker script объясняет линкеру, где в памяти микроконтроллера должны лежать разные части программы. Именно он связывает логические секции <code class="language-plaintext highlighter-rouge">.text</code>, <code class="language-plaintext highlighter-rouge">.data</code>, <code class="language-plaintext highlighter-rouge">.bss</code>, <code class="language-plaintext highlighter-rouge">.isr_vector</code> с физической памятью STM32.</p>

<p>У linker script в этом проекте четыре основных задачи:</p>

<ul>
  <li>
    <p>описать области памяти;</p>
  </li>
  <li>
    <p>разложить секции программы по этим областям;</p>
  </li>
  <li>
    <p>создать служебные символы для startup-кода;</p>
  </li>
  <li>
    <p>обеспечить правильный старт программы после сброса.</p>

    <p>Предлагаю взглянуть на линкер файл:</p>
  </li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_estack = = ORIGIN(RAM) + LENGTH(RAM);

MEMORY{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x00010000
    RAM   (rwx): ORIGIN = 0x20000000, LENGTH = 0x00005000
}

SECTIONS{
    .isr_vector :
    {
        KEEP(*(.isr_vector))
    } &gt; FLASH

    .text :
    {
        *(.text)
        *(.text*)
    } &gt; FLASH

    .rodata :
    {
        *(.rodata)
        *(.rodata*)
    } &gt; FLASH

    .data :
    {
        _sdata = .;  //Точный адрес в RAM
        *(.data)
        *(.data*)
        _edata = .;
    } &gt; RAM AT &gt; FLASH

    _sidata = LOADADDR(.data); //Точный адрес во FLASH памяти

    .bss :
    {
        _sbss = .;
        *(.bss)
        *(.bss*)
        _ebss = .;
    } &gt; RAM
}
</code></pre></div></div>

<h4 id="области-памяти">Области памяти</h4>

<p>Для STM32F103C8T6 используются две основные области:</p>

<table>
  <thead>
    <tr>
      <th>Область</th>
      <th>Базовый адрес</th>
      <th>Назначение</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>FLASH</td>
      <td><code class="language-plaintext highlighter-rouge">0x08000000</code></td>
      <td>код программы, константы, векторная таблица</td>
    </tr>
    <tr>
      <td>RAM</td>
      <td><code class="language-plaintext highlighter-rouge">0x20000000</code></td>
      <td>переменные, стек, <code class="language-plaintext highlighter-rouge">.data</code>, <code class="language-plaintext highlighter-rouge">.bss</code></td>
    </tr>
  </tbody>
</table>

<p>В ссылке на стек используется символ:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_estack = ORIGIN(RAM) + LENGTH(RAM);
</code></pre></div></div>

<p>Он указывает на вершину RAM и становится первым элементом векторной таблицы. Именно это значение Cortex-M3 загружает в регистр <code class="language-plaintext highlighter-rouge">SP</code> при старте.</p>

<h4 id="секции">Секции</h4>

<h3 id="isr_vector">.isr_vector</h3>

<p>Здесь лежит векторная таблица прерываний. Она должна обязательно остаться в итоговом бинарнике, поэтому используется <code class="language-plaintext highlighter-rouge">KEEP(...)</code>.</p>

<h3 id="text">.text</h3>

<p>Секция исполняемого кода. Обычно она хранится во FLASH.</p>

<h3 id="rodata">.rodata</h3>

<p>Константные данные. Тоже размещаются во FLASH.</p>

<h3 id="data">.data</h3>

<p>Инициализированные переменные. После старта они должны находиться в RAM, но их начальные значения хранятся во FLASH, поэтому эта секция размещается как <code class="language-plaintext highlighter-rouge">&gt; RAM AT &gt; FLASH</code>.</p>

<h3 id="bss">.bss</h3>

<p>Неинициализированные переменные. На старте они зануляются вручную.</p>

<h3 id="зачем-нужны-sidata-sdata-edata-sbss-_ebss">Зачем нужны sidata, sdata, edata, sbss, _ebss</h3>

<p>Эти символы создаёт линкер, а затем использует startup-код.</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">_sidata</code> — адрес исходных данных во FLASH;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">_sdata</code> — начало <code class="language-plaintext highlighter-rouge">.data</code> в RAM;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">_edata</code> — конец <code class="language-plaintext highlighter-rouge">.data</code> в RAM;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">_sbss</code> — начало <code class="language-plaintext highlighter-rouge">.bss</code>;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">_ebss</code> — конец <code class="language-plaintext highlighter-rouge">.bss</code>.</p>
  </li>
</ul>

<p>Без этих меток startup-код не смог бы понять, что именно нужно копировать и что нужно занулять.</p>

<h3 id="и-теперь-очень-простым-языком">И теперь очень простым языком</h3>

<p>*(.data) : Это поиск секций с точным именем <code class="language-plaintext highlighter-rouge">.data</code>. Сюда попадают обычные инициализированные глобальные переменные.</p>

<p>*(.data*) : Звездочка в конце означает «любое продолжение».</p>

<p>Представьте, что у вас есть два файла: <code class="language-plaintext highlighter-rouge">main.c</code> и <code class="language-plaintext highlighter-rouge">sensor.c</code>. Компилятор делает из них <code class="language-plaintext highlighter-rouge">main.o</code> и <code class="language-plaintext highlighter-rouge">sensor.o</code>. В каждом из них есть своя маленькая коробочка с надписью <code class="language-plaintext highlighter-rouge">.data</code>. Линковщик видит вашу инструкцию <code class="language-plaintext highlighter-rouge">*(.data)</code>. Он идет в <code class="language-plaintext highlighter-rouge">main.o</code>, забирает оттуда содержимое <code class="language-plaintext highlighter-rouge">.data</code>, затем идет в <code class="language-plaintext highlighter-rouge">sensor.o</code>, забирает данные оттуда и склеивает их в одну большую секцию <code class="language-plaintext highlighter-rouge">.data</code> в итоговом бинарном файле.</p>

<p>В больших проектах может не существовать секции .data для конкретного .o файла. Эта секция может быть разбита на много маленьких. Например, секция может называться .data.a. Тогда, наблюдая только инструкцию <code class="language-plaintext highlighter-rouge">*(.data),</code>линковщик не найдёт точного совпадения. Именно поэтому пишем *(.data*).</p>

<hr />

<h2 id="startup-файл-что-происходит-сразу-после-сброса">Startup-файл: что происходит сразу после сброса</h2>

<p>Startup-код — это первый код, который выполняется после подачи питания или аппаратного сброса. До вызова <code class="language-plaintext highlighter-rouge">main()</code> микроконтроллер ещё не готов к обычной работе, поэтому сначала нужно подготовить память.</p>

<h3 id="линкерные-символы">Линкерные символы</h3>

<p>В startup-файле обычно объявляются внешние символы:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extern uint32_t _estack;
extern uint32_t _sidata;
extern uint32_t _sdata;
extern uint32_t _edata;
extern uint32_t _sbss;
extern uint32_t _ebss;
</code></pre></div></div>

<p>Это не переменные в обычном смысле. Это адреса, созданные линкером. Они позволяют C-коду понять, где находятся границы секций памяти.</p>

<h3 id="векторная-таблица">Векторная таблица</h3>

<p>Векторная таблица — это массив адресов обработчиков. Процессор не ищет обработчик по имени. Он просто берёт адрес из нужной позиции таблицы.</p>

<p>В минимальном варианте достаточно описать <code class="language-plaintext highlighter-rouge">Reset_Handler</code>, <code class="language-plaintext highlighter-rouge">NMI_Handler</code> и <code class="language-plaintext highlighter-rouge">HardFault_Handler</code>. Остальные обработчики можно пока направить в <code class="language-plaintext highlighter-rouge">Default_Handler</code>.</p>

<h3 id="weak-alias">weak alias</h3>

<p>Конструкция <code class="language-plaintext highlighter-rouge">**attribute**((weak, alias("Default_Handler")))</code> означает: если отдельный обработчик не определён, вместо него будет использоваться <code class="language-plaintext highlighter-rouge">Default_Handler</code>.</p>

<p>Это удобно для минимального проекта. Не нужно реализовывать все обработчики сразу — неописанные прерывания просто уйдут в бесконечный цикл.</p>

<h3 id="reset_handler">Reset_Handler</h3>

<p>После сброса процессор выполняет <code class="language-plaintext highlighter-rouge">Reset_Handler</code>. В нём нужно сделать три обязательные вещи:</p>

<ol>
  <li>
    <p>скопировать <code class="language-plaintext highlighter-rouge">.data</code> из FLASH в RAM;</p>
  </li>
  <li>
    <p>занулить <code class="language-plaintext highlighter-rouge">.bss</code>.</p>
  </li>
  <li>
    <p>вызывать <code class="language-plaintext highlighter-rouge">main()</code>.</p>

    <p>Здесь main() и есть та самая функция, которая фигурирует во всех наших проектах.</p>
  </li>
</ol>

<p>Если пропустить этот этап, глобальные переменные окажутся не в том состоянии, которое ожидает программа.</p>

<h3 id="полный-startupc">Полный startup.c</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span>
<span class="k">extern</span> <span class="kt">uint32_t</span> <span class="n">_estack</span><span class="p">;</span>
<span class="k">extern</span> <span class="kt">uint32_t</span> <span class="n">_sidata</span><span class="p">;</span>
<span class="k">extern</span> <span class="kt">uint32_t</span> <span class="n">_sdata</span><span class="p">;</span>
<span class="k">extern</span> <span class="kt">uint32_t</span> <span class="n">_edata</span><span class="p">;</span>
<span class="k">extern</span> <span class="kt">uint32_t</span> <span class="n">_sbss</span><span class="p">;</span>
<span class="k">extern</span> <span class="kt">uint32_t</span> <span class="n">_ebss</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="kt">void</span> <span class="nf">Reset_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">Default_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="kt">void</span> <span class="nf">NMI_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>       <span class="n">__attribute__</span><span class="p">((</span><span class="n">weak</span><span class="p">,</span> <span class="n">alias</span><span class="p">(</span><span class="s">"Default_Handler"</span><span class="p">)));</span>
<span class="kt">void</span> <span class="nf">HardFault_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>  <span class="n">__attribute__</span><span class="p">((</span><span class="n">weak</span><span class="p">,</span> <span class="n">alias</span><span class="p">(</span><span class="s">"Default_Handler"</span><span class="p">)));</span>

<span class="n">__attribute__</span><span class="p">((</span><span class="n">used</span><span class="p">,</span> <span class="n">section</span><span class="p">(</span><span class="s">".isr_vector"</span><span class="p">)))</span>
<span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">vector_table</span><span class="p">[]</span> <span class="o">=</span>
<span class="p">{</span>
    <span class="o">&amp;</span><span class="n">_estack</span><span class="p">,</span>
    <span class="n">Reset_Handler</span><span class="p">,</span>
    <span class="n">NMI_Handler</span><span class="p">,</span>
    <span class="n">HardFault_Handler</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="nf">Reset_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">src</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_sidata</span><span class="p">;</span> <span class="c1">// Адрес начала данных во FLASH</span>
    <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">dst</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_sdata</span><span class="p">;</span>  <span class="c1">// Адрес начала данных в RAM</span>

    <span class="k">while</span> <span class="p">(</span><span class="n">dst</span> <span class="o">&lt;</span> <span class="o">&amp;</span><span class="n">_edata</span><span class="p">){</span>
        <span class="o">*</span><span class="n">dst</span><span class="o">++</span> <span class="o">=</span> <span class="o">*</span><span class="n">src</span><span class="o">++</span><span class="p">;</span>      <span class="c1">// Копируем данные из FLASH в RAM</span>
    <span class="p">}</span>

    <span class="n">dst</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_sbss</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="n">dst</span> <span class="o">&lt;</span> <span class="o">&amp;</span><span class="n">_ebss</span><span class="p">){</span>  <span class="c1">// Адрес начала секции .bss в RAM</span>
        <span class="o">*</span><span class="n">dst</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>        <span class="c1">//Зануляем неинициализированные переменные</span>
    <span class="p">}</span>

    <span class="n">main</span><span class="p">();</span>

    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">){</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">Default_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">){</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="заголовочные-файлы-интерфейсы-модулей">Заголовочные файлы: интерфейсы модулей</h2>

<p>В <code class="language-plaintext highlighter-rouge">inc/</code> лежат заголовочные файлы. Их задача — описать, какие функции и структуры доступны другим частям проекта.</p>

<p>Такой подход помогает не смешивать реализацию и интерфейс. Один <code class="language-plaintext highlighter-rouge">.c</code>-файл не должен “знать лишнего” о внутренностях другого <code class="language-plaintext highlighter-rouge">.c</code>-файла, если достаточно просто увидеть его прототипы.</p>

<p>При включении питания CPU смотрит в адрес <code class="language-plaintext highlighter-rouge">0x00000000</code>, берёт оттуда указатель на стек. Далее CPU читает адрес <code class="language-plaintext highlighter-rouge">0x00000004</code> - это адрес <code class="language-plaintext highlighter-rouge">Reset_Handler.</code>После CPU начинает выполнять ResetHandler() и оттуда уже прыгает в main().</p>

<h3 id="incgpioh">inc/GPIO.h</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef GPIO_H
#define GPIO_H
</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">GPIO_init</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="cp">#endif
</span></code></pre></div></div>

<h3 id="incledh">inc/LED.h</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef LED_H
#define LED_H
</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">turnOnLED</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">turnOffLED</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="cp">#endif
</span></code></pre></div></div>

<h3 id="inctimersh">inc/Timers.h</h3>

<p>Именно здесь удобно описать структуру регистра таймера.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef TIMERS_H
#define TIMERS_H
</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">TIMERS_init</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">delayOneSecond</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="k">typedef</span> <span class="k">struct</span>
<span class="p">{</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CR1</span><span class="p">;</span>   <span class="c1">// 0x00</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CR2</span><span class="p">;</span>   <span class="c1">// 0x04</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">SMCR</span><span class="p">;</span>  <span class="c1">// 0x08</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">DIER</span><span class="p">;</span>  <span class="c1">// 0x0C</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">SR</span><span class="p">;</span>    <span class="c1">// 0x10</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">EGR</span><span class="p">;</span>   <span class="c1">// 0x14</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCMR1</span><span class="p">;</span> <span class="c1">// 0x18</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCMR2</span><span class="p">;</span> <span class="c1">// 0x1C</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCER</span><span class="p">;</span>  <span class="c1">// 0x20</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CNT</span><span class="p">;</span>   <span class="c1">// 0x24</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">PSC</span><span class="p">;</span>   <span class="c1">// 0x28</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">ARR</span><span class="p">;</span>   <span class="c1">// 0x2C</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">RCR</span><span class="p">;</span>   <span class="c1">// 0x30</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCR1</span><span class="p">;</span>  <span class="c1">// 0x34</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCR2</span><span class="p">;</span>  <span class="c1">// 0x38</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCR3</span><span class="p">;</span>  <span class="c1">// 0x3C</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">CCR4</span><span class="p">;</span>  <span class="c1">// 0x40</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">BDTR</span><span class="p">;</span>  <span class="c1">// 0x44</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">DCR</span><span class="p">;</span>   <span class="c1">// 0x48</span>
    <span class="k">volatile</span> <span class="kt">uint32_t</span> <span class="n">DMAR</span><span class="p">;</span>  <span class="c1">// 0x4C</span>
<span class="p">}</span> <span class="n">TIM_TypeDef</span><span class="p">;</span>

<span class="cp">#define TIM2 ((TIM_TypeDef*)0x40000000)
</span>
<span class="cp">#endif
</span></code></pre></div></div>

<p>Здесь <code class="language-plaintext highlighter-rouge">TIM2</code> — это не “обычный объект” C, а типизированный доступ к блоку регистров по фиксированному адресу. В итоге запись вида <code class="language-plaintext highlighter-rouge">TIM2-&gt;PSC = 7999;</code> становится простой и читаемой.</p>

<h3 id="incconfigh">inc/config.h</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef CONFIG_H
#define CONFIG_H
</span>
<span class="cp">#include</span> <span class="cpf">"GPIO.h"</span><span class="cp">
#include</span> <span class="cpf">"Timers.h"</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">MCU_init</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="cp">#endif
</span></code></pre></div></div>

<h3 id="incmainh">inc/main.h</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef MAIN_H
#define MAIN_H
</span>
<span class="cp">#include</span> <span class="cpf">"config.h"</span><span class="cp">
#include</span> <span class="cpf">"LED.h"</span><span class="cp">
</span>
<span class="cp">#endif
</span></code></pre></div></div>

<h2 id="единая-точка-инициализации-mcu_init">Единая точка инициализации: MCU_init()</h2>

<p>Когда проект растёт, удобно собрать все базовые настройки в одну функцию. В этом проекте такой точкой входа становится <code class="language-plaintext highlighter-rouge">MCU_init()</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"config.h"</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">MCU_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">GPIO_init</span><span class="p">();</span>
    <span class="n">TIMERS_init</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Такой подход делает <code class="language-plaintext highlighter-rouge">main()</code> короче и понятнее: в нём остаётся только прикладная логика, а детали инициализации уходят в отдельные модули.</p>

<h2 id="настройка-gpioc-и-ножки-pc13">Настройка GPIOC и ножки PC13</h2>

<p>Теперь можно перейти к периферии. Первая задача — включить тактирование порта GPIOC и настроить вывод PC13.</p>

<h3 id="включение-clock-для-gpioc">Включение clock для GPIOC</h3>

<p>Регистр <code class="language-plaintext highlighter-rouge">RCC_APB2ENR</code> находится по адресу <code class="language-plaintext highlighter-rouge">0x40021018</code>. Бит 4 включает тактирование GPIOC.</p>

<p>Если этот бит не установить, регистры порта останутся доступны по адресу, но сама периферия не начнёт работать.</p>

<h3 id="настройка-режима-pc13">Настройка режима PC13</h3>

<p>Регистр <code class="language-plaintext highlighter-rouge">GPIOC_CRH</code> находится по адресу <code class="language-plaintext highlighter-rouge">0x40011004</code>. Он отвечает за ножки с 8-й по 15-ю.</p>

<p>Для PC13 используются биты <code class="language-plaintext highlighter-rouge">[23:20]</code>. Сначала они очищаются, затем записывается комбинация:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">MODE = 10</code> — выход 2 МГц;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">CNF = 00</code> — обычный push-pull output.</p>
  </li>
</ul>

<h3 id="полный-gpioc">Полный GPIO.c</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"GPIO.h"</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">GPIO_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">//Бит 4-й регистра RCC_APB2ENR устанавливается в единицу</span>
    <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x40021018</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">);</span>

    <span class="c1">//Очищение битов [23:20] регистра GPIOC_CRH</span>
    <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x40011004</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">0</span><span class="n">b1111</span> <span class="o">&lt;&lt;</span> <span class="mi">20</span><span class="p">);</span>  

    <span class="c1">//Запись битов [23:20] регистра   GPIOC_CRH</span>
    <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x40011004</span> <span class="o">|=</span>  <span class="p">(</span><span class="mi">0</span><span class="n">b0010</span> <span class="o">&lt;&lt;</span> <span class="mi">20</span><span class="p">);</span>  
<span class="p">}</span>
</code></pre></div></div>

<h2 id="управление-светодиодом">Управление светодиодом</h2>

<p>На многих платах с STM32F103C8T6 встроенный светодиод на PC13 подключён по схеме active-low.</p>

<p>Это означает:</p>

<ul>
  <li>
    <p>чтобы включить светодиод, нужно записать <code class="language-plaintext highlighter-rouge">0</code>;</p>
  </li>
  <li>
    <p>чтобы выключить — <code class="language-plaintext highlighter-rouge">1</code>.</p>
  </li>
</ul>

<p>Из-за этого логика включения и выключения выглядит немного “наоборот”, но для платы это совершенно нормально.</p>

<h3 id="полный-ledc">Полный LED.c</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"LED.h"</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">turnOnLED</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x4001100C</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">13</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">turnOffLED</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x4001100C</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">13</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Адрес <code class="language-plaintext highlighter-rouge">0x4001100C</code> — это <code class="language-plaintext highlighter-rouge">GPIOC_ODR</code>, то есть регистр данных выхода.</p>

<h2 id="таймер-tim2">Таймер TIM2</h2>

<p>В этой задаче таймер работает в режиме обычного счётчика: мы настраиваем предделитель, верхнюю границу счёта и ждём флаг переполнения.</p>

<h3 id="основные-сокращения">Основные сокращения</h3>

<table>
  <thead>
    <tr>
      <th>Сокращение</th>
      <th>Расшифровка</th>
      <th>Смысл</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>TIM</td>
      <td>Timer</td>
      <td>аппаратный таймер</td>
    </tr>
    <tr>
      <td>PSC</td>
      <td>Prescaler</td>
      <td>предделитель частоты</td>
    </tr>
    <tr>
      <td>ARR</td>
      <td>Auto-Reload Register</td>
      <td>значение автоперезагрузки</td>
    </tr>
    <tr>
      <td>CR1</td>
      <td>Control Register 1</td>
      <td>основной регистр управления</td>
    </tr>
    <tr>
      <td>SR</td>
      <td>Status Register</td>
      <td>регистр состояния</td>
    </tr>
    <tr>
      <td>UIF</td>
      <td>Update Interrupt Flag</td>
      <td>флаг события обновления</td>
    </tr>
  </tbody>
</table>

<h3 id="частота-таймера">Частота таймера</h3>

<p>После сброса STM32F103 обычно использует внутренний генератор HSI на 8 МГц.</p>

<p>Если отдельная настройка clock tree не выполняется, можно считать, что:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">HCLK = 8 MHz</code>;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">PCLK1 = 8 MHz</code>;</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">TIM2CLK = 8 MHz</code>.</p>
  </li>
</ul>

<h3 id="формирование-задержки-1-секунда">Формирование задержки 1 секунда</h3>

<p>В проекте используются параметры:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">PSC</span> <span class="o">=</span> <span class="mi">7999</span><span class="p">;</span>
<span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">ARR</span> <span class="o">=</span> <span class="mi">999</span><span class="p">;</span>
</code></pre></div></div>

<p>Формула работы таймера:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f_counter</span> <span class="o">=</span> <span class="n">f_tim</span> <span class="o">/</span> <span class="p">(</span><span class="n">PSC</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>Подставляем значения:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f_counter</span> <span class="o">=</span> <span class="mi">8</span> <span class="mo">000</span> <span class="mo">000</span> <span class="o">/</span> <span class="p">(</span><span class="mi">7999</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">=</span> <span class="mi">1000</span> <span class="n">Hz</span>
</code></pre></div></div>

<p>Это значит, что один тик счётчика длится 1 миллисекунду.</p>

<p>Дальше <code class="language-plaintext highlighter-rouge">ARR = 999</code> даёт 1000 тиков, то есть ровно 1 секунду.</p>

<h3 id="важная-деталь-включение-clock-для-tim2">Важная деталь: включение clock для TIM2</h3>

<p>Чтобы таймер реально начал считать, нужно включить его тактирование через <code class="language-plaintext highlighter-rouge">RCC_APB1ENR</code>, бит <code class="language-plaintext highlighter-rouge">TIM2EN</code>.</p>

<p>Адрес регистра: 0x4002101C</p>

<p>Без этого шага таймер не будет обновлять счётчик, и ожидание флага <code class="language-plaintext highlighter-rouge">UIF</code> превратится в бесконечный цикл.</p>

<p>Бит TIM2-&gt;SR становится единицей после каждого переполнения таймера. Следовательно, выставлять его в ноль перед запуском таймера - стандартная практика.</p>

<h3 id="полный-timersc">Полный Timers.c</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"Timers.h"</span><span class="cp">
</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">TIM2_init</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

<span class="kt">void</span> <span class="nf">TIMERS_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">TIM2_init</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">TIM2_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">//Ставим единичку в регистр RCC_APB1ENR в бит TIM2EN</span>
    <span class="o">*</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">uint32_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x4002101C</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">);</span>

    <span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">SR</span>  <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">PSC</span>  <span class="o">=</span> <span class="mi">7999</span><span class="p">;</span>
    <span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">ARR</span>  <span class="o">=</span> <span class="mi">999</span><span class="p">;</span>
    <span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">CR1</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">delayOneSecond</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">//Как только TIM2-&gt;SR бит UIF = 1, значит прошла 1 секунда</span>
    <span class="k">while</span> <span class="p">((</span><span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">SR</span> <span class="o">&amp;</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">){}</span>

    <span class="c1">//Сбрасываем Status Register, чтобы продолжить фиксировать переполнения</span>
    <span class="n">TIM2</span><span class="o">-&gt;</span><span class="n">SR</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">);</span> 
<span class="p">}</span>
</code></pre></div></div>

<h3 id="как-работает-delayonesecond">Как работает delayOneSecond()</h3>

<p>Функция построена на опросе флага <code class="language-plaintext highlighter-rouge">UIF</code>.</p>

<p>Сначала код ждёт, пока бит <code class="language-plaintext highlighter-rouge">UIF</code> в <code class="language-plaintext highlighter-rouge">SR</code> не станет равен 1. После этого флаг сбрасывается, и функция завершается.</p>

<p>Это блокирующая задержка: пока она выполняется, основной код не делает ничего другого. Для первого проекта это нормально, потому что такой вариант наглядно показывает сам принцип работы таймера.</p>

<p>Стоит отметить, что таймер после переполнения сбрасывается автоматически. Выставленная в бите <code class="language-plaintext highlighter-rouge">UIF</code> единица, не мешает таймеру продолжать считать.</p>

<h2 id="основной-файл-mainc">Основной файл main.c</h2>

<p>Когда инициализация уже вынесена в отдельные модули, <code class="language-plaintext highlighter-rouge">main()</code> остаётся очень короткой. И это хороший признак: прикладная логика читается сразу, без лишнего шума.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"main.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="n">MCU_init</span><span class="p">();</span>

    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">){</span>
        <span class="n">turnOnLED</span><span class="p">();</span>
        <span class="n">delayOneSecond</span><span class="p">();</span>

        <span class="n">turnOffLED</span><span class="p">();</span>
        <span class="n">delayOneSecond</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Здесь видно главное: сначала инициализация, затем бесконечный цикл, в котором выполняется только сценарий работы устройства.</p>

<h2 id="как-получается-firmwareelf-и-firmwarebin">Как получается firmware.elf и firmware.bin</h2>

<p>На этапе сборки исходники превращаются сначала в ELF-файл, а затем в плоский бинарный образ.</p>

<h3 id="firmwareelf">firmware.elf</h3>

<p>ELF (<code class="language-plaintext highlighter-rouge">Executable and Linkable Format</code>) содержит не только машинный код, но и информацию о секциях, символах и отладочных данных.</p>

<h3 id="firmwarebin">firmware.bin</h3>

<p>BIN — это уже чистый бинарный образ без служебной структуры. Именно его обычно прошивают во FLASH микроконтроллера.</p>

<h3 id="пример-сборки">Пример сборки</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>arm-none-eabi-gcc <span class="nt">-mcpu</span><span class="o">=</span>cortex-m3 <span class="nt">-mthumb</span> <span class="nt">-nostdlib</span> <span class="nt">-Iinc</span> <span class="se">\</span>
<span class="nt">-T</span> myLinker.ld startup/startup.c src/main.c src/config.c src/GPIO.c <span class="se">\</span>
src/LED.c src/Timers.c <span class="nt">-o</span> firmware.elf

arm-none-eabi-objcopy <span class="nt">-O</span> binary firmware.elf firmware.bin
</code></pre></div></div>

<p>Сначала линкер собирает все объекты в <code class="language-plaintext highlighter-rouge">firmware.elf</code>, учитывая <code class="language-plaintext highlighter-rouge">startup.c</code> и <code class="language-plaintext highlighter-rouge">myLinker.ld</code>. Затем <code class="language-plaintext highlighter-rouge">objcopy</code> извлекает из ELF только полезный бинарный образ.</p>

<h2 id="словарь-аббревиатур">Словарь аббревиатур</h2>

<table>
  <thead>
    <tr>
      <th>Аббревиатура</th>
      <th>Расшифровка</th>
      <th>Значение в проекте</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>MCU</td>
      <td>Microcontroller Unit</td>
      <td>сам микроконтроллер</td>
    </tr>
    <tr>
      <td>RCC</td>
      <td>Reset and Clock Control</td>
      <td>блок тактирования и сброса</td>
    </tr>
    <tr>
      <td>GPIO</td>
      <td>General Purpose Input/Output</td>
      <td>обычные ножки ввода-вывода</td>
    </tr>
    <tr>
      <td>ODR</td>
      <td>Output Data Register</td>
      <td>регистр выходных данных</td>
    </tr>
    <tr>
      <td>TIM</td>
      <td>Timer</td>
      <td>аппаратный таймер</td>
    </tr>
    <tr>
      <td>PSC</td>
      <td>Prescaler</td>
      <td>предделитель</td>
    </tr>
    <tr>
      <td>ARR</td>
      <td>Auto-Reload Register</td>
      <td>верхняя граница счёта</td>
    </tr>
    <tr>
      <td>CR1</td>
      <td>Control Register 1</td>
      <td>основной регистр управления таймером</td>
    </tr>
    <tr>
      <td>SR</td>
      <td>Status Register</td>
      <td>регистр флагов состояния</td>
    </tr>
    <tr>
      <td>UIF</td>
      <td>Update Interrupt Flag</td>
      <td>флаг обновления</td>
    </tr>
    <tr>
      <td>APB</td>
      <td>Advanced Peripheral Bus</td>
      <td>шина периферии</td>
    </tr>
    <tr>
      <td>PCLK1</td>
      <td>Peripheral Clock 1</td>
      <td>тактирование APB1</td>
    </tr>
    <tr>
      <td>HSI</td>
      <td>High Speed Internal</td>
      <td>внутренний RC-генератор 8 МГц</td>
    </tr>
    <tr>
      <td>FLASH</td>
      <td>Flash memory</td>
      <td>память программы</td>
    </tr>
    <tr>
      <td>RAM</td>
      <td>Random Access Memory</td>
      <td>оперативная память</td>
    </tr>
    <tr>
      <td>ELF</td>
      <td>Executable and Linkable Format</td>
      <td>файл результата компоновки</td>
    </tr>
  </tbody>
</table>

<h2 id="полные-исходники-проекта">Полные исходники проекта</h2>

<p>Все исходники проекта можете посмотреть на GitHub: <a href="https://github.com/dimchickka/codeForSTM32_fromScrath">https://github.com/dimchickka/codeForSTM32_fromScrath</a></p>

<h2 id="итог">Итог</h2>

<p>Этот проект полезен тем, что в нём видно весь путь старта микроконтроллера: от векторной таблицы и linker script до первой реальной работы GPIO и таймера. Такой разбор хорошо помогает не просто “собрать пример”, а понять, почему микроконтроллер вообще начинает выполнять программу и как именно он доходит до <code class="language-plaintext highlighter-rouge">main()</code>.</p>]]></content><author><name>Oleg Larkin</name></author><category term="habr" /><category term="habr" /><category term="embedding" /><category term="STM32" /><summary type="html"><![CDATA[Когда микроконтроллер получает питание или выходит из аппаратного сброса, выполнение программы начинается задолго до входа в main(). Сначала ядро Cortex-M3 загружает начальный указатель стека, затем берёт адрес обработчика сброса из векторной таблицы и только после этого запускает startup-код.]]></summary></entry><entry><title type="html">Подъем на Лахта-Центр</title><link href="http://localhost:4000/life/2026/02/19/lahta-center.html" rel="alternate" type="text/html" title="Подъем на Лахта-Центр" /><published>2026-02-19T00:00:00+03:00</published><updated>2026-02-19T00:00:00+03:00</updated><id>http://localhost:4000/life/2026/02/19/lahta-center</id><content type="html" xml:base="http://localhost:4000/life/2026/02/19/lahta-center.html"><![CDATA[<p><img src="/assets/images/life/lahta-center/photo_2026-02-23 11.20.33.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.20.46.jpeg" alt="" />
<!--more-->
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.20.48.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.20.49.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.20.54.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.20.55.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.04.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.06.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.10.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.12.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.14.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.16.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.18.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.19.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.21.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.22.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.28.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.33.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.34.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.43.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.45.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.46.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.48.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.49.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.50.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.52.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.21.54.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.09.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.13.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.16.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.18.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.19.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.24.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.26.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.29.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.30.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.35.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.39.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.48.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.50.jpeg" alt="" />
<img src="/assets/images/life/lahta-center/photo_2026-02-23 11.22.53.jpeg" alt="" /></p>]]></content><author><name>Oleg Larkin</name></author><category term="life" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Vim Cheatsheet</title><link href="http://localhost:4000/other/2026/02/01/vim-cheetsheet.html" rel="alternate" type="text/html" title="Vim Cheatsheet" /><published>2026-02-01T00:00:00+03:00</published><updated>2026-02-01T00:00:00+03:00</updated><id>http://localhost:4000/other/2026/02/01/vim-cheetsheet</id><content type="html" xml:base="http://localhost:4000/other/2026/02/01/vim-cheetsheet.html"><![CDATA[<h2 id="baby-steps">Baby Steps</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">h</code> move cursor to the left</li>
  <li><code class="language-plaintext highlighter-rouge">j</code> move down</li>
  <li><code class="language-plaintext highlighter-rouge">k</code> move up</li>
  <li><code class="language-plaintext highlighter-rouge">l</code> move right</li>
  <li><code class="language-plaintext highlighter-rouge">i</code> Go into Insert mode</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;ESC&gt;</code><code class="language-plaintext highlighter-rouge">&lt;C-C&gt;</code><code class="language-plaintext highlighter-rouge">&lt;C-[&gt;</code> Go back to Normal mode</li>
</ul>

<!--more-->

<h2 id="move-fast-word-by-word">Move Fast Word by Word</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">w</code> move to the beginning of next word</li>
  <li><code class="language-plaintext highlighter-rouge">b</code> move to the beginning of the previous word</li>
  <li><code class="language-plaintext highlighter-rouge">e</code> move to the end of the next word</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">ge</code> move to the end of the previous word</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">W</code> move to the beginning of next WORD</li>
  <li><code class="language-plaintext highlighter-rouge">B</code> move to the beginning of the previous WORD</li>
  <li><code class="language-plaintext highlighter-rouge">E</code> move to the end of the next WORD</li>
  <li><code class="language-plaintext highlighter-rouge">gE</code> move to the end of the previous WORD</li>
</ul>

<h2 id="find-character">Find Character</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">f{character}</code> Find next occurrence of character</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">F{character}</code> Find previous occurrence of character</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">t{character}</code> Find next occurrence of character and place cursor just before it</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">T{character}</code> Find previous occurrence of character and place cursor just before it</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">;</code> Go to next occurrence of {character}</li>
  <li><code class="language-plaintext highlighter-rouge">,</code> Go to previous occurrence of {character}</li>
</ul>

<h2 id="move-extremely-horizontally">Move Extremely Horizontally</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">0</code> Moves to the first character of a line</li>
  <li><code class="language-plaintext highlighter-rouge">^</code> Moves to the first non-blank character of a line</li>
  <li><code class="language-plaintext highlighter-rouge">$</code> Moves to the end of a line</li>
  <li><code class="language-plaintext highlighter-rouge">g_</code> Moves to the non-blank character at the end of a line</li>
</ul>

<h2 id="move-faster-vertically">Move Faster Vertically</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">}</code> Jumps entire paragraphs downwards</li>
  <li><code class="language-plaintext highlighter-rouge">{</code> similarly but upwards</li>
  <li><code class="language-plaintext highlighter-rouge">CTRL-D</code> lets you move down half a page by scrolling the page</li>
  <li><code class="language-plaintext highlighter-rouge">CTRL-U</code> lets you move up half a page also by scrolling</li>
</ul>

<h2 id="high-precision-vertical-motions-with-search">High Precision Vertical Motions with Search</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/{pattern}</code> Search for {pattern}. {pattern} is a regex.</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">?{pattern}</code> Search for {pattern} backwards.</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">/</code> Repeat last search forwards</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">?</code> Repeat last search backwards</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">n</code> Go to next match</li>
  <li><code class="language-plaintext highlighter-rouge">N</code> Go to previous match</li>
</ul>

<h2 id="move-faster-with-counts">Move Faster with Counts</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">{count}{motion}</code> Repeat {motion} {count} times</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">2w</code> Jump to second word</li>
  <li><code class="language-plaintext highlighter-rouge">4f"</code> Jump to fourth occurrence of the “ character</li>
  <li><code class="language-plaintext highlighter-rouge">3/cucumber</code> Jump to third match of “cucumber”</li>
</ul>

<h2 id="move-semantically">Move Semantically</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gd</code> Go to definition (of the word under the cursor)</li>
  <li><code class="language-plaintext highlighter-rouge">gf</code> Go to file (for file under the cursor)</li>
</ul>

<h2 id="more-nifty-core-motions">More Nifty Core Motions</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gg</code> Go to the top of the file</li>
  <li><code class="language-plaintext highlighter-rouge">{line}gg</code> Go to {line}</li>
  <li><code class="language-plaintext highlighter-rouge">G</code> Go to the end of the file</li>
  <li><code class="language-plaintext highlighter-rouge">%</code> jump to matching ({[]})</li>
</ul>

<h2 id="edit-like-magic-with-vim-operators">Edit Like Magic With Vim Operators</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">{operator}{count}{motion}</code> Apply operator on bit of text covered by motion</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">d</code> delete</li>
  <li><code class="language-plaintext highlighter-rouge">c</code> change</li>
  <li><code class="language-plaintext highlighter-rouge">y</code> yank (copy)</li>
  <li><code class="language-plaintext highlighter-rouge">p</code> p (paste)</li>
  <li><code class="language-plaintext highlighter-rouge">g~</code> switch case</li>
  <li><code class="language-plaintext highlighter-rouge">&gt;</code> shift right</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;</code> shift left</li>
  <li><code class="language-plaintext highlighter-rouge">=</code> format</li>
</ul>

<h2 id="linewise-operators">Linewise Operators</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">dd</code> delete a line</li>
  <li><code class="language-plaintext highlighter-rouge">cc</code> change a line</li>
  <li><code class="language-plaintext highlighter-rouge">yy</code> yank (copy) a line</li>
  <li><code class="language-plaintext highlighter-rouge">g~~</code> switch case of a line</li>
  <li><code class="language-plaintext highlighter-rouge">&gt;&gt;</code> shift line right</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;&lt;</code> shift lineleft</li>
  <li><code class="language-plaintext highlighter-rouge">==</code> format line</li>
</ul>

<h2 id="capital-case-stronger-version-operators">Capital case (Stronger version) Operators</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">D</code> delete from cursor to the end of the line</li>
  <li><code class="language-plaintext highlighter-rouge">C</code> change from cursor to the end of the line</li>
  <li><code class="language-plaintext highlighter-rouge">Y</code> yank (copy) a line. Like yy</li>
  <li><code class="language-plaintext highlighter-rouge">P</code> put (paste) before the cursor</li>
</ul>

<h2 id="text-objects">Text objects</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">{operator}a{text-object}</code> Apply operator to all text-object including trailing whitespace</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">{operator}i{text-object}</code> Apply operator inside text-object</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">diw</code> delete inner word</li>
  <li><code class="language-plaintext highlighter-rouge">daw</code> delete a word</li>
  <li><code class="language-plaintext highlighter-rouge">dis</code> delete inner sentence</li>
  <li><code class="language-plaintext highlighter-rouge">das</code> delete a sentence</li>
  <li><code class="language-plaintext highlighter-rouge">dip</code> delete inner paragraph</li>
  <li><code class="language-plaintext highlighter-rouge">dap</code> delete a paragraph</li>
  <li><code class="language-plaintext highlighter-rouge">di(</code><code class="language-plaintext highlighter-rouge">dib</code> delete inside parentheses</li>
  <li><code class="language-plaintext highlighter-rouge">da(</code><code class="language-plaintext highlighter-rouge">dab</code> delete text inside parentheses (including parentheses)</li>
  <li><code class="language-plaintext highlighter-rouge">di{</code><code class="language-plaintext highlighter-rouge">diB</code> delete inside braces</li>
  <li><code class="language-plaintext highlighter-rouge">da{</code><code class="language-plaintext highlighter-rouge">daB</code> delete text inside braces (including braces)</li>
  <li><code class="language-plaintext highlighter-rouge">di[</code> delete inside brackets</li>
  <li><code class="language-plaintext highlighter-rouge">da[</code> delete text inside brackets (including brackets)</li>
  <li><code class="language-plaintext highlighter-rouge">di"</code> delete inside quotes</li>
  <li><code class="language-plaintext highlighter-rouge">da"</code> delete a quoted text (including quotes)</li>
  <li><code class="language-plaintext highlighter-rouge">dit</code> delete inside tag</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">dat</code> delete a tag (including tag)</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">ciw</code> same goes for other operators…</li>
</ul>

<h2 id="repeat-last-change">Repeat Last Change</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">.</code> Repeat the last change</li>
</ul>

<h2 id="character-editing-commands">Character Editing Commands</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">x</code> delete a character. Like dl</li>
  <li><code class="language-plaintext highlighter-rouge">X</code> delete character before the cursor. Like dh</li>
  <li><code class="language-plaintext highlighter-rouge">s</code> change a character. Like cl</li>
  <li><code class="language-plaintext highlighter-rouge">~</code> switch case of a character</li>
</ul>

<h2 id="undo-and-redo">Undo and redo</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">u</code> undo last change</li>
  <li><code class="language-plaintext highlighter-rouge">C-R</code> redo last undo</li>
  <li><code class="language-plaintext highlighter-rouge">{count}u</code> undo last {count} changes</li>
</ul>

<h2 id="inserting-text">Inserting Text</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">i</code> go into insert mode before the cursor</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">a</code> go into insert mode after the cursor</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">I</code> go into insert mode at the beginning of a line</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">A</code> go into insert mode at the end of a line</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">o</code> insert new line below current line and go into insert mode</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">O</code> insert new line above current line and go into insert mode</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">gi</code> go to the last place you left insert mode</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">C-H</code> delete last character</li>
  <li><code class="language-plaintext highlighter-rouge">C-W</code> delete last word</li>
  <li><code class="language-plaintext highlighter-rouge">C-U</code> delete last line</li>
</ul>

<h2 id="visual-mode">Visual Mode</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">v</code> go into character-wise visual mode</li>
  <li><code class="language-plaintext highlighter-rouge">V</code> go into line-wise visual mode</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">C-V</code> go into block-wise visual mode (to select rectangular blocks of text)</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">{trigger visual mode}{motion}{operator}</code> Visual mode operates in kind of the opposite way to normal mode. First you specify the motion to select text, and then you apply the operator</li>
</ul>

<h2 id="operate-on-next-search-match">Operate on Next Search Match</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">{operator}gn</code> Apply operator on next match</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.</code> After using {op}gn, the dot commant repeats the last change on the next match. Woooot!</p>
  </li>
</ul>

<h2 id="copying-and-pasting">Copying and pasting</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">y{motion}</code> yank (copy) text covered by motion</li>
  <li><code class="language-plaintext highlighter-rouge">p</code> put (paste) after cursor</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">P</code> paste before cursor</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">yy</code> copy line</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Y</code> copy line</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">yyp</code> duplicate line</li>
  <li><code class="language-plaintext highlighter-rouge">ddp</code> swap lines</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">xp</code> swap characters</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">"ay{motion}</code> copy to register a</li>
  <li><code class="language-plaintext highlighter-rouge">"Ay{motion}</code> copy and append to register a</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">"ap</code> paste from register a</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">"</code> unnamed register</li>
  <li><code class="language-plaintext highlighter-rouge">0</code> yank register</li>
  <li><code class="language-plaintext highlighter-rouge">1-9</code> delete registers</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">[a-z]</code> named registers</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">C-R a</code> paste from register a when in Insert mode</li>
</ul>

<h2 id="command-line-mode">Command-line mode</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">:edit {file}</code><code class="language-plaintext highlighter-rouge">:e {file}</code> create or edit file</li>
  <li><code class="language-plaintext highlighter-rouge">:write</code><code class="language-plaintext highlighter-rouge">:w</code> save file</li>
  <li><code class="language-plaintext highlighter-rouge">:quit</code><code class="language-plaintext highlighter-rouge">:q</code> close file</li>
  <li><code class="language-plaintext highlighter-rouge">:write!</code><code class="language-plaintext highlighter-rouge">:w!</code> force save file</li>
  <li><code class="language-plaintext highlighter-rouge">:quit!</code><code class="language-plaintext highlighter-rouge">:q!</code> close file without saving</li>
  <li><code class="language-plaintext highlighter-rouge">:wq</code> save and close file</li>
  <li><code class="language-plaintext highlighter-rouge">:wall</code><code class="language-plaintext highlighter-rouge">:wa</code> save all files</li>
  <li><code class="language-plaintext highlighter-rouge">:qall</code><code class="language-plaintext highlighter-rouge">:qa</code> close all files</li>
  <li><code class="language-plaintext highlighter-rouge">:wqall</code><code class="language-plaintext highlighter-rouge">:wqa</code> save and close all files</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">:qall!</code><code class="language-plaintext highlighter-rouge">:qa!</code> close all files without saving</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">:[range]delete [register]</code><code class="language-plaintext highlighter-rouge">:[r]d [r]</code> delete multiple lines into register</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">@:</code> repeat last ex command</li>
  <li><code class="language-plaintext highlighter-rouge">@@</code> after repeating it once, you can continue repeating with this</li>
</ul>

<h2 id="command-line-mode-ranges">Command-line mode Ranges</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">:{start},{end}</code> start and end lines of range e.g. :1,2d</li>
  <li><code class="language-plaintext highlighter-rouge">:{start},{offset}</code> start and offset lines of range e.g. :1,+2d</li>
  <li><code class="language-plaintext highlighter-rouge">.</code> current line e.g. :.,+2d</li>
  <li><code class="language-plaintext highlighter-rouge">%</code> whole file e.g. :%d</li>
  <li><code class="language-plaintext highlighter-rouge">0</code> beginning of file e.g. :0,10d</li>
  <li><code class="language-plaintext highlighter-rouge">$</code> end of file e.g. :10,$d</li>
  <li><code class="language-plaintext highlighter-rouge">:'&lt;,'&gt;</code> visual selection</li>
</ul>

<h2 id="command-line-mode-substitute">Command-line mode Substitute</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">:[range]/{pattern}/{substitute}/[flags]</code> substitute matched pattern for string literal in given range</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">g flag</code> substitute all matches in a line</li>
  <li><code class="language-plaintext highlighter-rouge">i flag</code> case insensitive search</li>
  <li><code class="language-plaintext highlighter-rouge">c flag</code> confirm substitution for each match</li>
</ul>

<h2 id="split-windows">Split Windows</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">:sp {file}</code> Open file in a horizontal split</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">:vsp {file}</code> Open file in a vertical split</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">C-W S</code> Open same file in a horizontal split</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">C-W V</code> Open same file in a vertical split</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">C-W h</code> Move to split to the left</li>
  <li><code class="language-plaintext highlighter-rouge">C-W j</code> Move to split below</li>
  <li><code class="language-plaintext highlighter-rouge">C-W k</code> Move to split above</li>
  <li><code class="language-plaintext highlighter-rouge">C-W l</code> Move to split to the right</li>
</ul>

<h2 id="tabs">Tabs</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">:tabnew {file}</code> Open file in new tab</li>
  <li><code class="language-plaintext highlighter-rouge">:tabnext</code><code class="language-plaintext highlighter-rouge">:tabn</code> Jump to next tab</li>
  <li><code class="language-plaintext highlighter-rouge">:tabprev</code><code class="language-plaintext highlighter-rouge">:tabp</code> Jump to previous tab</li>
  <li><code class="language-plaintext highlighter-rouge">:tabonly</code><code class="language-plaintext highlighter-rouge">:tabo</code> Close all other tabs</li>
</ul>

<h2 id="vim-surround">Vim surround</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ds</code> delete surroundings e.g. ds”</li>
  <li><code class="language-plaintext highlighter-rouge">cs</code> change surroundings e.g. cs*tem&gt;</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">ys</code> add surroundings e.g. ysiw”</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">ds"</code> delete surrounding quotes</li>
  <li><code class="language-plaintext highlighter-rouge">cs*tem&gt;</code> change surrounding * for the <em> tag</em></li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">ysiw"</code> surround word under the cursor with quotes</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">S</code> In visual mode you can select some text, then type S to add surroundings. e.g. Stp&gt; to wrap the selection in a &lt;p&gt; tag</li>
</ul>

<h2 id="custom-mappings-they-need-to-be-added-to-your-config">Custom mappings (they need to be added to your config)</h2>

<p>Insert mode</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">jk</code> Go back to normal mode</li>
</ul>

<p>Normal mode</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">J</code> Go down faster</li>
  <li><code class="language-plaintext highlighter-rouge">K</code> Go up faster</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;j</code> Join lines</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;/</code> Clear highlighted text</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">&lt;leader&gt;w</code> Save file</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;p</code> Open command palette</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">&lt;leader&gt;t</code> Go to symbol in file</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">&lt;C-H&gt;</code> Move to the window on the left</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;C-J&gt;</code> Move to the window below</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;C-K&gt;</code> Move to the window above</li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">&lt;C-L&gt;</code> Move to the window on the right</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;tt</code> Create new tab</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;tn</code> Move to next tab</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;tp</code> Move to previous tab</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;to</code> Close all tabs but the current one</li>
</ul>

<h2 id="moving-faster-with-vim-sneak">Moving Faster with Vim-sneak</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">s{char}{char}</code> Jump to the next ocurrence of {char}{char}</li>
  <li><code class="language-plaintext highlighter-rouge">S{char}{char}</code> Jump to the previous ocurrence of {char}{char}</li>
  <li><code class="language-plaintext highlighter-rouge">;</code> Go to next occurrence of {char}{char}</li>
  <li><code class="language-plaintext highlighter-rouge">,</code> Go to previous occurrence of {char}{char}</li>
  <li><code class="language-plaintext highlighter-rouge">{op}z{char}{char}</code> Apply operator on text traversed by vim sneak motion</li>
</ul>

<h2 id="moving-faster-with-easymotion">Moving Faster with Easymotion</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;w</code> start of words</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;b</code> start of words backwards</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;bdw</code> start of words everywhere. The bd stands for bidirectional</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;e</code> end of words</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;ge</code> end of words backwards</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;bdw</code> end of words everywhere</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;j</code> beginning of lines</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;k</code> beginning of lines backwards</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;f{char}</code> find character</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;F{char}</code> find character backwards</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;t{char}</code> until character</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;T{char}</code> until character backwards</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;leader&gt;&lt;leader&gt;s{char}</code> search character everywhere</li>
</ul>

<h2 id="multiple-cursors">Multiple Cursors</h2>

<p>Based on Search</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">&lt;CMD-D&gt;</code><code class="language-plaintext highlighter-rouge">&lt;C-D&gt;</code><code class="language-plaintext highlighter-rouge">gb</code> Adds an additional cursor. This command puts you in visual mode. In effect, it selects all the words under each new cursor at once.</li>
  <li><code class="language-plaintext highlighter-rouge">{operator}</code> Applies operator on words selected by the multiple cursors.</li>
  <li><code class="language-plaintext highlighter-rouge">I</code> Insert before multiple words at the same time</li>
  <li><code class="language-plaintext highlighter-rouge">A</code> Append after multiple words at the same time</li>
</ul>

<p>In Consecutive Rows</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">&lt;C-V&gt;</code> Go into Visual-block mode and select text using motions</li>
  <li><code class="language-plaintext highlighter-rouge">{operator}</code> Applies operator on visual selection.</li>
  <li><code class="language-plaintext highlighter-rouge">I</code> Insert before visual selection</li>
  <li><code class="language-plaintext highlighter-rouge">A</code> Append after visual selection</li>
</ul>

<h2 id="reusable-editing-with-macros">Reusable Editing with Macros</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">q{register}</code> Start recording a new macro in {register}</li>
  <li><code class="language-plaintext highlighter-rouge">q</code> Stop recording macro</li>
  <li><code class="language-plaintext highlighter-rouge">@{register}</code> Replay macro in {register}</li>
  <li><code class="language-plaintext highlighter-rouge">@@</code> Replay the last macro that you executed</li>
</ul>

<h2 id="integrating-with-neovim">Integrating with Neovim</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">:[range]copy {address}</code> Copy lines after {address} line</li>
  <li><code class="language-plaintext highlighter-rouge">:[range]move {address}</code> Move lines after {address} line</li>
  <li><code class="language-plaintext highlighter-rouge">:[range]normal {commands}</code> Apply normal mode commands on a range of lines</li>
  <li><code class="language-plaintext highlighter-rouge">:execute "{ex-command}"</code> Execute string as an Ex command. Useful in combination with normal when the normal mode commands include special characters.</li>
  <li><code class="language-plaintext highlighter-rouge">:[range]global/{pattern}/{command}</code> Execute an Ex command on lines within a range that match a pattern. The default range is the whole file. Really useful in combination with the normal command.</li>
  <li><code class="language-plaintext highlighter-rouge">:[range]global!/{pattern}/{command}</code> Execute an Ex command on lines within a range that do not match a pattern.</li>
</ul>

<h2 id="some-handy-vscode-only-mappings">Some Handy VSCode Only Mappings</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">af</code> Select increasingly bigger blocks of text</li>
  <li><code class="language-plaintext highlighter-rouge">gh</code> Equivalent to hovering the mouse over a bit of text.</li>
</ul>

<h2 id="bonus-01-entire-and-arguments-text-objects">Bonus 01: Entire and Arguments Text Objects</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">dae</code> Delete complete buffer</li>
  <li><code class="language-plaintext highlighter-rouge">yae</code> Yank complete buffer</li>
  <li><code class="language-plaintext highlighter-rouge">cia</code> Change function argument</li>
  <li><code class="language-plaintext highlighter-rouge">daa</code> Delete function argument including separator</li>
</ul>]]></content><author><name>Oleg Larkin</name></author><category term="other" /><category term="vim" /><summary type="html"><![CDATA[Baby Steps h move cursor to the left j move down k move up l move right i Go into Insert mode &lt;ESC&gt;&lt;C-C&gt;&lt;C-[&gt; Go back to Normal mode]]></summary></entry><entry><title type="html">Makefile Tutorial By Example</title><link href="http://localhost:4000/other/2026/01/26/makefile-tutorial.html" rel="alternate" type="text/html" title="Makefile Tutorial By Example" /><published>2026-01-26T00:00:00+03:00</published><updated>2026-01-26T00:00:00+03:00</updated><id>http://localhost:4000/other/2026/01/26/makefile-tutorial</id><content type="html" xml:base="http://localhost:4000/other/2026/01/26/makefile-tutorial.html"><![CDATA[<p><b>I built this guide because I could never quite wrap my head around Makefiles.</b> They seemed awash with hidden rules and esoteric symbols, and asking simple questions didn’t yield simple answers. To solve this, I sat down for several weekends and read everything I could about Makefiles. I’ve condensed the most critical knowledge into this guide. Each topic has a brief description and a self contained example that you can run yourself.</p>

<p>If you mostly understand Make, consider checking out the <a href="#makefile-cookbook">Makefile Cookbook</a>, which has a template for medium sized projects with ample comments about what each part of the Makefile is doing.</p>

<p>Good luck, and I hope you are able to slay the confusing world of Makefiles!</p>

<h1 id="getting-started">Getting Started</h1>

<h2 id="why-do-makefiles-exist">Why do Makefiles exist?</h2>

<p>Makefiles are used to help decide which parts of a large program need to be recompiled. In the vast majority of cases, C or C++ files are compiled. Other languages typically have their own tools that serve a similar purpose as Make. Make can also be used beyond compilation too, when you need a series of instructions to run depending on what files have changed. This tutorial will focus on the C/C++ compilation use case.</p>

<p>Here’s an example dependency graph that you might build with Make. If any file’s dependencies changes, then the file will get recompiled:</p>
<div class="center">
<img src="/assets/dependency_graph.png" />
</div>

<h2 id="what-alternatives-are-there-to-make">What alternatives are there to Make?</h2>
<p>Popular C/C++ alternative build systems are <a href="https://scons.org/">SCons</a>, <a href="https://cmake.org/">CMake</a>, <a href="https://bazel.build/">Bazel</a>, and <a href="https://ninja-build.org/">Ninja</a>. Some code editors like <a href="https://visualstudio.microsoft.com/">Microsoft Visual Studio</a> have their own built in build tools. For Java, there’s <a href="https://ant.apache.org/">Ant</a>, <a href="https://maven.apache.org/what-is-maven.html">Maven</a>, and <a href="https://gradle.org/">Gradle</a>. Other languages like Go, Rust, and TypeScript have their own build tools.</p>

<p>Interpreted languages like Python, Ruby, and raw Javascript don’t require an analogue to Makefiles. The goal of Makefiles is to compile whatever files need to be compiled, based on what files have changed. But when files in interpreted languages change, nothing needs to get recompiled. When the program runs, the most recent version of the file is used.</p>

<h2 id="the-versions-and-types-of-make">The versions and types of Make</h2>
<p>There are a variety of implementations of Make, but most of this guide will work on whatever version you’re using. However, it’s specifically written for GNU Make, which is the standard implementation on Linux and MacOS. All the examples work for Make versions 3 and 4, which are nearly equivalent other than some esoteric differences.</p>

<h2 id="running-the-examples">Running the Examples</h2>

<p>To run these examples, you’ll need a terminal and “make” installed. For each example, put the contents in a file called <code class="language-plaintext highlighter-rouge">Makefile</code>, and in that directory run the command <code class="language-plaintext highlighter-rouge">make</code>. Let’s start with the simplest of Makefiles:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">hello</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"Hello, World"</span>
</code></pre></div></div>

<blockquote>
  <p>Note: Makefiles <strong>must</strong> be indented using TABs and not spaces or <code class="language-plaintext highlighter-rouge">make</code> will fail.</p>
</blockquote>

<p>Here is the output of running the above example:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>make
<span class="nb">echo</span> <span class="s2">"Hello, World"</span>
Hello, World
</code></pre></div></div>

<p>That’s it! If you’re a bit confused, here’s a video that goes through these steps, along with describing the basic structure of Makefiles.</p>

<div class="yt-video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/zeEMISsjO38" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>

<h2 id="makefile-syntax">Makefile Syntax</h2>

<p>A Makefile consists of a set of <em>rules</em>. A rule generally looks like this:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">targets</span><span class="o">:</span> <span class="nf">prerequisites</span>
	<span class="nb">command</span>
	<span class="nb">command</span>
	<span class="nb">command</span>
</code></pre></div></div>

<ul>
  <li>The <em>targets</em> are file names, separated by spaces. Typically, there is only one per rule.</li>
  <li>The <em>commands</em> are a series of steps typically used to make the target(s). These <em>need to start with a tab character</em>, not spaces.</li>
  <li>The <em>prerequisites</em> are also file names, separated by spaces. These files need to exist before the commands for the target are run. These are also called <em>dependencies</em></li>
</ul>

<h2 id="the-essence-of-make">The essence of Make</h2>

<p>Let’s start with a hello world example:</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">hello</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"Hello, World"</span>
	<span class="nb">echo</span> <span class="s2">"This line will print if the file hello does not exist."</span>
</code></pre></div></div>
<p>There’s already a lot to take in here. Let’s break it down:</p>
<ul>
  <li>We have one <em>target</em> called <code class="language-plaintext highlighter-rouge">hello</code></li>
  <li>This target has two <em>commands</em></li>
  <li>This target has no <em>prerequisites</em></li>
</ul>

<p>We’ll then run <code class="language-plaintext highlighter-rouge">make hello</code>. As long as the <code class="language-plaintext highlighter-rouge">hello</code> file does not exist, the commands will run. If <code class="language-plaintext highlighter-rouge">hello</code> does exist, no commands will run.</p>

<p>It’s important to realize that I’m talking about <code class="language-plaintext highlighter-rouge">hello</code> as both a <em>target</em> and a <em>file</em>. That’s because the two are directly tied together. Typically, when a target is run (aka when the commands of a target are run), the commands will create a file with the same name as the target. In this case, the <code class="language-plaintext highlighter-rouge">hello</code> <em>target</em> does not create the <code class="language-plaintext highlighter-rouge">hello</code> <em>file</em>.</p>

<p>Let’s create a more typical Makefile - one that compiles a single C file. But before we do, make a file called <code class="language-plaintext highlighter-rouge">blah.c</code> that has the following contents:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// blah.c</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>Then create the Makefile (called <code class="language-plaintext highlighter-rouge">Makefile</code>, as always):</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">blah</span><span class="o">:</span>
	cc blah.c <span class="nt">-o</span> blah
</code></pre></div></div>
<p>This time, try simply running <code class="language-plaintext highlighter-rouge">make</code>. Since there’s no target supplied as an argument to the <code class="language-plaintext highlighter-rouge">make</code> command, the first target is run. In this case, there’s only one target (<code class="language-plaintext highlighter-rouge">blah</code>). The first time you run this, <code class="language-plaintext highlighter-rouge">blah</code> will be created. The second time, you’ll see <code class="language-plaintext highlighter-rouge">make: 'blah' is up to date</code>. That’s because the <code class="language-plaintext highlighter-rouge">blah</code> file already exists. But there’s a problem: if we modify <code class="language-plaintext highlighter-rouge">blah.c</code> and then run <code class="language-plaintext highlighter-rouge">make</code>, nothing gets recompiled.</p>

<p>We solve this by adding a prerequisite:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">blah</span><span class="o">:</span> <span class="nf">blah.c</span>
	cc blah.c <span class="nt">-o</span> blah
</code></pre></div></div>

<p>When we run <code class="language-plaintext highlighter-rouge">make</code> again, the following set of steps happens:</p>
<ul>
  <li>The first target is selected, because the first target is the default target</li>
  <li>This has a prerequisite of <code class="language-plaintext highlighter-rouge">blah.c</code></li>
  <li>Make decides if it should run the <code class="language-plaintext highlighter-rouge">blah</code> target. It will only run if <code class="language-plaintext highlighter-rouge">blah</code> doesn’t exist, or <code class="language-plaintext highlighter-rouge">blah.c</code> is <em>newer than</em> <code class="language-plaintext highlighter-rouge">blah</code></li>
</ul>

<p>This last step is critical, and is the <strong>essence of make</strong>. What it’s attempting to do is decide if the prerequisites of <code class="language-plaintext highlighter-rouge">blah</code> have changed since <code class="language-plaintext highlighter-rouge">blah</code> was last compiled. That is, if <code class="language-plaintext highlighter-rouge">blah.c</code> is modified, running <code class="language-plaintext highlighter-rouge">make</code> should recompile the file. And conversely, if <code class="language-plaintext highlighter-rouge">blah.c</code> has not changed, then it should not be recompiled.</p>

<p>To make this happen, it uses the filesystem timestamps as a proxy to determine if something has changed. This is a reasonable heuristic, because file timestamps typically will only change if the files are
modified. But it’s important to realize that this isn’t always the case. You could, for example, modify a file, and then change the modified timestamp of that file to something old. If you did, Make would incorrectly guess that the file hadn’t changed and thus could be ignored.</p>

<p>Whew, what a mouthful. <strong>Make sure that you understand this. It’s the crux of Makefiles, and might take you a few minutes to properly understand</strong>. Play around with the above examples or watch the video above if things are still confusing.</p>

<h2 id="more-quick-examples">More quick examples</h2>
<p>The following Makefile ultimately runs all three targets. When you run <code class="language-plaintext highlighter-rouge">make</code> in the terminal, it will build a program called <code class="language-plaintext highlighter-rouge">blah</code> in a series of steps:</p>
<ul>
  <li>Make selects the target <code class="language-plaintext highlighter-rouge">blah</code>, because the first target is the default target</li>
  <li><code class="language-plaintext highlighter-rouge">blah</code> requires <code class="language-plaintext highlighter-rouge">blah.o</code>, so make searches for the <code class="language-plaintext highlighter-rouge">blah.o</code> target</li>
  <li><code class="language-plaintext highlighter-rouge">blah.o</code> requires <code class="language-plaintext highlighter-rouge">blah.c</code>, so make searches for the <code class="language-plaintext highlighter-rouge">blah.c</code> target</li>
  <li><code class="language-plaintext highlighter-rouge">blah.c</code> has no dependencies, so the <code class="language-plaintext highlighter-rouge">echo</code> command is run</li>
  <li>The <code class="language-plaintext highlighter-rouge">cc -c</code> command is then run, because all of the <code class="language-plaintext highlighter-rouge">blah.o</code> dependencies are finished</li>
  <li>The top <code class="language-plaintext highlighter-rouge">cc</code> command is run, because all the <code class="language-plaintext highlighter-rouge">blah</code> dependencies are finished</li>
  <li>That’s it: <code class="language-plaintext highlighter-rouge">blah</code> is a compiled c program</li>
</ul>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">blah</span><span class="o">:</span> <span class="nf">blah.o</span>
	cc blah.o <span class="nt">-o</span> blah <span class="c"># Runs third</span>

<span class="nl">blah.o</span><span class="o">:</span> <span class="nf">blah.c</span>
	cc <span class="nt">-c</span> blah.c <span class="nt">-o</span> blah.o <span class="c"># Runs second</span>

<span class="c"># Typically blah.c would already exist, but I want to limit any additional required files
</span><span class="nl">blah.c</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"int main() { return 0; }"</span> <span class="o">&gt;</span> blah.c <span class="c"># Runs first</span>
</code></pre></div></div>

<p>If you delete <code class="language-plaintext highlighter-rouge">blah.c</code>, all three targets will be rerun. If you edit it (and thus change the timestamp to newer than <code class="language-plaintext highlighter-rouge">blah.o</code>), the first two targets will run. If you run <code class="language-plaintext highlighter-rouge">touch blah.o</code> (and thus change the timestamp to newer than <code class="language-plaintext highlighter-rouge">blah</code>), then only the first target will run. If you change nothing, none of the targets will run. Try it out!</p>

<p>This next example doesn’t do anything new, but is nontheless a good additional example. It will always run both targets, because <code class="language-plaintext highlighter-rouge">some_file</code> depends on <code class="language-plaintext highlighter-rouge">other_file</code>, which is never created.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">some_file</span><span class="o">:</span> <span class="nf">other_file</span>
	<span class="nb">echo</span> <span class="s2">"This will always run, and runs second"</span>
	<span class="nb">touch </span>some_file

<span class="nl">other_file</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"This will always run, and runs first"</span>
</code></pre></div></div>

<h2 id="make-clean">Make clean</h2>
<p><code class="language-plaintext highlighter-rouge">clean</code> is often used as a target that removes the output of other targets, but it is not a special word in Make. You can run <code class="language-plaintext highlighter-rouge">make</code> and <code class="language-plaintext highlighter-rouge">make clean</code> on this to create and delete <code class="language-plaintext highlighter-rouge">some_file</code>.</p>

<p>Note that <code class="language-plaintext highlighter-rouge">clean</code> is doing two new things here:</p>
<ul>
  <li>It’s a target that is not first (the default), and not a prerequisite. That means it’ll never run unless you explicitly call <code class="language-plaintext highlighter-rouge">make clean</code></li>
  <li>It’s not intended to be a filename. If you happen to have a file named <code class="language-plaintext highlighter-rouge">clean</code>, this target won’t run, which is not what we want. See <code class="language-plaintext highlighter-rouge">.PHONY</code> later in this tutorial on how to fix this</li>
</ul>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">some_file</span><span class="o">:</span>
	<span class="nb">touch </span>some_file

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> some_file
</code></pre></div></div>

<h2 id="variables">Variables</h2>
<p>Variables can only be strings. You’ll typically want to use <code class="language-plaintext highlighter-rouge">:=</code>, but <code class="language-plaintext highlighter-rouge">=</code> also works. See <a href="#variables-pt-2">Variables Pt 2</a>.</p>

<p>Here’s an example of using variables:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">files</span> <span class="o">:=</span> file1 file2
<span class="nl">some_file</span><span class="o">:</span> <span class="nf">$(files)</span>
	<span class="nb">echo</span> <span class="s2">"Look at this variable: "</span> <span class="p">$(</span>files<span class="p">)</span>
	<span class="nb">touch </span>some_file

<span class="nl">file1</span><span class="o">:</span>
	<span class="nb">touch </span>file1
<span class="nl">file2</span><span class="o">:</span>
	<span class="nb">touch </span>file2

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> file1 file2 some_file
</code></pre></div></div>

<p>Single or double quotes have no meaning to Make. They are simply characters that are assigned to the variable. Quotes <em>are</em> useful to shell/bash, though, and you need them in commands like <code class="language-plaintext highlighter-rouge">printf</code>. In this example, the two commands behave the same:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">a</span> <span class="o">:=</span> one two# a is <span class="nb">set </span>to the string <span class="s2">"one two"</span>
<span class="nv">b</span> <span class="o">:=</span> <span class="s1">'one two'</span> <span class="c"># Not recommended. b is set to the string "'one two'"</span>
<span class="nl">all</span><span class="o">:</span>
	<span class="nb">printf</span> <span class="s1">'$a'</span>
	<span class="nb">printf</span> <span class="nv">$b</span>
</code></pre></div></div>

<p>Reference variables using either <code class="language-plaintext highlighter-rouge">${}</code> or <code class="language-plaintext highlighter-rouge">$()</code></p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">x</span> <span class="o">:=</span> dude

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>x<span class="p">)</span>
	<span class="nb">echo</span> <span class="p">${</span>x<span class="p">}</span>

	<span class="c"># Bad practice, but works
</span>	<span class="err">echo</span> <span class="err">$x</span>
</code></pre></div></div>

<h1 id="targets">Targets</h1>
<h2 id="the-all-target">The all target</h2>
<!--  (Section 4.4) -->
<p>Making multiple targets and you want all of them to run? Make an <code class="language-plaintext highlighter-rouge">all</code> target.
Since this is the first rule listed, it will run by default if <code class="language-plaintext highlighter-rouge">make</code> is called without specifying a target.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span> <span class="nf">one two three</span>

<span class="nl">one</span><span class="o">:</span>
	<span class="nb">touch </span>one
<span class="nl">two</span><span class="o">:</span>
	<span class="nb">touch </span>two
<span class="nl">three</span><span class="o">:</span>
	<span class="nb">touch </span>three

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> one two three

</code></pre></div></div>

<h2 id="multiple-targets">Multiple targets</h2>
<!--  (Section 4.8) -->
<p>When there are multiple targets for a rule, the commands will be run for each target. <code class="language-plaintext highlighter-rouge">$@</code> is an <a href="#automatic-variables">automatic variable</a> that contains the target name.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span> <span class="nf">f1.o f2.o</span>

<span class="nl">f1.o f2.o</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="nv">$@</span>
<span class="c"># Equivalent to:
# f1.o:
# 	echo f1.o
# f2.o:
# 	echo f2.o
</span>
</code></pre></div></div>

<h1 id="automatic-variables-and-wildcards">Automatic Variables and Wildcards</h1>
<h2 id="-wildcard">* Wildcard</h2>
<!--  (Section 4.2) -->
<p>Both <code class="language-plaintext highlighter-rouge">*</code> and <code class="language-plaintext highlighter-rouge">%</code> are called wildcards in Make, but they mean entirely different things. <code class="language-plaintext highlighter-rouge">*</code> searches your filesystem for matching filenames. I suggest that you always wrap it in the <code class="language-plaintext highlighter-rouge">wildcard</code> function, because otherwise you may fall into a common pitfall described below.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Print out file information about every .c file
</span><span class="nl">print</span><span class="o">:</span> <span class="nf">$(wildcard *.c)</span>
	<span class="nb">ls</span> <span class="nt">-la</span>  <span class="nv">$?</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">*</code> may be used in the target, prerequisites, or in the <code class="language-plaintext highlighter-rouge">wildcard</code> function.</p>

<p>Danger: <code class="language-plaintext highlighter-rouge">*</code> may not be directly used in a variable definitions</p>

<p>Danger: When <code class="language-plaintext highlighter-rouge">*</code> matches no files, it is left as it is (unless run in the <code class="language-plaintext highlighter-rouge">wildcard</code> function)</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">thing_wrong</span> <span class="o">:=</span> <span class="k">*</span>.o <span class="c"># Don't do this! '*' will not get expanded</span>
<span class="nv">thing_right</span> <span class="o">:=</span> <span class="p">$(</span>wildcard <span class="k">*</span>.o<span class="p">)</span>

<span class="nl">all</span><span class="o">:</span> <span class="nf">one two three four</span>

<span class="c"># Fails, because $(thing_wrong) is the string "*.o"
</span><span class="nl">one</span><span class="o">:</span> <span class="nf">$(thing_wrong)</span>

<span class="c"># Stays as *.o if there are no files that match this pattern :(
</span><span class="nl">two</span><span class="o">:</span> <span class="nf">*.o</span>

<span class="c"># Works as you would expect! In this case, it does nothing.
</span><span class="nl">three</span><span class="o">:</span> <span class="nf">$(thing_right)</span>

<span class="c"># Same as rule three
</span><span class="nl">four</span><span class="o">:</span> <span class="nf">$(wildcard *.o)</span>
</code></pre></div></div>

<h2 id="-wildcard-1">% Wildcard</h2>
<p><code class="language-plaintext highlighter-rouge">%</code> is really useful, but is somewhat confusing because of the variety of situations it can be used in.</p>
<ul>
  <li>When used in “matching” mode, it matches one or more characters in a string. This match is called the stem.</li>
  <li>When used in “replacing” mode, it takes the stem that was matched and replaces that in a string.</li>
  <li><code class="language-plaintext highlighter-rouge">%</code> is most often used in rule definitions and in some specific functions.</li>
</ul>

<p>See these sections on examples of it being used:</p>
<ul>
  <li><a href="#static-pattern-rules">Static Pattern Rules</a></li>
  <li><a href="#pattern-rules">Pattern Rules</a></li>
  <li><a href="#string-substitution">String Substitution</a></li>
  <li><a href="#the-vpath-directive">The vpath Directive</a></li>
</ul>

<h2 id="automatic-variables">Automatic Variables</h2>
<!--  (Section 10.5) -->
<p>There are many <a href="https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html">automatic variables</a>, but often only a few show up:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">hey</span><span class="o">:</span> <span class="nf">one two</span>
	<span class="c"># Outputs "hey", since this is the target name</span>
	<span class="nb">echo</span> <span class="nv">$@</span>

	<span class="c"># Outputs all prerequisites newer than the target
</span>	<span class="err">echo</span> <span class="err">$?</span>

	<span class="c"># Outputs all prerequisites
</span>	<span class="err">echo</span> <span class="err">$^</span>

	<span class="c"># Outputs the first prerequisite
</span>	<span class="err">echo</span> <span class="err">$&lt;</span>

	<span class="err">touch</span> <span class="err">hey</span>

<span class="nl">one</span><span class="o">:</span>
	<span class="nb">touch </span>one

<span class="nl">two</span><span class="o">:</span>
	<span class="nb">touch </span>two

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> hey one two

</code></pre></div></div>

<h1 id="fancy-rules">Fancy Rules</h1>
<h2 id="implicit-rules">Implicit Rules</h2>
<!--  (Section 10) -->
<p>Make loves c compilation. And every time it expresses its love, things get confusing. Perhaps the most confusing part of Make is the magic/automatic rules that are made. Make calls these “implicit” rules. I don’t personally agree with this design decision, and I don’t recommend using them, but they’re often used and are thus useful to know. Here’s a list of implicit rules:</p>
<ul>
  <li>Compiling a C program: <code class="language-plaintext highlighter-rouge">n.o</code> is made automatically from <code class="language-plaintext highlighter-rouge">n.c</code> with a command of the form <code class="language-plaintext highlighter-rouge">$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@</code></li>
  <li>Compiling a C++ program: <code class="language-plaintext highlighter-rouge">n.o</code> is made automatically from <code class="language-plaintext highlighter-rouge">n.cc</code> or <code class="language-plaintext highlighter-rouge">n.cpp</code> with a command of the form <code class="language-plaintext highlighter-rouge">$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@</code></li>
  <li>Linking a single object file: <code class="language-plaintext highlighter-rouge">n</code> is made automatically from <code class="language-plaintext highlighter-rouge">n.o</code> by running the command <code class="language-plaintext highlighter-rouge">$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@</code></li>
</ul>

<p>The important variables used by implicit rules are:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">CC</code>: Program for compiling C programs; default <code class="language-plaintext highlighter-rouge">cc</code></li>
  <li><code class="language-plaintext highlighter-rouge">CXX</code>: Program for compiling C++ programs; default <code class="language-plaintext highlighter-rouge">g++</code></li>
  <li><code class="language-plaintext highlighter-rouge">CFLAGS</code>: Extra flags to give to the C compiler</li>
  <li><code class="language-plaintext highlighter-rouge">CXXFLAGS</code>: Extra flags to give to the C++ compiler</li>
  <li><code class="language-plaintext highlighter-rouge">CPPFLAGS</code>: Extra flags to give to the C preprocessor</li>
  <li><code class="language-plaintext highlighter-rouge">LDFLAGS</code>: Extra flags to give to compilers when they are supposed to invoke the linker</li>
</ul>

<p>Let’s see how we can now build a C program without ever explicitly telling Make how to do the compilation:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">CC</span> <span class="o">=</span> gcc <span class="c"># Flag for implicit rules</span>
<span class="nv">CFLAGS</span> <span class="o">=</span> <span class="nt">-g</span> <span class="c"># Flag for implicit rules. Turn on debug info</span>

<span class="c"># Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
</span><span class="nl">blah</span><span class="o">:</span> <span class="nf">blah.o</span>

<span class="nl">blah.c</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"int main() { return 0; }"</span> <span class="o">&gt;</span> blah.c

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> blah<span class="k">*</span>
</code></pre></div></div>

<h2 id="static-pattern-rules">Static Pattern Rules</h2>
<!--  (Section 4.10) -->
<p>Static pattern rules are another way to write less in a Makefile. Here’s their syntax:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">targets...</span><span class="o">:</span> <span class="nf">target-pattern: prereq-patterns ...</span>
   <span class="err">commands</span>
</code></pre></div></div>

<p>The essence is that the given <code class="language-plaintext highlighter-rouge">target</code> is matched by the <code class="language-plaintext highlighter-rouge">target-pattern</code> (via a <code class="language-plaintext highlighter-rouge">%</code> wildcard). Whatever was matched is called the <em>stem</em>. The stem is then substituted into the <code class="language-plaintext highlighter-rouge">prereq-pattern</code>, to generate the target’s prereqs.</p>

<p>A typical use case is to compile <code class="language-plaintext highlighter-rouge">.c</code> files into <code class="language-plaintext highlighter-rouge">.o</code> files. Here’s the <em>manual way</em>:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">objects</span> <span class="o">=</span> foo.o bar.o all.o
<span class="nl">all</span><span class="o">:</span> <span class="nf">$(objects)</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="nv">$^</span> <span class="nt">-o</span> all

<span class="nl">foo.o</span><span class="o">:</span> <span class="nf">foo.c</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> foo.c <span class="nt">-o</span> foo.o

<span class="nl">bar.o</span><span class="o">:</span> <span class="nf">bar.c</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> bar.c <span class="nt">-o</span> bar.o

<span class="nl">all.o</span><span class="o">:</span> <span class="nf">all.c</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> all.c <span class="nt">-o</span> all.o

<span class="nl">all.c</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"int main() { return 0; }"</span> <span class="o">&gt;</span> all.c

<span class="c"># Note: all.c does not use this rule because Make prioritizes more specific matches when there is more than one match.
</span><span class="nl">%.c</span><span class="o">:</span>
	<span class="nb">touch</span> <span class="nv">$@</span>

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> <span class="k">*</span>.c <span class="k">*</span>.o all
</code></pre></div></div>

<p>Here’s the more <em>efficient way</em>, using a static pattern rule:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">objects</span> <span class="o">=</span> foo.o bar.o all.o
<span class="nl">all</span><span class="o">:</span> <span class="nf">$(objects)</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="nv">$^</span> <span class="nt">-o</span> all

<span class="c"># Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
</span><span class="nl">$(objects)</span><span class="o">:</span> <span class="nf">%.o: %.c</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-c</span> <span class="nv">$^</span> <span class="nt">-o</span> <span class="nv">$@</span>

<span class="nl">all.c</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"int main() { return 0; }"</span> <span class="o">&gt;</span> all.c

<span class="c"># Note: all.c does not use this rule because Make prioritizes more specific matches when there is more than one match.
</span><span class="nl">%.c</span><span class="o">:</span>
	<span class="nb">touch</span> <span class="nv">$@</span>

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> <span class="k">*</span>.c <span class="k">*</span>.o all
</code></pre></div></div>

<h2 id="static-pattern-rules-and-filter">Static Pattern Rules and Filter</h2>
<!--  (Section 4.10) -->
<p>While I introduce the <a href="#the-filter-function">filter function</a> later on, it’s common to use in static pattern rules, so I’ll mention that here. The <code class="language-plaintext highlighter-rouge">filter</code> function can be used in Static pattern rules to match the correct files. In this example, I made up the <code class="language-plaintext highlighter-rouge">.raw</code> and <code class="language-plaintext highlighter-rouge">.result</code> extensions.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">obj_files</span> <span class="o">=</span> foo.result bar.o lose.o
<span class="nv">src_files</span> <span class="o">=</span> foo.raw bar.c lose.c

<span class="nl">all</span><span class="o">:</span> <span class="nf">$(obj_files)</span>
<span class="c"># Note: PHONY is important here. Without it, implicit rules will try to build the executable "all", since the prereqs are ".o" files.
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">all</span>

<span class="c"># Ex 1: .o files depend on .c files. Though we don't actually make the .o file.
</span><span class="nl">$(filter %.o,$(obj_files))</span><span class="o">:</span> <span class="nf">%.o: %.c</span>
	<span class="nb">echo</span> <span class="s2">"target: </span><span class="nv">$@</span><span class="s2"> prereq: </span><span class="nv">$&lt;</span><span class="s2">"</span>

<span class="c"># Ex 2: .result files depend on .raw files. Though we don't actually make the .result file.
</span><span class="nl">$(filter %.result,$(obj_files))</span><span class="o">:</span> <span class="nf">%.result: %.raw</span>
	<span class="nb">echo</span> <span class="s2">"target: </span><span class="nv">$@</span><span class="s2"> prereq: </span><span class="nv">$&lt;</span><span class="s2">"</span>

<span class="nl">%.c %.raw</span><span class="o">:</span>
	<span class="nb">touch</span> <span class="nv">$@</span>

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> <span class="p">$(</span>src_files<span class="p">)</span>
</code></pre></div></div>

<h2 id="pattern-rules">Pattern Rules</h2>
<p>Pattern rules are often used but quite confusing. You can look at them as two ways:</p>
<ul>
  <li>A way to define your own implicit rules</li>
  <li>A simpler form of static pattern rules</li>
</ul>

<p>Let’s start with an example first:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Define a pattern rule that compiles every .c file into a .o file
</span><span class="nl">%.o </span><span class="o">:</span> <span class="nf">%.c</span>
        <span class="err">$(CC)</span> <span class="err">-c</span> <span class="err">$(CFLAGS)</span> <span class="err">$(CPPFLAGS)</span> <span class="err">$&lt;</span> <span class="err">-o</span> <span class="err">$@</span>
</code></pre></div></div>

<p>Pattern rules contain a ‘%’ in the target. This ‘%’ matches any nonempty string, and the other characters match themselves. ‘%’ in a prerequisite of a pattern rule stands for the same stem that was matched by the ‘%’ in the target.</p>

<p>Here’s another example:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Define a pattern rule that has no pattern in the prerequisites.
# This just creates empty .c files when needed.
</span><span class="nl">%.c</span><span class="o">:</span>
   <span class="err">touch</span> <span class="err">$@</span>
</code></pre></div></div>

<h2 id="double-colon-rules">Double-Colon Rules</h2>
<!--  (Section 4.11) -->
<p>Double-Colon Rules are rarely used, but allow multiple rules to be defined for the same target. If these were single colons, a warning would be printed and only the second set of commands would run.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span> <span class="nf">blah</span>

<span class="nl">blah</span><span class="o">::</span>
	<span class="nb">echo</span> <span class="s2">"hello"</span>

<span class="nl">blah</span><span class="o">::</span>
	<span class="nb">echo</span> <span class="s2">"hello again"</span>
</code></pre></div></div>

<h1 id="commands-and-execution">Commands and execution</h1>
<h2 id="command-echoingsilencing">Command Echoing/Silencing</h2>
<!--  (Section 5.1) -->
<p>Add an <code class="language-plaintext highlighter-rouge">@</code> before a command to stop it from being printed
You can also run make with <code class="language-plaintext highlighter-rouge">-s</code> to add an <code class="language-plaintext highlighter-rouge">@</code> before each line</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"This make line will not be printed"</span>
	<span class="nb">echo</span> <span class="s2">"But this will"</span>
</code></pre></div></div>

<h2 id="command-execution">Command Execution</h2>
<!--  (Section 5.2) -->
<p>Each command is run in a new shell (or at least the effect is as such)</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span>
	<span class="nb">cd</span> ..
	<span class="c"># The cd above does not affect this line, because each command is effectively run in a new shell</span>
	<span class="nb">echo</span> <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>

	<span class="c"># This cd command affects the next because they are on the same line
</span>	<span class="err">cd</span> <span class="err">..;echo</span> <span class="err">`pwd`</span>

	<span class="c"># Same as above
</span>	<span class="err">cd</span> <span class="err">..;</span> <span class="err">\</span>
	<span class="err">echo</span> <span class="err">`pwd`</span>

</code></pre></div></div>

<h2 id="default-shell">Default Shell</h2>
<!--  (Section 5.2) -->
<p>The default shell is <code class="language-plaintext highlighter-rouge">/bin/sh</code>. You can change this by changing the variable SHELL:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">SHELL</span><span class="o">=</span>/bin/bash

<span class="nl">cool</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"Hello from bash"</span>
</code></pre></div></div>

<h2 id="double-dollar-sign">Double dollar sign</h2>
<p>If you want a string to have a dollar sign, you can use <code class="language-plaintext highlighter-rouge">$$</code>. This is how to use a shell variable in <code class="language-plaintext highlighter-rouge">bash</code> or <code class="language-plaintext highlighter-rouge">sh</code>.</p>

<p>Note the differences between Makefile variables and Shell variables in this next example.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">make_var</span> <span class="o">=</span> I am a make variable
<span class="nl">all</span><span class="o">:</span>
	<span class="c"># Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell</span>
	<span class="nv">sh_var</span><span class="o">=</span><span class="s1">'I am a shell variable'</span><span class="p">;</span> <span class="nb">echo</span> <span class="nv">$$</span>sh_var

	<span class="c"># Same as running "echo I am a make variable" in the shell
</span>	<span class="err">echo</span> <span class="err">$(make_var)</span>
</code></pre></div></div>

<h2 id="error-handling-with--k--i-and--">Error handling with <code class="language-plaintext highlighter-rouge">-k</code>, <code class="language-plaintext highlighter-rouge">-i</code>, and <code class="language-plaintext highlighter-rouge">-</code></h2>
<!--  (Section 5.4) -->
<p>Add <code class="language-plaintext highlighter-rouge">-k</code> when running make to continue running even in the face of errors. Helpful if you want to see all the errors of Make at once.
Add a <code class="language-plaintext highlighter-rouge">-</code> before a command to suppress the error
Add <code class="language-plaintext highlighter-rouge">-i</code> to make to have this happen for every command.</p>

<!--  (Section 5.4) -->
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">one</span><span class="o">:</span>
	<span class="c"># This error will be printed but ignored, and make will continue to run</span>
	<span class="p">-</span><span class="nb">false</span>
	<span class="nb">touch </span>one

</code></pre></div></div>

<h2 id="interrupting-or-killing-make">Interrupting or killing make</h2>
<!--  (Section 5.5) -->
<p>Note only: If you <code class="language-plaintext highlighter-rouge">ctrl+c</code> make, it will delete the newer targets it just made.</p>

<h2 id="recursive-use-of-make">Recursive use of make</h2>
<!--  (Section 5.6) -->
<p>To recursively call a makefile, use the special <code class="language-plaintext highlighter-rouge">$(MAKE)</code> instead of <code class="language-plaintext highlighter-rouge">make</code> because it will pass the make flags for you and won’t itself be affected by them.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">new_contents</span> <span class="o">=</span> <span class="s2">"hello:</span><span class="se">\n\t</span><span class="s2">touch inside_file"</span>
<span class="nl">all</span><span class="o">:</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> subdir
	<span class="nb">printf</span> <span class="p">$(</span>new_contents<span class="p">)</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/^ //'</span> <span class="o">&gt;</span> subdir/makefile
	<span class="nb">cd </span>subdir <span class="o">&amp;&amp;</span> <span class="p">$(</span>MAKE<span class="p">)</span>

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-rf</span> subdir

</code></pre></div></div>

<h2 id="export-environments-and-recursive-make">Export, environments, and recursive make</h2>
<!--  (Section 5.6) -->
<p>When Make starts, it automatically creates Make variables out of all the environment variables that are set when it’s executed.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Run this with "export shell_env_var='I am an environment variable'; make"
</span><span class="nl">all</span><span class="o">:</span>
	<span class="c"># Print out the Shell variable</span>
	<span class="nb">echo</span> <span class="nv">$$</span>shell_env_var

	<span class="c"># Print out the Make variable
</span>	<span class="err">echo</span> <span class="err">$(shell_env_var)</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">export</code> directive takes a variable and sets it the environment for all shell commands in all the recipes:</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">shell_env_var</span><span class="o">=</span>Shell <span class="nb">env </span>var, created inside of Make
<span class="k">export</span> <span class="nv">shell_env_var</span>
<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>shell_env_var<span class="p">)</span>
	<span class="nb">echo</span> <span class="nv">$$</span>shell_env_var
</code></pre></div></div>

<p>As such, when you run the <code class="language-plaintext highlighter-rouge">make</code> command inside of make, you can use the <code class="language-plaintext highlighter-rouge">export</code> directive to make it accessible to sub-make commands. In this example, <code class="language-plaintext highlighter-rouge">cooly</code> is exported such that the makefile in subdir can use it.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">new_contents</span> <span class="o">=</span> <span class="s2">"hello:</span><span class="se">\n\t</span><span class="s2">echo </span><span class="se">\$</span><span class="p">$(</span><span class="s2">cooly</span><span class="p">)</span><span class="s2">"</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> subdir
	<span class="nb">printf</span> <span class="p">$(</span>new_contents<span class="p">)</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/^ //'</span> <span class="o">&gt;</span> subdir/makefile
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"---MAKEFILE CONTENTS---"</span>
	<span class="p">@</span><span class="nb">cd </span>subdir <span class="o">&amp;&amp;</span> <span class="nb">cat </span>makefile
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"---END MAKEFILE CONTENTS---"</span>
	<span class="nb">cd </span>subdir <span class="o">&amp;&amp;</span> <span class="p">$(</span>MAKE<span class="p">)</span>

<span class="c"># Note that variables and exports. They are set/affected globally.
</span><span class="nv">cooly</span> <span class="o">=</span> <span class="s2">"The subdirectory can see me!"</span>
<span class="k">export</span> <span class="nv">cooly</span>
<span class="c"># This would nullify the line above: unexport cooly
</span>
<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-rf</span> subdir
</code></pre></div></div>

<!--  (Section 5.6) -->
<p>You need to export variables to have them run in the shell as well.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">one</span><span class="o">=</span>this will only work locally
<span class="k">export </span><span class="nv">two</span><span class="o">=</span>we can run subcommands with this

<span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>one<span class="p">)</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="nv">$$</span>one
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>two<span class="p">)</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="nv">$$</span>two
</code></pre></div></div>

<!--  (Section 5.6) -->
<p><code class="language-plaintext highlighter-rouge">.EXPORT_ALL_VARIABLES</code> exports all variables for you.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">.EXPORT_ALL_VARIABLES</span><span class="o">:</span>
<span class="nv">new_contents</span> <span class="o">=</span> <span class="s2">"hello:</span><span class="se">\n\t</span><span class="s2">echo </span><span class="se">\$</span><span class="p">$(</span><span class="s2">cooly</span><span class="p">)</span><span class="s2">"</span>

<span class="nv">cooly</span> <span class="o">=</span> <span class="s2">"The subdirectory can see me!"</span>
<span class="c"># This would nullify the line above: unexport cooly
</span>
<span class="nl">all</span><span class="o">:</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> subdir
	<span class="nb">printf</span> <span class="p">$(</span>new_contents<span class="p">)</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/^ //'</span> <span class="o">&gt;</span> subdir/makefile
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"---MAKEFILE CONTENTS---"</span>
	<span class="p">@</span><span class="nb">cd </span>subdir <span class="o">&amp;&amp;</span> <span class="nb">cat </span>makefile
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"---END MAKEFILE CONTENTS---"</span>
	<span class="nb">cd </span>subdir <span class="o">&amp;&amp;</span> <span class="p">$(</span>MAKE<span class="p">)</span>

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-rf</span> subdir
</code></pre></div></div>

<h2 id="arguments-to-make">Arguments to make</h2>
<!--  (Section 9) -->

<p>There’s a nice <a href="http://www.gnu.org/software/make/manual/make.html#Options-Summary">list of options</a> that can be run from make. Check out <code class="language-plaintext highlighter-rouge">--dry-run</code>, <code class="language-plaintext highlighter-rouge">--touch</code>, <code class="language-plaintext highlighter-rouge">--old-file</code>.</p>

<p>You can have multiple targets to make, i.e. <code class="language-plaintext highlighter-rouge">make clean run test</code> runs the <code class="language-plaintext highlighter-rouge">clean</code> goal, then <code class="language-plaintext highlighter-rouge">run</code>, and then <code class="language-plaintext highlighter-rouge">test</code>.</p>

<h1 id="variables-pt-2">Variables Pt. 2</h1>
<h2 id="flavors-and-modification">Flavors and modification</h2>
<!-- (6.1, 6.2, 6.3) -->
<p>There are two flavors of variables:</p>
<ul>
  <li>recursive (use <code class="language-plaintext highlighter-rouge">=</code>) - only looks for the variables when the command is <em>used</em>, not when it’s <em>defined</em>.</li>
  <li>simply expanded (use <code class="language-plaintext highlighter-rouge">:=</code>) - like normal imperative programming – only those defined so far get expanded</li>
</ul>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Recursive variable. This will print "later" below
</span><span class="nv">one</span> <span class="o">=</span> one <span class="p">${</span>later_variable<span class="p">}</span>
<span class="c"># Simply expanded variable. This will not print "later" below
</span><span class="nv">two</span> <span class="o">:=</span> two <span class="p">${</span>later_variable<span class="p">}</span>

<span class="nv">later_variable</span> <span class="o">=</span> later

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>one<span class="p">)</span>
	<span class="nb">echo</span> <span class="p">$(</span>two<span class="p">)</span>
</code></pre></div></div>

<p>Simply expanded (using <code class="language-plaintext highlighter-rouge">:=</code>) allows you to append to a variable. Recursive definitions will give an infinite loop error.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">one</span> <span class="o">=</span> hello
<span class="c"># one gets defined as a simply expanded variable (:=) and thus can handle appending
</span><span class="nv">one</span> <span class="o">:=</span> <span class="p">${</span>one<span class="p">}</span> there

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>one<span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">?=</code> only sets variables if they have not yet been set</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">one</span> <span class="o">=</span> hello
<span class="nv">one</span> <span class="o">?=</span> will not be <span class="nb">set</span>
<span class="nv">two</span> <span class="o">?=</span> will be <span class="nb">set</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>one<span class="p">)</span>
	<span class="nb">echo</span> <span class="p">$(</span>two<span class="p">)</span>
</code></pre></div></div>

<p>Spaces at the end of a line are not stripped, but those at the start are. To make a variable with a single space, use <code class="language-plaintext highlighter-rouge">$(nullstring)</code></p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">with_spaces</span> <span class="o">=</span> hello   <span class="c"># with_spaces has many spaces after "hello"</span>
<span class="nv">after</span> <span class="o">=</span> <span class="p">$(</span>with_spaces<span class="p">)</span>there

<span class="nv">nullstring</span> <span class="o">=</span>
<span class="nv">space</span> <span class="o">=</span> <span class="p">$(</span>nullstring<span class="p">)</span> <span class="c"># Make a variable with a single space.</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="s2">"</span><span class="p">$(</span><span class="s2">after</span><span class="p">)</span><span class="s2">"</span>
	<span class="nb">echo </span>start<span class="s2">"</span><span class="p">$(</span><span class="s2">space</span><span class="p">)</span><span class="s2">"</span>end
</code></pre></div></div>

<p>An undefined variable is actually an empty string!</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span>
	<span class="c"># Undefined variables are just empty strings!</span>
	<span class="nb">echo</span> <span class="p">$(</span>nowhere<span class="p">)</span>
</code></pre></div></div>

<p>Use <code class="language-plaintext highlighter-rouge">+=</code> to append</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">foo</span> <span class="o">:=</span> start
<span class="nv">foo</span> <span class="o">+=</span> more

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>foo<span class="p">)</span>
</code></pre></div></div>

<p><a href="#string-substitution">String Substitution</a> is also a really common and useful way to modify variables. Also check out <a href="https://www.gnu.org/software/make/manual/html_node/Text-Functions.html#Text-Functions">Text Functions</a> and <a href="https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#File-Name-Functions">Filename Functions</a>.</p>

<h2 id="command-line-arguments-and-override">Command line arguments and override</h2>
<!--  (Section 6.7) -->
<p>You can override variables that come from the command line by using <code class="language-plaintext highlighter-rouge">override</code>.
Here we ran make with <code class="language-plaintext highlighter-rouge">make option_one=hi</code></p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Overrides command line arguments
</span><span class="nb">override</span> <span class="nv">option_one</span> <span class="o">=</span> did_override
<span class="c"># Does not override command line arguments
</span><span class="nv">option_two</span> <span class="o">=</span> not_override
<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>option_one<span class="p">)</span>
	<span class="nb">echo</span> <span class="p">$(</span>option_two<span class="p">)</span>
</code></pre></div></div>

<h2 id="list-of-commands-and-define">List of commands and define</h2>
<!--  (Section 6.8) -->
<p>The <a href="https://www.gnu.org/software/make/manual/html_node/Multi_002dLine.html">define directive</a> is not a function, though it may look that way. I’ve seen it used so infrequently that I won’t go into details, but it’s mainly used for defining <a href="https://www.gnu.org/software/make/manual/html_node/Canned-Recipes.html#Canned-Recipes">canned recipes</a> and also pairs well with the <a href="https://www.gnu.org/software/make/manual/html_node/Eval-Function.html#Eval-Function">eval function</a>.</p>

<p><code class="language-plaintext highlighter-rouge">define</code>/<code class="language-plaintext highlighter-rouge">endef</code> simply creates a variable that is set to a list of commands. Note here that it’s a bit different than having a semi-colon between commands, because each is run in a separate shell, as expected.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">one</span> <span class="o">=</span> <span class="nb">export </span><span class="nv">blah</span><span class="o">=</span><span class="s2">"I was set!"</span><span class="p">;</span> <span class="nb">echo</span> <span class="nv">$$</span>blah

<span class="k">define</span> <span class="nv">two</span>
<span class="k">export </span><span class="nv">blah</span><span class="o">=</span><span class="s2">"I was set!"</span>
<span class="err">echo</span> <span class="err">$$blah</span>
<span class="k">endef</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"This prints 'I was set'"</span>
	<span class="p">@$(</span>one<span class="p">)</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"This does not print 'I was set' because each command runs in a separate shell"</span>
	<span class="p">@$(</span>two<span class="p">)</span>
</code></pre></div></div>

<h2 id="target-specific-variables">Target-specific variables</h2>
<!--  (Section 6.10) -->
<p>Variables can be set for specific targets</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span> <span class="nf">one = cool</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo </span>one is defined: <span class="p">$(</span>one<span class="p">)</span>

<span class="nl">other</span><span class="o">:</span>
	<span class="nb">echo </span>one is nothing: <span class="p">$(</span>one<span class="p">)</span>
</code></pre></div></div>

<h2 id="pattern-specific-variables">Pattern-specific variables</h2>
<!--  (Section 6.11) -->
<p>You can set variables for specific target <em>patterns</em></p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">%.c</span><span class="o">:</span> <span class="nf">one = cool</span>

<span class="nl">blah.c</span><span class="o">:</span>
	<span class="nb">echo </span>one is defined: <span class="p">$(</span>one<span class="p">)</span>

<span class="nl">other</span><span class="o">:</span>
	<span class="nb">echo </span>one is nothing: <span class="p">$(</span>one<span class="p">)</span>
</code></pre></div></div>

<h1 id="conditional-part-of-makefiles">Conditional part of Makefiles</h1>
<h2 id="conditional-ifelse">Conditional if/else</h2>
<!--  (Section 7.1) -->
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">foo</span> <span class="o">=</span> ok

<span class="nl">all</span><span class="o">:</span>
<span class="k">ifeq</span> <span class="nv">($(foo), ok)</span>
	<span class="nb">echo</span> <span class="s2">"foo equals ok"</span>
<span class="k">else</span>
	<span class="nb">echo</span> <span class="s2">"nope"</span>
<span class="k">endif</span>
</code></pre></div></div>

<h2 id="check-if-a-variable-is-empty">Check if a variable is empty</h2>
<!--  (Section 7.2) -->
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">nullstring</span> <span class="o">=</span>
<span class="nv">foo</span> <span class="o">=</span> <span class="p">$(</span>nullstring<span class="p">)</span> <span class="c"># end of line; there is a space here</span>

<span class="nl">all</span><span class="o">:</span>
<span class="k">ifeq</span> <span class="nv">($(strip $(foo)),)</span>
	<span class="nb">echo</span> <span class="s2">"foo is empty after being stripped"</span>
<span class="k">endif</span>
<span class="k">ifeq</span> <span class="nv">($(nullstring),)</span>
	<span class="nb">echo</span> <span class="s2">"nullstring doesn't even have spaces"</span>
<span class="k">endif</span>
</code></pre></div></div>

<h2 id="check-if-a-variable-is-defined">Check if a variable is defined</h2>
<!--  (Section 7.2) -->
<p>ifdef does not expand variable references; it just sees if something is defined at all</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">bar</span> <span class="o">=</span>
<span class="nv">foo</span> <span class="o">=</span> <span class="p">$(</span>bar<span class="p">)</span>

<span class="nl">all</span><span class="o">:</span>
<span class="k">ifdef</span> <span class="nv">foo</span>
	<span class="nb">echo</span> <span class="s2">"foo is defined"</span>
<span class="k">endif</span>
<span class="k">ifndef</span> <span class="nv">bar</span>
	<span class="nb">echo</span> <span class="s2">"but bar is not"</span>
<span class="k">endif</span>

</code></pre></div></div>

<h2 id="makeflags">$(MAKEFLAGS)</h2>
<!-- `(Section 7.3) -->
<p>This example shows you how to test make flags with <code class="language-plaintext highlighter-rouge">findstring</code> and <code class="language-plaintext highlighter-rouge">MAKEFLAGS</code>. Run this example with <code class="language-plaintext highlighter-rouge">make -i</code> to see it print out the echo statement.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span>
<span class="c"># Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
</span><span class="k">ifneq</span> <span class="nv">(,$(findstring i, $(MAKEFLAGS)))</span>
	<span class="err">echo</span> <span class="s2">"i was passed to MAKEFLAGS"</span>
<span class="k">endif</span>
</code></pre></div></div>

<h1 id="functions">Functions</h1>
<h2 id="first-functions">First Functions</h2>
<!--  (Section 8.1) -->
<p><em>Functions</em> are mainly just for text processing. Call functions with <code class="language-plaintext highlighter-rouge">$(fn, arguments)</code> or <code class="language-plaintext highlighter-rouge">${fn, arguments}</code>. Make has a decent amount of <a href="https://www.gnu.org/software/make/manual/html_node/Functions.html">builtin functions</a>.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">bar</span> <span class="o">:=</span> <span class="p">${</span>subst not,<span class="s2">"totally"</span>, <span class="s2">"I am not superman"</span><span class="p">}</span>
<span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>bar<span class="p">)</span>

</code></pre></div></div>

<p>If you want to replace spaces or commas, use variables</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">comma</span> <span class="o">:=</span> ,
<span class="nv">empty</span><span class="o">:=</span>
<span class="nv">space</span> <span class="o">:=</span> <span class="p">$(</span>empty<span class="p">)</span> <span class="p">$(</span>empty<span class="p">)</span>
<span class="nv">foo</span> <span class="o">:=</span> a b c
<span class="nv">bar</span> <span class="o">:=</span> <span class="p">$(</span>subst <span class="p">$(</span>space<span class="p">)</span>,<span class="p">$(</span>comma<span class="p">)</span>,<span class="p">$(</span>foo<span class="p">))</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>bar<span class="p">)</span>
</code></pre></div></div>

<p>Do NOT include spaces in the arguments after the first. That will be seen as part of the string.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">comma</span> <span class="o">:=</span> ,
<span class="nv">empty</span><span class="o">:=</span>
<span class="nv">space</span> <span class="o">:=</span> <span class="p">$(</span>empty<span class="p">)</span> <span class="p">$(</span>empty<span class="p">)</span>
<span class="nv">foo</span> <span class="o">:=</span> a b c
<span class="nv">bar</span> <span class="o">:=</span> <span class="p">$(</span>subst <span class="p">$(</span>space<span class="p">)</span>, <span class="p">$(</span>comma<span class="p">)</span> , <span class="p">$(</span>foo<span class="p">))</span> <span class="c"># Watch out!</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="c"># Output is ", a , b , c". Notice the spaces introduced</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>bar<span class="p">)</span>

</code></pre></div></div>

<!-- # 8.2, 8.3, 8.9 TODO do something about the fns
# TODO 8.7 origin fn? Better in documentation?
-->

<h2 id="string-substitution">String Substitution</h2>
<p><code class="language-plaintext highlighter-rouge">$(patsubst pattern,replacement,text)</code> does the following:</p>

<p>“Finds whitespace-separated words in text that match pattern and replaces them with replacement. Here pattern may contain a ‘%’ which acts as a wildcard, matching any number of any characters within a word. If replacement also contains a ‘%’, the ‘%’ is replaced by the text that matched the ‘%’ in pattern. Only the first ‘%’ in the pattern and replacement is treated this way; any subsequent ‘%’ is unchanged.” (<a href="https://www.gnu.org/software/make/manual/html_node/Text-Functions.html#Text-Functions">GNU docs</a>)</p>

<p>The substitution reference <code class="language-plaintext highlighter-rouge">$(text:pattern=replacement)</code> is a shorthand for this.</p>

<p>There’s another shorthand that replaces only suffixes: <code class="language-plaintext highlighter-rouge">$(text:suffix=replacement)</code>. No <code class="language-plaintext highlighter-rouge">%</code> wildcard is used here.</p>

<p>Note: don’t add extra spaces for this shorthand. It will be seen as a search or replacement term.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">foo</span> <span class="o">:=</span> a.o b.o l.a c.o
<span class="nv">one</span> <span class="o">:=</span> <span class="p">$(</span>patsubst %.o,%.c,<span class="p">$(</span>foo<span class="p">))</span>
<span class="c"># This is a shorthand for the above
</span><span class="nv">two</span> <span class="o">:=</span> <span class="p">$(</span>foo:%.o<span class="o">=</span>%.c<span class="p">)</span>
<span class="c"># This is the suffix-only shorthand, and is also equivalent to the above.
</span><span class="nv">three</span> <span class="o">:=</span> <span class="p">$(</span>foo:.o<span class="o">=</span>.c<span class="p">)</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="nb">echo</span> <span class="p">$(</span>one<span class="p">)</span>
	<span class="nb">echo</span> <span class="p">$(</span>two<span class="p">)</span>
	<span class="nb">echo</span> <span class="p">$(</span>three<span class="p">)</span>
</code></pre></div></div>

<h2 id="the-foreach-function">The foreach function</h2>
<!--  (Section 8.4) -->
<p>The foreach function looks like this: <code class="language-plaintext highlighter-rouge">$(foreach var,list,text)</code>. It converts one list of words (separated by spaces) to another. <code class="language-plaintext highlighter-rouge">var</code> is set to each word in list, and <code class="language-plaintext highlighter-rouge">text</code> is expanded for each word.
This appends an exclamation after each word:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">foo</span> <span class="o">:=</span> <span class="nb">who </span>are you
<span class="c"># For each "word" in foo, output that same word with an exclamation after
</span><span class="nv">bar</span> <span class="o">:=</span> <span class="p">$(</span>foreach wrd,<span class="p">$(</span>foo<span class="p">)</span>,<span class="p">$(</span>wrd<span class="p">)</span><span class="o">!</span><span class="p">)</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="c"># Output is "who! are! you!"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>bar<span class="p">)</span>
</code></pre></div></div>

<h2 id="the-if-function">The if function</h2>
<!--  (Section 8.5) -->
<p><code class="language-plaintext highlighter-rouge">if</code> checks if the first argument is nonempty. If so, runs the second argument, otherwise runs the third.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">foo</span> <span class="o">:=</span> <span class="p">$(</span><span class="k">if </span>this-is-not-empty,then!,else!<span class="p">)</span>
<span class="nv">empty</span> <span class="o">:=</span>
<span class="nv">bar</span> <span class="o">:=</span> <span class="p">$(</span><span class="k">if</span> <span class="p">$(</span>empty<span class="p">)</span>,then!,else!<span class="p">)</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>foo<span class="p">)</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>bar<span class="p">)</span>
</code></pre></div></div>

<h2 id="the-call-function">The call function</h2>
<!--  (Section 8.6) -->
<p>Make supports creating basic functions. You “define” the function just by creating a variable, but use the parameters <code class="language-plaintext highlighter-rouge">$(0)</code>, <code class="language-plaintext highlighter-rouge">$(1)</code>, etc. You then call the function with the special <a href="https://www.gnu.org/software/make/manual/html_node/Call-Function.html#Call-Function"><code class="language-plaintext highlighter-rouge">call</code></a> builtin function. The syntax is <code class="language-plaintext highlighter-rouge">$(call variable,param,param)</code>. <code class="language-plaintext highlighter-rouge">$(0)</code> is the variable, while <code class="language-plaintext highlighter-rouge">$(1)</code>, <code class="language-plaintext highlighter-rouge">$(2)</code>, etc. are the params.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">sweet_new_fn</span> <span class="o">=</span> Variable Name: <span class="p">$(</span>0<span class="p">)</span> First: <span class="p">$(</span>1<span class="p">)</span> Second: <span class="p">$(</span>2<span class="p">)</span> Empty Variable: <span class="p">$(</span>3<span class="p">)</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="c"># Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>call sweet_new_fn, go, tigers<span class="p">)</span>
</code></pre></div></div>

<h2 id="the-shell-function">The shell function</h2>
<!--  (Section 8.8) -->
<p>shell - This calls the shell, but it replaces newlines with spaces!</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>shell <span class="nb">ls</span> <span class="nt">-la</span><span class="p">)</span> <span class="c"># Very ugly because the newlines are gone!</span>
</code></pre></div></div>

<h2 id="the-filter-function">The filter function</h2>

<p>The <code class="language-plaintext highlighter-rouge">filter</code> function is used to select certain elements from a list that match a specific pattern. For example, this will select all elements in <code class="language-plaintext highlighter-rouge">obj_files</code> that end with <code class="language-plaintext highlighter-rouge">.o</code>.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">obj_files</span> <span class="o">=</span> foo.result bar.o lose.o
<span class="nv">filtered_files</span> <span class="o">=</span> <span class="p">$(</span>filter %.o,<span class="p">$(</span>obj_files<span class="p">))</span>

<span class="nl">all</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="p">$(</span>filtered_files<span class="p">)</span>
</code></pre></div></div>

<p>Filter can also be used in more complex ways:</p>

<ol>
  <li>
    <p><strong>Filtering multiple patterns</strong>: You can filter multiple patterns at once. For example, <code class="language-plaintext highlighter-rouge">$(filter %.c %.h, $(files))</code> will select all <code class="language-plaintext highlighter-rouge">.c</code> and <code class="language-plaintext highlighter-rouge">.h</code> files from the files list.</p>
  </li>
  <li>
    <p><strong>Negation</strong>: If you want to select all elements that do not match a pattern, you can use <code class="language-plaintext highlighter-rouge">filter-out</code>. For example, <code class="language-plaintext highlighter-rouge">$(filter-out %.h, $(files))</code> will select all files that are not <code class="language-plaintext highlighter-rouge">.h</code> files.</p>
  </li>
  <li>
    <p><strong>Nested filter</strong>: You can nest filter functions to apply multiple filters. For example, <code class="language-plaintext highlighter-rouge">$(filter %.o, $(filter-out test%, $(objects)))</code> will select all object files that end with <code class="language-plaintext highlighter-rouge">.o</code> but don’t start with <code class="language-plaintext highlighter-rouge">test</code>.</p>
  </li>
</ol>

<h1 id="other-features">Other Features</h1>
<h2 id="include-makefiles">Include Makefiles</h2>
<p>The include directive tells make to read one or more other makefiles. It’s a line in the makefile that looks like this:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">include</span><span class="sx"> filenames...</span>
</code></pre></div></div>

<p>This is particularly useful when you use compiler flags like <code class="language-plaintext highlighter-rouge">-M</code> that create Makefiles based on the source. For example, if some c files includes a header, that header will be added to a Makefile that’s written by gcc. I talk about this more in the <a href="#makefile-cookbook">Makefile Cookbook</a></p>

<h2 id="the-vpath-directive">The vpath Directive</h2>
<!--  (Section 4.3.2) -->
<p>Use vpath to specify where some set of prerequisites exist. The format is <code class="language-plaintext highlighter-rouge">vpath &lt;pattern&gt; &lt;directories, space/colon separated&gt;</code>
<code class="language-plaintext highlighter-rouge">&lt;pattern&gt;</code> can have a <code class="language-plaintext highlighter-rouge">%</code>, which matches any zero or more characters.
You can also do this globallyish with the variable VPATH</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">vpath</span> <span class="err">%.h</span> <span class="err">../headers</span> <span class="err">../other-directory</span>

<span class="c"># Note: vpath allows blah.h to be found even though blah.h is never in the current directory
</span><span class="nl">some_binary</span><span class="o">:</span> <span class="nf">../headers blah.h</span>
	<span class="nb">touch </span>some_binary

<span class="nl">../headers</span><span class="o">:</span>
	<span class="nb">mkdir</span> ../headers

<span class="c"># We call the target blah.h instead of ../headers/blah.h, because that's the prereq that some_binary is looking for
# Typically, blah.h would already exist and you wouldn't need this.
</span><span class="nl">blah.h</span><span class="o">:</span>
	<span class="nb">touch</span> ../headers/blah.h

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-rf</span> ../headers
	<span class="nb">rm</span> <span class="nt">-f</span> some_binary

</code></pre></div></div>

<h2 id="multiline">Multiline</h2>
<p>The backslash (“\”) character gives us the ability to use multiple lines when the commands are too long</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">some_file</span><span class="o">:</span>
	<span class="nb">echo </span>This line is too long, so <span class="se">\</span>
		it is broken up into multiple lines
</code></pre></div></div>

<h2 id="phony">.phony</h2>
<p>Adding <code class="language-plaintext highlighter-rouge">.PHONY</code> to a target will prevent Make from confusing the phony target with a file name. In this example, if the file <code class="language-plaintext highlighter-rouge">clean</code> is created, make clean will still be run. Technically, I should have used it in every example with <code class="language-plaintext highlighter-rouge">all</code> or <code class="language-plaintext highlighter-rouge">clean</code>, but I wanted to keep the examples clean. Additionally, “phony” targets typically have names that are rarely file names, and in practice many people skip this.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">some_file</span><span class="o">:</span>
	<span class="nb">touch </span>some_file
	<span class="nb">touch </span>clean

<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">clean</span>
<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-f</span> some_file
	<span class="nb">rm</span> <span class="nt">-f</span> clean
</code></pre></div></div>

<h2 id="delete_on_error">.delete_on_error</h2>
<!-- (Section 5.4) -->

<p>The make tool will stop running a rule (and will propagate back to prerequisites) if a command returns a nonzero exit status.
<code class="language-plaintext highlighter-rouge">DELETE_ON_ERROR</code> will delete the target of a rule if the rule fails in this manner. This will happen for all targets, not just the one it is before like PHONY. It’s a good idea to always use this, even though make does not for historical reasons.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">.DELETE_ON_ERROR</span><span class="o">:</span>
<span class="nl">all</span><span class="o">:</span> <span class="nf">one two</span>

<span class="nl">one</span><span class="o">:</span>
	<span class="nb">touch </span>one
	<span class="nb">false</span>

<span class="nl">two</span><span class="o">:</span>
	<span class="nb">touch </span>two
	<span class="nb">false</span>
</code></pre></div></div>

<h1 id="makefile-cookbook">Makefile Cookbook</h1>
<p>Let’s go through a really juicy Make example that works well for medium sized projects.</p>

<p>The neat thing about this makefile is it automatically determines dependencies for you. All you have to do is put your C/C++ files in the <code class="language-plaintext highlighter-rouge">src/</code> folder.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
</span><span class="nv">TARGET_EXEC</span> <span class="o">:=</span> final_program

<span class="nv">BUILD_DIR</span> <span class="o">:=</span> ./build
<span class="nv">SRC_DIRS</span> <span class="o">:=</span> ./src

<span class="c"># Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. The shell will incorrectly expand these otherwise, but we want to send the * directly to the find command.
</span><span class="nv">SRCS</span> <span class="o">:=</span> <span class="p">$(</span>shell find <span class="p">$(</span>SRC_DIRS<span class="p">)</span> <span class="nt">-name</span> <span class="s1">'*.cpp'</span> <span class="nt">-or</span> <span class="nt">-name</span> <span class="s1">'*.c'</span> <span class="nt">-or</span> <span class="nt">-name</span> <span class="s1">'*.s'</span><span class="p">)</span>

<span class="c"># Prepends BUILD_DIR and appends .o to every src file
# As an example, ./your_dir/hello.cpp turns into ./build/./your_dir/hello.cpp.o
</span><span class="nv">OBJS</span> <span class="o">:=</span> <span class="p">$(</span>SRCS:%<span class="o">=</span><span class="p">$(</span>BUILD_DIR<span class="p">)</span>/%.o<span class="p">)</span>

<span class="c"># String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
</span><span class="nv">DEPS</span> <span class="o">:=</span> <span class="p">$(</span>OBJS:.o<span class="o">=</span>.d<span class="p">)</span>

<span class="c"># Every folder in ./src will need to be passed to GCC so that it can find header files
</span><span class="nv">INC_DIRS</span> <span class="o">:=</span> <span class="p">$(</span>shell find <span class="p">$(</span>SRC_DIRS<span class="p">)</span> <span class="nt">-type</span> d<span class="p">)</span>
<span class="c"># Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
</span><span class="nv">INC_FLAGS</span> <span class="o">:=</span> <span class="p">$(</span>addprefix <span class="nt">-I</span>,<span class="p">$(</span>INC_DIRS<span class="p">))</span>

<span class="c"># The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
</span><span class="nv">CPPFLAGS</span> <span class="o">:=</span> <span class="p">$(</span>INC_FLAGS<span class="p">)</span> <span class="nt">-MMD</span> <span class="nt">-MP</span>

<span class="c"># The final build step.
</span><span class="nl">$(BUILD_DIR)/$(TARGET_EXEC)</span><span class="o">:</span> <span class="nf">$(OBJS)</span>
	<span class="p">$(</span>CXX<span class="p">)</span> <span class="p">$(</span>OBJS<span class="p">)</span> <span class="nt">-o</span> <span class="nv">$@</span> <span class="p">$(</span>LDFLAGS<span class="p">)</span>

<span class="c"># Build step for C source
</span><span class="nl">$(BUILD_DIR)/%.c.o</span><span class="o">:</span> <span class="nf">%.c</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="p">$(</span><span class="nb">dir</span> <span class="nv">$@</span><span class="p">)</span>
	<span class="p">$(</span>CC<span class="p">)</span> <span class="p">$(</span>CPPFLAGS<span class="p">)</span> <span class="p">$(</span>CFLAGS<span class="p">)</span> <span class="nt">-c</span> <span class="nv">$&lt;</span> <span class="nt">-o</span> <span class="nv">$@</span>

<span class="c"># Build step for C++ source
</span><span class="nl">$(BUILD_DIR)/%.cpp.o</span><span class="o">:</span> <span class="nf">%.cpp</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="p">$(</span><span class="nb">dir</span> <span class="nv">$@</span><span class="p">)</span>
	<span class="p">$(</span>CXX<span class="p">)</span> <span class="p">$(</span>CPPFLAGS<span class="p">)</span> <span class="p">$(</span>CXXFLAGS<span class="p">)</span> <span class="nt">-c</span> <span class="nv">$&lt;</span> <span class="nt">-o</span> <span class="nv">$@</span>


<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">clean</span>
<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-r</span> <span class="p">$(</span>BUILD_DIR<span class="p">)</span>

<span class="c"># Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
</span><span class="k">-include</span><span class="sx"> $(DEPS)</span>
</code></pre></div></div>

<!--
TODO: This example fails initially because blah.d doesn't exist. I'm not sure how to fix this example, there are probably better ones out there..

# Generating Prerequisites Automatically (Section 4.12)
Example requires: blah.c
Generating prereqs automatically
This makes one small makefile per source file
Notes:
1) $$ is the current process id in bash. $$$$ is just $$, with escaping. We use it to make a temporary file, that doesn't interfere with others if there is some parallel builds going on.
2) cc -MM outputs a makefile line. This is the magic that generates prereqs automatically, by looking at the code itself
3) The purpose of the sed command is to translate (for example):
    main.o : main.c defs.h
    into:
    main.o main.d : main.c defs.h
4) Running `make clean` will rerun the rm -f ... rule because the include line wants to include an up to date version of the file. There is such a target that updates it, so it runs that rule before including the file.
```makefile
# Run make init first, then run make
# This outputs
all: blah.d

clean:
	rm -f blah.d blah.c blah.h blah.o blah

%.d: %.c
	rm -f $@; \
	 $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
	 sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	 rm -f $@.$$$$

init:
	echo "#include \"blah.h\"; int main() { return 0; }" > blah.c
	touch blah.h

sources = blah.c

include $(sources:.c=.d)
```
-->]]></content><author><name>Oleg Larkin</name></author><category term="other" /><category term="tutorials" /><category term="makefile" /><summary type="html"><![CDATA[I built this guide because I could never quite wrap my head around Makefiles. They seemed awash with hidden rules and esoteric symbols, and asking simple questions didn’t yield simple answers. To solve this, I sat down for several weekends and read everything I could about Makefiles. I’ve condensed the most critical knowledge into this guide. Each topic has a brief description and a self contained example that you can run yourself.]]></summary></entry><entry><title type="html">Трансляция видео посредством P2P-сетей</title><link href="http://localhost:4000/habr/2026/01/12/video-streaming-p2p.html" rel="alternate" type="text/html" title="Трансляция видео посредством P2P-сетей" /><published>2026-01-12T00:00:00+03:00</published><updated>2026-01-12T00:00:00+03:00</updated><id>http://localhost:4000/habr/2026/01/12/video-streaming-p2p</id><content type="html" xml:base="http://localhost:4000/habr/2026/01/12/video-streaming-p2p.html"><![CDATA[<p>Создавая сервис видеотрансляций, рано или поздно, при увеличении числа потребителей контента, возникает вопрос о масштабировании и доставке. Вы столкнетесь с проблемой не только вычислительных мощностей, но и пропускной способности вашей сети.</p>

<p>Большинство современных решений основаны на распределении статических видеочанков через HTTP-серверы (DASH, HLS), так как такие данные легко кэшируются, что позволяет масштабировать их распространение. При этом могут быть использованы уже существующие сети доставки контента (CDN).</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/9f4/bb0/a38/9f4bb0a3877c7043768614553327c328.png" alt="Как работает доставка видео на основе традиционных CDN" title="Как работает доставка видео на основе традиционных CDN" /></p>

<p>Как работает доставка видео на основе традиционных CDN</p>

<h2 id="узкие-горлышки-частных-сетей">Узкие горлышки частных сетей</h2>

<p>На схеме видно, как контент распространяется с учетом географических особенностей, разгружая инфраструктуру видеосервиса.</p>

<p>Однако, к сожалению, CDN не способны решить проблемы “последней мили”. Представим ситуацию, когда несколько пользователей из одной локальной сети смотрят трансляцию, каждый обращается к EDGE серверу, создавая нагрузку на пропускную способность сети. При достаточном числе таких зрителей они могут перегрузить канал, и никто больше не сможет просматривать контент. Обратите внимание, что при этом каждый из них получает один и тот же контент, но для каждого отдельно.</p>

<h2 id="локальные-кэш-сервера">Локальные кэш-сервера</h2>

<p>Для решения проблемы узкого горлышка необходимо установить внутри сети единого получателя трансляции, который будет выступать ретранслятором.</p>

<p>Можно разместить локальные кэш-сервера в наиболее ответственных сетях.</p>

<h2 id="p2p">P2P</h2>

<p>Но очевидно, что невозможно разместить кэш-сервер во всех частных сетях мира. Однако, что если только один из участников будет получать трансляцию извне сети и передавать её остальным внутри? Такой подход называется Peer-to-Peer сетью или eCDN.</p>

<h2 id="решения-на-рынке">Решения на рынке</h2>

<p>eCDN подход уже неплохо себя зарекомендовал и имеет несколько успешных реализаций различными компаниями. Вот некоторые из них:</p>

<p><a href="https://www.microsoft.com/en-us/microsoft-teams/ecdn">Microsoft eCDN (PEER5)</a></p>

<p><a href="https://kollective.com/how-does-peering-work/#e0od6z12bfe">Kollective</a></p>

<p><a href="https://teleport.media/">Teleport Media</a></p>

<p><a href="https://www.hivestreaming.com/">Hive Streaming</a></p>

<p><a href="http://www.streamroot.io/">Streamroot</a></p>

<p><a href="https://webtorrent.io/">WebTorrent</a></p>

<h2 id="как-устроены">Как устроены</h2>

<p>Практически все решения представляют из себя набор плагинов для основных библиотек видеоплееров (video.js, hls.js, dash.js, shaka и т.д.).</p>

<p>В основе решений используется технология webrtc. Эта технология предназначена для прямого P2P подключения двух пользователей между собой и поддерживается всеми современными браузерами.</p>

<h3 id="как-работает-webrtc">Как работает webrtc</h3>

<p>Чтобы подключить двух клиентов через интернет необходимо построить маршрут. Для этой задачи используется STUN-сервер, который помогает преодолеть NAT и установить маршрут от внешнего порта и IP-адреса до локального порта и IP-адреса. Такой маршрут называется набором ICE-кандидатов. Далее эти ICE-кандидаты должны быть переданы противоположному пиру для установки соединения, для этого, как правило, используется дополнительный сигнальный сервис.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/e8c/5fd/7a9/e8c5fd7a99a707a71e20860a44b50a84.png" alt="Процесс подключения по webrtc" title="Процесс подключения по webrtc" /></p>

<p>Процесс подключения по webrtc</p>

<h3 id="серверная-часть">Серверная часть</h3>

<p>Архитектура может различаться, и некоторые сервера бизнес-логики могут объединять функции нижеперечисленных сущностей.</p>

<h4 id="авторизатор">Авторизатор</h4>

<p>Авторизует пользователей в сети и предоставляет им первоначальные параметры.</p>

<h4 id="сворм-сервер">Сворм сервер</h4>

<p>Управляет сетью, владеет информацией о всех узлах сети и определяет её структуру, принимает решения о том, какой пир будет подключен к какому.</p>

<h4 id="stun">STUN</h4>

<p>Вспомогательный сервис для установления соединения по WebRTC, помогает пирам собрать свои ICE-кандидаты.</p>

<h4 id="сигналер">Сигналер</h4>

<p>Необходим для обмена сообщениями между пирами при установке соединения по WebRTC.</p>

<h4 id="сервис-сбора-статистики">Сервис сбора статистики</h4>

<p>Используется для анализа работы сети и тарификации.</p>

<h3 id="топология-сети---каждый-к-каждому">Топология сети - каждый к каждому</h3>

<p>За основу взяты принципы, лежащие в основе архитектуры протокола BitTorrent. Сервер Swarm пытается установить как можно больше соединений от каждого пира к каждому, учитывая ограничение на количество подключений к одному пиру (обычно несколько десятков), и отбрасывая неудачные попытки соединения в черный список.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/59c/0df/e3a/59c0dfe3a7c4fde5b868b11d01de323b.png" alt="Сеть представляет из себя граф" title="Сеть представляет из себя граф" /></p>

<p>Сеть представляет из себя граф</p>

<h3 id="алгоритм">Алгоритм</h3>

<h4 id="1-регистрация">1. Регистрация</h4>

<p>Первым делом новый пир регистрируется на сервере авторизации, предоставляя всю необходимую информацию и получая первоначальные параметры.</p>

<h4 id="2-получение-списка-пиров">2. Получение списка пиров</h4>

<p>После регистрации пир получает от сервера список пиров-кандидатов для подключения.</p>

<h4 id="3-подключение-ко-всем-доступным-пирам">3. Подключение ко всем доступным пирам</h4>

<p>Далее, пир пытается установить соединение с каждым кандидатом из списка с помощью STUN сервера и сигнализатора.</p>

<h4 id="4-распространение-чанков-в-сети">4. Распространение чанков в сети</h4>

<p>Обмен сообщениями и передача чанков между пирами осуществляются через webrtc data channel.</p>

<p>После загрузки чанка с CDN, аналогично BitTorrent, пир сообщает всем подключенным участникам о его наличии в своей памяти.</p>

<p>Получив уведомление о наличии чанка, пиры по мере необходимости запрашивают его. Если никто из сети не сообщил о наличии чанка, пир загружает его с CDN. Таким образом, HTTP запросы чанков плеером, по возможности, заменяются webrtc запросами к другим пирам сети.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/a90/49e/257/a9049e257c2aa35a6fb080b43cdd3557.png" alt="" /></p>

<h3 id="недостатки">Недостатки</h3>

<p>Данные решения, в первую очередь, направлены на снижение нагрузки и затрат на CDN.</p>

<p>Пиры в одной P2P сети не обязательно находятся в одной локальной сети, следовательно, проблема узкого горлышка не решена.</p>

<p>Подход с получением чанков из сети по запросу больше подходит для VOD, в то время как прямые трансляции не могут иметь задержку менее 6 секунд.</p>

<p>Как видно выше, для управления такой сетью требуется специальный высоконагруженный сервер, который управляет логикой всех подключений в сети.</p>

<h2 id="собственное-решение">Собственное решение</h2>

<p>Наше решение не предназначено для интеграции в сторонние сервисы и не ставит своей целью экономию или извлечение средств, а скорее направлено на повышение надежности. Это означает, что мы должны эффективно использовать вычислительные ресурсы и интегрироваться в существующую инфраструктуру.</p>

<p>Сбор информации о пирах и создание карты сети для P2P осуществляется сервисом сбора статистики, поскольку он уже обладает всей необходимой информацией.</p>

<p>Самое ресурсоемкое действие - это расчет структуры сети и определение поведения для каждого пира. Обычно этим занимается централизованный сервер. Представьте, сколько ресурсов требуется для онлайн-обработки сетей из нескольких тысяч, а иногда и десятков тысяч пиров. Благодаря ограничению наших P2P сетей рамками локальных сетей, мы смогли обеспечить каждого пира актуальной картиной своей сети. Это позволило отказаться от централизованного сервера и поручить пирам принятие решений о своем поведении и связанные с этим вычисления.</p>

<h3 id="топология-сети---дерево">Топология сети - дерево</h3>

<p>В нашем подходе используется структура дерева подключений, что усложняет реализацию, однако обеспечивает более высокий уровень контроля, снижает нагрузку и уменьшает паразитный трафик на узлах сети.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/dae/934/55b/dae93455b3756cbbd2b40542553db6b7.png" alt="" /></p>

<h3 id="алгоритм-1">Алгоритм</h3>

<h4 id="1-отправка-статистики">1. Отправка статистики</h4>

<p>Каждый зритель трансляции, независимо от того, является ли он участником P2P, отправляет о себе статистику, достаточную для участия в P2P.</p>

<h4 id="2-определение-своей-сети-и-необходимости-участия-в-p2p">2. Определение своей сети и необходимости участия в P2P</h4>

<h3 id="сбор-ice-candidates">Сбор ICE candidates</h3>

<p>Для определения того, в какой локальной сети находится потенциальный пир, необходимо воспользоваться STUN-сервером и собрать список ICE-кандидатов. </p>

<h3 id="определение-типа-кэша">Определение типа кэша</h3>

<p>Иногда внутри сети располагается локальный кеш. В таких случаях нет необходимости использовать P2P-сеть. Для определения, за каким кешем сейчас находится пир, скачивается специальный файл, из заголовков которого понятно, отдал ли его локальный кэш или нет.</p>

<p><em>Данные полученные при скачивании этого служебного файла могут быть полезны и для других функций, например для ABR.</em></p>

<h4 id="3-подписка-на-список-пиров-внутри-своей-сети">3. Подписка на список пиров внутри своей сети</h4>

<p>Поскольку принятие решений о собственном поведении возложено на сами пиры, им необходимо иметь актуальное представление о сети. Для этого пир подписывается на все изменения внутри сети.</p>

<p><em>Эти данные являются общими для всех участников сети и могут быть закешированы.</em></p>

<h4 id="4-определение-необходимости-поиска-источника-в-p2p">4. Определение необходимости поиска источника в P2P</h4>

<p>Критериями, побуждающими пир искать новый источник внутри сети, являются наличие других участников (потенциальных источников) и низкая скорость получения через CDN (текущего источника).</p>

<h3 id="наличие-в-сети-других-пиров">Наличие в сети других пиров</h3>

<p>Если в сети отсутствуют другие участники, нет смысла запускать алгоритм поиска лучшего кандидата для подключения.</p>

<h3 id="замер-скорости">Замер скорости</h3>

<p>Нет смысла искать новый источник, если текущий удовлетворяет потребностям. В качестве такого критерия используется не скорость напрямую, а зависимо от типа текущего источника разные показатели:</p>

<p>• При получении с CDN оценивается отклонение от целевой задержки.</p>

<p>• При получении из P2P оценивается время на прохождение пингов (<em>меньше 10 мс - хорошо, больше 50 мс - плохо</em>).</p>

<h4 id="5-выбор-подходящего-пира-для-подключения">5. Выбор подходящего пира для подключения</h4>

<p>При отборе кандидатов для подключения среди пиров сети используется несколько критериев в качестве фильтра:</p>

<h3 id="это-другой-пир">Это другой пир</h3>

<p>Необходимо исключить себя из кандидатов на подключение.</p>

<h3 id="есть-свободные-слоты">Есть свободные слоты</h3>

<p>Убеждаемся, что у кандидата есть свободные слоты для новых подключений.</p>

<p><em>Мы используем ограничение дочерних пиров до трех.</em></p>

<h3 id="не-отмечен-ранее-как-плохой">Не отмечен ранее как плохой</h3>

<p>Проверяем, чтобы кандидат не был ранее отмечен как неподходящий.</p>

<h3 id="быстрее">Быстрее</h3>

<p>Убеждаемся, что скорость подключения не ниже, чем у текущего узла.</p>

<h3 id="не-глубже-в-структуре-дерева-подключений">Не глубже в структуре дерева подключений</h3>

<p>Каждый пир должен стремится расположится в иерархии подключений как можно ближе к CDN и находиться на минимальной доступной глубине.</p>

<h3 id="нет-в-цепочке-родительских-и-дочерних-пиров">Нет в цепочке родительских и дочерних пиров</h3>

<p>Исключаем кандидата, если он находится в цепочке родительских или дочерних пиров, чтобы избежать образования замкнутых колец.</p>

<h3 id="не-в-кольце">Не в кольце</h3>

<p>В некоторых случаях, из-за асинхронной природы структуры дерева сети, на непродолжительное время, все же, могут образовываться замкнутые кольца, на этот счет производится дополнительная проверка.</p>

<p><em>Проверяется количество пиров в цепочке родителей, если глубина превышает лимит, делается вывод о закольцованности.</em></p>

<h4 id="6-подключение">6. Подключение</h4>

<p>Как только пир получает отобранный список кандидатов для подключения, производятся попытки установить эти соединения.</p>

<h3 id="типы-подключения">Типы подключения</h3>

<p>При P2P подключении есть две стороны: ретранслирующая стрим и получающая сторона.</p>

<h3 id="receiver">Receiver</h3>

<p>Пир, получающий стрим, называется Receiver. Receiver может иметь до трех активных подключений разных типов.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/e50/ce4/7a9/e50ce47a9eaca9a55ad6edba2bece484.png" alt="" /></p>

<p>• <strong>Основное подключение</strong> - активное соединение, через которое пир получает стрим.</p>

<p>• <strong>Резервное подключение</strong> - в случае разрыва основного соединения пир начинает получать стрим через резервное подключение.</p>

<p>• <strong>Тестовое подключение</strong> - при подключении к новому кандидату измеряются его характеристики, результаты сравниваются с основным и резервным соединениями; в случае положительного результата он заменяет собой текущее соединение.</p>

<h3 id="relay">Relay</h3>

<p>Пир, который ретранслирует стрим, называется Relay. Получив запрос на подключение от ресивера, релей проверяет наличие свободных слотов. Каждый релей может иметь не более трех активных подключений от ресиверов.</p>

<h3 id="сигналинг-для-подключения">Сигналинг для подключения</h3>

<p>Значительную нагрузку на сервера БЛ создает взаимодействие между пирами, поэтому необходимая сигнализация для подключения сведена к минимуму.</p>

<h3 id="минимальный-набор-данных-для-подключения">Минимальный набор данных для подключения</h3>

<p>При установке WebRTC соединения пиры обмениваются записями SDP (Session Description Protocol), содержащими описание сессии, включающее следующие данные:</p>

<ul>
  <li>
    <p>Описание сессии</p>
  </li>
  <li>
    <p>Открытый ключ шифрования</p>
  </li>
  <li>
    <p>ICE кандидаты</p>
  </li>
  <li>
    <p>Описание медиа секций</p>
  </li>
</ul>

<p>Поскольку мы контролируем обоих участников соединения, можно не передавать большую часть этих данных, а генерировать SDP по шаблону. К уникальным данным для соединения можно отнести только два пункта: ключ шифрования и набор ICE кандидатов.</p>

<h3 id="предварительная-генерация-сертификата">Предварительная генерация сертификата</h3>

<p>Обычно браузер для каждого соединения WebRTC генерирует новый сертификат и размещает его открытый ключ в SDP. Однако можно заранее сгенерировать сертификат и использовать его во всех последующих подключениях. Таким образом, открытый ключ становится общедоступным параметром, который нет необходимости передавать через сигналинг.</p>

<h3 id="нужны-только-ice-кандидаты">Нужны только ICE кандидаты</h3>

<p>К сожалению, нельзя использовать предварительно сгенерированные ICE кандидаты, так как порты для подключения уникальны для каждого подключения.</p>

<h3 id="сигналинг-через-datachannel">Сигналинг через dataChannel</h3>

<p>Таким образом, собранный из шаблона исходный SDP содержит только одну медиа-секцию для передачи данных (dataChannel). Все последующее взаимодействие между пирами, включая сигнализацию для пересогласования SDP и добавления новых медиа-секций (аудио, видео), производится через этот канал.</p>

<h3 id="способы-передачи-данных">Способы передачи данных</h3>

<p>WebRTC предоставляет возможность передавать потоковое медиа разных типов - аудио, видео, данные. Это дает нам некоторую вариативность в способах передачи потока.</p>

<h3 id="передача-стрима-через-datachannel">Передача стрима через dataChannel</h3>

<p>Такой подход основан на подмене HTTP запросов плеера и их перенаправлении в P2P сеть. Содержимое чанков в виде ArrayBuffer передается через dataChannel и используется в качестве ответа на HTTP запросы плеера. Это позволяет плавно переключаться между источниками, получая чанки от любого из пиров или от CDN, в случае необходимости, незаметно для зрителя. Однако, у этого способа есть особенности и недостатки. Необходима проверка целостности и подмены чанка, необходимо иметь контрольную сумму чанка и сверять ее при получении из P2P. Есть некоторые осложнения, связанные с ABR: пиры могут просматривать трансляцию в разных качествах, технически это разные видео с разными файлами чанков. Эту проблему можно решить различными способами, например:</p>

<ol>
  <li>
    <p>Использование разных сетей для разных вариантов качества может привести к необходимости смены сети при изменении качества, что приведет к дополнительным расходам. Кроме того, каждая дополнительная сеть увеличивает нагрузку на канал.</p>
  </li>
  <li>
    <p>Можно использовать чанки пира источника в любом доступном качестве, подменять чанки в своем качестве. Но это может вызвать недоумение у пользователя, который ожидает определенного качества, но получает другое.</p>
  </li>
  <li>
    <p>Можно ограничить выбор качества для пользователя во время получения данных из P2P сети, используя строгое качество пира источника.</p>
  </li>
</ol>

<p>Еще одной проблемой может быть игнорирование перегрузки локальной сети. Между двумя пирами качество видео не адаптируется для компенсации падения скорости внутри сети.</p>

<h3 id="передача-стрима-через-аудио-и-видео-медиа-секции">Передача стрима через аудио и видео медиа секции</h3>

<p>При данном подходе необходимо пересогласовывать SDP для каждого нового медиастрима с новыми видео и аудио медиасекциями. Однако, остальную работу по управлению потоками данных WebRTC берет на себя, включая адаптацию качества для устранения проседания скорости в сети. В связи с этим пользователю временно приходится лишить возможности выбора качества вручную при получении данных из P2P сети. К недостаткам такого подхода можно отнести переключение между источниками через небольшой фриз.</p>

<h3 id="симулятор">Симулятор</h3>

<p>Для отладки алгоритма построения топологии таких сложных сетей нельзя обойтись без симуляции. Мы создали отдельную программу, которая эмулирует поведение заданного количества пиров в сети с учетом заданных параметров. Сеть может состоять от одного до сотен тысяч пиров с меняющимися параметрами скорости, обрывов, выходов и входов и т.д.</p>

<h3 id="мониторинг-и-анализ">Мониторинг и анализ</h3>

<h4 id="сбор-статистики">Сбор статистики</h4>

<p>Для функционирования сети, при сборе статистики с пользователей, мы опираемся на следующие метрики:</p>

<ul>
  <li>
    <p>Информация о локальной сети</p>
  </li>
  <li>
    <p>Наличие в сети локального кеша</p>
  </li>
  <li>
    <p>Глубина в дереве подключений</p>
  </li>
  <li>
    <p>К каким пирам подключен</p>
  </li>
  <li>
    <p>Откуда получает данные (P2P или CDN)</p>
  </li>
  <li>
    <p>Скорость получения</p>
  </li>
  <li>
    <p>Скорость отдачи данных другим пирам</p>
  </li>
</ul>

<h4 id="визуализация-сети">Визуализация сети</h4>

<p>Для отладки и контроля не обойтись без инструмента, который бы визуализировал сеть в онлайн режиме.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/294/ebd/711/294ebd71128ca329b0f463cf198da465.png" alt="" /></p>

<h2 id="выводы">Выводы</h2>

<p>Распространение видео через пиринговые сети ECDN представляет собой эффективный и экономичный подход к инфраструктуре, позволяющий значительно снизить затраты на передачу данных. Этот метод особенно полезен при проведении крупномасштабных мероприятий, таких как чемпионат мира по футболу, когда необходимо обеспечить быструю и надежную трансляцию контента большому количеству зрителей.</p>

<p>В нашем случае, применение данной технологии решает конкретную и узконаправленную задачу, оптимизируя процесс передачи видеосигнала и обеспечивая высокое качество стриминга внутри локальных сетей.</p>

<h4 id="потенциал">Потенциал</h4>

<p>Прогресс в области сетевых технологий приводит к постоянному обновлению и увеличению пропускной способности. Однако, этот процесс происходит неравномерно, что вынуждает ориентироваться на большинство существующих сетей. В то же время, требования к качеству и размерам видеоконтента продолжают расти.</p>

<p>Появление и внедрение новых стандартов связи, таких как 5G и 6G, а также спутниковый интернет и рост производительности устройств, могут вывести актуальность P2P-сетей на новый уровень.</p>]]></content><author><name>Oleg Larkin</name></author><category term="habr" /><category term="habr" /><category term="p2p" /><category term="streaming" /><summary type="html"><![CDATA[Создавая сервис видеотрансляций, рано или поздно, при увеличении числа потребителей контента, возникает вопрос о масштабировании и доставке. Вы столкнетесь с проблемой не только вычислительных мощностей, но и пропускной способности вашей сети.]]></summary></entry><entry><title type="html">Голый Линукс — запуск ядра-одиночки</title><link href="http://localhost:4000/habr/2026/01/12/run-pure-linux-kernel.html" rel="alternate" type="text/html" title="Голый Линукс — запуск ядра-одиночки" /><published>2026-01-12T00:00:00+03:00</published><updated>2026-01-12T00:00:00+03:00</updated><id>http://localhost:4000/habr/2026/01/12/run-pure-linux-kernel</id><content type="html" xml:base="http://localhost:4000/habr/2026/01/12/run-pure-linux-kernel.html"><![CDATA[<p>Итак, Linux - не операционная система, а только ядро для неё. Всё остальное приходит от проекта GNU (и других). И вот интересно - на что годится ядро само по себе?</p>

<p>Эта статья - очень “начального” уровня. Устроим маленький эксперимент - создадим чистую виртуальную машину и попробуем запустить ядро Linux “без всего”. Или почти “без”, т.к. нам понадобится загрузчик ОС - и какая-нибудь “пользовательская программа” (её мы сотворим сами). Конечно, продвинутые пользователи Linux такой “эксперимент” могут провести просто отредактировав параметры запуска при включении - но наш рассказ всё же для тех кто почти (или совсем) не в теме :)</p>

<p>Бонусом чуть-чуть коснёмся системных вызовов и пару слов скажем о других ядрах.</p>

<h2 id="что-такое-ядро---и-как-им-воспользоваться">Что такое ядро - и как им воспользоваться</h2>

<p>На данном этапе нам достаточно представлять что ядро - некая программулина в виде здорового файла. Говоря по-программистски - фреймворк для запуска наших программ - и в то же время большая библиотека системных вызовов и коллекция драйверов.</p>

<p>Иными словами ядро воплощает работу со всеми основными сущностями ОС (в частности, процессами - в которых будут запускаться другие программы) - а также содержит драйвера для работы со всевозможным оборудованием компьютера. Ну, не все возможные на свете драйвера - но для наиболее актуальных и популярных систем. Для дисков, экрана, сетевых интерфейсов.</p>

<p>Поэтому наша цель - запустить ядро и передать управление небольшой демонстрационно программке которая сможет успешно “дёргать” эти системные вызовы. Попутно мы рассмотрим некоторые дополнительные вещи - хотя не актуальные для нашего эксперимента - но о которых полезно иметь представление.</p>

<h2 id="создайте-виртуальную-машину">Создайте виртуальную машину</h2>

<p>Вообще можно и на физической машине потренироватся, но мы рекомендуем начать с виртуалки. Скачайте VirtualBox (или другой эмулятор, если у вас есть какой-то любимый), создайте новую виртуалку (можно задать ей гигабайт оперативки, а можно и меньше - и диск автоматического размера - нам потребуется совсем немного).</p>

<p><em>Здесь и далее мы хотя говорим подробно о шагах которые следует выполнить, но всё же избегаем излишне детальных указаний “нажмите такую-то кнопку”. Пусть подобные мелочи останутся именно в качестве “упражнения” :) В частности интерфейс VirtualBox довольно интуитивный, да и нагуглить вопросы по ней легко.</em></p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/a51/b33/fd0/a51b33fd0b5b3ebd4ede3f6b421d9f7e.png" alt="пустая виртуалка - кнопка настроек (шестеренка) и запуска (стрелка) сверху" title="пустая виртуалка - кнопка настроек (шестеренка) и запуска (стрелка) сверху" /></p>

<p>пустая виртуалка - кнопка настроек (шестеренка) и запуска (стрелка) сверху</p>

<p>Итак, машина создана - если вы попытаетесь её запустить, появится чёрный экранчик с сообщением что не найдено устройство с которого можно загрузиться. Оно и понятно - ведь диск пока девственно чист. Нужно записать на него загрузчик. Очевидно для любых операций нужно временно запуститься с какого-нибудь LiveCD. Опять же можете выбрать по своему усмотрению, но я рекомендую (для данной цели) скачать относительно небольшой образ SystemRescueCD. Скачайте образ и подключите в настройках созданной виртуалки в качестве компакт-диска. Перезапустите машину - после загрузки должно появиться загрузочное меню системы на LiveCD:</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/0b7/88f/3ba/0b788f3bafdee87653bcc3dba1d014e0.png" alt="вообще эта штука может и в хозяйстве пригодиться" title="вообще эта штука может и в хозяйстве пригодиться" /></p>

<p>вообще эта штука может и в хозяйстве пригодиться</p>

<p>Сейчас мы запустим дефолтный пункт меню (просто нажмите Enter) - но на будущее обратите внимание на пункт “Boot existing OS” - он отменяет загрузку с LiveCD и грузит то что у вас установлено на основном диске - мы будем этим пользоваться чтобы удобнее проверять что получилось.</p>

<h2 id="подготовка-жёсткого-диска">Подготовка жёсткого диска</h2>

<p>Итак нажмите “Boot SystemRescueCD using default options”. Начнётся какая-то активность которая рано или поздно закончится надписью “automatic login” и ниже приглашением командной строки в духе <code class="language-plaintext highlighter-rouge">[root@systemresccd ~]#</code> - в принципе можно запустить графическую оболочку (startx) но нам это сейчас не нужно.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/89e/fec/46c/89efec46c872ce95b509f33e57c6399e.png" alt="SystemRescueCD грузится в консольный режим но предлагает запустить оконный интерфейс" title="SystemRescueCD грузится в консольный режим но предлагает запустить оконный интерфейс" /></p>

<p>SystemRescueCD грузится в консольный режим но предлагает запустить оконный интерфейс</p>

<p>Наш жёсткий диск виден среди девайсов, попробуйте ввести <code class="language-plaintext highlighter-rouge">ls /dev/sd*</code> и вы должны обнаружить вероятно диск <code class="language-plaintext highlighter-rouge">/dev/sda</code> - он ещё не разбит на разделы (вроде <code class="language-plaintext highlighter-rouge">/dev/sda1</code>) - и этим мы сейчас займёмся.</p>

<p>Запустите утилиту <code class="language-plaintext highlighter-rouge">fdisk</code> указав ей в качестве параметра обнаруженный диск, то есть <code class="language-plaintext highlighter-rouge">fdisk /dev/sda</code> - у неё простой интерфейс, команды однобуквенные - и сразу напоминает что для списка команд можно нажать <code class="language-plaintext highlighter-rouge">m</code> (почему-то).</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/147/8c9/b0c/1478c9b0c01e4e3006493ad0e2458187.png" alt="список команд появляющийся по команде &quot;m&quot;" title="список команд появляющийся по команде &quot;m&quot;" /></p>

<p>список команд появляющийся по команде “m”</p>

<p>Из всего этого многообразия нам нужно немного:</p>

<ul>
  <li>
    <p>создать новую таблицу разделов (сделайте DOS partition table) нажав “o”</p>
  </li>
  <li>
    <p>создать новый раздел (первичный) нажмите “n” и выделите под него весь диск</p>
  </li>
  <li>
    <p>сделайте этот раздел загружаемым (toggle bootable flag) нажав “a”</p>
  </li>
  <li>
    <p>можете проверить получившуюся таблицу нажав “p”</p>
  </li>
  <li>
    <p>и наконец запишите все изменения (и выйдите) нажав “w”</p>
  </li>
</ul>

<p>Теперь команда <code class="language-plaintext highlighter-rouge">ls /dev/sd*</code> будет сообщать что у вас появился ещё и раздел <code class="language-plaintext highlighter-rouge">/dev/sda1</code> - им мы будем активно пользоваться в дальнейшем.</p>

<p>В частности нужно создать на нём файловую систему - это простая команда<br />
<code class="language-plaintext highlighter-rouge">mkfs.ext4 /dev/sda1</code></p>

<p>Теперь всё готово к записи загрузчика. Ну или почти всё (но об этом чуть позже).</p>

<p><em>Можно было создать таблицу разделов GPT (не имеющую отношения к модному сейчас ИИ) - но для наших целей это не важно, а более архаичная MBR немного упростит эксперимент.</em></p>

<h2 id="загрузчик-extlinux">Загрузчик “extlinux”</h2>

<p>Популярным в Linux загрузчиком является <code class="language-plaintext highlighter-rouge">grub2</code>. Однако он достаточно большой и сложный в смысле конфигурации, поэтому в рамках эксперимента полезно посмотреть на альтернативы:</p>

<ul>
  <li>
    <p>также популярный <code class="language-plaintext highlighter-rouge">systemd-boot</code> - но он по-моему требует EFI что создаёт дополнительные ненужные шаги в нашем эксперименте</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">syslinux</code> / <code class="language-plaintext highlighter-rouge">extlinux</code> - а вот их мы и возьмём в дело, тем более что они использованы для самого SystemRescueCD</p>
  </li>
</ul>

<p>Мы используем <code class="language-plaintext highlighter-rouge">extlinux</code> - это версия <code class="language-plaintext highlighter-rouge">syslinux</code> для ext4fs, линуксовой файловой системы. Если вы попробуете запустить его из командной строки он выдаст подсказку, которая сообщает среди прочего что диск для установки нужно сперва примонтировать.</p>

<p>У нас есть пустая директория <code class="language-plaintext highlighter-rouge">/mnt</code> - давайте туда его и подключим:</p>

<p><code class="language-plaintext highlighter-rouge">mount /dev/sda1 /mnt</code></p>

<p>Это необязательно, но чтобы не нарушать популярной схемы размещения файлов, давайте создадим папку <code class="language-plaintext highlighter-rouge">/boot</code> в корне:</p>

<p><code class="language-plaintext highlighter-rouge">mkdir /mnt/boot</code></p>

<p>Теперь всё готово к записи загрузчика, используйте команду которую он и сам подсказывает:</p>

<p><code class="language-plaintext highlighter-rouge">extlinux --install /mnt/boot</code></p>

<p>Он поместит пару файлов в указанный каталог а кроме того (благодаря ключу –install) запишет загрузочный код в начало раздела. К сожалению этого ещё недостаточно для запуска, сейчас мы убедимся.</p>

<p>Можете для любопытства посмотреть что теперь в корневой папке нашего диска и в папке /boot - с помощью команд <code class="language-plaintext highlighter-rouge">ls /mnt</code> и <code class="language-plaintext highlighter-rouge">ls /mnt/boot</code> сооответственно - когда налюбуетесь, давайте отмонтируем диск (чтобы быть уверенными что всё записалось) командой <code class="language-plaintext highlighter-rouge">umount /mnt</code> после чего выполним следующее:</p>

<ul>
  <li>
    <p>в настройках виртуалки (меню Devices) извлеките виртуальный CD</p>
  </li>
  <li>
    <p>в меню Machine нажмите Reset</p>
  </li>
</ul>

<p>Машина перезагрузится и снова пожалуется что у вас нет загрузочного девайса!</p>

<p>Это потому что отсутствует загрузочный код в самом первом секторе таблицы разделов (MBR - master boot record). Вставьте виртуальный диск обратно, перезагрузите машину снова в SystemRescue и давайте исправим этот недочёт.</p>

<h2 id="запись-mbr---мелкий-штрих">Запись MBR - мелкий штрих</h2>

<p><em>Вообще-то это опционально. Можно обойти проблему стартуя с SystemRescueCD и выбирая пункт “Boot existing OS” - в этом случае “эстафетная палочка” загрузки переходит сразу к нужному разделу диска, минуя MBR. Но всё же потратим пару минут чтобы сделать сразу хорошо.</em></p>

<p>Где-то в недрах файловой системы лежит файл <code class="language-plaintext highlighter-rouge">mbr.bin</code> - в нём как раз код который нужно записать. Найдите его командой</p>

<p><code class="language-plaintext highlighter-rouge">find / -name mbr.bin</code></p>

<p>у меня он оказался в <code class="language-plaintext highlighter-rouge">/usr/lib/syslinux/bios/mbr.bin</code> - запишите его на диск командой cat:</p>

<p><code class="language-plaintext highlighter-rouge">cat /usr/lib/syslinux/bios/mbr.bin &gt; /dev/sda</code></p>

<p>(просто sda а не sda1 - т.к. это MBR). Теперь если вы повторите эксперимент с перезагрузкой, вы должны увидеть что <code class="language-plaintext highlighter-rouge">extlinux</code> запустился - он скажет что не нашёл конфигурационного файла и покажет приглашение <code class="language-plaintext highlighter-rouge">boot:</code> - в принципе тут можно вручную указать ядро и параметры загрузки. Но ядра у нас пока нет.</p>

<h2 id="добавим-ядро-а-лучше-два">Добавим ядро, а лучше два</h2>

<p>Итак, вновь перезагрузите машину в SystemRescue - в дальнейшем не “извлекайте” виртуальный CD а когда требуется попробовать загрузку с жёсткого диска просто используйте пункт “Boot Existing OS” из загрузочного меню.</p>

<p>Примонтируйте жёсткий диск как и раньше и перейдите в директорию <code class="language-plaintext highlighter-rouge">/mnt/boot</code> - давайте затащим сюда ядро!</p>

<p>А где его взять? нетрудно догадаться что как минимум одно должно быть где-то в недрах самого SystemRescueCD - попробуем найти его (оно обычно имеет название начинающееся с <code class="language-plaintext highlighter-rouge">vmlinuz</code>:</p>

<p><code class="language-plaintext highlighter-rouge">find / -name vmlinuz*</code></p>

<p>У меня оно нашлось например где-то в недрах <code class="language-plaintext highlighter-rouge">/usr/lib</code> - файл размером около 5 мегабайт. Скопируем его (находясь в /mnt/boot):</p>

<p><code class="language-plaintext highlighter-rouge">cp /usr/lib/.../vmlinuz vmlinuz1</code></p>

<p>Как видите, мы задали ему имя с суффиксом <code class="language-plaintext highlighter-rouge">1</code> - загрузке это не помешает, а мы сможем различать ядра. Так как мы собираемся попробовать разные.</p>

<p>Дело в том что Linux (и другие *nix системы) позволяют легко подменять ядра, выбирая нужное при загрузке. Второе ядро я взял из основной ОС на моём ноутбуке (Ubuntu 18.04 кажется). У вас под рукой такой возможности может не быть, но наверняка вы можете найти разные ядра в интернете. Свои я <a href="https://github.com/rodiongork/bare-linux-experiment">загрузил на гитхаб</a> - так что вы можете воспользоваться прямой ссылкой:</p>

<p><code class="language-plaintext highlighter-rouge">wget https://github.com/RodionGork/bare-linux-experiment/raw/refs/heads/main/vmlinuz64</code></p>

<p>Естественно, сделайте это в той же папке /mnt/boot чтобы ядра лежали рядом с загрузочными файлами (это необязательно но удобно). Если вы обнаружите что виртуалка не может достучаться в сеть, проверьте настройки сети (в ней) - для запросов в интернет проще всего выбрать NAT.</p>

<p><strong><em>Внимание:</em></strong> <em>использовать “готовые” ядра затащенные непонятно откуда - плохая идея! По-хорошему нужно взять исходники и вдумчиво скомпилировать ядро нужной версии и с требуемыми настройками. Мы пропускаем этот шаг только для упрощения эксперимента!</em></p>

<p>Так или иначе, надеюсь запастись ядрами вам удалось и команда <code class="language-plaintext highlighter-rouge">ls /mnt/boot</code> показывает наличие файлов <code class="language-plaintext highlighter-rouge">vmlinuz1</code> и <code class="language-plaintext highlighter-rouge">vmlinuz64</code> - попробуем их загрузить!</p>

<p>Не забудьте <code class="language-plaintext highlighter-rouge">umount</code>, а теперь перезапускайте машину и выбирайте “Boot existing OS”.</p>

<p>В приглашении загрузчика, которое выглядит как <code class="language-plaintext highlighter-rouge">boot:</code> пишите <code class="language-plaintext highlighter-rouge">/boot/vmlinuz1</code> например - полный путь до скачанного нами ядра. Жмите Enter.</p>

<p>Через пару секунд активной деятельности на экране появится сообщение c “kernel panic” и “Unable to mount root fs…”</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/476/b1c/851/476b1c851975f3574467d52c94fbad8b.png" alt="" /></p>

<p>Прекрасно, ядро грузится но зачем-то хочет какую-то “VFS”?</p>

<h2 id="готовим-initrd---виртуальную-файловую-систему">Готовим initrd - виртуальную файловую систему</h2>

<p>Дело обстоит так что современные *nix-овые ядра предполагают такой порядок загрузки, что сразу после запуска оно ищет небольшой образ с файловой системой которую можно развернуть прямо в оперативке. А уж остальные файловые системы (на диске и т.п.) подключить потом, проведя разные дополнительные инициализации.</p>

<p>Образ с этой файловой системой передаётся параметром ядра <code class="language-plaintext highlighter-rouge">initrd=...</code> (от слов “init root directory” что ли)</p>

<p>Мы подготовим ему такой образ, состоящий из всего одного файла - нашей пользовательской программы! Здесь мы пойдём на ещё один трюк - первая программа которую ядро пытается запустить - это <code class="language-plaintext highlighter-rouge">init</code> - некий самый главный процесс ОС. Вот мы и назовём исполнимый файл нашей приложеньки именно так - и разместим в корне.</p>

<p><em>Далее мы попробуем написать и скомпилировать пару незамысловатых программ - если у вас под рукой нет на чем их скомпилировать - не беда - вы сможете скачать готовые образа initrd в том же репозитории где лежат ядра.</em></p>

<p>Напишем незамысловатую программу на С - она просто вводит строчки от пользователя и печатает их длину:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include &lt;stdio.h&gt;#include &lt;string.h&gt;int main() {  printf("I'm mini-shell, type in your commands:\n");  while (1) {    char ur[1024];    fgets(ur, sizeof(ur), stdin);    if (ur[0] &lt; ' ') break;    printf("%ld - not supported\n", strlen(ur));  }  return 0;}
</code></pre></div></div>

<p>Программа представляется как “mini-shell” хотя на самом деле конечно никакой это не shell - если вы захотите добавить здесь какие-то полезные команды, придётся их заимплементить. Пока что простим себе этот маленький обман и соберем программу, указав ключ статической компиляции (т.к. никаких динамических библиотек у нас под рукой не будет). Эта программа использует только функции стандартной библиотеки C (которые будут добавлены в исполнимый код) и системные вызовы ядра для ввода и вывода - так что все должно быть в порядке. Назовите файл <code class="language-plaintext highlighter-rouge">init.c</code></p>

<p><code class="language-plaintext highlighter-rouge">gcc --static -o init init.c</code></p>

<p>Теперь нужно закинуть скомпилированный файл <code class="language-plaintext highlighter-rouge">init</code> в виртуалку. В ней во-первых перезагрузитесь снова в SystemRescueCD, примонтируйте диск и перейдите в <code class="language-plaintext highlighter-rouge">/mnt/boot</code> - а во-вторых переключите настройки сети на “host network only” (перед этим нужно в настройках самого VirtualBox создать новый host-only адаптер). После этого вы сможете либо приконнектиться из виртуалки к родительской машине по <code class="language-plaintext highlighter-rouge">sftp</code> и стянуть файл, либо запустите на родительской машине какой-нибудь веб-сервер (хотя бы <code class="language-plaintext highlighter-rouge">python3 -m http.server</code>) и из виртуалки вытяните файл wget-ом. В обоих случаях адрес родительской машины будет что-то в духе 192.168.56.1 (проверьте ifconfig-ом).</p>

<p>Когда вам удалось заполучить скомпилированный файл <code class="language-plaintext highlighter-rouge">init</code> в виртуалке, убедитесь что у него присутствует исполнимый флаг (или просто проставьте его для уверенности <code class="language-plaintext highlighter-rouge">chmod u+x init</code>) - теперь нам нужна уличная магия собирающая образ с помощью команды cpio:</p>

<p><code class="language-plaintext highlighter-rouge">echo init | cpio -o --format=newc &gt; initrd-c</code></p>

<p>в результате появится файл <code class="language-plaintext highlighter-rouge">initrd-c</code> который мы и собираемся использовать при запуске. Отмонтируем диск, перезагружаемся в “existing OS” и пробуем загрузить ядро указав нужный initrd файл:</p>

<p><code class="language-plaintext highlighter-rouge">boot: /boot/vmlinuz1 initrd=/boot/initrd-c</code></p>

<p>С большой долей вероятности эта попытка обломится - вы увидите похожий экран с логом загрузки, однако утверждающий что не удалось запустить <code class="language-plaintext highlighter-rouge">init</code>. Немного выше по логу возможно будет отыскать конкретную ошибку, например <code class="language-plaintext highlighter-rouge">(error -8)</code>, как-то так:</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/e06/cde/b7a/e06cdeb7a9d1f4a6d2634a0490c3c800.png" alt="" /></p>

<p>Если вы увидите <code class="language-plaintext highlighter-rouge">error -13</code> - это означает “permission denied” - вы забыли сделать файл исполнимым. А вот <code class="language-plaintext highlighter-rouge">error -8</code> про другое “wrong executable file format” - файл собран под 64-битную систему а ядро запускает 32-битную.</p>

<p>Естественно это зависит от того на какой системе и как компилировали файл.</p>

<p>Исправить ситуацию можно двумя способами - либо попробуйте указать другое ядро при запуске (то которое vmlinuz64) - либо возьмите другой initrd-файл - например в <a href="https://github.com/rodiongork/bare-linux-experiment">репозитории упомянутом выше</a> есть <code class="language-plaintext highlighter-rouge">initrd-asm</code> - в нём <code class="language-plaintext highlighter-rouge">init</code> собранный маленькой программой на ассемблере (её код там тоже где-то есть для любопытных - своего рода “hello-world”). Если захотите собрать сами (исходник там рядом лежит), используйте команды</p>

<p><code class="language-plaintext highlighter-rouge">as --32 init.c &amp;&amp; ld -m elf_i386 -o init a.out</code></p>

<p>После чего затащите его в виртуалку и запакуйте как и раньше (для удобства предлагаю файл назвать иначе - например initrd32 или initrd-asm).</p>

<p><em>Что касается этой ассемблерной программы - она лишь развитие примерчика из прошлой</em> <a href="https://habr.com/ru/articles/855158/"><em>статьи про 5 ассемблеров</em></a> <em>- если вам любопытно углубиться, я обязательно напишу отдельную статейку с разбором этой программульки после которой вы сами сможете писать подобные - для развлечения или в образовательных целях!</em></p>

<p>Думаю, с нескольких попыток вам повезет :) Вы либо увидите сообщение что “mini-shell” готов к вашим экспериментам - попробуйте вводить строки а когда надоест нажмите Ctrl-C - чтобы узнать что случается если <code class="language-plaintext highlighter-rouge">init</code>-процесс в линуксе завершается. Либо увидите сообщение про “nedo-bash”.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/c30/f8b/201/c30f8b201674666bb6a36781b0230367.png" alt="С vmlinuz64 наш &quot;недо-баш&quot; умеет читать с клавиатуры строчки и сообщает их длину" title="С vmlinuz64 наш &quot;недо-баш&quot; умеет читать с клавиатуры строчки и сообщает их длину" /></p>

<p>С vmlinuz64 наш “недо-баш” умеет читать с клавиатуры строчки и сообщает их длину</p>

<h2 id="конфигурация-для-загрузчика-extlinux">Конфигурация для загрузчика extlinux</h2>

<p>Можно добавить рядом с файлами загрузчика файл конфигурации, чтобы по умолчанию загружалось некоторое выбранное ядро с некоторым выбранным initrd (и прочими опциями если нужно). Для этого в SystemRescueCD примонтируйте диск (в очередной раз) и используя <code class="language-plaintext highlighter-rouge">vi</code> или <code class="language-plaintext highlighter-rouge">nano</code> создайте файл <code class="language-plaintext highlighter-rouge">/mnt/boot/extlinux.conf</code> с примерно таким содержимым:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prompt 1
timeout 100
default testlinux

label testlinux
kernel vmlinuz64
append initrd=initrd-c
</code></pre></div></div>

<p>Первая строчка означает что нужно показать приглашение <code class="language-plaintext highlighter-rouge">boot:</code> чтобы пользователь мог ввести альтернативные параметры загрузки, вторая задаёт таймаут (в десятых долях секунды) после которого продолжится загрузка отмеченная названием указанным в третьей строке.</p>

<h2 id="заключение">Заключение</h2>

<p>Резюмируя - повторимся, что ядро это “фреймворк и библиотека” для наших, пользовательских программ - а также “прослойка над железом” абстрагирующая и унифицирующая как архитектуру процессора так и зоопарк периферийных устройств. Собственного пользовательского интерфейса у него как такового не предусмотрено - за исключением параметров запуска и лога загрузки.</p>

<p>Надеюсь вам удалось справиться с этим “упражнением” до конца. Как вы понимаете - оно лишь отправная точка для дальнейших экспериментов. Остались разнообразные интересные вопросы которые можно поисследовать.</p>

<p>Например можно утащить <code class="language-plaintext highlighter-rouge">initrd</code> файл с Убунты (он весит около 50мб) - и попробовать подключить его. Вы получите уже более менее рабочую систему - однако убедитесь что надо создать на диске кое-какие папки (вроде <code class="language-plaintext highlighter-rouge">/dev</code>) да и подмонтировать его как рутовую систему.</p>

<p>А можно поинтересоваться, почему наш “nedo-bash” хотя выводит сообщение на экран, но ввод осуществляет только будучи запущенным с одним из двух ядер - как будто у другого ядра не включена клавиатура. Впрочем тут лучше вернуться к совету упомянутому выше - не стоит использовать непонятные-незнакомые ядра. Попробуйте собрать своё.</p>

<p>Отдельным направлением может быть эксперимент с другими ядрами - возьмите ядро от <strong>Gnu Hurd</strong> - или от <strong>FreeBSD</strong>. Правда компилируя для них программы нужно иметь в виду что у них немного отличающийся формат системных вызовов в сравнении с Linux (так что просто собрав программу на Linux-машине вы возможно не получите то что нужно).</p>

<p><em>Вообще это отдельная интересная тема - номера системных функций у *nix-овых систем совпадают, у Линукса в том числе - но во-первых в 64-битном линуксе их внезапно перетасовали, во-вторых Линукс делает вызовы передавая параметры в регистрах (а-ля ДОС) - в то время как остальные пушают их “по-сишному” через стек. В общем, тоже нужна отдельная статья!</em></p>

<p>Пожалуй часть этих вопросов я сам постараюсь осветить в ближайшее время, они достаточно занятны!</p>]]></content><author><name>Oleg Larkin</name></author><category term="habr" /><category term="habr" /><category term="linux" /><summary type="html"><![CDATA[Итак, Linux - не операционная система, а только ядро для неё. Всё остальное приходит от проекта GNU (и других). И вот интересно - на что годится ядро само по себе?]]></summary></entry><entry><title type="html">Собираем и запускаем минимальное ядро Linux</title><link href="http://localhost:4000/habr/2026/01/12/build-kernel-and-userspace.html" rel="alternate" type="text/html" title="Собираем и запускаем минимальное ядро Linux" /><published>2026-01-12T00:00:00+03:00</published><updated>2026-01-12T00:00:00+03:00</updated><id>http://localhost:4000/habr/2026/01/12/build-kernel-and-userspace</id><content type="html" xml:base="http://localhost:4000/habr/2026/01/12/build-kernel-and-userspace.html"><![CDATA[<p>Однажды на работе техлид порекомендовал мне проштудировать книгу <em>Understanding the Linux Kernel</em> Бове и Чезати. В ней рассмотрена версия Linux 2.6, сильно не дотягивающая до более современной версии 6.0. Но в ней явно ещё много ценной информации. Книга толстая, поэтому на её изучение мне потребовалось немало времени. Занимаясь по ней, я решил настроить такую среду разработки, в которой я мог бы просматривать и изменять новейшую версию ядра Linux — чтобы было ещё интереснее.</p>

<p>Есть и другие статьи, в которых рассказано, как собрать ядро Linux. Но в этой статье я немного иначе организую и подаю информацию.</p>

<h2 id="этапы">Этапы</h2>

<p>Работа пойдёт в 2 основных этапа:</p>

<ol>
  <li>
    <p>Собираем и запускаем Linux на qemu</p>
  </li>
  <li>
    <p>Собираем и запускаем Linux на qemu с поддержкой пользовательского пространства Busybox</p>
  </li>
</ol>

<p>Кроме того, через qemu можно подключить отладчик прямо к действующему ядру Linux. Сначала я планировал изучить этот процесс и именно о нём написать статью, но передумал, увидев, что статья получается слишком длинной. <a href="https://www.kernel.org/doc/html/v4.14/dev-tools/kgdb.html">Здесь</a> можно почитать о kgdb. Можете сами убедиться – раньше я с ней не работал.</p>

<h2 id="установка-qemu">Установка qemu</h2>

<p>Будем работать с эмулятором Qemu, который имитирует железо. Именно на нём будет работать тот Linux, который bs собираем. Для установки выполните:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ sudo apt install qemu qemu-system
</code></pre></div></div>

<p>Сначала я пытался собрать qemu, взяв за основу исходный код, но у него очень много зависимостей, и вскоре я стал понимать, что напрасно теряю время.</p>

<h2 id="клонируем-linuxнастраиваем-ветку-на-локальной-машине">Клонируем Linux,настраиваем ветку на локальной машине</h2>

<p>Сначала клонируем репозиторий с Linux.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
</code></pre></div></div>

<p>Отличная задача, на которой можно протестировать скорость загрузки.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ du --max-depth=1 --block-size=GB | grep linux6GB     ./linux
</code></pre></div></div>

<p>Затем отметим галочкой ту версию, которая нас интересует. Я остановился на 5.19.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ cd linux
# alias gco="git checkout"
~ gco v5.19
~ git branch -M 5.19
</code></pre></div></div>

<h2 id="собираем-linux">Собираем Linux</h2>

<p>Как вы можете убедиться, весь процесс сборки документирован прямо в дереве исходников Linux, см. <a href="https://github.com/torvalds/linux/blob/df26327ea097eb78e7967c45df6b23010c43c28d/Documentation/admin-guide/README.rst">readme</a>. Также можно ввести команду <code class="language-plaintext highlighter-rouge">make help</code>, и она выведет доступные опции. Ниже я пошагово опишу работу, которую проделал.</p>

<h4 id="очистка-на-первый-раз-не-нужна">Очистка (на первый раз не нужна)</h4>

<p>Избавьтесь от всех устаревших файлов <code class="language-plaintext highlighter-rouge">.o</code>, оставшихся предыдущих попыток. Нам это сейчас не нужно, поскольку мы в первый раз приступаем к сборке, однако хорошие привычки не повредит усваивать заблаговременно.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ make mrproper
</code></pre></div></div>

<p><a href="https://github.com/torvalds/linux/blob/df26327ea097eb78e7967c45df6b23010c43c28d/Documentation/admin-guide/README.rst">исходник</a></p>

<h4 id="собираем-образ-ядра">Собираем образ ядра</h4>

<p>У ядра множество возможностей — выбирайте те, что вам нравятся. Например, в ядре есть множество драйверов, вам же, вероятно, нужны лишь некоторые из них. Если скомпилировать ядро сразу со всеми драйверами, это даже может привести к отказу некоторых функций. Драйверы – это лишь один пример. Есть подобные возможности, связанные с виртуализацией, файловыми системами и т.д. Много чего конфигурировать! Следовательно, при сборке ядра делается отдельный шаг, на котором вы явно указываете все возможности, которые вам понадобятся, а лишь затем приступаете собственно к сборке.</p>

<p>Если вы выбрали для сборки ядра путь <code class="language-plaintext highlighter-rouge">/home/$USER/linux-build</code>, то укажите флаг <code class="language-plaintext highlighter-rouge">O</code> (каталог вывода) как показано ниже.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ OUTPUT_DIR=/home/$USER/linux/build
# создать файл конфигурации сборки ядра. При этом максимально возможное количество значений
# устанавливается в no. В сущности, нужно отключить как можно больше фич, 
# так у вас получится компактное ядро.
# Если хотите сделать минимальное ядро, воспользуйтесь tinyconfig вместо allnoconfig.
# Не представляю, чем они отличаются.
~ make O=$OUTPUT_DIR allnoconfig
# Здесь можно просматривать конфигурацию ядра через визуально приятный пользовательский интерфейс.
# Тут пока нечего включать.
~ make O=$OUTPUT_DIR menuconfig
# Собираем само ядро
# Заменяем 8 на столько процессов, сколько поддерживает ваш компьютер.
# cat /proc/cpuinfo | grep processor | wc -l.
~ make O=$OUTPUT_DIR -j8
</code></pre></div></div>

<p>На выходе получаем образ ядра –файл <code class="language-plaintext highlighter-rouge">bzImage</code>—и убеждаемся, что его размер составляет всего 1,5 МБ.</p>

<h2 id="этап-второй-запускаем-linux-на-qemu">Этап второй: Запускаем Linux на qemu</h2>

<p>Теперь давайте попробуем запустить при помощи qemu то ядро, которое у нас получилось.</p>

<p><a href="https://manpages.debian.org/jessie/qemu-system-x86/qemu-system-x86_64.1.en.html">В man-подобной справке по qemu</a> достаточно хорошо объяснено, как работают разные флаги.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ OUTPUT_DIR=/home/$USER/linux/build
# -nographic в сущности, означает, что мы обходимся одной лишь консолью для последовательного ввода и не нуждаемся в gui/устройстве с дисплеем.
# -append позволяет qemu передать следующую строку в качестве командной строки ядра.
#     Так можно сконфигурировать ядро в процессе загрузки:
#     - console=ttyS0 сообщает ядру, что нужно использовать последовательный порт.
#     - earlyprintk=serial,ttyS0 сообщает ядру, что нужно отправлять через последовательный порт информацию логов, чтобы мы могли, опираясь на неё,  
#        отлаживать систему после отказов ещё до того, как инициализируется код консоли. Попробуйте от этого избавиться – и увидите, что получится! 
# -kernel указывает, какой образ ядра использовать.
# 
~ qemu-system-x86_64 -kernel $OUTPUT_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0"
</code></pre></div></div>

<p>Если нажать <code class="language-plaintext highlighter-rouge">ctrl + a</code>, а затем <code class="language-plaintext highlighter-rouge">x</code>, то мы покинем экран консоли. Если нажать  <code class="language-plaintext highlighter-rouge">ctrl + a</code>, а затем <code class="language-plaintext highlighter-rouge">h</code>, то будет выведено меню справки и другие опции.</p>

<p>В любом случае, если запустить собранное ядро через qemu, в ядре возникнет паника:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Warning: unable to open an initial console.
List of all partitions:
No filesystem could mount root, tried:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
</code></pre></div></div>

<p>На следующем этапе мы от этой паники избавимся.</p>

<h2 id="обзаводимся-busybox">Обзаводимся Busybox</h2>

<p>Теперь у нас есть ядро Linux, но нет ни пользовательского пространства, ни файловой системы. Воспользуемся файловой системой, в которой обеспечена поддержка памятью (<a href="https://wiki.gentoo.org/wiki/Custom_Initramfs">initramfs</a>, можете посмотреть эту ссылку Gentoo в качестве тизера). Что-то должно пойти в файловую систему, поскольку мы не хотим, чтобы наше пользовательское пространство пустовало.</p>

<p>Именно здесь нам пригодится Busybox. В нём предоставляются такие команды как <code class="language-plaintext highlighter-rouge">ls</code>, <code class="language-plaintext highlighter-rouge">cd</code>, <code class="language-plaintext highlighter-rouge">cp</code>, <code class="language-plaintext highlighter-rouge">mv</code>, <code class="language-plaintext highlighter-rouge">vim</code>, <code class="language-plaintext highlighter-rouge">tar</code>, <code class="language-plaintext highlighter-rouge">grep</code>, <code class="language-plaintext highlighter-rouge">dhcp</code>, <code class="language-plaintext highlighter-rouge">mdev</code> (события горячего подключения устройств к Linux), <code class="language-plaintext highlighter-rouge">ifplugd</code> (мониторинг сетевого канала/интерфейса) – всё через маленький двоичный файл. Пожалуй, эти команды не будут такими многофункциональными и разнообразно конфигурируемыми, как их альтернативы, применяемые вне Busybox, но их нам хватит.</p>

<p>Посмотрите файл README к исходному коду busybox после того, как скачаете его по ссылке ниже – там всё подробно написано.</p>

<p>Переходите по ссылке <a href="https://busybox.net/">https://busybox.net/</a> и забирайте новейшую стабильную версию busybox.</p>

<h2 id="конфигурируем-и-собираем-busybox">Конфигурируем и собираем Busybox</h2>

<p>Процесс такой же, как и при работе с ядром Linux.</p>

<p>Посмотрите файл INSTALL file в исходниках к busybox, как только они скачаются.</p>

<p>Сначала выбираем желаемую конфигурацию Busybox, а затем приступаем к сборке.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ cd busybox-1.33.2~ mkdir -pv build~ OUTPUT_DIR=/home/$USER/busybox-1.33.2/build# создаём файл .config, в котором выставлено множество «yes». Получаем# массу возможностей Busybox, может быть, даже больше, чем нам требуется. # Я недостаточно разбираюсь в теме, чтобы начинать с allnoconfig# поэтому включаю только абсолютный минимум функций – собственно, вот он. # Возможно, сборка получится и крупнее, чем ядро на 1,5 МБ. Давайте посмотрим ~ make O=$OUTPUT_DIR defconfig# Открываем конфигурационный UI~ make O=$OUTPUT_DIR menuconfig
</code></pre></div></div>

<p>Когда конфигурационный пользовательский интерфейс открыт, выбираем в нём “Settings” (Настройки) (клавишей ввода), а затем “Build Busybox as a static binary” (Собрать Busybox как статический двоичный файл) (клавишей пробела). Дело в том, что в файловой системе пользовательского пространства нашего пустого ядра не будет никаких разделяемых библиотек, поэтому мы можем сразу приступить к работе.</p>

<p>Теперь выходим из конфигурационного меню и сохраняем внесённые изменения.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/e14/01e/86d/e1401e86d845daee3926ded2ce6e47f9.jpg" alt="" /></p>

<p>Мы готовы приступать к сборке!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># введите make help, чтобы просмотреть доступные опции,# но, в сущности, можно включить make all или make busybox.# При первой опции также собирается документация, при второй - только busybox.~ make O=$OUTPUT_DIR -j8 busybox
</code></pre></div></div>

<p>И вот,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ ls -la $OUTPUT_DIR --block-size=KB | grep busybox-rw-r--r--  1 yangwenli yangwenli    2kB Aug 23 15:33 .busybox_unstripped.cmd-rwxr-xr-x  1 yangwenli yangwenli 2694kB Aug 23 15:33 busybox-rwxr-xr-x  1 yangwenli yangwenli 2987kB Aug 23 15:33 busybox_unstripped-rw-r--r--  1 yangwenli yangwenli 2340kB Aug 23 15:33 busybox_unstripped.map-rw-r--r--  1 yangwenli yangwenli  105kB Aug 23 15:33 busybox_unstripped.out
</code></pre></div></div>

<p>Двоичный файл <code class="language-plaintext highlighter-rouge">busybox</code>, который мы хотели получить, действительно оказался размером около  2,7 МБ, больше, чем собранное нами ядро. Вариант <code class="language-plaintext highlighter-rouge">busybox_unstripped</code> нас не интересует. Он немного крупнее и, очевидно, предназначен для изучения при помощи аналитических инструментов, так, как <a href="https://busybox.net/FAQ.html">об этом рассказано в Busybox FAQ</a>.</p>

<h2 id="создаём-исходную-структуру-каталогов">Создаём исходную структуру каталогов</h2>

<p><em>Следующие два раздела сильно вдохновлены вики-справкой по Gentoo, которая приводится в Custom Initramfs</em> <a href="https://wiki.gentoo.org/wiki/Custom_Initramfs"><em>здесь</em></a><em>.</em></p>

<p>Теперь нам предстоит собрать исходную структуру файлов для пользовательского пространства нашего Linux.</p>

<p>Нам потребуется убедиться наверняка, что двоичный файл <code class="language-plaintext highlighter-rouge">busybox</code> на своём месте. А также предусмотреть init-процесс/скрипт, чтобы настроить наше пользовательское пространство.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ mkdir /home/$USER/initramfs &amp;&amp; cd initramfs
# создаём ряд базовых каталогов, которые понадобятся нам в нашем пользовательском пространстве Linux 
# dev, proc и sys нужны для хранения всякого материала, относящегося к работе ядра – в частности, procfs, sysfs и устройств.
# В etc будем хранить заготовки для конфигурации того материала, которым собираемся заняться в будущем.
# Из root мы будем действовать.
# В bin будут храниться исполняемые файлы.
~ mkdir {bin,dev,etc,proc,root,sys}
# busybox также рассчитывает, что в нём будут эти дополнительные каталоги, 
# так что давайте создадим их для него
~ mkdir {usr/bin,usr/sbin,sbin}
# Мы хотим, чтобы busybox был включён в наш initramfs
~ cp /home/$USER/busybox-1.33.2/build/busybox bin/busybox
</code></pre></div></div>

<h2 id="создаём-init-процесс">Создаём init-процесс</h2>

<p>Теперь давайте создадим init-процесс. В каталоге <code class="language-plaintext highlighter-rouge">initramfs</code> создаём файл под названием <code class="language-plaintext highlighter-rouge">init</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ touch init &amp;&amp; chmod +x init
</code></pre></div></div>

<p>И заполняем его следующим материалом:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/busybox sh
# Получаем busybox, чтобы создать нежёсткие ссылки на команды 
/bin/busybox --install -s
# Монтируем файловые системы /proc и /sys.
# Можете пропустить этот шаг, если хотите. Просто мне показалось, что хорошо бы их иметь.
mount -t proc none /proc
mount -t sysfs none /sys
# Загружаем командную оболочку, которая теперь должна быть мягко связана с busybox
exec /bin/sh
</code></pre></div></div>

<h2 id="создаём-initramfs-cpio">Создаём initramfs cpio</h2>

<p>Cpio – это инструмент-архиватор. В сущности, это означает, что он берёт набор файлов и каталогов и обратимо преобразует их в единственный файл. Примерно как tar. Не понимаю, почему, но initramfs указывается через cpio, поэтому и его мы должны обязательно использовать, чтобы всё упаковать. Для сжатия воспользуемся gzip.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ find . -print0 | cpio --null --create --verbose --format=newc | gzip --best &gt; ./custom-initramfs.cpio.gz
.
./etc
./root
./sys
./dev
./bin
./bin/busybox
./init
./proc
cpio: File ./custom-initramfs.cpio.gz grew, 1310720 new bytes not copied
./custom-initramfs.cpio.gz
7824 blocks
</code></pre></div></div>

<p>Вот мы и подготовили initramfs, которым собираемся пользоваться!</p>

<h2 id="этап-выполняем-linux-на-qemu-при-помощи-busybox-с-применением-initramfs">Этап: выполняем Linux на qemu при помощи Busybox (с применением initramfs)</h2>

<p>Теперь давайте запустим ядро Linux с включённым initramfs!</p>

<p>Возьмём команду qemu, приведённую выше, и добавим флаг command from above and add an initrd, указывая таким образом initramfs.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ LINUX_BUILD_DIR=/home/$USER/linux/build
~ INITRAMFS_DIR=/home/$USER/initramfs/custom-initramfs.cpio.gz
# В прпинципе, флаг --initrd разрешает Linux использовать тот ram-диск, который мы собрали 
~ qemu-system-x86_64 -kernel $LINUX_BUILD_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0" --initrd $INITRAMFS_DIR
</code></pre></div></div>

<p>Вероятно, вас расстраивает, что паника ядра до сих пор возникает. Дело в том, что мы до сих пор не включили поддержку initramfs в ядре, а также не предусмотрели ещё пару деталей, необходимых для нормальной работы в нашем пользовательском пространстве.</p>

<p>Слегка затронем вопрос о том, как ядро запускает пользовательское пространство, и как оно узнаёт, где найти процесс <code class="language-plaintext highlighter-rouge">init</code>. Чтобы запустить работу пользовательского пространства, ядро ищет <a href="https://github.com/torvalds/linux/blob/6991a564f59742a0926be7421dff370135c44a97/init/main.c#L1540">/init</a>, а затем <a href="https://github.com/torvalds/linux/blob/6991a564f59742a0926be7421dff370135c44a97/init/main.c#L1556-L1559">/sbin/init, /etc/init, /bin/init и, наконец, finally /bin/sh</a> — именно в таком порядке. Я оставил ссылку на исходник. Кроме того, в командной строке <a href="https://github.com/torvalds/linux/blob/6991a564f59742a0926be7421dff370135c44a97/init/main.c#L1547">здесь</a>. Я разместил файл init по адресу /bin/init.</p>

<p>Теперь, наладив поддержку initramfs, давайте соберём ещё одно ядро. Повторите шаги, проделанные выше (когда мы его конфигурировали) и соберите ядро:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ cd /home/$USER/linux
~ make O=$LINUX_BUILD_DIR menuconfig
</code></pre></div></div>

<p>Перейдите в <code class="language-plaintext highlighter-rouge">General Setup</code> и найдите там файловую систему <code class="language-plaintext highlighter-rouge">Initial RAM</code>, а также диск с оперативной памятью (RAM), затем нажмите «пробел».</p>

<p>В самом верху конфигурационного файла также активируйте 64-битное ядро. Если при работе с двоичным файлом Busybox вы воспользуетесь командой file, то увидите, что он собран для архитектуры x86_64. Вы также убедитесь, что это файл в формате elf, поэтому мы должны будем предусмотреть в ядре поддержку и для этого формата. Поскольку мы используем в нашем init-файле нотацию <code class="language-plaintext highlighter-rouge">!#</code>, нам и для неё нужно будет обеспечить поддержку.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/722/d2e/9cc/722d2e9cc1c698688c8e74bebd50d60e.jpg" alt="" /></p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/5ce/071/fff/5ce071fffa56e5b6d2691bd52257a7e5.jpg" alt="" /></p>

<p>Наконец спуститесь из начала файла к <code class="language-plaintext highlighter-rouge">Device Drivers &gt; Character devices &gt; Serial drivers</code> и <code class="language-plaintext highlighter-rouge">8250/16550</code> и <code class="language-plaintext highlighter-rouge">compatible serial support</code> и <code class="language-plaintext highlighter-rouge">Console on 8250/16550</code> и <code class="language-plaintext highlighter-rouge">compatible serial port</code>. Эти конфигурационные настройки нужны для того, чтобы использовать последовательный порт в качестве консоли. Подробнее об этом в <a href="https://www.kernel.org/doc/html/v4.15/admin-guide/serial-console.html">документации</a>. Если не внести эти изменения, init работать не сможет. Думаю, именно поэтому и нужна последняя строка <code class="language-plaintext highlighter-rouge">exec /bin/sh</code>.</p>

<p><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/9d5/3f2/b9f/9d53f2b9fd027e1d3addb4e4c2609760.jpg" alt="" /></p>

<p>Теперь соберём ядро:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ make O=$LINUX_BUILD_DIR -j8
</code></pre></div></div>

<p>А потом снова запустим qemu:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~ qemu-system-x86_64 -kernel $LINUX_BUILD_DIR/arch/x86/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0 debug" --initrd $INITRAMFS_DIR
</code></pre></div></div>

<p>Итак, мы сделали себе рабочий Linux. Если у вас достаточно свободного времени, то можете продолжить этот опыт и выстроить на основе проделанной здесь работы ваш собственный дистрибутив.</p>

<p><strong>Источники</strong></p>

<ul>
  <li><a href="https://www.centennialsoftwaresolutions.com/post/build-the-linux-kernel-and-busybox-and-run-them-on-qemu">Build the Linux kernel and busybox and run them on qemu</a></li>
  <li><a href="https://medium.com/@daeseok.youn/prepare-the-environment-for-developing-linux-kernel-with-qemu-c55e37ba8ade">Prepare the environment for developing linux kernel with qemu</a></li>
  <li><a href="https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html">Kernel parameter guide</a></li>
  <li><a href="https://stackoverflow.com/a/30383581">Stack Overflow answer about kernel serial console command line</a></li>
  <li><a href="https://github.com/torvalds/linux/blob/df26327ea097eb78e7967c45df6b23010c43c28d/Documentation/admin-guide/README.rst">Linux source tree &gt; Documentation &gt; admin-guide &gt; README</a></li>
  <li><a href="https://manpages.debian.org/jessie/qemu-system-x86/qemu-system-x86_64.1.en.html">qemu man page</a></li>
  <li><a href="https://www.kraxel.org/blog/2019/09/display-devices-in-qemu/">Display devices in qemu</a></li>
  <li><a href="https://wiki.gentoo.org/wiki/Custom_Initramfs">Gentoo wiki &gt; Custom Initramfs</a></li>
  <li><a href="https://github.com/torvalds/linux/blob/df26327ea097eb78e7967c45df6b23010c43c28d/Documentation/admin-guide/init.rst">Linux source tree &gt; Documentation &gt; admin-guide &gt; init.rst</a></li>
  <li><a href="http://Kernel.org">Kernel.org</a> <a href="https://www.kernel.org/doc/html/v4.15/admin-guide/serial-console.html">&gt; Linux Serial Consol</a></li>
</ul>]]></content><author><name>Oleg Larkin</name></author><category term="habr" /><summary type="html"><![CDATA[Однажды на работе техлид порекомендовал мне проштудировать книгу Understanding the Linux Kernel Бове и Чезати. В ней рассмотрена версия Linux 2.6, сильно не дотягивающая до более современной версии 6.0. Но в ней явно ещё много ценной информации. Книга толстая, поэтому на её изучение мне потребовалось немало времени. Занимаясь по ней, я решил настроить такую среду разработки, в которой я мог бы просматривать и изменять новейшую версию ядра Linux — чтобы было ещё интереснее.]]></summary></entry><entry><title type="html">36 материалов о нейросетях: книги, статьи и последние исследования</title><link href="http://localhost:4000/habr/2026/01/12/36-neuro-materials.html" rel="alternate" type="text/html" title="36 материалов о нейросетях: книги, статьи и последние исследования" /><published>2026-01-12T00:00:00+03:00</published><updated>2026-01-12T00:00:00+03:00</updated><id>http://localhost:4000/habr/2026/01/12/36-neuro-materials</id><content type="html" xml:base="http://localhost:4000/habr/2026/01/12/36-neuro-materials.html"><![CDATA[<p>Что делать, если хочется побольше узнать про нейронные сети, методы распознавания образов, компьютерное зрение и глубокое обучение? Один из очевидных вариантов — подыскать для себя какие-либо курсы и начать активно изучать теорию и решать практические задачи. Однако на это придется выделить значительную часть личного времени. Есть другой способ — обратиться к «пассивному» источнику знаний: выбрать для себя литературу и погрузиться в тему, уделяя этому всего полчаса-час в день.</p>

<p>Поэтому, желая облегчить жизнь себе и читателям, мы сделали краткую подборку из книг, статей и текстов по направлению нейросетей и глубокого обучения, рекомендуемых к прочтению резидентами GitHub, Quora, Reddit и других платформ. В неё вошли материалы как для тех, кто только начинает знакомство с нейротехнологиями, так и для коллег, желающих расширить свои знания в этой области или просто подобрать «легкое чтение» на вечер.</p>

<p><a href="https://habrahabr.ru/company/neurodatalab/blog/336218/"><img src="https://habrastorage.org/r/w1560/web/96a/bd4/b83/96abd4b83965408b9994c0239ee450fd.jpg" alt="" /></a><em>/ Flickr / <a href="https://www.flickr.com/photos/giuseppemilo/12570992595/">Giuseppe Milo</a> / <a href="https://creativecommons.org/licenses/by/2.0/">CC</a></em></p>

<h2 id="актуальный-контекст">Актуальный контекст</h2>

<p>Любой предложенный список, сколь бы длинным он ни был, будет обладать главной и определяющей чертой — неполнотой. Потому что жизнь не стоит на месте: и научная мысль, и технологии развиваются, многие постановки задач описываются, а полученные решения — раскрываются в отчетных публикациях конференций, в журналах и сборниках. Для тех, кто задается вопросом, что происходит в настоящий момент и чем живет сообщество, стоит порекомендовать следить за материалами профильных мероприятий — <a href="https://2017.icml.cc/">ICML</a> и <a href="https://nips.cc/">NIPS</a>.</p>

<h2 id="и-всё-же-с-чего-начать">И всё же, с чего начать?</h2>

<p><a href="http://neuralnetworksanddeeplearning.com/">Neural Networks and Deep Learning</a><br />
Это бесплатная онлайн-книга ученого и программиста Майкла Нильсена (Michael Nielsen). Автор раскрывает тему глубокого обучения нейронных сетей и отвечает на такие вопросы, как: «Почему нейросети сложно тренировать?», «Как работает алгоритм обратного распространения ошибки?».</p>

<p><img src="https://habrastorage.org/r/w1560/web/81e/db9/d70/81edb9d7008346a1a745bcf453036cc6.jpg" alt="" /><br />
<em>Автор книги: <a href="https://www.amazon.com/Tariq-Rashid/e/B01N1YH9L9/ref=dp_byline_cont_book_1">Tariq Rashid</a></em></p>

<p><a href="https://www.amazon.com/gp/product/1530826608/">Make Your Own Neural Network</a><br />
Книга раскрывает математические принципы, лежащие в основе нейронных сетей, и предлагает написать собственную нейросеть на Python. Сеть будет распознавать написанные от руки цифры. Цель книги — дать читателю четкое понимание того, как работают нейросистемы, сделать информацию доступнее.</p>

<p><a href="http://www.dkriesel.com/en/science/neural_networks">A Brief Introduction to Neural Networks</a><br />
Автор книги, специалист по анализу данных и машинному обучению, простым языком объясняет принципы работы нейронных сетей. После прочтения можно самому начать работать с нейросистемами и разбираться в чужом коде. Книга постоянно улучшается, в обновленных версиях, на основании фидбека от читателей.</p>

<p><a href="http://www-bcf.usc.edu/~gareth/ISL/">An Introduction to Statistical Learning</a><br />
Книга-введение в методы статистического обучения. Целевая аудитория — студенты и выпускники вузов, в том числе нематематических специальностей. Всё очень доступно и с туториалами на R.</p>

<p><a href="http://www.amazon.in/Programming-Collective-Intelligence-Segaran/dp/8184043708">Programming Collective Intelligence</a><br />
Книга рассказывает, как анализировать пользовательский опыт и человеческое поведение на основании информации, получаемой нами ежедневно. Предложенные алгоритмы сопровождаются кодом, который можно сразу использовать на веб-сайте или в приложении. Каждая глава включает практические упражнения, задача которых — усилить, отшлифовать алгоритмы.</p>

<p><a href="https://www.amazon.com/Neural-Networks-Introduction-Raul-Rojas/dp/3540605053">Neural Networks: A Systematic Introduction</a><br />
Общая теория о создании искусственных нейронных сетей. Каждая глава содержит примеры, иллюстрации и библиографию. Книга подойдет для тех, кто хочет углубить свои знания в этой сфере, но также может служить неплохой базой для курсов по нейровычислениям.</p>

<p><a href="http://research.microsoft.com/pubs/209355/DeepLearning-NowPublishing-Vol7-SIG-039.pdf">Deep Learning: Methods and Applications</a><br />
Книга от Microsoft Research с основными методологиями глубокого обучения. Авторы рассказывают о том, как нейросети используются в задачах обработки сигналов и информации. Рассматриваются области, в которых глубокое обучение уже нашло активное применение, а также сферы, где оно может оказать значительное влияние в долгосрочной перспективе.</p>

<p><a href="http://deeplearning.net/tutorial/deeplearning.pdf">Deep Learning Tutorial</a><br />
Издание Монреальского университета (Канада). Здесь собраны руководства по наиболее важным алгоритмам глубокого обучения. В книге показано, как реализовать их с помощью библиотеки Theano. Как отмечают авторы, читатель должен иметь <a href="https://docs.python.org/3/tutorial/">представление</a> о Python и NumPy, а также пройти курс по <a href="http://deeplearning.net/software/theano/tutorial/">обращению</a> с Theano.</p>

<p><a href="https://www.amazon.com/Pattern-Recognition-Learning-Information-Statistics/dp/0387310738/">Pattern Recognition and Machine Learning</a><br />
Это первый учебник по распознаванию образов, представляющий Байесовский метод. В книге собраны алгоритмы приближенного вывода для ситуаций, в которых точные ответы получить невозможно. Информация подкрепляется графическими моделями для описания распределения вероятностей. Книга подходит каждому, поскольку для её свободного чтения не требуется доскональное знание концепций машинного обучения и распознавания паттернов.</p>

<p><img src="https://habrastorage.org/r/w1560/web/79f/145/0e6/79f1450e68b249fc9ce805f5fd71bd27.jpg" alt="" /><br />
<em>Автор книги: <a href="https://www.amazon.com/s/ref=dp_byline_sr_book_1?ie=UTF8&amp;text=Simon+S+Haykin&amp;search-alias=books&amp;field-author=Simon+S+Haykin&amp;sort=relevancerank">Simon S Haykin</a></em></p>

<p><a href="https://www.amazon.com/Neural-Networks-Learning-Machines-3rd/dp/8120340000">Neural Networks and Learning Machines</a><br />
В книге разбираются концепции и принципы работы нейронных сетей и самообучающихся машин. На сегодняшний день выпущено уже третье издание.</p>

<p><a href="https://www.amazon.com/gp/product/1491962291">Hands-On Machine Learning</a><br />
С помощью наглядных примеров, минимума теории и двух production-ready фреймворков для Python автор помогает понять, как строятся интеллектуальные системы. Вы узнаете о различных техниках: начиная с простой линейной регрессии и заканчивая глубоким обучением. В каждой главе предусмотрены упражнения для закрепления приобретенных знаний.</p>

<p><a href="https://karpathy.github.io/neuralnets/">Руководство хакера по нейронным сетям</a><br />
Андрей Карпаты (Andrej Karpathy), заведующий разработкой ИИ в Tesla, предлагает заглянуть в прошлое нейронных сетей и начать знакомство с технологией real-valued circuits. Автор также является преподавателем курса CS231 в Стэнфорде, материалы которого тесно связаны с этой статьей. Слайды можно найти по <a href="http://cs231n.stanford.edu/syllabus.html">ссылке</a>. А заметки – <a href="http://cs231n.github.io/">тут</a>.</p>

<p><a href="http://colah.github.io/posts/2014-07-NLP-RNNs-Representations/">Глубокое обучение, обработка естественного языка и представление данных</a><br />
Как использовать глубокие нейронные сети для обработки естественного языка (NLP). Автор также старается дать ответ на вопрос, почему работают нейронные сети.</p>

<p><a href="https://www.toptal.com/machine-learning/an-introduction-to-deep-learning-from-perceptrons-to-deep-networks">Глубокое обучение: Руководство</a><br />
Java-разработчик Иван Васильев представляет ключевые концепции и алгоритмы, стоящие за глубоким обучением, используя для этого язык программирования Java. Библиотека Java для глубокого обучения находится <a href="https://github.com/ivan-vasilev/neuralnetworks">здесь</a>.</p>

<p><a href="https://arxiv.org/pdf/1702.07800.pdf">Происхождение глубокого обучения</a><br />
Эта публикация — исторический обзор развития глубоких моделей обучения. Авторы начинают повествование с того, как появились нейронные сети, и плавно переходят к технологиям последнего десятилетия: глубоким сетям доверия, сверточным и рекуррентным нейронным сетям.</p>

<p><a href="https://arxiv.org/pdf/1701.07274v2.pdf">Глубокое обучение с подкреплением: Обзор</a><br />
Материал посвящен последним достижениям в отрасли глубокого обучения с подкреплением (RL). Сперва авторы обращаются к принципам глубокого обучения и обучения с подкреплением, а затем переходят к проблемам их реальной применимости: играм (AlphaGo), робототехнике, чат-ботам и др.</p>

<p><img src="https://habrastorage.org/r/w1560/web/ae4/bbc/113/ae4bbc113a8240cc94c0927ef62832de.jpg" alt="" /><em>/ Flickr / <a href="https://www.flickr.com/photos/branduren/8024422965/">Brandur Øssursson</a> / PD</em></p>

<h2 id="продвинутое-чтение">Продвинутое чтение</h2>

<p><a href="https://www.amazon.com/Neural-Networks-Applied-Sciences-Engineering/dp/084933375X/">Neural Networks for Applied Sciences and Engineering</a><br />
Обзор архитектур нейронных сетей для непосредственного анализа данных. В отдельных главах авторы рассуждают о применимости самоорганизующихся карт для кластеризации нелинейных данных, а также об использовании рекуррентных сетей в науке.</p>

<p><a href="https://www.ozon.ru/context/detail/id/135794246/">Нейронные сети. Полный курс</a><br />
В книге рассматриваются парадигмы искусственных нейронных сетей с иллюстрациями и примерами конкретных задач. Анализируется роль нейронных сетей при решении задач распознавания образов, управления и обработки сигналов. Книга будет полезна для инженеров, специалистов в области компьютерных наук, физиков, а также для всех, кто интересуется искусственными нейронными сетями.</p>

<p><a href="https://www.ozon.ru/context/detail/id/5040102/">Самоорганизующиеся карты</a><br />
Самоорганизующиеся карты, вместе с их разновидностями, представляют собой одну из наиболее популярных нейросетевых архитектур, ориентированных на обучение без учителя. В книге дается детальное изложение математического аппарата и применений для самоорганизующихся карт. Подходит для специалистов в области нейромоделирования, а также студентов и аспирантов университетов.</p>

<p><img src="https://habrastorage.org/r/w1560/web/246/efe/941/246efe9416b247a6a8c11eee6f57020c.jpg" alt="" /><br />
<em>Автор книги: <a href="https://www.amazon.com/Ian-Goodfellow/e/B01MQGN8N0/ref=dp_byline_cont_book_1">Ian Goodfellow</a></em></p>

<p><a href="https://www.amazon.com/Deep-Learning-Adaptive-Computation-Machine/dp/0262035618">Deep Learning (Adaptive Computation and Machine Learning series)</a><br />
«Deep Learning» является единственной исчерпывающей книгой в этой области», — это слова Илона Маска, соучредителя Tesla и SpaceX. В тексте аккумулирован математический бэкграунд, рассматриваются важные концепции линейной алгебры, теории вероятностей, теории информации и машинного обучения.</p>

<p><a href="https://www.amazon.com/gp/product/0198538642">Neural Networks for Pattern Recognition</a><br />
В книге приведены техники для моделирования функций плотности вероятности. Рассматриваются алгоритмы минимизации функции ошибок, а также Байесовский метод и его применение. Кроме того, авторы собрали под этой обложкой более ста полезных упражнений.</p>

<p><a href="http://www.cs.utoronto.ca/~hinton/absps/ncfast.pdf">Быстрый обучающий алгоритм для глубоких сетей доверия</a><br />
Авторы статьи предлагают алгоритм, способный обучать глубокие сети доверия (DBM) по одному слою за раз. Также стоит обратить внимание на <a href="http://videolectures.net/jul09_hinton_deeplearn/">видеоруководство</a> по глубоким сетям доверия от одного из авторов — Джеффри Хинтона (G. E. Hinton).</p>

<p><a href="https://www.iro.umontreal.ca/~vincentp/ift3395/lectures/backprop_old.pdf">Обучение представлений методом обратного распространения ошибки</a><br />
Считается основой концепции обучения нейронных сетей. Исторический экскурс и реализация. Рекомендуется к прочтению.</p>

<p><a href="https://arxiv.org/pdf/1411.5928.pdf">Учимся генерировать кресла, столы и автомобили с помощью сверточных сетей</a><br />
Статья показывает, что генеративные сети могут находить сходства между объектами, обладая более высокой производительностью, по сравнению с конкурентными решениями. Концепция, представленная в этой статье, также может использоваться для <a href="https://zo7.github.io/blog/2016/09/25/generating-faces.html">генерации</a> лиц.</p>

<p><a href="https://bamos.github.io/2016/08/09/deep-completion/">Завершение изображений с глубоким обучением в TensorFlow</a><br />
Статья рассказывает, как использовать глубокое обучение для завершения изображений, используя DCGAN. Пост рассчитан на техническую аудиторию с бэкграундом в машинном обучении. Весь исходный код автор выложил на <a href="https://github.com/bamos/dcgan-completion.tensorflow">GitHub</a>.</p>

<p><a href="http://torch.ch/blog/2015/11/13/gan.html">Генератор лиц в Torch</a><br />
Автор реализует генеративную модель, которая превращает случайный «шум» в изображения лиц. Делается это с помощью генеративной состязательной сети (GAN).</p>

<p><a href="http://www.cs.utoronto.ca/~hinton/absps/guideTR.pdf">Практическое руководство по тренировке ограниченных машин Больцмана</a><br />
Обзор ограниченных машин Больцмана. Авторы приводят множество рецептов по отладке и улучшению работы системы: назначению весов, мониторингу, выбору числа скрытых узлов.</p>

<p><a href="https://arxiv.org/pdf/1207.0580v1.pdf">Улучшение нейронных сетей путем предотвращения коадаптации детекторов признаков</a><br />
Когда крупная нейронная сеть обучается на небольшом тренировочном наборе данных, она обычно выдает плохие результаты. Авторы предлагают способ, который должен решить проблему «переобучения», научив нейроны определять признаки, помогающие сгенерировать корректный ответ.</p>

<p><a href="http://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Redmon_You_Only_Look_CVPR_2016_paper.pdf">YOLO: обнаружение объектов в реальном времени</a><br />
Авторы демонстрируют подход к распознаванию объектов — YOLO (You Only Look Once). По их задумке, с изображением работает одна нейронная сеть, которая делит его на регионы. Регионы очерчиваются граничными рамками и «взвешиваются» на основании предсказанных вероятностей. Как реализовать «миниверсию» YOLO для работы на мобильных устройствах под iOS вы можете узнать из этой <a href="http://machinethink.net/blog/object-detection-with-yolo/">статьи</a>.</p>

<p><a href="https://arxiv.org/pdf/1412.1897.pdf">Как предсказать нераспознаваемые изображения</a><br />
Одно из недавних исследований <a href="https://arxiv.org/pdf/1312.6199.pdf">показало</a>, что изменение изображения (незаметное для человека) способно обмануть глубокие нейронные сети, заставив последнюю установить неверный маркер. Эта работа проливает свет на интересные различия между человеческим и машинным зрением.</p>

<p><a href="https://arxiv.org/pdf/1702.07825v2.pdf">Deep Voice: преобразование «текст-в-речь» в реальном времени</a><br />
Авторы представляют систему Deep Voice для преобразования текста в речь, построенную на глубоких нейронных сетях. По уверениям ученых, за каждый компонент отвечает своя нейросеть, поэтому их система гораздо быстрее традиционных решений. Стоит пощупать.</p>

<p><a href="https://arxiv.org/pdf/1702.06506v1.pdf">PixelNet: Представление пикселей, пикселями и для пикселей</a><br />
Авторы исследуют принципы генерализации на пиксельном уровне, предлагая алгоритм, адекватно показывающий себя в таких задачах, как семантическая сегментация, выделение границ и оценка нормалей к поверхностям.</p>

<p><a href="https://blog.openai.com/generative-models/">Генеративные модели от OpenAI</a><br />
Этот пост описывает четыре проекта, которые адаптируют генеративные модели. Авторы рассказывают, что это такое, где они используются и почему важны.</p>

<p><a href="http://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Dosovitskiy_Learning_to_Generate_2015_CVPR_paper.pdf">Учимся генерировать кресла с помощью сверточных нейронных сетей</a><br />
Здесь описан процесс тренировки генеративной сверточной нейронной сети для генерации изображений объектов по типу и цвету. Сеть умеет интерполировать ряды изображений и заполнять «пустые места» недостающими элементами.</p>

<p><a href="https://medium.com/@devnag/generative-adversarial-networks-gans-in-50-lines-of-code-pytorch-e81b79659e3f">Генеративно-состязательная сеть в 50 строках кода</a><br />
Как натренировать генеративную состязательную сеть (GAN)? Нужно лишь взять <a href="http://pytorch.org/">PyTorch</a> и написать 50 строчек кода. Попробуем на досуге.</p>

<h2 id="and-last-but-not-least">And last but not least</h2>

<p>Какая книга лежит на столах у многих сотрудников <a href="http://neurodatalab.com/">Neurodata Lab</a> и может считаться одной из любимых?</p>

<p><img src="https://habrastorage.org/r/w1560/web/1cd/845/d27/1cd845d275044300b3629a643b547d43.png" alt="" /><br />
<em>Авторы книги: <a href="https://www.amazon.com/Amit-Konar/e/B001K6L386/ref=dp_byline_cont_book_1">Amit Konar</a>, <a href="https://www.amazon.com/s/ref=dp_byline_sr_book_2?ie=UTF8&amp;text=Aruna+Chakraborty&amp;search-alias=books&amp;field-author=Aruna+Chakraborty&amp;sort=relevancerank">Aruna Chakraborty</a></em></p>

<p><a href="https://www.amazon.com/Emotion-Recognition-Pattern-Analysis-Approach/dp/1118130669">Emotion Recognition. A Pattern Analysis Approach</a><br />
Прекрасный материал, грамотно структурированный и опирающийся на большой объем источников и данных. Книга подходит для всех, кто увлечен проблематикой детекции и распознавания эмоций с технической точки зрения, и тем кто просто ищет захватывающее чтение.</p>

<hr />

<p><em>P.S. Мы понимаем, что осветить все доступные материалы на эту тему в рамках одной статьи невозможно. Поэтому если вам интересно, то можете уделить толику своего внимания подборкам на GitHub и прочих платформах. Вот некоторые из них:</em></p>

<ul>
  <li><a href="https://github.com/robertsdionne/neural-network-papers">neural-network-papers</a></li>
  <li><a href="http://meta-guide.com/software-meta-guide/100-best-github-deep-learning">100 Best GitHub: Deep Learning</a></li>
  <li><a href="https://github.com/terryum/awesome-deep-learning-papers">awesome-deep-learning-papers</a></li>
</ul>]]></content><author><name>Oleg Larkin</name></author><category term="habr" /><category term="habr" /><category term="neuro" /><summary type="html"><![CDATA[Что делать, если хочется побольше узнать про нейронные сети, методы распознавания образов, компьютерное зрение и глубокое обучение? Один из очевидных вариантов — подыскать для себя какие-либо курсы и начать активно изучать теорию и решать практические задачи. Однако на это придется выделить значительную часть личного времени. Есть другой способ — обратиться к «пассивному» источнику знаний: выбрать для себя литературу и погрузиться в тему, уделяя этому всего полчаса-час в день. Поэтому, желая облегчить жизнь себе и читателям, мы сделали краткую подборку из книг, статей и текстов по направлению нейросетей и глубокого обучения, рекомендуемых к прочтению резидентами GitHub, Quora, Reddit и других платформ. В неё вошли материалы как для тех, кто только начинает знакомство с нейротехнологиями, так и для коллег, желающих расширить свои знания в этой области или просто подобрать «легкое чтение» на вечер. / Flickr / Giuseppe Milo / CC]]></summary></entry><entry><title type="html">Новогодние праздники в Москве 2026</title><link href="http://localhost:4000/life/2026/01/04/moscow.html" rel="alternate" type="text/html" title="Новогодние праздники в Москве 2026" /><published>2026-01-04T00:00:00+03:00</published><updated>2026-01-04T00:00:00+03:00</updated><id>http://localhost:4000/life/2026/01/04/moscow</id><content type="html" xml:base="http://localhost:4000/life/2026/01/04/moscow.html"><![CDATA[<h2 id="просто-рандомные-фото-города">Просто рандомные фото города</h2>

<p><img src="/assets/images/life/moscow/2026-01-11 12.03.06.jpg" alt="" />
<!--more-->
<img src="/assets/images/life/moscow/IMG20250108183324.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104135659.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104152629.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104153844.jpg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.31.jpeg" alt="Вид из окна. Киберпанк который у нас уже есть" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.34.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.36.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.40.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.42.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.46.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.48.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.57.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.03.59.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.00.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.02.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.04.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.05.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.07.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.08.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.10.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.13.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.17.jpeg" alt="" /></p>

<h2 id="московский-зоопарк">Московский зоопарк</h2>

<p><img src="/assets/images/life/moscow/IMG20260104141225.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104152530.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104141539.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104141746.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104141820.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104143002_03.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104143605.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104144036.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104145102.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104145428.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104145835_01.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104150102.jpg" alt="" />
<img src="/assets/images/life/moscow/IMG20260104151844.jpg" alt="" /></p>

<h2 id="кремлевская-ёлка">Кремлевская ёлка</h2>

<p><img src="/assets/images/life/moscow/IMG20260105112914.jpg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.19.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.21.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.22.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.23.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.25.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.27.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.28.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.30.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.32.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.34.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.36.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.37.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.39.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.40.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.43.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.44.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.46.jpeg" alt="" />
<img src="/assets/images/life/moscow/photo_2026-01-11 12.04.48.jpeg" alt="" /></p>]]></content><author><name>Oleg Larkin</name></author><category term="life" /><summary type="html"><![CDATA[Просто рандомные фото города]]></summary></entry><entry><title type="html">Собираем Linux, который весит меньше, чем мем с котиком, ну или почти…</title><link href="http://localhost:4000/habr/2025/12/03/build-micro-linux.html" rel="alternate" type="text/html" title="Собираем Linux, который весит меньше, чем мем с котиком, ну или почти…" /><published>2025-12-03T00:00:00+03:00</published><updated>2025-12-03T00:00:00+03:00</updated><id>http://localhost:4000/habr/2025/12/03/build-micro-linux</id><content type="html" xml:base="http://localhost:4000/habr/2025/12/03/build-micro-linux.html"><![CDATA[<p>Мне всегда было интересно, насколько минимальным может быть Linux.</p>

<p>Три года назад я уже публиковал <a href="https://habr.com/ru/companies/ruvds/articles/702570/">статью</a>, но понимание темы меняется. Сейчас хочу освежить знания и поделиться ими.</p>

<p>В отличие от предыдущей статьи, в этой я рассматриваю Linux с более свежим ядром с минимальной конфигурацией на основе tinyconfig, оформленный в виде одного файла и загружаемый при помощи UEFI.</p>

<p>Зачем вообще нужно создавать такие сборки Linux?</p>

<p>Установил готовый дистрибутив или загрузился в Live — работай и изучай. Но такой подход не даёт настоящей уверенности в знаниях. Представьте: водитель и шофёр. Водитель просто ездит. Шофёр знает устройство автомобиля и может его починить.</p>

<p>Современные автомобили сложны — починить всё самому уже нельзя. Но вы можете открыть капот, измерить уровень масла, заменить колесо в случае прокола, установить винт для буксировочного троса и т. д. Так же и с Linux: коммерческий дистрибутив вы вряд ли соберёте, но на одну ступеньку вверх по лестнице профессионализма подниметесь.</p>

<p>На мой взгляд, описание создания минимального Linux не должно быть большим, поэтому я старался его сделать минимально возможным, вложив максимум смысла.</p>

<p>Как минимальный Linux служит для понимания более сложных вещей, так и моя статья даёт базу для дальнейшего более глубокого изучения.</p>

<p>В комментариях к моей предыдущей статье я видел замечания, что можно использовать <a href="https://buildroot.org/">buildroot</a> или книгу <a href="https://www.linuxfromscratch.org/">Linux From Scratch</a>. Скажу только, что я преследую другие цели: собрать минимальный Linux без использования специализированных программ и сделать это как можно проще.</p>

<p>Всем, кого заинтересовало, добро пожаловать под кат.</p>

<p>Действия, описанные в статье, также приведены в виде небольшого bash-скрипта в <a href="https://github.com/artyomsoft/minimal-uefi-linux">моём репозитории на GitHub</a>.</p>

<h3 id="архитектура">Архитектура</h3>

<p>Начнём с того, что необходимо для минимального Linux.</p>

<p>Сначала нам нужно определиться с <em>платформой</em>, на которой нужно запустить Linux. <em>Платформа</em> — это набор программно-аппаратных средств, на которых запускается Linux. Программный код ядра Linux общий для всех платформ, но часть кода является платформенно-зависимым.</p>

<p>Нам нужно:</p>

<ul>
  <li>
    <p><em>ядро</em>, которое содержит только самое необходимое;</p>
  </li>
  <li>
    <p><em>корневая файловая система</em>;</p>
  </li>
  <li>
    <p>минимальный набор пользовательских программ и библиотек в корневой файловой системе;</p>
  </li>
  <li>
    <p>нечто, что может запустить само ядро на платформе (загрузчик).</p>
  </li>
</ul>

<h4 id="платформа">Платформа</h4>

<p>Существует множество платформ, на которых можно запустить Linux. Мы выбираем ту, которая сейчас есть практически у всех — <em>UEFI x86_64</em>. Это ноутбук или стационарный компьютер, где в качестве Firmware установлен UEFI. Скорее это не платформа, а семейство платформ, так как компьютеры и ноутбуки отличаются аппаратной начинкой. В нашем случае это не имеет значения, так как мы будем использовать минимальные возможности. Для тестирования Linux удобно использовать <em>QEMU</em>. <em>QEMU</em> — это система виртуализации, поддерживающая эмуляцию множества платформ, которая и сама может также рассматриваться как платформа.</p>

<h4 id="ядро">Ядро</h4>

<p><em>Ядро</em> — это файл в простейшем случае или набор файлов в более сложных случаях. Мы создаём минимальный Linux, поэтому я рассматриваю только первый случай. Как я говорил ранее, бо́льшая часть исходного кода ядра Linux одинаковая для всех платформ, но бинарный код ядра будет отличаться из-за <em>архитектуры системы команд</em> (ISA) выбранной платформы, аппаратных средств выбранной платформы и специфики загрузки ядра на выбранной платформе.</p>

<h4 id="корневая-файловая-система">Корневая файловая система</h4>

<p><em>Корневая файловая система</em> — это структура данных, где находится набор пользовательских программ, файлы устройств, другие служебные файловые системы, используемые ядром. Корневая файловая система служит связующим звеном между пространством ядра и пространством пользователя.</p>

<h4 id="набор-пользовательских-программ-и-библиотек">Набор пользовательских программ и библиотек</h4>

<p>Другим звеном являются <em>системные вызовы</em> — особые функции, исполняемые в <em>пространстве ядра</em>, которые можно вызывать из <em>пространства пользователя</em>.</p>

<p>Обычно программисты вызывают не их, а обёртки для них, которые находятся в <em>стандартной библиотеке (libc)</em>. В настоящее время наибольшее распространение получили две: <em>glibc</em> и <em>musl</em>. Первая, как правило, используется для десктопных и серверных систем, вторая — для встраиваемых и минималистичных (например, Alpine Linux — минималистичная Linux, предназначенная для запуска в Docker-контейнерах). Кроме стандартной библиотеки, существуют и другие библиотеки, предназначенные для решения специфичных задач.</p>

<p>Приложения могут:</p>

<ul>
  <li>
    <p>включать <em>объектный код</em> используемых функций (<em>статическая линковка</em>) в себя;</p>
  </li>
  <li>
    <p>хранить только ссылку на используемые библиотеки и использовать совместно с другими приложениями (<em>разделяемые библиотеки</em>, <em>динамическая линковка</em>).</p>
  </li>
</ul>

<p>С целью упрощения мы будем использовать статическую линковку.</p>

<p>Набор пользовательских приложений для Linux громаден. При общем изучении Linux используют стандартные приложения, которые являются аналогами команд из операционной системы Unix.</p>

<p>Это пакеты программ: <em>coreutils</em>, <em>net-tools</em>, <em>util-linux</em>, <em>procps</em>, и др. Но для минималистичных Linux существует проект <em>BusyBox</em>, который содержит большинство аналогов программ из ранее перечисленных пакетов.</p>

<p>Из- за того, что он минималистичный, некоторые команды являются упрощёнными и поддерживают не все опции. Особенностью BusyBox является то, что для всех команд используется один и тот же исполняемый файл. А его имя или имя ссылки на него определяют конкретную команду, которую мы хотим запустить.</p>

<h4 id="система-инициализации">Система инициализации</h4>

<p>Для понимания процесса загрузки Linux важно понимание роли <em>системы инициализации</em>. При загрузке Linux наступает момент, когда ядро готово запустить первый процесс в пользовательском пространстве.</p>

<p>Имя файла, с которого запускается этот процесс, имеет имя (<em>init</em>), а процесс — особый идентификатор (<em>PID</em> равный 1). Файл init должен располагаться по определённому пути, где его сможет найти ядро.</p>

<p>Теоретически <code class="language-plaintext highlighter-rouge">init</code> может делать что угодно, но основная его задача — запустить <em>фоновые процессы</em> в пространстве пользователя и проинициализировать другие компоненты Linux. Такой init-процесс называется <em>системой инициализации</em>. Для Linux разработаны несколько таких систем:</p>

<ul>
  <li>
    <p>System V init — классическая UNIX-подобная система инициализации;</p>
  </li>
  <li>
    <p>systemd — современная, широко используемая система инициализации;</p>
  </li>
  <li>
    <p>OpenRC — альтернативная система инициализации, основанная на скриптах;</p>
  </li>
  <li>
    <p>BusyBox init — минималистичная система инициализации для встраиваемых систем.</p>
  </li>
</ul>

<p>Но мы создадим свою и уместим её в bash-скрипт.</p>

<h4 id="корневая-файловая-система-и-initramfs">Корневая файловая система и initramfs</h4>

<p>Загрузка Linux возможна как с использованием <em>промежуточной корневой файловой системы</em>, служащей для первоначальной инициализации пространства пользователя так и без неё. Промежуточная корневая файловая система называется <em>initramfs</em>. В этом случае необходимо при загрузке Linux указать как имя файла образа ядра, так и имя файла образа initramfs. Физически файл образа initramfs — это сжатый или несжатый архив формата <em>cpio</em>.</p>

<p>Возможен вариант, когда образ initramfs внедряется в файл образа ядра, мы воспользуемся этой возможностью для упрощения и получения wow-эффекта.</p>

<p>Программы и модули, находящиеся в initramfs, служат для того, чтобы подготовить ядро к переключению на постоянную корневую файловую систему. То есть если в файл ядра включить необходимые модули, то возможно монтирование корневой файловой системы и запуск init-процесса без использования initramfs, но в случае минимальной конфигурации Linux это усложняет подготовку загрузочного образа, так как нужно:</p>

<ul>
  <li>
    <p>разметить диск;</p>
  </li>
  <li>
    <p>записать в один из разделов образ корневой файловой системы;</p>
  </li>
  <li>
    <p>добавить загрузчик операционной системы, в случае традиционной загрузки при помощи GRUB;</p>
  </li>
  <li>
    <p>добавить и отформатировать <em>ESP-раздел</em> в случае непосредственной загрузки при помощи UEFI.</p>
  </li>
</ul>

<p>Поэтому мы разместим корневую файловую систему в файле образа initramfs, а файл образа initramfs включим файл образа ядра Linux.</p>

<p>Чтобы использовать fallback-загрузку UEFI и загрузить Linux с флеш-диска:</p>

<ul>
  <li>
    <p>отформатируем флеш-диск файловой системой <em>FAT32</em>,</p>
  </li>
  <li>
    <p>переименуем файл образа ядра Linux в <code class="language-plaintext highlighter-rouge">BOOTX64.EFI</code>,</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">BOOTX64.EFI</code> поместим в директорию <code class="language-plaintext highlighter-rouge">/EFI/BOOT</code>.</p>
  </li>
</ul>

<h4 id="загрузчик">Загрузчик</h4>

<p><em>Загрузчик</em> играет важную роль в процессе запуска Linux. Его можно представить как некоторую программу, которая способна запустить ядро Linux. Ядру, как и обычной программе, запущенной из командной строки, можно передавать <em>параметры командной строки</em>. Требования к тому, как загрузчик передаёт параметры и управление ядру называются <em>протокол загрузки (Boot Protocol)</em>. Протоколы загрузки отличаются для разных платформ, некоторые платформы поддерживают несколько протоколов загрузки.</p>

<p>Мы будем использовать протокол загрузки EFI. Суть его заключается в следующем:</p>

<p>Для загрузки используются возможности UEFI. В этом случае файл ядра является валидным UEFI-приложением. В самом файле ядра есть небольшой код, который подготавливает ядро к вызову функции <code class="language-plaintext highlighter-rouge">start_kernel</code>. Этот код называется <em>EFIStub</em>.</p>

<p>UEFI поддерживает так называемые <em>Boot Entries</em> — записи в <em>NVRAM</em> о том, какие ядра доступны, на каких разделах они располагаются, какую командную строку им нужно передавать. Но эту возможность при использовании сменных носителей не используют.</p>

<p>Также существует возможность поместить <em>Linux Command Line</em> в файл образа ядра. В целях упрощения мы также использовать не будем eё использовать.</p>

<h4 id="командная-строка-ядра-linux">Командная строка ядра Linux</h4>

<p>Хотя в полученном Linux мы не будем использовать командную строку ядра Linux, я приведу о ней краткую информацию. В командной строке передаются такие значения как:</p>

<ul>
  <li>
    <p>кастомный путь и имя файла для процесса init;</p>
  </li>
  <li>
    <p>блочное устройство (раздел на диске), которое содержит корневую файловую систему;</p>
  </li>
  <li>
    <p>какое устройство будет использоваться в качестве консоли при загрузке.</p>
  </li>
</ul>

<p>Кроме того, через командную строку передаются параметры программам, запускающиеся с initramfs. Поэтому от дистрибутива к дистрибутиву список параметров, которые можно передать в качестве параметров ядра, различается.</p>

<p>Какая командная строка была передана ядру, можно посмотреть следующим образом:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /proc/cmdline
</code></pre></div></div>

<h3 id="сборка">Сборка</h3>

<p><em>Сборка операционной системы Linux</em> подразумевает сборку ядра, сборку пользовательских программ, создание и наполнение корневой файловой системы.</p>

<p>Обычно для этой цели используются специализированные средства, но так как статья сфокусирована на простоте, я рассматриваю самый простой случай, когда вы сможете собрать Linux на коленке.</p>

<p>Для сборки нам понадобятся:</p>

<ul>
  <li>
    <p>Docker;</p>
  </li>
  <li>
    <p>Docker-образ Linux-дистрибутива;</p>
  </li>
  <li>
    <p>пакеты, которые содержат программы, позволяющие собрать ядро и пользовательские программы;</p>
  </li>
  <li>
    <p>исходники ванильного ядра Linux c сайта <em>kernel.org</em>;</p>
  </li>
  <li>
    <p>исходники BusyBox;</p>
  </li>
  <li>
    <p>немного терпения и умение работать с командной строкой Linux.</p>
  </li>
</ul>

<p>Кросс-компиляцию, а также создание Linux-системы с initramfs и постоянной корневой файловой системой я рассматривать не буду.</p>

<p>Порядок изложения выбран так, чтобы шаги сборки шли в нужной последовательности — это позволяет удобно выполнять команды по ходу текста. Поэтому структура немного отличается от той, что была бы при чисто теоретическом описании.</p>

<h4 id="создание-окружения-для-сборки">Создание окружения для сборки</h4>

<ol>
  <li>
    <p>Скачать и установить <a href="https://www.docker.com/">Docker</a>, если он у вас не установлен.</p>
  </li>
  <li>
    <p>Запустить Docker-контейнер в окне терминала:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it --rm -v "%cd%":/result debian:bookworm bash
</code></pre></div>    </div>
  </li>
  <li>
    <p>Установить пакеты:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># apt update &amp;&amp; apt install -y apt-file &amp;&amp; apt-file update
# apt install -y build-essential wget xz-utils cpio flex bison bc \
file tree qemu-system  ncurses-dev libelf-dev libssl-dev
</code></pre></div>    </div>
  </li>
  <li>
    <p>Создать директорию для сборки</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># mkdir build &amp;&amp; cd build
</code></pre></div>    </div>
  </li>
</ol>

<h4 id="сборка-пользовательских-программ">Сборка пользовательских программ</h4>

<p>В нашей системе будет только одна пользовательская программа — BusyBox. Исходный код BusyBox включает встроенный конфигуратор, по своему принципу похожий на те, что используются в ядре Linux или в Buildroot — среде, предназначенной для сборки кастомных дистрибутивов из исходного кода.</p>

<ol>
  <li>
    <p>Скачиваем исходный код BusyBox:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2
# tar xvf busybox-1.37.0.tar.bz2
# cd busybox-1.37.0
</code></pre></div>    </div>
  </li>
  <li>
    <p>Создаём конфигурацию по умолчанию:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># make defconfig
</code></pre></div>    </div>
  </li>
  <li>
    <p>Выполняем сборку:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># make CONFIG_STATIC=y
</code></pre></div>    </div>
  </li>
  <li>
    <p>Убеждаемся, что BusyBox был собран со статической линковкой:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># file busybox
busybox: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=6540c48477b6eae56a14377ae0de4d0229ea75dc, for GNU/Linux 3.2.0, stripped
</code></pre></div>    </div>
  </li>
</ol>

<h4 id="создание-корневой-файловой-системы">Создание корневой файловой системы</h4>

<p>Наша Linux-система не будет иметь полноценную корневую файловую систему. Вместо этого мы будем использовать только initramfs-образ.</p>

<p>Initramfs — это архив в формате cpio, который содержит каталоги, файлы пользовательских программ, файлы устройств и конфигурационные файлы.</p>

<p>Чтобы сэкономить место, его можно сжать с помощью стандартного архиватора.</p>

<ol>
  <li>
    <p>Создаём структуру корневой файловой системы:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># mkdir -p ../rootfs/{bin,sbin,etc,proc,sys,usr/{bin,sbin},dev,run,tmp,var}
</code></pre></div>    </div>
  </li>
  <li>
    <p>Выполняем установку BusyBox в директорию с корневой файловой системой:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># make CONFIG_STATIC=y CONFIG_PREFIX=../rootfs install
</code></pre></div>    </div>
  </li>
  <li>
    <p>Добавляем скрипт для системы инициализации:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &lt;&lt; EOF &gt; ../rootfs/init
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs tmpfs /run
mount -t tmpfs tmpfs /tmp
mdev -s
if [ -c /dev/fb0 ]; then
  TTY=/dev/tty1
else
  TTY=/dev/ttyS0
fi
echo 0 &gt; /proc/sys/kernel/printk
printf "\033c" &gt; \$TTY
cat &lt;&lt; INNER &gt; \$TTY
                           /)
                  /\___/\ ((
                  \ @_@'/  ))
                  {_:Y:.}_//
================{_}^-'{_}====================
~                                           ~
~    Welcome to ArtyomSoft Minimal Linux    ~
~                                           ~
=============================================

TTY: \$(basename \$TTY)
Time: \$(date)
Kernel version: \$(uname -r)
=============================================
INNER

exec setsid sh -c "exec sh &lt;'\$TTY' &gt;'\$TTY' 2&gt;'\$TTY'"

EOF
</code></pre></div>    </div>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># chmod +x ../rootfs/init
</code></pre></div>    </div>
  </li>
  <li>
    <p>Создаём initramfs-образ с корневой файловой системой:</p>
  </li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cd ../rootfs
# find . | cpio -o -H newc | gzip &gt; ../initramfs.cpio.gz
</code></pre></div></div>

<h4 id="сборка-ядра">Сборка ядра</h4>

<p>При сборке ядра важно его правильно сконфигурировать. Для самого минимального ядра используется конфигурация <code class="language-plaintext highlighter-rouge">tinyconfig</code>. Если вы выберите эту конфигурацию, ядро запустится, но не увидите работу такого ядра, так как оно практически никак не взаимодействует с пользовательским пространством и устройствами. Поэтому нужно добавить некоторое количество опций.</p>

<p>Несмотря на то, что определение опций — достаточно сложное занятие, я нахожу его увлекательным. Сильно упрощает работу по определению нужных опций использование <code class="language-plaintext highlighter-rouge">make menuconfig</code> или <code class="language-plaintext highlighter-rouge">make nconfig</code>. В первом приложении после запуска необходимо нажать <code class="language-plaintext highlighter-rouge">/</code>, во втором — <code class="language-plaintext highlighter-rouge">F8</code> и в появившееся окно ввести часть названия опции, которую вы хотите добавить. После ввода появится окно найденных параметров с описанием назначения, зависимостями и указанием того, где они располагаются в меню.</p>

<p><img src="%D0%A1%D0%BE%D0%B1%D0%B8%D1%80%D0%B0%D0%B5%D0%BC%20Linux,%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B8%CC%86%20%D0%B2%D0%B5%D1%81%D0%B8%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5,%20%D1%87%D0%B5%D0%BC%20%D0%BC%D0%B5%D0%BC%20%D1%81%20%D0%BA%D0%BE%D1%82%D0%B8%D0%BA%D0%BE%D0%BC,%20%D0%BD%D1%83%20%D0%B8%D0%BB%D0%B8%20%D0%BF%D0%BE%D1%87%D1%82%D0%B8%E2%80%A6%20_%20%D0%A5%D0%B0%D0%B1%D1%80_files/iu_xm_onnnhytvbo12isaftobzk.png" alt="Окно поиска опций ядра menuconfig" /></p>

<p>Окно поиска опций ядра menuconfig</p>

<p><img src="%D0%A1%D0%BE%D0%B1%D0%B8%D1%80%D0%B0%D0%B5%D0%BC%20Linux,%20%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D1%8B%D0%B8%CC%86%20%D0%B2%D0%B5%D1%81%D0%B8%D1%82%20%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B5,%20%D1%87%D0%B5%D0%BC%20%D0%BC%D0%B5%D0%BC%20%D1%81%20%D0%BA%D0%BE%D1%82%D0%B8%D0%BA%D0%BE%D0%BC,%20%D0%BD%D1%83%20%D0%B8%D0%BB%D0%B8%20%D0%BF%D0%BE%D1%87%D1%82%D0%B8%E2%80%A6%20_%20%D0%A5%D0%B0%D0%B1%D1%80_files/qv_lqjz_da1cucp1hijo4mur__u.png" alt="Окно с описанием опций ядра menuconfig" /></p>

<p>Окно с описанием опций ядра menuconfig</p>

<p>Опции можно выбирать непосредственно в интерфейсе приложения, а можно, используя скрипт ‘./scripts/config’. Помните, что если вы выбрали опцию, зависящую от опции, которая не включена у вас в конфигурации, то опция будет молчаливо проигнорирована при сборке.</p>

<blockquote>
  <p>Если вы добавили какую-то опцию и попытаетесь собрать ядро, часто сборщик ядра у вас запросит дополнительные опции для конфигурирования. Можно просто множество раз нажимать клавишу <code class="language-plaintext highlighter-rouge">Enter</code>, а можно просто перед запуском сборки ввести команду:</p>

  <p><code class="language-plaintext highlighter-rouge">$ make olddefconfig</code></p>

  <p>и недоконфигурированные опции примут значения по умолчанию.</p>
</blockquote>

<p>Ниже я привожу, какие параметры я добавил и зачем.</p>

<table>
  <thead>
    <tr>
      <th>Параметр</th>
      <th>Назначение</th>
      <th>Комментарий / зачем включать</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>64BIT</strong></td>
      <td>Поддержка 64-битной архитектуры (x86_64).</td>
      <td>Необходимо для сборки 64-битного ядра.</td>
    </tr>
    <tr>
      <td><strong>PCI</strong></td>
      <td>Поддержка шины PCI.</td>
      <td>Позволяет обнаруживать и использовать устройства PCI.</td>
    </tr>
    <tr>
      <td><strong>PCI_HOST_GENERIC</strong></td>
      <td>Универсальный драйвер PCI-хоста.</td>
      <td>Минимальный вариант для QEMU и виртуальных машин.</td>
    </tr>
    <tr>
      <td><strong>TTY</strong></td>
      <td>Поддержка терминалов (tty).</td>
      <td>Без этого не будет консоли и ввода/вывода.</td>
    </tr>
    <tr>
      <td><strong>BLK_DEV_INITRD</strong></td>
      <td>Поддержка initrd/initramfs как блочного устройства.</td>
      <td>Требуется для загрузки с initramfs.</td>
    </tr>
    <tr>
      <td><strong>SERIAL_8250</strong></td>
      <td>Драйвер UART 8250/16550.</td>
      <td>Нужен для вывода через COM-порт.</td>
    </tr>
    <tr>
      <td><strong>SERIAL_8250_CONSOLE</strong></td>
      <td>Использование последовательного порта как консоли.</td>
      <td>Позволяет видеть вывод ядра через UART.</td>
    </tr>
    <tr>
      <td><strong>BINFMT_ELF</strong></td>
      <td>Поддержка ELF-бинарников.</td>
      <td>Формат исполняемых файлов Linux.</td>
    </tr>
    <tr>
      <td><strong>BINFMT_MISC</strong></td>
      <td>Поддержка нестандартных бинарных форматов.</td>
      <td>Например, для интерпретаторов (Python, Wine и др.).</td>
    </tr>
    <tr>
      <td><strong>BINFMT_SCRIPT</strong></td>
      <td>Запуск скриптов с <code class="language-plaintext highlighter-rouge">#!</code>.</td>
      <td>Нужен для shell-скриптов и init.</td>
    </tr>
    <tr>
      <td><strong>DEVTMPFS</strong></td>
      <td>Автоматическое создание <code class="language-plaintext highlighter-rouge">/dev</code>.</td>
      <td>Cлужит для размещения файлов устройств.</td>
    </tr>
    <tr>
      <td><strong>DEVTMPFS_MOUNT</strong></td>
      <td>Автоматическое монтирование <code class="language-plaintext highlighter-rouge">/dev</code>.</td>
      <td>Упрощает запуск init.</td>
    </tr>
    <tr>
      <td><strong>TMPFS</strong></td>
      <td>Временная файловая система в RAM.</td>
      <td>Используется для <code class="language-plaintext highlighter-rouge">/tmp</code>, <code class="language-plaintext highlighter-rouge">/run</code>.</td>
    </tr>
    <tr>
      <td><strong>PRINTK</strong></td>
      <td>Вывод диагностических сообщений ядра.</td>
      <td>Нужен для логов и отладки.</td>
    </tr>
    <tr>
      <td><strong>EARLY_PRINTK</strong></td>
      <td>Ранний вывод сообщений ядра.</td>
      <td>Показывает ошибки до инициализации консоли.</td>
    </tr>
    <tr>
      <td><strong>PRINTK_TIME</strong></td>
      <td>Метки времени в <code class="language-plaintext highlighter-rouge">printk</code>.</td>
      <td>Удобно для отладки и анализа.</td>
    </tr>
    <tr>
      <td><strong>PROC_FS</strong></td>
      <td>Поддержка <code class="language-plaintext highlighter-rouge">/proc</code>.</td>
      <td>Требуется для многих утилит (<code class="language-plaintext highlighter-rouge">ps</code>, <code class="language-plaintext highlighter-rouge">top</code>).</td>
    </tr>
    <tr>
      <td><strong>SYSFS</strong></td>
      <td>Поддержка <code class="language-plaintext highlighter-rouge">/sys</code>.</td>
      <td>Используется системными инструментами.</td>
    </tr>
    <tr>
      <td><strong>INITRAMFS_SOURCE</strong></td>
      <td>Путь к initramfs (например <code class="language-plaintext highlighter-rouge">"../initramfs.cpio.gz"</code>).</td>
      <td>Позволяет встроить initramfs в ядро.</td>
    </tr>
    <tr>
      <td><strong>RD_GZIP</strong></td>
      <td>Поддержка gzip-сжатых initrd/initramfs.</td>
      <td>Нужна, если initramfs сжат gzip-ом.</td>
    </tr>
    <tr>
      <td><strong>INITRAMFS_COMPRESSION_GZIP</strong></td>
      <td>Сжатие встроенного initramfs при сборке.</td>
      <td>Экономит место.</td>
    </tr>
    <tr>
      <td><strong>ACPI</strong></td>
      <td>Поддержка ACPI (управление питанием и устройствами).</td>
      <td>Важно для ПК и ноутбуков, не нужно в простых VM.</td>
    </tr>
    <tr>
      <td><strong>EFI</strong></td>
      <td>Поддержка загрузки через UEFI.</td>
      <td>Обязательно, если система использует UEFI.</td>
    </tr>
    <tr>
      <td><strong>EFI_STUB</strong></td>
      <td>Позволяет ядру быть EFI-приложением.</td>
      <td>Для прямой загрузки ядра без GRUB.</td>
    </tr>
    <tr>
      <td><strong>FB</strong></td>
      <td>Поддержка framebuffer-графики.</td>
      <td>Для вывода на экран без X11.</td>
    </tr>
    <tr>
      <td><strong>FB_EFI</strong></td>
      <td>Использование EFI framebuffer.</td>
      <td>Для вывода на экран в UEFI.</td>
    </tr>
    <tr>
      <td><strong>FRAMEBUFFER_CONSOLE</strong></td>
      <td>Консоль на framebuffer.</td>
      <td>Позволяет видеть текст на графическом экране.</td>
    </tr>
  </tbody>
</table>

<ol>
  <li>
    <p>Скачиваем исходный код ядра Linux:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cd ..
# wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.56.tar.xz
# tar xvf linux-6.12.56.tar.xz
# cd linux-6.12.56
</code></pre></div>    </div>
  </li>
  <li>
    <p>Создаём минимально возможную конфигурацию tinyconfig:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># make tinyconfig
</code></pre></div>    </div>
  </li>
  <li>
    <p>Устанавливаем нужные опции для компиляции ядра:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ./scripts/config \
-e 64BIT \
-e PCI \
-e PCI_HOST_GENERIC \
-e TTY \
-e BLK_DEV_INITRD \
-e SERIAL_8250 \
-e SERIAL_8250_CONSOLE \
-e BINFMT_ELF \
-e BINFMT_MISC \
-e BINFMT_SCRIPT \
-e DEVTMPFS \
-e DEVTMPFS_MOUNT \
-e TMPFS \
-e PRINTK \
-e EARLY_PRINTK \
-e PRINTK_TIME\
-e PROC_FS \
-e SYSFS \
--set-str INITRAMFS_SOURCE "../initramfs.cpio.gz" \
-e RD_GZIP \
-e INITRAMFS_COMPRESSION_GZIP \
-e ACPI \
-e EFI \
-e EFI_STUB \
-e FB \
-e FB_EFI \
-e FRAMEBUFFER_CONSOLE
</code></pre></div>    </div>
  </li>
  <li>
    <p>Собираем ядро:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># make olddefconfig
# make -j$(nproc)
</code></pre></div>    </div>
  </li>
  <li>
    <p>Копируем файл ядра в директорию:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># mkdir -p /result/ESP/EFI/BOOT
# cp arch/x86/boot/bzImage /result/ESP/EFI/BOOT/BOOTX64.EFI
</code></pre></div>    </div>
  </li>
  <li>
    <p>Добавляем EFI Firmware для QEMU</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cp /usr/share/OVMF/OVMF_VARS.fd /result/
# cp /usr/share/OVMF/OVMF_CODE.fd /result/
</code></pre></div>    </div>
  </li>
  <li>
    <p>Запускаем наш Linux в QEMU:</p>

    <p>В Docker:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># qemu-system-x86_64 -m 4096m -kernel /result/ESP/EFI/BOOT/BOOTX64.EFI -append "console=ttyS0" -nographic
</code></pre></div>    </div>

    <p>В Windows, чтобы проверить, как всё будет выглядеть при запуске на реальном железе:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qemu-system-x86_64 -m 4096m -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.fd -drive if=pflash,format=raw,readonly=on,file=./OVMF_VARS.fd -drive format=raw,file=fat:rw:./esp
</code></pre></div>    </div>
  </li>
  <li>
    <p>Для выхода из QEMU нажимае <code class="language-plaintext highlighter-rouge">Ctlr+A</code> <code class="language-plaintext highlighter-rouge">X</code></p>
  </li>
</ol>

<h4 id="запуск-на-компьютере">Запуск на компьютере</h4>

<p>Проверить работу собранного Linux вы можете не только в QEMU, но и на компьютере.</p>

<ol>
  <li>
    <p>Записать файл с ядром на флеш-диск.</p>
  </li>
  <li>
    <p>Инициировать перезагрузку и зайти в UEFI/BIOS Setup Utility.</p>
  </li>
  <li>
    <p>Убедиться, что у вас в UEFI отключён Secure Boot.</p>
  </li>
  <li>
    <p>Выбрать флеш-диск с файлом ядра в качестве загрузочного устройства.</p>
  </li>
  <li>
    <p>Выполнить загрузку с флеш-диска.</p>
  </li>
  <li>
    <p>Поэкспериметировать с командами Linux.</p>
  </li>
  <li>
    <p>Выключить компьютер.</p>
  </li>
</ol>

<p>Как выглядит работа с собранным Linux на реально железе, показано на видео ниже:</p>

<h3 id="выводы">Выводы</h3>

<p>Мы создали почти минимально возможный Linux для UEFI-систем x86_64.</p>

<p>Размер можно уменьшить ещё немного, если использовать musl в BusyBox и отключить некоторые опции при сборке. Но чтобы не усложнять материал, я этого не делал. Думаю, Linux размером около 2,5 МБ, в котором можно работать, когда современные системы занимают гигабайты, сам по себе впечатляет.</p>

<p>В статье я рассмотрел лишь малую часть того, что обычно присутствует в дистрибутиве, но при этом построил рабочую систему, на которой уже можно изучать базовые команды Linux. Она занимает очень мало места, быстро загружается и не требует установки.</p>

<p>В системе нет:</p>

<ul>
  <li>
    <p>поддержки дисковых файловых систем;</p>
  </li>
  <li>
    <p>полноценной поддержки USB;</p>
  </li>
  <li>
    <p>Ethernet;</p>
  </li>
  <li>
    <p>Wi-Fi;</p>
  </li>
  <li>
    <p>TCP/IP и сетевых интерфейсов;</p>
  </li>
  <li>
    <p>аппаратного ускорения графики;</p>
  </li>
  <li>
    <p>пользовательских программ, кроме одного статически слинкованного BusyBox;</p>
  </li>
  <li>
    <p>разделяемых библиотек;</p>
  </li>
  <li>
    <p>полноценной системы инициализации;</p>
  </li>
  <li>
    <p>полноценной корневой файловой системы;</p>
  </li>
  <li>
    <p>модулей ядра;</p>
  </li>
  <li>
    <p>подсистемы логирования;</p>
  </li>
  <li>
    <p>подсистемы udev;</p>
  </li>
  <li>
    <p>многопользовательского режима;</p>
  </li>
  <li>
    <p>графического интерфейса.</p>
  </li>
</ul>

<p>Тем не менее статья даёт понимание базового фундамента, который делает изучение более сложных аспектов Linux значительно проще.</p>

<p>Статья должна быть полезной, если вы только начинаете изучать Embedded-разработку: вы лучше поймёте, зачем создавали <a href="https://buildroot.org/">buildroot</a> и <a href="https://www.yoctoproject.org/">Yocto</a>.</p>

<p>После прочтения статьи принципы построения Unified Kernel Image (UKI) и основы systemd становятся понятнее, так же как и инструменты, создающие initramfs, — такие как Dracut или mkinitramfs.</p>

<p>Одним из минусов созданного Linux является невозможность сохранить результаты работы: всё хранится в RAM и исчезает после перезагрузки. Но это уже тема для отдельной статьи, если вам будет интересно.</p>]]></content><author><name>Oleg Larkin</name></author><category term="habr" /><summary type="html"><![CDATA[Мне всегда было интересно, насколько минимальным может быть Linux.]]></summary></entry></feed>