UbIvItS Что меняя программу на исходном языке, можно в определённых случаях влиять на качество кода, генерируемое компилятором, спорить глупо, однако это зависит в том числе и от компилятора. Строго говоря, если два варианта одной программы являются алгоритмически абсолютно идентичными, то и генерируемый компилятором код должен быть одним и тем же -- иначе смело можно говорить о том, что компилятор не способен выполнять глубокую оптимизацию и "не видит дальше собственного носа". При использовании таких плохих компиляторов возможность "извращаться" на самом ЯВУ действительно может оказаться полезной -- однако ценой потери читабельности программы, возможного ухудшения эффективности при использовании другого компилятора, возникновении проблем с переносимостью и т.д. и т.п. Ассемблер никуда не денется вне зависимости от умения компиляторов оптимизировать код -- Вы ж прекрасно понимаете, что есть вещи, которые в принципе невозможно написать на ЯВУ. Точно так же навряд ли возможно создать компилятор, который всегда генерирует оптимальный код (т.е. такой, какой в принципе улучшить возможности нет). Но причём здесь невозможность хаять Си? Я его охаиваю не за качество компиляторов (к коим претензий как раз нет), а за качество самого языка. Если надо выжать из железа всё -- тогда надо писать на ассемблере, поскольку никакие ухищрения на ЯВУ не дадут того, что позволит достичь ассемблер, да к тому же окажутся не слишком переносимыми (если не в плане пригодности для использования на другой платформе, то, по крайней мере, в плане эффективности). А если эффективность не стоит на первом месте, то зачем извращаться на ЯВУ и запутывать программу? Тогда надо писать так, чтобы не эффективность, а читабельность была выше. Соглашусь, но ровно наполовину: писать коммерческие системы на асме действительно экономически невыгодно, но это абсолютно никакого отношения не имеет к возможности их портирования на другие машины. Например, та же Солярис долгое время существовала только под собственные Сановские процессоры, и вопрос о портировании вообще никак не ставился, да и Вин9x никто ни на чём, кроме IA-32, использовать и не собирался. Можно вспомнить и OS/2, и вообще MS DOS. Так что отказ от ассемблера объясняется не необходимостью в портировании, а скоростью разработки плюс оплатой труда (которая зависит как от скорости, так и от квалификации программистов -- ну а час "среднестатистического" ассемблерщика явно ощутимо более дорогой, чем час среднего программиста, работающего на ЯВУ -- хотя бы из-за количества этих специалистов). Я же, когда говорю про ось на асме, во-первых, подразумеваю лишь ядро системы (управление памятью, задачами, низкоуровневым вводом-выводом, синхронизацией), а никак не всё вообще (например, командную оболочку -- типа shell или там command.com -- писать на асме резона нет, кроме разве что выпендрёжа или ситуации действительно острой нехватки памяти, чего в наши дни не бывает). Ну а во-вторых, я говорю про "идеальную ось", при разработке которой коммерческая сторона вообще не рассматривается, а упор делается на достижение наименьших накладных расходов и наибольшей надёжности (причём первое требование однозначно исключает использование в ядре любых языков, кроме асма, а второе достигается тщательным проектированием, кодированием и отладкой, а отнюдь не использованием "безошибочных" языков, коих вообще не существует и существовать не может). rei3er Определимся всё же с терминологией. В книге Роберта Лава "Программирование ядра Линух" выполняемый код пользователя носит название “процесс”, причём несколько раз оговаривается, что в Линухе нет разницы между процессом и потоком. Однако применительно к коду режима ядра, исполняемому под управлением планировщика и разделяющим процессорное время с этими самыми процессами пользователя, автор использует термин “поток ядра”. О том, что именно такое название верно, говорит и имя функции, которая создаёт такие потоки: kernel_thread. Я же, чтобы избежать дальнейшей путаницы, буду использовать только термин “поток”, и никаких “процессов”. Есть потоки режима пользователя, есть потоки режима ядра. И те, и другие ставятся на процессор и снимаются с него планировщиком, и разница между ними заключается только в том, в каком адресном пространстве и с какими привилегиями они работают (потоки ядра работают при CPL=0 в адресном пространстве ядра, потоки пользователя – при CPL=3 в своих собственных адресных пространствах). Ну а теперь собственно по теме Это в Линух так, но у меня – нет. В режиме ядра работают обработчики прерываний и потоки ядра. Первые не имеют права обращаться к системным сервисам, а значит, никаких int при их работе не возникает (могут возникнуть лишь прерывания ввода-вывода; возникновение любого программного прерывания свидетельствует о том, что ядро системы упало). Вторые ничем, кроме режима работы и адресного пространства, не отличаются от потоков пользователя, а поэтому свободно могут выдавать int для обращения к системным сервисам. Нет, у каждого потока ядра свой собственный стек, используемый именно для его личных нужд. Но в режиме ядра имеется ещё один стек, и именно адрес его вершины хранится в TSS – это стек собственно ядра, применяемый при обработке любых прерываний. Переключение между стеками происходит либо программно, либо аппаратно. Программное переключение требуется в том случае, если прерывание (неважно, какое именно) произошло во время выполнения потока ядра. Аппаратное переключение выполняется, понятное дело, автоматически – либо при переходе от кода пользователя к коду ядра (когда происходит прерывание во время работы потока пользователя), либо при обратном переходе (когда управление вновь передаётся тому же самому или новому потоку пользователя). Программное переключение выполняется в подпрограммах сохранения и завершения прерывания (шаги 1 и 5 в приведённом мною алгоритме). Аппаратные – соответственно в момент вызова обработчика прерывания и при завершении подпрограммы завершения прерывания (именно она выдаёт IRET). Переключение на стек потока выполняется при завершении обработки прерывания. Если это поток ядра, переключение будет программным, если – пользователя, то аппаратным. Естественно, планировщик, вызываемый при необходимости из подпрограммы завершения прерывания, должен “подсунуть” нужный указатель стека.
SII должен в идеале, но творения чуловеков далеки от идеала) что именно: управлять стеком, доступ к регистрам, I\O Low-Level: первые две вещи не имеют значения при автооптимайзе кода; третья решается путём внедрения функи в яву, коя и будет передавать нужные значения в нужный порт и контролировать размер передаваемых данных. во-во, "не слишком", а на чистом асме переносимости нет, вообще) насчёт цены разработки == согласен на все 100, а вот то, что портирование не причём - нет: вопрос портирование определяется рынком: если есть группа фирм, делающих процы довольно распостранёные, то, конечно, разработчики ПО решают вопрос о переносе кода на эти процы, если им не влом занять ещё немножко рынка
Добавлю. Эволюция разработки ПО имеет долгую историю. Были поиски, метания. Некоторые идеи были утопичными изначально, хотя и выглядели красиво. Писать ОС на Асме перестали давно в силу нескольких причин. 1. Низкая скорость разработки. 2. Огромная сложность в управлении проектом (не забываем, что времена программистов одиночек канули в прошлое). Если не сказать, что невозможность управлять проектом вовсе. 3. Сложность отладки. 4. Плохая переносимость кода (а часто и невозможная). 5. Практическая невозможность (или огромная сложность) расширять проект, и применительно к новым платформам и к отдельно взятой платформе. Это лишь часть причин, почему стали искать другой путь. Им оказался процедурный подход и ЯВУ. Из их огромного количества выбор пал на С. К чему бы это? 1. Он создавался специализированно. 2. Он реально доказал свои возможности. Теперь касательно его гибкости и сложности. Это как раз «наследие» Асма. Решение задачи разными способами, в результате чего, генерится различный код. И наверное для этого должны быть причины и причины есть - при возможности управления компилятором и гибкости самого языка, мы можем получать на выходе, то что нам нужно. //---------- Аргумент того что это «линия партии» верен. Не просто так разрабатывались стандарты. Но не стоит забывать, что это бизнес, а не хобби. А бизнес ищет наиболее оптимальные и дешевые решения. Очевидно, на данном этапе таковых нет. //----------- Поиски ведутся, по прежнему. И сейчас никто не откатывается назад. Исследования идут в области ООП и промежуточного кода. Кода который окончательно будет компилиться на конечном компе, учитывая особенности его архитектуры. Это будущее. С – настоящее. Все остальное – пройденный путь. //------------- Очевидно, при всем огромном разнообразии языков, не нашлось среди них такового, который отвечал нужным требованиям. Если бы таковой нашелся, то бизнес не прошел бы мимо. Ну не стоит, думать что люди, которые стоят у «руля» крупных компаний и корпораций, столь некомпетентны. И не видят очевидной выгоды, которая у них перед носом.
такое разделение ошибочно в виду отсутствия в Linux потоков по поводу названий, Linux в этом плане неоднозначная система некоторые названия достаточно странные, в том числе и функция создания процесса ядра хорошо, но только в этой теме нет поток ядра идентичен пользовательскому потоку за исключением того, что пользовательский поток может выполняться как при CPL = 3, так и при CPL = 0 в то время как поток ядра только при CPL = 0 т. е потоки ядра являются подмножеством пользовательских потоков такая схема работы достигается засчет наследования потоком ядра структуры mm пользовательского потока иными словами, во время своей работы (пока не истечет квант времени) поток ядра является воплощением вытесненного им пользовательского (или ядерного) потока с точки зрения адресного пространства теперь касаемо алгорима идею вашу понял но вы не учли один момент смотрите вот тут узкое место когда в следующий раз поток получит управление, единственное что он сможет сделать - это вернуться к выполнению пользовательского кода посредством iretd будет потеряно предыдущее состояние потока, т. е что он вообще выполнял до вытеснения в стеке системы будут сохранены значения регистров потока до int и стековый фрейм (ss, esp, eflags, cs, eip) вам не знакома следующая конструкция? Код (Text): ... set_current_state(TASK_INTERRUPTIBLE); while (!condition()) schedule() set_current_state(TASK_RUNNING); ... так вот, до и после этого кода может быть другой код если использовать ваш алгоритм, то во первых не должно быть цикла (т. е просто должен быть одиночный schedule()), а во-вторых, после schedule() не должно быть кода иначе мы не сможем корректно реализовать нужную функциональность но в свою очередь есть функциональность, которая просто требует наличия некоторого кода до и после приведеннго противоречие
UbIvItS все архитектурно-зависимые вещи ЯВУ их поддерживающие напрямую - уже не являются ЯВУ, поскольку они становятся зависимыми от архитектуры использование ассемблерных вставок не является использованием чистого ЯВУ + теряется переносимость, что невозможно в ЯВУ
rei3er а что компиль уже не в моде??) функа low-level i\o для каждого камня реализуется в соответствие с архитектурой его, как и любая другая - так в чём проблема???...?%)) фундаментальная проблема - это то, что асм кодер способен настрофать код лучше, чем компиль.
в конкретном компиляторе да мы же говорим о языке по определению язык высокого уровня определяется конструкциями, представление которых на уровне процессора возможно на любой архитектуре скажем >>, <<, &, +, -, ... но никак не функции, скажем, sidt(), sgdt(), wrmsr(), wbinvd(), ... они специфичны конкретно для IA-32 и не всегда возможно подобрать в таком случае реализацию для других архитектур по причине того, что это другие архитектуры
rei3er возможность послать данные в порт есть на любой архитектуре...... именно эта вещь и нужна для написания осей, все остальные тонкости нужны лишь по причине: )
rei3er Тем не менее, в названии функции стоит слово thread, а не process, а на русский оно переводится как "поток", а не как "процесс". Если сами линухоиды напутали в своей терминологии, я-то причём? Впрочем, здесь мы не терминологию как таковую обсуждаем Ешё раз повторяю: такой подход, когда пользовательский поток может типа выполняться и при CPL=3, и при CPL=0, имеет место в Линух, и именно это я считаю категорически неправильным подходом, ведущим лишь к пустому расходу памяти (кстати говоря, этот подход и нелогичен: при CPL=0 на самом деле выполняется код ядра, который лишь "приписали" к программе пользователя). При моём подходе пользовательский поток работает только при CPL=3. Оторвитесь на некоторое время от того, как это сделано в Линух -- похоже, именно привычка к линуховому подходу мешает Вам сходу "въехать" в то, о чём я говорю (со мной тоже так было, правда, в другую сторону -- до тех пор, пока я только с одной осью знаком был). Итак, поток имеет собственный стек, но поток всегда работает на одном и том же уровне привилегий (либо CPL=0, либо CPL=3). Если происходит прерывание (неважно, вызванное ли неким событием в программе -- в частности, появлением int, или же внешним событием), управление получает обработчик прерывания, который не является потоком, потому что неподвластен планировщику потоков (потоки ставит и снимает с процессора планировщик, обработчики прерываний "ставятся" на процессор аппаратно при появлении прерывания, а снимаются "по собственному желанию" вызовом подпрограммы завершения прерывания, в конечном счёте выполняющей IRET). Опять-таки, так это реализовано в Линух, но это не единственная возможность! Хотя время во время обработки прерывания, вызванного потоком, продолжает бежать (и убывать у текущего кванта потока), при его истечении во время обработки прерывания поток немедленно снят с процессора не будет: обработчик прерывания (в данном случае реализующий ту или иную функцию API ядра) всё равно доработает до конца, и лишь после того, возможно (если в очереди нет других ожидающих обработчиков прерывания) снятием потока с процессора займётся планировщик. До упомянутой Вами структуры Линух я ещё не дошёл, но, насколько понимаю, она управляет отображением памяти. Грубо говоря, в первом приближении можно считать, что это -- набор таблиц страниц, необходимый для отображения виртуальных адресов на страницы, занимаемые потоком. Естественно, при обработке запроса потока к ядру для выполнения какой-либо функции API ядра необходимо отображать адреса именно на этот поток, а не на какой-то другой. И это требование выполняется: переключение отображения адресов выполняется планировщиком потоков во время переключения с одного потока на другой. Пока планировщик не вызван, обработчик прерывания имеет доступ к адресному пространству потока. Не понял, в чём тут состоит узкое место? Обработчик прерывания сделал своё дело (выполнил действия, запрошенные от него потоком) и теперь возвращает управление. Если этот поток должен быть снят с процессора (например, он захотел перейти в ожидание, о чём и попросил систему с помощью соответствующего вызова), то обработчик прерывания, реализующий этот функционал, перед передачей управления подпрограмме завершения прерывания установит флаг, указывающий на необходимость вызова планировщика. В этом случае подпрограмма завершения прерывания не возвращает управление прерванному потоку, просто восстановив регистры и выполнив iret, а передаёт управление планировщику, который сохраняет контекст потока (в том числе регистры, находящиеся в данный момент в стеке), загружает контекст другого потока, который должен теперь получить управление (причём значения регистров "подсовываются" в стек на место регистров снятого потока) и возвращает управление подпрограмме завершения прерывания, которая на этот раз с чистой совестью вернёт управление потоку (она даже не знает, что это уже другой поток -- её это не касается). Ещё раз: отвлекитесь от того, как это сделано в Линух! Я предлагаю иной путь реализации! А значит, код из Линуха напрямую перетянуть будет невозможно (во всяком случае, тот код, что управляет переключением потоков и обработкой прерываний). Что же касается этого фрагмента кода, то это, похоже, бесконечный вызов планировщика, пока ожидаемое задачей событие не наступило. При моём подходе нужды в таком цикле попросту нет: задача, ожидающая какое-либо событие, выведена из списка готовых к выполнению задач и никогда не получит управления, пока не будет выполнено соответствующее условие. Возвращает же её в очередь готовых задач другой обработчик прерывания -- а именно тот, который обнаружит, что условие для данной задачи выполнено. Например, если задача остановила себя на определённое время (ждёт событие от таймера), то она будет возвращена в число готовых к выполнению обработчиком прерывания от таймера, когда тот обнаружит, что интервал ожидания истёк. Никакого противоречия нет. Вы мыслите категориями ядра Линух, а у меня принципиально иной подход к организации работы на уровне ядра. У меня -- не Линух! И никакого кода после действительно единственного вызова планировщика у меня выполнять не требуется. Например, для вывода потока из состояния ожидания соответствующий обработчик прерывания делает следующее: -- сбрасывает в блоке управления потоком флаги, указывающие, что поток находится в ожидании (какие именно -- зависит от "интимных подробностей" системы и для нас в данном случае не важно); -- помещает блок управления потоком в список готовых к выполнению потоков (т.е. тех, которые можно ставить на процессор); -- устанавливает флаг необходимости вызова планировщика (появился новый готовый к выполнению поток -- возможно, следует изменить очерёдность выполнения потоков, а за это отвечает планировщик); -- передаёт управление подпрограмме завершения прерывания.
rei3er UbIvItS Вообще-то проблема переносимости более широкая. Возьмём ту же Линух: для переноса на другую архитектуру надо не только переписать куски системы, зависящие от архитектуры -- необходимо ещё и переписать конкретный компилятор (GCC), даже если на целевой архитектуре уже есть свой компилятор Си. Так что сам по себе ЯВУ ещё не гарантирует переносимости; она достигается только тогда, когда программа использует исключительно официальные и всеми поддерживаемые стандарты, а не "фишки" того или иного конкретного компилятора. Это не значит, конечно, что я против переносимости и всего такого Только надо определиться с приоритетами. Если переносимость стоит на первом месте, то код на ЯВУ надо писать соответствующим образом -- в полном соответствии со стандартами, без "фишек" компилятора. Если требуется некий компромиссный подход (как в Линухе) -- можно использовать "фишки", но за это надо платить обязательным переносом не только собственно программы (ядра Линуха, например), но и конкретного компилятора. Ну а если главное и абсолютное требование -- "эффективность любой ценой", то на переносимость смело забиваем и пишем на асме. Собственно, в ядре системы, ИМХО, последний подход является наиболее правильным -- но это, повторяюсь, моя система приоритетов.
UbIvItS не только эта вещь для написания, скажем IA-32 совместимой ОС, нужно еще очень много кроме in и out SII отвечу позже
rei3er UbIvItS Ну а на целом ряде архитектур отдельного пространства ввода-вывода (а значит, и команд, аналогичных in и out) нет вовсе. Зато могут быть другие команды для организации ввода-вывода. В общем, для создания оси действительно нужно много что дополнительное, причём переносимость временами вступает в явное противоречие с эффективностью. Строго говоря, любая переносимая ось будет менее эффективной, чем "заточенная" под конкретную архитектуру, и вопрос лишь в том, насколько сильно эффективность уменьшается. Если на 1% -- не страшно, а если втрое?
SII речь идет о введении неких абстрактных инструкций, которые предоставляют доступ к пространству I/O (реализация для каждой архитектуры будет использовать реальные инструкции данной архитектуры) код ядра в контексте потока если хотите контекст - это текущий активный процесс, адрес дескриптора которого можно получить одним из способов, которые мы уже обсуждали опять терминология естественно, обработчик не является потоком и в Linux он не является потоком, и точно также не является оъектом панировщика (хотя бы потому, что во время обработки прерывания новые прерывания возникать не могут в связи с маскированием соответствующей линии IRQ) он работает в контексте потока и даже в вашем подходе все равно есть понятие контекста нет прерывания запрещены, поэтому квант не может уменьшаться (он уменьшается в обработчике прерывания таймера) вот это и есть контекст потока, о чем я и говорю суть в том, что могут существовать некоторые отложенные действия, которые могут быть выполнены после того, как некоторое событие произошло (напремер инкрементация счетчика семафора (после декрементации и ожидания)) код - это просто пример ситуации когда нужно выполнение некоторых действий после ожидания или вы уверены, что систему можно реализовать так, что такие ситуации будут отсутствовать?! да, я согласен и в Linux точно так же но повторюсь, могут быть действия, которые могут быть выполнены только в контексте этого возвращенного в очередь процесса ведь другой обработчик "знает" только о событии, но не о контексте возникновения этого события в контексте процесса.
rei3er ИМХО контекст - общедоступные для потока с CPL=0 (ядра) и потока с CPL=3 (пользовательского) сегменты памяти, содержащие одинаково итерпретируемые данные.
rei3er Тяжело объяснить дистанционно, если честно... В общем, код обработчика прерывания принадлежит ядру, а не потоку. Он, конечно, "знает", какой поток был прерван, и при необходимости может получить доступ к управляющим блокам этого потока или к его адресному пространству, однако частью потока от этого он не становится. В Линух (и не только в Линух, но это к слову), насколько я понял, код ядра, реализующий функции API, "заставили" работать в контексте потока и как часть этого потока (т.е., когда выдаётся int 80h, с точки зрения системы, и, в частности, планировщика поток не приостанавливает свою работу, а лишь переходит в режим ядра). Я же ратую за другой подход: код ядра всегда выполняется независимо от потоков, даже если выполняет действия, связанные с каким-то определённым потоком. От понятия контекста мы никуда в принципе не денемся Вопрос только в том, что в Линухе функции API ядра работают в контексте вызвавшего их потока, а в моём подходе -- в своём собственном контексте (контекст потока сохранён -- частично в стеке, частично в управляющих блоках -- и является доступным для обработчиков прерываний, но не является "активным"). В Линух, как я понял, не являются потоками обработчики внешних прерываний (от внешних устройств), но не прерываний, возникающих "по вине" потока (т.е. в результате использования команды int или там деления на нуль и т.п.) -- последние рассматриваются как часть потока, "сообразившего" это прерывание. Кстати, в моём подходе на определённых этапах функционирования обработчика могут возникать новые прерывания, что уменьшает время реакции системы на них (при полностью запрещённых прерываниях работа ведётся очень короткое время, не превышающее, скажем, выполнения сотни машинных команд; при частично запрещённых -- тоже ограниченное). Так реализовано в Линухе, но это не всегда правильно. Например, такой подход недопустим в ОСРВ: там таймер должен работать чётко, без пропусков циклов, да и прерывания должны запрещаться лишь на минимальное время. И какая проблема? Происходит событие -- вызывается обработчик этого события, он и выполняет это действие. Не врубаюсь, в чём проблема. Кто должен перейти в ожидание? Поток? Ну так я ещё в самом первом посте показал, как он будет переведён в ожидание: он просто будет помечен как ожидающий, после чего будет вызван планировщик, который полностью сохранит его контекст в отведённом для этого месте (до этого содержимое регистров потока было сохранено в стеке, а остальные элементы контекста просто не затрагиваются обработчиком и поэтому в цельности и сохранности "лежат на своих местах") и запустит другой поток. И всё! Когда же этот поток выйдет из ожидания, он вновь получит управление и будет делать всё, что ему надо. Действий, которые могут быть выполнены "только в контексте", не существует. Есть действия, которые для своего выполнения требуют доступа к управляющим блокам или к памяти конкретного потока. Но это вовсе не означает, что эти действия должны выполняться в контексте этого потока.
t00x Да нет... Контекст потока -- это и содержимое его регистров, и содержимое его виртуальной памяти, и списки используемых им ресурсов (файлов и т.п.). В общем -- целый набор всего.
речь шла о "коде ядра в контексте потока". (#174) имелся в виду не "Контекст потока", а "контекст" выполнения кода ядра на данных пользовательского потока. и как следствие методы передачи (или скорее расшаривания) данных пользовательского потока для потока ядра, в котором выполняется код ядра.
rei3er SII вы излагаете общими фразами - поправте ламера, если в чём не прав камень выполняет след. ф-ии: > ариф. операции; > лог. операции; > условное/безусловное ветвление; > I\O; > IRQ. ------------ разве что-то из этого невозможно представить ввиде яву фунок??? SII это, итак, всем ясно), но что легче: настрофать компиль и пропустить через него тонны кода с мин. правками или эти тонны переписывать с нуля)???