Главная » Хабрахабр » К вопросу о параметрах драйвера в Linux, или как я провел выходные

К вопросу о параметрах драйвера в Linux, или как я провел выходные

«Мы ленивы и нелюбопытны»

Поскольку подобные вещи (подключение, не ОС) входят в сферу моих профессиональных интересов, статью просмотрел с вниманием, потом нашел собственно текст «драйвера» и был слегка удивлен, что ЭТО можно хвалить. На сей раз поводом к посту послужила статья в неплохом журнале, посвященном ОС Linux (далее по тексту Л), в которой привлеченный «эксперт» хвалил драйвер подключения ЖКИ к плате Raspbery. Казалось бы, и фиг с ним, мало ли кто что пишет для себя, но выкладывать подобное в открытый доступ — «я и не знал, что так можно». Ну, в общем то, уровень эксперта можно определить хотя бы потому, что он упорно именовал программу драйвером, несмотря на то, что она им никоим образом не является.

Я, кстати, заметил, что на форумах, посвященных Л, наиболее популярный ответ на любой вопрос о проблемах в ПО, «пересобери последнюю версию ядра». Особенно порадовало то обстоятельство, что адрес устройства на шине I2C напрямую задавался в тексте программы и для его изменения требовалась ее перекомпиляция (хорошо, что не всего ядра). Но, тем не менее, возник вопрос, а как в Л действительно реализуется (внутри, не снаружи — там все просто и понятно) параметризация драйвера, ответу на который и посвящен данный пост.
Не то, чтобы я постоянно писал драйверы для Л, но с процессом в целом знаком и гугление подтвердило смутные воспоминания, что существует набор макросов, которые следует использовать при создании исходного текста модуля, чтобы иметь возможность передавать ему параметры функционирования, например, адрес устройства на шине. Мне данный подход представляется несколько странным, наверное, я чего-то не знаю. В многочисленных ссылках я видел один и тот же текст (кстати, интересный вопрос — зачем это делать, то есть размещать чужой фрагмент текста на своем ресурсе — я не очень понимаю смысл данной операции), в котором описывались вышеуказанные макросы. Тем не менее, сама механика процесса нигде не описывалась. Сразу отмечу, что я постараюсь не дублировать информацию, которую Вы можете почерпнуть из других источников, и ограничусь только той, которая необходима для понимания текста. Ни одного упоминания о механизме выполнения операции я не нашел, для другой известной операционной системы (Windows) пришлось бы констатировать факт, и этим ограничиться, но ведь одно из преимуществ Л — наличие исходных текстов и возможность найти ответ на любой вопрос о ее внутреннем устройстве, чем мы и займемся.

Итак, имеется возможность создания модуля — некоей специальным образом оформленной программной единицы, которая может быть загружена в память для исполнения при помощи некоторой системной утилиты (insmode — далее И), при этом в качестве параметров запуска передается строка символов. Но, прежде чем посмотреть исходники, сначала немного подумаем, а как бы мы это сделали, если бы получили подобную задачу (а вдруг пригласят меня после этого поста в майнтейнеры Л — и ведь не откажешься). Данная строка может иметь в своем составе строго определенные лексические единицы, описание формата которых задается при создании исходного тексте модуля, и эти единицы содержат информацию, позволяющую изменить значение внутренних переменных данного модуля.

Определяется единица разбора вызовом макроса, которому сообщается необходимая информация — имя переменной, которую надлежит модифицировать в процессе настройки, ее внешнее имя (обычно совпадает с предыдущим), тип переменной из ограниченного набора и права доступа к переменной в стиле rw-rw-rw. Рассмотрим более внимательно способ описания вышеуказанных лексических единиц, это нам необходимо для рассмотрения различных вариантов решения. Очевидно, что эта информация необходима и достаточна (в совокупности с правилами оформления синтаксических единиц — разделителей и лексем) для построения парсера списка параметров, заданного в виде текстовой строки, но оставляет простор для реализации распределения функций между участника процесса. Дополнительно может быть задана (необязательная) текстовая строка описания переменной.

Для настройки модуля нам потребуется:

  1. формировать (ну это на этапе компиляции, можно делать как угодно, хотя все равно интересно, как именно) и хранить таблицу вышеуказанных параметров настройки,
  2. парсить входные параметры в соответствии с этой таблицей и
  3. производить изменение определенных областей памяти в соответствии с результатом разбора синтаксической единицы.

