Pull to refresh

Comments 32

М.м… А сколько % CPU уходит на переключение потока?
Это зависит от того, каким событием вызвано переключение. Например, если это произошло вследствие вызова функции ожидания — то тратится время на перевод текущего потока в режим ожидания, поиск другого потока в состоянии готовности, перевод его в состояние исполнения, и восстановление его регистров. Считать такты лень, но я старался оптимизировать код диспетчера. Например, если поток был вытеснен при вызове им функции ожидания — то при последующем пробуждении этого потока состояние его регистров не восстанавливается. Считается, что функция ожидания может использовать все регистры, и если потоку нужно их сохранять — то он должен делать это сам. Если при этом сохранять только те регистры, которые используются потоком — то можно сэкономить на их сохранении/восстановлении.

Вообще, поскольку в данном диспетчере не предусмотрено псевдопараллельное исполнение (Round-Robin) — то потоки переключаются настолько редко, насколько это возможно. А сколько это времени займет от общего времени процессора — зависит от решаемой задачи. Программист должен учитывать, что переключение потоков — относительно дорогая операция, и стараться поменьше к нему прибегать. Но это справедливо и для современных ОС. Например, в некоторых местах MSDN четко указано, что WaitForSingleObject — дорогая функция. В Windows она значительно затратнее, чем в моем диспетчере, так как там реализовано больше функций, плюс защита памяти, переключение адресных пространств процессов.
Думается вопрос был про все переключения, то есть, про общую статистику
Всё зависит от приложения. Диапазон очень широкий. Можно составить приложение из двух потоков, которые постоянно будут обмениваться семафором. Тогда 99.99% процессорного времени будет тратиться на переключение контекста. И наоборот, можно составить однопоточное приложение или такое, где потоков несколько, но контекст не переключается (активный поток всегда отрабатывает до конца). Тогда накладные расходы будут 0%.

Так что правильнее говорить не о % процессорного времени для сферического случая в вакууме, а о количестве машинных тактов, необходимых на переключение контекста.

Для опубликованного мной диспетчера можно рассмотреть следующую ситуацию. Работающий поток вызывает функцию KeSetSynchrEvent. Контекст переключается на ожидающий поток c более высоким приоритетом, который до этого вызвал KeWaitForObject. Вооружаемся таблицей времянок Z80 и считаем количество тактов, когда исполняется код диспетчера от вызова KeSetSynchrEvent до возврата из KeWaitForObject в контексте ожидающего потока.

У меня получилось 648 (если не ошибся — работа очень кропотливая и муторная).

Много это или мало? На ZX-Spectrum Z80 работает на тактовой частоте 3,5МГц, так что время переключения контекста составляет 185мкс.

Время между кадровыми прерываниями составляет 69888 тактов (на оригинальном ZX). Если каждый кадр происходит в среднем два переключения контекста по описанному выше сценарию (100 переключений в секунду) — то имеем 1.8% процессорного времени.

Но это грубая оценка. Если интересует более точная — то переключения контекста возможны и по другим сценарием (например, при вытеснении по прерыванию). Исходник доступен, путь исполнения при тех или иных вариантах переключения контекста могу подсказать. Собственно же подсчёты предлагаю провести самостоятельно.
У меня под руками нет Z80, но я не вижу, как в нем реализовать именно вытесняющую многозадачность.

Скажем, обычный код

di
m: jp m

введет в ступор всю систему, ибо NMI (тут могу ошибаться, давно дело было) не позволяет баловаться с «чужими» запретами на прерывания.

Может, все-таки корпоративная многозадачность получилась?
Нет, вытесняющая. Смотрите статью Википедии по ссылке. Реализована многозадачность, а не защита. Это разные вещи. Потоки, исполняемые в моей системе, не должны запрещать прерывания. Если они их запрещают — то работа системы будет нарушена. Подобные ограничения присутствуют в AmigaOS, например, и это не мешает данной ОС по праву иметь вытесняющую многозадачность.

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

И да, вопрос уже «на интерес»: а в промышленном применении сейчас это имеет смысл? или только как задачка «сделать, потому что никто не делал»?
Имеет прямой смысл на микроконтроллерах. Разрабатывая алгоритмы их работы, я часто был вынужден реализовывать те или иные упрощенные формы многозадачности. Наличие универсального диспетчера позволяет применить единообразный подход и сократить время на разработку.

