Pull to refresh

Comments 36

Как глубока кроличья нора? (с)

Да, пожалуй если говорить про "глубокое погружение", то стоит опуститься и ниже. И до уровня дешифраторов/счетчиков/регистров, и до уровня стандартной логики. Другое дело, что на каждом шаге мы отбрасываем весьма существенные детали. Иначе это все не влезло бы в статью. Ибо "глубокому пониманию" надо учиться всю жизнь. И то не факт, что постигнешь.

Автору (или все же переводчику) стоит сказать спасибо. Материал полезный (хотя в таком виде и не уникальный). Правда, обычно мы идем все же от электронов к процессам в операционной системе. Но абсолютная высота горы не зависит от выбранной точки отсчета.

Вообще, очень забавно наблюдать тенденции.

В мои 16 (ох, давно это было!) фактически универсальной отмазкой было "Чего с тобой разговаривать - ты даже до конца не понимаешь, как работает транзистор!" При этом, да, черт возьми, мало кто и тогда мог объяснить почему транзистор с двумя PN-переходами работает, а два встречно включенных диода - нет. И замечательное видео от Veritasium (к слову - есть лучше, но как всегда - в нужный момент не найти) появится еще не скоро.

Сейчас далеко не каждый системный программист-контроллерщик представляет себе внутренности того же STM32 с точностью до хотя бы регистров-дешифраторов, но говоря уже о стандартной логике или транзисторах. Ограничиваемся уровнями на выводах и неким "черным ящиком", который их обеспечивает.

Хорошо, что есть желающие опуститься ниже, а не только расти в ширь (как это в основном принято у прикладных программистов). Впрочем, жизнь меняется. Для того чтобы слушать радио или смотреть телевизор не обязательно было знать про лампы и реле. И даже создавать отличные радиоспектакли и телепередачи вполне получалось без этих знаний. Отчасти поэтому приятно скромно чувствовать себя истинным строителем храма, что бы остальные не думали по этому поводу.

Сейчас далеко не каждый системный программист-контроллерщик представляет себе внутренности того же STM32 с точностью до хотя бы
регистров-дешифраторов, но говоря уже о стандартной логике или транзисторах. Ограничиваемся уровнями на выводах и неким "черным
ящиком", который их обеспечивает.

Тут, на мой взгляд, Вы слегка перегибаете. Внутреннее устройство современных вычислетильных ядер (даже таких "простых" как Cortex M1) доступно очень ограниченному кругу лиц. Так, что знать устойство МК до дешифратора мало кто может по чисто организационным причинам. Но вот слепить своё вычислительное ядро из стандартной логики или написать на Verilog-е свой вариант RISC-V, я считаю, полезно каждому программисту и, тем более, железячнику. Почему таких вещей не преподают в наших профильных ВУЗах я радикально не понимаю, зато матана всякого разного-безобразного - до блевоты.

На счет строителей храма. Программисты и электронщики это современные жрецы и заклинатели, без их участия не работает ни один "волшебный шар" и именно поэтому они имеют полное право на привелегии, ровно как и физики, химики и специалисты по материаловедению и обработке материалов. Но почему-то в реальной жизни все получается строго наоборот.

Да, это именно то самый типовой комментарий, с комментариями на тему "почему так". На самом деле, внутренне устройство именно ядра не так и сложно. Во всяком случае в первом приближении. Без оптимизаций для максимальной производительности. Давайте спросим у людей в теме - допустим у присутствующего здесь @YuriPanchul.

Применительно к ARM'ам вообще сложно не ядро. Сложны шины, соединяющие периферийные блоки. И тут да - декомпозиция их вниз до транзисторов - это та еще забава.

Но речь-то даже не об этом. Внутренне устройство банальных I2C/SPI/UART/I2S в терминах регистров и стандартной логики уже не всякий системщик себе представляет. А уж интерфейсы посложнее, типа MIPI или USB - и того меньше, а уж PCIe - вообще магия даже для достаточно продвинутых.

