Разница между API и ABI - PullRequest
       47

Разница между API и ABI

170 голосов
/ 24 сентября 2010

Я новичок в системном программировании Linux, и я столкнулся с API и ABI, читая Системное программирование Linux .

Определение API:

API определяет интерфейсы, с помощью которых одна часть программного обеспечения взаимодействует с другой на уровне источника.

Определение ABI:

Принимая во внимание, что API определяет интерфейс источника, ABI определяет низкоуровневый двоичный интерфейс между двумя или более частями программного обеспечения в определенной архитектуре.Он определяет, как приложение взаимодействует с самим собой, как приложение взаимодействует с ядром и как приложение взаимодействует с библиотеками.

Как программа может взаимодействовать на уровне источника?Что такое уровень источника?Это связано с исходным кодом в любом случае?Или источник библиотеки включается в основную программу?

Единственное, что я знаю, это то, что API в основном используется программистами, а ABI - компилятором.

Ответы [ 9 ]

278 голосов
/ 24 сентября 2010

API: интерфейс прикладных программ

Это набор открытых типов / переменных / функций, которые вы предоставляете из своего приложения / библиотеки.

В C / C ++ это то, что вы демонстрируете вфайлы заголовков, которые поставляются вместе с приложением.

ABI: двоичный интерфейс приложения

Вот как компилятор создает приложение.
Он определяет вещи (но не ограничивается ими):

  • Как параметры передаются в функции (регистры / стек).
  • Кто очищает параметры из стека (вызывающий / вызываемый).
  • Где возвращаемое значениепомещено для возврата.
  • Как распространяются исключения.
42 голосов
/ 24 сентября 2010

Я чаще всего сталкиваюсь с этими терминами в смысле несовместимых с API изменений или несовместимых с ABI изменений.

Изменение API, по сути, происходит там, где код, который был бы скомпилирован с предыдущей версией, больше не будет работать. Это может произойти, потому что вы добавили аргумент в функцию или изменили имя чего-то доступного за пределами вашего локального кода. Каждый раз, когда вы меняете заголовок и он заставляет вас что-то изменить в файле .c / .cpp, вы вносите изменение API.

Изменение ABI - это то, где код, который уже скомпилирован для версии 1, больше не будет работать с версией 2 кодовой базы (обычно это библиотека). Это обычно сложнее, чем отслеживать изменения, несовместимые с API, поскольку такая простая вещь, как добавление виртуального метода в класс, может быть несовместимой с ABI.

Я нашел два чрезвычайно полезных ресурса для выяснения, что такое совместимость с ABI и как ее сохранить:

42 голосов
/ 24 сентября 2010

API - это то, что используют люди.Мы пишем исходный код.Когда мы пишем программу и хотим использовать некоторую библиотечную функцию, мы пишем код вроде:

 long howManyDecibels = 123L;
 int ok = livenMyHills( howManyDecibels);

, и нам нужно было знать, что существует метод livenMyHills(), который принимает параметр целого числа long.Так что как интерфейс программирования все это выражается в исходном коде.Компилятор превращает это в исполняемые инструкции, которые соответствуют реализации этого языка в данной операционной системе.И в этом случае приводят к некоторым операциям низкого уровня на аудиоустройстве.Таким образом, определенные биты и байты выделяются на некотором оборудовании.Поэтому во время выполнения происходит множество действий на двоичном уровне, которые мы обычно не видим.

19 голосов
/ 24 сентября 2010

это мои непрофессиональные объяснения:

  • api - думаю include файлы.они обеспечивают интерфейс программирования
  • abi - думаю, модуль ядра.когда вы запускаете его на каком-то ядре, они должны договориться о том, как общаться без включаемых файлов, то есть как низкоуровневый двоичный интерфейс
8 голосов
/ 22 февраля 2017

Позвольте мне привести конкретный пример того, как ABI и API отличаются в Java.

Несовместимое изменение ABI, если я изменю метод A # m () с принятия String в качестве аргумента на String... аргументЭто не совместимо с ABI , потому что вам нужно перекомпилировать код, вызывающий этот вызов, но оно совместимо с API , поскольку вы можете разрешить его путем перекомпиляции без каких-либо изменений кода в вызывающей программе.*

Вот пример, изложенный.У меня есть библиотека Java с классом A

// Version 1.0.0
public class A {
    public void m(String string) {
        System.out.println(string);
    }
}

И у меня есть класс, который использует эту библиотеку

public class Main {
    public static void main(String[] args) {
        (new A()).m("string");
    }
}

Теперь автор библиотеки скомпилировал свой класс A, я скомпилировал свой класс Mainи все это работает хорошо.Представьте, что приходит новая версия A

// Version 2.0.0
public class A {
    public void m(String... string) {
        System.out.println(string[0]);
    }
}

Если я просто возьму новый скомпилированный класс A и урону его вместе с ранее скомпилированным классом Main, я получу исключение при попытке вызвать метод

Exception in thread "main" java.lang.NoSuchMethodError: A.m(Ljava/lang/String;)V
        at Main.main(Main.java:5)

Если я перекомпилирую Main, это исправлено, и все снова работает.

7 голосов
/ 05 февраля 2017

( A pplication B inary I nterface) Спецификация для конкретной аппаратной платформы в сочетании с операционной системой.Это один шаг за API ( A pplication P rogram I nterface), который определяет вызовы от приложения к операционной системе.ABI определяет API плюс машинный язык для определенного семейства процессоров.API не гарантирует совместимость во время выполнения, но ABI делает, потому что он определяет машинный язык или формат времени исполнения.

enter image description here

Предоставлено

4 голосов

