Что происходит при запуске компьютерной программы? - PullRequest
176 голосов
/ 02 марта 2011

Я знаю общую теорию, но не могу вписаться в детали.

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

Я также знаю, что компьютерная программа использует два вида памяти: стек икуча, которые также являются частью основной памяти компьютера.Стек используется для нединамической памяти, а куча - для динамической памяти (например, все, что связано с оператором new в C ++)

Я не могу понять, как эти две вещи соединяются.В какой момент стек используется для выполнения инструкций?Инструкции идут из ОЗУ, в стек, в регистры?

Ответы [ 4 ]

158 голосов
/ 02 марта 2011

Это действительно зависит от системы, но современные ОС с виртуальной памятью имеют тенденцию загружать свои образы процесса и выделять память примерно так:

+---------+
|  stack  |  function-local variables, return addresses, return values, etc.
|         |  often grows downward, commonly accessed via "push" and "pop" (but can be
|         |  accessed randomly, as well; disassemble a program to see)
+---------+
| shared  |  mapped shared libraries (C libraries, math libs, etc.)
|  libs   |
+---------+
|  hole   |  unused memory allocated between the heap and stack "chunks", spans the
|         |  difference between your max and min memory, minus the other totals
+---------+
|  heap   |  dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
|   bss   |  Uninitialized global variables; must be in read-write memory area
+---------+
|  data   |  data segment, for globals and static variables that are initialized
|         |  (can further be split up into read-only and read-write areas, with
|         |  read-only areas being stored elsewhere in ROM on some systems)
+---------+
|  text   |  program code, this is the actual executable code that is running.
+---------+

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

Обратите внимание, что позиции, например, стека и кучи, могут находиться вдругой порядок в некоторых системах (см. ответ Билли О'Нила ниже для более подробной информации о Win32).

Другие системы могут быть очень разными.DOS, например, работал в режиме в реальном режиме , и его распределение памяти при запуске программ выглядело по-другому:

+-----------+ top of memory
| extended  | above the high memory area, and up to your total memory; needed drivers to
|           | be able to access it.
+-----------+ 0x110000
|  high     | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
|  upper    | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
|           | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+ 
|    DOS    | DOS permanent area, kept as small as possible, provided routines for display,
|  kernel   | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained 
|  vector   | the addresses of routines called when interrupts occurred.  e.g.
|  table    | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that 
|           | location to service the interrupt.
+-----------+ 0x0

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

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

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

Разные системы (встроенные, что угодно) могут иметь очень разные архитектуры, такие как системы без стеков, системы с гарвардской архитектурой (с кодом).и данные хранятся в отдельной физической памяти), системы, которые фактически хранят BSS в постоянной памяти (первоначально установленной программистом) и т. д. Но это общая суть.


Вы сказали:

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

«Стек»и «куча» - это просто абстрактные понятия, а не (обязательно) физически различные «виды» памяти.

стек - это просто структура данных «последний пришел, первый вышел».В архитектуре x86 к нему можно обратиться произвольно, используя смещение от конца, но наиболее распространенными функциями являются PUSH и POP для добавления и удаления элементов из него соответственно.Он обычно используется для локальных переменных функций (так называемое «автоматическое хранение»), аргументов функций, адресов возврата и т. Д. (Подробнее ниже)

A «куча» - это простопсевдоним для фрагмента памяти, который может быть выделен по требованию и адресован случайным образом (это означает, что вы можете получить доступ к любому месту в нем напрямую).Он обычно используется для структур данных, которые вы выделяете во время выполнения (в C ++, используя new и delete, malloc и друзей в C и т. Д.).

Стек и куча наАрхитектура x86 физически находится в системной памяти (ОЗУ) и отображается посредством выделения виртуальной памяти в адресное пространство процесса, как описано выше.

Регистры (все еще на x86),физически находятся внутри процессора (в отличие от ОЗУ) и загружаются процессором из области ТЕКСТ (а также могут быть загружены из других мест в памяти или других местах в зависимости от фактически выполняемых инструкций ЦП).По сути, это просто очень маленькие, очень быстрые ячейки памяти на кристалле, которые используются для различных целей.

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


Ваш вопрос:

В какой момент стек используется для выполнения инструкций? Инструкции идут из ОЗУ, в стек, в регистры?

Стек (в системах / языках, которые его используют и используют) чаще всего используется так:

int mul( int x, int y ) {
    return x * y;       // this stores the result of MULtiplying the two variables 
                        // from the stack into the return value address previously 
                        // allocated, then issues a RET, which resets the stack frame
                        // based on the arg list, and returns to the address set by
                        // the CALLer.
}

int main() {
    int x = 2, y = 3;   // these variables are stored on the stack
    mul( x, y );        // this pushes y onto the stack, then x, then a return address,
                        // allocates space on the stack for a return value, 
                        // then issues an assembly CALL instruction.
}