За счет минималистичности и хорошей комментированности моего диспетчера, его перевод на другой ассемблер не должен составить труда. Вместе с тем есть возможность свести к минимуму затраты ресурсов на реализацию многозадачности, отказавшись от ненужных в конкретном проекте функций диспетчера.
Черт, сегодня как-то у меня слог не слаживается. Я не спорю про полезность диспетчера, даже на микроконтроллерах. Вот прямо сейчас я на stm32 аналогичное использую.

Я именно про Z80. Ведь его вроде больше не выпускают и не используют?
Выпускают и используют. Есть большая группа энтузиастов ZX Spectrum, в которой идет разработка новых программ под этот компьютер. Иногда удается привлечь открытия науки и техники, которые появились уже после ухода этого компьютера из мейнстрима. В результате на нем удается реализовать вещи, которые раньше были немыслимы для компьютеров такого класса.

Пишутся новые игры, демонстрации, системные и прикладные программы. Разрабатываются компиляторы, языки программирования.

Какой в этом смысл? А смысл в том, чтобы оттачивать мастерство. Ограниченность ресурсов вынуждает искать нетривиальные решения. Но зачастую им впоследствии находится применение уже далеко за пределами этого ретро-компьютера.
Как говорится, обалдеть. Глядишь, так и Радио-86РК к чему-нить полезному приспособить придумают :)
«Радио-86РК» тоже живет и побеждает. Есть и у него группы энтузиастов, ведущих новые разработки. Но я этим не интересовался. Можно погуглить, если интересно.
Про файберы вместо вытесняющей многозадачности не думали?
Там цена переключения минимальна, что для контроллера — важнейший плюс.
Пока не думал. Спасибо что подсказали. Кстати, я давно уже задумывался на тему подходящих применений для файберов. Пока почти ничего не приходит на ум, кроме задач обработки avi-файлов и подобных им. Может быть у вас есть идеи?
Первое и самоочевидное — итераторы, одинаково удобные и для реализации, и для использования.
Второе, имеющее прямое отношение к микроконтроллерам — обработка ввода по событиям.
Можно писать простой линейный код без разбиения на отдельную обработку каждого байтика, при этом все остальное тоже будет работать, причем прозрачно.
Третье — работа в стиле «потоки данных», можно реализовать сложные алгоритмы в виде цепочек простых фильтров, работающих одновременно без промежуточных буферов.
При этом процессор не простаивает, оверхед от переключений минимальный, а уровень абстракции высок.
Еще можно обратить внимание на реализацию кооперативной многозадачности на базе форт-системы — там стоимость переключения можно вообще почти в ноль уронить.
Пожалуйста объясните подробнее про итераторы и обработку ввода по событиям, приведите примеры, а то я ничего не понял. Понял только про работу в стиле «потоки данных», но это я сам имел в виду под «обработкой avi-файлов».

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

(примечание для Grammar Nazi: слово «конгениально» употреблено в шутку в неправильном значении намеренно ошибочно. Мне известно его правильное значение).
UFO just landed and posted this here
Ок, учту. Главное — чтобы Grammar Nazi тоже знали, что это означает!
Про обработку ввода по событиям.
Допустим, мы грузим что-то по сети, при этом давая возможность пользователю вводить с клавиатуры.
Информация приходит маленькими кусочками и нам приходится дробить код на множество мелких операций, выглядящих как каша с постоянной проверкой состояния и переходами.
На файберах мы можем сделать так:
1. Файбер, проверяющий пришедшее по сети и кладущий результат (если есть) в файбер-канал 1 иначе отдающий управление фиберу-планировщику.
2. Файбер, проверяющий нажатие клавиши и кладущий результат (если есть) в файбер-канал 2 иначе отдающий управление фиберу-планировщику.
3. Файбер, читающий файбер-канал 1 и выполняющий пришедшие по сети команды
4. Файбер, читающий файбер-канал 2 и выполняющий пришедшие с клавиатуры команды
5. Файбер-планировщик (например, round robin)

