Со стороны пользовательского пространства, механика идентична созданию системного вызова на 32-битном собственном ядре - весь код пользовательского режима, включая 32-битный glibc, работает одинаково.
Со стороны ядра,старые точки входа IA32 из пользовательского пространства (например, int 0x80
) настроены для вызова процедуры ассемблера ia32_syscall
.(Переход к пространству ядра включает в себя загрузку процессором селектора сегмента кода ядра, что приводит к переходу в 64-битный «длинный» режим).
Затем подпрограмма ia32_syscall
перемешивает некоторые аргументы для соответствияСоглашение о вызове системного вызова x86_64:
movl %edi,%r8d
.if \noebp
.else
movl %ebp,%r9d
.endif
xchg %ecx,%esi
movl %ebx,%edi
movl %edx,%edx /* zero extension */
Затем он использует номер системного вызова IA32 для вызова функции через таблицу ia32_sys_call_table
.Это, по существу, сопоставляет номера системных вызовов IA32 с собственными реализациями системных вызовов (номера системных вызовов сильно отличаются между IA32 и x86_64).Первая часть этой таблицы выглядит следующим образом:
ia32_sys_call_table:
.quad sys_restart_syscall
.quad sys_exit
.quad stub32_fork
.quad sys_read
.quad sys_write
Для большинства системных вызовов реализация x86_64 теперь может вызываться напрямую - как exit()
.Для других, например fork()
, предусмотрена оболочка, которая корректно реализует ожидаемую семантику IA32 (в частности, если требуется расширение знака аргументов с 32-битного до 64-битного).
Как видите,накладные расходы в коде ядра минимальны - несколько тривиальных модификаций для регистрации значений, а для некоторых функций - дополнительный вызов функции.Я не уверен, что загрузка селектора сегмента кода, который вызывает переход из 32-битного режима в 64-битный режим, медленнее для процессора, чем тот, который не выполняет - проверьте руководства по архитектуре процессора для этого.