API-интерфейс совместно используемой библиотеки Linux с примером ABI

Этот ответ был извлечен из моего другого ответа: Что такое двоичный интерфейс приложения (ABI)? , но я чувствовал, что он также отвечает и на этот, и что вопросы не являются дубликатами.

В контексте разделяемых библиотек наиболее важным следствием «наличия стабильного ABI» является то, что вам не нужно перекомпилировать свои программы после изменения библиотеки.

Как мы увидим в примере ниже, можно изменить ABI, нарушая работу программ, даже если API не изменился.

main.c

#include <assert.h>
#include <stdlib.h>

#include "mylib.h"

int main(void) {
    mylib_mystrict *myobject = mylib_init(1);
    assert(myobject->old_field == 1);
    free(myobject);
    return EXIT_SUCCESS;
}

mylib.c

#include <stdlib.h>

#include "mylib.h"

mylib_mystruct* mylib_init(int old_field) {
    mylib_mystruct *myobject;
    myobject = malloc(sizeof(mylib_mystruct));
    myobject->old_field = old_field;
    return myobject;
}

mylib.h

#ifndef MYLIB_H
#define MYLIB_H

typedef struct {
    int old_field;
} mylib_mystruct;

mylib_mystruct* mylib_init(int old_field);

#endif

Компилируется и работает нормально с:

cc='gcc -pedantic-errors -std=c89 -Wall -Wextra'
$cc -fPIC -c -o mylib.o mylib.c
$cc -L . -shared -o libmylib.so mylib.o
$cc -L . -o main.out main.c -lmylib
LD_LIBRARY_PATH=. ./main.out

Теперь предположим, что для v2 библиотеки мы хотим добавить в mylib_mystrict новое поле с именем new_field.

Если мы добавили поле до old_field как в:

typedef struct {
    int new_field;
    int old_field;
} mylib_mystruct;

и перестроить библиотеку, но не main.out, тогда утверждение не выполняется!

Это потому, что строка:

myobject->old_field == 1

сгенерировал сборку, которая пытается получить доступ к самой первой int структуры, которая теперь new_field вместо ожидаемого old_field.

Поэтому это изменение сломало ABI.

Если, однако, мы добавим new_field после old_field:

typedef struct {
    int old_field;
    int new_field;
} mylib_mystruct;

тогда старая сгенерированная сборка все еще обращается к первой int структуры, и программа все еще работает, потому что мы сохранили ABI стабильным.

Вот полностью автоматизированная версия этого примера на GitHub .

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

API против ABI

В предыдущем примере интересно отметить, что добавление new_field перед old_field только нарушило ABI, но не API.

Это означает, что если бы мы перекомпилировали нашу main.c программу с библиотекой, она бы работала независимо от этого.

Однако мы бы также нарушили API, если бы изменили, например, сигнатуру функции:

mylib_mystruct* mylib_init(int old_field, int new_field);

, поскольку в этом случае main.c вообще перестанет компилироваться.

Семантический API против API программирования против ABI

Мы также можем классифицировать изменения API по третьему типу: семантические изменения.

Например, если мы изменили

myobject->old_field = old_field;

до:

myobject->old_field = old_field + 1;

тогда это не сломало бы ни API, ни ABI, но main.c все равно сломалось бы!

Это потому, что мы изменили «человеческое описание» того, что должна делать функция, а не программно заметный аспект.

У меня только что было философское понимание того, что формальная проверка программного обеспечения в некотором смысле перемещает больше "семантического API" в более "программно проверяемый API".

Семантический API против API программирования

Мы также можем классифицировать изменения API по третьему типу: семантические изменения.

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

Поэтому возможно нарушить семантический API, не нарушая саму сборку программы.

Например, если мы изменили

myobject->old_field = old_field;

до:

myobject->old_field = old_field + 1;

тогда это не нарушило бы ни API программирования, ни ABI, но main.c семантический API сломался бы.

Существует два способа программной проверки API контракта:

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

Протестировано в Ubuntu 18.10, GCC 8.2.0.

4 голосов
/ 21 октября 2017

Ваша программа (исходный код) может быть скомпилирована с модулями, которые обеспечивают надлежащий API .

Ваша программа (двоичная) может работать на платформах, которые предоставляют надлежащие ABI .

API ограничивает определения типов, определения функций, макросы, иногда глобальные переменные, которые должна предоставлять библиотека.

ABI ограничивает то, что "платформа" должна предоставлять вашей программе для запуска.Мне нравится рассматривать его на трех уровнях:

  • уровень процессора - набор инструкций, соглашение о вызовах

  • уровень ядра - соглашение о системных вызовахспециальное соглашение о пути к файлу (например, файлы /proc и /sys в Linux) и т. д.

  • уровень ОС - формат объекта, библиотеки времени выполнения и т. д.

Рассмотрим кросс-компилятор с именем arm-linux-gnueabi-gcc.«arm» обозначает архитектуру процессора, «linux» обозначает ядро, «gnu» указывает, что его целевые программы используют libc GNU в качестве библиотеки времени выполнения, в отличие от arm-linux-androideabi-gcc, которые используют реализацию libc в Android.

1 голос
/ 07 июня 2019

enter image description here

Application programming interface (API) - представлен наивысшим уровнем абстракции.Этот API связывает приложения с библиотеками и / или основной ОС.

Application Binary Interface (ABI) охватывает такие факты, как низкоуровневые типы данных и соглашения о вызовах, а также определяет формат для многих программ.В основном, системные вызовы определяются на этом уровне.Более того, интерфейс этого типа обеспечивает переносимость различных приложений и библиотек в разных ОС, в которых используется один и тот же ABI.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...