Порассуждаем немного в стиле «если бы директором был я» и придумаем возможные реализации. Как мы могли бы реализовать подобное поведение системной утилиты и модуля — начнем разбор вариантов в порядке возрастания сложности.

Данное решение просто, понятно и вполне реализуемо, но нужно учесть следующее обстоятельство: никоим образом нельзя оставлять разбор параметров на волю автора модуля, поскольку это предоставит ему недопустимый простор, а ведь два программиста всегда напишут три варианта парсера. Первое решение — утилита И почти ничего не делает, просто вызывает указанный ей модуль и передает ему остальные параметры в стиле командной строки, а уже модуль парсит их, опираясь на имеющуюся в нем информацию и осуществляет необходимые модификации. Мы и так пошли ему навстречу, допустив параметры неопределенного типа, которые имеют в качестве значения текстовую строку, хватит с него.

Поэтому некий стандартный парсер должен включаться в текст модуля автоматически, это несложно реализовать на уровне макроподстановки.

У этого решения есть два недостатка:

  1. непонятно, зачем нам вообще И, можно сразу вызывать модуль с параметрами из командной строки,
  2. код модуля (инициализационная часть) должен содержать все три раздела необходимой информации, причем эта информация необходима только при запуске модуля и в дальнейшем не используется, а место занимает всегда. Сразу оговоримся, что эта информация в обязательном порядке занимает место в файле, а вот в память при загрузке модуля может и не уходить, если все сделать аккуратно. Для того, чтобы сделать именно так, вспоминаем директивы _init и _initdata (кстати, а как они работают, надо бы разобраться — вот и тема очередного поста — будете ждать его с нетерпением?). Но и в последнем случае разделы 2 и 3 информации в файле явно избыточны, поскольку один и тот же код будет присутствовать во множестве модулей, злостно нарушая принцип DRY.

В силу отмеченных недостатков реализация данного варианта весьма маловероятна. Более того, непонятно, зачем тогда в макросе задавать информацию о типе параметра, ведь сам модуль прекрасно знает, что он модифицирует (хотя она, может быть, нужна для парсера при проверке параметров). Общая оценка вероятности подобного решения — процента 2-3.

Ну а 650 кб, которых должно хватить любой программе, были вообще чем то из области ненаучной фантастики. Необходимое отступление по поводу отмеченного недостатка номер 2 — я формировался, как специалист, в те времена, когда 256 кбайт оперативной памяти хватало для организации 4 рабочих мест, в 56 кбайтах работала двухзадачная ОС, а однозадачная ОС начинала работать при 16 кбайтах. Поскольку большая часть моих читателей сформировалась в иных реалиях, у Вас могут быть свои оценки предпочтительности того либо иного варианта. Поэтому я привык считать оперативную память дефицитным ресурсом и крайне неодобрительно отношусь к ее расточительному использованию, если это не вызвано крайней необходимостью (как правило, требованиями к быстродействию), а в данном случае я такой ситуации не наблюдаю.

Тогда мы сохраняем единообразие задания параметров и уменьшаем требования к размеру модуля. Второе решение — собственно парсер переносим в И, который передает модулю (его инициализационной части) извлеченные данные — номер параметра и значение. Решение лучше предыдущего, но все равно избыточная память в модуле остается. Остается вопрос, как обеспечить получение И списка возможных параметров, но это обеспечивается макросами путем создания заранее обусловленной структуры модуля и расположения блока в некотором конкретном месте (файла или памяти). Тем не менее вероятность реализации именно этого варианта не слишком велика — процентов 5. В общем то, решение мне нравится, поскольку мой парсер (а чем я хуже всех остальных программистов, у меня есть свой парсер, не лишенный недостатков, но точно не имеющий фатального) работает именно по этой схеме, возвращая главной программе номер отождествленного правила и значение параметра.

У нас появляется уникальная возможность менять параметры «на лету», не реализуемая в других вариантах. Под-вариант второго решения — передавать извлеченные параметры не в стартовую часть модуля, а непосредственно его загруженной рабочей части, например, через ioctl — требования по памяти те же. Недостаток — 1) потребуется заранее зарезервировать часть области функций под, возможно, не используемый запрос и 2) код модификатора должен присутствовать в памяти постоянно. Не очень понятно, зачем нам может потребоваться такая фича, но выглядит красиво. Оценка вероятности реализации — процентов 5.

