Różnica między API a ABI

192

Jestem nowy w programowaniu w systemie Linux i natknąłem się na API i ABI podczas czytania Linux System Programming .

Definicja API:

Interfejs API definiuje interfejsy, za pomocą których jedno oprogramowanie komunikuje się z innym na poziomie źródła.

Definicja ABI:

Podczas gdy interfejs API definiuje interfejs źródłowy, interfejs ABI definiuje interfejs binarny niskiego poziomu między dwoma lub więcej programami w określonej architekturze. Definiuje sposób interakcji aplikacji z samym sobą, sposób interakcji aplikacji z jądrem oraz sposób interakcji aplikacji z bibliotekami.

Jak program może komunikować się na poziomie źródłowym? Co to jest poziom źródłowy? Czy w ogóle jest to związane z kodem źródłowym? Czy źródło biblioteki zostaje włączone do programu głównego?

Jedyną różnicą, jaką znam, jest to, że API jest najczęściej używany przez programistów, a ABI jest głównie używany przez kompilator.

Searock
źródło
2
przez poziom źródłowy
rozumieją
1
Zobacz także stackoverflow.com/q/2171177/632951
Pacerier

Odpowiedzi:

49

Interfejs API jest tym, czego używają ludzie. Piszemy kod źródłowy. Kiedy piszemy program i chcemy skorzystać z funkcji biblioteki, piszemy kod taki jak:

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

i musieliśmy wiedzieć, że istnieje metoda livenMyHills(), która wymaga długiego parametru liczby całkowitej. Jako interfejs programistyczny wszystko jest wyrażone w kodzie źródłowym. Kompilator zamienia to w instrukcje wykonywalne, które są zgodne z implementacją tego języka w tym systemie operacyjnym. I w tym przypadku powodują pewne operacje na niskim poziomie na urządzeniu audio. Tak więc niektóre bity i bajty są wyrzucane na jakiś sprzęt. Więc w czasie wykonywania dzieje się wiele akcji na poziomie binarnym, których zwykle nie widzimy.

djna
źródło
309

API: interfejs aplikacji

Jest to zestaw typów publicznych / zmiennych / funkcji udostępnianych przez aplikację / bibliotekę.

W C / C ++ ujawnia się to w plikach nagłówkowych dostarczanych z aplikacją.

ABI: Application Binary Interface

W ten sposób kompilator tworzy aplikację.
Definiuje rzeczy (ale nie ogranicza się do):

  • Jak parametry są przekazywane do funkcji (rejestry / stos).
  • Kto czyści parametry ze stosu (dzwoniący / odbierający).
  • Gdzie zwracana jest wartość zwrotu.
  • Jak propagują się wyjątki.
Martin York
źródło
17
Jest to prawdopodobnie najlepsze zwięzłe wyjaśnienie tego, czym jest ABI, jakie kiedykolwiek widziałem; gj!
TerryP
3
Musisz zdecydować, czy ta odpowiedź jest zwięzła czy szczegółowa. :)
jrok
1
@jrok: Rzeczy mogą być zwięzłe i szczegółowe, nie wykluczają się wzajemnie.
Martin York
@LokiAstari, więc czy ABI nie jest tak naprawdę interfejsem API?
Pacerier,
4
@Pacerier: Oba są interfejsami. Ale są na różnych poziomach abstrakcji. Interfejs API znajduje się na poziomie programisty aplikacji. ABI jest na poziomie kompilatora (gdzieś, gdzie nigdy nie idzie programista aplikacji).
Martin York
47

Najczęściej spotykam się z tymi terminami w znaczeniu zmiany niezgodnej z API lub zmiany niezgodnej z ABI.

Zmiana interfejsu API polega zasadniczo na tym, że kod, który zostałby skompilowany z poprzednią wersją, nie będzie już działał. Może się tak zdarzyć, ponieważ dodałeś argument do funkcji lub zmieniłeś nazwę czegoś dostępnego poza lokalnym kodem. Za każdym razem, gdy zmieniasz nagłówek, który zmusza cię do zmiany czegoś w pliku .c / .cpp, dokonałeś zmiany API.

Zmiana ABI polega na tym, że kod, który został już skompilowany z wersją 1, nie będzie już działał z wersją 2 bazy kodu (zwykle biblioteki). Jest to na ogół trudniejsze do śledzenia niż zmiana niezgodna z API, ponieważ coś tak prostego jak dodanie metody wirtualnej do klasy może być niezgodne z ABI.

Znalazłem dwa niezwykle przydatne zasoby, aby dowiedzieć się, czym jest kompatybilność ABI i jak ją zachować:

