В прошлой части возник вопрос организации программы по задачам. Чтобы можно было разбить программу на кучу независимых частей и не заморачиваться на тот счет, что где то у нас будет затык.
Затык, конечно может быть, это все же не вытесняющая многозадачность с защищенным режимом, но разрулить все будет гораздо проще.
Общая диаграмма работы ОС
Что из себя представляет задача
Это практически то же самое, что и процедура, вызываемая командой RCALL с тремя отличиями:
- Вызывается она не мгновенно, а в порядке очереди.
- Вызов задачи идет не по ее адресу, а по ее порядковому номеру в таблице переходов.
- Возврат из нее идет не в то же место откуда вызывали, а в цикл диспетчера задач.
Сама задача представляет собой обычную процедуру, записанную без каких либо замудреностей.
Распологается там, где обычно прописаны вызываемые процедуры. В этом отношении ничего не поменялось. В принципе, вызывать можно код и из внешнего файла, главное правильно прописать все это в таблице переходов.
В тестовом примере это выглядит так:
Расположение: Trash-rtos.asm — главный файл программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | ;=========================================================
;Tasks
;=========================================================
Idle: RET ; Задача пустого цикла, но ничего
; не мешает сунуть сюда любой код.
; Он будет выполнен. В последнюю очередь.
;-------------------------------------------------------
Fire: LDS OSRG,U_B ; Это код задачи "Fire"
OUTI UDR,'A' ; Выдрано из реального проекта
; Суть данного кода не важна
CBI PORTD,7 ; Поэтому можешь не вникать. Тут
NOP ; Может быть абсолютно любой
NOP ; код -- код твоей задачи!
SBI PORTD,7
; Если любопытно, то тут обычное
LDS Counter,PovCT ; заполнение сдвигового регистра
LDPA Lines ; из трех микросхем 74HC164
; средствами SPI передатчика
CLR OSRG ; Оставил его лишь для примера.
; Чтобы наглядно показать, что
ADD ZL,Counter ; Из себя представляет задача.
ADC ZH,OSRG
LPM OSRG,Z+
OUT SPDR,OSRG
Wait0: SBIS SPSR,SPIF
RJMP Wait0
INC Counter
CPI Counter,150
BRSH Clear
STS PovCT,Counter
RET
Clear: CLR Counter
STS PovCT,Counter
RET ; Выход из задачи только по RET!!!
;-------------------------------------------------------
Task2: RET ; Это пустые заглушки. На этом месте
; могла бы быть ваша задача! :)
;-------------------------------------------------------
Task3: RET ; Аналогично, надо будет задействую.
;-------------------------------------------------------
Task4: RET ; Названия в стиле Task4 тоже живут
; недолго. Обычно переименовываю
;-------------------------------------------------------
Task5: RET ; Как с задачей "Fire"
;-------------------------------------------------------
Task6: RET
;-------------------------------------------------------
Task7: RET
;-------------------------------------------------------
Task8: RET
;-------------------------------------------------------
Task9: RET |
Таблица переходов
После всего кода задач распологается таблица переходов и код самой ОС:
Расположение: Trash-rtos.asm — главный файл программы, в самом низу, в конце ПЗУ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ;======================================================================== ; RTOS Here ;======================================================================== .include "kerneldef.asm" ; Настройки ядра - переменные и ряд макросов. .include "kernel.asm" ; Подклчюаем ядро ОС. ;Это таблица переходов. TaskProcs: .dw Idle ; [00] Она содержит в себе реальные адреса задач .dw Fire ; [01] Как видишь, 0 тут у задачи пустого цикла, .dw Task2 ; [02] 01 у "Fire", ну и дальше .dw Task3 ; [03] По порядку. .dw Task4 ; [04] .dw Task5 ; [05] .dw Task6 ; [06] .dw Task7 ; [07] .dw Task8 ; [08] .dw Task9 ; [09] |
Причем, в таблице задач не обязательно должны быть разные задачи. Можно делать одну, но на разные ячейки, например так:
1 2 3 4 5 6 7 8 9 10 | TaskProcs: .dw Idle ; [00] .dw Fire ; [01] .dw Task2 ; [02] .dw Task3 ; [03] .dw Task4 ; [04] .dw Fire ; [05] .dw Task6 ; [06] .dw Idle ; [07] .dw Idle ; [08] .dw Task9 ; [09] |
Это иногда бывает удобно.
Таблица переходов нужна для того, чтобы можно было любому адресу в программе дать адрес-смещение относительно начала таблицы перехода. То есть, теперь, чтобы перейти на Task4 нам не нужно знать точный адрес этой задачи, достаточно лишь знать, что ее адрес записан в таблице переходов в четвертой ячейке. Адрес начала таблицы переходов у нас фиксированный, поэтому просто прибавляем к нему смещение (равное номеру задачи*2) и берем оттуда искомый адрес. Благодаря этому, мы можем в очереди задач держать не двубайтные адреса переходов, а однобайтные номера под которыми эти адреса размещены в таблице.
А чтобы не путаться под каким номером какая задача спрятана, то введем для них символическое обозначение:
Расположение: kerneldef.asm — файл макроопределений ядра
1 2 3 4 5 6 7 8 9 10 | .equ TS_Idle = 0 ; Просто нумерация. Не более того .equ TS_Fire = 1 ; Зато теперь можно смело отправлять в очередь .equ TS_Task2 = 2 ; задачу с именем TS_Fire и не париться на счет того, .equ TS_Task3 = 3 ; что запишется что то не то. .equ TS_Task4 = 4 ; Тут все по порядку, жестко привязано к ячейкам таблицы! .equ TS_Task5 = 5 ; Так что если в таблице и можно делать одинаковые задачи, .equ TS_Task6 = 6 ; то тут у них идентификаторы должны быть разные!!! .equ TS_Task7 = 7 ; А имена можно придумывать любые, они не привязаны ни к чему, .equ TS_Task8 = 8 ; Главное самому не забыть что где. .equ TS_Task9 = 9 |
Очередь задач.
Логически выглядит как строка в памяти, где каждый байт это номер задачи. Два числа 0 и FF зарезервированы системой. 0 — это Idle, холостой цикл диспетчера. FF — нет задачи, конец очереди.
В коде это выглядит так:
Расположение: Trash-rtos.asm — главный файл программы, самое начало. Где идет разметка ОЗУ
1 2 3 | .DSEG .equ TaskQueueSize = 11 ; Длина очереди задач TaskQueue: .byte TaskQueueSize ; Адрес очереди сотытий в SRAM |
Длина очереди задается с запасом, чтобы не произошло ее срыва. Располагать ее лучше после остальных данных, чтобы она тянулась навстречу стеку.
Диспетчер задач
Это небольшая процедурка, которая берет из очереди байт, сравнивает его с FF. Если равно, значит очередь пуста и происходит выход в главный цикл. Если же там есть какое либо число отличное от FF, то оно загружается в регистр, происходит вычисление адреса по таблице переходов и прыжок на адрес задачи и выполнение кода. Перед прыжком текущий номер задачи удаляется из очереди, а вся очередь сдвигается на один байт вперед. При следующем заходе в диспетчер все повторяется заново до полного опустошения очереди. При этом в очередь можно добавлять новые задачи.
Главный цикл программы при этом выглядит следующим образом:
Расположение: Trash-rtos.asm — главный файл программы
1 2 3 4 5 | Main: SEI ; Разрешаем прерывания. WDR ; Reset Watch DOG RCALL ProcessTaskQueue ; Обработка очереди процессов (Диспетчер) RCALL Idle ; Простой Ядра RJMP Main |
Сам обработчик очереди несложен. Распологается в файле kernel.asm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | ProcessTaskQueue: ldi ZL, low(TaskQueue) ; Берем адрес начала очереди задач ldi ZH, high(TaskQueue) ; Напомню, что это в ОЗУ. ld OSRG, Z ; Берем первый байт (OSRG = R17 рабочий cpi OSRG, $FF ; регистр OS) Сравниваем с FF breq PTQL02 ; Равно? Значит очередь пуста - выход. clr ZH ; Сбрасываем старший байт lsl OSRG ; А взятый номер задачи умножаем на 2 mov ZL, OSRG ; Так как адреса у нас двубайтные, а значит ; И ячейки в таблице перехода двубайтные ; Получается смещение по таблице subi ZL, low(-TaskProcs*2) ; Прибавляем получившееся смещение к адресу sbci ZH, high(-TaskProcs*2) ; начала таблицы переходов. Ну и что, что AVR ; не умеет ; складывать регистр с числом + перенос. ; Зато умеет вычитать, а минус на минус дают плюс! :) ; Математика царица всех наук! ; Теперь в Z у нас адрес где лежит адрес перехода. lpm ; Берем этот адрес! Сначала в R0 mov OSRG, r0 ; Потом в OSRG ld r0, Z+ ; Эта команда ничего ценного на грузит, мы ее применили ; Ради "Z+" чтобы увеличить адрес в Z и взять второй байт ; Целевого адреса по которому мы перейдем. lpm ; Берем в R0 второй байт адреса. mov ZL, OSRG ; А из OSRG перекладываем в ZL mov ZH, r0 ; И из R0 в ZH. Теперь у нас в Z полный адрес перехода. ; Можно драпать, в смысле IJMP - индексный переход по Z ; Но пока рано! Надо же еще очередь в порядок привести! push ZL ; Спрячем наш адрес, наше сокровище, в стек... push ZH ; Глубоко зароем нашу прелесссть.... ; Займемся грязной работой. Продвинем очередь. ldi Counter, TaskQueueSize-1; Загрузим длинну очереди. Иначе мы всю память ldi ZL, low(TaskQueue) ; подвинем. И хапнем в Z начало очереди. ldi ZH, high(TaskQueue) cli ; Запретим прерывания. А то если очередь сорвет получим ; армагедец. PTQL01: ldd OSRG, Z+1 ; Грузим из следующего Z+1 байта и перекладываем st Z+, OSRG ; все в Z, а Z после этого увеличиваем на 1 dec Counter ; Уменьшаем счетчик (там длинна очереди!) brne PTQL01 ; Если не конец, то в цикле. ldi OSRG, $FF ; А если конец, то по последнему адресу записываем FF st Z+, OSRG ; Который является признаком конца очереди. sei ; Разрешаем прерывания. Можно расслабиться pop ZH ; Достаем из стека нашу прелессть... наш адрес перехода pop ZL ; Оба байта, старший и младший. ijmp ; Переходим в задачу!!! ; Обрати внимание - сюда мы пришли по RCALL из главного цикла ; Значит в стеке у нас лежит адрес возврата. А ушли мы в задачу по IJMP ; который стек не меняет. Но это не страшно! Ведь из задачи мы ; выходим по RET! PTQL02: ret |
Для большей понятности нарисовал диаграммку со стрелочками разными, про то как формируется адрес в таблице переходов:
Задачи кладутся в очередь другой процедурой:
1 2 | ldi OSRG, TS_Task4 ; Запускаем в очередь задачу Task4 rcall SendTask |
Делать это можно где угодно, хоть в прерывании, хоть в другой задаче.
Для удобства был написан макрос:
1 | SetTask [task] |
Сама процедура SendTask работает тоже несложно, она всего лишь ставит задачу в очередь.
Расположение: kernel.asm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | ; OSRG - Event ; В рабочем регистре ОС - номер задачи SendTask: push ZL ; Сохраняем все что используется push ZH ; в стеке push Tmp2 push Counter in Tmp2,SREG ; Сохраняем значение флагов push Tmp2 ldi ZL, low(TaskQueue) ; Грузим в Z адрес очереди задач. ldi ZH, high(TaskQueue) ldi Counter, TaskQueueSize ; А в счетчик длинну очереди, чтобы ; не начать всю память засаживать. cli ; запрещаем прерывания. SEQL01: ld Tmp2, Z+ ; Грузим в темп байт из очереди cpi Tmp2, $FF ; и ищем ближайшее пустое место = FF breq SEQL02 ; Если нашли, то переходим на сохранение dec Counter ; Либо конец очереди по счетчику. breq SEQL03 rjmp SEQL01 SEQL02: st -Z, OSRG ; Нашли? Сохраняем в очереди номер задачи. SEQL03: pop Tmp2 ; Возвращаем флаги. Если там прерывание было out SREG,Tmp2 ; разрешено, то оно вернется в это значение. pop Counter ; Выходим, достав все заныченное. pop Tmp2 pop ZH pop ZL ret |
Архив с исходниками и работающим проектом для ATMega8
З.Ы.
Ухх, ну накатал телегу. Аж самому страшно. А там еще столько же, про таймерную службу, да пошаговые руководства по инсталляции и использованию… Но это после. Пока переварите это.
Продолжение следует…





