244

我是 Linux 系统编程的新手,在阅读 Linux 系统编程时遇到了 API 和 ABI 。

API定义:

API 定义了一个软件在源代码级别与另一个软件通信的接口。

ABI的定义:

API 定义了源接口,而 ABI 定义了特定架构上两个或多个软件之间的低级二进制接口。它定义了应用程序如何与自身交互、应用程序如何与内核交互以及应用程序如何与库交互。

程序如何在源代码级别进行通信?什么是源级别?它与源代码有任何关系吗?还是库的源代码包含在主程序中?

我知道的唯一区别是 API 主要由程序员使用,而 ABI 主要由编译器使用。

4

11 回答 11

367

API:应用程序接口

这是您从应用程序/库中公开的一组公共类型/变量/函数。

在 C/C++ 中,这是您在应用程序随附的头文件中公开的内容。

ABI:应用程序二进制接口

这就是编译器构建应用程序的方式。
它定义了一些东西(但不限于):

  • 参数如何传递给函数(寄存器/堆栈)。
  • 谁从堆栈中清除参数(调用者/被调用者)。
  • 返回值放置在哪里返回。
  • 异常如何传播。
于 2010-09-24T06:25:42.753 回答
60

API是人类使用的。我们编写源代码。当我们编写程序并想要使用一些库函数时,我们编写如下代码:

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

我们需要知道有一个方法livenMyHills(),它接受一个长整数参数。因此,作为一个编程接口,它全部用源代码表示。编译器将其转换为符合该语言在该特定操作系统上的实现的可执行指令。在这种情况下,会导致音频单元上的一些低级操作。因此,某些硬件会喷射特定的位和字节。所以在运行时有很多我们通常看不到的二进制级别的动作。

于 2010-09-24T05:29:19.657 回答
54

我主要是在 API 不兼容的更改或 ABI 不兼容的更改的意义上遇到这些术语。

API 更改本质上是在以前版本编译的代码将不再工作的地方。这可能是因为您向函数添加了参数,或者更改了本地代码之外可访问的名称。每当您更改标头并强制您更改 .c/.cpp 文件中的某些内容时,您就进行了 API 更改。

ABI 更改是已经针对版本 1 编译的代码将不再适用于代码库的版本 2(通常是库)。这通常比 API 不兼容的更改更难跟踪,因为像向类添加虚拟方法这样简单的事情可能与 ABI 不兼容。

我发现了两个非常有用的资源,可用于弄清楚 ABI 兼容性是什么以及如何保留它:

于 2010-09-24T06:32:05.750 回答
29

Linux 共享库最小可运行 API 与 ABI 示例

这个答案是从我的另一个答案中提取的:什么是应用程序二进制接口(ABI)?但我觉得它也直接回答了这个问题,而且问题不是重复的。

在共享库的上下文中,“拥有稳定的 ABI”最重要的含义是您不需要在库更改后重新编译程序。

正如我们将在下面的示例中看到的,即使 API 未更改,也可以修改 ABI,破坏程序。

主程序

#include <assert.h>
#include <stdlib.h>

#include "mylib.h"

int main(void) {
    mylib_mystruct *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_mystruct被调用添加一个新字段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_fieldbeforeold_field只会破坏 ABI,但不会破坏 API。

这意味着,如果我们main.c针对库重新编译我们的程序,无论如何它都会工作。

但是,如果我们更改了函数签名,我们也会破坏 API:

mylib_mystruct* mylib_init(int old_field, int new_field);

因为在这种情况下,main.c将完全停止编译。

语义 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 中测试。

于 2019-03-03T15:34:52.527 回答
26

这是我的外行解释:

  • API - 想想include文件。它们提供编程接口。
  • ABI - 想想内核模块。当您在某个内核上运行它时,它必须就如何在没有包含文件的情况下进行通信达成一致,即作为低级二进制接口。
于 2010-09-24T05:28:43.307 回答
10

应用程序二进制接口)与操作系统相结合的特定硬件平台的规范。它比 API(应用程序接口)更进一步它定义了从应用程序到操作系统的调用。ABI 定义了 API 以及特定 CPU 系列的机器语言。API 不能确保运行时兼容性,但 ABI 可以,因为它定义了机器语言或运行时格式。

在此处输入图像描述

礼貌

于 2017-02-05T02:57:43.063 回答
9

让我举一个具体的例子,A​​BI 和 API 在 Java 中有何不同。

ABI 不兼容的更改是如果我将方法 A#m() 从将 aString作为参数更改为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,这是固定的,一切都可以再次工作。

于 2017-02-22T16:08:01.723 回答
8

您的程序(源代码)可以使用提供适当API的模块进行编译。

您的程序(二进制)可以在提供适当ABI的平台上运行。

API 限制类型定义、函数定义、宏,有时是库应该公开的全局变量。

ABI 限制了“平台”应该为您的程序运行提供的内容。我喜欢从 3 个层面来考虑它:

  • 处理器级别 - 指令集,调用约定

  • 内核级别 - 系统调用约定、特殊文件路径约定(例如 Linux 中的/proc/sys文件)等。

  • 操作系统级别 - 对象格式、运行时库等。