Тогда в процессе загрузки бинарного кода модуля И может модифицировать данные в промежуточной памяти и загрузить код драйвера с измененными параметрами в место постоянной дислокации, либо осуществить эти модификации прямо в области памяти, в которую загрузил бинарник, причем таблица параметров, присутствующая в файле, в память может как грузиться, так и не занимать ее (помним про директивы). Третий вариант решения — переносим в И еще и модификацию параметров. Сразу же отметим основной недостаток подобного решения — невозможность проконтролировать значения параметров и их непротиворечивость, но тут ничего не поделать. Решение ответственное, потребует, как и предыдущее, наличия предопределенной области связи между модулем и И для хранения описания параметров, зато еще более снижает требования к излишней памяти в модуле. Вполне себе нормальное решение, скорее всего так и есть — процентов 75.

В принципе, то же самое можно сделать и в предыдущем варианте, когда модуль содержит конфигурационную часть, которая используется И в процессе загрузки, но в оперативную память, содержащую собственно исполняемую часть модуля, не загружается. Вариант третьего решения — информация о параметрах хранится не в самом модуле, а в некотором вспомогательном файле, тогда избыточной памяти в модуле просто нет. По сравнению с предыдущим вариантом добавился лишний файл и непонятно, за что мы платим, но, может быть, И делали до изобретения директив инициализации — процентов 5.

Ну а теперь, когда наша фантазия себя исчерпала (моя точно, если есть еще идеи, прошу в комментарии), приступим к изучению исходников Л. Оставшиеся 7 процентов оставим на прочие варианты, которые я придумать не смог.

Учитывая наличие вложенных инклудов, классическое изучение скачанных исходников при помощи редактора превращается в странноватый квест и будет малопродуктивным. Для начала отмечу что, судя по всему, искусство распределения исходных текстов по файлам утеряно вместе с ОС, умещающимися в 16 кб, поскольку структура директорий, их наименования и имена файлов связаны с содержанием чуть более, чем никак. Я свои дальнейшие изыскания проводил на сайте elixir.bootlin.com. К счастью, есть очаровательная утилита Elixir, доступная в онлайн режиме, которая позволяет осуществлять контекстный поиск, и вот с ней процесс становится куда более интересным и плодотворным. Да, этот сайт не является официальным сборником сырков ядра, в отличии от kernel.org, но будем надеяться, что исходники на них идентичны.

Расположен он в файле moduleparam.h — вполне разумно, но это приятная неожиданность, учитывая то, что мы увидим далее. Для начала посмотрим макрос определения параметров — во первых, мы знаем его название, во вторых, это должно быть проще (ага, сейчас). Макрос

module_param(name,type,perm)

представляет собой обертку над

{0a}module_param_named(n,n,t,p)

— синтаксический сахар для наиболее часто встречающегося случая. При этом почему-то перечисление допустимых значений одного из параметров, а именно типа переменной, дано в комментариях перед текстом обертки, а не второго макроса, который действительно делает работу и может быть использован напрямую.

Макрос {0а} содержит вызов трех макросов

{1}param_check_##t(n,&v)

(здесь есть набор макросов для всех допустимых типов),

