API和ABI之间的区别
我是Linux系统编程的新手,在阅读Linux系统编程时遇到了API和ABI。
API的定义:
一个API定义了一个软件在源代码层与另一个软件进行通信的接口。
ABI的定义:
API定义了源接口,而ABI则定义了特定体系结构上两个或更多软件之间的低级二进制接口。 它定义了应用程序如何与自身交互,应用程序如何与内核交互以及应用程序如何与库进行交互。
程序如何在源代码级进行通信? 什么是来源水平? 它与源代码有关吗? 或者图书馆的来源包含在主程序中?
我所知道的唯一区别是API主要由程序员使用,ABI主要由编译器使用。
API是人类使用的。 我们编写源代码。 当我们编写一个程序,并希望使用一些库函数时,我们编写如下代码:
long howManyDecibels = 123L; int ok = livenMyHills( howManyDecibels);
我们需要知道有一个方法livenMyHills()
,它需要一个长整型参数。 所以作为一个编程接口,它全部用源代码表示。 编译器将其转换成符合这个特定操作系统上这种语言实现的可执行指令。 在这种情况下,会在audio单元上产生一些低级别的操作。 所以特定的位和字节在某些硬件上被喷射。 所以在运行时有很多二进制级别的操作,我们通常不会看到。
API:应用程序接口
这是您从应用程序/库中公开的一组公共types/variables/函数。
在C / C ++中,这是您在应用程序附带的头文件中公开的内容。
ABI:应用程序二进制接口
这是编译器如何构build应用程序。
它定义的东西(但不限于):
- 参数如何传递给函数(寄存器/堆栈)。
- 谁从堆栈(调用者/被调用者)清除参数。
- 返还价值的地方。
- exception如何传播。
我主要是遇到这些术语的API不兼容的变化,或ABI不兼容的变化。
一个API的变化实质上就是用以前的版本来编译的代码。 发生这种情况可能是因为您向函数中添加了参数,或者更改了本地代码之外可访问的内容的名称。 每当你改变一个头文件,并且迫使你改变一个.c / .cpp文件中的内容,你就改变了API。
ABI更改是已经针对版本1编译的代码将不再适用于代码库(通常是库)的版本2。 与API不兼容的变化相比,这通常比较棘手,因为像向类中添加虚拟方法那样简单,可能是ABI不兼容的。
我已经find了两个非常有用的资源来确定ABI的兼容性以及如何保留它:
- KDE项目的Do和Dont的C ++列表
- Ulrich Drepper的“如何写共享库”.pdf (glibc的主要作者)
这是我的外行解释:
- API – 认为
include
文件。 他们提供编程接口 - abi – 思考内核模块。 当你在某个内核上运行它时,他们必须同意如何在没有包含文件的情况下进行通信,即作为低级二进制接口
(应用程序接口)与操作系统相结合的特定硬件平台的规范。 这是API(应用程序接口)的一个步骤,它定义了从应用程序到操作系统的调用。 ABI定义了API以及特定CPU系列的机器语言。 一个API不能确保运行时兼容性,但是ABI会这样做,因为它定义了机器语言或运行时格式。
礼貌
让我举一个具体的例子,说明Java中ABI和API的区别。
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: Am(Ljava/lang/String;)V at Main.main(Main.java:5)
如果我重新编译主,这是固定的,所有重新工作。
您的程序(源代码)可以通过提供适当API的模块进行编译。
您的程序(二进制)可以在提供适当ABI的平台上运行。
API限制types定义,函数定义,macros,有时候库应该公开全局variables。
ABI限制了什么“平台”应该提供给你程序运行。 我喜欢从三个层面考虑:
-
处理器级别 – 指令集,调用约定
-
内核级别 – 系统调用约定,特殊文件path约定(例如Linux中的
/proc
和/sys
文件)等 -
操作系统级别 – 对象格式,运行时库等
考虑一个名为arm-linux-gnueabi-gcc
的交叉编译器。 “arm”表示处理器架构,“linux”表示内核,“gnu”表示其目标程序使用GNU的libc作为运行时库,不同于使用Android的libc实现的arm-linux-androideabi-gcc
。