Комментарии 56
Продолжайте, пока пост очень поверхностный даже для вводного.
+32
Да, я знаю. Честно сначала хотелось узнать, интересно ли хабралюдям такая тематика.
+5
Интересно, жду новых статей.
+4
Тоже с удовольствием почитал бы. Считаю, что будет полезно перенять/освежить/переосмыслить некоторые техники.
0
Думаю тут немало наберется желающих почитать по написание оси, хотя бы просто для общего развития.
+1
Конечно, интересно! Пишите, ждём :)
0
НЛО прилетело и опубликовало эту надпись здесь
Хе, тоже планирую начать свой цикл, посвященный этому делу, и скорее всего он будет довольно сильно отличаться от вашего.
Ибо у вас действительно всё как-то куцо и поверхностно, надеюсь исправитесь.
Ибо у вас действительно всё как-то куцо и поверхностно, надеюсь исправитесь.
0
Писал свою однозадачную. Сделал свою Файловую систему, внутренний язык. Даже систему обновлений ядра сделал. Модульная была.
Как работать с многозадачностью вобще нигде не нашел. Жалко было бросать проект, но пришлось. Хотел бы увидеть про многозадачность.
Как работать с многозадачностью вобще нигде не нашел. Жалко было бросать проект, но пришлось. Хотел бы увидеть про многозадачность.
+1
НЛО прилетело и опубликовало эту надпись здесь
Начните с этого простого туториала: hosted.cjmovie.net/TutMultitask.htm
Это основа основ — сохранение в стеке ядра задачи контекста, подмена стека ядра, восстановление контекста из другого стека ядра.
Происходит переключение по прерыванию от таймера и, возможно, некоторым другим. Обертка обработчика прерывания должна досохранить в стек все регистры общего назначения к уже находящимся там EFLAGS, CS и EIP (если произошло переключение из ring 3 в ring 0 там еще и указатель с сегментом пользовательского стека есть), потом вызвать обработчик прерывания, который обращается к планировщику, переключает стек задачи, и если это нужно, адресное пространство. Далее управление возвращается к обертке, которая восстанавливает регистры из стека уже другой задачи.
TSS одна пригодится для того, чтобы производить переключение из ring3 в ring0. В ней придется подменять лишь одно поле — esp0, остальное будет общим для всех ring3 задач.
Это основа основ — сохранение в стеке ядра задачи контекста, подмена стека ядра, восстановление контекста из другого стека ядра.
Происходит переключение по прерыванию от таймера и, возможно, некоторым другим. Обертка обработчика прерывания должна досохранить в стек все регистры общего назначения к уже находящимся там EFLAGS, CS и EIP (если произошло переключение из ring 3 в ring 0 там еще и указатель с сегментом пользовательского стека есть), потом вызвать обработчик прерывания, который обращается к планировщику, переключает стек задачи, и если это нужно, адресное пространство. Далее управление возвращается к обертке, которая восстанавливает регистры из стека уже другой задачи.
TSS одна пригодится для того, чтобы производить переключение из ring3 в ring0. В ней придется подменять лишь одно поле — esp0, остальное будет общим для всех ring3 задач.
+2
Писал свою однозадачную под реалмод. Типа доса короче, FAT-16 держало и некоторые int 21h нопейсал. Ну это понятно, не сложно. А вот с защищенным режимом так и не разобрался. На тот момент инет был очень дорог, инфы — нуль, так, потыкался-потыкался в слепую и плюнул.
Давайте писать сразу 64-разрядную ОС. Вот это было бы здорово почитать. А ваще, плюсую статью и афтора. Пока вроде и не о чем, но надеюсь продолжение выйдет достойным.
Давайте писать сразу 64-разрядную ОС. Вот это было бы здорово почитать. А ваще, плюсую статью и афтора. Пока вроде и не о чем, но надеюсь продолжение выйдет достойным.
+2
Ребят вы бы объединились, да написали цикл статей.Сообща материал получится лучше.
А тема очень интересная как для саморазвития почитать так и для практики.Пишите обязательно. :)
А тема очень интересная как для саморазвития почитать так и для практики.Пишите обязательно. :)
+3
Будет интересно почитать о разработке ОС для многоядерных систем.
0
Почитайте www.intel.com/design/pentium/datashts/242016.HTM
Также надо знать что такое APIC, и немного понимать банальные примитивы синхронизации вроде активного ожидания (в простейшем случае) на основе префикса LOCK некоторых инструкций.
Мультипроцессорность очень просто реализована в xv6. О ней ниже.
Также надо знать что такое APIC, и немного понимать банальные примитивы синхронизации вроде активного ожидания (в простейшем случае) на основе префикса LOCK некоторых инструкций.
Мультипроцессорность очень просто реализована в xv6. О ней ниже.
0
Автор уже принимается за написание более достойной статьи.
1) С 64-разрядными процами автор не работал никогда.
2) Сейчас интеловские камни поддерживают аппаратную многозадачность, но она достаточно медлительна и корява, потому всю многозадачность нужно писать ручками. В 64-разрядной архитектуре аппаратная мультизадачность была упразднена по вышеизложенным причинам.
1) С 64-разрядными процами автор не работал никогда.
2) Сейчас интеловские камни поддерживают аппаратную многозадачность, но она достаточно медлительна и корява, потому всю многозадачность нужно писать ручками. В 64-разрядной архитектуре аппаратная мультизадачность была упразднена по вышеизложенным причинам.
+1
а на каком языке собираетесь писать? просто я занимался некоторое время написанием собственной ОС (защищенный режим, микроядро, страничная организация памяти (сегментно-страничную, насколько я знаю, почти не используют), многопоточная, некоторые сервисы) на ассемблере (fasm). Могу выслать исходники если понадобятся.
0
Очень интересная тема, еще интересно было бы прочесть про другие архитектуры процессоров
0
Уж не напишут ли хабралюди ХабраОС? :)
0
В этом блоге на второй странице уже писали свою ОС. Авторов хватило только на 2 шага.
Автор точно хочет написать много шагов? :)
Автор точно хочет написать много шагов? :)
0
Автор пишет всё на FASM'e и автор сам этим занимается. Сидит и пишет, читает маны, гуглит. Так что тему я бросать не собираюсь.
+1
НЛО прилетело и опубликовало эту надпись здесь
ну зачем вы так? может человек есть человек дела, сказал — сделал!
0
Файловая система: вообще лучше начинать с FAT’a, потом переходить уже к другим. Некоторые решают написать свою собственную, и это хорошо – очень хорошо проясняется архитектура и работа с дисковыми накопителями.
В молодости, помню, писали с ребятами отладчик под защищенный режим. И захотелось мне сделать возможность записи дампов дизассемблера на диск… При первой пробной попытке прочитать сектор с fat-таблицой в память немного перепутал битовую последовательность и в результате записал какой-то мусор из памяти на сектор с fat-таблицей :(
Потом я реализовал этот функционал, но первый опыт у меня был запоминающийся :)
+1
measme, по крайней мере несколько шагов у меня уже сделано: есть полностью рабочий код; сейчас пишу функции дисковой подсистемы.
0
Отличное начинание, вот только каков прок от еще одного туториала по разработке ОС? Комлпексных, толстых книг по этому делу достаточно. Простых и ненавязчивых туториалов тоже в изобилии, правда они по большей части не на родном могучем, а на менее родном заморском.
Вот несколько достойных примеров:
pdos.csail.mit.edu/6.828/xv6/ — великолепная учебная реализация unix v6. И лекции есть. Учитесь на здоровье.
pdos.csail.mit.edu/6.828/2010/schedule.html — а тут их же курс, отличный. И лабораторные по разработке JOS — маленького экзоядра. Попробуйте себя.
jamesmolloy.co.uk/tutorial_html/index.html — хороший туториал. Много чего изложено. Но стоит быть аккуратнее, у автора много «грязного» кода, в чем он несколько раз признавался. Но как материал для ознакомления со всей внутренней ядерной кухней и железом — отлично.
www.brokenthorn.com/Resources/OSDevIndex.html — известный и качественный туториал.
en.skelix.org/skelixos/ — не очень аккуратный, но информативный.
Отдельно по разным вопросам могут помочь:
wiki.osdev.org/Main_Page — куча материалов, просто огромное сообщество.
www.osdever.net/tutorials/index — древний ресурс по любительской разработке ОС.
Обращу внимание на старую русскоязычную серию от lonesome — ищите архивы в сети.
И также несколько советов тем, кто хочет начать, но никак не решается: как можно больше практики, пробуйте под виртуалкой с отладчиком, тестируйте, пишите больше кода, читайте документацию по процессору, спецификации, смотрите много хорошего кода ядер ОС — HelenOS, MINIX3, xv6, Linux, Plan9.
Вот несколько достойных примеров:
pdos.csail.mit.edu/6.828/xv6/ — великолепная учебная реализация unix v6. И лекции есть. Учитесь на здоровье.
pdos.csail.mit.edu/6.828/2010/schedule.html — а тут их же курс, отличный. И лабораторные по разработке JOS — маленького экзоядра. Попробуйте себя.
jamesmolloy.co.uk/tutorial_html/index.html — хороший туториал. Много чего изложено. Но стоит быть аккуратнее, у автора много «грязного» кода, в чем он несколько раз признавался. Но как материал для ознакомления со всей внутренней ядерной кухней и железом — отлично.
www.brokenthorn.com/Resources/OSDevIndex.html — известный и качественный туториал.
en.skelix.org/skelixos/ — не очень аккуратный, но информативный.
Отдельно по разным вопросам могут помочь:
wiki.osdev.org/Main_Page — куча материалов, просто огромное сообщество.
www.osdever.net/tutorials/index — древний ресурс по любительской разработке ОС.
Обращу внимание на старую русскоязычную серию от lonesome — ищите архивы в сети.
И также несколько советов тем, кто хочет начать, но никак не решается: как можно больше практики, пробуйте под виртуалкой с отладчиком, тестируйте, пишите больше кода, читайте документацию по процессору, спецификации, смотрите много хорошего кода ядер ОС — HelenOS, MINIX3, xv6, Linux, Plan9.
+3
megabrain, вообще это можно оформить как заметки. Лично мне хватает того же осьдева и васма, но я предложил людям идею этих постов. Люди сказали — интересно. Так что постараюсь их порадовать статьями.
+1
Вообще решил сделать так: напишу переферию. А потом начну всё это связывать в ядро.
0
Уважаемый автор, так уж случилось, что я тоже пишу операционную систему. Пишу много лет и продвинулся весьма далеко. Вероятно, те, кто интересуется темой создания операционной системы, уже знакомы с Хамелеоном.
В общем, обращаюсь ко всем, кто пишет операционные системы. Если в какой-то момент у вас угаснет интерес к теме, то я готов принять некоторый исходный код. Интересуют прежде всего драйвера сетевых карт, код USB стека и драйверы, поддерживающие графический видео-режим.
Моя система микроядерная, поэтому лицензия не имеет значения — линковки с моим кодом не будет, ваш код может распространятся в виде отдельного модуля и исполняться как сервис, реализующий определённый протокол, и взаимодействующий с другими модулями посредством IPC. Не претендую на какие либо права — ваш код остаётся вашим. В случае любого конфликта в любой момент можете отозвать свой код из проекта. Язык реализации может быть почти любой, включая C/C++/Pascal, но модули ядра должны быть в формате Elf. Также есть возможность запускать исполняемые файлы формата PE (Portable Executable), но не на этапе начальной загрузки.
Более того, если кто-нибудь не чувствует силы завершить начатое до конца, а интересно продолжить, готов предоставить спецификации протоколов взаимодействия модулей. В идеале, можно помочь друг-другу развиваться вместе. Кроме того могу предложить участие в разработке новых протоколов.
В настоящее время я переписываю доморощенный tcp/ip стек, в надежде сделать его более стабильным и быстродействующим. Имею несколько идей для описания их на Хабре, но надеюсь каждую статью приурочить к новой сборке.
Если кого заинтересовало моё предложение, то выглядит моя система приблизительно так:
![image](https://habrastorage.org/getpro/habr/comment_images/64f/a48/fa7/64fa48fa7d0c6a505e3639ef04e1464f.gif)
В общем, обращаюсь ко всем, кто пишет операционные системы. Если в какой-то момент у вас угаснет интерес к теме, то я готов принять некоторый исходный код. Интересуют прежде всего драйвера сетевых карт, код USB стека и драйверы, поддерживающие графический видео-режим.
Моя система микроядерная, поэтому лицензия не имеет значения — линковки с моим кодом не будет, ваш код может распространятся в виде отдельного модуля и исполняться как сервис, реализующий определённый протокол, и взаимодействующий с другими модулями посредством IPC. Не претендую на какие либо права — ваш код остаётся вашим. В случае любого конфликта в любой момент можете отозвать свой код из проекта. Язык реализации может быть почти любой, включая C/C++/Pascal, но модули ядра должны быть в формате Elf. Также есть возможность запускать исполняемые файлы формата PE (Portable Executable), но не на этапе начальной загрузки.
Более того, если кто-нибудь не чувствует силы завершить начатое до конца, а интересно продолжить, готов предоставить спецификации протоколов взаимодействия модулей. В идеале, можно помочь друг-другу развиваться вместе. Кроме того могу предложить участие в разработке новых протоколов.
В настоящее время я переписываю доморощенный tcp/ip стек, в надежде сделать его более стабильным и быстродействующим. Имею несколько идей для описания их на Хабре, но надеюсь каждую статью приурочить к новой сборке.
Если кого заинтересовало моё предложение, то выглядит моя система приблизительно так:
![image](https://habrastorage.org/getpro/habr/comment_images/64f/a48/fa7/64fa48fa7d0c6a505e3639ef04e1464f.gif)
+6
Это здорово. Про TCP/IP интересно было бы послушать. Да и про USB тоже.
0
За USB я пока не брался, поэтому и спрашивал помощи. Заняться всерьёз USB стеком собираюсь после перехода на VM Ware, а для этого шага сначала придётся запустить драйвер сетевой карты Intel 8254x — именно такая поддерживается той версией VM Ware, которая стоит у меня.
Что касается TCP/IP то именно в этом модуле ничего революционного (пока) нет. Например, Supervisor использует оригинальный алгоритм распределения виртуальной памяти задачам. Файловая система использует хитрый алгоритм диспетчеризации системных вызовов. А вот TCP/IP пока отлаживается. Могу рассказать историю его написания.
В далёком 2000 году мой друг «вытянул» меня в Сеул, где мы писали Bluetooth стек. Я занимался драйвером виртуального COM порта для Windows NT, чтобы поверх него запустить RFCOMM протокол, а друг реализовал базовые протоколы Bluetooth. На понадобился код для работы с COM портом и как любой ленивый программист, друг нашёл какую-то свободную реализацию. Но эта реализация не поддерживала одновременную приём и передачу данных — она работал в одом потоке, поэтому друг написал свою реализацию, которая использовала один поток для передачи данных, а другой для приёма. Это предыстория.
Через пару лет мне захотелось поработать с TCP/IP напрямую, не используя сокетов. Поскольку в любой современной системе получить доступ к регистрам сетевой карты из юзерспейса невозможно,
то тут и вспомнился код, который очень аккуратно и оптимально работал с COM портом. Был взят RS232 кабелm и с его помощью были соединены девелоперский компьютер Windows 2000 и какой-то RedHat линукс. На линуксе был поднят PPP сервер — история началась.
Всё происходило следующим образом — все пакеты, приходящие от PPP сервера анализировались, затем находился соответсвующий RFC и по нему писался код, который парсил входящий протокол. Кода какой-то протокол нёс в себе вложенный проткол, то вся повторялось сначала — сначала анализировались принятые данные, затем брался соответствующий RFC и писалась реализация. В некоторые моменты для продвижения вперёд был недостаточно анализировать входящие пакеты, но нужно было что-то отвечать удалённой стороне — так постепенно родился Xameleon TCP/IP стек.
Здесь можно сделать лирическое отступление и кинуть камень в огород Microsoft. (Хотя с тех пор я повзраслел и в Holy War больше не участвую). Майкрософт часто обвиняют в отступлении от стандартов. Мне пришлось испытать это на своей шкуре. Так вышло, что вместо Linux PPP, в один момент пришлось использовать Microsoft RAS сервер — т.е. всю отладку перенести на одну машину. Какого же был удивление, что Microsoft RAS сервер вместо привычных PPP пакетов, gthbjlbxtcrb посылал в COM порт строку CLIENT. Это была засада. Проблема решилась ответом CLIENTSERVER, после чего сценарий установки соединения совпадал с традиционным PPP.
В результате, прикладная программа, содержащая в себе TCP/IP стек, через шнур RS-232, который соединял порты COM1 и COM2, могла ходить в интернет. Максимум, что она умела, посылать запрос GET на близлежащий WEB сервер и показывать его ответ на консоли. Потом код TCP/IP стека несколько лет «пылился» на флешке.
Через несколько лет, когда файловая система Хамелеона стала более-менее стабильной, было решено достать «те самые» наработки, о которых я написал выше. Для начала нашлась спецификация на сетевую карту DEC21140 (потому-что её поддерживает Virtual PC). По спецификации написан код, который просто отображал заголовки все принятых Ethernet пакетов на консоли Хамелеона. Далее идёт длинная история о переносе TCP/IP стека на Xameleon, проектировании протокола обмена с сетевыми устройствами (между TCP/IP стеком и драйвером сетевухи), доработке драйвера сетевой карты, дизайн сокетов и разработка socket libc, реализующей POSIX совместимый протокол работы с сокетами.
При переносе своего TCP/IP стека под Xameleon, я вынес каждый слой в отдельный поток — это было, наверное, правильное решение. Оно позволяло сделать стек модульным и легко расширяемым. Но когда появились реальные приложения, работающие поверх стека, любая ошибка в стеке становилась трудно отлавливаемой, а поскольку всё работает асинхронно, то при любой нагрузке стек мог заблокироваться. Поэтому TCP/IP был полностью переработан — всё сетевые протоколы были вынесены в один поток. В это же время была реализована идея, подсмотренная при изучении исходников какого-то TCP/IP стека для встраиваемых устройств — размер буфера для передачи был выбрать чуть более, чем размер Ethernet MTU. Данные, полученные стеком для передачи (send, sendto) пишутся в этот буфер со смещением, а каждый сетевой протокол (TCP/UDP, IP, Ethernet) лишь дописывает свои заголовки к данным. Таким образом удалось избавиться от множества memcpy и несколько повысить быстродействие. (К слову говоря, это лишь один из способов и не самый главный). Все буфера имеют reference count, поскольку UDP и TCP разным образом работают с данными, то время жизни данных в передающих буферах для этих пакетов разное — UDP буфера освобождаются сразу после подтверждения передачи данных сетевой картой, а TCP буфера освобождаются после подтверждения удалённой стороной. Соответственно, использование reference count позволяет упростить работу с буферами — буфер освобождается когда RefCount = 0.
В общем, я могу бесконечно долго рассказывать об этом, но пожалейте мои пальцы. Выйдет новая сборка — опишу исправленные баги и добавленные вкусности, а пока лишь «поток сознания». Извините.
Что касается TCP/IP то именно в этом модуле ничего революционного (пока) нет. Например, Supervisor использует оригинальный алгоритм распределения виртуальной памяти задачам. Файловая система использует хитрый алгоритм диспетчеризации системных вызовов. А вот TCP/IP пока отлаживается. Могу рассказать историю его написания.
В далёком 2000 году мой друг «вытянул» меня в Сеул, где мы писали Bluetooth стек. Я занимался драйвером виртуального COM порта для Windows NT, чтобы поверх него запустить RFCOMM протокол, а друг реализовал базовые протоколы Bluetooth. На понадобился код для работы с COM портом и как любой ленивый программист, друг нашёл какую-то свободную реализацию. Но эта реализация не поддерживала одновременную приём и передачу данных — она работал в одом потоке, поэтому друг написал свою реализацию, которая использовала один поток для передачи данных, а другой для приёма. Это предыстория.
Через пару лет мне захотелось поработать с TCP/IP напрямую, не используя сокетов. Поскольку в любой современной системе получить доступ к регистрам сетевой карты из юзерспейса невозможно,
то тут и вспомнился код, который очень аккуратно и оптимально работал с COM портом. Был взят RS232 кабелm и с его помощью были соединены девелоперский компьютер Windows 2000 и какой-то RedHat линукс. На линуксе был поднят PPP сервер — история началась.
Всё происходило следующим образом — все пакеты, приходящие от PPP сервера анализировались, затем находился соответсвующий RFC и по нему писался код, который парсил входящий протокол. Кода какой-то протокол нёс в себе вложенный проткол, то вся повторялось сначала — сначала анализировались принятые данные, затем брался соответствующий RFC и писалась реализация. В некоторые моменты для продвижения вперёд был недостаточно анализировать входящие пакеты, но нужно было что-то отвечать удалённой стороне — так постепенно родился Xameleon TCP/IP стек.
Здесь можно сделать лирическое отступление и кинуть камень в огород Microsoft. (Хотя с тех пор я повзраслел и в Holy War больше не участвую). Майкрософт часто обвиняют в отступлении от стандартов. Мне пришлось испытать это на своей шкуре. Так вышло, что вместо Linux PPP, в один момент пришлось использовать Microsoft RAS сервер — т.е. всю отладку перенести на одну машину. Какого же был удивление, что Microsoft RAS сервер вместо привычных PPP пакетов, gthbjlbxtcrb посылал в COM порт строку CLIENT. Это была засада. Проблема решилась ответом CLIENTSERVER, после чего сценарий установки соединения совпадал с традиционным PPP.
В результате, прикладная программа, содержащая в себе TCP/IP стек, через шнур RS-232, который соединял порты COM1 и COM2, могла ходить в интернет. Максимум, что она умела, посылать запрос GET на близлежащий WEB сервер и показывать его ответ на консоли. Потом код TCP/IP стека несколько лет «пылился» на флешке.
Через несколько лет, когда файловая система Хамелеона стала более-менее стабильной, было решено достать «те самые» наработки, о которых я написал выше. Для начала нашлась спецификация на сетевую карту DEC21140 (потому-что её поддерживает Virtual PC). По спецификации написан код, который просто отображал заголовки все принятых Ethernet пакетов на консоли Хамелеона. Далее идёт длинная история о переносе TCP/IP стека на Xameleon, проектировании протокола обмена с сетевыми устройствами (между TCP/IP стеком и драйвером сетевухи), доработке драйвера сетевой карты, дизайн сокетов и разработка socket libc, реализующей POSIX совместимый протокол работы с сокетами.
При переносе своего TCP/IP стека под Xameleon, я вынес каждый слой в отдельный поток — это было, наверное, правильное решение. Оно позволяло сделать стек модульным и легко расширяемым. Но когда появились реальные приложения, работающие поверх стека, любая ошибка в стеке становилась трудно отлавливаемой, а поскольку всё работает асинхронно, то при любой нагрузке стек мог заблокироваться. Поэтому TCP/IP был полностью переработан — всё сетевые протоколы были вынесены в один поток. В это же время была реализована идея, подсмотренная при изучении исходников какого-то TCP/IP стека для встраиваемых устройств — размер буфера для передачи был выбрать чуть более, чем размер Ethernet MTU. Данные, полученные стеком для передачи (send, sendto) пишутся в этот буфер со смещением, а каждый сетевой протокол (TCP/UDP, IP, Ethernet) лишь дописывает свои заголовки к данным. Таким образом удалось избавиться от множества memcpy и несколько повысить быстродействие. (К слову говоря, это лишь один из способов и не самый главный). Все буфера имеют reference count, поскольку UDP и TCP разным образом работают с данными, то время жизни данных в передающих буферах для этих пакетов разное — UDP буфера освобождаются сразу после подтверждения передачи данных сетевой картой, а TCP буфера освобождаются после подтверждения удалённой стороной. Соответственно, использование reference count позволяет упростить работу с буферами — буфер освобождается когда RefCount = 0.
В общем, я могу бесконечно долго рассказывать об этом, но пожалейте мои пальцы. Выйдет новая сборка — опишу исправленные баги и добавленные вкусности, а пока лишь «поток сознания». Извините.
+4
Что ж вы микроядро или обертку для поддержки линуксовых драйверов не напишете? Чтобы каждый драйвер был сервисом, или как там это называется. Там уже столько написано, все опенсурсное, исходники доступны, а вы предлагаете все это переписать? Зачем это нужно?
0
Это же всё в образовательных целях. Зачем брать готовое, если хочешь разобраться?
0
Драйвер Floppy взят из FreeDOS, а туда он попал из Linux. Так что кое-что таки использую.
Вообще, формат сообщений между сервисами, реализующими протоколы, и драйверами устройств, почти устоялся. Во всяком случае это можно сказать о символьных и блочных устройствах. О вот что касается сетевых карт, то протокол ещё не устоялся, поэтому переносить много драйверов пока лениво — при каждом апдейте протокола придётся править каждый драйвер.
Что касается графических режимов видеокарт, то тут ещё не понятно каким образом лучше всего писать данные в видеопамять и какие примитивы использовать. Есть идея использовать нечто вроде виртуального холста, рисовать на нём в модуле, представляющем абстракцию для видеоустройства, а затем сообщениями передавать этот «холст» в драйвер видеокарты. Но всё это представляю очень туманно, а браться сейчас за графические режимы — значит оставить без внимания остальные модули.
Наконец, USB. Нет особого смысла брать готовую реализацию USB из open source проекта, пока не разработан внутренний протокол обмена с USB. Попробую пояснить — к примеру, у меня есть USB floppy привод, оставшийся от старого ноутбука. Здравый смысл подсказывает, что USB обеспечивает лишь интерфейс для обмена командами и данными с этим Floppy приводом, а формат команд соответствует обычному (не USB) приводу. И только когда появится понимание того, каким образом команды и данные транслируются удалённому устройству, можно разработать протокол (API/формат сообщений — называйте как хотите) для обмена. И уже после этого можно брать код из open source проекта и делать ему обвязку этого протокола.
И да, я прошу прощения за множество опечаток в моих постах — сильно сильно спешу, меня переполняют эмоции, хочется выразить свои мысли и чувства, а в результате получается дислексия.
Вообще, формат сообщений между сервисами, реализующими протоколы, и драйверами устройств, почти устоялся. Во всяком случае это можно сказать о символьных и блочных устройствах. О вот что касается сетевых карт, то протокол ещё не устоялся, поэтому переносить много драйверов пока лениво — при каждом апдейте протокола придётся править каждый драйвер.
Что касается графических режимов видеокарт, то тут ещё не понятно каким образом лучше всего писать данные в видеопамять и какие примитивы использовать. Есть идея использовать нечто вроде виртуального холста, рисовать на нём в модуле, представляющем абстракцию для видеоустройства, а затем сообщениями передавать этот «холст» в драйвер видеокарты. Но всё это представляю очень туманно, а браться сейчас за графические режимы — значит оставить без внимания остальные модули.
Наконец, USB. Нет особого смысла брать готовую реализацию USB из open source проекта, пока не разработан внутренний протокол обмена с USB. Попробую пояснить — к примеру, у меня есть USB floppy привод, оставшийся от старого ноутбука. Здравый смысл подсказывает, что USB обеспечивает лишь интерфейс для обмена командами и данными с этим Floppy приводом, а формат команд соответствует обычному (не USB) приводу. И только когда появится понимание того, каким образом команды и данные транслируются удалённому устройству, можно разработать протокол (API/формат сообщений — называйте как хотите) для обмена. И уже после этого можно брать код из open source проекта и делать ему обвязку этого протокола.
И да, я прошу прощения за множество опечаток в моих постах — сильно сильно спешу, меня переполняют эмоции, хочется выразить свои мысли и чувства, а в результате получается дислексия.
+1
У товарища Кулакова в книге 'Программирование дисковых подсистем' в красках описана работа DMA и FDC. Я по нему сейчас иду. А в другой его книжке описано написание драйвера под USB. Тоже в красках.
0
Спасибо. Заглянул в В.Кулаков «Программирование на аппаратном уровне». Действительно, есть даже пример работы с принтером и мышью через USB.
Жаль, примеры на ассемблере. Не хочу развязывать очередные войны, но последние годы стараюсь поменьше иметь дело с ассемблером. Есть на то причины — довелось писать не только для x86, но и для ARM, а также всяких экзотических архитектур. Поэтому хочется писать переносимые решения, а не под конкретную архитектуру. Вторая причина охлаждения любви к ассемблеру, это два разных синтаксиса на одной архитектуре — AT&T Syntax и Intel Syntax — это окончательно сломало мою голову и теперь только читаю на ассемблере, но почти не пишу. Только в тех местах, где без ассемблера действительно нельзя обойтись, благо, таких мест ничтожно мало.
Эх, боюсь как бы меня за многословность не сочли балаболом. Но давайте порассуждаем, как бы мог выглядеть сервис, обеспечивающий прозрачную работу с USB устройствами. Не будем углубляться в подробности реализации, а просто абстрактно опишем.
Некая задача, назовём её драйвер, инициализирует USB контроллер, проверяет все подключенные устройства, строит дерево устройств и производит начальную инициализацию найденных устройств, точнее не самих устройств, конфигурацию USB транспорта этих устройств. Также задача предоставляет некий интерфейс другим задачам, через который они могут получить список уже подключенных устройств. Причём, здесь логично использовать перечисление, скажем нечто вроде FindUsbHost, FindNextUsbDevice, где выходной параметр функций будет использован как аргумент для следующей итерации или служить признаком остановки перечисления. (Привет от MS-DOS и его FindFirst FindNext :) )
Далее, нам понадобится hot-plug/unplug. Для этого в интерфейс драйвера добавляем новый вызов — регистрация внешней задачи, принимающей сообщения от драйвера USB, о том, что появилось новое устройство или удалено существующее — эдакая регистрация callback'а. Т.е. когда мы втыкаем флэшку или любое другое устройство, внешняя задача получает сообщение о новом устройстве. Мы специально не рассматриваем детали реализации программы, которая следит за новыми устройствами — она может автоматически монтировать файловую систему, находящуюся на устройстве, начать импорт фотографий или установить связь через появившийся USB-модем или просто добавить новую запись в /dev — это нас не касается.
Следующее, что понадобится добавить в интерфейс драйвера, функции управления USB устройством — задать режим работы, скорость и прочие параметры USB.
Наконец, нам понадобятся четыре вызова для работы с самим устройством — ReadData, WriteData, ReadDeviceControl, WriteDeviceControl. С точки зрения драйвера USB, не имеет значения, какое устройство подключено — символьное, блочное или какое-либо ещё.
ReadDeviceControl и WriteDeviceControl — логично представить как блок памяти, представляющий собой некий диапазон портов ввода/вывода. Т.е. если на обычном PCI/ISA устройстве порты ввода/вывода отображаются в физическое адресное пространство, адресуемое микропроцессором, то в случае USB любая запись на удалённое устройство происходит сначала в локальный буфер, который после передачи удалённой стороне запишется в порты ввода/вывода удалённого устройства. Тут возникает вопрос, как быть, если надо записать лишь в один регистр? Я не знаю. Возможно, предварительно прочитать ReadDeviceControl, поменять регистр, а затем вызвать WriteDeviceControl. Я невнимательно читал документацию, возможно есть способ записать лишь один регистр. Хотя бы потому, что существуют регистры только для чтения. А для некоторых регистров вообще важен сам факт записи и даже если записать в него то же значение, которое там и было, то такая запись может изменить логику работы устройства.
Наконец, возникает вопрос в поведении ReadData. По идее, она может быть блокирующейся функцией, которая возвращает значение только в случае наличия данных на удалённом устройстве. В этом случае работу с портами придётся организовывать в отдельном потоке, что в данном случае не очень удобно. Можно поступить иначе — использовать неблокируемый вызов ReadData, который в случае отсутствия данных вернёт 0. В таком случае, чтобы избежать опроса устройства в цикле, необходимо предусмотреть возможность драйвером информировать приложение о готовности данных для чтения. Можно даже попробовать сочетать оба подхода (блокированный/неблокированный Read) и для каждого устройства использовать подходящий метод.
Извините за очередной поток сознания — пытался разобраться «как это работает» в процессе написания поста. Буду признателен всем, кто может дополнить, поправить или указать на ошибки и нестыковки.
Жаль, примеры на ассемблере. Не хочу развязывать очередные войны, но последние годы стараюсь поменьше иметь дело с ассемблером. Есть на то причины — довелось писать не только для x86, но и для ARM, а также всяких экзотических архитектур. Поэтому хочется писать переносимые решения, а не под конкретную архитектуру. Вторая причина охлаждения любви к ассемблеру, это два разных синтаксиса на одной архитектуре — AT&T Syntax и Intel Syntax — это окончательно сломало мою голову и теперь только читаю на ассемблере, но почти не пишу. Только в тех местах, где без ассемблера действительно нельзя обойтись, благо, таких мест ничтожно мало.
Эх, боюсь как бы меня за многословность не сочли балаболом. Но давайте порассуждаем, как бы мог выглядеть сервис, обеспечивающий прозрачную работу с USB устройствами. Не будем углубляться в подробности реализации, а просто абстрактно опишем.
Некая задача, назовём её драйвер, инициализирует USB контроллер, проверяет все подключенные устройства, строит дерево устройств и производит начальную инициализацию найденных устройств, точнее не самих устройств, конфигурацию USB транспорта этих устройств. Также задача предоставляет некий интерфейс другим задачам, через который они могут получить список уже подключенных устройств. Причём, здесь логично использовать перечисление, скажем нечто вроде FindUsbHost, FindNextUsbDevice, где выходной параметр функций будет использован как аргумент для следующей итерации или служить признаком остановки перечисления. (Привет от MS-DOS и его FindFirst FindNext :) )
Далее, нам понадобится hot-plug/unplug. Для этого в интерфейс драйвера добавляем новый вызов — регистрация внешней задачи, принимающей сообщения от драйвера USB, о том, что появилось новое устройство или удалено существующее — эдакая регистрация callback'а. Т.е. когда мы втыкаем флэшку или любое другое устройство, внешняя задача получает сообщение о новом устройстве. Мы специально не рассматриваем детали реализации программы, которая следит за новыми устройствами — она может автоматически монтировать файловую систему, находящуюся на устройстве, начать импорт фотографий или установить связь через появившийся USB-модем или просто добавить новую запись в /dev — это нас не касается.
Следующее, что понадобится добавить в интерфейс драйвера, функции управления USB устройством — задать режим работы, скорость и прочие параметры USB.
Наконец, нам понадобятся четыре вызова для работы с самим устройством — ReadData, WriteData, ReadDeviceControl, WriteDeviceControl. С точки зрения драйвера USB, не имеет значения, какое устройство подключено — символьное, блочное или какое-либо ещё.
ReadDeviceControl и WriteDeviceControl — логично представить как блок памяти, представляющий собой некий диапазон портов ввода/вывода. Т.е. если на обычном PCI/ISA устройстве порты ввода/вывода отображаются в физическое адресное пространство, адресуемое микропроцессором, то в случае USB любая запись на удалённое устройство происходит сначала в локальный буфер, который после передачи удалённой стороне запишется в порты ввода/вывода удалённого устройства. Тут возникает вопрос, как быть, если надо записать лишь в один регистр? Я не знаю. Возможно, предварительно прочитать ReadDeviceControl, поменять регистр, а затем вызвать WriteDeviceControl. Я невнимательно читал документацию, возможно есть способ записать лишь один регистр. Хотя бы потому, что существуют регистры только для чтения. А для некоторых регистров вообще важен сам факт записи и даже если записать в него то же значение, которое там и было, то такая запись может изменить логику работы устройства.
Наконец, возникает вопрос в поведении ReadData. По идее, она может быть блокирующейся функцией, которая возвращает значение только в случае наличия данных на удалённом устройстве. В этом случае работу с портами придётся организовывать в отдельном потоке, что в данном случае не очень удобно. Можно поступить иначе — использовать неблокируемый вызов ReadData, который в случае отсутствия данных вернёт 0. В таком случае, чтобы избежать опроса устройства в цикле, необходимо предусмотреть возможность драйвером информировать приложение о готовности данных для чтения. Можно даже попробовать сочетать оба подхода (блокированный/неблокированный Read) и для каждого устройства использовать подходящий метод.
Извините за очередной поток сознания — пытался разобраться «как это работает» в процессе написания поста. Буду признателен всем, кто может дополнить, поправить или указать на ошибки и нестыковки.
+1
Мне кажется, вместо уродских findNextDevice гораздо умнее будет findDevices(struct condition { type, busId, deviceId, vendorId }) и так далее, причем универсальная функция для любой шины а не только USB. Часто же надо например найти все жесткие диски, или что то вроде этого.
Чтение шины мне кажется стоит отложить до момента первого обращения к findDevices (что зря процессор нагружать).
Вместо callback о присоединении устройства стоило бы сделать единую систему событий, которую userland и kernel mode программы могут слушать (с фильтром естественно), а драйвера в нее писать. Можно, как в Линуксе, на файловых дескрипторах, можно как то по своему, лишь бы меньше процессорного времени и памяти расходовать.
Задать скорость и т.д. должен драйвер устройства, откуда обычная программа знает какие параметры выставлять? Драйвер должен сам ставить оптимальные значения.
Вместо readdevicecontrol etc лучше сделать функции: getDeviceOptionList() (какие опции поддерживает устройство), setManyOptions, getManyOptions.
Ну и неблокируемое чтение тоже должно быть. Можно внутри ядра все операции сделать неблокируемыми, а юзерские процессы блокировать. Опять же, пригодилась бы единая, с минимальным оверхедом система работы с любыми асинхронными функциями, можно даже какое то подобие очереди событий сделать:
Делаем в памяти буфер, ядро в него пишет, userland только читает. Userland процесс изначально заблокирован, а при поступлении новых событий разблокируеится, читает из из очереди, обрабатыает и снова засыпает. Это например сильно бы упростил осоздание многопоточных серверов, если бы вяс эта вещь поддерживалась на уровне ядря и можно было бы все системные вызовы сделать неблокирующими.
А, еще наверно понадобится втторая очередь, куда процесс пишет запросы к ядру. Это действительно было бы что-то новое.
Чтение шины мне кажется стоит отложить до момента первого обращения к findDevices (что зря процессор нагружать).
Вместо callback о присоединении устройства стоило бы сделать единую систему событий, которую userland и kernel mode программы могут слушать (с фильтром естественно), а драйвера в нее писать. Можно, как в Линуксе, на файловых дескрипторах, можно как то по своему, лишь бы меньше процессорного времени и памяти расходовать.
Задать скорость и т.д. должен драйвер устройства, откуда обычная программа знает какие параметры выставлять? Драйвер должен сам ставить оптимальные значения.
Вместо readdevicecontrol etc лучше сделать функции: getDeviceOptionList() (какие опции поддерживает устройство), setManyOptions, getManyOptions.
Ну и неблокируемое чтение тоже должно быть. Можно внутри ядра все операции сделать неблокируемыми, а юзерские процессы блокировать. Опять же, пригодилась бы единая, с минимальным оверхедом система работы с любыми асинхронными функциями, можно даже какое то подобие очереди событий сделать:
Делаем в памяти буфер, ядро в него пишет, userland только читает. Userland процесс изначально заблокирован, а при поступлении новых событий разблокируеится, читает из из очереди, обрабатыает и снова засыпает. Это например сильно бы упростил осоздание многопоточных серверов, если бы вяс эта вещь поддерживалась на уровне ядря и можно было бы все системные вызовы сделать неблокирующими.
А, еще наверно понадобится втторая очередь, куда процесс пишет запросы к ядру. Это действительно было бы что-то новое.
+1
Лучше тогда уже реализовать и для всех девайсов, и только для определённых.
0
Мне кажется, вместо уродских findNextDevice гораздо умнее будет findDevices(struct condition { type, busId, deviceId, vendorId }) и так далее, причем универсальная функция для любой шины а не только USB. Часто же надо например найти все жесткие диски, или что то вроде этого.
Насчёт универсальной функции, то она, конечно, не помешает. Но мне кажется, что это уже более высокоуровневый протокол. Т.е. некий Hardware Abstraction Layer (HAL) работающий поверх USB и других шин.
Фуникция findDevices — логичное решение, однако, не будет ли проблемы пересечений deviceId и vendorId для PCI и USB устройств? Если такой проблемы нет, то вообще прекрасно. Или Вы предлагаете определять тип шины параметрами type и busId?
Насёт FindFirst и FindNext — согласен, что это перебор устройств не лучший вариант с точки зрения производительности, но оптимальный с точки зрения затрат на память. Можно реализовать оба подхода и дать программисту выбор — использовать findDevices или findFirst/findNext.
Чтение шины мне кажется стоит отложить до момента первого обращения к findDevices (что зря процессор нагружать).
Согласен, это было бы оптимально.
Вместо callback о присоединении устройства стоило бы сделать единую систему событий, которую userland и kernel mode программы могут слушать (с фильтром естественно), а драйвера в нее писать. Можно, как в Линуксе, на файловых дескрипторах, можно как то по своему, лишь бы меньше процессорного времени и памяти расходовать.
Интересно. Прежде всего интересен формат сообщений. Детали реализации можно отдать на усмотрение разработчикам. Например, я бы реализовал такой протокол на основе L4 IPC.
Задать скорость и т.д. должен драйвер устройства, откуда обычная программа знает какие параметры выставлять? Драйвер должен сам ставить оптимальные значения.
Не спорю. Тут только один вопрос/пожелание — хочется по максимуму (насколько возможно) абстрагироваться от шины. Т.е. чтобы код, который читает/пишет в порты и читает/пишет данные, как можно меньше зависел от шины. Ради этого, я верю, можно даже поступиться быстродействием. Скажем, я бы смирился с ~10% потерей быстродействия ради универсальности. В случае же, когда из «железки» нужно выжать максимум производительности, то под неё пишется кастомный драйвер специально под определённую модель. Сожалею, сейчас у меня не хватает сил, чтобы вербализовать свою мысль.
Вместо readdevicecontrol etc лучше сделать функции: getDeviceOptionList() (какие опции поддерживает устройство), setManyOptions, getManyOptions.
Вот бы на конкретном примере какого-либо устройства это рассмотреть. То есть, скажем, USB HDD — в случае обычного IDE устройства, подключенного к IDE контроллеру на PCI шине, в простейшем случае программа устанавливает в регистрах контроллера номер головки, цилиндра и сектора, количество секторов для чтения, посылает команду, а затем, получив прерывание, читает данные из устройства, анализируя статус. В случае DMA/UDMA режима, операция несколько отличается — контроллер захватывает шину и сам пишет в память. А как это будет работать, если IDE устройство подключено через USB шину? Вот если бы кто-нибудь это расписал, мои посты были бы более внятные.
Ну и неблокируемое чтение тоже должно быть. Можно внутри ядра все операции сделать неблокируемыми, а юзерские процессы блокировать. Опять же, пригодилась бы единая, с минимальным оверхедом система работы с любыми асинхронными функциями, можно даже какое то подобие очереди событий сделать:
Что касается блокировок — совершенно согласен с Вашей идеей, но предлагаю детали реализации оставить разработчикам. Мне удалось решит проблему, развязав синхронные IPC в асинхронные через «контекст запроса» и конечный автомат, который переключается по этому контексту. Кстати, отдалённо напоминает Ваше описание работы многопоточных серверов.
0
Только мне этот комментарий напомнил один эпичнейший момент из прошлого? :)
+5
Статью обещаю на выходных. Раньше никак. Люди попросили рассказать, описать PM, хотя по-моему это бесполезно, есть много туториалов и манов по этому вопросу.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Продолжаем написание операционок. Шаг за шагом