По производительности — второй пример был из планировщика ehci_select_hs_interrupt_list — там же не вызывается сброс контроллера каждый раз?
Нет, планировщик вызывается при открытии канала, то есть несколько раз при начальной конфигурации устройства — требующей как минимум тех самых 100 мс в начале. На фоне которых говорить о паре тактов просто смешно.
Трудоёмкость — да, очень хочу убедить
Речь шла про трудоёмкость изменения кода только из-за того, что он на ассемблере. Я думаю, что демонстрация была достаточно убедительной, чтобы этот миф можно было закрыть.
так как шишек много. Опять таки, это был первый попавшийся на глаза пример.
Это неубедительное теоретизирование. Убедительной демонстрацией было бы «вот, смотрите, я заменил movi на mov и теперь <что-нибудь> занимает не две секунды, а одну».
Если очень интересно — можно ещё найти места, где используются лишние копирования (потому что
человеку не под силу держать в голове текущий контекст программы)
Вот это интересно, подобное ещё и размер раздувает. Найдите — я исправлю и скажу «спасибо».
где инструкции идут не в самом благоприятном порядке — это всё макросами не исправишь
Инструкции можно переставить. Но здесь уже надо доказывать, что перестановка инструкций что-то даст.
(movi — оперативненько!)
Спасибо. Я стараюсь.
И насчёт «размер результата будет в разы больше» это вы погорячились. Я тоже так думал, ровно до тех пор, пока компилятор не стал генерить сравнимый (а зачастую и более короткий и быстрый код).
Распространённое заблуждение. Я приведу два примера.
Пример 1. У нас есть драйверы для видеокарт Intel и ATI, портированные с Linux, — естественно, на Си. Их нет в дистрибутиве: в образ они не влезают, а методы, позволяющие обратиться к дополнительным источникам данных, где бы они ни были, пока в разработке — но их без проблем можно найти на форуме. Так вот, дословный кусок кода из одной версии одного из драйверов:
Упражнение на понимание: выяснить, что делает код, записать результат одной ассемблерной командой и сравнить размеры.
Пример 2. Типичный фрагмент программы на Си:
extern void f(void* something, int x, int y, int z);
...
void* p;
...
f(p, 1, 2, 3);
Вменяемый ассемблерщик в зависимости от желания/наличия макросов может написать либо
ccall f,[p],1,2,3
либо, что то же самое,
push 3
push 2
push 1
push [p]
call f
add esp,10h
Если p — локальная переменная и создан кадр стека, то [p] — что-то типа [ebp-4] и конструкция занимает 2*3+3+5+4 = 18 байт.
Барабанная дробь, gcc с ключом -Os, который якобы означает «оптимизировать по размеру»:
Здесь кадра стека уже не будет, и [p] — что-то типа [esp+20]. Теперь конструкция занимает 4+8*3+3+5 = 36 байт. Разница, как видно, в два раза. Сравнимый код, говорите?
Это не так. Производительность не меняется. Даже если вы можете считать отдельные такты, вы собьётесь со счёта в момент сброса контроллера — он занимает больше, чем вся остальная инициализация, вместе взятая, — а 100-миллисекундный интервал, требуемый перед началом какой бы то ни было работы с USB-устройством, покажется вам вечностью.
Ну и не понятно тогда, почему же, если вы оптимизируете под размер, то всё равно много где используется нормальная загрузка значения, как
Я надеюсь, это не будет для вас шоком, но я должна признаться: я написала далеко не весь код ядра.
Кроме того, в некоторых местах производительность таки важнее размера.
А Си-шый код так легко перекомпилировать, и новым компилятором, и под новый процессор…
… и какая разница, что размер результата будет в разы больше…
Да вы и сами, наверняка, знаете, что не всё с кодом оптимально, но из-за ассемблера, изменения становятся вся более трудоёмкими, поэтому начинает преобладать принцип: «работает? — не лезь!»
Вы серьёзно хотите продемонстрировать тезис о трудоёмкости изменений задачей, решаемой заменой по регэкспу? Специально для вас в свежей ревизии ядра я заменила пары push / pop на специальный макрос, который можно определить как простой mov, а можно определить так, как он сейчас определён — парой push / pop. Для критичных ко скорости участков по-прежнему есть mov.
1) Выделение части, работающей с HID, в отдельный драйвер имело бы смысл, если было бы несколько различных источников данных HID. Пока источник один и других не просматривается, это лишь ненужное усложнение.
2) Этот вопрос я не совсем поняла. Если «службой» вы называете отдельное usermode-приложение — фактически тот же драйвер, но в usermode — то писать сам драйвер проще бы не было — действия ровно те же самые. Возможно, было бы проще его отлаживать, несомненно, были бы куда легче последствия от того, что что-нибудь пошло не так. Ядру было бы, наоборот, сложнее. Я думаю над чем-нибудь типа libusb, но это явно неприоритетная вещь. Если вы имеете в виду что-то другое — уточните.
3) Ещё проще было бы ничего не делать. Естественно, это не позволило бы достичь цели. Если бы я ставила цель продемонстрировать какой-нибудь пример — скорее всего, я бы использовала Си как язык, понятный достаточно многим программистам и не слишком мешающий низкоуровневым вещам. Если бы я ставила цель реализовать пример какого бы то ни было USB-драйвера — я рассмотрела бы также вариант с той же libusb и, например, python, это бы ещё расширило круг читателей. Но моя цель — написать операционную систему возможно меньшего размера в той степени, пока это не мешает производительности, и C--, C, C++ не годятся для этого совсем никак.
Вместо add eax,2 и mov ecx,32 соответственно? Так короче. inc eax однобайтовая, add eax,2 занимает 3 байта. mov с непосредственным операндом — одна из немногих команд, не имеющих опкода, где непосредственное значение хранится как байт и расширяется до требуемого размера в ходе выполнения, поэтому mov ecx,32 занимает 5 байт — один на опкод, 4 на значение 32 — в отличие от push 32 с двумя байтами — один на опкод, 1 на значение 32.
Это также медленнее на один-два такта, но конкретно этот участок выполняется один раз в жизни контроллера при инициализации, и несколько тактов роли не играют, а вот несколько байт заметны везде.
Давайте отложим технические вопросы до технической статьи.
Краткий ответ на конкретный вопрос — нет, драйверного интерфейса для навешивания фильтра нет и не планируется. Отдельный нужный обработчик — например, сохранение сырых данных, передаваемых туда-обратно по шине, — легко добавить непосредственно в ядро.
Спасибо, что напомнили. В истории Колибри было несколько попыток, продвинувшихся в разной степени, /drivers/usb — одна из них, ограничившаяся частичной поддержкой мыши и клавиатуры для UHCI. Они более неактуальны, я сейчас удалила их.
Правильные ссылки:
инфраструктура USB: kernel/trunk/bus/usb
описание API для драйверов USB: kernel/trunk/docs/usbapi.txt
драйвер мыши и клавиатуры: kernel/trunk/drivers/usbhid.asm
драйвер флешек: kernel/trunk/drivers/usbstor.asm.
Intel выложила часть своей реализации — не включающую hardware-specific вещи — под именем TianoCore, sourceforge.net/apps/mediawiki/tianocore. Как можно убедиться, написано с нуля.
Это не список контрибьюторов, это список администраторов и менторов для GSoC. То есть тех, кто готов потратить много времени и сил на общение/обучение/надзор за студентами, которые и будут писать собственно код. Соответственно, задачи в том же списке — не те, которыми занимаются люди, а те, которые можно предложить тем же студентам на выбор.
32-битную архитектуру убрать пытались, правда, не MS, а Intel: первыми 64-битными процессорами, поддерживаемыми релизом Windows, были Itanium, где WOW64-подсистема содержала программную эмуляцию x86 с соответствующей скоростью работы. Если бы Itanium взлетел, то отсутствие 64-битной версии какой-нибудь важной программы было бы близким к стопперу перехода, что MS невыгодно.
Простой перекомпиляции в случае сколько-нибудь нетривиального кода, конечно, недостаточно для переноса на новую архитектуру. Но 1) каждый новый момент, который нужно учитывать, уменьшает число желающих хоть что-то делать и 2) если имя уже было захардкожено, то получатся два варианта кода — под #ifdef _WIN64 и #elif — что усложнит дальнейшую поддержку, а в MS всё же стараются упрощать жизнь программистам, а не усложнять.
When updating the interfaces for 64-bit Windows, there were a few guiding principles. Here are two of them.
* Don't change an interface unless you really need to.
* Do you really need to?
The only consequence (so far) is that the number of «things in code being ported from 32-bit Windows to 64-bit Windows needs to watch out for» has been incremented by one. Of course, too much of this incrementing, and the list of things becomes so long that developers are going to throw up their hands and say «Porting is too much work, screw it.»
These are the worst types of breaking changes: The ones where the compiler doesn't tell you that something is wrong. Your code compiles, it even basically runs, but it doesn't run correctly.
Remember, you want to make it easier for people to port their program to 64-bit Windows, not harder. The goal is make customers happy, not create the world's most architecturally pure operating system. And customers aren't happy when the operating system can't run their programs (because every time the vendor try to port it, they keep stumbling over random subtle behavior changes that break their program).
Проблема в том, что подобная ошибка выглядит не как окошко с сообщением «Прописан неправильный путь», а, например, как «Модуль, написанный уволившимся пять лет назад человеком, падает при инициализации» — причём расследование покажет, что падение происходит из-за некорректной обработки ошибки E_NOTFOUND, возвращаемой другим модулем, написанным шесть лет назад.
Другая проблема, обсуждаемая в той статье, — наличие скриптов, в частности, bat-ников, с жёсткими путями. Их перекомпилировать не нужно.
technet.microsoft.com/en-us/magazine/ff955767.aspx
В мире оказалось слишком много программ с захардкоженным именем папки System32, и чтобы их не сломать, оказалось проще ввести перенаправление на уровне файловой системы. 32-битные приложения, обращаясь к system32, видят 32-битные версии, реально лежащие в syswow64, 64-битные, обращаясь туда же, видят 64-битные версии.
В комментариях до вас рассматривали выбор с вероятностями b/(a+b) и a/(a+b). У вас, если я правильно поняла, вероятности (a-b)/a и b/a. У меня получается, что точное значение среднего числа карт при таком выборе 18108755632747595237/687343086666072144 = 26.34602134… Если у вас получается 31-33 карты, хотелось бы видеть код.
Среднее число угаданных карт легко посчитать точно, без метода Монте-Карло. Если на каждом ходу называть масть наугад равновероятно, то, очевидно, в среднем за каждый ход будет угадано 1/2 карты, в сумме за 52 хода — ровно 26 карт.
Если на каждом ходу называть ту масть, которой осталось больше, то вычисления чуть сложнее. Обозначим f(m,n) = среднее число угаданных карт, если начинать с колоды из m красных и n чёрных карт, равновероятно выбираемой из всех (m+n)! вариантов. Ясно, что f(m,n) = f(n,m), и что f(m,0) = f(0,m) = m. Если теперь m >= n > 0, то мы на первом ходу назовём красную масть. Для первой карты есть m+n вариантов, в m из них мы угадываем цвет и в колоде остаётся m-1 красных и n чёрных карт (любая перестановка которых равновероятна), в n оставшихся мы не угадываем и в колоде остаётся m красных и n-1 чёрных карт (любая перестановка которых опять же равновероятна). В обоих случаях ситуация после хода совершенно идентична ситуации до хода с изменёнными параметрами, поэтому мы можем вычислить среднее число карт, угаданных после первого хода, рекуррентно: f(m,n) = (f(m-1,n)+1)m/(m+n) + f(m,n-1)n/(m+n) при m >= n > 0.
Для 52 карт получается f(26,26) = 3724430600965475/123979633237026 = 30.04066477..., что довольно близко к 30, но не равно в точности.
Для 36 карт получается f(18,18) = 96587303059/4537567650 = 21.28614061…
GRUB сам умеет устанавливать видеорежим, стоит только его об этом попросить одним из двух способов: задать нужные параметры в заголовке Multiboot, www.gnu.org/software/grub/manual/multiboot/multiboot.html#Header-graphics-fields, либо задать параметр gfxpayload — по ссылке упоминается слово Linux, но для Multiboot-загрузки gfxpayload тоже поддерживается.
Если не нужно переключать видеорежим прямо в процессе работы системы, то тащить в систему полноценный эмулятор 16-битного кода совершенно ни к чему.
Так много букв и ни одной картинки. Сразу видно, что материал писался задолго до того, как у компьютеров появились графические дисплеи, сейчас намного более наглядным примером служат картинки фракталов: правила для построения достаточно просты и дают неожиданно красивый результат.
Ссылки по теме: множество Мандельброта (там даже код есть), множества Жюлиа.
Понятно, что если прямая задана своим уравнением ax + by + c = 0, то тут и решать нечего. Достаточно подставить координаты точки в уравнение прямой и проверить чему оно равно. Если больше нуля, то точка находится в верхней полуплоскости, если равна нулю, то точка находится на прямой и если меньше нуля, то точка находится в нижней полуплоскости.
Это работает только в половине случаев. Например, уравнения y = 0 и -y = 0 задают одну и ту же прямую — ось абсцисс — но для второго уравнения верхняя полуплоскость характеризуется тем, что результат подстановки меньше нуля. А для уравнений x = 0 и -x = 0 вообще не определено, какая полуплоскость верхняя, а какая нижняя.
Понятно, что на практике обычно достаточно просто различать две полуплоскости, называя их, к примеру, первой и второй, и для такой постановки ответ из статьи корректен, но формулировка глаз всё же режет.
Нет, планировщик вызывается при открытии канала, то есть несколько раз при начальной конфигурации устройства — требующей как минимум тех самых 100 мс в начале. На фоне которых говорить о паре тактов просто смешно.
Речь шла про трудоёмкость изменения кода только из-за того, что он на ассемблере. Я думаю, что демонстрация была достаточно убедительной, чтобы этот миф можно было закрыть.
Это неубедительное теоретизирование. Убедительной демонстрацией было бы «вот, смотрите, я заменил movi на mov и теперь <что-нибудь> занимает не две секунды, а одну».
Вот это интересно, подобное ещё и размер раздувает. Найдите — я исправлю и скажу «спасибо».
Инструкции можно переставить. Но здесь уже надо доказывать, что перестановка инструкций что-то даст.
Спасибо. Я стараюсь.
Распространённое заблуждение. Я приведу два примера.
Пример 1. У нас есть драйверы для видеокарт Intel и ATI, портированные с Linux, — естественно, на Си. Их нет в дистрибутиве: в образ они не влезают, а методы, позволяющие обратиться к дополнительным источникам данных, где бы они ни были, пока в разработке — но их без проблем можно найти на форуме. Так вот, дословный кусок кода из одной версии одного из драйверов:
Упражнение на понимание: выяснить, что делает код, записать результат одной ассемблерной командой и сравнить размеры.
Пример 2. Типичный фрагмент программы на Си:
Вменяемый ассемблерщик в зависимости от желания/наличия макросов может написать либо
либо, что то же самое,
Если p — локальная переменная и создан кадр стека, то [p] — что-то типа [ebp-4] и конструкция занимает 2*3+3+5+4 = 18 байт.
Барабанная дробь, gcc с ключом -Os, который якобы означает «оптимизировать по размеру»:
Здесь кадра стека уже не будет, и [p] — что-то типа [esp+20]. Теперь конструкция занимает 4+8*3+3+5 = 36 байт. Разница, как видно, в два раза. Сравнимый код, говорите?
Это не так. Производительность не меняется. Даже если вы можете считать отдельные такты, вы собьётесь со счёта в момент сброса контроллера — он занимает больше, чем вся остальная инициализация, вместе взятая, — а 100-миллисекундный интервал, требуемый перед началом какой бы то ни было работы с USB-устройством, покажется вам вечностью.
Я надеюсь, это не будет для вас шоком, но я должна признаться: я написала далеко не весь код ядра.
Кроме того, в некоторых местах производительность таки важнее размера.
… и какая разница, что размер результата будет в разы больше…
Вы серьёзно хотите продемонстрировать тезис о трудоёмкости изменений задачей, решаемой заменой по регэкспу? Специально для вас в свежей ревизии ядра я заменила пары push / pop на специальный макрос, который можно определить как простой mov, а можно определить так, как он сейчас определён — парой push / pop. Для критичных ко скорости участков по-прежнему есть mov.
2) Этот вопрос я не совсем поняла. Если «службой» вы называете отдельное usermode-приложение — фактически тот же драйвер, но в usermode — то писать сам драйвер проще бы не было — действия ровно те же самые. Возможно, было бы проще его отлаживать, несомненно, были бы куда легче последствия от того, что что-нибудь пошло не так. Ядру было бы, наоборот, сложнее. Я думаю над чем-нибудь типа libusb, но это явно неприоритетная вещь. Если вы имеете в виду что-то другое — уточните.
3) Ещё проще было бы ничего не делать. Естественно, это не позволило бы достичь цели. Если бы я ставила цель продемонстрировать какой-нибудь пример — скорее всего, я бы использовала Си как язык, понятный достаточно многим программистам и не слишком мешающий низкоуровневым вещам. Если бы я ставила цель реализовать пример какого бы то ни было USB-драйвера — я рассмотрела бы также вариант с той же libusb и, например, python, это бы ещё расширило круг читателей. Но моя цель — написать операционную систему возможно меньшего размера в той степени, пока это не мешает производительности, и C--, C, C++ не годятся для этого совсем никак.
add eax,2
иmov ecx,32
соответственно? Так короче. inc eax однобайтовая, add eax,2 занимает 3 байта. mov с непосредственным операндом — одна из немногих команд, не имеющих опкода, где непосредственное значение хранится как байт и расширяется до требуемого размера в ходе выполнения, поэтому mov ecx,32 занимает 5 байт — один на опкод, 4 на значение 32 — в отличие от push 32 с двумя байтами — один на опкод, 1 на значение 32.Это также медленнее на один-два такта, но конкретно этот участок выполняется один раз в жизни контроллера при инициализации, и несколько тактов роли не играют, а вот несколько байт заметны везде.
Краткий ответ на конкретный вопрос — нет, драйверного интерфейса для навешивания фильтра нет и не планируется. Отдельный нужный обработчик — например, сохранение сырых данных, передаваемых туда-обратно по шине, — легко добавить непосредственно в ядро.
Правильные ссылки:
инфраструктура USB: kernel/trunk/bus/usb
описание API для драйверов USB: kernel/trunk/docs/usbapi.txt
драйвер мыши и клавиатуры: kernel/trunk/drivers/usbhid.asm
драйвер флешек: kernel/trunk/drivers/usbstor.asm.
Простой перекомпиляции в случае сколько-нибудь нетривиального кода, конечно, недостаточно для переноса на новую архитектуру. Но 1) каждый новый момент, который нужно учитывать, уменьшает число желающих хоть что-то делать и 2) если имя уже было захардкожено, то получатся два варианта кода — под #ifdef _WIN64 и #elif — что усложнит дальнейшую поддержку, а в MS всё же стараются упрощать жизнь программистам, а не усложнять.
Ещё про переход 32 -> 64, хоть и не про файловую систему, но философию проясняет: blogs.msdn.com/b/oldnewthing/archive/2012/10/29/10363484.aspx. Несколько цитат, курсив из оригинала:
Другая проблема, обсуждаемая в той статье, — наличие скриптов, в частности, bat-ников, с жёсткими путями. Их перекомпилировать не нужно.
В мире оказалось слишком много программ с захардкоженным именем папки System32, и чтобы их не сломать, оказалось проще ввести перенаправление на уровне файловой системы. 32-битные приложения, обращаясь к system32, видят 32-битные версии, реально лежащие в syswow64, 64-битные, обращаясь туда же, видят 64-битные версии.
Если на каждом ходу называть ту масть, которой осталось больше, то вычисления чуть сложнее. Обозначим f(m,n) = среднее число угаданных карт, если начинать с колоды из m красных и n чёрных карт, равновероятно выбираемой из всех (m+n)! вариантов. Ясно, что f(m,n) = f(n,m), и что f(m,0) = f(0,m) = m. Если теперь m >= n > 0, то мы на первом ходу назовём красную масть. Для первой карты есть m+n вариантов, в m из них мы угадываем цвет и в колоде остаётся m-1 красных и n чёрных карт (любая перестановка которых равновероятна), в n оставшихся мы не угадываем и в колоде остаётся m красных и n-1 чёрных карт (любая перестановка которых опять же равновероятна). В обоих случаях ситуация после хода совершенно идентична ситуации до хода с изменёнными параметрами, поэтому мы можем вычислить среднее число карт, угаданных после первого хода, рекуррентно: f(m,n) = (f(m-1,n)+1)m/(m+n) + f(m,n-1)n/(m+n) при m >= n > 0.
Для 52 карт получается f(26,26) = 3724430600965475/123979633237026 = 30.04066477..., что довольно близко к 30, но не равно в точности.
Для 36 карт получается f(18,18) = 96587303059/4537567650 = 21.28614061…
Если не нужно переключать видеорежим прямо в процессе работы системы, то тащить в систему полноценный эмулятор 16-битного кода совершенно ни к чему.
«Просите, и дано будет вам; ищите, и найдёте»
Так много букв и ни одной картинки. Сразу видно, что материал писался задолго до того, как у компьютеров появились графические дисплеи, сейчас намного более наглядным примером служат картинки фракталов: правила для построения достаточно просты и дают неожиданно красивый результат.
Ссылки по теме: множество Мандельброта (там даже код есть), множества Жюлиа.
Картинка спёрта отсюда, если что.
Это работает только в половине случаев. Например, уравнения y = 0 и -y = 0 задают одну и ту же прямую — ось абсцисс — но для второго уравнения верхняя полуплоскость характеризуется тем, что результат подстановки меньше нуля. А для уравнений x = 0 и -x = 0 вообще не определено, какая полуплоскость верхняя, а какая нижняя.
Понятно, что на практике обычно достаточно просто различать две полуплоскости, называя их, к примеру, первой и второй, и для такой постановки ответ из статьи корректен, но формулировка глаз всё же режет.