Любимая забава, современный аналог той самой, про "как работает транзистор". Вот Arduino, вот документация на AVR, вот SPI интерфейс (стр. 136). Нарисуй его реализацию, начиная с регистров из документации. Без процессорного ядра. Без шин. Как думаете, много ли могут? Чтоб не спросить "вообще кто-нить может"? Или хотя бы попытается? И это при том, что в документации даже очень подробная структурная схема приведена.

Ладно, бросаю я ворчать. Все равно ворчанием делу не поможешь.

Мне в свое время вот этот курс понравился. Тут начинается с физики, дальше устройство транзисторов, лоджик гейты и как на этом сделать простой процессор.

Отличная работа по систематизации, спасибо!
Сохраню в закладках для сына-самоделкина :-)

Более удобный формат статьи

Таблица на треть экрана, половину от которой занимает навигация - ни разу не удобнее.

Начал читать и тут же споткнулся: «Код, не загруженный в ОП, не может быть выполнен.»

Что такое ОП? Оперативная память? Почему не ОЗУ, как принято? И вообще это утверждение мне видится неверным, оперативную память сначала надо проинициализировать, прежде чем с ней работать, откуда будет выполняться код сразу после нажатия на кнопку питания?

Код, на загруженный в ОП(ОЗУ), может быть выполнен, если это код, прошитый в ПЗУ.

Неплохой материал для новичков, спасибо. Буду предлагать к почтению юным подаванам.

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

Полистал оглавление "Архитектура компьютера" от Э. Таненбаум, вроде как присутствуют все темы, должна быть толковой.

У меня есть уже два толмуда со словосочетанием "архитектура компьютера" в названии: "Компьютерная архитектура. Количественный подход" от Хеннесси и Паттерсона и "Цифровая схемотехника и архитектура компьютера" от Харрис и Харрис. Обе сосредоточены на устройствах микропроцессоров и выше почти не поднимаются. Книга Харрисов хороша ровно до середины, где повествование резко перескакивает от аппаратного устройства ПЛИС и ПЛМ сразу на систему команд RISC-V. Зачем такой финт - не ясно. Почему нельзя было постепено рассмотреть куски процессора в отдельности и плавно подвести к системе команд и дальше к программированию ? Книга Паттерсона неплоха, но это скорее диссертация вперемешку с историческим очерком - дано описание развития архитектур в хронологическом порядке, рассмотрены некоторые типовые узлы процессоров, масса формул, выкладок и графиков. Подходит больше для научных сотруников, нежели студентам. :-)

У товарища Таненбаума есть еще такие же монументальные труды о сетях и операционнках (если нужно выше).

Вообще в Беларуси, почти на всех технарских компьютерных специальностях в универах по этим книгам учат. Интересно, думал что все про эти книги знают, но похоже это какая-то локальная особенность моей среды, раз вы раньше не натыкались.

Да нет, не локальная особенность. Может в вузах и не везде применяют, но просто гугля и ища книги по определенным темам не раз натыкалась именно на Таненбаума. Правда, его читать стоит, если действительно погружаешься глубоко в тематику, все же простому бэкендеру многовато будет. Но это мое имхо.

Проектирование 4004 началось в 1970 году, а не в конце 60х.

После выполнения всех инструкций, ЦП возвращает указатель в начало, и процесс повторяется. После выполнения инструкции указатель передвигается за нее в ОП и таким образом указывает на следующую инструкцию.

Как-то очень мутно написано. Если ЦП возвращает указатель в начало, значит программа зациклилась. Вся фишка в том что каждое чтение автоматически увеличивает счётчик команд ведь. Причем там внутри физически счётчик, никаких "указателей" и их "передвижения".

https://ru.wikipedia.org/wiki/Счётчик_команд

