[Назад] [Далее]

11.5. Программирование без использования libc

Может оказаться, что программа вынуждена многократно вызывать те или иные стандартные функции из 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с при компоновке.


п»ї
"target=_blank><\/a>") //-->