Файбер-канал — вместо записи в буфер переключает на планировщик, а при чтении берет по оставленному при записи указателю сколько есть… и переключает на планировщик, если недостаточно.

В результате логика каждой функции проста и линейна, а параллельность обеспечивается ручным переключением на планировщик и каналами на файберах.
Спасибо большое. Было бы хорошо иметь на Хабре пост о применениях файберов. Пишите!
Я как-то чисто в учебных целях написал на файберах сервер для Win32 без единого потока.
Иэх…
Делал нечто подобное чуть более 20 лет назад.
Точнее — не делал, а перепиливал некую ОСРВ РТ/ОС-11 (в написании сильно не уверен):
* Не для Z80, а для 8080
* зашиваемое в ПЗУ
* для промышленных контроллеров КРВМ (для буровых, такие себе герметичные ящики, зарываются в землю лет на 15..20 (по документации)).
Не понравилось «API» — дизасемблировал, почистил, выкинул дебагер, редактор — осталось только ядро (практически — планировщик).
Вытесняющая (или вытесняюще-корпоративная?..) многозадачность — все потоки заканчивались nop (перед которым взводили таймер — когда их будить (опрос датчиков же)). Сам планировщик тоже заканчивается nop.
Защиты памяти, еснно, нет (а кому она нужна в промконтроллерах?).
PID'ы, мютексы (как это называется сейчас, оказывается) — всё на месте.

200 байт.

хорошее было время… Неторопливое.
200 байт и мутексы — очень впечатляющий результат. У меня получилось значительно больше байт (610 включая данные и стек трех потоков). Можете поделиться вашей разработкой?

Насчет ОСРВ — их было две. Первая — RT-11 (более ранняя и простая), вторая — RSX-11 (она же ОСРВ, более поздняя и развитая). Но это же на «мини-ЭВМ», которые занимали целую комнату! Там еще был процессор PDP-11. Это очень не похоже на 8080. Думаю, перепилить пришлось многое!
Транслирую из лички ответ TIEugene (он не может сам написать из-за слитой кармы):
* мютексы и вся остальная динамика были в ОЗУ, а все программы — в ПЗУ, поэтому расход памяти надо считать отдельно.
* вся алхимия сводится к:
— все задачи _перед_ nop ставят себя в очередь, заказывая — когда их разбудить (в тиках)
— раз в 1/50s подрывается NMI и подрывает ядро
— которое декрементирует счетчик тиков, и те задачи, которым пора
— удаляет из очереди
— вызывает
Всё
Задачи сами между собой разбираются. Ядру же — пофик.
* мютексы… ну как — мютексы… Например задвинуть задвижку — это запуск четырех задач:
— одна — быстро запускает движок (если выставлен флаг «запустить движок»), выставляет флаг «движок пошел» — и тикает
— вторая — вешается на прерывание от датчика концевика (или вешается на таймер опросить датчик — случаи разные бывают), быстро считывает, записывает куда-то данные, выставляет флаг «готово» — и тикает.
— третья — обрабатывает данные (если они есть)
— четвертая — следит, чтобы из п.1 дошло до п.2 хотя бы. Или вызывает пожарных.
Ну и т.д.
Да, приходится рисовать state machine и подсчитывать такты процессора для каждой задачи (min и max). А что делать?

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

Наверно всё-таки hlt они заканчивались, а не nop
--некую ОСРВ РТ/ОС-11

RT11SJ наверное.

В Томском Политехе ее перепилили в многопроцессорную, многозадачную и перемещаемую еще в 1991-м.
Как-то делал подобное на 51 контроллере. В нем очень удобно реализовывать до 4 задач т.к. есть 4 банка регистров.
Вспоминается, как загружаешь Dizzy, остаешься в интерпретаторе BASIC'е, запускаешь функцию старта прерывания IM2 (по которому идет обработка ввода клавиатуры/джойстика и движущихся объектов), идет выход назад в интерпретатор — а Dizzy начинается бегать прям по текстовым командам на экране, реагируя на джойстик — пока не добежит до области, где ничего не напечатано («нет земли») — и полетел кувыркаясь по диагонали =). При этом интерпретатор BASIC'а успешно работал одновременно с процедурой обработки джойстика и отрисовкой движения Dizzy.
Sign up to leave a comment.

Articles