Jkerian
źródło
4
+1 za wskazanie ich wzajemnej wyłączności. Na przykład wprowadzenie słowa kluczowego assert przez Javę jest niezgodną z API, ale zgodną z ABI zmianą docs.oracle.com/javase/7/docs/technotes/guides/language/… .
Pacerier
Możesz dodać do sekcji zasobów „3.6. Niezgodne biblioteki” tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html , która zawiera listę przyczyn, które mogą spowodować zmianę ABI.
Demi-Lune
20

Oto moje wyjaśnienia dla laika:

  • API - pomyśl include plikach. Zapewniają interfejsy programowania.
  • ABI - pomyśl o module jądra. Kiedy uruchomisz go na jakimś jądrze, musi uzgodnić sposób komunikacji bez plików dołączanych, tj. Jako binarny interfejs niskiego poziomu.
Anycorn
źródło
13

Przykład współdzielonej biblioteki Linuksa w Linuksie z minimalnym działaniem API vs ABI

Ta odpowiedź została wyodrębniona z mojej drugiej odpowiedzi: Co to jest interfejs binarny aplikacji (ABI)?ale czułem, że bezpośrednio odpowiada również na to pytanie i że pytania nie są duplikatami.

W kontekście bibliotek współdzielonych najważniejszą implikacją „posiadania stabilnego ABI” jest to, że nie trzeba ponownie kompilować programów po zmianach w bibliotece.

Jak zobaczymy w poniższym przykładzie, możliwe jest zmodyfikowanie ABI, łamanie programów, nawet jeśli API pozostaje niezmienione.

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

Kompiluje się i działa poprawnie z:

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

Załóżmy teraz, że dla v2 biblioteki chcemy dodać nowe pole do mylib_mystrictwywołanego new_field.

Jeśli dodaliśmy pole wcześniej old_fieldjak w:

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

i przebudował bibliotekę, ale nie main.out, to nie powiedzie się!

Wynika to z faktu, że wiersz:

myobject->old_field == 1

wygenerował zestaw, który próbuje uzyskać dostęp do pierwszej intstruktury, która jest teraz new_fieldzamiast oczekiwanej old_field.

Dlatego ta zmiana złamała ABI.

Jeśli jednak dodajemy new_fieldpo old_field:

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

wtedy stary wygenerowany zestaw nadal uzyskuje dostęp do pierwszej intstruktury, a program nadal działa, ponieważ utrzymaliśmy stabilność ABI.

Oto w pełni zautomatyzowana wersja tego przykładu na GitHub .

Innym sposobem na utrzymanie stabilnego ABI byłoby traktowanie go mylib_mystructjako nieprzezroczystej struktury i dostęp do jego pól tylko poprzez pomocników metod. Ułatwia to utrzymanie stabilności ABI, ale wiązałoby się to z dodatkowym obciążeniem wydajności, ponieważ wykonujemy więcej wywołań funkcji.

API vs ABI

W poprzednim przykładzie warto zauważyć, że dodanie new_fieldwcześniejszego old_fieldtylko zepsuło ABI, ale nie API.

Oznacza to, że gdybyśmy skompilowali nasz main.cprogram z biblioteką, działałby niezależnie.

Zerwalibyśmy również interfejs API, gdybyśmy zmienili na przykład podpis funkcji:

mylib_mystruct* mylib_init(int old_field, int new_field);

ponieważ w takim przypadku main.cprzestałby się całkowicie kompilować.

Semantyczny interfejs API a programujący interfejs API a ABI

Możemy również klasyfikować zmiany API w trzecim typie: zmiany semantyczne.

Na przykład, gdybyśmy zmodyfikowali

myobject->old_field = old_field;

do:

myobject->old_field = old_field + 1;

to nie zepsułoby ani API ani ABI, ale i tak by się zepsuło main.c!

Jest tak, ponieważ zmieniliśmy „ludzki opis” tego, co funkcja ma robić, a nie aspekt zauważalny programowo.

Właśnie miałem filozoficzny wgląd, że formalna weryfikacja oprogramowania w pewnym sensie przenosi więcej z „semantycznego API” do bardziej „programowo weryfikowalnego API”.

Semantyczny interfejs API a programujący interfejs API

Możemy również klasyfikować zmiany API w trzecim typie: zmiany semantyczne.

Semantyczny interfejs API jest zwykle opisem w języku naturalnym tego, co powinien robić interfejs API, zwykle zawartym w dokumentacji interfejsu API.

