我是linux系统编程的新手,在阅读Linux系统编程时遇到了API和ABI .
API的定义:
API定义了一个软件在源级别与另一个软件通信的接口.
ABI的定义:
API定义源接口,而ABI定义特定体系结构上两个或多个软件之间的低级二进制接口.它定义了应用程序如何与自身交互,应用程序如何与内核交互以及应用程序如何与库交互.
程序如何在源级别进行通信?什么是源级别?它无论如何都与源代码有关?或者库的源代码包含在主程序中?
我所知道的唯一区别是API主要由程序员使用,而ABI主要由编译器使用.
Mar*_*ork 290
这是您从应用程序/库中公开的一组公共类型/变量/函数.
在C/C++中,这是您在应用程序附带的头文件中公开的内容.
这是编译器构建应用程序的方式.
它定义的东西(但不限于):
djn*_*jna 43
API是人类使用的.我们写源代码.当我们编写程序并想要使用某些库函数时,我们编写如下代码:
long howManyDecibels = 123L;
int ok = livenMyHills( howManyDecibels);
Run Code Online (Sandbox Code Playgroud)
我们需要知道有一个方法livenMyHills(),它采用一个长整数参数.所以作为一个编程接口,它都用源代码表示.编译器将其转换为可执行指令,这些指令符合此特定操作系统上此语言的实现.在这种情况下,会导致音频单元上的某些低级操作.因此,在某些硬件上会喷射特定的位和字节.所以在运行时,我们通常不会看到很多二进制级别的操作.
jke*_*ian 43
我主要是在API不兼容的更改或ABI不兼容的更改的意义上遇到这些术语.
API更改本质上是使用以前版本编译的代码将不再起作用的地方.这可能是因为您向函数添加了参数,或者更改了本地代码之外可访问的名称.每次更改标题时,它都会强制您更改.c/.cpp文件中的某些内容,您已经进行了API更改.
ABI更改是已针对版本1编译的代码将不再适用于代码库的版本2(通常是库).跟踪API不兼容的更改通常比较难以跟踪,因为向类添加虚方法这样简单的操作可能与ABI不兼容.
我发现了两个非常有用的资源,用于确定ABI的兼容性以及如何保留它:
Any*_*orn 20
这是我的外行解释:
include文件.他们提供编程接口让我举一个具体的例子,说明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);
}
}
Run Code Online (Sandbox Code Playgroud)
我有一个使用这个库的类
public class Main {
public static void main(String[] args) {
(new A()).m("string");
}
}
Run Code Online (Sandbox Code Playgroud)
现在,图书馆作者编写了他们的A类,我编写了我的类Main,它们都运行良好.想象一下A的新版本
// Version 2.0.0
public class A {
public void m(String... string) {
System.out.println(string[0]);
}
}
Run Code Online (Sandbox Code Playgroud)
如果我只使用新编译的类A并将其与先前编译的类Main一起删除,则在尝试调用该方法时会出现异常
Exception in thread "main" java.lang.NoSuchMethodError: A.m(Ljava/lang/String;)V
at Main.main(Main.java:5)
Run Code Online (Sandbox Code Playgroud)
如果我重新编译Main,这是固定的,一切都在重新运行.
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;
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
mylib.h
#ifndef MYLIB_H
#define MYLIB_H
typedef struct {
int old_field;
} mylib_mystruct;
mylib_mystruct* mylib_init(int old_field);
#endif
Run Code Online (Sandbox Code Playgroud)
可以使用以下命令进行编译和正常运行:
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
Run Code Online (Sandbox Code Playgroud)
现在,假设对于库的v2,我们要向mylib_mystrict调用添加一个新字段new_field。
如果我们old_field像之前那样添加字段:
typedef struct {
int new_field;
int old_field;
} mylib_mystruct;
Run Code Online (Sandbox Code Playgroud)
并重建了库,但没有重建main.out,则断言失败!
这是因为行:
myobject->old_field == 1
Run Code Online (Sandbox Code Playgroud)
已生成了尝试访问int结构的第一个结构的程序集,该结构现在已new_field不是预期的old_field。
因此,此更改中断了ABI。
但是,如果new_field在old_field以下位置添加:
typedef struct {
int old_field;
int new_field;
} mylib_mystruct;
Run Code Online (Sandbox Code Playgroud)
然后int,由于我们保持了ABI的稳定,因此旧生成的程序集仍然会访问结构的第一个结构,并且程序仍然可以正常工作。
保持此ABI稳定的另一种方法是将其mylib_mystruct视为不透明的结构,并且只能通过方法助手来访问其字段。这样可以更轻松地保持ABI稳定,但是会增加性能开销,因为我们需要进行更多的函数调用。
API与ABI
在前面的示例中,有趣的是,添加new_fieldbefore old_field只会破坏ABI,而不会破坏API。
这意味着,如果我们main.c针对库重新编译程序,则无论如何它都可以工作。
但是,如果我们更改了例如函数签名,我们也会破坏API。
mylib_mystruct* mylib_init(int old_field, int new_field);
Run Code Online (Sandbox Code Playgroud)
因为在那种情况下,main.c将完全停止编译。
语义API与编程API与ABI
我们还可以将API更改分类为第三种类型:语义更改。
例如,如果我们修改了
myobject->old_field = old_field;
Run Code Online (Sandbox Code Playgroud)
至:
myobject->old_field = old_field + 1;
Run Code Online (Sandbox Code Playgroud)
那么这将不会破坏API或ABI,但main.c仍然会破坏!
这是因为我们更改了该功能应该执行的“人工描述”,而不是程序上引人注目的方面。
我只是有一种哲学上的见解,即某种意义上的软件形式验证将更多的“语义API”转移到了一个“可程序验证的API”中。
语义API与编程API
我们还可以将API更改分类为第三种类型:语义更改。
语义API通常是API应该执行的自然语言描述,通常包含在API文档中。
因此,可以在不破坏程序构建本身的情况下破坏语义API。
例如,如果我们修改了
myobject->old_field = old_field;
Run Code Online (Sandbox Code Playgroud)
至:
myobject->old_field = old_field + 1;
Run Code Online (Sandbox Code Playgroud)
那么这既不会破坏编程API,也main.c不会破坏ABI,但是语义API会破坏。
有两种方法可以以编程方式检查合同API:
在Ubuntu 18.10,GCC 8.2.0中进行了测试。
小智 6
可以使用提供适当API的模块来编译您的程序(源代码)。
您的程序(二进制)可以在提供适当ABI的平台上运行。
API限制类型定义,函数定义,宏,有时还会限制库应公开的全局变量。
ABI限制了应为您的程序提供“平台”的运行条件。我喜欢从三个层面来考虑:
处理器级别-指令集,调用约定
内核级别-系统调用约定,特殊文件路径约定(例如Linux中的/proc和/sys文件)等。
操作系统级别-对象格式,运行时库等。
考虑一个名为的交叉编译器arm-linux-gnueabi-gcc。“ arm”表示处理器体系结构,“ linux”表示内核,“ gnu”表示其目标程序使用GNU的libc作为运行时库,与arm-linux-androideabi-gcc使用Android的libc实现不同。
| 归档时间: |
|
| 查看次数: |
53810 次 |
| 最近记录: |