Может оказаться, что программа вынуждена многократно вызывать те или иные стандартные функции из libc в критическом участке, тормозящем выполнение всей программы. В этом случае стоит обратить внимание на то, что многие функции libc на самом деле всего лишь более удобный для языка С интерфейс к системным вызовам, предоставляемым самим ядром операционной системы. Такие операции, как ввод/вывод, вся работа с файловой системой, с процессами, с TCP/IP и т.п., могут выполняться путем передачи управления ядру операционной системы напрямую.
Чтобы осуществить системный вызов, надо передать его номер и параметры на точку входа ядра аналогично функции libc syscall(2). Номера системных вызовов (находятся в файле /usr/include/sys/syscall.h) и способ обращения к точке входа (дальний call по адресу 0007:00000000) стандартизированы SysV/386 ABI, но, например в Linux, используется другой механизм — прерывание 80h, так что получается, что обращение к ядру операционной системы напрямую делает программу привязанной к этой конкретной системе. Часть этих ограничений можно убрать, используя соответствующие #define, но в общем случае этот выигрыш в скорости оборачивается еще большей потерей переносимости, чем само использование ассемблера в UNIX.
Посмотрим, как реализуются системные вызовы в рассматриваемых нами примерах:
// hellolnx.s
// Программа, выводящая сообщение "Hello world" на Linux
// без использования libc
//
// Компиляция:
// as -о hellolnx.o hellolnx.s
// ld -s -o hellolnx hellolnx.o
//
.text
.globl _start
_start:
// системный вызов #4 "write", параметры в Linux помещают слева направо,
// в регистры %еах, %ebx, %ecx, %edx, %esi, %edi
movl $4,%eax
xorl %ebx,%ebx
incl %ebx
// %ebx = 1 (идентификатор stdout)
movl $message,%ecx
movl $message_l,%edx
// передача управления в ядро системы - прерывание с номером 80h
int $0x80
// системный вызов #1 "exit" (%еах = 1, %ebx = 0)
xorl %eax,%eax
incl %eax
xorl %ebx,%ebx
int $0x80
hlt
.data
message:
.string "Hello world\012"
message_l = . - message
Linux — это довольно уникальный случай в отношении системных вызовов. В более традиционных UNIX-системах — FreeBSD и Solaris — системные вызовы реализованы согласно общему стандарту SysV/386, и различие в программах заключается только в том, что ассемблер, поставляемый с FreeBSD, не поддерживает некоторые команды и директивы.
// hellobsd.s
// Программа, выводящая сообщение "Hello world" на FreeBSD
// без использования libc
//
// Компиляция:
// as -о hellobsd.o hellobsd.s
// ld -s -o hellobsd hellobsd.o
//
.text
.globl _start
_start:
// системная функция 4 "write"
// в FreeBSD номер вызова помещают в %еах, а параметры - в стек
// справа налево плюс одно двойное слово
pushl $message_l
// параметр 4 - длина буфера
pushl $message
// параметр 3 - адрес буфера
pushl $1
// параметр 2 - идентификатор устройства
movl $4,%еах
// параметр 1 - номер функции в еах
pushl %eax
// в стек надо поместить любое двойное слово, но мы поместим номер вызова
// для совместимости с Solaris и другими строгими операционными системами
// lcall $7,$0 - ассемблер для FreeBSD не поддерживает эту команду
.byte 0x9a
.long 0
.word 7
// восстановить стек
addl $12,%esp
// системный вызов 1 "exit"
xorl %eax,%eax
pushl %eax
incl %eax
pushl %eax
// lcall $7,$0
.byte 0x9A
.long 0
.word 7
hlt
.data
message:
.ascii "Hello world\012"
message_l = . - message
И теперь то же самое в Solaris:
// hellosol.s
// Программа, выводящая сообщение "Hello world" на Solaris/x86
// без использования libc
//
// Компиляция:
// as -о hellosol.o hellosol.s
// ld -s -o hellosol hellosol.o
.text
.globl _start
_start:
// комментарии - см. hellobsd.s
pushl $message_l
pushl $message
movl $4,%eax
pushl %eax
lcall $7,$0
addl $16,%esp
xorl %eax,%eax
pushl %eax
incl %eax
pushl %eax
lcall $7,$0
hit
.data
message:
.string "Hello world\012"
message_l = . - message
Конечно, создавая эти программы, мы нарушили спецификацию SysV/386 ABI несколько раз, но из-за того, что мы не обращались ни к каким разделяемым библиотекам, это прошло незамеченным. Требования к полноценной программе сильно разнятся в различных операционных системах, и все они выполнены с максимально возможной тщательностью в файлах crt*.o, которые мы подключали в примере с использованием библиотечных функций. Поэтому, если вы не ставите себе цель сделать программу абсолютно минимального размера, гораздо удобнее назвать свою процедуру main (или _main) и добавлять crt*.o и -lс при компоновке.
|