Linuxシステムプログラミングは初めてで、Linux System Programmingを読んでいる間にAPIとABIに出会いました。
APIの定義:
APIは、1つのソフトウェアがソースレベルで別のソフトウェアと通信するためのインターフェースを定義します。
ABIの定義:
APIはソースインターフェイスを定義しますが、ABIは特定のアーキテクチャ上の2つ以上のソフトウェア間の低レベルのバイナリインターフェイスを定義します。アプリケーションがそれ自体とどのように相互作用するか、アプリケーションがカーネルとどのように相互作用するか、そしてアプリケーションがライブラリとどのように相互作用するかを定義します。
プログラムはソースレベルでどのように通信できますか?ソースレベルとはとにかくソースコードに関連していますか?または、ライブラリのソースがメインプログラムに含まれますか?
私が知っている唯一の違いは、APIは主にプログラマーによって使用され、ABIは主にコンパイラーによって使用されることです。
APIは人間が使用するものです。ソースコードを書きます。プログラムを作成し、ライブラリ関数を使用する場合、次のようなコードを作成します。
long howManyDecibels = 123L;
int ok = livenMyHills( howManyDecibels);
そして、長整数パラメーターをとるメソッドlivenMyHills()
があることを知る必要がありました。したがって、プログラミングインターフェイスとしては、すべてソースコードで表現されます。コンパイラは、これをこの特定のオペレーティングシステムでのこの言語の実装に適合する実行可能命令に変換します。そして、この場合、Audioユニットで低レベルの操作が行われます。そのため、一部のハードウェアでは特定のビットとバイトが噴出されます。そのため、実行時には、通常は表示されない多くのバイナリレベルのアクションが実行されます。
これは、アプリケーション/ライブラリから公開するパブリック型/変数/関数のセットです。
C/C++では、これはアプリケーションに付属するヘッダーファイルで公開します。
これは、コンパイラがアプリケーションを構築する方法です。
物事を定義します(ただし、これに限定されません):
ほとんどの場合、これらの用語は、APIと互換性のない変更、またはABIと互換性のない変更という意味で使用されています。
APIの変更とは、基本的に、以前のバージョンでコンパイルされたコードが機能しなくなることです。これは、関数に引数を追加したか、ローカルコードの外部からアクセス可能なものの名前を変更したために発生する可能性があります。ヘッダーを変更し、.c/.cppファイル内の何かを強制的に変更するたびに、APIを変更しました。
ABIの変更は、バージョン1に対してすでにコンパイルされたコードがコードベース(通常はライブラリ)のバージョン2で動作しなくなる場所です。仮想メソッドをクラスに追加するのと同じくらい簡単なことはABI互換性がない可能性があるため、これは一般にAPI非互換の変更よりも追跡するのが難しいです。
ABIの互換性とその保持方法を理解するために、2つの非常に役立つリソースを見つけました。
これは私の素人の説明です:
include
ファイルだと思います。プログラミングインターフェイスを提供しますJavaでABIとAPIがどのように異なるかを具体的な例を挙げましょう。
ABIの互換性のない変更は、メソッドA#m()をString...
引数の引数としてString
から取得することから変更した場合です。これはABI互換ではありませんです。これを呼び出しているコードを再コンパイルする必要がありますが、API互換です呼び出し元でコードを変更せずに再コンパイルすることで解決できます。
以下に例を示します。クラスAのJavaライブラリがあります
// 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を再コンパイルすると、これは修正され、すべてが再び機能します。
(ApplicationBinaryInterface)オペレーティングシステムと組み合わせた特定のハードウェアプラットフォームの仕様。 APIの一歩先(ApplicationProgramInterface)、これはアプリケーションからオペレーティングシステムへの呼び出しを定義します。 ABIは、特定のCPUファミリのAPIとマシン言語を定義します。 APIはランタイムの互換性を保証しませんが、ABIはマシン言語、またはランタイム、フォーマットを定義するため、保証します。
プログラム(ソースコード)は、適切なAPIを提供するモジュールを使用してコンパイルできます。
プログラム(バイナリ)は、適切なABIを提供するプラットフォームで実行できます。
APIは、型定義、関数定義、マクロ、ライブラリが公開するグローバル変数を制限します。
ABIは、プログラムを実行するために「プラットフォーム」が提供するものを制限します。私は3つのレベルでそれを検討したいです:
プロセッサレベル-命令セット、呼び出し規約
カーネルレベル-システムコール規則、特別なファイルパス規則(Linuxの/proc
および/sys
ファイルなど)など。
OSレベル-オブジェクト形式、ランタイムライブラリなど.
arm-linux-gnueabi-gcc
という名前のクロスコンパイラを検討してください。 「arm」はプロセッサアーキテクチャ、「linux」はカーネル、「gnu」はAndroidのlibc実装を使用するarm-linux-androideabi-gcc
とは異なり、ターゲットプログラムがランタイムライブラリとしてGNUのlibcを使用することを示します。
Linux共有ライブラリの最小実行可能APIとABIの例
この回答は、他の回答から抽出されました: アプリケーションバイナリインターフェイス(ABI)とは何ですか? しかし、この回答にも直接回答し、質問は重複していないと感じました。
共有ライブラリのコンテキストでは、「安定したABIを持つ」ことの最も重要な意味は、ライブラリの変更後にプログラムを再コンパイルする必要がないことです。
以下の例で見るように、APIが変更されていなくても、ABIを変更してプログラムを破壊する可能性があります。
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が安定しているためです。
このABIを安定させる別の方法は、mylib_mystruct
を opaque struct として扱い、メソッドヘルパーを介してそのフィールドにのみアクセスすることです。これにより、ABIを安定させやすくなりますが、より多くの関数呼び出しを行うとパフォーマンスのオーバーヘッドが発生します。
API vs ABI
前の例では、new_field
の前にold_field
を追加すると、ABIのみが破損し、APIは破損しないことに注意してください。
これが意味するのは、ライブラリに対してmain.c
プログラムを再コンパイルしていた場合、それは関係なく機能するということです。
ただし、たとえば関数のシグネチャを変更した場合は、APIも破損します。
mylib_mystruct* mylib_init(int old_field, int new_field);
その場合、main.c
はコンパイルを完全に停止するためです。
セマンティックAPI vsプログラミングAPI vs ABI
APIの変更は、セマンティックの変更という3番目のタイプに分類することもできます。
たとえば、変更した場合
myobject->old_field = old_field;
に:
myobject->old_field = old_field + 1;
この場合、APIもABIも壊れませんでしたが、main.c
はまだ壊れます。
これは、プログラムで認識できる側面ではなく、関数が実行することになっている「人間の説明」を変更したためです。
ソフトウェアの正式な検証 ある意味で、「セマンティックAPI」をより「プログラム的に検証可能なAPI」に移行させるという哲学的な洞察がありました。
セマンティックAPI vsプログラミングAPI
APIの変更は、セマンティックの変更という3番目のタイプに分類することもできます。
通常、セマンティックAPIは、APIが行うことを想定した自然言語の記述であり、通常はAPIドキュメントに含まれています。
したがって、プログラムビルド自体を壊すことなく、セマンティックAPIを壊すことができます。
たとえば、変更した場合
myobject->old_field = old_field;
に:
myobject->old_field = old_field + 1;
その場合、これはプログラミングAPIもABIも破損しませんが、main.c
セマンティックAPIは破損します。
プログラムでコントラクトAPIをチェックする方法は2つあります。
Ubuntu 18.10、GCC 8.2.0でテスト済み。
Application programming interface (API)
-最高レベルの抽象化によって表されます。このAPIは、アプリケーションをライブラリやコアOSにインターフェースします。
Application Binary Interface (ABI)
は、低レベルのデータ型や呼び出し規約などの事実をカバーし、多くのプログラムのフォーマットも定義します。主に、システムコールはこのレベルで定義されます。さらに、このタイプのインターフェイスにより、同じABIを使用するOS全体でさまざまなアプリケーションやライブラリの移植性が実現します。
続きを読む こちら