Ковыряясь в разнообразном СПО, я периодически нахожу всякие интересные штуки: иногда это просто смешной комментарий, иногда — нечто остроумное в более широком смысле. Подобные подборки периодически появляются и в «глобальном Интернете», и на Хабре — есть, скажем, широко известный вопрос на StackOverflow про комментарии в коде, а здесь недавно публиковалась подборка забавных названий юрлиц и топонимов. Попробую и я структурировать и выложить то, что постепенно у меня копилось. Под катом вас ждут цитаты из QEMU, ядра Linux, и не только.
Linux kernel
Думаю, для многих не является секретом то, что письма из Linux Kernel Mailing List периодически расходятся на цитаты. Поэтому давайте лучше посмотрим в код. И сразу же система сборки ядра встречает нас сюрпризом: как известно, проекты, собираемые Autoconf имеют Makefile с двумя стандартными целями для очистки: clean
и distclean
. Естественно, ядро не собирается с помощью Autoconf, да и чего только стоит один лишь menuconfig
, поэтому целей здесь побольше: clean
, distclean
и mrproper
— да-да, «Мистер Пропер», ядро чище в два раза быстрей.
Кстати о системе конфигурирования: когда-то давно я был удивлён, когда наткнулся в ней помимо понятных команд вроде allnoconfig
, allyesconfig
(подозреваю, что вкомпилироваться может что-нибудь сильно отладочное, поэтому сейчас бы я такое загружать на реальном железе не рискнул бы...) и allmodconfig
на загадочную цель allrandconfig
. «Они что, издеваются» — подумал я, потом рассказал об этом наблюдении своему знакомому, на что тот ответил, что, вероятно, это вполне осмысленная команда, но не для реальной сборки, а для тестирования правильности расстановки зависимостей между опциями — как я сказал бы уже сейчас, этакий фаззинг параметров конфигурации.
Впрочем, есть в ядре жизнь и за пределами сборочной системы: документация иногда представляет не только техническую, но и, своего рода, художественную ценность. Предположим, вы хотите предупредить пользователей спящего режима о его хрупкости и риске потери данных при несоблюдении определённых правил. Я бы уныло написал, мол ВНИМАНИЕ: <подставить пару максимально нудных строчек>. Но разработчик, писавший это, поступил по-другому:
Some warnings, first.
* BIG FAT WARNING *********************************************************
*
* If you touch anything on disk between suspend and resume...
* ...kiss your data goodbye.
*
* If you do resume from initrd after your filesystems are mounted...
* ...bye bye root partition.
* [this is actually same case as above]
*
* ...
Маленькие хитрости
Неудивительно, что не всякий код можно собирать с оптимизациями: когда я попытался принудительно их включить для всех объектных файлов, я закономерно напоролся на некий источник энтропии или что-то подобное, который выдавал #error
, если включена оптимизация. Ну, криптография — она такая. А не хотите ли код, который не соберётся, если вы отключите все оптимизации, инлайнинг и т.д.? Как такое возможно? А это такой статический assert:
/* SPDX-License-Identifier: GPL-2.0 */
// ...
/*
* This function doesn't exist, so you'll get a linker error if
* something tries to do an invalidly-sized xchg().
*/
extern void __xchg_called_with_bad_pointer(void);
static inline
unsigned long __xchg(unsigned long x, volatile void *ptr, int size)
{
unsigned long ret, flags;
switch (size) {
case 1:
#ifdef __xchg_u8
return __xchg_u8(x, ptr);
#else
local_irq_save(flags);
ret = *(volatile u8 *)ptr;
*(volatile u8 *)ptr = x;
local_irq_restore(flags);
return ret;
#endif /* __xchg_u8 */
// ...
default:
__xchg_called_with_bad_pointer();
return x;
}
}
Предполагается, видимо, что при любом использовании с константным аргументом эта функция развернётся в одну лишь ветвь switch
, а при использовании с корректным аргументом, эта ветвь будет не default:
.
В не оптимизированном виде эта функция вызовет ошибку линковки практически by design...
Знаете ли вы
- … что в ядре есть JIT-компилятор байткода из пользовательского режима? Эта технология называется eBPF и используется для маршрутизации, трассировки и много чего ещё. Кстати, если не боитесь экспериментальных «ядерных» инструментов, посмотрите на пакет bpftools.
- … что в ядро можно уйти минут на пять процессорного времени? Есть такой системный вызов
sendfile
, копирующий байты из одного файлового дескриптора в другой. Если указать ему один и тот же дескриптор и выставить правильное смещение в файле, он будет наматывать одни и те же данные, пока не скопирует 2 Гб. - … что есть вариант работы гибернации, проводимой пользовательским процессом — не удивлюсь, если можно и на сетевое хранилище так сохраниться.
QEMU
Вообще, когда я почитал Роберта Лава про устройство Linux kernel, а потом полез в исходники QEMU, у меня возникло некоторое ощущение дежавю. Там были и списки, встраивающиеся в структуры по значению (а не как в начальном курсе программирования учат — через указатели), и некая подсистема RCU (что это такое, я так до конца и не понял, но в ядре оно тоже есть) и, наверное, много ещё похожего.
С чем первым делом знакомится аккуратный человек, желающий поработать над проектом? Наверное, с Coding style. И уже в этом, можно сказать, парадном, документе, мы видим:
1. Whitespace
Of course, the most important aspect in any coding style is whitespace.
Crusty old coders who have trouble spotting the glasses on their noses
can tell the difference between a tab and eight spaces from a distance
of approximately fifteen parsecs. Many a flamewar has been fought and
lost on this issue.
Здесь же про извечный вопрос о максимальной длине строк:
Lines should be 80 characters; try not to make them longer.
...
Rationale:
- Some people like to tile their 24" screens with a 6x4 matrix of 80x24
xterms and use vi in all of them. The best way to punish them is to
let them keep doing it.
...
(Хмм… Это же в два раза больше по каждой оси, чем иногда использую я. Это такой Linux HD?)
Там ещё много интересного — почитайте.
И снова хитрости
Говорят, С — низкоуровневый язык. Но если хорошо извратиться, проявлять чудеса compile-time кодогенерации можно и без всяких Scala и даже C++.
Вот, например, в кодовой базе QEMU притаился файл softmmu_template.h
. Когда я увидел это название, то подумал, что его предполагается скопировать в свою реализацию TCG backend-а и подправлять, пока из него не получится правильная реализация TLB. Как бы не так! Вот как правильно его использовать:
accel/tcg/cputlb.h:
define DATA_SIZE 1
#include "softmmu_template.h"
#define DATA_SIZE 2
#include "softmmu_template.h"
#define DATA_SIZE 4
#include "softmmu_template.h"
#define DATA_SIZE 8
#include "softmmu_template.h"
Как видите, ловкость рук и никакого C++. Но это довольно простой пример. Как насчёт чего посложнее?
Есть такой файл: tcg/tcg-opc.h. Содержимое его довольно загадочно и выглядит как-то так:
...
DEF(mov_i32, 1, 1, 0, TCG_OPF_NOT_PRESENT)
DEF(movi_i32, 1, 0, 1, TCG_OPF_NOT_PRESENT)
DEF(setcond_i32, 1, 2, 1, 0)
DEF(movcond_i32, 1, 4, 1, IMPL(TCG_TARGET_HAS_movcond_i32))
/* load/store */
DEF(ld8u_i32, 1, 1, 1, 0)
DEF(ld8s_i32, 1, 1, 1, 0)
DEF(ld16u_i32, 1, 1, 1, 0)
DEF(ld16s_i32, 1, 1, 1, 0)
...
На самом деле всё очень просто — используется он вот так:
tcg/tcg.h:
typedef enum TCGOpcode {
#define DEF(name, oargs, iargs, cargs, flags) INDEX_op_ ## name,
#include "tcg-opc.h"
#undef DEF
NB_OPS,
} TCGOpcode;
Или так:
tcg/tcg-common.c:
TCGOpDef tcg_op_defs[] = {
#define DEF(s, oargs, iargs, cargs, flags) { #s, oargs, iargs, cargs, iargs + oargs + cargs, flags },
#include "tcg-opc.h"
#undef DEF
};
Даже странно, что с ходу других случаев использования не нашлось. И заметьте, в данном случае нет никаких хитрых скриптов для кодогенерации — только C, только хардкор.
Знаете ли вы
- … что QEMU может работать не только в режиме эмуляции полной системы, но и запускать отдельный процесс для другой архитектуры, общающийся с хостовым ядром?
Java, JVM и все-все-все
Что же я всё о Линуксе? Давайте о чём-нибудь кросс-платформенном поговорим. О JVM, например. Ну, про GraalVM, наверное, многие разработчики в этой экосистеме уже слышали. Если не слышали, то в двух словах: это эпично. Итак, после рассказа о Graal перейдём к старой-доброй JVM.
Иногда JVM нужно остановить все managed-потоки — стадия сборки мусора такая заковыристая или ещё чего — но вот незадача, останавливать потоки можно только на так называемых safepoints. Как рассказывается здесь, нормальная проверка глобальной переменной занимает много времени, в том числе некое шаманство с memory barriers. Что же сделали разработчики? Они ограничились одним чтением переменной.
Есть такой шуточный язык — HQ9+. Он создавался как "очень удобный учебный язык программирования", а именно, на нём очень просто выполнять типичные задачи, которые задают ученикам:
- по команде 'H' интерпретатор печатает Hello, World!
- по команде 'Q' печатает сам текст программы (квайн)
- на '9' он печатает текст песенки про 99 bottles of the beer
- по 'i' он увеличивает переменную i на единицу
- больше он ничего делать не умеет, а зачем?..
Как же JVM одной инструкцией добивается цели? А очень просто — при необходимости остановки она убирает отображение для страницы памяти с этой переменной — потоки падают по SIGSEGV, а JVM их паркует и снимает с паузы, когда "техобслуживание" заканчивается. Помню, на StackOverflow на вопрос с собеседования How do you crash a JVM? ответили:
JNI. In fact, with JNI, crashing is the default mode of operation. You have to work extra hard to get it not to crash.
Шутки шутками, а иногда в JVM оно на самом деле так.
Ну и раз уж я упомянул про кодогенерацию в Scala, да и говорим мы сейчас как раз об этой экосистеме, то вот вам занятный факт: кодогенерация в Scala (та, которая макросы), устроена приблизительно так: вы пишите код на Scala, использующий API компилятора, и компилируете его. Потом при следующем запуске компилятора вы просто передаёте получившийся кодогенератор в classpath самого компилятора, а тот, увидев специальную директиву, его вызывает, передав синтаксические деревья, полученные при вызове. В ответ он получает AST, которое нужно подставить в месте вызова.
Особенности идеологий лицензирования
Мне нравится идеология свободного ПО, но и в ней бывают забавные особенности.
Когда-то, лет десять назад, я обновил свой Debian stable и, задумавшись про синтаксис какой-то команды, привычно набрал man <команда>
, на что получил исчерпывающее описание вроде «[имя программы] — это программа с документацией, распространяемой под лицензией GNU GFDL с неизменяемыми секциями, которая не является DFSG-free». Говорят, эту программу написали какие-то злые проприетарщики из какого-то FSF... (Сейчас гуглится обсуждение.)
А какая-то маленькая, но важная библиотека некоторыми дистрибутивами считается несвободным ПО, поскольку к стандартной пермиссивной лицензии автор сделал приписку про то, что эта программа должна использоваться для добра, а не для зла. Смех смехом, а я бы тоже, наверное, побоялся такое в production брать — мало ли, какие представления о добре и зле у автора.
Всякое разное
Особенности интернационального компиляторостроения в период закона Мура
Суровые разработчики LLVM ограничили поддерживаемое выравнивание:
The maximum alignment is 1 << 29.
Как говорится, заставляет сначала посмеяться, а потом задуматься: первая мысль — да кому нужно выравнивание на 512 MiB. Потом почитал про разработку ядра на Rust, а там предлагают сделать структуру «таблица страниц», выровненную на 4096 байтов. А как почитаешь Википедию, так там вообще:
A full mapping hierarchy of 4 KB pages for the whole 48-bit space would take a bit more than 512 GB of memory (about 0.195% of the 256 TB virtual space).
Версия формата — как хранить?
Как-то раз решил я разобраться, почему не работает экспорт в одной программе, а он, оказывается, работает… Или нет?
Позапускав вручную команды backend-а, понял, что, в принципе, всё в порядке, просто версия должна передаваться как "2.0", а уходит просто "2". Предвкушая тривиальное исправление путём правки строковой константы, обнаруживаю функцию double getVersion()
— ну а что, major есть, minor есть, даже точка есть! Впрочем, в итоге всё решилось не намного сложнее, чем предполагалось, я просто повысил точность вывода переправил тип данных и пробросил строки.
О разнице между теоретиками и практиками
По моему, где-то на Хабре я уже видел перевод статьи про то, какова минимальная падающая при запуске, но всё же компилирующаяся программа на C? int main;
— символ main
есть, и технически, можно передать на него управление. sirikid правильно подметил, что даже байты int
тут лишние. В общем, даже говоря про программу размером 9 байтов, лучше не разбрасываться утверждениями, что она самая маленькая... Правда, программа при этом упадёт, но правилам это вполне соответствует.
Итак, ронять то, что должно работать, мы умеем, а как насчёт того, чтобы запустить не запускаемое?
$ ldd /bin/ls
linux-vdso.so.1 (0x00007fff93ffa000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f0b27664000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0b2747a000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f0b27406000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f0b27400000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0b278e9000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0b273df000)
$ /lib/x86_64-linux-gnu/libc.so.6
… а libc ему человеческим голосом:
GNU C Library (Ubuntu GLIBC 2.28-0ubuntu1) stable release version 2.28.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 8.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
Программисты играют в гольф
Есть целый сайт на StackExchange, посвященный Code Golf — соревнованиям с стиле «Решите эту задачу с минимальным штрафом, зависящим от размера исходного кода». Сам формат предполагает весьма изысканные решения, но иногда они становятся уж очень изысканными. Поэтому в одном из вопросов была собрана коллекция стандартных запрещённых лазеек. Мне особенно нравится эта:
Using MetaGolfScript
MetaGolfScript is a family of programming languages. For example, the empty program in MetaGolfScript-209180605381204854470575573749277224 prints "Hello, World!".
Одной строкой
Не инициализированный bool, который обрушил программу, или куда приводит undefined behavior
… который, кстати, is magic. Кто-то в LLVM на первое апреля пошутил, а другой из этого доклад сделал: My little optimizer: undefined behavior is magic
Помните неоднозначный призыв хвалить разработчиков СПО в трекере? В Binaryen он реализовался просто и со вкусом:
Issue: This project is AMAZING
Resolution: wontfix, works as intended
в феврале 2009 года человек задавал вопрос на StackOverflow: "Как сделать то-то и то-то кросс-браузерно? Или ну его, Three working browsers are great and who uses Chrome anyway?"
Наконец, откуда такое название статьи? Это перефразированный трюк из вывода компилятора emcc
из состава Emscripten:
$ emcc --help
...
emcc: supported targets: llvm bitcode, javascript, NOT elf
(autoconf likes to see elf above to enable shared object support)
Комментарии (12)
Cerberuser
04.02.2019 16:03+1Some people like to tile their 24" screens with a 6x4 matrix of 80x24 xterms and use vi in all of them.
По одному экземпляру vi на каждый дюйм диагонали? Это точно были челябинские программисты…
кодогенерация в Scala (та, которая макросы), устроена приблизительно так: вы пишите код на Scala, использующий API компилятора, и компилируете его. Потом при следующем запуске компилятора вы просто передаёте получившийся кодогенератор в classpath самого компилятора, а тот, увидев специальную директиву, его вызывает, передав синтаксические деревья, полученные при вызове. В ответ он получает AST, которое нужно подставить в месте вызова.
Если я правильно понимаю, в Rust процедурные макросы работают точно так же (обычные — несколько проще): делается отдельный пакет, который на вход получает цепочку токенов (всё-таки не AST, да), поданную в макрос, а на выход подаёт цепочку токенов, которой вызов макроса нужно заменить.
к стандартной пермиссивной лицензии автор сделал приписку про то, что эта программа должна использоваться для добра, а не для зла
Шутки шутками, а в одной программе для редактирования ID3-тэгов её лицензия прямо запрещает использовать её в военных целях…atrosinenko Автор
04.02.2019 16:16+1Шутки шутками, а в одной программе для редактирования ID3-тэгов её лицензия прямо запрещает использовать её в военных целях…
Ну, тут хоть приблизительно описано, для чего использовать нельзя, хотя и не СПО, конечно, но её использование выглядит не так рискованно.
WooHoo
05.02.2019 09:00Some people like to tile their 24" screens with a 6x4 matrix of 80x24 xterms and use vi in all of them.
Тут имеется в виду 6x4 матрица с xterm-ами размером 24 ряда, 80 колонн.Cerberuser
05.02.2019 09:02+1Так и я о том же. 6 xterm-ов по горизонтали, 4 по вертикали, итого 24 штуки.
atrosinenko Автор
05.02.2019 09:06WooHoo, наверное, хочет сказать, что сравнивать "площадь" (
38 попугаев24 икстерма) и диагональ (24 дюйма) — размерность не сходится.Cerberuser
05.02.2019 09:07Строго говоря, при известном соотношении сторон дисплея размерность таки сойдётся, потому что площадь с любым размером, в том числе и с диагональю, будет соотноситься взаимно однозначно. Хотя замечание уместное, признаю.
webkumo
05.02.2019 11:54А вот результат рассчётов — не очень. По моим прикидкам в каждом терминале по 3 дюйма диагонали наберётся...
maxzhurkin
04.02.2019 21:45Мои соображения по поводу undefined behavior: есть только 4 варианта, в которые укладываются все ЯП:
- синтакическая мощь языка больше семантической, UB во всей красе
- пространства синтаксиса и семантики пересекаются, случаи UB существуют
- пространство синтаксиса и семантики полностью совпадают, UB не может быть в той же степени, как и такого ЯП
- семантическая мощь больше синтаксической, UB невозможен — очень печальное зрелище, IMHO, поскольку некоторые возможности языка невозможно реализовать в силу ограниченности его синтаксиса
P.S. Возможно, я не сам это придумал
Sirikid
05.02.2019 00:57Если игнорировать предупреждения, то самая маленькая программа будет
main;
.atrosinenko Автор
05.02.2019 08:44Да уж, даже говоря про программу размером в 10, байт лучше не употреблять термин самая маленькая. Ну, будем считать, что это была версия под C++ :)
Cheater
05.02.2019 19:20+1Из документации к ядру Linux:
Software Interrupt Context: Softirqs and Tasklets
(...)
The name 'tasklet' is misleading: they have nothing to do with 'tasks', and probably more to do with some bad vodka Alexey Kuznetsov had at the time.
MMik
Из исходников Asterisk IP PBX (ссылка), модуль Asterisk Manager: