Pull to refresh
69
0.8
Иван Савватеев @SIISII

Микроконтроллеры, цифровая электроника, ОС…

Send message

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

strlen -- да, будет очень долгим, но он-то как раз станет ненужным, если длина хранится в явном виде. Что до malloc и прочих подобных вещей, то они завязаны не на язык, а на конкретную реализацию, которая может накладывать свои не очень-то обоснованные ограничения. В частности, нет принципиальных причин для того, почему нельзя выделить за одну операцию, скажем, 2**40 байтов -- ограничения имеются чисто технические (объём доступного файла подкачки, например: если выделяемая область не может быть целиком размещена в физическом ОЗУ, придётся её держать, хотя б частично, именно в файле подкачки, а он тоже резиновый не до бесконечности). Соответственно, как по мне, malloc должен выделять любой объём, какой способна выделить приложению ОС.

Ну, наиболее разумным выглядит выделение под счётчик стольких разрядов, сколько имеется в виртуальном адресе. Правда, 8 байт для 64-разрядных архитектур выглядит избыточным с практической точки зрения, но на них-то каждый байт уж точно считать не приходится, а при таком подходе (ширина поля длины = ширине виртуального адреса) можно было бы формат строки стандартизировать на уровне библиотеки для любых платформ.

Поправочка: напрямую PDP-11 могла адресовать только 64 Кбайта (если с разделением на код и данные, что есть у PDP-11/70, но отсутствует у многих других моделей -- то по 64 Кбайт кода и данных), 80286 -- в совокупности до 256 Кбайт в 4 сегментах. Выход за эти пределы -- только манипуляциями с регистрами MMU (на PDP-11) или с сегментными регистрами (на 80286), что является нетривиальной задачей и усложняет адресацию и не всегда возможно без вмешательства ОС на том или ином уровне.

Вообще-то, оптимизирующие компиляторы в 1970-е уже точно были.

Ну а усложнять компилятор для работы со строками не требуется, если с ними он работать не умеет (а сишный НЕ умеет, по большому счёту, единственная "поддержка строк" -- это формирование массива символов с завершающим нулём, что ничуть не сложней, чем формирование массива символов с предваряющей его длиной).

Вообще-то, мне за 50, и на Турбо Паскале я много что писал, причём ещё на Роботроне-1715 и лишь позже на IBM-совместимых ПК -- и всегда не понимал, что им мешало выделить хотя б 2 байта.

Ну, лично я изначально считал порочной идею не хранить длину строки в явном виде, а заканчивать строку нулевым символом. Да, длина строки будет ограничена максимальным значением счётчика, но если выделить под него 4 байта, то вряд ли найдётся разумный случай, где этой длины для строки будет мало (4 Гбайта или 4 Гсимвола в зависимости от того, как трактовать значение длины, что в данном случае вторично). Ну а выделение лишних трёх байтов даже на машинах с адресным пространством в 64 Кбайта (в т.ч. PDP-11, где, по сути, и появился Уних вместе с Це) -- отнюдь не гигантский перерасход памяти, не говоря о том, что на таких архитектурах можно было бы выделять под длину два байта, а не четыре -- всё равно строка в памяти не может быть длинней, чем этой памяти можно адресовать. (Ну а где каждый байт на счету в буквальном смысле -- пишите на ассемблере, и будет вам счастье.)

А вот с заключением автора не очень-то согласен. Противоречия возникают не между реальным поведением программ на реальных процах и "здравым смыслом", а между реальным поведением и ожиданием программиста, плохо понимающего (или вообще не понимающего), как в реальности работает железо. Когда такое понимание есть, сюрпризов будет намного меньше.

Ну, из контекста можно сделать вывод, что имелся в виду каждый бит точности, а не занимаемого места в памяти.

В PDP-11 просто возникало прерывание по неизвестной команде, так что была возможность программной эмуляции нужных команд при отсутствии железа, что выполнялось незаметно для прикладных программ (не считая более медленного выполнения, конечно) -- этим в определённых случаях пользовались.

Как в 8086 -- честно говоря, не помню, а поднимать документацию и разбираться лениво :) Но, по идее, тоже должно быть прерывание.

Ну, он сопроцессором и был -- в частности, он не мог самостоятельно выбирать команды, это делал настоящий процессор (8086). Собственно, сопроцессоры, которые не являлись обязательными, но могли наращивать возможности основного процессора -- вещь не новая; скажем, у многих процессоров PDP-11 были необязательные FPU, которые тем или иным путём следили за выборкой команд и перехватывали управление, когда видели свою команду. Это, конечно, не микропроцессоры, но идея довольно близкая.

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

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

Ну а что взгляд программиста и электронщика различается -- это да. Я говорил чисто про программную сторону вопроса.

И да, потоков как таковых в Линухе нет. Там изврат в виде процессов с общим адресным пространством (в Винде потоки -- это потоки, принадлежащие процессу, т. е. чётко отличается одно от другого).

Пы. Сы. Строго говоря, сравнительно недавно в Линухе появился асинхронный ввод-вывод -- IO_URING, если не ошибаюсь. Мне лично он показался переусложнённым; как по мне, ядро оси должно предоставлять максимально простые и низкоуровневые сервисы, а уж навороты поверх, если они нужны, должны строиться на прикладном уровне.

Компиляторы все умеют её использовать -- другое дело, что не всегда используется на практике, но это уже не к компилятору, а к программисту. А у автора, да, терминология хромает.

Вообще, можно. Я, например, склонен использовать термин "буфер", а не FIFO, и если б писал про DMA, то вряд ли FIFO упоминал бы -- хотя буферы были б оптом и в розницу.

Ну, с минсками у меня чисто теоретическое знакомство: в живом виде уже не застал (живое -- ЕСки и СМки, если самое старое).

Ну а 68000, как и Z8000 -- куда приятней в плане системы команд, чем 8086. Может, этим запомнился -- в сравнении, так сказать?..

Ага, я вот наткнулся пару лет назад на слишком уж агрессивную оптимизацию, из-за чего пришлось волатильными объявлять даже те вещи, которые таковыми, строго говоря, не являются: просто нет иных разумных и стандартных средств ограничить компилятор в оптимизациях для некоторых вещей. (Мне, по сути, требовалось, чтобы он не обращался заранее к одному полю структуры, вот после первого обращения в порядке записи в программе можно было бы использовать уже загруженную в регистр копию этого поля, что волатильность запрещает делать; ну а компиль об этом не знал и выбирал одной командой два поля, хотя в том месте программы ему нужно было только одно).

Ну, что 2, что 4... Если весь объём лезет во внутренний буфер самого UARTа, то точно проще и быстрей сделать на проце, если данные требуется досылать (или допринимать) отдельно -- уже вопрос.

Каналов DMA не очень много, но: а) иногда устройства, даже примитивные, сами умеют обращаться к памяти (например, в классических АТМЕЛовских армах так было -- не путать с современными микрочиповскими), либо центральное железо умеет работать с любыми устройствами (каналы ввода-вывода Системы 360); б) зависит от количества используемых устройств: если ДМА на всех хватает, то почему б (обычно) не использовать? Вот если не хватает -- тады конечно.

Ну, первым был 6502 на школьном Агате (кривом клоне Эпла-2), за ним -- СМ-4 и СМ-1420 (в девичестве PDP-11). Дальше точную хронологию уже не помню, но 8080 (куда ж без него), немного 8048, немного 8051, 8086, ЕС ЭВМ (System/360), немного Z80, немного Z8000, немного 68000, немного СМ-2М (HP21xx), AVR8 (один раз, зато коммерческий проект: МК выбрали до меня, и все хотелки на сях не полезли б в память :) ), обе 32-разрядные системы команд ARM (и собсно ARM, и Тумба), чуть-чуть MIPS, чуть-чуть PIC24 (но совсем чуть-чуть), сейчас вот начал поглядывать RISC-V... Ну, плюс теоретическое знакомство -- сходу 6800, IA-64 aka Itanium, БЭСМ-6, Минск-2/22/32, Минск-23... может, ещё кого забыл. В общем, если и не два десятка, то больше десятка точно наберётся, с чем прямо имел дело.

Насчёт нескольких дней -- ну, в определённых ситуациях так и есть, тут Вы правы. Скажем, для 8086, любого ARMа или той же Системы 360 простую переключалку можно-таки за несколько часов написать, ничего до этого не зная об их архитектуре, но уже имея опыт решения такой задачи на других архитектурах, а вот для IA-32 (80386 и его последыши) -- вряд ли, ведь придётся кучу всяких структур данных заполнять (таблицы дескрипторов и всё такое прочее), чтоб заставить его работать в нормальном 32-разрядном режиме (т.е. в защищённом).

А я вот никогда не использую ни HAL от поставщика, ни фриртос. Если проект достаточно сложный, чтоб оправдать реализацию в виде нескольких потоков, а не простого главного цикла, просто делаю свою переключалку потоков -- там работы на несколько часов даже на незнакомой архитектуре, с которой впервые столкнулся (при условии, конечно, что уже писал на паре десятков ассемблеров :) )

На самом деле, даже для какого-нибудь там UARTа на 9600 DMA тоже выгодней, если нужно переслать достаточно большой объём данных: не отвлекать проц от работы (или от сна :) ) по пустякам. Вот для передачи двух байтов его задействовать глупо: проц больше времени потратит на настройку DMA, чем на "ручную" пересылку этих самых двух байтов.

Вообще, отложенной обработки хватает всегда -- просто не всегда удобно обходиться только ей. Для большинства удобней вообще писать абсолютно последовательный код в виде обычных потоков, который приостанавливается при вызове чего-то долгоиграющего, а затем возобновляется с точки останова, когда ожидание закончилось. Но это возможно, только если за тебя кто-то сделал "нижний уровень" -- например, ОС :)

Information

Rating
1,828-th
Location
Солнечногорск, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Embedded Software Engineer
Lead