Всякие jmp, call, etc же как раз в него и пишут новые значения при вызове. А счётчик и правда вернётся в самое начало, если не будет инструкций его зацикливающих - т.е. пустая память, вся в nop.

На многих архитектурах и пользователю ничто не мешает напрямую писать в “счётчик команд”. Например, на PDP-11 этот самый PC (program counter) — это R7 (%7). А все регистры (8 штук) в PDP-11 равнозначны, там нет каких-то особых “аккумулятора”, “базы”, etc. Просто два регистра имеют “особенности” (R6/SP и R7/PC), но ничто не мешает их использовать точно так же, как и R0-R6.

В режиме ядра разрешено все: ЦП может выполнять любую поддерживаемую инструкцию и обращаться к любой памяти. В режиме пользователя разрешен только определенный набор инструкций, ввод/вывод (input/output, I/O) и доступ к памяти ограничены

А почему не выделить один процессор для ядра, а другой для user mode? Или одно из ядер процессора. И разделить память (шины) физически, чтобы из user mode в запрещенную память не залезть?

Нужно определиться в терминах. Суть в том, что нет такой единой сущности как "защищённая память". Есть физические страницы памяти, которые временно принадлежат каким-то процессам или ядру. И чтобы пресечь попытки одного процесса (из-за ошибки программиста или его злого умысла) подглядывать или что-то записывать в память другого процесса, ядро операционной системы "защищает" память каждого процесса от подобных действий. Придумано это было для разделения ресурсов (процессорных тактов, пространства оперативной памяти, доступа к переферицным устройствам).


Иначе, если отказаться от принципа разделения и защиты областей памяти каждого процесса друг от друга и, следуя вашему предложению, но довести его до логического конца, нужно будет строить некоторую аппаратную реализацию компьютера, в котором есть условно бесконечное количество процессоров, у каждого из которых есть своя собственная оперативная память и некий способ коммуникации между такими доменами. (Чем-то напоминает локальную сеть). Тогда каждый процесс получает свой выделенный процессор и всю оперативную память этого процессора, живёт в физическом адресном пространстве и ему никто не может помешать, как и он, собственно. А коммуникация с переферийными устройствами будет проходить через некоторый коммутатор-посредник, к которому имеет доступ каждый такой домен.

Т.е. уже сейчас это можно реализовать в примитивном виде на каких-нибудь одноплатниках, но тогда у вас дома будет столько маленьких компьютеров в локальной сети, сколько процессов вы захотите запустить. И при этом, т.к. ресурсы одноплатников ограниченны, если у процесса закончилась оперативная память, то увы и ах, вам нужно его запустить заново на другом таком одноплатнике, но с большим количеством оперативной памяти. Т.е. идея начинает выглядеть абсурдно, т.к. каждая новая вкладка в браузере будет требовать своего микрокомпьютера с достаточным количеством ресурсов. А чтобы управлять всей этой сворой железа будет нужен какой-нибудь отдельный компьютер-супервайзер-мастер-майнд, который будет распоряжаться доступными одноплатниками и руководить функциями загрузки и запуска процессов, а также диспетчеризацией обмена данными и убийством процессов.

Дальше перефразируя по М.М. Жванецкого: "Чем ядро сейчас и занято, но без этих хлопот". Т.е. виртуализируя адресное пространство оперативной памяти и работая супервайзером.

Не, ну речь не шла о том, что на каждый процесс свое процессорное ядро, это перебор. )
Один процессор и память для ядра, другой - для user mode с . ЧТобы пользовательский процесс физически не мог залезть в память ядра. Другой момент - а как организовать в этом случае взаимодействие между ядром и пользовательскими процессами.

Эм.. странный вопрос :) с какой целью? Ядро - это та же библиотека, но которой можно больше чем всем )

Ну это же классика:
Профессор: - Как работает трансформатор?
Студент: - У-у-у-у (изображает звук гудения трансформатора)
П: - Правильно
:))))