Możliwe jest zatem zerwanie semantycznego interfejsu API bez naruszenia samej kompilacji programu.

Na przykład, gdybyśmy zmodyfikowali

myobject->old_field = old_field;

do:

myobject->old_field = old_field + 1;

wtedy nie złamałoby to ani programowania API, ani ABI, ale main.c semantyczny API by się .

Istnieją dwa sposoby programowego sprawdzenia interfejsu API umowy:

  • przetestuj kilka skrzynek narożnych. Łatwe do zrobienia, ale zawsze możesz go przegapić.
  • weryfikacja formalna . Trudniejsze, ale daje matematyczny dowód poprawności, zasadniczo ujednolicając dokumentację i testy w sposób „ludzki” / weryfikowalny przez maszynę! Dopóki oczywiście nie ma błędu w twoim formalnym opisie ;-)

Testowane w Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli
źródło
2
Twoja odpowiedź była wystarczająco szczegółowa, aby pomóc mi zrozumieć różnicę między API a ABI. Dziękuję Ci!
Rakshith Ravi
9

( ASTOSOWANIE B inary I nterface) Opis specyficznych dla platformy sprzętowej połączeniu z systemem operacyjnym. Jest to jeden krok poza API ( TOSOWANIE P ROGRAM I nterface), który określa połączeń z aplikacji do systemu operacyjnego. ABI definiuje API oraz język maszynowy dla konkretnej rodziny procesorów. Interfejs API nie zapewnia zgodności środowiska wykonawczego, ale interfejs ABI tak, ponieważ definiuje język maszyny lub format środowiska wykonawczego.

wprowadź opis zdjęcia tutaj

Kurtuazja

Premraj
źródło
9

Podam konkretny przykład, w jaki sposób ABI i API różnią się w Javie.

Zmiana niekompatybilna z ABI polega na zmianie metody A # m () z przejmowania Stringargumentu na String...argument. Nie jest to zgodne z ABI, ponieważ musisz ponownie skompilować kod, który to wywołuje, ale jest kompatybilny z interfejsem API, ponieważ możesz go rozwiązać poprzez rekompilację bez żadnych zmian w kodzie wywołującym.

Oto opisany przykład. Mam swoją bibliotekę Java z klasą A.

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

I mam klasę, która korzysta z tej biblioteki

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

Teraz autor biblioteki skompilował swoją klasę A, skompilowałem swoją klasę Main i wszystko działa dobrze. Wyobraź sobie, że nadchodzi nowa wersja A.

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

Jeśli po prostu wezmę nową skompilowaną klasę A i upuszczę ją razem z poprzednio skompilowaną klasą Main, otrzymam wyjątek przy próbie wywołania metody

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

Jeśli ponownie skompiluję Main, zostanie to naprawione i wszystko znów będzie działać.

użytkownik7610
źródło
6

Twój program (kod źródłowy) można skompilować z modułami, które zapewniają odpowiedni interfejs API .

Twój program (binarny) może działać na platformach, które zapewniają odpowiedni ABI .

Interfejs API ogranicza definicje typów, definicje funkcji, makra, czasem zmienne globalne, które biblioteka powinna ujawnić.

ABI ogranicza to, co „platforma” powinna zapewniać programowi do działania. Lubię rozważyć to na 3 poziomach:

  • poziom procesora - zestaw instrukcji, konwencja wywoływania

  • poziom jądra - konwencja wywołań systemowych, specjalna konwencja ścieżek plików (np. /proci /syspliki w systemie Linux) itp.

  • Poziom systemu operacyjnego - format obiektu, biblioteki wykonawcze itp.

Zastanów się nad kompilatorem krzyżowym o nazwie arm-linux-gnueabi-gcc. „uzbrojenie” oznacza architekturę procesora, „linux” oznacza jądro, „gnu” oznacza, że ​​jego programy docelowe używają biblioteki GNU libc jako biblioteki wykonawczej, inne niż te, arm-linux-androideabi-gccktóre używają implementacji libc Androida.

ZhouZhuo
źródło
1
jest to bardzo zwięzłe wyjaśnienie różnicy między nimi, w bardzo wyjątkowej perspektywie.
Sajuuk
1

API- Application Programming Interfaceto interfejs czasu kompilacji , który może być wykorzystywany przez programistę do korzystania z funkcji niezwiązanych z projektem, takich jak biblioteka, system operacyjny, podstawowe wywołania w kodzie źródłowym

ABI[About] -Application Binary Interfacejestinterfejsem wykonawczym używanym przez program podczas wykonywania komunikacji między komponentami w kodzie maszynowym

yoAlex5
źródło