Linux kernel development для самых маленьких
Любой программист знает, что теоретически он может внести свой посильный вклад в развитие Linux ядра. С другой стороны, подавляющее большинство уверено, что занимаются этим исключительно небожители, а процесс контрибьюта в ядро настолько сложен и запутан, что обычному человеку разобраться в нём нет никакой возможности. А значит, и надобности.
Сегодня мы попробуем развеять эту легенду и покажем, как абсолютно любой инженер при наличии достойной идеи, воплощённой в коде, может предложить ее на рассмотрение Linux community для включения в ядро.
0. Подготовка
Как и перед любой инженерной операцией, всё начинается с подготовки своего рабочего места. И первейшее здесь действие — это завести себе аккаунт с адекватным именем. В идеальном мире это будет просто транскрипция имени и фамилии. Если за учётку вроде MamkinC0d$r или Developer31337 в других местах пальцем в вас тыкать не будут, то правила LKC (Linux kernel community) такое прямо запрещают — инкогнито контрибьютить в ядро не принято.
Далее вам понадобится место на локальной машине. Сама папка Linux со скачанными исходниками весит чуть меньше 3-х гигов. Но если ядро пробовать собирать, то вместе с модулями займёт все 30 GB.
Захотелось собрать несколько веток? Умножаем 30 на число веток.
И помним — скорость сборки прямо связана с количеством доступных ядер! Больше ядер — быстрее соберётся. Так что не стесняйтесь выделять под это самую мощную машину.
1. Mail
Самый спорный и поэтому регулярно вызывающий споры момент — это канал коммуникации с LKC. Он безальтернативно один. Почта. Причём сообщения отправляются по классике через smtp.
Вокруг необходимости делать всё через плейнтекст в почте есть масса споров. Недавно в сети была очередная громкая статья на эту тему. Суть материала: письма — это, конечно, здорово, но пихать туда всё, включая куски кода — это вам (т.е. LKC) популярности не добавляет и даже наоборот, отпугивает новичков. С одной стороны вроде и да, если ты не можешь спокойно и структурировано изложить свои мысли в голом тексте, то в низкоуровневой разработке ловить будет особо нечего. С другой стороны, слать в письмах сорсы патчей — это даже архаизмом назвать уже сложно.
Но, как принято в уютном мирке ядра, Линус хлопнул кулаком по столу — и все пишут письма. Возможно, буквально завтра это изменится, но на момент выхода статьи это письма и только письма.
Какой email-client выбрать — есть рекомендации. Самым рекомендуемым почтовым агентом для LKC остаётся mutt. Да, тот самый текстовый почтовый клиент, от которого сводит олдскулы. Для начала mutt нужно поставить (я думаю, со своим пакетным менеджером вы и сами справитесь), а потом задать параметры в файле
Но почты недостаточно. Без Git никуда.
2. Git
Прежде чем что-то делать с исходниками ядра, нужно настроить Git. Можно конфигурировать файлы напрямую, но есть упрощающая жизнь утилита git config, через которую можно регулировать все аспекты работы Git’a.
Внутри есть три уровня настроек: общие для всех пользователей системы и для всех репозиториев (git config —system), общие для всех репозиториев конкретного пользователя (git config —global), отдельные для каждого репозитория (git config —local).
Глобальные настройки хранятся в /etc/gitconfig, настройки пользователя в
/.config/git/config, а настройки отдельных репозиториев хранятся в файле config в каталоге .git/config.
В общем случае будет достаточно законфигурировать файл для пользователя
/.gitconfig . Основная идея: при отправке коммитов должно отображаться ваше корректное имя.
Примечательно, что параметр sslVerify = true препятствует работе с Git напрямую через git://git.kernel.org и форсит доступ только через https://git.kernel.org . Так, наверное, секьюрнее, хотя какого-то смысла я в этом не вижу. Но, может, чего-то не знаю?
signOff обязателен, чтоб в коммитах была информация об авторе. По идее, надо бы, чтобы коммиты подписывались. Была тут недавно статья на эту тему.
Отправка патча выполняется командой git send-email. У git send-email есть несколько параметров с участием smtp, которые можно (и нужно) переопределить.
Полезный параметр —smtp-debug=1. Осуществляет логирование SMTP запросов и ответов, что помогает при разборе проблем с настройками почты. Например, я столкнулся с тем, что почтовый сервер, на котором есть у меня почтовый ящик, не поддерживает TLS. Возможны проблемы с аутентификацией, а сообщения об ошибке git send-email выдаёт не особо разнообразные и адекватные.
Можно задавать пароль к почте через параметр —smtp-pass=p4ssw0rd или вообще захардкорить в конфиге, но это это для тех, кому терять нечего. Но если каждый раз вводить пароль лень, то есть некий хак: если username был задан (через —smtp-user или sendmail.smtpUser), а пароль не указан, тогда пароль получается через git-credential.
Итак, окно в большой мир прорубили. Можно переходить к воплощению своей грандиозной идеи в коде.
3. Coding
Итак, мы готовы сделать первый шаг непосредственно в разработке — склонировать к себе репозиторий. Советую делать это сразу с указанием ветки, которая будет создана. Также отмечу, что работать лучше не над текущим состоянием в master, а над стабильной версией или кандидатом на релиз. Так вы будете более уверены, что ядро соберётся и будет как-то работать, не вызовет неожиданных конфликтов из-за изменений в других подсистемах. Поэтому всегда внимательно смотрите тэги.
И — опять же — имя своей ветке постарайтесь дать чёткое и лаконичное. В идеальном мире оно даже может отображать суть вносимых изменений. Если вы исправляете известный баг, то хорошим тоном считается включить в название ветки номер issue.
Операция довольно долгая, так что смело можно идти за кофе или на обед. А если попробовать ускорить процесс, отказавшись от истории, то работать с «этим» будет невозможно.
Итак, мы получили ветку, в которой можно начинать свою разработку. Здесь всё очевидно: пишем код, собираем, тестируем, исправляем баги — и так до получения нужного результата. О том, как собирать ядро и проводить отладку, информации в сети море, так что подробно описывать весь процесс я не буду. Лишь вкратце пробежимся по нему чуть позже. Единственный нюанс, который добавлю от себя прямо сейчас: перед сборкой проверьте наличие всех необходимых программ из этого списка и их минимальные версии.
Если бродить вслепую по гуглу не хочется, то вся максимально полезная информация по ядру сконцентрирована тут. Прочитать стоит действительно всё. Особенно правильно будет начать с How To о том, как правильно коммуницировать. Потому что мейнтейнеры, как правило, люди весьма занятые, и вникать в невнятно составленные письма им никакого интереса. Да и вам будет обидно, если из-за плохого описание ваше детище не примут в апстрим.
И вот свой небольшой и эффективный код вы написали, отладили, всё протестировали и готовы отправлять на рассмотрение. Но не спешите этого делать. Для начала обязательно проверьтесь на code style. В этом вам поможет ./script/checkpatch.pl . Для этого сделаем патч и отправим его на проверку.
После того, как пройдёт первое удивление и вы доустановите необходимые компоненты для python2 типа ply и git (который у меня так и не установился), наступит чудесное время исправления ошибок и ворнингов. По результатам которых вы а) поймёте, что красивый код писать вы не умеете б) потеряете всякое желание что-то куда-то отправлять. Ведь даже если отбросить все шутки, ещё можно как-то смириться с тем, что длина строк ограничена 100 символами (это начиная с версии 5.7, раньше так было вообще 80). Но вот такие места оставляют неизгладимое впечатление:
Для .h файлов строка с информацией о лицензии должна быть в ремарках / * */, а для *.c файлов должна быть в ремарках //. Такое запросто выбьет кого угодно из душевного равновесия. Вопрос: «Зачем?!» до сих пор болтается в моей голове, хотя есть вера в то, что это не просто ошибка в скриптах.
Кстати, чтобы просто проверить один файл достаточно вызвать
Можно прикрутить этот вызов к git, чтобы автоматически запускался этот скрипт при попытке что-то зачекинить.
Ещё важное замечание: clang-format добрался и до ядра Linux. Файл .clang-format расположился в корне ядра в кучке с другими конфигами. Но я не советую добавлять его в хук для git. Лучше всего корректно настроить среду разработки и запомнить code style. Лично мне не понравилось как он делает переносы длинных строк. Если вдруг строка оказалась длиннее допустимой, то скорее всего функция, в которой эта строка расположилась, является кандидатом на рефакторинг, и лучше его не откладывать. С другой стороны, если у вас много уже готового кода который нужно адаптировать для ядра — clang-format может сильно облегчить вам задачу.
4. Kernel build
Несмотря на то, что процесс описан в других статьях тут и тут, я все же повторюсь.
По шагам процесс сборки ядра довольно прост, если не вдаваться в детали. Для начала ставим необходимые пакеты (использовался Debian 10):
Это без компилятора и обычного для С/С++ разработчика набора программ.
Запускаем конфигурацию:
Тут есть интересный аспект: в качестве шаблона будет браться config ядра от вашего боевого ядра, которое, скорее всего, подготовлено дистрибьютером. Для Debian 10 сборка проходит успешно, если в конфиге потереть информацию о встраиваемых в ядро сертификатах.
Перед попыткой собрать проверьте, что нужные программы уже установлены. Список тут. Чтобы собрать само ядро:
Этого достаточно для проверки собираемости, но недостаточно для запуска ядра в системе, так как без модулей ядро на реальной системе практически беспомощно.
Если какой-то модуль не собирается, просто вырубите его в ближайшем Makefile-е (если 100% уверены, что не пытались в нём что-то улучшить). Наверняка он вам не пригодится, и тратить время на исправления смысла нет.
Теперь можно деплоить то, что получилось, на эту же систему.
Хотя, конечно, экспериментировать с ядром на той же машине, где ведётся разработка — дело рискованное.
Поэтому как минимум нужно снять снапшот, сделать резервную копию или лучше вообще выполнять отладку на другой (лучше виртуальной) машине. Но как это делать, я пока не разбирался. Если у кого есть такой опыт — пишите в комменты, добавлю в статью.
На моей системе загрузчик после установки ядра автоматически обновился. Если у вас этого не произошло, то это делается это на Debian-подобных системах командой:
Update: Как верно заметил gavk, ядро давно уже умеет собирать пакеты, причём как для deb, так и для rpm.
Команда
выводит весь ассортимент. Так что команда
должна собрать пакет с ядром.
5. Patches
Вот теперь мы действительно подготовили код для отправки. Лучше всего, чтобы это был единственный коммит. Так проще делать ревью и так быстрее вам ответят. Всё проверив, наконец-то делаем коммит.
Ещё можно комментарии к коммиту дополнить в человеческом текстовом редакторе.
И теперь его можно оформить в виде того самого письма. Правила хорошего тона, или best practice, если угодно — это 75 символов на строку.
В результате получите два файла. В первом 000-cover-letter.patch нужно указать заголовок письма «Subject» и основное описание патча. В описании патча пишем, для чего он создавался, кому он сделает жизнь на нашей планете лучше и каким образом. Только не словоблудим про космические корабли в Большом театре, а пишем лаконично и по делу. И не в коем случае не пишите корпоративную лабуду а-ля «Без этого патча мой бизнес встанет, меня уволят, а дети мои умрут от голода». Нет, строго по существу: «Увидел вот такую проблему вот тут, починить решил вот таким образом, исходники патча прилагаю». Всё, вы восхитительны! А если не превысили 75 символов на строку, то восхитительны в квадрате.
А ещё один волшебный скриптик ./scripts/getmaintainers.pl
позволит узнать, кому письмо отправлять.
И вот он, момент отправления письма, ради которого всё и затевалось:
Дальше остаётся только сидеть и ждать ответного письма, где вам скорее всего скажут, что всё здорово, но надо ещё сделать вот это и поправить вот здесь. Возможно, придётся доказывать, что ваш функционал действительно новый и не создаёт дублирования. Хотя могут и сразу всё принять, сказав большое спасибо (шутка :-).
После того, как ваш патч был принят, крайне желательно удалить свою ветку.
6. Debuging
И чуть-чуть про отладку. Бонус «на сладкое» для начинающих разработчиков ядра, так сказать.
Как правило, при ошибке вы получаете лог с calltrace-ом. Там указываются имена функций и смещения. Примерно вот так:
Так вот, чтобы понять, в каком месте функции произошла ошибка, достаточно запустить дебагер с подгруженным в него модулем:
Важно, чтобы в модуле сохранились символы (stripped модуль вам тут не поможет).
Выполнив команду list
вы увидите строку кода, приведшую к ошибке. В случае передачи управления по неверному адресу, вы увидите следующую за ошибкой строку.
И на этом позвольте откланяться.
Буду рад вопросам и замечаниям в комментариях.
Источник
Destroy All Software
We normally think of git merges as having two parent commits. For example, the most recent Linux kernel merge as I write this is commit 2c5d955, which is part of the run-up to release 4.10-rc6. It has two parents:
Git also supports octopus merges, which have more than two parents. This seems strange for those of us who work on smaller projects: wouldn’t a merge with three or four parents be confusing? Well, it depends. Sometimes, a kernel maintainer needs to merge dozens of separate histories together at once. Having 30 merge commits, one after another, would be more confusing than a single 30-way merge, especially if that 30-way merge was conflict-free.
Octopuses are more common than you might expect. There are 649,306 commits in the kernel’s history. 46,930 (7.2%) are merges. Of the merges, 1,549 (3.3%) are octopus merges. (This is as of commit 566cf87, which is my current HEAD.)
As a comparison point, 20% of all Rails commits are merges (12,401 out of 63,111), but it has zero octopus merges. Rails is probably more representative of the average project; I expect that most git users don’t know that octopus merges are even possible.
Now, the obvious question: how big do these octopus merges get? The «>» lines here are continuations; the command is written in five lines total. All of the commands in this post are as I typed them into the terminal while experimenting, so they’re not necessarily easy to read. I’m more interested in the conclusions and include code only for the curious.
66 parents! That’s a lot of parents. What happened?
This broke some history visualization tools, provoking a reaction from Linus Torvalds:
I just pulled the sound updates from Takashi, and as a result got your merge commit 2cde51fbd0f3. That one has 66 parents.
It’s pulled, and it’s fine, but there’s clearly a balance between «octopus merges are fine» and «Christ, that’s not an octopus, that’s a Cthulhu merge».
From what I can see, this unusual 66-parent commit was an otherwise mundane merge of various changes to the ASoC code. ASoC stands for ALSA System on Chip. ALSA is the sound subsystem; «system on a chip» is a term for a computer packed into a single piece of silicon. Putting those together, ASoC is sound support for embedded devices.
Now, how often do merges like this happen? Never! The second-place merge is fa623d1 with «only» 30 parents. However, the large distance from 30 to 66 parents isn’t surprising with sufficient context.
The number of parents for a git commit is probably distributed according to a fat one-sided distribution (often informally called a power law distribution, but that’s usually not strictly correct for reasons that aren’t interesting here). Many properties of software systems fall into fat one-sided distributions. Hold on; I’ll generate a plot to be sure. (much nitpicking of chart layout ensues). Yes, it’s fat and one-sided:
To be terse and coarse about it, «fat one-sided» means that there are far more small things than large things, but also that the maximum size of the things is unbounded. The kernel contains 45,381 two-parent merges, but only one 66-parent merge. Given enough additional development history, we can expect to see a merge with more than 66 parents.
Lines of code per function or per module are also fat and one-sided (most functions and modules will be small, but some will be large; think of a «User» class in a web app). Likewise for the rate of change for modules (most modules will change infrequently, but some will change constantly; think of «User» again). These distributions pop up everywhere in software, appearing as straight lines on log-log plots like this one.
OK, so much for the biggest merge in terms of parent count. What about the biggest merge in terms of divergence? By divergence, I mean the difference between the two branches being merged. We can measure that by simply diffing the merge’s parents against each other and counting the lines in the diff.
For example, if a branch diverged from master a year ago, changed one line, and then was merged back into master, all of the changes to master during that time would be counted, as would the changes on our branch. We can come up with more intuitive notions of divergence, but they’re difficult or impossible to calculate because git doesn’t retain branch metadata.
In any case, as a starting point for calculating divergence, here’s the divergence for the most recent kernel merge:
In English, this command reads: «diff the two parents of the most recent merge against each other, then count the lines.» To find the most-diverged merges, we can loop through every merge commit, counting the number of diff lines in a similar way. Then, as a test, we’ll search the results for all merges with exactly 2,000 lines of divergence.
(This command takes a long time to run: around twelve hours, I think, though I was away for much of it.)
I expect merge size to follow a fat one-sided distribution, just like the parent counts did. It should show up as a straight line on a log-log plot. Let me check. yep:
(I’ve binned the diff sizes by rounding them into 1,000-line buckets; otherwise there aren’t enough samples to form a useful curve.)
The bottom right is ugly partly due to quantization and partly due to small sample sizes caused by a lack of huge commits, as with the previous plot.
Now, the obvious question: what’s the most-diverged merge in history?
22,445,760 lines of diff! This seems impossibly large – the diff is longer than the entire source code of the kernel.
Greg Kroah-Hartman made this commit on September 19, 2016, during development of 4.8-rc6. Greg is one of Linus Torvalds’ «lieutenants» – his close, trusted developers. Roughly speaking, lieutenants form the first level of the Kernel’s pull request tree. Greg maintains the stable branch of the kernel, the driver core, the USB subsystem, and several other subsystems.
We need a bit of background before examining this merge more closely. Normally, we think of merges as part of a diamond branch-then-merge pattern:
Back in 2014, Greg started development on Greybus (a bus for mobile devices) in a fresh repo, as if he were starting a totally new project. Eventually, development on Greybus was finished, and it was merged into the kernel. But, because it was started in a fresh repo, it shared no history with the rest of the kernel source. That merge added another «initial commit» to the kernel, in addition to the commit back in 2005 that we normally think of as the initial commit. Instead of the usual diamond branch-and-merge pattern, the repo now had two separate initial commits:
We can see some evidence of this by looking at how many files exist in each of the merge commit’s parents:
One side has a lot of files because it contains the entire kernel source. The other contains few because it’s a separate history containing only Greybus.
Like octopus merges, this will strike some git users as strange. But the kernel developers are expert git users and tend to use its features with abandon, though certainly not reckless abandon.
One final question: how many times has this happened? How many separate «initial» commits does the kernel have? Four, as it turns out:
Just to be clear, if we drew these commits, ignoring all other history, it would look like the graph below.
Each of these four is a distant ancestor of the current kernel HEAD, and none of them has a parent commit. From git’s perspective, the kernel history «begins» four different times, with all of those eventually being merged together.
The first of these four (at the bottom of our output) is what we usually think of as the initial commit to git back in 2005. The second is the development of the file system btrfs, which was done in isolation. The third is Greybus, also done in isolation, which we already saw.
The fourth initial commit, a101ad9, is weird. Here it is:
It just creates a file README.md. But then, it’s immediately merged into the normal kernel history in commit e5451c8!
Why would someone create a new initial commit containing a two-line README file, then immediately merge it into the mainline history? I can’t come up with any reason, so I suspect that this was an accident! It doesn’t do any harm, though; it’s just very strange. (Update: it was an accident, which Linus responded to in his usual fashion.)
Incidentally, this is also the second-most-diverged commit in the history, simply because it’s a merge of an unrelated commit, just like the Greybus merge that we looked at more closely.
There you have it: some of the weirdest things in the Linux kernel’s git history. There are 1,549 octopus merges, one of which has 66 parents. The most heavily diverged merge has 22,445,760 lines of diff, though it’s a bit of a technicality because it shares no history with the rest of the repo. The kernel has four separate «initial» commits, one of which was a mistake. None of this will show up in the vast majority of git repos, but all of it is well within git’s design parameters.
(If you liked this post, you might like the Destroy All Software screencasts, several of which build up the kinds of complex shell commands seen in this post, methodically and piece by piece. «History Spelunking With Unix» is especially relevant.)
Источник