Слишком сумбурно. Такую обширную тему в такой объём не уместить.

В цикле работы ЦП ничего не сказано про обработку прерываний, а это ключевой момент для понимания работы компьютера.

Планировщики (schedulers) ОС используют чипы таймеров, такие как PIT, для запуска программных прерываний в целях многозадачности:

От таймера же прилетает аппаратное прерывание, а не программное.

Как работает компьютер:

Ну без Линуха это конечно не объяснить.

Ну дык смешались в кучу кони, люди...

Вообще-то, 4004 -- отнюдь не первый массовый ЦП. Это первый доступный (не секретный) микропроцессор, но компьютеры к моменту его появления выпускались уже порядка 25 лет -- и все они имели центральные процессоры. Ну а что они были выполнены (и ещё достаточно долго выполнялись) на целой куче деталек, а не в виде единственной микросхемы -- это технические подробности, а не принципиальное различие.

Intel и AMD не очень хорошо координировали свои действия при работе над x86-64. Поэтому у нас имеется 2 набора оптимизированных инструкций системных вызовов. SYSCALL и SYSENTER

Позвольте, но это же не совсем так! SYSCALL и SYSENTER появились в процессорах AMD и Intel соответственно во второй половине 1990-х, то время, когда процессоры у них были ещё 32-х битными. Мало того, как раз при разработке x86-64 проблем с координацией никаких не было, была стандартизирована всего одна инструкция для быстрого осуществления системного вызова, и это SYSCALL. См. подробности ниже.

Немного истории

Появление SYSENTER и SYSCALL в x86

В Linux поддержку этих инструкций завезли начиная с Linux 2.5(но многие узнали об этом только после релиза Linux 2.6, ЕМНИП), для кода x86 aka IA-32.

Поддержка SYSENTER в CPU от Intel была c Pentium II+, см. пруф и немного больше информации об этой инструкции.

У AMD примерно в это же время внедрялась поддержка инструкций, аналогичных инструкциям Intel по смыслу, но отличающихся по названию/опкоду. См. описание SYSCALL от AMD в SYSCALL and SYSRET Instruction Specification от 98-го года...

Соответственно, под x86 у нас было два разных набора инструкций от двух конкурирующих вендоров. И было это задолго до появления x86_64. Это были x86(IA-32) инструкции, 32-х битные, не имевшие отношения к появившемуся позже инструкциями архитектуры x86_64.

SYSCALL из x86-64

Для x86-64 в Linux используется только инструкция SYSCALL, так как набор 64-битных инструкций для архитектуры x86-64 разрабатывался целиком, и полностью, компаний AMD. Intel в то время пилила мертвеннорожденный Itanium, и к разработке x86-64 не прилагала никаких усилий. А потом, когда Intanium не взлетел, просто лицензировала уже разработанный ранее AMD набор инструкций x86-64. Вместе с инструкцией SYSCALL для быстрого вызова системных функций. Точно так же, как когда-то AMD у них набор команд IA-32 когда-то лицензировал.

Тут вот какое дело получилось: AMD x86-64 поддерживает только SYSCALL, а Intel расширила SYSENTER и на 64-битный набор инструкций, согласно подробному мануалу.

Но, AMD не поддерживает 64-битный от Intel. В итоге, разработчики Linux справедливо решили, что можно на x86-64 везде использовать SYSCALL, ведь эта инструкция работает на всех x86-64 одинаково.

А SYSENTER будет работать только на части x86-64 процессоров. И, при этом, не имеет никаких явных преимуществ перед SYSCALL.

Подробно ситуация с поддержкой этих инструкций для x86_64 и legacy mode(режима совместимости) описана в замечательном первом ответе на странице.

Режим совместимости