考虑一个名为arm-linux-gnueabi-gcc. “arm”表示处理器架构,“linux”表示内核,“gnu”表示其目标程序使用GNU的libc作为运行时库,与arm-linux-androideabi-gccAndroid的libc实现不同。

于 2017-10-21T07:01:32.837 回答
2

API-Application Programming Interface是一个编译时接口,开发人员可以使用它来使用非项目功能,如库、操作系统、源代码中的核心调用

ABI[关于] -Application Binary Interface是一个运行时接口,程序在执行期间用于机器代码中组件之间的通信

于 2019-12-10T15:33:21.967 回答
1

ABI 指的是目标文件/库和最终二进制文件的布局,从成功链接、加载和执行某些二进制文件的角度来看,不会因二进制不兼容而发生链接错误或逻辑错误。

  • 二进制格式规范(PE、COFF、ELF、.obj、.o、.a、.lib(导入库、静态库)、.NET 程序集、.pyc、COM .dll):标头、标头格式、定义这些部分在哪里,导入/导出/异常表在哪里以及这些表的格式
  • 用于对代码段中的字节进行编码的指令集,以及具体的机器指令
  • API 中定义的函数和数据的实际签名(以及它们在二进制文件中的表示方式(接下来的 2 点))
  • 代码部分中函数的调用约定,可能被其他二进制文件调用(与 ABI 兼容性特别相关的是实际导出的函数)
  • 数据在数据部分中相对于其类型的表示和对齐方式(与 ABI 兼容性特别相关,即实际导出的数据)
  • 挂在代码中的系统调用号或中断向量
  • 导出函数和数据的名称修饰
  • 目标文件中的链接器指令
  • API 程序员使用的预处理器/编译器/汇编器/链接器标志和指令,以及如何解释它们以省略、优化、内联或更改库或最终二进制文件中某些符号或代码的链接(无论是二进制文件 a .dll 还是在静态链接的情况下可执行)

.NET C# 的字节码格式是 ABI(通用),其中包括 .NET 程序集 .dll 格式。解释字节码的虚拟机具有基于 C++ 的特定 ABI,其中类型需要在本机代码的特定 ABI 使用的本机 C++ 类型与从本机代码和本机调用字节码时虚拟机 ABI 的盒装类型之间编组来自字节码的代码。在这里,我将特定程序的 ABI 称为特定 ABI,而一般的 ABI,例如“MS ABI”或“C ABI”只是指调用约定和结构的组织方式,而不是特定的体现特定二进制文件的 ABI 引入了新级别的 ABI 兼容性问题。

API 是指由特定库导出的一组类型定义,该库在特定翻译单元中导入和使用,从翻译单元的编译器的角度来看,以成功解析和检查类型引用以能够编译二进制文件,以及该二进制文件将遵循目标 ABI 的标准,这样如果实际实现 API 的库也被编译为兼容的 ABI,它将按预期链接和工作。如果 API 更新,应用程序仍然可以编译,但现在会出现二进制不兼容,因此需要使用新的二进制。

API 涉及:

  • 函数、变量、类、对象、常量、它们的名称、类型和定义,它们以语法和语义正确的方式编码的语言呈现
  • 这些函数实际上做了什么以及如何在源语言中使用它们
  • 需要包含的源代码文件/需要链接到的二进制文件才能使用它们,以及它们的 ABI 兼容性
于 2021-02-18T19:50:50.387 回答
0

我将首先回答您的具体问题。

1.什么是源级?它与源代码有任何关系吗?

是的,术语源级别是指源代码级别。术语级别是指计算需求的语义级别,因为它们从应用程序域级别转换为源代码级别,从源代码级别转换为机器代码级别(二进制代码)。应用程序域级别指的是软件的最终用户想要什么,并指定为他们的计算要求。源代码级别是指程序员根据应用程序级别的要求,然后指定为某种语言的程序。

  1. 程序如何在源代码级别进行通信?还是库的源代码包含在主程序中?

语言 API 专门指一种语言需要(指定)(因此是接口)以该语言编写可重用模块的所有内容。可重用程序符合这些接口 (API) 要求,以便在相同语言的其他程序中重用。每次重用也需要符合相同的 API 要求。所以,“沟通”这个词指的是重用。

是的,源代码(可重用模块的;在 C/C++ 的情况下,.h 文件)被包含(在预处理阶段复制)是C/C++ 中重用的常用方式,因此是 C++ API 的一部分。即使您只是在 C++ 程序的全局空间中编写一个简单的函数 foo() 然后调用该函数,foo();因为根据 C++ 语言 API 可以重用任意次数。Java 包中的 Java 类是 Java 中的可重用模块。Java bean 规范也是一个 Java API,使可重用程序(bean)能够在运行时/容器(符合该规范)的帮助下被其他模块(可能是另一个 bean)重用。

关于语言 API 和 ABI 之间区别的总体问题,以及面向服务的 API 与语言 API 的比较,我在 SO 上的回答应该会有所帮助。

于 2022-02-24T07:27:10.617 回答