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

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

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

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

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

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

ABI: Application Binary Interface

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

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

Ответ 3

В основном я сталкиваюсь с этими терминами в смысле изменений, несовместимых с API, или изменений, несовместимых с ABI.

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

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

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

Ответ 4

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

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

Ответ 5

РИМЕНЕНИЕ В Инарах я nterface) Спецификации для конкретной аппаратной платформы в сочетании с операционной системой. Это один шаг за пределы API (A РИМЕНЕНИЕ P rogram я nterface), который определяет вызовы от приложения к операционной системе. ABI определяет API плюс машинный язык для определенного семейства процессоров. API не гарантирует совместимость во время выполнения, но ABI делает, потому что он определяет машинный язык или формат времени исполнения.

enter image description here

учтивость

Ответ 6

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

Несовместимое изменение ABI заключается в том, что я изменяю метод A # m() от принятия a 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

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 контракта:

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

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

Ответ 8

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

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

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

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

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

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

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

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

Ответ 9

enter image description here

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

API гарантирует, что если программа удовлетворяет API, это source compatible. Это означает, что потребитель API будет успешно скомпилирован.

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

ABI предоставляет binary compatibility - он гарантирует, что программа будет работать на любой системе с тем же ABI без перекомпиляции.

Подробнее здесь