Важно помнить, что в x86-64 реализован не только набор 64-битных инструкций x86-64, но и весь старый набор инструкций x86(IA-32), работающих в режиме совместимости. Это было сделано по тем же соображениям, по которым в 32-х битных CPU архитектуры x86 в своё время сохранили 16-ти битный набор инструкций, и регистров. Если кратко, то новые CPU чисто с 64-битным набором инструкций никто бы не покупал, так как всё ПО для PC во время появления x86-64 было рассчитано на x86, а переписать/портировать быстро всё это 32-битное ПО на новую архитектуру было задачей не посильной. В итоге, CPU с архитектурой x86-64 кроме нового набора 64-битных инструкций содержат, одновременно, и старый набор инструкций(всё легаси времён x86 и Win9.x). Жаль, что легаси не выкинули из новых CPU, но имеем то, что имеем...

Способы использования инструкций SYSCALL и SYSENTER

В режиме совместимости(наборе 32-битных инструкций, унаследованных целиком от IA-32) вы можете использовать 32-битный вариант использования инструкции SYSCALL на CPU от AMD, и SYSENTER на CPU от Intel. Но, только в 32-битных приложениях.

По факту, 64-битная инструкция SYSCALL в x86-64 от обоих вендоров — это расширение 32-битной инструкции SYSCALL от AMD. Так как она реализована у обоих вендоров, в кои-то веки, одинаково, мы можем забыть про SYSENTER в 64-битном коде.

То есть, если рассуждать логически:

  • 64-битное приложение всегда использует SYSCALL, нет смысла дёргать SYSCALL на AMD, а SYSENTER на Intel, раз первая работает везде, а вторая только на чипах Intel;

  • 32-битное приложение, выполняемое в режиме совместимости, использует 32-битную инструкцию SYSCALL, или SYSENTER, в зависимости от вендора CPU.

Жестокая реальность мира 32-битных приложений в Linux

Но, на самом деле всё ещё хитрее, и куда запутаннее. В разных версиях clib это реализовано по разному, но конкретно glibc(GNU Libc) использует два разных варианта поведения, в зависимости от того, динамически собрано ПО, или статически.

Для статически собранного 32-битного бинарника, ЕМНИП, используется более медленный вариант с вызовом int0x80( __libc_do_syscall_int80) в случае отключенного I386_USE_SYSENTER, в противном случае будет вызываться быстрый вариант __libc_do_syscall с SYSENTER/SYSCALL.

Для динамически собранных бинарников используется обращение к call *%%gs:%P2, или call *_dl_sysinfo, в обоих случаях вызов передаётся в vdso. VDSO — вирутальная динамическая библиотека, которой не видно в ФС вашей системы, но в списке подгруженных вашим процессом so-шек она будет видна. По сути является частью ядра.

Механизм vdso предоставляет ускоренную реализацию нескольких особо часто вызываемых read-only системных вызовов прямо в пространстве пользователя, чтобы зря не переключать туда-сюда уровень выполнения кода, ибо смена контекста затратна по ресурсам, и свою оптимизированную версию функции вызова нужной syscall, работающей по принципу:

  1. CPU от AMD, юзаем ускоренную инструкцию системного вызова syscall;

  2. CPU от Intel, поддерживает ускоренную инструкцию sysenter, вызываем sysenter;

  3. CPU старый, не поддерживает эти инструкции — вызываем по старинке int0x80.

Итог

SYSCALL из набора 64-битных инструкций, и SYSCALL из набора 32-битных инструкций CPU от AMD — две разные реализации инструкции SYSCALL. Одна нативная для всех x86-64 CPU от AMD и Intel, а другая — реализация инструкции быстрого системного вызова *только для 32-битных AMD, и режима совместимости x86 для 32-битных приложений.

SYSENTER использовался широко только в 32-битных x86 процессорах от Intel, и используется в режиме совместимости для 32-битных приложений на x86-64 CPU от Intel.

Подробное описание SYSCALL для 32-битного использования, и 64-битного режимов, и SYSENTER только для режима совместимости у CPU от AMD есть тут