Напишите простую подобную программу, а затем скомпилируйте ее в сборку (gcc -S foo.c, если у вас есть доступ к GCC) и посмотрите. Сборка довольно проста для подражания. Вы можете видеть, что стек используется для локальных переменных функций, а также для вызова функций, хранения их аргументов и возвращаемых значений. Это также почему, когда вы делаете что-то вроде:

f( g( h( i ) ) ); 

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

К вашему сведению, приведенное выше соглашение о вызовах C также используется в C ++. Другие языки / системы могут помещать аргументы в стек в другом порядке, а некоторые языки / платформы даже не используют стеки и используют это по-разному.

Также обратите внимание, что это не фактические строки выполнения кода на C. Компилятор преобразовал их в инструкции машинного языка в вашем исполняемом файле. Затем они (как правило) копируются из области TEXT в конвейер ЦП, затем в регистры ЦП и оттуда выполняются. [Это было неверно. См. исправление Бена Фойгта ниже.]

59 голосов
/ 02 марта 2011

Sdaz получил значительное количество голосов за очень короткое время, но, к сожалению, увековечивает неправильное представление о том, как инструкции перемещаются через ЦП.

Заданный вопрос:

Инструкции идут из ОЗУ, в стек, в регистры?

Сдаз сказал:

Также обратите внимание, что это не фактические строки выполнения кода на Си.Компилятор преобразовал их в инструкции машинного языка в вашем исполняемом файле.Затем они (как правило) копируются из области TEXT в конвейер ЦП, затем в регистры ЦП и оттуда выполняются.

Но это неправильно.За исключением специального случая самоизменяющегося кода, инструкции никогда не вводятся в путь к данным.И они не могут и не могут быть выполнены из канала данных.

Регистры ЦП x86 :

  • Общие регистры EAX EBX ECX EDX

  • Сегментные регистры CS DS ES FS GS SS

  • Указатели и указатели ESI EDI EBP EIP EIP

  • Индикатор EFLAGS

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

Ни один из этих регистров не используется для исполняемого кода.EIP содержит адрес выполняемой инструкции, а не саму инструкцию.

Инструкции проходят через CPU совершенно иной путь, нежели данные (архитектура Гарварда).Все современные машины имеют архитектуру Гарварда внутри процессора.Большинство этих дней также гарвардской архитектуры в кеше.x86 (ваш обычный настольный компьютер) - это архитектура фон Неймана в основной памяти, что означает, что данные и код перемешаны в оперативной памятиЭто не относится к делу, поскольку мы говорим о том, что происходит внутри ЦП.

Классическая последовательность обучения в архитектуре компьютера - fetch-decode-execute.Контроллер памяти просматривает инструкцию, хранящуюся по адресу EIP.Биты инструкции проходят через некоторую комбинационную логику для создания всех управляющих сигналов для различных мультиплексоров в процессоре.А через несколько циклов арифметико-логический блок достигает результата, который синхронизируется с пунктом назначения.Затем извлекается следующая инструкция.

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

В довершение, терминология немного запутана, потому что register является термином электротехники для коллекции D-триггеров.И инструкции (или особенно микроинструкции) вполне могут временно храниться в такой коллекции D-триггеров.Но это не то, что подразумевается, когда ученый, программист или заурядный разработчик использует термин регистр .Они означают регистры канала передачи данных, как указано выше, и они не используются для передачи кода.

Имена и количество регистров канала передачи данных различаются для других архитектур ЦП, таких как ARM, MIPS, Alpha, PowerPC, но всеони выполняют инструкции, не пропуская их через АЛУ.

17 голосов
/ 02 марта 2011

Точная структура памяти во время выполнения процесса полностью зависит от используемой вами платформы. Рассмотрим следующую тестовую программу:

#include <stdlib.h>
#include <stdio.h>

int main()
{
    int stackValue = 0;
    int *addressOnStack = &stackValue;
    int *addressOnHeap = malloc(sizeof(int));
    if (addressOnStack > addressOnHeap)
    {
        puts("The stack is above the heap.");
    }
    else
    {
        puts("The heap is above the stack.");
    }
}

В Windows NT (и его дочерних элементах) эта программа обычно выдает:

Куча выше стека

На POSIX-боксах будет сказано:

Стек находится над кучей

Модель памяти UNIX довольно хорошо объясняется здесь @Sdaz MacSkibbons, поэтому я не буду повторять это здесь. Но это не единственная модель памяти. Причиной, по которой POSIX требуется эта модель, является системный вызов sbrk . По сути, на коробке POSIX, чтобы получить больше памяти, процесс просто говорит ядру переместить разделитель между «дырой» и «кучей» дальше в область «дыры». Невозможно вернуть память операционной системе, а сама операционная система не управляет вашей кучей. Ваша библиотека времени выполнения C должна обеспечить это (через malloc).

