Что происходит, когда вы запускаете программу?
Я хотел бы собрать здесь, что происходит, когда вы запускаете исполняемый файл в Windows, Linux и OSX. В частности, я хотел бы точно понять порядок операций: я предполагаю, что формат исполняемого файла (PE, ELF или Mach-O) загружается ядром (но я игнорирую различные разделы ELF(Executable and Linkable Format) и их значение), а затем у вас есть динамический компоновщик, который разрешает ссылки, затем __init
часть исполняемого файла запускается, затем основной, затем __fini
, а затем программа завершена, но я уверен, что это очень грубо, и, вероятно, неправильно.
Изменить: вопрос сейчас CW. Я заполняю для Linux. Если кто-то хочет сделать то же самое для Win и OSX, это было бы здорово.
5 ответов
Это только на очень высоком и абстрактном уровне, конечно!
Executable - No Shared Libary:
Client request to run application
->Shell informs kernel to run binary
->Kernel allocates memory from the pool to fit the binary image into
->Kernel loads binary into memory
->Kernel jumps to specific memory address
->Kernel starts processing the machine code located at this location
->If machine code has stop
->Kernel releases memory back to pool
Executable - Shared Library
Client request to run application
->Shell informs kernel to run binary
->Kernel allocates memory from the pool to fit the binary image into
->Kernel loads binary into memory
->Kernel jumps to specific memory address
->Kernel starts processing the machine code located at this location
->Kernel pushes current location into an execution stack
->Kernel jumps out of current memory to a shared memory location
->Kernel executes code from this shared memory location
->Kernel pops back the last memory location and jumps to that address
->If machine code has stop
->Kernel releases memory back to pool
JavaScript/.NET/Perl/Python/PHP/Ruby (Interpretted Languages)
Client request to run application
->Shell informs kernel to run binary
->Kernel has a hook that recognises binary images needs a JIT
->Kernel calls JIT
->JIT loads the code and jumps to a specific address
->JIT reads the code and compiles the instruction into the
machine code that the interpretter is running on
->Interpretture passes machine code to the kernel
->kernel executes the required instruction
->JIT then increments the program counter
->If code has a stop
->Jit releases application from its memory pool
Как говорится в routeNpingme, регистры устанавливаются внутри процессора, и волшебство происходит!
Обновление: Да, я не могу правильно написать сегодня!
Хорошо, отвечаю на мой вопрос. Это будет сделано постепенно, и только для Linux (и, возможно, Mach-O). Не стесняйтесь добавлять больше материалов к вашим личным ответам, чтобы за них проголосовали (и вы можете получить значки, так как теперь это CW).
Я начну на полпути, а остальное соберу, как узнаю. Этот документ был сделан с x86_64, gcc (GCC) 4.1.2.
Открытие файла, инициализация
В этом разделе мы опишем, что происходит при запуске программы, с точки зрения ядра, до тех пор, пока она не будет готова к выполнению.
- ELF открыт.
- ядро ищет раздел.text и загружает его в память. Отмечает это как только для чтения
- ядро загружает раздел.data
- ядро загружает раздел.bss и инициализирует весь контент до нуля.
- ядро передает управление динамическому компоновщику (имя которого находится внутри файла ELF, в разделе.interp). Динамический компоновщик разрешает все вызовы общей библиотеки.
- контроль передается в приложение
Выполнение программы
- вызывается функция _start, так как заголовок ELF указывает ее как точку входа для исполняемого файла
_start вызывает __libc_start_main в glibc (через PLT), передавая ему следующую информацию
- адрес фактической основной функции
- адрес argc
- адрес argv
- адрес процедуры _init
- адрес процедуры _fini
- указатель на функцию для регистрации atexit()
- самый высокий доступный адрес стека
_init вызывается
- вызывает call_gmon_start для инициализации профилирования gmon. на самом деле не связано с исполнением.
- вызывает frame_dummy, который обертывает __register_frame_info(адрес секции eh_frame, адрес секции bss) (FIXME: что делает эта функция? инициализирует глобальные переменные из секции BSS, очевидно)
- вызывает __do_global_ctors_aux, роль которого заключается в вызове всех глобальных конструкторов, перечисленных в разделе.ctors.
- главный называется
- основные концы
- Вызывается _fini, который в свою очередь вызывает __do_global_dtors_aux для запуска всех деструкторов, как указано в разделе.dtors.
- программа выходит.
В Windows сначала изображение загружается в память. Ядро анализирует, какие библиотеки (читай "DLL") ему потребуется, и загружает их тоже.
Затем он редактирует образ программы для вставки адресов памяти каждой из библиотечных функций, которые ему требуются. Эти адреса уже имеют пробел в двоичном файле.EXE, но они просто заполнены нулями.
Затем каждая процедура DllMain () DLL выполняется, одна за другой, от самой необходимой DLL до последней, как в порядке следования зависимостей.
Как только все библиотеки были загружены и готовы, наконец, образ запускается, и то, что сейчас произойдет, будет зависеть от используемого языка, используемого компилятора и самой программы.
Как только изображение загружается в память, волшебство вступает во владение.
Ну, в зависимости от вашего точного определения, вы должны учитывать JIT-компиляторы для таких языков, как.Net и Java. Когда вы запускаете.Net "exe", который технически не "исполняем", JIT-компилятор включается и компилирует его.