Вывод: статья хорошая, добротная, но её писал человек не помнивший старых реалий мира x86, или не сильно глубоко погрузившийся в тему, а может и намеренно её упростивший. Нужно проверять источники, не принимая на веру даже то, что написано в хорошей статье.

Кстати, в Windows выбор правильной инструкции из SYSCALL/SYSENTER осуществляется так же, как и в Linux, в зависимости от режима(64-битное приложение, или 32-битный код в режиме совместимости), вот тут есть немного информации для интересующихся этой темой.

За ссылку спс, в ней не хватает буквовки "l" в конце.

Подскажите, пожалуйста, зачем выделять ядру верхнюю память, если можно воспользоваться таблицей виртуальной памяти?

ЕМНИП, это наследние старых ограничений архитектуры x86.

Если кратко, то ядро Linux в x86 не может прямо рулить не отображённой в адресное пространство ядра памятью, так как ему необходим свой виртуальный адрес для любой памяти, с которой ядро непосредственно имеет дело. А в старых x86 из 4Гб памяти по умолчанию только 1, или 2Гб памяти, в зависимости от железа и конфигурации ядра, имели логические адреса.

С учётом этого, разрабы ядра решили, что ядро будет использовать 1Гб нижней памяти(этот порог был задан произвольно, можно было выбрать и другое значение), из общих 4Гб, к которым в теории можно было бы наладить прямую 32-битную адресацию. При этом ядро линковалось с постоянными смещениями относительно базового адреса, откуда стартовал диапазон памяти ядра, в диапазоне в 1Гб. В итоге, многие структуры ядра требовали размещения именно в нижней памяти.

А остальные 3Гб выделяются под верхнюю память, для которой не существуют логические адреса, потому что она выходит за границы диапазона адресов, отведённых под виртуальные адреса ядра. Перед доступом к верхней памяти ядро должно установить виртуальное соответствие для того, чтобы сделать запрашиваемую страницу памяти из верхней памяти доступной в адресном пространстве ядра. С учётом того, что и данные о подобных соответствиях хранятся ядром, в нижней части памяти, получается что многие структуры ядра нельзя было размещать где угодно, используя виртуальное соответствие. Тут получается заколдованный круг: чтобы работало виртуальное соответствие, нужно чтобы работало ядро, и механизм виртуального соответствия, а чтобы они работали, их нужно разместить туда, куда ядро может производить адресацию, не используя виртуальное соответствие, так как без него не получается работать с верхней памятью. Проблема курицы и яйца в чистом виде.

В итоге получается, что непосредственно ядру нужно было выделять нижнюю память, с которой оно может работать, не используя виртуальное соответствие, так как многие структуры необходимо разместить в памяти ещё до того, как заработает механизм виртуального соответствия. А вот приложения подобного ограничения не имеют, их запускает код, отвечающий за обработку соответствующих сисколов уже когда ядро загружено, и функционирует. А значит для них уже ядро может работать с верхней памятью.

В некоторых других 32-х битных архиртектурах процессоров подобного ограничения объёма памяти, для которой были доступны логические адреса, не было. И там и проблемы с необходимостью деления на нижнюю и верхнюю память бы не возникло. Но, на заре своего развития Linux очень сильно был привязан к x86, и дизайнился исходя из особенностей этой архитектуры. В 64-битных CPU, в том числе x86_64 эта проблема также изначально не стояла, если бы Linux писали с нуля под x86_64, деления памяти на нижнюю и верхнюю не было бы. Но, получилось, как получилось.

Немножко [подробностей по этой теме](Перед доступом к заданной странице верхней памяти ядро должно установить явное виртуальное соответствие).

Также это дело подробно описывалось в книжке "Драйверы устройств Linux", можете её тоже где-то отыскать.

Любой неофит найдёт в статье что-то новое и полезное, да. Но сама по себе статья — какой-то эклектичный микс поверхностности и глубинных раскопок.

Sign up to leave a comment.

Articles