Je suis nouveau dans la programmation système Linux et je suis tombé sur API et ABI en lisant Programmation système Linux.
Définition de l'API:
Une API définit les interfaces par lesquelles un logiciel communique avec un autre au niveau source.
Définition de ABI:
Alors qu'une API définit une interface source, une ABI définit l'interface binaire de bas niveau entre deux logiciels ou plus sur une architecture particulière. Il définit comment une application interagit avec elle-même, comment une application interagit avec le noyau et comment une application interagit avec des bibliothèques.
Comment un programme peut-il communiquer au niveau source? Qu'est-ce qu'un niveau source? Est-ce lié au code source de toute façon? Ou la source de la bibliothèque est-elle incluse dans le programme principal?
La seule différence que je connaisse est que l'API est principalement utilisé par les programmeurs et ABI est principalement utilisé par le compilateur.
L'API est ce que les humains utilisent. Nous écrivons du code source. Lorsque nous écrivons un programme et souhaitons utiliser une fonction de bibliothèque, nous écrivons du code comme:
long howManyDecibels = 123L;
int ok = livenMyHills( howManyDecibels);
et nous avions besoin de savoir qu'il existe une méthode livenMyHills()
, qui prend un paramètre entier long. Donc, en tant qu'interface de programmation, tout est exprimé en code source. Le compilateur transforme cela en instructions exécutables conformes à l'implémentation de ce langage sur ce système d'exploitation particulier. Et dans ce cas, il en résulte des opérations de bas niveau sur une unité audio. Des bits et des octets particuliers sont donc envoyés à certains matériels. Donc, au moment de l'exécution, il y a beaucoup d'actions au niveau binaire que nous ne voyons généralement pas.
C'est l'ensemble des types/variables/fonctions publics que vous exposez à partir de votre application/bibliothèque.
En C/C++, c'est ce que vous exposez dans les fichiers d'en-tête fournis avec l'application.
Voici comment le compilateur construit une application.
Il définit les choses (mais n'est pas limité à):
Je rencontre principalement ces termes dans le sens d'un changement incompatible avec les API, ou d'un changement incompatible avec les ABI.
Une modification de l'API est essentiellement où le code qui aurait été compilé avec la version précédente ne fonctionnera plus. Cela peut être dû au fait que vous avez ajouté un argument à une fonction ou modifié le nom d’un élément accessible en dehors de votre code local. Chaque fois que vous modifiez un en-tête et que cela vous oblige à modifier quelque chose dans un fichier .c/.cpp, vous apportez une modification à l'API.
Une modification ABI est où le code qui a déjà été compilé avec la version 1 ne fonctionnera plus avec la version 2 d'une base de code (généralement une bibliothèque). Cela est généralement plus délicat à garder à l’esprit que les modifications incompatibles avec les API car l’ajout d’une méthode virtuelle à une classe peut être incompatible avec ABI.
J'ai trouvé deux ressources extrêmement utiles pour comprendre ce qu'est la compatibilité ABI et comment la préserver:
ce sont mes explications profanes:
include
fichiers. ils fournissent une interface de programmationPermettez-moi de donner un exemple spécifique en quoi ABI et API diffèrent en Java.
Un changement ABI incompatible est si je modifie une méthode A # m () de prendre un String
comme argument de String...
argument. Ceci est non compatible ABI car vous devez recompiler le code qui appelle cela, mais il est compatible avec l'API car vous pouvez le résoudre en recompilant sans aucune modification de code dans l'appelant.
Voici l'exemple énoncé. J'ai ma Java librairie de classe A
// Version 1.0.0
public class A {
public void m(String string) {
System.out.println(string);
}
}
Et j'ai une classe qui utilise cette bibliothèque
public class Main {
public static void main(String[] args) {
(new A()).m("string");
}
}
Maintenant, l'auteur de la bibliothèque a compilé sa classe A, j'ai compilé ma classe Main et tout fonctionne bien. Imaginez une nouvelle version de A arrive
// Version 2.0.0
public class A {
public void m(String... string) {
System.out.println(string[0]);
}
}
Si je prends simplement la nouvelle classe A compilée et la dépose avec la classe Main précédemment compilée, j'obtiens une exception lors d'une tentative d'invocation de la méthode
Exception in thread "main" Java.lang.NoSuchMethodError: A.m(Ljava/lang/String;)V
at Main.main(Main.Java:5)
Si je recompile Main, tout est corrigé et tout fonctionne à nouveau.
( [~ # ~] a [~ # ~] application [~ # ~] b [~ # ~ ] inary [~ # ~] i [~ # ~] nterface) Spécification combinée d'une plate-forme matérielle spécifique avec le système d'exploitation. C’est une étape au-delà de l’API ( [~ # ~] et une [~ # ~] application [~ # ~] p [~ # ~] rogramme [~ # ~] i [~ # ~] nterface), qui définit les appels de l'application au système d'exploitation. L’ABI définit l’API ainsi que le langage machine pour une famille de processeurs particulière. Une API n'assure pas la compatibilité de l'exécution, contrairement à une ABI, car elle définit le format du langage machine ou de l'exécution.
Votre programme (code source) peut être compilé avec des modules fournissant proprement API .
Votre programme (binaire) peut être exécuté sur des plates-formes qui fournissent des informations correctes ABI .
L'API limite les définitions de type, les définitions de fonction, les macros et parfois les variables globales qu'une bibliothèque doit exposer.
ABI limite ce qu'une "plate-forme" devrait fournir à votre programme pour fonctionner. J'aime le considérer en 3 niveaux:
niveau du processeur - le jeu d'instructions, la convention d'appel
niveau du noyau - la convention d’appel système, la convention de chemin de fichier spéciale (par exemple, la /proc
et /sys
fichiers sous Linux), etc.
Niveau du système d'exploitation - le format de l'objet, les bibliothèques d'exécution, etc.
Considérons un compilateur croisé nommé arm-linux-gnueabi-gcc
. "arm" indique l'architecture du processeur, "linux" le noyau, "gnu" indique que ses programmes cibles utilisent la libc de GNU comme bibliothèque d'exécution, différente de arm-linux-androideabi-gcc
qui utilise l'implémentation libc d'Android.
API exécutable minimale de la bibliothèque partagée Linux par rapport à l'exemple ABI
Cette réponse a été extraite de mon autre réponse: Qu'est-ce qu'une interface binaire d'application (ABI)? mais j'ai eu le sentiment qu'elle répond directement à celle-ci également, et que les questions ne sont pas des doublons.
Dans le contexte des bibliothèques partagées, l'implication la plus importante de "disposer d'une ABI stable" est qu'il n'est pas nécessaire de recompiler vos programmes après les modifications de la bibliothèque.
Comme nous le verrons dans l'exemple ci-dessous, il est possible de modifier l'ABI, en interrompant des programmes, même si l'API est inchangée.
principal 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
Compile et fonctionne bien avec:
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
Supposons maintenant que pour la v2 de la bibliothèque, nous voulions ajouter un nouveau champ à mylib_mystrict
appelé new_field
.
Si nous avons ajouté le champ avant old_field
un péché:
typedef struct {
int new_field;
int old_field;
} mylib_mystruct;
et reconstruit la bibliothèque mais pas main.out
, l'assertion échoue!
C'est parce que la ligne:
myobject->old_field == 1
avait généré Assembly qui essayait d'accéder au tout premier int
de la structure, qui est maintenant new_field
au lieu du attendu old_field
.
Par conséquent, ce changement a brisé le ABI.
Si, toutefois, on ajoute new_field
après old_field
:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
alors, l’ancienne Assemblée générée accède toujours au premier int
de la structure, et le programme fonctionne toujours, car nous avons maintenu l’ABI stable.
Voici un version entièrement automatisée de cet exemple sur GitHub .
Une autre façon de garder cette ABI stable aurait été de traiter mylib_mystruct
en tant que structure opaque , et n’accède à ses champs qu’à travers des assistants de méthode. Cela facilite la stabilité de l’ABI, mais entraîne un surcoût en termes de performances car nous appelons davantage de fonctions.
API vs ABI
Dans l'exemple précédent, il est intéressant de noter que l'ajout du new_field
avant old_field
, seulement cassé l'ABI, mais pas l'API.
Ce que cela signifie, c'est que si nous avions recompilé notre main.c
programme contre la bibliothèque, ça aurait marché quand même.
Cependant, nous aurions également cassé l'API si nous avions changé par exemple la signature de la fonction:
mylib_mystruct* mylib_init(int old_field, int new_field);
puisque dans ce cas, main.c
arrêterait complètement de compiler.
API sémantique vs API de programmation vs ABI
Nous pouvons également classer les modifications d'API dans un troisième type: les modifications sémantiques.
Par exemple, si nous avions modifié
myobject->old_field = old_field;
à:
myobject->old_field = old_field + 1;
alors cela n'aurait cassé ni API ni ABI, mais main.c
serait encore briser!
En effet, nous avons changé la "description humaine" de ce que la fonction est censée faire plutôt qu’un aspect perceptible par le programme.
Je viens tout juste de comprendre de manière philosophique que vérification formelle du logiciel dans un sens déplace davantage "l'API sémantique" vers une "API vérifiable par programme".
API sémantique vs API de programmation
Nous pouvons également classer les modifications d'API dans un troisième type: les modifications sémantiques.
L'API sémantique est généralement une description en langage naturel de ce que l'API est censée faire, généralement incluse dans la documentation de l'API.
Il est donc possible de casser l'API sémantique sans casser le programme lui-même.
Par exemple, si nous avions modifié
myobject->old_field = old_field;
à:
myobject->old_field = old_field + 1;
alors cela n'aurait cassé ni l'API de programmation, ni l'ABI, mais main.c
l'API sémantique se briserait.
Il existe deux manières de vérifier par programme l'API du contrat:
Testé sous Ubuntu 18.10, GCC 8.2.0.
Application programming interface (API)
- est représenté par le plus haut niveau d'abstraction. Cette API relie les applications aux bibliothèques et/ou au système d'exploitation principal.
Application Binary Interface (ABI)
couvre des faits tels que les types de données de bas niveau et les conventions d'appel et définit également un format pour de nombreux programmes. Les appels système sont principalement définis à ce niveau. De plus, ce type d'interface permet la portabilité de diverses applications et bibliothèques d'un système d'exploitation utilisant le même ABI.
Lire la suite ici