Тут обновлял свой
CMakeProjectManager2 и краем глаза заметил добавление файла
dist/changes-7.0.0.md. Решил перечитать (с некоторыми изменениями я сталкиваюсь регулярно, так как живу на master-ветке).
Там фигурирует:
### C++
* Switched to Clangd by default (QTCREATORBUG-22917)
И таки да, они переключились на кодовую модель от Clangd. Оно появилось ещё раньше, где-то к версии 6.0, но была выключена. Можно ознакомиться с анонсом из первоисточника:
Qt Creator and clangd: An Introduction. Или почитать
комментарии к соответствующей
новости на Phoronix.
В частности, они там объясняют причины. Основная (как мне кажется): libclang больше активно не развивается и для полноценной навигации, рефакторинга и прочего приходится держать две модели.
Там же они рекомендуют использовать самую последнюю версию clangd. Так что ссылки ниже ваши други:
Ну а теперь мои личные впечатления… Точнее табличка, которую я для себя составил. Плюсы и минусы.
В
2016 году (а в
2020 дошли по Большой Пионерской) уже была попытка добраться до Острой по хребту, пробираясь по “милым Приморским рощам” (они же: “Южный Лес”). В этом году решил закрыть этот гештальт, плюс размять ногу после микротравмы на пробежке.
Встречайте:
GeoCrop v2.0
Нового функционала не завезли, но обновил для сборки с современными Proj. Сейчас актуальная версия
API - 6. Проверял с Proj 8.0.1.
Итак:
- CMake: почищен файл, приведён чуточку к виду Modern CMake.
- Для сборки нужен Proj ≥ 6.0, так как используется его API теперь. Proj4 не поддерживается, если нужно, то берите код по метке
v1.0
- Исправлен досадный баг с полигоном обрезки: последним элементом нужно указать начальную точку, что бы полигон закрылся. Старые версии Gdal ругались (точно не помню), но игнорировали и обрезали, моя текущая версия (3.3.1 на момент публикации) уже вываливается с ошибкой.
Задача: сделать USB стик с Ubuntu, но так, что бы состояние сохранялось. Между перезагрузкам.
Забегая вперёд, это делается, но с одним ограничением: ядро не меняется. Но под мои задачи этого достаточно.
И смешно и грустно :)
Итак, начнём.
Мне нужно было скачать одну статью с информацией по ковидной тест-системе (пытался понять значение КП, которое сам получил на тесте). И располагалась она на сайте:
Электронный журнал “Справочник заведующего КДЛ”, который является частью сервисов Action Media:
https://action-media.ru и использует для входа единый аккаунт. На статью перешёл с сайта производителя тест системы Векто-Бест.
Доступ к журналу платный, но предоставляется пробный бесплатный доступ на 3 или 4 дня.
При регистрации требую телефон и почту. Но так как для меня этот сайт является мусорным, то оставлять свой основной мейл и телефон, ровно как и мои реальные ФИО мне нет никакого резона, то я воспользовался сервисом
https://smska.us/. Взял второй в списке и ввёл.
Скачал статью и успокоился. Но они начали закидывать меня тоннами спама. Решил снести аккаунт. Но, как это полагается у подобных сайтов, кнопки удаления нет. Полез в саппорт и вот такой вот диалог состоялся:
Я: День добрый! Как удалить свой аккаунт?
Бот помощник: Здравствуйте! Если удалить учетную запись, вы больше не сможете зарегистрироваться на сайтах «Актион» и получать приглашения на закрытые мероприятия. Отписаться от рассылок можно в разделе
«Рассылки».
Не переживайте, если после отписки придет несколько писем. Нам нужно время, чтобы остановить рассылки.
Вы получили ответ на свой вопрос?
Я: Нет, нужен менеджер
Елена Табачукова: Здравствуйте, Алекс , рады видеть Вас на нашем сайте
Я: День добрый! Нужно удалить аккаунт
Кристина Вырлан: Здравствуйте, Алекс , рады видеть Вас на нашем сайте
Минуту, пожалуйста
Алекс, к сожалению технически удалить аккаунт невозможно, так как ранее проходили обучение и хранятся сертификаты на данном аккаунте
Я: Я не проходил у вас обучения и сертификатов нет. Я уже 5 минут кликаю на отписку от чёртовой горы подписок и ещё не дошёл до конца. С таким отношением в печь такие сервисы. Спасибо. Запросите технический персонал (разработчиков), что бы удалили записи в базе данных по e-mail
ZXXXXXY@inbox.ru. Надеюсь на понимание.
Кристина Вырлан: Алекс, ранее данный аккаунт принадлежал ХмХХХву Роману, Вы переделали аккаунт на себя и свою почту, просьба вернуть все данные обратно, в таком случае все данные и Ваш аккаунт удалиться и Вы вернете аккаунт Роману
Я: Вы по телефону смотрите? :) Это телефон сервиса Онлайн-СИМ, то что нужно для регистрации на мусорных сайтах
Кристина Вырлан: Screenshot_5.png 0.03Mb
Вы стерли чужой аккаунт , сменив все данные на себя
На скрине - логи системы с изменением настроек профиля :) скрины переписки под катом.
Короче, Роман сфейлился, указав левый номер, потом работал с аккаунтом. Там пачка платёжных документов, пройденные курсы, сертификаты.
Будьте внимательны и осторожны :)
Цитата:
Many functions in the kernel sleep (ie. call schedule()) directly or indirectly: you can never call them while holding a spinlock, or with preemption disabled. This also means you need to be in user context: calling them from an interrupt is illegal.
Неделю достаточно трудоёмкой (в плане воспроизведения) отладки пришлось потрать по причинам:
- Незнания информации из цитаты выше (компетентность? не, не слышал).
- Наличия неявной блокировки в недрах ALSA при вызове колбека
snd_pcm_ops::trigger()
.
Про второй пункт попадалась информация:
Run under stream lock
Этот самый “stream lock” обеспечивается семейством функций:
snd_pcm_stream_lock()
/snd_pcm_stream_unlock()
snd_pcm_stream_lock_irq()
/snd_pcm_stream_unlock_irq()
snd_pcm_stream_lock_irqsave()
/snd_pcm_stream_unlock_irqsave()
И вводит понятия: “atomic context” and “non-atomic context”.
Контекст определяется значением флага substream->pcm->nonatomic
(bool snd_pcm::nonatomic
).
Если не сказано иного, то там значение 0, или “atomic context”.
И этот самый контекст вляет на то, какой тип блокировки будет использоваться: mutex или spin lock.
По неудачному стечению обстоятельств в колбеке snd_pcm_ops::trigger()
появился вызов cancel_delayed_work_sync()
. А контекст был объявлен как “atomic”. И работать всё стало очень весело, в зависимости от того, куда какое нейтрино попадёт :)
Проявлялось по разному:
- случайные зависания при закрытии аудио устройства
- случайные зависания при перезагрузке (если устройство было открыто)
В терминал при этом вылетало такое:
Requesting system reboot
[ 56.034544] watchdog: BUG: soft lockup - CPU#3 stuck for 23s! [init:7475]
А дальше вариции:
-
Стектрейсы, например:
[ 52.084474] Modules linked in: sch_htb xlnx_sdi_audio regmap_mmio v_comp(O) v_scaler(O) v_process(O) snd_soc_xlnx_audio_formatter snd_soc_xlnx_i2s mali(O) xilinx_dp_card xilinx_dp_pcm xilinx_dp_codec snd_soc_tlv320aic3x pwm_cadence(O) max6966_keys(O) max6966_backlight(O) max6966_leds(O) max6966_mfd(O) fbili9341(O) it666x(O) xilinx_hdmi_rx(O) xilinx_vphy(O) xilinx_sdirxss u_dma_buf(O) uio_pdrv_genirq dmaproxy(O) al5d(O) xlnx_vcu_clk al5e(O) allegro(O) xlnx_vcu xlnx_vcu_core xlnx_pl_snd_card snd_soc_core snd_pcm_dmaengine snd_pcm snd_timer snd soundcore xilinx_vpss_scaler xilinx_video videobuf2_v4l2 v4l2_fwnode videobuf2_common videobuf2_dma_contig videobuf2_memops videodev media
[ 52.144543] CPU: 1 PID: 6938 Comm: init Tainted: G O 4.19.0-xilinx-v2019.2 #83
[ 52.153054] Hardware name: Epiphan X3 (DT)
[ 52.157136] pstate: 20000005 (nzCv daif -PAN -UAO)
[ 52.161915] pc : smp_call_function_single+0x140/0x170
[ 52.166952] lr : smp_call_function_single+0x110/0x170
[ 52.171985] sp : ffffff802e0f3bb0
[ 52.175283] x29: ffffff802e0f3bb0 x28: ffffffc03d3eac00
[ 52.180586] x27: 0000000000000002 x26: ffffff8008b887c8
[ 52.185881] x25: ffffff8008b9a9a0 x24: ffffff8008125800
[ 52.191185] x23: 0000000000000002 x22: ffffff8008b9a9c0
[ 52.196480] x21: ffffffbebffc7b58 x20: ffffff8008b88548
[ 52.201774] x19: ffffff802e0f3be0 x18: 0000000000000000
[ 52.207069] x17: 0000000000000000 x16: 0000000000000000
[ 52.212364] x15: 0000000000000000 x14: 0000000000000000
[ 52.217659] x13: 0000000000000000 x12: 0000000000000000
[ 52.222953] x11: 0000000000000000 x10: 0000000000000000
[ 52.228248] x9 : 0000000000000000 x8 : 0000000000000000
[ 52.233543] x7 : 0000000000000000 x6 : ffffff802e0f3be0
[ 52.238838] x5 : ffffff802e0f3be0 x4 : 0000000000000004
[ 52.244133] x3 : ffffff802e0f3bf8 x2 : 0000000000000000
[ 52.249428] x1 : 0000000000000003 x0 : 0000000000000000
[ 52.254723] Call trace:
[ 52.257155] smp_call_function_single+0x140/0x170
[ 52.261844] perf_event_exit_cpu_context+0x84/0xe0
[ 52.266624] perf_reboot+0x2c/0x60
[ 52.270011] notifier_call_chain+0x54/0x90
[ 52.274099] blocking_notifier_call_chain+0x54/0x80
[ 52.278960] kernel_restart_prepare+0x1c/0x40
[ 52.283299] kernel_restart+0x14/0x60
[ 52.286945] __se_sys_reboot+0xd4/0x1f0
[ 52.290764] __arm64_sys_reboot+0x18/0x20
[ 52.294758] el0_svc_handler+0xc8/0x120
[ 52.298584] el0_svc+0x8/0xc
-
Периодические репорты:
[ 62.477485] BUG: workqueue lockup - pool cpus=2 node=0 flags=0x0 nice=0 stuck for 36s!
В общем, рекомендую к внимательному чтению:
Только не смотрите на год :)
И перед тем, как ставить msleep()
/mutex_lock()
/etc, изучите, кто вызывает этот код, есть ли там блокировка и какая. А если код ваш, посмотрите на
шпаргалку по выбору типа блокировки.
PS а если полезно под рукой иметь
глоссарий. В частности он объяснил мне, что это за функции с суффиксом _bh
и что такое tasklet.
Тот самый случай, когда WA ломается.
Причина проста: наличие параметра ядра scsi_mod.scan=sync
.
По умолчанию его нет, поэтому и проблема не слишком актуальна. А появился он… как WA проблемы с тем же самым Resume after Speep. Но тогда рушился XOrg:
Таким образом, наличие этой опции на ядре 4.15+ решает проблему с выходом из сна и падением XOrg и никак не мешает работе вплоть до ядер 5.9.x, но начиная с ядра 5.10 эта опция должна быть удалена, иначе уже она является причиной зависания при выходе из сна.
Скорее всего такое поведение тоже является ошибочным
Возникла задача: запускать тест, только если явно указано, что его нужно запускать. Иными словами пропускать его при обычным прогоне, без параметров.
Тестовый фреймворк
GTest.
GTest, как и многие другие имеет возможность фильтровать тесты, иными словами, выбирать только те, которые нужно запустить в данный момент времени:
./tests --gtest_filter=Categoty.some_test
Если фильтр не указывается, то запускаются все тесты. При этом, у Google Test есть макрос, который в рантайме позволяет исключить тест из прогона:
Остаётся придумать, как передать условие, по которому этот макрос вызовется.
Дополнительный параметр командной строки заводить не хотелось, тем более, что уже есть фильтр… Оказалось, что достучаться к тому, что передано для --gtest_filter=
в качестве параметра проще простого:
std::string str = ::testing::GTEST_FLAG(filter);
Сама строка фильтра: набор масок, разделённых двоеточием. Собственно, нам достаточно проверить, что фильтр не содержит имени нашего теста и в этом случае скипнуть его:
TEST(Category, some_test) {
std::string str = ::testing::GTEST_FLAG(filter);
auto enable_pos = str.find("Category.some_test");
if (enable_pos == std::string::npos) {
GTEST_SKIP();
}
}
Теперь, тест запустится, только если явно указать имя теста при запуске:
./tests --gtest_filter=Category.some_test
или все тесты, включая наш:
./tests --gtest_filter=Category.some_test:*
Под катом небольшой бонус :)
Сгенерировать нули.
В этом месяце (точнее в прошлом) я не прошёл бы тест на продуктивность (недавно узнал про такие: productivity test): 5 тасок и не все в завершённом состоянии.
С одной из них провозился 1.5 недели. Хотя сложного, на первый, взгляд ничего и нет.
А суть её заключается вот в чём: генерировать нули в звуковом устройстве, то есть - тишину.
Поехали!
Точнее генерировать ничего и не нужно: буфера в ALSA кольцевые, нужно его заполнить нулями один раз и просто двигать указатель по нему.
Двигать указатель.
Казалось бы, а тут-то какие сложности?
В
предыдущей заметке я конспективно указал основные термины и понятия связанные с PCM. Нам нужен период, его размер и sample rate, он же - частота дискретизации.
Берём таймер. Пусть будет
High Resolution Timer. Когда срабатывает ALSA колбек struct snd_pcm_ops::prepare
у нас уже есть все нужные параметры для стрима, типа количества каналов, битности семплов, частоты. Есть и информация о размере выделенного кольцевого буфера: substream->runtime->period_size
. Только нужно быть внимательным, смотрим на тип: snd_pcm_uframes_t
и понимаем, что это размер периода не в герцах, не в единицах времени, а во фреймах.
Так вот, в этом колбеке, мы настраиваем таймер, что бы он тикал один раз в Period Size. Максимально точно в целых числах получается вот так:
period = runtime->period_size; // во фреймах, тут как договоритесь в hw_params, может быть нифига не кратен rate
rate = runtime->rate; // в HZ, например 48000, это частата **фреймов**
sec = period / rate; // вычленим секунды, в более-менее low-latency здесь всегда будет 0
// ну и посчитаем максимально точно ту часть, что в наносекундах
period %= rate;
nsecs = div_u64((u64)period * 1000000000UL + rate - 1, rate);
// чисто хелпер, по сути sec * 10^9 + nsecs
period_time = ktime_set(sec, nsecs);
После чего, в обработчике struct snd_pcm_ops::trigger
, по START активируем таймер и он будет тикать раз в period_time
наносекунд. А дальше, в обработчике прерывания таймера, мы вызываем специально предназначенную для этого функцию:
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
Как сказано в документации, вольный перевод:
обновляет статус PCM для следующего периода
Вызывается из обработчика прерывания, когда источник семплов запроцессил (сиречь - положил в память) period_size фреймов. Заобновится указатель на данные, пробудятся те, кто данные ждёт и т.п.
Если вдруг между вызовами случилось несколько period_size - нужно сделать один вызов.
По сути, эта функция, вызывает другой наш колбек: struct snd_pcm_ops::pointer
, который и говорит текущую позицию в кольцевом буфере.
То есть, на основании показаний таймера мы можем сказать виртуальную позицию в буфере. И тут никаких проблем: нам нужно спросить сколько таймер накрутил времени относительно какой-то точки отсчёта (логично сохранить некий аналог now()
при старте таймера). И пересчитать эту дельту на основании rate
и buffer_size
в позицию. В коде это выглядит так:
now = hrtimer_cb_get_time(timer); // текущее время таймера, обновляется пока он работает
delta = ktime_us_delta(now, base_time); // дельта между текущим временем и базовым
delta = div_u64(delta * runtime->rate + 999999, 1000000); // время множим на частоту, получаем дельту во фреймах
// относительно старта таймера
// применяем трюки для минимизации ошибки округления
// делим на 10^6, потому как выше дельту в мкс (us) получили
div_u64_rem(delta, runtime->buffer_size, &pos); // Ну и берём остаток от деления на размер буфера, получаем искомую
// позцию
Действительно, текста много, а сложностей - ноль. Оно работает, данные шлются. Вот только процесс в пользовательском пространстве жрёт 100% CPU. А при работе с реальным железом - нет.
Начинаем разбираться.
Для начала открываем для себя (ещё кто-то будет спрашивать, зачем я ВСЁ ядро в IDE гружу? :), что struct snd_pcm_ops::pointer
вызывается далеко не из одного места:
Красными кружками обведены наиболее активные потребители, выявленные при помощи логов.
__snd_pcm_lib_xfer()
используется для любого трансфера, осуществляющегося через read()
/write()
на устройстве. И частота его следований, в целом, соответствовало таковому для железа. А вот snd_pcm_delay()
, который является обработчиком для IOCTL: SNDRV_IOCTL_DELAY
зовётся очень активно. При этом, было замечено, что при работе с реальным железом, задержка или 0, что означает - данных ещё нет, или 8 и больше, что означает - приложению есть что прочитать. А вот у меня с тишиной (помимо 0) и 1, и 2, и, редко, больше.
А всё почему? А потому, что между вызовами таймер тикает и время движется. И в любой момент времени (ну практически) выходит так, что данные как бы есть и софт старательно их вычитывает.
Вылечилось, как обычно, при помощи sleep()
… Шутка. Почти.
Для железного модуля позиция высчитывалась на основании переданных байт в регистре счётчика. Согласно документации, данные эти обновляются каждый period_size
, но на самом деле - каждые 8 фреймов. Точнее так: каждые 32 байта: модуль, скорее всего, завязан именно на байты, просто в моём режиме: PCM_S16LE/Stereo получается 8 фреймов.
Выходит, что у железа есть задержка в 8 фреймов. На частоте 48000Hz это примерно 166мкс. И этого хватает, что бы не вводить процессор в busy loop.
В общем, я добавил такую “виртуальную” задержку и в программный генератор: считается позиция, если она сдвинулась на 8 или более фреймов, то сохраняем и возвращаем новое значение. В противном случае возвращаем старое значение.
Вот теперь - работает!
Для чего это нужно или чуть больше конкретики
Есть IP Core от Xilinx: Audio PCM Formatter. Он нужен для того, что бы переложить данные из других PL (FPGA) модулей, подключенных в цепочку по AXI шине в память, к которой можно достучаться из CPU в прочитать устройством. Это режим Capture. Есть и обратный режим: переложить данные из памяти и передать по AXI шине.
Как и всё на шине этот модуль тактируется, когда данные есть. Если источником данных является ADC, то данные есть всегда, пока работает ADC, а он работает, обычно, всегда когда устройство открыто и в работе. ADC без разницы что оцифровывать: реальный звук или наводки на проводах.
Другое дело, когда источником сигнала является SDI или HDMI вход. У этих чудиков может быть и физически кабель воткнут, а аудио не быть. Или просто кабель не воткнут. С одной стороны это удобно: семплы не идут, значит сигнала нет. Пробуем потом (ага, тут или поллинг или платформоспецифичные проверки со стороны user-space).
Но теряется унификация: для аналогового аудио у нас данные всегда идут: или реальные или условные нули (наводки).
В общем возникла идея: а что если в драйвере PCM Formatter’а детектировать отсутствие данных и переключаться на программный генератор тишины. И переключаться обратно, когда данные появились. Если это сделать быстрее, чем period size, то мало кто и заметит. Точнее так, мы не озаботились о плавном переходе между позицией указателя между генератором и железом, поэтому в момент переключения могут быть некие переходные процессы, но, в целом, всё работает нормально.
Поэтому 1.5 недели включали в изучение вопроса - как это вообще скрестить между собой.
Просто ссылка для мемориза:
Под катом краткий конспект (читать не обязательно).
Недавно приехали покупки с магазина, среди которых была титановая полноразмерная ложка от Урал ВСМПО, купленная взамен утерянной. Но по прибытии, оказалось, что она ощутимо тяжелее и прочнее старого образка. В результате, возникла идея перемерить коэффициент ложки, сделанный несколько лет назад камрадом Live:
Вёсла разные нужны, вёсла разные важны
Участники:
Слева на право:
- Ложка пластиковая, обыкновенная. Далее просто Пластик
- Ложка стальная, десертная, любезно предоставлена сыном :). Далее: Сталь (мал)
- Ложка стальная столовая, просто взята из посудницы. Далее: Сталь (больш)
- Ложка титановая Урал ВСМПО, старого образца. Далее: Ti (old)
- Ложка титановая Урал ВСМПО, нового образца. Далее: Ti (new)
- Ложка алюминиевая, далее Al. Любезно предоставлена коллегой с работы.
Методика измерений точно повторяет Колину:
- взвешиваем ложку (в таблице строка ВЕС);
- набираем ложкой ПРОДУКТ, столько сколько влезет максимально, с горкой. Взвешиваем набранный продукт (строка ВЕСЛО).
Продукт набираем 3 раза, показания усредняем. Если весы прыгают между значениями, то брал максимальное.
Коэффициент ложки (или просто Коэффициент) считается как ВЕСЛО/ВЕС
.
Открытый вопрос: кому больше драйва и радости от строительства? :)
Под катом больше фото.
Как сказано на
GitHub разработчика:
Alacritty - A fast, cross-platform, OpenGL terminal emulator
Со слов автора он создал его, так как неудобно было конфигурировать
st. (–htrd: add cite)
Если коротко, то это эмулятор терминала с быстрым выводом текста на экран, используя GPU (OpenGL). Без поддержки табов, без конфигурировать через меню, но с достаточно гибкими возможностями конфигурации через файлы, включая подхват конфигурации на лету.
Нельзя вот так просто взять и создать MyProgram.desktop
файл в главном меню какого-нить KDE ручками из консоли. Особенно если путь вроде Menu → Wine → Programs → Some Application
Казалось бы, просто идём в ~/.local/share/applications/wine/Programs/Some Application
и создаём MyProgram.desktop
с нужным содержимым. Ан нет. Во-первых, программа появится в меню только после рестарта или после выполнения команды:
Во-вторых, она появится совсем в другом месте, а именно в Menu → Прочее
. Вот это поворот…
Время от времени возникает необходимость организовать аналог /etc/hosts
, но индивидуально для пользователя. Такая возможность есть, и называется HOSTALIASES
.