Это также имеет значение для вида кода, фактически используемого в двоичных файлах POSIX. Коробки POSIX (почти универсально) используют формат файла ELF. В этом формате операционная система отвечает за связь между библиотеками в разных файлах ELF. Следовательно, все библиотеки используют позиционно-независимый код (то есть сам код может быть загружен в разные адреса памяти и все еще работать), и все вызовы между библиотеками передаются через таблицу поиска, чтобы выяснить, куда нужно перейти элемент управления для перекрестного вызовы библиотечных функций. Это добавляет некоторые накладные расходы и может быть использовано, если одна из библиотек изменяет таблицу поиска.

Модель памяти Windows отличается, потому что тип кода, который она использует, отличается. Windows использует формат файла PE, который оставляет код в позиционно-зависимом формате. То есть код зависит от того, где именно в виртуальной памяти загружается код. В спецификации PE есть флаг, который указывает ОС, где именно в памяти библиотека или исполняемый файл хотели бы отображаться при запуске вашей программы. Если программа или библиотека не может быть загружена по предпочтительному адресу, загрузчик Windows должен перебазировать библиотеку / исполняемый файл - в основном он перемещает зависимый от позиции код, чтобы указывать на новые позиции - что не делает не требует таблиц поиска и не может быть использован, потому что нет таблицы поиска для перезаписи. К сожалению, это требует очень сложной реализации в загрузчике Windows и требует значительных временных затрат при запуске, если требуется перезагружать образ. Крупные коммерческие программные пакеты часто модифицируют свои библиотеки так, чтобы целенаправленно запускаться по разным адресам, чтобы избежать перебазирования; Windows сама делает это с помощью своих собственных библиотек (например, ntdll.dll, kernel32.dll, psapi.dll и т. д. - все они имеют разные начальные адреса по умолчанию)

В Windows виртуальная память получается из системы посредством вызова VirtualAlloc , и она возвращается в систему через VirtualFree (Хорошо, технически фермы VirtualAlloc отправляются в NtAllocateVirtualMemory, но это деталь реализации) (Сравните это с POSIX, где память не может быть восстановлена). Этот процесс медленный (и IIRC требует выделения в виде кусков физического размера страницы; обычно это 4 КБ или более). Windows также предоставляет свои собственные функции кучи (HeapAlloc, HeapFree и т. Д.) Как часть библиотеки, известной как RtlHeap, которая включена как часть самой Windows, для которой выполняется среда выполнения C (то есть malloc и друзья). обычно применяется.

Windows также имеет довольно много устаревших API-интерфейсов выделения памяти со времен, когда ей приходилось иметь дело со старыми 80386-ми, и эти функции теперь основаны на RtlHeap. Дополнительные сведения о различных API-интерфейсах, управляющих управлением памятью в Windows, см. В этой статье MSDN: http://msdn.microsoft.com/en-us/library/ms810627.

Обратите внимание, что это означает, что в Windows один процесс (и обычно он) имеет более одной кучи. (Как правило, каждая разделяемая библиотека создает свою собственную кучу.)

(Большая часть этой информации взята из "Безопасного кодирования на C и C ++" Роберта Сикорда)

5 голосов
/ 03 марта 2011

стек

В архитектуре X86 ЦПУ выполняет операции с регистрами. Стек используется только для удобства. Вы можете сохранить содержимое ваших регистров в стеке перед вызовом подпрограммы или системной функции, а затем загрузить их обратно, чтобы продолжить работу с того места, где вы оставили. (Вы можете сделать это вручную без стека, но это часто используемая функция, поэтому она поддерживает ЦП). Но вы можете делать практически все что угодно без стека в ПК.

Например, целочисленное умножение:

MUL BX

Умножает регистр AX на регистр BX. (Результат будет в DX и AX, DX содержит старшие биты).

Машины на основе стека (например, JAVA VM) используют стек для своих основных операций. Вышеуказанное умножение:

DMUL

Это извлекает два значения из верхней части стека и умножает их, затем возвращает результат обратно в стек. Стек необходим для такого рода машин.

Некоторые языки программирования более высокого уровня (например, C и Pascal) используют этот более поздний метод для передачи параметров в функции: параметры помещаются в стек в порядке слева направо и извлекаются из тела функции, а возвращаемые значения возвращаются назад. (Это выбор, который делают производители компиляторов, и злоупотребляет тем, как X86 использует стек).

Куча

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

Доступ к системным ресурсам

Операционная система имеет открытый интерфейс для доступа к ее функциям. В DOS параметры передаются в регистры процессора. Windows использует стек для передачи параметров для функций ОС (Windows API).

...