Різниця між API та ABI


194

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

Визначення API:

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

Визначення ABI:

Тоді як API визначає інтерфейс джерела, ABI визначає бінарний інтерфейс низького рівня між двома або більше частинами програмного забезпечення для певної архітектури. Він визначає, як програма взаємодіє з самим собою, як програма взаємодіє з ядром, і як програма взаємодіє з бібліотеками.

Як програма може спілкуватися на рівні джерела? Що таке рівень джерела? Чи пов’язано це взагалі з вихідним кодом? Або джерело бібліотеки потрапляє до основної програми?

Єдине, що я знаю, це те, що API в основному використовується програмістами, а ABI в основному використовується компілятором.


2
за рівнем джерела вони означають щось на зразок включати файл для викриття визначень функцій
Anycorn

Відповіді:


49

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

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

і нам потрібно було знати, що існує метод livenMyHills(), який займає довгий цілий параметр. Отже, як інтерфейс програмування все виражається у вихідному коді. Компілятор перетворює це на виконувані інструкції, які відповідають застосуванню цієї мови в цій конкретній операційній системі. І в цьому випадку виникла деяка операція низького рівня на аудіоапараті. Тож окремі біти та байти обприскані на деякий апарат. Тож під час виконання відбувається безліч дій бінарного рівня, які ми зазвичай не бачимо.


310

API: Інтерфейс програми програми

Це набір загальнодоступних типів / змінних / функцій, які ви експонуєте зі свого додатка / бібліотеки.

У C / C ++ це викриття у файлах заголовків, які ви постачаєте разом із програмою.

ABI: Бінарний інтерфейс програми

Ось так компілятор будує додаток.
Він визначає речі (але не обмежується цим):

  • Як параметри передаються функціям (регістри / стеки).
  • Хто очищає параметри зі стека (calller / callee).
  • Там, де повертається значення для повернення.
  • Як розповсюджуються винятки.

17
Це, мабуть, найкраще стисле пояснення того, що таке ІПС, що я коли-небудь бачив; гж!
TerryP

3
Ви, хлопці, повинні вирішити, чи є ця відповідь стислою чи детальною. :)
jrok

1
@jrok: Речі можуть бути стислими та деталізованими, вони не є взаємовиключними.
Мартін Йорк

@LokiAstari, Тож чи ABI насправді також не є API?
Pacerier

4
@Pacerier: Вони обидва інтерфейси. Але вони знаходяться на різних рівнях абстракції. API знаходиться на рівні розробника додатків. ABI знаходиться на рівні компілятора (десь розробник додатків ніколи не йде).
Мартін Йорк

47

В основному я стикаюся з цими термінами в сенсі змін, несумісних з API, або змін, несумісних з ABI.

Зміна API - це по суті, коли код, який було б складено з попередньою версією, більше не працюватиме. Це може статися тому, що ви додали аргумент до функції або змінили назву чогось, доступного поза місцевим кодом. Щоразу, коли ви змінюєте заголовок, і це змушує вас щось змінити у .c / .cpp-файлі, ви внесли зміни в API.

Зміна ABI полягає в тому, що код, який вже був складений проти версії 1, більше не працюватиме з версією 2 кодової бази (зазвичай це бібліотека). Це, як правило, складніше відслідковувати зміни, несумісні з API, оскільки щось таке просте, як додавання віртуального методу до класу, може бути не сумісним ABI.

Я знайшов два надзвичайно корисні ресурси для з'ясування того, що таке сумісність з ABI та як його зберегти:


4
+1 за вказівку на їх взаємну винятковість. Наприклад, введення Java ключового слова assrt - це не сумісна з API, але сумісна з ABI зміна docs.oracle.com/javase/7/docs/technotes/guides/language/… .
Pacerier

Ви можете додати до розділу ресурсів "3.6. Несумісні бібліотеки" tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html , де перераховано, що може спричинити зміни ABI.
Демі-Луна

20

Це мої мирянські пояснення:

  • API - придумайте includeфайли. Вони забезпечують інтерфейси програмування.
  • ABI - придумайте модуль ядра. Коли ви запускаєте його на якомусь ядрі, він повинен узгодити, як спілкуватися без включення файлів, тобто як бінарний інтерфейс низького рівня.

13

Приклад 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_mystrictcall 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 .

Іншим способом збереження цього АБІ було б ставитися 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взагалі перестане складати.

Semantic API vs 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.


2
Ваша відповідь була досить детальною, щоб допомогти мені зрозуміти різницю між API та ABI. Дякую!
Ракшит Раві

9

( Рименение В Інарі я nterface) специфікацію для конкретної апаратної платформи в поєднанні з операційною системою. Це один крок за межі API ( рименение Р rogram Я nterface), який визначає виклики від додатка до операційної системи. ABI визначає API плюс машинну мову для певного сімейства процесорів. API не забезпечує сумісність із виконанням часу, але ABI це робить, оскільки він визначає машинну мову або формат виконання.

введіть тут опис зображення

Люб'язно


9

Дозвольте навести конкретний приклад того, як 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, це виправлено, і все працює знову.


6

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

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

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

ABI обмежує, що "платформа" повинна надавати програмі для запуску програми. Мені подобається розглянути це в 3 рівні:

  • рівень процесора - набір інструкцій, умова виклику

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

  • Рівень ОС - формат об'єкта, бібліотеки часу виконання тощо.

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


1
це дуже коротке пояснення різниці між ними, і в дуже унікальній перспективі.
Саджук

1

API- Application Programming Interfaceце інтерфейс часу компіляції, який розробник може використовувати для використання непроектних функцій, таких як бібліотека, ОС, основні дзвінки у вихідному коді

ABI[About] -Application Binary Interfaceцеінтерфейс виконання, який використовується програмою під час виконання для зв'язку між компонентами в машинному коді

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.