{2}module_param_cb(n,&op##t,&v,p)

и

{3}__MODULE_PARM_TYPE(n,t)

(обратите внимание на названия, правда, прелесть), причем первый из них в других местах на используется, то есть рекомендациями Оккама и принципом KISS создатели Л также смело пренебрегают — видимо, какой то задел на будущее. Конечно, это всего лишь макросы, а они не стоят ничего, но все таки….

Первый из трех макросов {1}, как нетрудно понять из названия, проверяет соответствие типов параметров и обертывает

__param_check(n,p,t)

Обратим внимание, что на первой стадии обертывания уровень абстракции макроса понижается, а на второй повышается, наверное, по-другому нельзя, и мне только кажется, что можно было проще и логичнее, особенно учитывая, что средний макрос более нигде не используется. Ладно, положим в копилку еще один способ проверки параметров макроса и идем дальше.

Почему два, а не один — у меня не спрашивайте, я давно перестал понимать логику создателей Л. А вот два следующих макроса собственно и генерируют элемент таблицы параметров. Макрос {2}, как всегда, маскирует от нас макрос Скорее всего, исходя из разницы в стиле этих двух макросов, начиная с имен, второй из них был добавлен позже для расширения функциональности, а модифицировать имеющуюся структуру было нельзя, поскольку изначально пожалели выделить место для указания варианта параметров.

{2a}_module_param_call(MODULE_PARAM_PREFIX,n,ops,arg,p,-1,0)

(забавно, что этот макрос не вызывается напрямую нигде, за исключением 8250_core.c, причем там вызывается с таким же дополнительными параметрами), а вот последний уже продуцирует исходный код.

Не слишком обнадеживает, ведь нам может потребоваться поиск объекта вне текущего файла, но «в конце концов, другого у нас нет». Маленькое замечание — в процессе поисков убеждаемся, что навигация по текстам работает хорошо, но есть два неприятных обстоятельства: не работает поиск по фрагменту наименования (не найден check_param_, хотя check_param_byte обнаружен) и поиск работает только по объявлениям объектов (не найдена переменная — , то есть найдена в данном файле по ctrF, но встроенным поиском по исходникам не обнаруживается).

В результате работы {1} в тексте компилируемого модуля при наличии следующих двух строк

module_param_named(name, c, byte, 0x444);
module_param_named(name1, i, int, 0x444);

появляется фрагмент типа нижеприведенного

static const char __param_str_name[] = "MODULE" "." "name";
static struct kernel_param const __param_name \ __attribute__((__used__)) \ __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \ = { __param_str_name, ((struct module *)0), &param_ops_byte, (0x444), -1, 0, { &c } };
static const char __UNIQUE_ID_nametype72[] \ __attribute__((__used__)) __attribute__((section(".modinfo"), unused, aligned(1))) \ = "parmtype" "=" "name" ":" "byte"; static const char __param_str_name1[] = "MODULE" "." "name1"; static struct kernel_param const __param_name1 \ __attribute__((__used__)) \ __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \ = { __param_str_name1, ((struct module *)0), &param_ops_int, (0x444), -1, 0, { &i } };
static const char __UNIQUE_ID_name1type73[] __attribute__((__used__)) \ __attribute__((section(".modinfo"), unused, aligned(1))) \ = "parmtype" "=" "name1" ":" "int";

(на самом деле там порождаются однострочники, я разбил их на строки для удобства рассмотрения) и мы сразу же можем констатировать, что нет и намека на появление в исходном тексте включения программной секции парсера либо модуля присвоения значения параметрам, так что варианты 1 и 2 можно считать исключенными из дальнейшего рассмотрения. Наличие же специальных атрибутов для линкера как бы намекает на существование области связи, расположенной в некотором заранее определенном месте, через которую передается описание параметров. В то же время с недоумением отметим полнейшее отсутствие какого либо описания сформированного блока возможных параметров в виде текста, который мог бы использоваться модулем парсера. Понятно, что хорошо написанный код самодокументирован, но не до такой же степени, что опять не поднимает вероятность варианта 1 либо 2, с написанием парсера разработчиком модуля.

Забавно выглядит в последней порожденной строке сочетание атрибутов __used__ и unused одновременно, особенно, если посмотреть на следующий фрагмент кода макроса

#if GCC_VERSION < 30300
# define __used __attribute__((__unused__))
#else
# define __used __attribute__((__used__))
#endif

Что же такое забористое курят разработчики Л, уж больно извилист ход их мыслей, воплощенный в коде. Я знаю, что можно применять обе формы записи атрибута, но зачем это делать в одной строке — не понимаю.

Пока неясно, зачем это сделано, но сам факт не вызывает сомнения. Можно отметить еще одну интересную особенность полученного кода — дублирование информации о имени переменной и ее типе. Также остается неясной необходимость формирования уникального имени с использованием номера строки исходного кода, ведь в первой порожденной строке без этого обошлись. Конечно, эта информация когерентна, поскольку построена в автоматическом режиме, и эта когерентность будет сохраняться при изменении исходного текста (и это хорошо), но она дублирована (и это плохо), может, позднее мы поймем необходимость подобного решения.

Под капотом осталась стрингификация и двойная склейка параметров, формирование уникальных имен, а также другие хитрые приемы работы с макросами, я же излагаю только результаты. Еще одно примечание — выяснение того, во что именно превращается определение параметра, оказалось не совсем тривиальной задачей, но, благодаря MinGW, все таки было завершено. Подводя промежуточный итог, могу сказать, что изучение макросов Л — это не то, чем бы я хотел зарабатывать на жизнь, это возможно только в качестве развлечения, но продолжаем.

Дальнейшее изучения макросов нас в понимании задачи не продвинет, поэтому обращаемся к исходному тексту утилиты И и попытаемся понять, что она делает.

Да, я готов согласится, что И — утилита и взаимодействует с ядром через точку входа для загрузки модуля, но любая книга по драйверам Л нам рассказывает об этой утилите, поэтому отсутствие «официальной» версии ее исходников где-нибудь рядом с исходниками ядра вызывает у меня непонимание. Первым делом мы с изумлением убеждаемся, что требуемые сырки в исходники ядра не входят. Ну да ладно, Гугл не подкачал и на сырки мы все равно вышли.

Если у Вас установлен Л, то командой — вы можете узнать, из какого пакета утилита И собрана и далее искать именно его, но если мы проводим теоретические изыскания (лично я на домашнем компе Л не держу в силу ряда соображений, некоторые из которых я в своих постах излагал, такой вот боксер-теоретик) то данный способ нам недоступен и остается только поиск в Инет, к счастью, он дает результаты. Вторая удивительная вещь заключается в том, что данная утилита формируется из пакета, название которого никоим образом с ее названием не связано, таких пакетов более, чем один, и каждый назван по своему в разных местах — забавно, если не сказать больше.

Необходимое примечание — очень много вещей в языке С сделано по принципу «у нас так принято», наверное, когда-то сделать по-другому было трудно, или даже невозможно, ну да что теперь поделаешь, тащим с собой чемодан без ручки дальше. Ну и третья удивительная вещь, что собственно название утилиты нигде в исходниках не фигурирует, в названиях файлов не используется и встречается только в make файле, я знаю, что в С мы обязаны именовать главную функцию main, и это не обсуждается (лично я не в восторге от подобного, поскольку избалован Pascal, но моего мнения при проектировании языка не спрашивали), но хоть в комментариях написать внешнее имя утилиты можно было бы.

Далее изучаем только файл на git, тем более, что здесь он как раз называется insmod.c, обнаруживаем, что И для начала преобразует список параметров в одну длинную нуль-терминированную строку, в которой отдельные элементы разделены пробелами. Находим два пакета, содержащих исходный текст И, также находим сырки на github, видим, что они идентичны и принимаем на веру, что именно так выглядит исходный код утилиты. Вслед за этим он вызывает две функции, первая из которых называется grub_file и очевидно открывает бинарник, а вот вторая имеет имя init_module и принимает указатель на открытый файл с бинарником модуля и строку параметров и называется load_module, что позволяет предположить назначение именно этой функции, как загрузку с модификацией параметров.

Гугл опять спешит на помощь и возвращает нас к сыркам ядра под Elixir и файлу module.c. Обращаемся к тексту второй функции, которая лежит в файле… а вот тут облом — ни в одном из файлов исследуемого репозитория на Гит (ну это то как раз логично, это часть ядра и ее место не здесь) ее нет. Следует отметить, что, на удивление, имя файла, содержащего функции работы с модулями, выглядит логично, даже не пойму, чем это объяснить, наверное, случайно получилось .

Начиная с этого момента, становится понятным и отсутствие внятной внешней информации о структуре параметров, поскольку ядро набросило их само себе через свои же макросы и все о них прекрасно знает, а остальным и не нужно ничего о внутреннем устройстве знать (в свете того, что исходники доступны для обозрения, немного комментариев не помешали бы, но в принципе действительно все дальше понятно и без них), но на реализацию собственно механизма исполнения пока почти не бросает света. Теперь нам стало понятно отсутствие текста И рядом с ядром — она на самом деле не делает почти ничего, только переводит параметры из одной формы в другую и передает управления собственно ядру, так что недостойна она даже лежать рядом.

То, что точка входа в обработку данной функции оформлена неким особым образом, через SYSCALL_DEFINE3, косвенно свидетельствует в пользу второго варианта, но я уже давно понял, что мои представлениям о логичном и нелогичном, приемлемом и неприемлемом, а также о допустимом и недопустимом весьма существенно расходятся с таковыми у разработчиков Л. Замечание — по поводу передачи управления ядру я несколько погорячился, мы пока точно видим использование функции, в исходниках ядра определенной, а будет ли бинарная часть прилинкована к модулю, либо лежать собственно в образе ядра, пока неизвестно, надо расследовать дальше.

Примечание — еще один камешек в огород встроенного поиска — при поиске определения этого макроса я увидел множество мест его использования, как функции, среди которых очень скромно пряталось собственно определение его, как макроса.

Особенно, если учесть то обстоятельство, что пользователь вводит строку параметров в виде нуль-терминированной строки с пробелами в качестве разделителей и утилита в ядре преобразует ее в форму (argc,argv). Например, я совершенно не понимаю, зачем делать внешнюю утилиту, чтобы переводить параметры из стандартной для операционной системы формы (agrc, argv) в форму нуль-терминированной строки с пробелами в качестве разделителей, которая обрабатывается далее системным же модулем — такой подход несколько превосходит мои когнитивные способности. А поскольку я стараюсь придерживаться принципа «Считай собеседника не глупее себя, до тех пор, пока он не докажет обратное. Сильно напоминает старый анекдот «Снимаем чайник с плиты, выливаем из него воду и получаем задачу, решение которой уже известно». Если кто может предложить разумное объяснение изложенному факту двойного преобразования, то прошу в комментарии. И даже после этого, ты можешь ошибаться», и в отношении разработчиков Л первая фраза однозначно справедлива, то это означает, что я чего-то недопонимаю, а я так не привык. Но продолжим расследование.

И точно, в тексте функции load_module, мы довольно-таки быстро обнаруживаем вызов parse_args, — похоже, мы на верном пути. Перспективы осуществления вариантов 1 и 2 становятся «просматривающимися весьма слабо» (очаровательная формулировка из недавней статьи, посвященной перспективам разработки отечественных высокоскоростных АЦП), поскольку было бы весьма странно загрузить модуль в память при помощи функции ядра, а потом передать ему управление для осуществления реализуемой ядром функции, встроенной в его тело. Далее быстро проходим по цепочке вызовов (как всегда, мы увидим и функции-обертки, и макросы-обертки, но мы уже привыкли закрывать глаза на подобные милые шалости разработчиков) и обнаруживаем функцию parse_one, которая и размещает требуемый параметр в нужном месте.

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

Быстрый просмотр кода показывает, что применен грязный хак очевидный прием – в бинарном файле функция find_module_sections ищет именованную секцию __param, делит ее размер на размер записи (делает еще много чего) и возвращает необходимые данные через структуру. Однако, мы совсем упустили из вида вопрос о том, как функции разбора получают доступ к массиву образцов параметров, ведь без этого разбор несколько затруднителен. Я бы все-таки поставил буковки p перед именами параметров этой функции, но это дело вкуса.

Вроде бы все ясно и понятно, единственное, что настораживает – отсутствие атрибута __initdata у порожденных данных, неужели они остаются в памяти после инициализации, наверное, данный атрибут описан где-нибудь в общей части, например, в данных линкера, мне уже, честно говоря, лениво искать, смотри эпиграф.

Подводя итоги – выходные прошли с пользой, было интересно разбираться в исходниках Л, кое-что вспомнил и кое-что узнал, а знание лишним не бывает.
Ну а в своих предположениях я не угадал, в Л реализован вариант, который оказался в 7 оставшихся процентах, но уж больно он не очевиден.

Операционная система с разделением функций.
РАФОС. Ну и в заключение плач Ярославны (как же без него) почему необходимую информацию (я не имею в виду внутреннюю кухню, а внешнее представление) приходится искать по различных источникам, которые не имеют статуса официальных, где документ, аналогичный книге
«Программное обеспечение СМ ЭВМ. Руководство системного программиста.», или таких больше не делают?


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Беспроводные устройства Xiaomi в умном доме ioBroker

Приветствую всех любителей домашней автоматизации. Решил поделиться опытом использования беспроводных Xiaomi устройств с интерфейсом ZigBee. Я, честно говоря, против применения любых беспроводных устройств в любой автоматизации, от серьезных АСУТП больших объектов до малой автоматики типа охранно-пожарной сигнализации или умного дома, ...

[Из песочницы] Финансовый менеджмент в IT компании

Вопросы финансового учета, планирования и бюджетирования — острая и актуальная тема для всех  IT компаний. По мере роста компании денежные потоки сложно контролировать и становится очевидно, что нужен системный подход. Заказная разработка ПО, выпуск коробочных решений, наличие технической поддержки, выпуск ...