Как загрузочная работа работает для gcc?

Я искал проект pypy (Python на Python) и начал размышлять над вопросом о том, что работает с внешним слоем python? Разумеется, я предположил, что это не может быть так, как говорится в старой поговорке "черепахи полностью вниз"! Afterall, python недопустим сборку x86!

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

Это все хорошо, но вопрос все еще остается, как компьютер получает этот файл сборки?!

Скажем, я покупаю новый процессор, на котором ничего нет. Во время первой операции я хочу установить ОС, которая запускает C. Что запускает компилятор C? Есть ли миниатюрный компилятор C в BIOS?

Может кто-нибудь объяснить это мне?

Ответ 1

Скажем, я покупаю новый процессор, на котором ничего нет. Во время первой операции я хочу установить ОС, которая запускает C. Что запускает компилятор C? Есть ли миниатюрный компилятор C в BIOS?

Я понимаю, о чем вы спрашиваете... что произойдет, если у нас не будет компилятора C и он должен начинаться с нуля?

Ответ: вам придется начинать с сборки или аппаратного обеспечения. То есть вы можете построить компилятор в программном или аппаратном обеспечении. Если бы не было компиляторов во всем мире, в эти дни вы, вероятно, могли бы сделать это быстрее в сборке; однако, в тот же день, я считаю, что компиляторы были на самом деле посвящены части оборудования. статья wikipedia немного коротка и не поддерживает меня в этом, но неважно.

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

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

Как только это будет сделано, теоретически вы можете переходить от одной платформы к другой. Следующими этапами являются: создание ядра, загрузчика и некоторых базовых утилит userland для этой платформы.

Затем вы можете попробовать компилировать компилятор для этой платформы (как только у вас есть рабочая область и все необходимое для запуска процесса сборки). Если это удастся, у вас есть основные утилиты, работающее ядро, userland и система компилятора. Теперь у вас все в порядке.

Обратите внимание, что в процессе портирования компилятора вам, вероятно, необходимо написать ассемблер и компоновщик для этой платформы. Чтобы упростить описание, я опустил их.

Если это интересно, Linux из Scratch - интересное чтение. Он не говорит вам о том, как создать новую цель с нуля (что существенно нетривиально) - предполагается, что вы собираетесь строить существующую известную цель, но она показывает вам, как вы переходите компиляцию основ и начинаете строить вверх по системе.

Python фактически не собирается для сборки. Для начала запущенная программа python отслеживает количество ссылок на объекты, что CPU не сделает для вас. Однако концепция кода на основе инструкций также лежит в основе Python. Поиграйте с этим:

>>> def hello(x, y, z, q):
...     print "Hello, world"
...     q()
...     return x+y+z
... 
>>> import dis
dis.dis(hello)


  2           0 LOAD_CONST               1 ('Hello, world')
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  3           5 LOAD_FAST                3 (q)
              8 CALL_FUNCTION            0
             11 POP_TOP             

  4          12 LOAD_FAST                0 (x)
             15 LOAD_FAST                1 (y)
             18 BINARY_ADD          
             19 LOAD_FAST                2 (z)
             22 BINARY_ADD          
             23 RETURN_VALUE

Здесь вы можете увидеть, как Python думает о введенном вами коде. Это байт-код python, то есть язык ассемблера python. Он эффективно имеет свой собственный "набор команд", если вам нравится реализация языка. Это концепция виртуальной машины.

Java имеет точно такую ​​же идею. Я взял функцию класса и запустил javap -c class, чтобы получить следующее:

invalid.site.ningefingers.main:();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iconst_0
   3:   istore_1
   4:   iload_1
   5:   aload_0
   6:   arraylength
   7:   if_icmpge   57
   10:  getstatic   #2; 
   13:  new #3; 
   16:  dup
   17:  invokespecial   #4; 
   20:  ldc #5; 
   22:  invokevirtual   #6; 
   25:  iload_1
   26:  invokevirtual   #7; 
   //.......
}

Я понимаю, вы поняли. Это языки ассемблера миров python и java, т.е. Как интерпретируют интерпретатор python и java-компилятор.

Что-то еще, что стоит прочитать, это JonesForth. Это как рабочий переводчик, так и учебник, и я не могу рекомендовать его достаточно, чтобы подумать о том, "как все получается" и как вы пишете простой, легкий язык.

Ответ 2

В интересах производительности я уверен, что компиляторы C только что собраны из сборки.

Компиляторы

C в настоящее время (почти?) полностью написаны на языках C (или более высокого уровня - Clang - С++, например). Компиляторы почти ничего не получают, включая рукописный ассемблерный код. Вещи, которые занимают больше всего времени, так же медленны, как и потому, что они решают очень сложные проблемы, где "жесткие" средства означают "большую вычислительную сложность" - переписывание в сборке приносит практически постоянное ускорение, но на самом деле это не имеет значения уровень.

Кроме того, большинство компиляторов хотят высокой переносимости, поэтому архитектурные трюки в переднем и среднем конце не могут быть и речи (и в бэкэнд они тоже нежелательны, потому что они могут нарушить кросс-компиляцию).

Скажем, я покупаю новый процессор, на котором ничего нет. Во время первой операции я хочу установить ОС, которая запускает C. Что запускает компилятор C? Есть ли миниатюрный компилятор C в BIOS?

Когда вы устанавливаете ОС, там (как правило) нет компилятора C. Установочный компакт-диск заполнен скомпилированными двоичными файлами для этой архитектуры. Если включен компилятор C (как в случае со многими дистрибутивами Linux), это уже скомпилированное exectable тоже. И те дистрибутивы, которые заставляют вас создавать собственное ядро ​​и т.д., Также включают в себя как минимум один исполняемый файл - компилятор. Это, конечно, если вы не должны скомпилировать свое собственное ядро ​​при существующей установке чего-либо с компилятором C.

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

Ответ 3

Если вы покупаете новую машину с предустановленной ОС, ей даже не нужно включать компилятор в любом месте, потому что весь исполняемый код был скомпилирован на какой-либо другой машине, кем бы вы ни предоставляли ОС - ваша машина не работает Не нужно ничего компилировать.

Как вы дошли до этого, если у вас есть совершенно новая архитектура процессора? В этом случае вы, вероятно, начнете с написания нового поколения генерации кода для новой архитектуры процессора ( "целевой" ) для существующего компилятора C, который работает на какой-либо другой платформе ( "хост" ) - a кросс-компилятор.

Как только ваш кросс-компилятор (работает на хосте) работает достаточно хорошо, чтобы создать правильный компилятор (и необходимые библиотеки и т.д.), который будет запущен на целевой, тогда вы можете скомпилировать компилятор с собой на целевой платформе, и в конечном итоге с целевым-родным компилятором, который работает на целевом объекте и генерирует код, который выполняется на целевом сервере.

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