Возможно, забегу на перёд, но вот такие вот вопросы появились. Я так понял, что тут «задача» и «событие» — это синонимы? И задача выполняется полность, т.е. пока не упрётся в свой RET, проц будет полностью отдан ей?
После публикации первой затравочной части, я тоже задумался, как бы сделать ОС на мк. Можно было бы отдать один таймер чисто под ОС, и по его интерапту делать квантование времени, отдаваемого под задачу (по ходу сохраняя PC). Т.е. делать прыжки по очереди задач через определённые промежутки времени, не важно, завершилась задача или нет. Память и стек у каждой задачи свои (внешняя SRAM на крайняк). А размер самой задачи не имел бы значения, хоть там цикл крутить. Праавда, отсюда доп. правила на написание задач…
Жаль в исходниках нет везде таких подробных комментов, как в статье)
Фактически да. Синонимы.
Тут задача выполняется до тех пор, пока не упрется в RET и это надо учитывать. Т.е. писать ее максимально короткой и быстрой, разделяя все циклы между многими кусками, развесив все на службу таймеров.
Квантовать с полноценным отрубом текущей задачи на АВР крайне сложно. Приходится очень многое сохранять, а ОЗУ мало. Одним PC тут не ограничешься, нужно еще все регистры сохранять, флаги. Опять же стэк у каждого свой, выделенная память. Я думал над этим, но все получается слишком жирно, а хочется иметь универсальную крошечную ОС, способную развернуться даже на Тини13.
Да, ещё и регистры. Можно и не все, это уже зависит от логики ОС. И, в общем, выделать под задачу в сумме байт по 30-60.
Просто в данном примере описан, скорее, менеджер очереди, чем ОС. ОС, в моём понимании, предполагает захват и круговую оборону всего железа. Для задач — API.
…крошечную ОС. Просто само словосочетание «Операционная система» слишком большое чтобы засунуть его в Тайни).
А ОС начинается с диспетчера задач. Тут вопрос скорей терминологий, где у крокодила отрубить хвост :)
Сюда же можно довесить и полноценный API и приоритеты и много чего еще. Если это действительно надо.
Вообще — то для малых контроллеров, особенно типа Tiny 13,15, — изменяемая очередь задач, по — моему, избыточна. Другое дело — к примеру, на Меге 128, с файловой системой, консолью, дисплеем, флэшкой на гигобайт…
А для простых я в главном цикле просто просматриваю задачи по очереди (их редко бывает больше десятка), проверяя их флаги состояния, и если задача неактивна — пропускаю ее. Это занимает меньше ресурсов, чем перезапись очереди.
Сами же задачи делаю более крупными, но выполняю их поэтапно, выставляя по ходу флажки в слове состояния задачи, запуская и проверяя контрольные таймеры.
Критичные события работают по прерываниям, совершая минимум действий (например, считывание принятого с UART байта в буфер), и выставляя флаги для обработки их в соответствующей задаче. В результате кольцо задач крутится довольно быстро.
Квантовать задачи принудительно по времени — требует интенсивной работы стека и сохранения кучи параметров, затем их восстановление, кроме того, специфика работы микроконтроллеров в задачах реального времени такова, что не всякое действие можно прервать в любой момент без нежелательных последствий.
Для 13-15 избыточно, да. Но возможно извратиться. А вот уже на Тини2313 очень удобно. А главное получается единый шаблон в который можно уложить практически любую программу. Резко ускоряется написание логики, а оверхед получается очень незначительным, т.к. всю структуру в любом случае пришлось бы с нуля писать, а тут уже все готовое. Где то избыточное, но в большинстве случаев это оправдано. А по поводу проверки флажков… если у тебя на каждую задачу по три флажка, десяток задач, то это сколько же проверок условий будет. Вряд ли оно будет быстрей чем сдвиг очереди. Тем более очередь может быть и короткой, на считаные единицы задач.
Флажки я обьединяю в слова состояния задачи. Если байт слова состояния задачи = 0, пропускаю. Это всего 1-2 команды: загрузка слова состояния в аккумулятор и пропуск вызова, если 0, (или, наоборот, вызов, если не 0 — смотря какой контроллер). Подпрограмма задачи побитно проверяет остальные флажки, соответствующие этапам выполнения задачи, и выполняет соответствующие действия, сбрасывая и устанавливая флажки. Обработчик прерывания обрабатывает срочные события и также выставляет или сбрасывает флажки в слове состояния соответствующей задачи, а также крутит программные таймеры. Если флажков у задачи много, делаю 2-3 байта, располагая в порядке нужности. (В главном цикле проверяется только основной бйт).
У PICов, например, короткий стек (кроме старших моделей), зато есть команды проверки бит с переходом (пропуском) по результатам, для них мой подход получается проще. Для I8080 и I8048 когда-то тоже получалось неплохо.
А сдвинуть очередь даже из 5 задач — это переписать с места на место до десятка байт, а это 2-3 десятка команд… Добавить в очередь — тоже одной командой не обойдешься, да и длину надо скорректировать. Не, для десятка задач флажки проще. Тем более что и тебе тоже без флажков не обойтись, особенно если сами задачи короткие, — тогда будет больше взаимодействия между ними.
Собственно все. Сдвинуть одну задачу это одна итерация. Можно ускорить введя индекс текущей задачи, тогда очередь можно не двигать, а двигать по кругу ползунок указателя, записывая новые задачи до него
Строчек — то немного, а вот количество реально исполняемых машинных команд — десятки. Опять же, для простых программ вот такая структура агоритма проще и нагляднее:
Start:
if Sost1 > 0 then call Zad_1;
if Sost2 > 0 then call Zad_2;
if Sost3 > 0 then call Zad_3;
if Sost4 > 0 then call Zad_4;
if Sost5 > 0 then call Zad_5;
….
Goto Start;
Zad_1:
if Sost1.0 > 0 then call Zad_1_0;
if Sost1.1 > 0 then call Zad_1_1;
if Sost1.2 > 0 then call Zad_1_2;
….
Return;
Zad_2:
if Sost2.0 > 0 then call Zad_2_0;
if Sost2.1 > 0 then call Zad_2_1;
if Sost2.2 > 0 then call Zad_2_2;
….
Return;
Zad_1_1:
{Выполняемый кусок задачи}
….
Sost1.1 := 0; Sost1.2 := 1;
Return;
И так далее.
В принципе, можно и так. Да, будет быстро.
А по поводу наглядности… фиг знает, я в таких ветвлениях быстро путаюсь. Мне проще эвентов в кучу накидать. Тем более под каждую задачу делать дополнительную проверку, по сути один и тот же код дописывать.
А тут единое ядро, подключил и забыл.
Ты — то забыл, а контроллер его постоянно крутит, тратя ресурсы, которых в младших моделях и так немного, особенно памяти. Вообще, приличная операционка действительно нужна там, где есть чем рулить, и где кроме жестко заложенных задач, есть еще подгружаемые, например, с флэшки, в виде файлов. Вот на обслуживание файловой системы, консольного ввода — вывода, диалога с оператором, WEB — сервисов, USB — тут действительно уже без операционки труба. По крайней мере, без нее проще не будет. Но это уже начиная примерно с Меги32. Меньше — уже не всегда необходимо, зависит от задач. А на Меге 128 и выше — система нужна однозначно («мигалки» на них лепить никто не будет).
Сразу узнается программная реализация конечного автомата (он же FSM — Finite State machine) :-).
Уххх!!! Вот это сила! Еще одна бессонная ночь гарантирована мне)) Теперь надо переварить прочитанное и поэкспериментировать!
з.ы. завтра прочту еще и статью в журнале для полного счастья
Завтра будет еще продолжение, про службу таймеров :)
Вся эта система с добовлением в очередь и диспетчером напомнила мне State Machine в LabVIEW и там есть еще замечательная фишка добавление событий в конец и в начало очереди по приоритетам. Надеюсь это будет в планах)
Задумка с началом и концом уже давно на примете, правда пока не было задачи где бы она понадобилась, а так наверное реализую.
Полностью согласен с позицией «Лишнее всё это». Обычно количество квазипараллельных процессов ограничено, и известно изначально. Требования по каждому из процессов в плане времени реакции и возникновения возможных тормозов тоже изначально известны, и не меняются в одном проекте.
Поэтому я лично предпочитаю набор из нескольких конечных автоматов. Изначально линейные процедуры режутся в местах возникновения тормозов. Например, при передачи строки через uart имеем два фрагмента — (1)проверка готовности регистра передатчика и (2)выборка элемента строки с запховыванием его в UDR.
Рулится это переменной «фаза», которая может принимать любое значение из подмножества состояния автомата. Каждый из фрагментиков автомата выполняет своё действие, и по его результатам воздействует на значение переменной «фаза». В результате следующее вхождение в автомат вызовет либо повтор текущего действия, либо следующее действие.
А несколько автоматов вставляются в основное тело программы, которое представляет собой бесконечный цикл.
Ох, это проще показать, чем на пальцах объяснить.
Преимущества такие: практически нулевой расход памяти и низкий расход процессорного времени на работу собственно переключателя.
Недостатки — тему надо вкурить.
После понимания принципа всё становится ясно и просто, но до понимания — программа похожа на жуткую мешанину кода. Особенный ступор это зрелище вызывает, когда его видишь на дизассемблерном листинге.
С другой стороны это даже плюс. Труднее похачить. Ну и новое знание оно это. Пользительно бывает.
У всех методов есть проблемы — условий много городить приходится. Ну и в голове всю структуру держать. Я тоже когда то так делал. Да, можно. Быстро, не требуется память, эффективно. Но когда надо по быстрому накидать проектик, то вот так получается куда эффективней в плане временных расходов. А главное, ляпов сделать сложней. В конечнике же ошибешься где нибудь и привет. А тут таски цепочками гоняй друг за другом и благодать. Ну и для экспериментов лучше не придумаешь — очень быстро конфигурируется под другую задачу.
Во, вспомнил!
Есть хорошая книжка:
М. Дамке Операционные системы микроЭВМ.
Перевод с английского В.Л. Григорьева,
Москва, «Финансы и статистика», 1985г.
Оригинал:
MICROCOMPUTER OPERATING SYSTEMS, Mark Dahmke,
BYTE BOOKS, Subsidiary of McGraw-Hill, 1982.
В свое время я ее до дыр зачитал, хоть были и другие, посолидней. В ней все разжевано от и до, начиная от учебной системы — монитора на Z80 с матричной клавиатурой и семисегментными индикаторами (схема выглядит, как вполне современная, с микроконтроллером!), и через принципы построения — до подробного описания устройства CP/M80 и UNIX.
Весьма рекомендую для прочтения! Много вопросов сразу отпадет, и изобретать велосипед станет намного проще и увлекательнее. Думаю, в Интернете найти ее в электронном виде — не проблема, а то мне в лом 250 страниц сканить.
Еще есть хорошая толстая книжка: (680стр)
С.Кейслер Проектирование операционных систем для малых ЭВМ
Москва, «Мир», 1986г.
Оригинал — THE DESIGN OF OPERATING SYSTEMS FOR SMALL COMPUTER SYSTEMS, Stephen Kaisler, 1983г.
В ней тоже очень подробно и доходчиво описаны принципы построения операционок — диспетчеризация очередей задач, распределение ресурсов, человеко-машинные интерфейсы. Правда, она несколько более академична по сравнению с предыдущей, рассчитанной больше на практиков. Но тоже читал с удовольствием.
Щас нароем. Спасибо
А можно ссылки на данные книги?
В Гугле забанили? ;)
М. Дамке Операционные системы микроЭВМ.
Перевод с английского В.Л. Григорьева,
Москва, “Финансы и статистика”, 1985г.
Оригинал:
MICROCOMPUTER OPERATING SYSTEMS, Mark Dahmke,
BYTE BOOKS, Subsidiary of McGraw-Hill, 1982.
Господа, ну не могу найти эту книгу в инете!
У кого нибудь это получилось? — Дайте ссылку плиззз…
Не ты один ее ищешь. :(
Я интереса ради глянул тогда в поисковиках — есть в нескольких местах у букинистов сама книжка, но за деньги. Деньги невелики, 200-300р, да морока с оплатой, доставкой… В электронном виде вроде не попалась, хотя упоминаний — сотни.
Вот, например: http://yandex.ru/yandsearch?text=%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B5+%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B+%D0%BC%D0%B8%D0%BA%D1%80%D0%BE%D0%AD%D0%92%D0%9C%2C+%D0%94%D0%B0%D0%BC%D0%BA%D0%B5
или: http://www.google.kz/search?hl=ru&q=%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B5+%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B+%D0%BC%D0%B8%D0%BA%D1%80%D0%BE%D0%AD%D0%92%D0%9C%2C+%D0%94%D0%B0%D0%BC%D0%BA%D0%B5&btnG=%D0%9F%D0%BE%D0%B8%
Толи мне и вправду свою отсканить да выложить? Книжка — то интересная, и без «воды»… Может, за ночь управлюсь…
Отсканировал для начала 50 страниц и содержание. ОС_МИКРО_ЭВМ.djvu Size: 1144 KB
__1054___1057____1052___1048___1050___1056___1054____1069___1042___1052_.djvu
Постепенно отсканирую остальное. Сегодня уже лень, да и устал мало-мало…
Неправильно дал ссылку, а редактор что-то глючит, вешается… Вот нормальная:
http://rapidshare.com/files/226414648/__1054___1057____1052___1048___1050___1056___1054____1069___1042___1052_.djvu
Отсканировал еще полсотни страниц книги М. Дамке Операционные системы микроЭВМ.
Поскольку обьем файла в формате DJVU невелик, и чтобы не собирать книжку по кусочкам, выложил вместе с предыдущими страницами.
OS_MICRO_EWM.djvu Size: 2650 KB http://rapidshare.com/files/226850446/OS_MICRO_EWM.djvu
Теперь имеем 100 страниц книги + содержание. Продолжение следует…
Наконец — то отсканировал до конца книжку М. Дамке Операционные системы микроЭВМ. Полностью лежит теперь здесь:
http://rapidshare.com/files/228735744/OS_MICRO_EVM.djvu Size: 4378 KB
БОЛЬШОЕ СПАСИБО ВАМ!
спс*
Спасибо, мил человек :-)
эх… хорошая эта штука ртос))) тока вот асм…))
тут такая бредовая идейка пришла — чо если создать проект на gcc — и в нем везде сделать ассемблерные вставки с этой ртос))) а сами задачи писать на си ? =)
Планирую портировать все это дело на Си.
А асм.. а чо асм, на нем просто писать ;)
это будит по-любому не раньше октября? просто руки чешутся)))
а если сделать как я написал — не прокатит?
Только сейчас дошел до OS. И возник вопрос: что будет с задачей, если в момент добавления ее в очередь, сработает таймер и тоже попытается добавить задачу?
Например в момент прохода по списку при поиске конца очереди?
SEQL01: ld Tmp2, Z+ ; Грузим в темп байт из очереди
>>>>>>>>>>>>>>> Тут произошло прерывание <<<<<<<<<<<<<<<<<
cpi Tmp2, $FF ; и ищем ближайшее пустое место = FF
breq SEQL02 ; Если нашли, то переходим на сохранение
dec Counter ; Либо конец очереди по счетчику.
breq SEQL03
rjmp SEQL01
В момент добавления задачи в очередь прерывания должны быть запрещены. Эта операция должна быть атомарной. По крайней мере на том этапе пока возможно вклинивание другой задачи в процесс. Если у меня этого нет (или там закомменчены эти строчки, не помню уже. ЧТо то такое я делал для отладки, мог забыть вернуть) то это ошибка.
Как то жизнь повернулась, что я вернулся к микроконтроллерам. В качестве жертвы был избран AVR.
Спасибо автору! Хоть для меня не много нового, но дело в нюансах и мелочах именно для AVR. Поэтому читаю с удовольствием, правда параллельно с книжкой.
Ну к сути проблемы.
И все же что бы ядро выглядело более универсальным, я бы добавил к нему многозадачность. И то что это занимает много памяти, автор не прав.
1. если ввести два режима: монопольный и многозадачный, то многозадачность не сожрет всю память, а будет использоваться только для избранных.
2. работа со спулингом тогда существенно упростится не влияя на другие фоновые задачи.
Да и переделка не большая. немного перепишется диспетчер, появится слово состояния задачи. Ну конечно таймер нужно забить для кванта времени, да и задача должна сама себя убирать из очереди во время завершения или генерировать сигнал (если ввести систему сигнализации)
З.Ы.
Для тех кто хочет изучить основы ОС, я бы порекомендовал следующие книжки. Могу ошибиться с названиями, а искать среди кучи книжного хлама заткнутого по всей квартире, честно в лом.
1. Дейтл то ли введение в операционные системы, то Операционные системы.
2. Кейслер. Что-то Проектирование ОС для малых ЭВМ. могу ошибаться… Вот это мощь! тооолстый. приведены коды на псевдоязыке русском.
Правда староваты… но классика. В кейслере даже есть подробные описания блоков управления процессом, памятью и т.д. Хорошо расписаны оверлеи. Правда зачем уж они… хотя мир вращается по кругу, или по спирали (в какой проекции смотреть )) )
Помнится пришлось мне работать СМ1234 на 589 (был монстр с микропрограммированием — это вам не AVR — это круче геморой), голой, без ОС с программированием кнопках на морде процессора. Пол дня забивал монитор вручную… периферии тогда у ней не было ((
Пол дня мучал свою плату. Рядом стоял монстр СМ2 на ферритовой памяти с сдвоенным процессором и к нему было книжка. Описание ейной ОС — ДОС АСПО. ОС дрянь — но книжка прелесть. Описание всяких полей блоков управления, инструкции по написанию драйверов.
Во как в СССР документацию делали! СМ2 списали, тем более на ней БПФ не шло… ошибка с плавающей точкой была в процессоре, которая выскакивала только при определеных условиях )). Ну а книжку ценную я слямзил, до сих пор у меня валяется где-то.
Книги, это как любовь — оторвать от сердца можно только с мясом!
1. Для избранных кого? Задач? Ну так и делается, только многозадачный диспетчер все равно жрет очень много памяти, ведь он силой переключает контекст и сгружает все в память.
Не подскажите как использовать в ОС написанной на асме функции которые описаны в Си?
А конкретнее можно ли задачи Fire, Task2, Task3 и т.д. написать в Си?
Один в один не получится, не предназначена она для этого. Возьми лучше ту же самую систему, но написанную уже на Си. В цикле статей про архитектуру программ был пример. Вот она легко портируется вообще на любую архитектуру. Я ее за 20 минут на арм перетащил
Спасибо за ответ, а в плане объема и быстродействия она сильно уступает ассемблерной?
Раза в полтора тяжелей будет, а по скорости также. Ну и там ряд багов, что есть в этой ассемблерной реализации уже исправлен. В частности доступ атомарный пофиксен. И поудобней там получается, задачи добавляешь на лету, не прописывая их в таблицу переходов. Да и самой таблицы как таковой нету. В очередь бросается сразу же указатель на нужную функцию.
А зачем было сделано так?
lpm
mov OSRG, r0
ld r0, Z+
lpm
Почему не сразу
lpm OSRG, Z+
lpm R0, Z+
?
Точнее, с учетом дальнейшего:
lpm OSRG, Z++
push OSRG
lpm OSRG, Z++
push OSRG
Да уже не помню. Писалось все еще для малых тини у которых была только LPM потом еще достраивалась и переделывалась.
Ну у совсем мелкой (11/12) нет LD Rd,Z+ а у всех постарше есть adiw :)
20 лет не писал на ассемблере (всмысле как на языке, а не конкретно на atm), а руки помнят. :)
В любом случае спасибо :)
Ни как не могу поняить как работает этот участок кода:
PTQL01: ldd OSRG, Z+1 ; Грузим из следующего Z+1 байта и перекладываем
st Z+, OSRG ; все в Z, а Z после этого увеличиваем на 1
dec Counter ; Уменьшаем счетчик (там длинна очереди!)
brne PTQL01 ; Если не конец, то в цикле.
ldi OSRG, $FF ; А если конец, то по последнему адресу записываем FF
st Z+, OSRG ; Который является признаком конца очереди.
Поясните пожалуйста.
Хм так вроде все досконально прокомментировано.
А почему сдвиг очереди в диспетчере происходит по разному у вас в статье и в исходники для mega8?
Потому что оно постоянно обновляется и дополняется от проекта к проекту, а статьи я не переписываю под каждый чих. Там главное не сам код, а концепция. Код может быть и весьма глючным, так что смотри в оба :)
И ещё неясность:
ldi Counter,TaskQueueSize-1 получается что это команда грузит 10 в Counter при каждом сдвигом очереди??????? или на делает что-то другое??
Она загружает длинну очереди в счетчик.
http://easyelectronics.ru/repository.php?act=view&id=69
в подпрограмме ProcessTaskQueue:
clr ZH ; Сбрасываем старший байт
lsl OSRG ; А взятый номер задачи умножаем на 2
mov ZL, OSRG ; Так как адреса у нас двубайтные, а значит
; И ячейки в таблице перехода двубайтные
; Получается смещение по таблице
но когда мы сдвигаем влево, тобишь умножаем на 2, мы перенос не учитываем. В ZH в любом случае у нас записывается 0! не ошибка ли это?
оказывается это нужно если очень много задач(больше 128), но с таким количеством не получится работать, так что вопрос некорректен :)