Aros/开发者/ABIv1
ABI v1 主要涉及结构对齐、LVO 和 C 库,因为当前的 AROS 在库向量中存在一些不寻常的不兼容性,如果不破坏二进制兼容性,则无法修复。大多数情况下,应该自动打开库,但仍然需要讨论如何创建和使用插件。人们需要了解 OpenLibrary(),但不是为了使用普通的/标准的共享库。
至于让新的开发者感到困惑……恕我直言,我们应该提供一些入门指南,解释可以做什么以及在什么情况下使用什么方法更合适。一些带有手动打开库的代码,以及对这样做的原因进行适当的解释,可以很好地说明幕后发生了什么,并成为学习辅助工具,而不是令人困惑的东西。作为我自己的解释,您可以看看新的 libnet.a。现在我们可以自动打开 bsdsocket.library 和 miami.library。这可能会极大地帮助移植。
随着 arosc.library 分割工作的进展,我们应该在发布 ABIV1 之前完成这项工作。
- C library typedefs screening - libbase passing to C library functions - How to extend OS3.x shared libraries - struct ETask - Varargs handling - dos.library BPTR - What is part of ABIV1, what is not - Autodocs screening + update (reference manual) - screening m68k bugs and specifics
在 ABI V1 中,支持通过在当前 relbase 的偏移量处查找 libbase 来调用库函数(i386 上为 %ebx,m68k 上为 A4 或 A6,列表中仍然需要讨论哪一个)。这将允许创建纯代码和可 ROM 化代码(例如,没有 .bss 部分),而无需这些难看的 #define 技巧。希望这可以配置,例如 m68k 应用程序应该使用 A4,但 m68k 库应该使用 A6。希望程序和库使用相同的寄存器。当前在 ABI V1 中,每个库都会生成一个额外的 libmodname_rel.a 链接库,该库使用此偏移量调用函数。它在 ABI V1 中使用,以便每个打开库都可以使用 C 库,并且每个 libbase 也都打开了 C 库(请记住,librom.a 已消失)。如果库和程序中的寄存器不同,则可能需要提供另一个额外的链接库;每个寄存器一个。我希望避免这种情况。
关于 C 调用约定的另一个问题:第一个参数在哪里,最后一个参数在哪里。函数参数通常通过哪个寄存器访问?
第一个参数在堆栈中最低(在 *(ULONG *)(A7 + 4) 处,*(ULONG *)A7 是返回地址)。下一个在 *(ULONG *)(A7 + 8) 处,下一个在 *(ULONG *)(A7 + 12) 处,依此类推。
函数参数通过 A7 (sp) 或 A5 (fp),或 sp 或 fp 的任何其他别名进行访问。你无法真正预测它 - 优化器可以对重新排序进行各种操作。
a) 在 x86 上,%ebx 寄存器保留给系统使用,因此所有内容都使用 -ffixed-ebx 编译,对于 gcc 交叉编译器,则使用适当的补丁进行编译。
b) 进入库的 C 函数作为普通的 C 函数编译。这样,C 代码就可以是原始的 C 代码,没有任何样板代码;这样做的原因是
- 最大程度地减少移植外部代码所需的工作,通常只需要一个合适的 .conf 文件来告诉库哪些函数需要从库中导出。
- 它允许在标准 ISO C 包含文件中定义标准 ISO C 库函数,这些函数可以正确地分离,而不会造成命名空间污染(例如 stdio.h、string.h 等)。
- 我在 C 库补丁中所做的另一件事。一些(旧版)代码甚至在其代码中仅包含函数原型,而没有包含正确的包含文件。恕我直言,我们必须支持这种情况,这意味着我们可能无法假设任何属性可以添加到函数原型中。
c) 函数地址通过将它们列在库的 .conf 文件中放入库的 LVO 表中。在库中的任何点,无论调用链有多深,都可以使用 AROS_GET_LIBBASE 宏访问库基地址(实际上只是检索 %ebx 指向的地址处的数值)。
d) 对于库中的每个函数,在 libfoo.a 静态链接库中都会生成一个相应的存根函数。在 i386 上,此函数唯一执行的操作是设置 libbase 并跳转到正确的地址。为了能够在我退出调用的库函数时恢复以前的 libbase,我实现了 %ebx 作为第二个堆栈指针。这样,我可以在设置新的 libbase 之前将当前的 libbase 推送到此堆栈上。函数返回后,可以弹出堆栈以返回到调用之后的堆栈。(我实现了这个堆栈以与正常堆栈相反的方向增长,对于新的任务,它被初始化为堆栈的末尾;这意味着在开始时 SP 指向堆栈的顶部,%ebx 指向底部,并且两个指针都开始彼此增长)。
查看如何在其他 CPU 上实现它。对于大多数 CPU,我们可能可以复制 %ebx 堆栈技巧以获取 libbase 指针。不幸的是,这对于 m68k 来说是不可能的。此 ABI 要求 i386 在所有情况下 %ebx 都包含一个有效的堆栈指针,可以在其中推送数据。在 m68k 上,这无法保证,因为 A6 在普通程序中可能为任何值。
所以我想实现的是在上面的 d) 中设置 A6,但我需要一个地方来存储当前的 libbase。这再次提出了一些我最初为 i386 玩的一些东西
- 在 ETask 结构中存储堆栈指针。这里的问题是并非每个任务都保证拥有 ETask。因此,这里要解决的问题是能够保证每个任务都有一个 ETask,或者假设没有 ETask 的任务永远不会使用 C 参数传递调用共享库函数。(这实际上何时发生)这也违背了我树中的其他补丁,在这些补丁中我实际上试图摆脱 ETask。这也会在存根函数中增加一些内存访问,例如在每次共享库函数调用期间(SysBase->ThisTask->ETask->StackPointer)。
- 实现一个编译器选项,该选项将以这样的方式编译代码:函数调用者同时设置 SP 和 FP。函数参数将始终通过 FP 访问。然后,在我的存根函数中,我可以将 A6 放入堆栈中,将其设置为 libbase 并调用例程。这里的问题是,想要链接到共享库的所有静态链接库也必须使用此选项进行编译(例如 libgcc.a 等)。我不知道在 gcc 中实现这样的事情需要多少工作量
需要。
- 甚至使用稍微更 hacky/更巧妙的技巧。实现一个编译器选项,该选项始终强制将帧指针设置为进入函数的第一件事,然后从那时起通过帧指针访问参数,例如在伪代码中
function: A6 -> A5 function_FP: ...
在存根代码中,然后可以再次设置帧指针,推送并设置 A6 并跳转到 function_FP(实际上此地址将存储在 LVO 中,并且可以根据“function”的地址计算)。我认为这是侵入性最小的补丁,因为所有包含库导出的函数的文件(例如 LVO 表的一部分)都使用该选项进行编译是可以的。同样,我不知道这需要多少工作量。
- 我考虑了其他选项,例如将所有函数参数在堆栈上移动一个位置,然后在空闲空间中存储 libbase,或者将 libbase 推送到堆栈上,然后重新推送函数参数,但我发现这在计算周期方面开销过大,对于后者来说,在堆栈空间方面开销过大。
以下是主要补丁的简要概述,这些补丁也将作为单独的提交进行。
- 在 arossupport(altstack)中实现替代堆栈。这基本上使用堆栈末尾作为替代堆栈,可以在程序中使用,而不会干扰程序的堆栈、编译器内部或函数参数传递。因此,它也可以在存根函数中使用,而无需标准堆栈操作。
- 支持 setjmp/longjmp 在执行 longjmp 时记住替代堆栈状态。
- 使用 altstack 将 libbase 传递给使用 C 参数传递的共享库函数。这样,libbase 可以在共享库函数内部访问,而无需将其显式添加到函数的参数中。这应该有助于移植需要每个打开库基地址的外部代码;例如,每个打开库的不同状态。
- 到目前为止,共享库函数的存根使用全局 libbase 在 LVO 表中查找函数地址。在此补丁中,提供了存根函数,这些函数将 libbase 作为当前 libbase 中的偏移量查找。这允许每个打开的库打开其他每个打开的库。每次第一次打开时,也会打开其他库,并将 libbase 值存储在当前的 libbase 中。然后,当第一个库调用其他库的函数时,它们的存根函数将在当前的 libbase 中找到正确的 libbase。此功能也应该可用于创建纯程序,但尚未实现对该功能的支持。
- 还使用 altstack 将 libbase 传递给一般的 AROS_LHxxx。由于 m68k 有自己的 AROS_LHxxx 函数,因此它不会对其产生影响。
- 使用新的 C 参数传递方式将 arosc.library 转换为一个使用 %build_module 编译的“普通”库,无需在 struct ETask 中使用特殊字段。
- 此外,从 struct ETask 中移除程序启动(例如,来自 compiler/startup/startup.c)信息,并将这些信息存储在 arosc.library 的 libbase 中。
x86_64、i386 或 m68k 的所有实现都不是最终的。它们仍然需要进一步优化。对于 m68k,我们应该找到一种方法,即使在使用 C 参数传递时,也能将 libbase 存储在 A6 中,而不是 altstack 上。我实际上假设 libbase 始终存储在相同的位置,无论函数是使用寄存器参数传递还是 C 参数传递。幸运的是,目前还没有任何代码依赖于此功能。>对于 i386,我想使用 %ebx 传递 libbase,而不是 altstack 的顶部。问题是如何使用 -ffixed-ebx 标记编译所需的代码,以便编译器不会覆盖该寄存器。对于其他 CPU,找到用于 libbase 参数传递的寄存器也是一个好主意。我计划尝试在大多数架构上使用寄存器传递 libbase(例如,m68k 上的 A6,如果对速度的影响不大,则 i386 上的 %ebx,PPC 上的 r12,等等)。
将 altstack 的顶部作为 libbase 的实现并非旨在成为任何 CPU 的最终实现。它存在的目的是能够快速地启动一个新的架构,而无需太多工作。
如果某些内容不清楚,或者您想讨论实现方案。我们仍然有改进的空间。我将在接下来的几天尝试编写一些文档来解释当前的实现。
也请告诉我“双栈”目前是否为默认解决方案?我不确定是否喜欢它,因为它对我来说似乎是一个很大的 hack。我们能否只在 struct ETask 中为库基址指针实现一个辅助栈?我认为将其放在那里会更合理,至少这是我的感觉……最后,我不介意第二个栈位于哪里。它应该始终能够以低开销访问(在 ETask 中可能只需要多一次内存间接寻址,我认为这仍然是可以接受的)。您确定吗?
底部栈方法
1. FindTask(NULL) 2. 读取 tc_SPLower 3. 从那里读取“辅助栈的顶部” 4. 读取库基址
ETask 方法
1. FindTask(NULL) 2. 读取 tc_ETask 3. 从那里读取“辅助栈的顶部” 4. 读取库基址
我最初的补丁甚至使用 -ffixed-ebx 编译了整个 i386 宿主,并且 %ebx 本身始终是指向一个栈的指针,该栈的顶部是 libbase。不知道我是否需要重新审视它,关于预留 %ebx 对速度的影响有一些疑问;所以我就放弃了这条路。我从未在使用我为使用 -ffixed-ebx 编译它所做的更改的原生环境下让 vesa 工作起来。如果走这条路,则需要为每个架构预留一个寄存器。
基本原理 SysBase->ThisTask->tc_SPLower 包含一个指向栈顶的指针;**(SysBase->ThisTask->tc_SPLower) 是栈顶,并且大多数情况下包含 libbase。要移植当前的功能,我认为您需要做的是
- arch/arm-all/exec/newstackswap.S 在那里实现伪 C 代码。
- 在 arch/arm-all/clib 中实现 (SysBase->ThisTask->tc_SPLower) 的存储/恢复。
- 在 setjmp/longjmp/vfork/vfork_longjmp 中。
我认为这应该可以实现基本功能。对于其余部分,最好等待就如何进行达成一些共识。最终目标应该是使用寄存器将 libbase 传递给函数。
正在为 m68k 实现“寄存器中的 libbase”(选择 A4),并且遇到了一些障碍。我认为它必须是 A6,与 regcall 相同。这样,libbase 也可以用于在其中放置相对寻址的变量,并且函数是使用 stackcall 还是 regcall 调用都没有关系。
问题是 - 在调用存根时,我在哪里存储旧的 A4?我无法将其存储在栈上(这会弄乱对“真实”例程的调用序列),并且使用 altstack 会消除使用寄存器保存 libbase 的大部分优势。当从一个 libbase 移动到另一个 libbase 时,您对 %EBX 的解决方案是什么?我对 %ebx 的解决方案是,我让 %ebx 指向栈顶。放置一个值会将 %ebx 增加 4 并将新值存储到 (%ebx) 等。替代方案:也许指示编译器 a4 (a6 ?) 是易失的,并且会被函数调用覆盖。在这种情况下,您无需保存它。我想这就是 AmigaOS 存根中所做的事情。
或者,如果我们取消对 varadic funcstub 例程的支持,那么实现一个机制将非常简单,在这种机制中,库基址是最后一个参数(第一个被压入列表中),并且 AROS_LIBFUNCSTUBn() 宏可以利用该存储空间来保存旧的 A4/%EBX。
在 UCLinux 中,基本上为系统中的每个可能的库分配了一个每个任务的插槽,并且该插槽用于保存库数据指针(基本上,在我们的例子中是库基址 - 这给出了关于如何实际处理库的数据段的提示:库基址的扩展)。当然,这限制了系统在任何给定时间可以处于活动状态(或处于休眠状态)的库的数量和类型,但可能可以考虑一种扩展此方法的更动态的方法。问题是 libbases 需要在任务之间共享。YAM 端口被阻止,因为在一个任务中打开的文件无法在另一个任务中访问。这现在应该解决了,但文件操作尚未线程安全。想法是使用 A4 指向这个“全局数据段”,它实际上可以是 ELF 术语中的 GOT(全局偏移表)。此外,我们基本上是在谈论 AROS 特定的 PIC 实现,并且可以将 ELF 标准作为参考。
我看到了这种机制的巨大潜力,它可以解决我们的许多问题。我将扩展该机制,以便每个 LoadSegable 对象都可以请求在此 GOT 中获得一个插槽。然后它基本上变成了一个 TLS 表格,而不是 GOT;后者存储指向函数或变量的指针。可执行文件还可以利用它通过相对于存储在其 TLS 插槽中的地址访问其变量来使其自身成为纯净的。
如果它是针对每个 LoadSegable 对象的,则加载器/重定位器可以在加载期间同时重定位符号,因此之后无需进行查找。实际上,Pavel 建议使用 TLS,但我担心缓存、锁定和查找的开销,但此系统实际上可以解决这个问题。
强烈建议为此使用 pr_GlobVec - 知道它仅适用于进程任务,但它最初的设计目的与此非常相似,并且在 AROS 上未使用(除了 m68k,它可以很容易地修改以适应此目的)。如果我们这样做,那么“AROS_GET_LIBBASE”将是
#define AROS_GET_LIBBASE(libuid) \ ({ struct Process *me = (struct Process *)FindTask(NULL); \ (me->tc_Node.ln_Type == NT_PROCESS) ? \ (struct Library *)(((IPTR *)me->pr_GlobVec)[libuid]) : \ (struct Library *)NULL; \ })
pr_GlobVec[0] 保留用于系统中支持的 LIBUID 数量。
LIBUID 可以是静态分配的(糟糕)或者我们有一个 uuid.library,它保存库名称到 uid 的主映射(或任何字符串到 uid),库将在 Init() 中从中分配。最好它不是基于字符串而是基于唯一地址。例如,程序主函数的地址,共享库中 libInit 中 libbase 的地址等。
不要忘记 x86 有两个可以用来存储指向 TLS 的指针的寄存器:段寄存器 %fs 和 %gs。它们在 linux 和其他操作系统上也已经被使用。在这种情况下,您在任务启动时设置寄存器。但这可以稍后再处理,当整个基础设施到位时。不,我们不能使用它们,我真的很遗憾!我得出了与您现在相同的结论,但后来有人告诉我这会破坏托管架构的兼容性。该解决方案仅适用于原生目标……顺便说一句。ARM 架构也适用。ARMv7 功能提供了一个特殊的寄存器,可以用作 TLS 指针。当然,这会在托管目标和原生目标之间引入不兼容……是的!拥有 AROS 托管版本绝对是 AROS 的一大特色,但也是一个巨大的劣势……因为我们无法以相同的方式访问每个 AROS 目标(例如 x86 架构)中的数据?对于每个第三方程序,必须拥有不同的“linux-i386”和“i386”编译的二进制文件,这将非常丑陋。设置和检索 TLS 通过 %fs/%gs 将在一个架构(pc-i386)中被允许,但在另一个架构(linux-i386)中被禁止。因此,任何使用 AROS_GET_LIBBASE 的内容都需要针对这两种情况进行不同的编译。它在 linux 上根本不被禁止。Mac 呢?Windows 呢?我们也为这些操作系统提供了 AROS 的托管版本。在这些操作系统上是否被禁止?没有正确的信息,我认为我们无法做出任何决定?此外,编译器/链接器可以更改为生成由 elf 加载器修补的代码,以满足主机平台的需求。我认为我们可以像处理 SysBase 一样处理它(ELF 文件中的绝对符号,在加载时解析)。
* There is a list of known symbol to SIPTR mappings, stored in a resource (let's call it globals.library), with the following API: - BOOL AddGlobal(CONST_STRPTR symbol, SIPTR value); - BOOL RemGlobal(CONST_STRPTR symbol); - BOOL ClaimGlobal(CONST_STRPTR symbol, SIPTR *value); - VOID ReleaseGlobal(CONST_STRPTR symbol); - BOOL AddDynamicGlobal(CONST_STRPTR symbol, APTR func, APTR func_data) where func is: SIPTR claim_or_release_value(BOOL is_release, APTR func_data); Then you can support per-task (well, per seglist) libraries that autoinit. * The loader, while resolving symbols, if the symbol is 'external', then it: * Looks up the symbol in a list attached to the end of the seglist, which is a BPTR to a struct that looks like: struct GlobalMap { BPTR gm_Dummy[2]; /* BNULL, so that this looks like an empty Seg */ ULONG gm_Magic; /* AROS_MAKE_ID('G','M','a','p') */ struct GlobalEntry { struct MinNode ge_Node; STRPTR ge_Symbol; SIPTR ge_Value; } *gm_Symbols; }; * If the symbol is already present, use it. * If the symbol is not present, use ClaimGlobal() to claim its value, then put it into the process's GlobalMap. * Stuff the symbol into the in-memory segment for the new SegList * DOS/UnLoadSeg needs to modified to: * On process exit, call ReleaseGlobal() on all the symbols in the trailing seglist * Libraries can use AddGlobal() in their Init() to add Globals to the system, and RemGlobal() in their Expunge(). - NOTE: RemGlobal() will fail is there is still a Seg loaded with the named symbol!
- 优点
- 所有支持 global.library 的“普通”库都可以通过在
它们的 Init 中添加“AddGlobal(“LibraryBase”, libbase)”来设置为“autoinit”,尽管我们确实有 uuid.library……
- 每个打开者的库继续像现在一样工作(它们不导出全局变量)
如何将 libbase 传递给这些函数?这就是我们试图解决的全部问题。对不起,我总是忘记非 m68k,在那里您不再通过栈上的 AROS_LHA 传递 libbases。我在 m68k 上唯一的问题是“libstub”库。那么您就遇到了真正的困难。
- 每个任务的库可以具有由系统动态分配的 ETask/pr_GlobVec LIBUID 索引(即“aroscbaseUID”符号),并且
注入到段中。
- 缺点
- 维护字符串的唯一性:例如 PROGDIR:libs/some.library 和 libs:some.libary。安装了同一个程序的两个版本……
在这种情况下,“some.library”不会向 SysBase 注册自身,对吧?并且打开这些库的任何程序都在运行(已经被 loadsegged),对吧?
在这种情况下,some.library 的加载器(lddaemon?)可以在加载库时向 GlobalMap 段添加“ThisLibrary_UID”符号。
也许在进程中也添加一个 pr_SegList[7] GlobalMap,在搜索 global.library 之前先搜索它,以便程序可以通过 LoadSeg 向它加载的覆盖提供符号?
此外,我认为操作系统应该能够很好地了解系统中对象的数量,以便能够很好地猜测分配情况,我认为 GOT 的可能扩展应该是可以处理的。
在另一封邮件中,我评论了在不同任务之间共享 libbase 的问题。我认为这可以通过将父 GOT 的 GOT 表复制到子 GOT 表来解决;并且可能将其限制为那些已指示要复制的 TLS。
另一个困难可能是 RunCommand 的实现,它重用了 Task 结构。我认为可以通过将当前的 TLS 表复制到另一个位置,清除它,并在程序运行后恢复旧表来解决。
一个可能的讨论点是将此 LTS 表指针存储在 ETask 中或为其保留一个系统范围的寄存器。我认为存储在 ETask 中是可以的,我假设优化编译器足够聪明,可以在提高性能时将信息缓存到寄存器中。
我认为我们唯一无法实现的功能是在同一个 Task 中拥有共享库的两个 libbases。目前,您可以两次打开一个 peropener base,并且您将获得两个 libbases。例如,它可以被共享库使用,以便它的 malloc 从另一个堆分配,而不是主程序本身内部的 malloc。另一个库可以选择共享 libbase,以便库中的 stdout 与主程序中的 stdout 相同,并且库可以输出到主任务的输出。虽然我喜欢这种灵活性,但我认为我们可以在没有它的情况下生存,并且如果需要类似的东西,可以找到解决方法。对于从 Linux 移植共享库,它肯定不是必需的,因为它们已经假设每个进程一个库。
总结:希望大家原谅我进一步探索这种机制,而不是继续记录当前的实现。
IMO printf 等必须在 arosc 中。IMO 移植带有可变参数的库不应该增加额外的工作。
(请注意,这仅讨论 per-opener 库,*不*包括 altstack 或“stub”库,其中库基址通过 AROS_GET_LIBBASE 获取)。也许对于 per-opener 库,一个更简单的机制,而不是使用 AVL 树,是更像 AROSTCP 的实现,并使与 SysBase 注册的库成为一个生成库的“工厂”。目前,AVL 树不用于 peropener 库;它们用于“perid”库,例如,当从同一任务两次打开同一个库时返回相同的 libbase。当我查看生成的代码时,MakeLibrary 仅在 OpenLib 中调用,而不是在 InitLib 中调用,所以我想我们已经做了你说的事情。不是吗?
与系统 libbase(“工厂”)注册的库*仅*在其负面方面具有 Open/Close/Init/Expunge,并且在其正面方面仅具有 struct Library。不过,它仍然与“真实”库具有相同的名称。
在工厂的 Open() 中,它
- 创建完整的库,但*不*将其注册到 SysBase
- 调用完整库的 Open(),并增加工厂的使用计数
- 将完整库返回给调用者。
在工厂的 Close() 中,它
- 调用完整库的 close
- 减少工厂的使用计数
这应该简化 per-opener 库调用的实现,并且(通过 genmodule)使将“纯”库从系统全局库转换为 per-opener 库变得微不足道。
它将外部模板模式从“每个进程”更改为“每个打开者”(即假设前一个是每个进程)。
即,如果某些代码自动打开一个库,并且 - 同时 - 也在用户代码中稍后显式地打开它,那么新方法是否仍然会在两个地方返回相同的库基址?这是否会再次在“完整打开”中使用 AVL 树?不,使用这种方法,它们将是两个独立的基址。根据情况,这可能正是您想要的。(例如,想象一下一个“libgcrypt” - 您不希望这两个基址共享内存)。在某些其他情况下 - 例如具有自定义堆和 malloc/free 或 open/close 的 libnix 克隆 - 您可能正好想要相反的结果。听起来约定仍然很重要 ;-)(与之无关,我想知道,如果一个进程与子任务共享其库基址是否仍然可以,除非库执行 DOS I/O。)
如果任务不是进程会发生什么?调用是 NO-OP,还是会发生崩溃?NO-OP 意味着对结果进行检查,这会大大降低速度。此外,请考虑在 loadseg 时分配 ID 的选项,并相应地重新定位二进制文件。
如果我记得没错,当时我们说我们可以只使用一种新的重定位类型来在两个寄存器之间切换,具体取决于平台。
我将我们的方法称为 MLS(模块化本地存储),它是一种本地存储,但与 TLS 不同,它不绑定到 Task 或 Processes。
我认为大多数情况下符号可以是匿名的,不需要名称。因此,我将以以下方式修改您的提议
- 基本 API
off_t AllocGlobVecSlot(void); BOOL FreeGlobVecSlot(off_t);
第一个为您提供一个插槽,第二个释放插槽。
- LoadSeg/UnloadSeg
有一些特殊的符号表示 MLS 符号,例如 MLS.1、MLS.2 和 MLS.3。加载程序然后会调用 AllocGLobVecSlot() 三次,并将每个符号替换为三个值。
在 UnloadSeg() 期间,会使用三个偏移量三次调用 FreeGlobVecSlot()。
- library/libInit() & lib/libExpunge()
此函数仅调用一次,因此它可以为所需的插槽调用 AllocGlobVecSlot() 并将其存储在 libbase 中。Expunge 将执行正确(tm)的操作。libbase 本身的存储将使用 LoadSeg 方法。
- 具有共享插槽的纯可执行文件,例如来自同一个的所有运行
SegList 共享一个插槽(不知道是否会需要,但它可以处理)。
off_t slot = 0; /* offset 0 is not a valid offset */
PROTECT
if (!slot) slot = AllocGlobVecSlot();
UNPROTECT
- 需要非匿名插槽的用例
因此,可以提供一个新的 API,它基于上面的 API,使用内部哈希、AVL 查找或其他索引机制。
off_t AllocNamedGlobVecSlot(SIPTR) BOOL FreeNamedGlobVecSlot(SIPTR)
可以处理 Peropener 库,尽管它们的效率不如 MLS 库。类似于我的旧补丁,其中 %ebx 是指向带有已推送 libbase 的堆栈的指针,MLS 插槽可以指向这样的堆栈。这将执行以下操作……
- 在进入共享库的函数时,一个存根将推送堆栈的 libbase,并在函数执行后弹出。
- 在库内部,libbase 始终可以作为堆栈的顶部检索。
- libInit 将使用(小)分配的堆栈初始化 MLS 插槽。这些 peropener 库的一个难点是。是 setjmp/longjmp 的含义。如果从 peropener 库中函数的调用链下方到调用链上方的函数或主代码执行 longjmp,则需要将堆栈指针置于旧值。此跳转可能是由于 abort() 或 signal() 导致的。这目前在 altstack 实现中完成,但我认为对于 MLS 方法来说很难做到。
我不会尝试首先使这些 peropener 库工作。
此外,我还想重新审视需要 dos.library 来拥有这些库的要求。我想到的用例是互联网路由,其中没有文件系统,固件中也没有 dos.library,但我仍然希望在软件中使用 C 库。或者也许我们想用 newdos.library 替换 dos.library,它使用 IOFS 方法并摆脱所有这些丑陋的 AOS 兼容性 hack,这些 hack 不断出现在 svn 提交列表中:)。
在我们存储库的头部,现在只有 3 个目录
admin/ branches/ trunk/
我认为在其中添加两个额外的目录:tags 和 imports 会很有价值
实际上,在获得稳定的 ABI 后,我想尽可能多地从 contrib 目录转移到其他存储库。我觉得应该这样做有几个原因
- AROS 存储库应用于核心 AROS 代码。
- 我认为其他 contrib 项目应该尝试为所有类 Amiga 操作系统编译。
- AROS 和其他程序的发布方案不必保持一致。
- 二进制版本应在 aros-archives 和 aminet 和/或 OS4Depot 上提供以安装它们。(一些聪明的程序可能应该提供以使发行版开发人员的生活更轻松)。
- 我们应该避免为 AROS 和其他 Amiga 操作系统并行分叉程序。
如果确实需要一个托管 AROS 项目的地方,我们可以调查建立这样的服务器,但随后为每个项目单独包含错误跟踪、治理、邮件列表等。我个人认为,像 sourceforge、google code、savannah 等已经有足够的地方供人们去托管此类项目。
如我们在分支 ABI V0 和 ABI V1 时所讨论的,引入标签也很好。通常,这在存储库中名为 tags 的目录中完成。目前,我们没有这个目录。(我们确实有 branches/tags,这是一个我做的 hack,因为一个人没有顶级目录的写访问权限。我认为这个目录不干净,应该删除)。
我想引入的第二个目录是 imports 目录,用于实现供应商分支,如 svn book 中所讨论的那样。目前,我们使用来自几个不同项目的代码,并且该代码存储在 AROS 树中;我们似乎在保持此代码最新并将其更改合并到上游方面存在问题。上游项目的维护者(例如 MUI 类等)对此表示抱怨(轻描淡写地说)。
我认为引入这些供应商分支将使更容易查看我们所做的更改并使补丁发送到上游,并使导入其代码的更新版本变得更容易。
AROS 存储库应用于核心 AROS 代码。我会保留开发内容。拥有启用了调试的链接器库等是很好的。也许我们应该保留构建 AROS 所需的任何内容(在 AROS 下)。
我同意。我认为拥有广泛的 SDK 是一件好事,并将其放在 AROS 存储库中;可能是单独的 dev 或 sdk 目录;替换 contrib?
我仍然认为“ports”目录是一个好主意,使为所有 AROS 版本构建应用程序变得更容易。目前缺少的是一种启用例如每月构建的方法。
我不反对,但有一些意见
- 普通用户永远不必编译自己的程序。他们应该能够下载安装包。
- 安装不应该是非此即彼的。用户应该能够选择安装特定的程序。
- 我更希望每个程序都有一个或多个官方维护者。我不喜欢现在的方式:将一些源代码放到一个大型的源代码树中然后置之不理。
OS4 确实使用了真正的 ELF 可执行文件。我想他们只是在加载时重新分配它们(在请求的地址和使用的地址之间添加一个差值)。还有一个问题 - 页面对齐。当我们有内存保护时,这将很重要。AmigaOS4 是一个在单一 CPU 和有限的硬件范围内运行的系统。我们的条件要广泛得多。坚持 64KB 对齐(ELF 常用页面大小)会导致相当大的内存和磁盘空间浪费。
MOS 使用可重定位对象。是的,还有自己的 BFD 后端。没错。他们使用 -q 标记编译代码,这会保留 ELF 重定位信息。用 binutils 创建我们自己的自定义格式并不容易,我很久以前就尝试过,甚至到了可以生成非常类似于 hunk 的 ELF 可执行文件(以节省磁盘空间)的地步,事实上,我构建并提交了一个加载器,但从未设法清理混乱的代码以将其提交给 binutils 维护者。GNU 代码太疯狂了。:D
参见 binfmt_misc 内核模块... Linux 可以支持任何人愿意为其编写加载器的任何格式。有点像可执行文件的类型 :):) 不过,实际上没有人使用它,特别是因为文件管理器中的文件关联对于大多数用户来说使其变得无关紧要(例如,您可能想要使用它的内容,例如在点击 ADF 时启动 UAE,或者在尝试启动 Windows 可执行文件时启动 Wine 通常都内置在文件管理器中,而 Linux 上的 shell 用户往往是喜欢自虐的家伙,讨厌背后发生的事情)。
librom.a 在 ABI V1 中消失了,并且共享 C 库是内核的一部分。C 库的初始化和打开会因这些更改而导致问题。
对于 i386,我会选择一个稍微大一点的 jmp_buf(大约 128 字节)。对于其他 CPU,我依赖您的专业知识。x86 的当前大小是 8 个长整型,即 32 字节。您是想以防万一保留额外的空间,还是有一些想法可以存储什么内容?
我一直喜欢查看其他人都在做什么。例如,nix 操作系统在 jmp_buf 中存储了什么,我们是否需要出于某种原因以相同的方式执行?
查看 nix 系统并不总是最好的做法。他们不太担心未来的二进制兼容性。如果他们需要破坏 ABI,他们会增加共享库的版本(例如 libc.so.n -> libc.so.(n+1)),以及所有依赖它的共享库。然后,他们会根据需要并行运行旧版本和新版本。
我想为 arosstdc.library 避免这种情况,这就是为什么我想预留一些额外的空间以供将来使用,以便在需要时可以在 jmp_buf 中存储更多内容,而不会破坏 ABI。
另一种方法是有人进一步研究这个问题,并提供良好的文档,确保我们将来永远不需要扩展 jmp_buf 的大小。
我现在认为 setjmp/longjmp 是 OS 的核心功能,不再是编译器支持的功能。这样,它就可以安全地用于整个 AROS,而无需依赖特定编译器的特定实现。
在 ABI V1 分支中,如果您使用 AROS_UFH 和 AROS_LH 来定义函数,则 libbase 的处理方式有所不同。当使用 AROS_UFH 定义时,所有参数都通过堆栈传递,使用 AROS_LH 时,ebx 寄存器用于传递 libbase。
这意味着不能使用 AROS_CALLx 宏来调用使用 AROS_UFHx 定义的函数。
因此,如果您想对我友好,请尝试使用 AROS_CALLx 或 AROS_LC 调用 AROS_LHx 函数,并使用 AROS_UFCx 调用 AROS_UFHx 函数。否则,我必须进行调试,这需要花费大量时间。(我现在正在启动 ABI V1 分支,直到第一次使用 loadseg)。
如何在 Snoopy 中定义补丁?您应该使用与原始文件相同的 AROS_LH 定义,并使用 AROS_CALL 函数来调用该函数。
AROS_UFH2(BPTR, New_CreateDir, AROS_UFHA(CONST_STRPTR, name, D1), AROS_UFHA(APTR, libbase, A6) ) { AROS_USERFUNC_INIT // result is exclusive lock or NULL BPTR result = AROS_UFC2(BPTR, patches[PATCH_CreateDir].oldfunc, AROS_UFCA(CONST_STRPTR, name, D1), AROS_UFCA(APTR, libbase, A6)); if (patches[PATCH_CreateDir].enabled) { main_output("CreateDir", name, 0, (IPTR)result, TRUE); } return result; AROS_USERFUNC_EXIT }
例如(希望没有错误)
AROS_LH1(BPTR, New_CreateDir, AROS_LHA(CONST_STRPTR, name, D1), struct DosLibrary *, DOSBase, 20, Dos ) { AROS_LIBFUNC_INIT // result is exclusive lock or NULL BPTR result = AROS_CALL1(BPTR, patches[PATCH_CreateDir].oldfunc, AROS_LDA(CONST_STRPTR, name, D1), struct DosLibary *, DOSBase ); if (patches[PATCH_CreateDir].enabled) { main_output("CreateDir", name, 0, (IPTR)result, TRUE); } return result; AROS_LIBFUNC_EXIT }
要获取此函数的名称,您必须使用 AROS_SLIB_ENTRY(New_CreateDir, Dos)。
我假设宏都是正确的。我认为 AddDataTypes 中对于 m68k 仍然存在一些问题。我用 FIXME 标记了它,请查看是否需要在那里更改某些内容。
我认为这些函数必须转换为使用 AROS_LH 宏。(它在 ABI V1 上可以工作,因为除了 libbase 之外,所有内容都在那里通过堆栈传递)。
AROS_UFCx() 应该*仅*用于 regcall 例程。如果 VNewRawDoFmt() 需要一个 stackcall 例程,请*不要*使用 AROS_UFCx()。只需声明一个普通的 C 例程。我们应该修改文档使其更清晰。
我想我们应该切换到 muimaster_bincompact.conf 在所有架构上,*包括* i386,只要我们使用 ABI v1。
不幸的是,WB 3.x 磁盘在没有正确的 Lock 结构的情况下永远无法正确启动。
例如,WB 3.0 Assign 命令获取一个锁,读取 fl_Volume 字段。结束。(所有卷名都显示为 ???,并且在尝试分配任何内容时都会显示“无法取消 <name of assign>”)
顺便说一句,我将 afs.handler MORE_CACHE (AddBuffers) 更新提交到了 SVN。大多数使用“addbuffers df0: <num>” 的引导磁盘如果没有此更改,将显示难看的错误消息。
在调试另一个无法工作的 CLI 程序时,问题再次出现在程序读取 Lock 结构字段上。这比最初想象的更常见。如果大多数问题都与 Lock 和 dos 数据包相关,那么调试浪费的时间真的不值得。那么现在的计划是什么?
这是否意味着 dos 例程(如 Lock())应该像“真正的”dos 一样直接调用 DoPkt(DeviceProc->dvp_Port, action number, arg,..),或者我需要将 packet.handler 放在某个地方?(如果是,为什么?)
我想先在 m68k-amiga/dos 中进行实验(覆盖 rom/dos 中的文件,易于禁用/启用更改),直到我确定自己知道自己在做什么(在完成 UAE FS 的所有工作后,我应该知道,但 dos 是一个相当奇怪的东西).. 它看起来足够简单,只是有点无聊.. 我也可以使用 UAE 目录文件系统进行测试(“只需要”修复 dos,一开始不需要触碰 afs.handler)。
据我了解,在*最终*实现(ABI V1)中,packet.handler 将消失,并且所有文件系统都将被重写为基于数据包的。我向...提出了一个“捷径”建议
“由于我们今天使用的文件系统(SFS、AmberRAM、CDROM、FAT)都是基于数据包的(无论是通过 packet.handler 还是 SFS 数据包模拟),那么为什么不进行以下操作,而不是整个系统更改
- 添加 packet.handler 中缺少的功能
- 迁移 SFS 以使用 packet.handler
- 所有新编写的模块都需要基于数据包
优点:1.0 版本的工作量减少,为将来的更改做准备 缺点:此解决方案可能直到下一个主要版本才会更改”。DOS 将继续使用设备接口(因此 afs 不会迁移),将禁止编写新的基于设备的处理程序,packet.handler 将被升级以具有完整的功能,并且 SFS 中的数据包模拟功能(在创建它之前编写的 packet.handler 的副本)将被删除并替换为 packet.handler 的使用。
我更希望 DOS 兼容性工作在 ABI V1 分支中第一次就做好。我们现在主要做的是为 m68k 开发一个兼容的 DOS,并为其余架构重新执行工作,并在稍后集成 m68k 的工作。
我一直不喜欢在另一个分支中对公共结构等的修复,因为(我认为)这些是错误,应该立即在主代码中修复。
基本的理由是我们可以使用一个临时寄存器将 libbase 传递给共享库,例如 arosc.library。如果我们有这个 ABI,我看不出为什么要为 AROS_Lxxx 宏引入第二种方法(除了 m68k 为了向后兼容性)的原因。这样做的原因仅仅是为了解决我们使用的开发工具的不足。
一些针对 libcall.h 的补丁,这些补丁更改了使用我们的 AROS_L[CDHP]xxx 宏定义的函数传递 libbase 的方式。这接近我认为我们的最终 ABI 可以成为的样子,未来几天将提供更详细的文档,但这里有一些快速总结
在 AROS 共享库中,您目前可以有两种类型的函数,使用 m68k 寄存器定义的函数和不使用的函数。前者在源代码中由我们的 AROS_L[CDHP]xxx 宏处理;后者是常规的 C 函数。
之前的补丁添加了将 libbase 传递给普通 C 函数的功能;一项计划用于简化 Linux/Windows/... 共享库移植的功能。第一个版本使用备用堆栈实现来记住设置新值时 libbase 的先前值。这并不是所有人都喜欢。在 Jason 和 Pavel 的帮助下,补丁被重新设计为使用临时寄存器传递值,因此不需要记住先前值,因为编译器知道该值无论如何都会被覆盖。使用的寄存器如下(通常是编号最高的临时寄存器)
- i386: %edx
- x86_64: %r11
- arm: ip (=r12)
- ppc: r12(与 MOS 上使用的相同)
- m68k: a1
存根函数存在于与共享 .library 相关的 libxxx.a 中,以添加这些设置 libbase。
现在我刚刚提交了一个补丁,对除了 68k 之外的所有 CPU 上使用 m68k 寄存器定义的函数执行相同的操作。在保持与 m68k 上的经典 AOS 的二进制兼容性的情况下,后者是不可能的。但是对于其他 CPU,现在进入共享库函数时的状况与它们的定义方式无关:函数参数按照 SYSV 标准指定的方式处理,libbase 通过上面列出的 AROS 指定的临时寄存器传递。
关于 m68k 的更多信息;现在的情况是:使用 m68k 寄存器指定的函数当然会使用这些寄存器。那些不使用的现在使用 SYSV 标准,即将参数放在堆栈上,然后 libbase 位于 a1 中,如上所述。
想知道我们是否不应该在这里偏离标准,并在这里使用寄存器,例如按照以下顺序:d5、a4、d4、a3、d3、a2、d2、d1、a0、d0;a1 仍然包含 libbase。您怎么看?当然,这意味着必须使用特定于 aros 的 gcc 补丁,并可能导致性能不佳的代码和大量错误修复工作;必须查看可变参数处理;等等。另一方面,我认为它应该更快,特别是如果我们希望继续支持具有非常有限或没有数据缓存的旧版 m68k CPU。
- 如果我们有一个可用的 m68k LLVM 工具链,我会说“是的,听起来不错,算我一个”。
但 gcc 使用起来实在太糟糕了,无法正确获得 regcall 语义。让我们暂时忍受 stackcall 吧——目前有一些更大的性能瓶颈(例如 graphics.library 中的 LONGxLONG 乘法),它们远远超过了 regcall 与 stackcall 的差异。愿意让 m68k 的 'C' 接口的 ABI 保持为 stackcall,用于 ABIv1。如果 m68k 能够存活到 ABIv2,我们可以重新审视这个问题。最好在那个时候说 m68k 的 ABI 尚未确定。这意味着所有使用例如 arosc.library 的程序将停止工作,没有向后兼容性。如果可能,我更希望在 ABIv1 之后没有向后兼容性中断;只提供扩展。
为什么 libbase 不放在 A6 中?这正是 A1 被使用的原因。因为一些原因以及 C 兼容性[*],你需要能够在存根函数中设置 libbase。如果你使用 A6 作为寄存器,则需要在存根中保留旧值。对于这个问题,我们没有找到任何我们三个人都满意的解决方案。使用一个 scratch 寄存器基本上允许在不增加调用方开销的情况下传递 libbase。无论如何,你需要将 libbase 地址加载到一个寄存器中,以便使用它的 LVO 编号计算要调用的函数的地址。
[*] Jason 曾经在我们关于需求的私下讨论中做了一个总结
- 1) 必须支持库的每个打开者的全局变量
- 2) 不应要求对第三方库代码进行任何更改
- 3) 应该支持在栈上没有库基址的函数调用,但库基址对被调用函数可用
- 4) 应该支持在栈上没有库基址的可变参数函数调用
我可以补充一点,由于函数指针等原因,3) 遵循 2)。例如,解析例程通常会传递一个函数指针来从流中获取字符。#define 技巧与之不兼容。
之前有人提出过,但我认为我们确实需要为每个架构定义一个参考编译器版本,以避免出现 A 人引入的代码对 B 人无效的情况,因为他们使用不同的编译器。在这种情况下,问题应该是:代码是否在 nightly build 上工作 - 如果是,那么我很抱歉,但 B 人需要做所有工作才能使代码与其设置一起工作。我们使用多种 Linux 发行版和多种开发环境,我们任何人都无法保证他的代码在所有这些场景中都能工作。我见过太多次人们抱怨这里和那里的“编译器错误”,而实际问题几乎总是代码错误,并且编译器最终改进到足以注意到它可以进一步优化代码。非常常见。所以最好先确认一下 :)
你用于跳过库函数调用的内联汇编代码:在我看来有点复杂,但我可能遗漏了一些东西。
这种更简单(并且更快、更小,就生成的代码而言)的方法有什么问题?
typedef void (*LibFuncCall)(Library *base, Type1 arg1, ...)__attribute__((regparm(1))); ((FuncCall)( ((struct JumpVec *)__SysBase)[LVO].vec))(base, arg1, ...);
基本上,为什么你选择 %edx 而不是 %eax 来传递 libbase 参数?这只是在所有 CPU 上使用编号最高的 scratch 寄存器的规则;但我对在 i386 上切换到 %eax 以帮助 gcc 没有问题。我们可以同意,对于 i386,我们可以选择一个更适合 gcc 当前工作方式的寄存器,并且愿意进行更改。问题是我需要对 ARM 使用类似的 hack,并且在那里你没有这些函数属性来做出更优雅的解决方案。我希望有一个 libbase 函数属性,它可以将 libbase 放到我们支持的 CPU 的正确寄存器中。同意,但最大的问题是谁将完成这项任务并在之后进行维护。每个人都愿意将自己固定到一个标准化的编译器来编译 AROS。好吧,很久以前我们就将自己固定到了 gcc。对于其他编译器,可能会采用其他方法。我主要考虑的是 x86 端口。任何架构都可能拥有自己的特定 ABI。
此外,你的选择仅限于一个在调用函数和进入函数之间可用的寄存器。任何 scratch 寄存器都可以使用,我只是没有看到 trampoline 的必要性。它也可能对速度产生很大影响,因为分支预测和其他类似的东西 Michal 可能对此有更好的了解。在 ARM 上,除了 ip 之外的所有 scratch 寄存器都用于函数参数传递。因此,我们需要使用该寄存器才能与在函数参数已设置时设置该寄存器兼容;例如,在存根函数中。
所以我仍然想知道是什么导致 gcc 优化掉了代码,以及它是否是 gcc 的错误,或者代码中是否存在一些非平凡的严格别名违规。没有深入研究,但这似乎是罪魁祸首,在 4.5.3 中得到解决:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45967
无论如何,我认为这只会解决部分问题,因为 Pavel 说在他的编译器中,他在 x86_64 上也遇到了问题,而我的编译器启动了。x86_64 不使用 jmp 技巧。
还可以使用“thiscall”属性。
`thiscall' On the Intel 386, the `thiscall' attribute causes the compiler to pass the first argument (if of integral type) in the register ECX. Subsequent and other typed arguments are passed on the stack. The called function will pop the arguments off the stack. If the number of arguments is variable all arguments are pushed on the stack. The `thiscall' attribute is intended for C++ non-static member functions. As gcc extension this calling convention can be used for C-functions and for static member methods.
请注意,regparm 和 thiscall 都被禁用用于可变参数函数。当我尝试 thiscall 属性时,我收到一条消息,指出该属性被忽略;regparm(1) 似乎工作正常。我看到它是在 4.6 中引入的。使用它将很有用,这样库也可以作为 C++ 类实现。实际上,我更喜欢 %ecx 而不是 %eax,但原因是 %eax 保证会被函数调用的返回值覆盖,而 %ecx 可能在整个库中用作全局偏移表指针,方法是使用 -ffixed-ecx 或类似的东西。
可以在 gcc <4.6 上使用 regparm(2),并将 0 作为第一个参数,libbase 作为第二个参数。当然,然后可以讨论这个 hack 是否比我启动这个讨论的复杂的 jmp hack 更糟糕或更好:)。
可能遗漏了一些东西,但你只需要将 libbase 作为第一个参数吗?我们希望能够在通过函数指针调用函数但其参数列表中没有 libbase 时也设置寄存器;因此,我们不想干扰正常的函数参数传递。
目的是能够移植外部共享库,而无需对源代码进行任何更改;例如,无需保留 AROS 特定的补丁。我的最终愿望是,你获取一个外部共享库代码,编写一个相应的 .conf 文件,然后编译共享库。这意味着你不能使用我们现在在 AROS_Lxxx 宏中使用的许多技巧。也不想引入加载时链接来执行此任务;例如,像 OS4 引入的 .so 对象一样。我仍然希望保留编译时链接。此外,OS4 需要虚拟内存才能真正地在程序之间共享它们的 .so 对象。你基本上想要一个 AROS 版本的 PIC 代码:在寄存器中传递 libbase 是一个实现细节,你真正需要的是一种方法来告诉编译器库中定义的所有全局变量都需要相对于此 libbase 访问,并且加载器或库初始化代码必须准备一个包含所有这些变量的 libbase。如果你的目标是不更改库代码,就是这样。是的,但还没有,我只是迈出了第一步,以确保当你进入共享库函数时 libbase 可用,因为这是 ABI 的一部分,并且必须尽快修复。稍后可以完全实现 PIC 机制,而无需破坏 ABI 或扩展 ABI;这只是库如何使用它获得的 libbase 和在打开库时初始化 libbase 的内部事务。传递 libbase 的机制也比仅仅模仿 UNIX 共享库的行为更灵活。一个任务可以打开一个库两次,并在调用库的函数之前选择使其中一个或另一个处于活动状态。这实际上从当今市场上各种编译器的角度来看使事情变得复杂。PIC 代码通常对调用方来说是透明地实现的,但在我们的例子中,相反,调用方有责任处理它。我想可以用 trampoline 来完成,就像 libc 现在所做的那样,但你明白这是一个重大的设计决策。调用方不需要知道库是否使用 PIC。
调用方唯一的责任是在程序入口处打开库,并在调用其函数时将共享库的 libbase 放入 scratch 寄存器中。我看到的一个问题是,我们目前的库只处理其 LVO 表中的函数指针,并且我们可能需要类似的东西来处理变量。因此,arosc 的 errno 目前是一个调用库函数的 #define。对于线程安全,它也必须这样:)。或者它必须是一个 __thread 变量。但我认为这会使事情变得更加复杂。
是否有关于 uclinux 在不同 CPU(例如 m68k、i386、x86_64、ARM 和 PPC)上如何实现它的良好最新文档,我找到的文档提到大多数 CPU(尚)不支持共享库。
你的系统在 AROS 当前支持的 CPU 上会是什么样子?所有 CPU 上都有 scratch 寄存器可用,%fs/%gs 没有。
确实,我想找到一个最适合 Amiga 类型共享库的 ABI,而不是一个最适合当前现有编译器的 ABI。但如果有相同的选择,我会选择在当前编译器中最易于使用的那个。最接近的是一个支持“GOT 树”的系统。你可以将每个 GOT 视为一个 libbase,并且整个系统编译为 PIC。但是,在现有的系统中,我无法想到任何一个。
另一种方法是使用每个任务的指针指向全局分配的“库基址”(实际上是 GOT 和 PLT 表),可能通过 %fs 或 %gs 寄存器访问(这样你也可以节省一个 scratch 寄存器)。
我想我们可以尝试你的方法,看看效果如何。PIC 可以通过让编译器将遇到的任何函数视为属于“全局”类的成员来实现。一旦决定修改编译器,使用这种方法可能会更容易。
库本身可以决定在同一任务从同一库打开两次时返回相同的 libbase;例如,模仿 UNIX 共享库时的预期行为。它还可以决定只将某些变量放入 PIC 表(例如 libbase),并使其他变量共享。…
我没有读完所有内容,但看起来您正在讨论在 x86 上使用哪个寄存器来存储对 GOT 或共享库类似内容的引用?我建议使用 ebx 寄存器,因为这个寄存器就是为此而设计的?它是扩展基址寄存器。它也被 ELF 用于共享库。我知道 AROS 的共享库并不是真正的共享库,尤其不是 ELF 共享库,但如果您使用 ebx,我认为您会遇到更少的 gcc 问题。它可能不再重要了,但这就是最初的实现方式。问题是 %ebx 不是一个临时寄存器,因此如果您想在 asm 存根代码中放入一个新值,则需要保留旧值。没有找到好的解决方案来做到这一点,使用临时寄存器解决了这个问题。好吧,难道您不是只将 GOT 的地址一次放入 ebx,然后不再触碰该寄存器吗?至少 GCC 生成的 PIC 代码就是这样做的。如果绝对必须触碰它,它也会恢复它,因为它是在 e{a,b,c,d}x 中唯一一个非易失性寄存器。然后它通常尽可能地避免使用 ebx。共享库仍然可以在内部使用 %ebx 作为 got 寄存器,它只需要在通过 LVO 表进入函数时保存 %ebx 的先前值并将 %eax 移动到 %ebx 即可。在调用方站点,如果 libbase 是一个临时寄存器,则会更容易。我们希望能够通过函数指针调用共享库中的函数,而无需在其中包含 libbase 的原型,例如用于解析例程的 fgetc。然后,您可以使用小的存根代码,该代码只需从全局变量中获取 libbase 并将其放入临时寄存器中。
几年前,某些 Amiga 编译器有一个组合的 .data/.bss 段,该段通过 A4 寄存器引用。如果正确实现,则意味着最终二进制文件始终是“纯净的”。这是一个示例实现,其中 Task->tc_TrapData 用于存储 A4。Task->tc_TrapCode 是一个代理,如果用户提供了,它将调用“C 安全”陷阱例程。对 .data/.bss 的所有引用都是间接的,通过 A4 寄存器。这允许 .data/.bss 段位于内存中的任何位置,并且 .text 中没有 .data/.bss 符号修复。
在 LoadSeg 修复了所有 .text 符号(当然,这些符号仅引用 .text)之后,程序启动按如下方式进行
__start: UWORD *a4 = AllocVec(sizeof(.data + .bss), MEMF_CLEAR) /* Set up .data, from an offset, value table. On AOS, you could * do this by calling InternalLoadSeg on a RELA segement that is * stored in .rodata or .data, or roll your own mechanism. */ For Each Symbol in .data; do Set a4 + SymbolOffset = SymbolValue FindTask(NULL)->tc_TrapCode = .text.TrapProxy FindTask(NULL)->tc_TrapData = a4 /* Load all the libraries */ For Each Automatically Loaded Library in .text.Library List a4 + Library Offset = OpenLibrary(.text.Library List Item) /* Call the C library startup, which calls 'main', etc. etc */ jsr __start_c /* Close all the libraries */ For Each Automatically Loaded Library in .text.Library List CloseLibrary(a4 + Library Offset) /* Free the .data+.bss */ FreeVec(a4) ret
从那时起,编译器确保不使用 A4,如果 LVO 调用需要覆盖 A4,则将其保存在堆栈上并在调用后恢复它。因此,编译程序中的每个函数都可以通过 A4 访问 .data+.bss,并且程序的后续调用(可以设置为 Resident)将获得自己的 .data+.bss 段,并共享原始调用的 .text 段。
注意:一个可怕的副作用是,您需要显式使用一些疯狂的宏来根据您的任务 ID 将数据段检索回 A4,例如 BOOPSI 类、中断处理程序和任何其他回调。对于 AROS 上的任何“自动纯净”解决方案,我都不明白如何在没有深度编译器魔术的情况下解决这个问题。
注意 2:向 GCC 添加此支持并不容易。对于 m68k 来说,甚至可能不可能。我之所以提出这一点,是为了作为一些“编译为纯净”的实现如何在 AOS 上工作以及 AROS LLVM 团队需要考虑的事项的历史参考。
我认为我在某个地方读到,变量堆应存储在 ABI v1 上的 A6 中,并在 A6 保存库基址时溢出到堆栈中。因此,即使在创建纯可重入代码时,A4 仍然可供 AROS 中的通用代码使用。这是一个想法。在 AOS 编译中使用 A4 是因为很少有库将其用于其 LVO。
我认为甚至不需要在 ABI 中修复它。我认为使用 A4 的程序可以在与使用 A6 作为基址指针的程序相同的 OS 上运行。当然,对整个 m68k AROS 使用相同的方法会很好。要标准化静态链接库(例如 libxxx.a),需要定义它。对于 m68k,已经需要为使用的不同代码模型提供 .a 文件。我们能否并且是否希望摆脱它?
我查看的大多数反汇编的 Amiga 代码都需要大量的溢出才能使其工作,而任何代码需要足够多的地址寄存器以至于必须溢出 A4 这种情况非常罕见。在 m68k 中,AROS gcc 宏在调用共享库中的函数时不会溢出 A6 寄存器吗?仅在未修补的 GCC 中,帧指针为 A6。建议用于 AROS m68k 的 GCC 具有一个小的补丁,该补丁将帧指针更改为 A5。由于我们还使用 -fomit-frame-pointer 编译,这减少了(但没有消除)帧指针的使用次数,因此 A6 溢出非常少见。snt a6 帧指针对于像 muforce 这样的调试软件是否必要?我从未使用过 muforce,但在反汇编的 Amiga 软件中,我从未见过使用 A6 作为帧指针的示例,因此这听起来像是一个奇怪的要求。至少大多数早期的 Amiga 编译器使用(过) A5。当然,我已经很久没有在 m68k 上做太多工作了,所以我没有查看过 gcc 或 vbcc 生成的很多代码。
在 AROS rom/ 中,我们只有
rom/exec/exec.conf:APTR NewAddTask rom/graphics/graphics.conf:LONG DoRenderFunc
在 AROS 工作台中,我们只有
workbench/libs/icon/icon.conf:BOOL GetIconRectangleA workbench/libs/reqtools/reqtools.conf:ULONG rtEZRequestA workbench/libs/datatypes/datatypes.conf:ULONG SaveDTObjectA workbench/libs/datatypes/datatypes.conf:ULONG DoDTDomainA workbench/libs/workbench/workbench.conf:struct AppIcon *AddAppIconA workbench/classes/datatypes/png/png.conf:void PNG_GetImageInfo
因此,A4 用于 LVO 调用的情况仍然*非常*罕见,这为您节省了一个寄存器重新加载(您必须对使用 A6 的每个调用执行此操作)。(Mesa 不在此列表中,因为它有许多函数一直到 A5!)
A6 是该库的 libbase,图形库中的大多数库内调用都是 LVO(寄存器)调用,因此这并不奇怪。不,这些是内部对内部子例程的调用。其他参数被压入堆栈,但 a6 没有被压入。被调用者继续通过 a6 访问 GfxBase。然后他们可能使用了混合调用序列,即
void foo(int a, char b, long d, REGPARM(A6) struct Library *GfxBase);
SAS/C 和其他编译器能够完成一些不寻常的特技。
我还希望看到 68k LLVM 编译器中加入的一点是,使堆偏向于从堆指针中减去 -128 并使用 -128 作为基址而不是 0。这将使变量具有与堆指针单个字节偏移的可能性提高一倍。如果变量堆的大小超过 32k+128,那么我们可以将堆指针向下偏置到 -32768,以便在平面 68k 上最多获得 64k 的堆。我认为 PhxAss 汇编程序在内部使用了这种技巧。巧妙的技巧。那是字节偏移还是长偏移?在检查 M68000PRM 后,它说带有索引和位移模式的地址寄存器间接寻址模式始终使用 16 位位移。所以这意味着偏差需要是 -32768 而不是 -128。其实现方式很简单:当我们在启动代码中分配堆时,我们减去偏差 (SUBA -32768.w, A6),并在关闭代码中,我们在释放它之前将其加回去 (ADDA -32768.w, A6)。这将使我们能够对所有变量使用无符号偏移量,从而使我们的堆大小达到 64K 而不是 32K。我认为 MorphOS 也使用了类似的技巧。
至于使用 A6 作为堆指针持有者,LLVM 的 PBQP 寄存器分配器(1) 仍然能够在堆栈之前将地址寄存器内容溢出到数据寄存器中,因此地址寄存器的压力将小于普通寄存器分配器。PBQP 的问题在于它需要一个矩阵求解器才能有效地工作,这最好通过在基于 OpenCL 的 OS 上使用最新的显卡来完成。考虑到 PBQP 分配器在旧系统上的速度缓慢,在 68k 上编译发布版 68k 代码将是一项令人沮丧的练习。但是调试代码会很好,因为它通常会使用 LinearScan 寄存器分配器。
当前在 ABI_V1 分支(trunk/branches/ABI_V1)中的更改
dos.library 更改:BPTR 始终基于字 对处理程序的更改 %ebx 寄存器在 i386 上保留为相对寻址的基址以及传递给库函数的 libbase 指针
分支中的持续更改
正在处理 C 库和 ETask。此更改的目的是从 AROS Task 结构中删除所有 arosc 特定的扩展。我正在扩展 genmodule 生成的代码,以便它提供功能,以便 arosc.library 可以将其所有状态存储在其自己的每个打开程序/每个任务的 libbase 中。
to be done (discussion has to happen and actions to be defined and assigned):
SysBase 位置 当前它是由 ELF 加载程序处理的特殊全局变量;但有些人似乎不喜欢它……我想保持原样。另一个问题是,当前 SysBase 是所有任务共享的一个全局指针。这与 SMP 不兼容,因为我们至少需要每个 CPU/内核单独的 SysBase。然后 Michal 建议每次您需要 SysBase 时都调用一个函数。我建议为此使用虚拟内存;例如,SysBase 指针对每个任务具有相同的虚拟地址,但对每个 CPU 指向不同的物理地址。
exec:AllocTrap/FreeTrap 和 ETask?请参见上面正在进行的更改 kernel.resource(它可以等待 ABI>1.0 吗?)。此资源旨在收集 exec.library 的所有体系结构和 CPU 特定代码。这样,exec.library 将不需要任何体系结构特定的代码,而是在需要体系结构特定信息或操作时使用对 kernel.resource 函数的调用。此更改可以延迟到 ABI V1.0 之后,因为 exec.library 已修复;但直接使用 kernel.resource 的程序将与 ABI 的下一次迭代不兼容。
dos.library 兼容性 将所有内容切换到 DOS 数据包并删除当前基于设备的系统。这在邮件列表中已经讨论了很多。当前的 AROS 实现使用设备进行消息传递,而经典系统使用端口和 DOS 数据包。后者被许多人认为是 AmigaOS 上的一个 hack,没有完全遵循 OS 内部结构的其余部分。这也是 AROS 替代方案首先开发的原因。但很明显,我们无论如何都必须实现 DOS 数据包接口,因此问题变成了我们是否应该在 AROS 中同时拥有两个替代实现。一开始,我也强烈反对 DOS 数据包,主要是因为设备接口允许在调用者的任务上运行一些代码,从而避免任务切换以减少吞吐量。使用 AROS 端口的 PA_CALL 和 PA_FASTCALL 功能可以实现相同的功能。最后得出的结论是,您可以使用设备接口完成的所有操作也可以在端口接口中完成,反之亦然,并且并排存在两个系统会导致膨胀。在当前的实现中,文件句柄和锁是相同的,在切换到 DOS 数据包接口时也应该更改这一点。
标准 C 函数的问题在于编译器在调用时可能不知道它需要设置 %ebx。一些代码只包含标准 C 函数的原型而不是包含文件。此外,您必须确保可以将函数指针传递给标准 C 函数,例如
#include <stdio.h> int parser(int (*parsefunc)(FILE *)) { ''' while (parsefunc(myfile) != EOF) { ... } } int main(void) { ... parser(fgetc); ... }
这就是为什么在静态库lib中使用存根将%ebx的值设置为C库基址,它是一个全局变量(或当前库基址的某个偏移量)。问题是我需要保存%ebx的旧值,除了将%ebx设为栈指针并在该栈上压入新值并在函数调用后弹出它之外,我没有找到其他简单的方法。如果将旧值压入栈,它将被解释为返回地址或函数参数。
关于C调用约定还有一个问题:第一个参数在哪里,最后一个参数在哪里。函数参数通常通过哪个寄存器访问?第一个参数在栈中最低(在*(ULONG *)(A7 + 4)处,*(ULONG *)A7是返回地址)。下一个在*(ULONG *)(A7 + 8)处,下一个在*(ULONG *)(A7 + 12)处,依此类推。
函数参数可以通过A7(sp)或A5(fp)或sp或fp的任何其他别名来访问。这是不可预测的——优化器可以对重新排序的东西进行各种操作。
但据我所知,这并没有解决将libbase传递给使用基于栈的参数传递的C函数的问题。再次总结一下:因为我需要使用函数指针指向标准C库中的函数的功能,所以我需要在存根函数中设置libbase。在这一点上,A6中的旧值需要被保留,但没有简单快捷的方法来存储它。
另一个问题是,进入这样的XIP库时A4将如何设置?开销是多少?啊,我现在更清楚地理解你的问题了。你指的是AROS C库的特殊情况,而不是通用的struct Library *情况。
不是AROS C特有的,而是对于所有使用基于栈的参数传递的函数。这最好用于所有移植的代码,否则你只会引入只会产生调用开销的存根函数(例如,libxxx.a存根中的栈参数在寄存器中,库本身内部再次反过来)。我们为什么会有lib<foo>.a存根库?在m68k上,<proto/foo.h>应该使用内联函数,并且存根库根本不会被使用。在其他架构上也应该一样?我们为什么有存根库?因为C有函数指针。
你是否意识到gcc允许你将静态内联函数用作函数指针,对吧?
#include <stdio.h> static inline int foo(int bar) { return bar + 0x100; } void calc(int i, int (*func)(int val)) { printf("0x%x => 0x%x\n", i, func(i)); } int main(int argc, char **argv) { int i; for (i = 0; i < 10; i++) calc(i, foo); }
问题是一些代码还包含函数原型,如果函数也是静态内联的,则会报错。一些代码——特别是BSD——甚至只包含函数原型而不包含头文件,因此没有机会将其定义为静态内联。此外,对于标准C包含以及我认为还有SDL等,包含文件之间存在分离,如果你只包含一个头文件,则只会公开库接口的一部分。我们的proto包含公开库的完整接口。我认为通过内联函数来做会增加库和使用它的程序的移植工作量。
想象一下GL的情况——移植的代码不会有#include <proto/mesa.h>,但会有#include <GL/gl.h>。makefile也将有-lGL,因此libGL.a将被链接。系统目前的设置方式使得移植使用GL或SDL(或可能来自外部世界的任何库)的东西变得非常容易。
存根函数对于AROSMesaGetProcAddress的目的(至少对于GL)也是必需的。参数是函数的名称,返回值是指向该函数的指针。但是,该函数需要满足GL本身定义的接口,因此它不能是“Amiga库函数”,因为这样的函数也以libbase作为参数。它需要是一个来自存根的函数,该函数将调用带有传递的库基址的库函数。
示例
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = AROSMesaGetProcAddress("glBindVertexArray");
glBindVertexArray(10);
这将调用存根库中的glBindVertexArray函数,然后该函数将调用Mesa_glBindVertexArray(10, MesaBase)。(至少现在是这样工作的)。
为什么不让C库像其他任何库一样,在arosc.conf中列出函数,并使用libbase?AROSC libbase可以兼作pid。这将消除arosc.library的“特殊性”。
AROSCBase将由-lauto初始化代码打开,就像任何其他库一样。所有这些都已实现,这不是问题。(顺便说一句,自动打开不会由-lauto完成,而是由-larosc或uselibs=arosc完成;例如,它应该如何工作:))。
问题是在进入函数时设置库基址。由于需要支持函数指针(如另一封邮件中所述),编译器不知道它需要为函数设置libbase。这就是为什么我在存根函数中设置libbase的原因。问题是我需要保留旧的libbase值,而且没有快速的方法来存储旧值。这就是我实现第二个栈的原因,这样我就可以简单地压入新的libbase值。
???arosc.library没有静态版本这使得构建arosc非常复杂,并且所有程序都应该能够使用共享版本。我认为目前没有任何程序使用库的静态版本。???删除librom.a我将arosc.library分成三个部分:一个可以放在ROM中的标准C子集作为第一个库,因此不需要dos.library。然后,这应该取代librom的所有用法。一个标准C实现,stdio作为Amiga OS文件句柄的轻量级包装器完整的POSIX实现;可能还包括一些BSD函数。然后,这将提供一个(几乎)完整的POSIX实现。此库应该是可选的,真正的Amiga程序不应该需要此库。
变长参数处理(stdarg.h、va_arg、SLOWSTACK_ARGS、...)目前在邮件列表中被广泛讨论,但我自己没有时间深入研究。不同提案的摘要以及一些示例代码以查看对实际代码的影响将非常有用。我的建议是切换到adtools gcc编译器的startvalinear&co。这将使用与OS4相同的解决方案。优点是只需要对代码进行有限的更改即可适应它。缺点是必须使用adtools gcc来编译AROS。
oop.library优化(它可以等到ABI>1.0吗?)我认为当前的oop.library和hidds仍然不是最佳的,需要进一步调查。我认为这项工作可以等到ABI V1.0之后,并在ABI V1.0中提到直接使用oop.library或hidds的程序将与ABI V2.0不兼容。
libcall.h/cpu.h清理(它可以等到ABI>1.0吗?)在我看来,这些文件中积累了一些废弃的东西,它们可以很好地讨论哪些东西可以删除,哪些东西可以组合,然后进行适当的文档记录。这可能不会影响ABI,因此可以延迟,但对这些文件进行更改可能会导致源代码兼容性问题。例如,如果程序依赖于某些特定功能并且需要进行调整,则它们将无法再使用新版本进行编译,但使用旧版本编译的程序将继续在AROS上运行。
如何扩展OS3.x共享库?在哪里放置AROS扩展而不会影响与未来版本的MOS和/或AOS的兼容性?
在将这些补丁应用到主干之前,在邮件列表中讨论和讨论这些补丁。可能改进或重写其中的一部分。
编写(i386)ABI参考手册并将其发布到网上。这将涉及在邮件列表中进行大量讨论以确定V1.0版本的几个API。在编写此手册的过程中,会出现很多尚未考虑过的事情,因此只有在完成此任务后才能发布ABI V1和AROS 1.0。
其中一项重要的讨论是明确编译器处理什么以及AROS本身的ABI定义什么。目前,这条界限非常模糊。
对于库中的每个函数,都会在libfoo.a静态链接库中生成相应的存根函数。在i386上,此函数唯一执行的操作是设置libbase并跳转到正确的地址。为了能够在我退出调用的lib函数时恢复以前的libbase,我实现了%ebx作为第二个栈指针。这样,我可以在设置新的libbase之前将当前的libbase压入此栈。函数返回后,可以弹出栈以返回到调用之后的先前栈。(我实现了这个栈以与普通栈相反的方向增长,并且对于一个新的任务,它被初始化为栈的末端;这意味着在开始时SP指向栈的顶部,%ebx指向底部,并且两个指针都开始向彼此增长)。
对于普通栈,将来可能可以使用MMU检测栈溢出,甚至自动扩展栈。但是,如果栈区域实际上由两个朝相反方向增长的栈组成,我不确定是否有好的方法来检测它们何时在中间相遇。
然后,您可以使栈指针指向两个不同的内存页;没有任何东西强制它们指向相同的栈。但是对于当前的实现——其中栈通常由用户代码分配——当前的解决方案是最兼容的。
并且由于这仅适用于arosc应用程序,因此它不应影响BCPL和旧版AOS应用程序。
我认为最终我们可以避免第二个栈,如果库的代码以特殊方式编译。如果函数参数永远不会通过栈指针访问,而是始终通过帧/参数指针访问,我们可以设置帧/参数指针使其指向函数参数,并将旧的libbase压入普通栈,然后跳转到普通函数内部(在帧指针设置之后)。如果我们能让它工作,我更倾向于它。不再需要为i386上的系统保留%ebx;只有在库内部,它才会是一个包含libbase的固定寄存器。我认为在m68k上它可以用相同的方式工作,但现在使用A6。
寄存器有两个钉住的方法:一种是永远不允许分配它,另一种是我之前描述的,使用自定义调用约定将libbase作为“this”指针传递,就像C++一样。后者通常更可取,因为编译器有时可以将libbase塞入堆栈并在高压寄存器加载情况下稍后检索它。现在这可能是个好主意 - 利用C++编译器。嗯。是否可以深入研究一下,看看我们能否滥用C++编译器来生成我们想要的代码?它可能需要静态类数据(即类foo { static int val; }),命名空间操作以及其他一些疯狂的事情。
这是我的疯狂原型。实际上可以编译和运行,并且似乎生成了正确的C接口存根。
/****** Library definition *********/ struct fooBase { int a; int b; }; /****** Library C++ prototype ******/ class fooBaseClass { private: static struct fooBase base; public: static int Open(void); static int DoSomething(int a); static int DoSomethingElse(int b); }; /****** 'C++' implementation ***********/ struct fooBase fooBaseClass::base = {}; int fooBaseClass::Open(void) { base.a = 0; base.b = 1; return 1; } int fooBaseClass::DoSomething(int new_a) { int old_a = base.a; base.a = new_a; return old_a; } int fooBaseClass::DoSomethingElse(int new_b) { int old_b = base.b; base.b = new_b; return old_b; } /***** 'C' interface ***************/ extern "C" { int OpenFoo(void); int DoSomething(int a); int DoSomethingElse(int b); }; int OpenFoo(void) { return fooBaseClass::Open(); } int DoSomething(int a) { return fooBaseClass::DoSomething(a); } int DoSomethingElse(int b) { return fooBaseClass::DoSomethingElse(b); } /******** test app ************/ #include <stdio.h> int main(int argc, char **argv) { if (!OpenFoo()) { printf("OpenFoo(): Failed\n"); return 0; } DoSomething(100); DoSomethingElse(101); printf("DoSomething: %d\n", DoSomething(0)); printf("DoSomethingElse: %d\n", DoSomethingElse(0)); return 0; }
由于库结构无论如何都会使用自定义调用约定,因此它也不需要使用C调用约定。问题在于可变参数。如果您拥有比寄存器中所能容纳的更多的参数,我们将需要某种方法来指定调用约定。.a存根可以包含一个小的子例程,该子例程对其varargs输入使用C调用约定,只要它使用替代的libcall调用约定调用子例程的主体即可。我们可以创建一个始终进行寄存器加载的libcall约定,以及另一个约定,该约定可能在%ECX或其他某个地方使用参数计数器,用于库的可变参数调用约定吗?
当前的LLVM调用约定规定,C调用约定支持可变参数,而Fastcall调用约定不支持,但使用尽可能多的寄存器。对于尾递归调用,Fastcall是首选,因为编译器可以将其转换为迭代。在许多情况下,C调用约定是默认的,但如果函数不使用可变参数,编译器有时可以将其优化为Fastcall。
我将我关于面向对象库格式的想法传达给了Chris Handley,供PortablE编译器使用。如果我在我的发件箱中翻找,我可能仍然有一些电子邮件。这是我找到的面向对象库基类和接口继承的文本图
-------------------------------- interface names (stored as EStrings) -------------------------------- interface offsets to initialize hash table -------------------------------- hash table (array aligned to pointer size boundary) -------------------------------- globals (varying number and size) -------------------------------- size of hash table (UINT containing number of array indexes in hash table) -------------------------------- pointer to hash table (to allow the global space to grow) -------------------------------- parent library handle -------------------------------- library structure ====================== standard jump table entries for library (including init and expunge, etc.) -------------------------------- interface1 jump table entries -------------------------------- interface 2 jump table entries -------------------------------- ... -------------------------------- the actual code
=号创建的双线表示库句柄指向的地址,其上方是全局变量,下方是跳转表。
据我所知,POSIX子系统将与其他元素分离。我想问一下POSIX文件系统将如何处理。恕我直言,ixemul.library中使用的方法很糟糕。该库实际上使用了自己的内部文件系统。这包括/dev模拟。如果我们在DOS级别获得DEV:处理程序会怎样?我认为这会更好,因为
- 这与系统更好地集成,每个原生程序都可以根据需要访问此文件系统。
- 由于这是完整的文件系统,因此所有文件系统调用都按预期工作,而不仅仅是open()和close()。
您可以正常浏览它,例如chdir("/dev"); open("null")也会工作。
- 无需单独的ZERO:处理程序,因为它将被DEV:zero替换。
- 我们还获得了DEV:null、DEV:urandom等。
- 我们可以为磁盘获取DEV:sdXXX条目。
- 我们可以通过引入专用的IOCTL数据包来实现完整的UNIX IOCTL集。这将使
移植UNIX程序变得非常简单。我反对这样做。我们不应该用POSIX/Linux的兼容性内容污染Amiga方面的东西。恕我直言,在POSIX兼容的C库中执行这些操作是更好的位置。我喜欢cygwin的处理方式:它允许在cygwin文件名空间中拥有挂载点,这些挂载点不需要与DOS/Win文件名空间对应。我不确定,我现在调用lib arosuxc.library,但如果有人想移植ixemul,我们可能不再需要这个库了。
如果不是,我将继续将arosc代码移动到arosuxc。我认为AROS POSIX实现和ixemul之间的一个主要区别在于,前者将stdio包装在dos.library调用(如Read/Write/Seek等)周围,而后者直接使用DOS数据包。我不确定我是否喜欢使用DOS数据包,但它可能是与UNIX/Linux良好兼容所必需的。据我所知,对于ixemul,其文件名空间也直接与Amiga名称空间链接。
恕我直言,ixemul过于重量级。也许事情可以简化。顺便说一句,如果我们设法在性能上击败ixemul,我们的项目可能会成为跨平台项目。也许我们应该给它换个名字,比如posix.library?此外,“arosuxc.library”听起来很刺耳,因为它包含“sux”。:)
我认为AROS POSIX实现和ixemul之间的一个主要区别在于,前者将stdio包装在dos.library调用(如Read/Write/Seek等)周围,而后者直接使用DOS数据包。我不确定我是否喜欢使用DOS数据包,但它可能是与UNIX/Linux良好兼容所必需的。我想这与select()的实现有关。
据我所知,对于ixemul,其文件名空间也直接与Amiga名称空间链接。这与当前的arosc相同。第一个组件被视为卷名。因此/usr变为usr:,依此类推。普通的"/"提供虚拟目录列表卷名和分配。顺便说一句,恕我直言,ixemul的虚拟文件系统需要重新设计。例如,您无法cd /dev并列出条目。当然,这些条目的stat()不会给出预期结果(主/次编号等)。/proc完全缺失(尽管它可能很有用)。有一天我想过重写整个东西,但这实际上意味着分叉开发,因为目前ixemul.library被MorphOS团队作为私有项目获取,并且无法加入开发。因此,无论如何,我相信分叉意味着更改名称以防止将来发生冲突。
另一个问题是,现在findutils和coreutils的配置脚本认为我们有getmnt函数:| 是的,其他人的做法就是这样,他们使用各种其他函数来获取有关已挂载文件系统的信息。在我们需要它们用于其他内容之前,我不想实现它们。
通过研究findutils/gnulib/lib/mountlist.c,我找到的唯一其他替代方案是struct statfs(compiler/clib/include/sys/mount.h)中的另一个字段,名为f_fstypename,而我们目前没有这个字段。因此,添加它可能会破坏二进制兼容性,因此我将将其转移到ABI v1切换,并暂时恢复使用mnt_names数组。
在ABI V1中,librom.a消失了。arosstdc.library中有一个库可供所有模块使用。它将作为最先初始化的模块之一进行初始化。因此,i/o函数也已移动到arosstdcdos.library,这是一个基于adisk[检查拼写]的模块。
您如何处理ctype.h系列函数?我希望看到一个仅限LANG=C的精简集选项。
目前,arosstdc.library中的字符串函数仅支持“C”区域设置。这也使我能够将大多数字符串C函数标记为不需要C libbase,从而减少了调用其中一个函数的开销。担心将所有C函数放在共享库中带来的开销的人,以后仍然可以在头文件中提供这些函数的内联版本,或者在编译器为gcc时使用__builtin_变体。
我认为我们需要更深入地讨论C库的区域设置与Amigas locale.library以及不同的字符编码之间的交互……
并且我了解了C和POSIX库内部的复杂情况,例如vfork、函数指针等。在短时间内,我成为了一名git高级用户,我的补丁现在是一个很好的小提交树,定期在本地重新基于主干。这使我能够有效地处理补丁;如果我将其提交到主干,则部分概述将丢失。这就是为什么我想将此补丁提升到每个cpu都有已知解决方案的级别,然后再提交它,并让您这些cpu专家做自己的事情。例如,我针对i386的-ffixed-ebx更改似乎破坏了本机vesa驱动程序,但我不会修复它,因为我不是本机专家也不是vesa专家。
但是据我所知,这并没有解决将libbase传递给使用基于堆栈的参数传递的C函数的问题。再次总结一下:由于我需要使用函数指针指向标准C库中的函数的功能,因此需要在存根函数中设置libbase。此时,需要保留A6中的旧值,但没有简单快捷的方法来存储它。您正在谈论AROS C库的特殊情况,而不是一般的struct Library *情况。为什么不将C库像任何其他库一样,在arosc.conf中列出函数,以及libbase?AROSC libbase可以兼作pid。这将消除arosc.library的“特殊性”。AROSCBase将由-lauto init代码打开,就像任何其他库一样。
由于AOS没有扩展的任务结构,因此此代码设置acpd = NULL->iet_acpd修复arosc不使用任务结构中的私有字段。您可以使用AVL树进行关联(OS3.5+)。或者在arosc中静态地复制这些函数。
这在ABI V1树中完成。在那里,它像任何其他库一样使用每个任务的libbase。后者使用AVL树实现。但在ABI V1中,仅在OpenLibrary期间需要在AVL树中查找,因为libbase通过%ebx寄存器传递。
还要记住,arosc.library依赖于一些其他AROS扩展,例如NewAddTask()和NewStackSwap()。它会与我为ABI V1所做的更改冲突。我将用AROS_FLAVOUR_BINCOMPAT括起我的arosc更改 - 在您合并ABI V1时只需删除它们,因为它们将不再需要。
也许这是一个愚蠢的想法,但我很好奇是否可以使OpenLibrary支持使用内部avl列表库基地址,用于支持它们的task/库 - 这样外部库就可以为特定task打开正确的库基地址(例如,mesa/egl/glu/ect共享相同的库基地址,而无需为每个task实现自己的列表)?
在ABI V1中,lib .conf文件中有一个peridbase选项(默认情况下,它为每个task返回一个唯一的libbase,其中task定义为task指针和进程返回地址的唯一组合)。在合并到主干之前,我可能会决定更改选项名称。在我看来,决定何时创建新的libbase不是exec的任务;它必须在库的OpenLib(例如,LVO == 1)函数中决定。
很久以前,我承诺总结一些关于ABI v1待办事项的信息。这是第一部分。这是对exec.library LVO表所需修复的分析。目标是使AROS函数与AmigaOS 3.9以及可能与MorphOS二进制兼容。这包括(重新)移动与MorphOS冲突的LVO。
但我试图为POSIX.1-2008中定义的所有函数保留LVO,因此添加POSIX函数将不会那么难以处理。
在下面,您将找到来自AROS exec.conf文件以及AmigaOS 3.9和MorphOS v2.5 exec_lib.fd文件中的片段,其中包含编号的LVO。我删除了原始的AmigaOS v3.1函数,以使列表更短。
AROS(当前)
131 ULONG ObtainQuickVector(APTR interruptCode) (A0) 132 .skip 2 # MorphOS: NewSetFunction(), NewCreateLibrary() 134 IPTR NewStackSwap(struct StackSwapStruct *newStack, APTR function, struct StackSwapArgs *args) (A0, A1, A2) 135 APTR TaggedOpenLibrary(LONG tag) (D0) 136 ULONG ReadGayle() () 137 STRPTR VNewRawDoFmt(CONST_STRPTR FormatString, VOID_FUNC PutChProc, APTR PutChData, va_list VaListStream) (A0, A2, A3, A1) 138 .skip 1 # MorphOS: CacheFlushDataArea() 139 struct AVLNode *AVL_AddNode(struct AVLNode **root, struct AVLNode *node, AVLNODECOMP func) (A0, A1, A2) 140 struct AVLNode *AVL_RemNodeByAddress(struct AVLNode **root, struct AVLNode *node) (A0, A1) 141 struct AVLNode *AVL_RemNodeByKey(struct AVLNode **root, AVLKey key, AVLKEYCOMP func) (A0, A1, A2) 142 struct AVLNode *AVL_FindNode(const struct AVLNode *root, AVLKey key, AVLKEYCOMP func) (A0, A1, A2) 143 struct AVLNode *AVL_FindPrevNodeByAddress(const struct AVLNode *node) (A0) 144 struct AVLNode *AVL_FindPrevNodeByKey(const struct AVLNode *root, AVLKey key, AVLKEYCOMP func) (A0, A1, A2) 145 struct AVLNode *AVL_FindNextNodeByAddress(const struct AVLNode *node) (A0) 146 struct AVLNode *AVL_FindNextNodeByKey(const struct AVLNode *node, AVLKey key, AVLKEYCOMP func) (A0, A1, A2) 147 struct AVLNode *AVL_FindFirstNode(const struct AVLNode *root) (A0) 148 struct AVLNode *AVL_FindLastNode(const struct AVLNode *root) (A0) 149 APTR AllocVecPooled(APTR pool, ULONG size) (D0, D1) 150 void FreeVecPooled(APTR pool, APTR memory) (D0, D1) 151 BOOL NewAllocEntry(struct MemList *entry, struct MemList **return_entry, ULONG *return_flags) (A0, A1, D0) 152 APTR NewAddTask(struct Task *task, APTR initialPC, APTR finalPC, struct TagItem *tagList) (A1, A2, A3, A4) 153 .skip 14 167 BOOL AddResetCallback(struct Interrupt *resetCallback) (A0) 168 void RemResetCallback(struct Interrupt *resetCallback) (A0) 169 .skip 2 # MorphOS: private9(), private10() 171 .skip 2 # MorphOS: DumpTaskState(), AddExecNotifyType() 173 ULONG ShutdownA(ULONG action) (D0) # MorphOS functions follow: # private11() # AvailPool() # private12() # PutMsgHead() # NewGetTaskPIDAttrsA() # NewSetTaskPIDAttrsA() ##end functionlist
相反,68k AROS可以在与OS3.9兼容的LVO中拥有AVL函数,而#?VecPooled()函数可以在OS3.9未使用的LVO中或在amiga.lib中。为了(某些)简单起见,其他架构应遵循68k或PPC LVO,而不是使用第三套。
AmigaOS 3.9
131 ObtainQuickVector(interruptCode)(a0) ##private 132 execPrivate14()() 133 execPrivate15()() 134 execPrivate16()() 135 execPrivate17()() 136 execPrivate18()() 137 execPrivate19()() ##public *--- functions in V45 or higher --- *------ Finally the list functions are complete 138 NewMinList(minlist)(a0) ##private 139 execPrivate20()() 140 execPrivate21()() 141 execPrivate22()() ##public *------ New AVL tree support for V45. Yes, this is intentionally part of Exec! 142 AVL_AddNode(root,node,func)(a0/a1/a2) 143 AVL_RemNodeByAddress(root,node)(a0/a1) 144 AVL_RemNodeByKey(root,key,func)(a0/a1/a2) 145 AVL_FindNode(root,key,func)(a0/a1/a2) 146 AVL_FindPrevNodeByAddress(node)(a0) 147 AVL_FindPrevNodeByKey(root,key,func)(a0/a1/a2) 148 AVL_FindNextNodeByAddress(node)(a0) 149 AVL_FindNextNodeByKey(root,key,func)(a0/a1/a2) 150 AVL_FindFirstNode(root)(a0) 151 AVL_FindLastNode(root)(a0) ##private 152 *--- (10 function slots reserved here) --- ##bias 972 ##end
据推测,这意味着调用这些函数的MorphOS二进制文件在PPC AROS下将无法工作。或者,如何将这些函数保留在PPC AROS的与MorphOS兼容的LVO中,并将AVL函数放在MorphOS未使用的LVO中或在amiga.lib中。
MorphOS
131 ObtainQuickVector(interruptCode)(a0) 132 NewSetFunction(library,function,offset,tags)(a0,a1,d0,a2) 133 NewCreateLibrary(tags)(a0) 134 NewPPCStackSwap(newStack,function,args)(a0,a1,a2) 135 TaggedOpenLibrary(LibTag)(d0) 136 ReadGayle()() 137 VNewRawDoFmt(FmtString,PutChProc,PutChData,args)(base,sysv) 138 CacheFlushDataArea(Address,Size)(a0,d0) 139 CacheInvalidInstArea(Address,Size)(a0,d0) 140 CacheInvalidDataArea(Address,Size)(a0,d0) 141 CacheFlushDataInstArea(Address,Size)(a0,d0) 142 CacheTrashCacheArea(Address,Size)(a0,d0) 143 AllocTaskPooled(Size)(d0) 144 FreeTaskPooled(Address,Size)(a1,d0) 145 AllocVecTaskPooled(Size)(d0) 146 FreeVecTaskPooled(Address)(a1) 147 FlushPool(poolHeader)(a0) 148 FlushTaskPool()() 149 AllocVecPooled(poolHeader,memSize)(a0,d0) 150 FreeVecPooled(poolHeader,memory)(a0/a1) 151 NewGetSystemAttrsA(Data,DataSize,Type,Tags)(a0,d0,d1,a1) 152 NewSetSystemAttrsA(Data,DataSize,Type,Tags)(a0,d0,d1,a1) 153 NewCreateTaskA(Tags)(a0) 154 NewRawDoFmt(FmtString,PutChProc,PutChData,...)(base,sysv) 155 AllocateAligned(memHeader,byteSize,alignSize,alignOffset)(base,sysv) 156 AllocMemAligned(byteSize,attributes,alignSize,alignOffset)(base,sysv) 157 AllocVecAligned(byteSize,attributes,alignSize,alignOffset)(base,sysv) 158 AddExecNotify(hook)(base,sysv) 159 RemExecNotify(hook)(base,sysv) 160 FindExecNode(type,name)(d0/a0) 161 AddExecNodeA(innode,Tags)(a0/a1) 162 AllocVecDMA(byteSize,requirements)(d0/d1) 163 FreeVecDMA(memoryBlock)(a1) 164 AllocPooledAligned(poolHeader,byteSize,alignSize,alignOffset)(base,sysv) 165 AddResident(resident)(base,sysv) 166 FindTaskByPID(processID)(base,sysv) ##private 167 private7()() 168 private8()() 169 private9()() 170 private10()() ##public 171 DumpTaskState(task)(a0) 172 AddExecNotifyType(hook,type)(base,sysv) 173 ShutdownA(TagItems)(base,sysv) ##private 174 private11()() ##public 175 AvailPool(poolHeader,flags)(base,sysv) ##private 176 private12()() ##public 177 PutMsgHead(port,message)(base,sysv) 178 NewGetTaskPIDAttrsA(TaskPID,Data,DataSize,Type,Tags)(d0,a0,d1,d2,a1) 179 NewSetTaskPIDAttrsA(TaskPID,Data,DataSize,Type,Tags)(d0,a0,d1,d2,a1) ##end
可以看出以下问题:1. AVL树函数与AmigaOS 3.9都不兼容。实际上,它们本应是兼容的,但AVL支持作者放错了位置。2. AllocVecPooled()和FreeVecPooled()与MorphOS不兼容。它们也与AmigaOS3.9 AVL函数不兼容,在MorphOS中它们占据了它们的偏移量;并且MorphOS中的AVL树函数被移动到单独的btree.library(它也提供红黑树)。3. AROS特有的NewAddTask()和NewAllocEntry()函数占据了另一个MorphOS函数拥有的LVO。
我解决所有这些问题的建议:1. 将AVL函数移动到与AmigaOS 3.9兼容的偏移量(142-151)。有一个强烈的理由将它们保留在exec.library中,因为它们对于构建关联数组(例如GfxAssociate()所做的那样)很有用,而且我将在新的受保护内存分配器中使用它们。在AROS应用程序中,AVL函数已经通过使用AVL树将基地址与task关联的库中使用每个打开者的基地址而变得流行起来。我不知道AVL树函数在AmigaOS应用程序中有多流行。2. 将AllocVecPooled()和FreeVecPooled()移动到libamiga.a。它们足够小且简单,可以做到这一点。3. 删除NewAddTask(),其功能由MorphOS NewCreateTaskA()函数涵盖,该函数使用起来简单得多。4. NewAllocEntry()有待进一步讨论。首先,我不喜欢它的声明(它可以返回struct MemList *而不是BOOL)。其次,也许我们可以将其功能容纳到现有的AllocEntry()中。第三种方案是将其移动到AmigaOS和MorphOS都保留的某个LVO(例如169)。
还有其他意见吗?
其他库将遵循(层、直觉、图形等)。
arch/common用于难以确定属于哪个CPU和/或架构的驱动程序:例如,使用PCI API的图形驱动程序可以在托管Linux内部以及在PPC原生上运行。然后它是与架构无关的代码,实际上它应该在arch之外。目前它们位于workbench/devs/drivers中。可以讨论,但看起来这仅仅是使用到特定位置的问题。至少没有人改变过这一点。
这与程序大小无关。问题是>存储库中的代码经常被程序员作为开始>新项目的起点。这样,坏习惯就会蔓延。
可以争辩说,手动执行所有操作是一种坏习惯。AmigaOS纯程序总是这样做,因为没有其他方法。移植的代码也这样做。
过去花费了大量时间来消除这些经常充满错误的手动打开库的操作,因为异常子句不在公共代码路径中。看到这段代码被重新添加感到恼火。仅仅是为了小的CLI工具。顺便说一句,二进制文件大小甚至更小。
在ABI V1中,我确实考虑实现一个编译开关,允许程序驻留而无需执行任何操作。为什么等待?我们现在就可以拥有可驻留的CLI实用程序。这难道不实用吗?顺便说一句,想总体介绍一下ABI v1。我没有时间写一篇关于这方面的文章,但我尝试将ET_EXEC类型的文件作为AROS可执行文件实现。我得出的结论是这是不可行的。由于静态地址分配,加载它们变得更加困难。实现一个生成可重定位文件的BFD后端,但执行最终链接步骤(使用-r省略)要容易得多。至于-fPIC,它没有任何改进。对于非UNIX系统,GOT是开销,仅此而已。产生的代码比大型代码模型慢且不小。我提交了一个补丁到AROS gcc,它告诉它在x86-64上默认使用大型代码模型。
ET_EXEC不适用于AROS,因为它是非可重定位的地址空间快照。是的,我们实际上可以重新定位它,前提是我们保留输出中的重定位(使用-q选项),在这种情况下,重定位甚至更简单(将固定偏移量添加到所有绝对地址),但还有另一个问题:段对齐。ELF建议不同的段(代码和数据)具有不同的内存访问权限。代码是只读的,数据不可执行。这意味着这些段与内存页面边界对齐。为了匹配不同的页面大小(4KB、8KB、16KB等),链接器使用“通用页面大小”,即64KB。这意味着文件中的代码和数据之间有64KB的空闲空间。它不占用磁盘空间,因为它编码在段偏移量中。它在UNIX上也不占用内存空间(其中地址空间是虚拟的,它只是缺少那部分),但如果加载到AROS上,它将占用这64KB,其中内存具有1:1映射。是的,我们可以缩短这个大小,例如到4KB,但我们可能会遇到问题,尤其是在托管AROS上?主机操作系统是否可以使用大于4KB的页面?如果是这样,这意味着我们无法在这些操作系统上正确工作。可以通过逐段分割文件来绕过此问题,就像现在一样。但是,在这种情况下,ET_EXEC比ET_REL更难以处理,并且没有优于当前加载器的优势。它更难处理,因为对于ET_REL,隐式加法被破坏,我们需要重新计算它们。我在这里描述的内容意味着在不久的将来,AROS将具有内存保护。我知道我和Michal将如何实现它,但它还没有准备好。我测试了另一种方法:我为ld链接器实现了一个后端,它生成可重定位文件作为输出。它运行良好,它甚至成功地构建了带有-fPIC的二进制文件。这是在x86-64上使用小型PIC代码模型进行实验时完成的。很抱歉它没有存活下来。在发现PIC对AROS没有真正优势后,我将其删除了(因为它旨在共享映射在不同地址空间中的相同代码,减轻x86-64寻址限制是一个纯粹的副作用,并且效率低于使用大型代码模型)。我认为这项工作没有必要,并且没有考虑未来。以下是它所做工作的总结:1. 后端作为新的模拟(-melf_aros_x86_64)实现,默认情况下使用它。可以通过在命令行上指定-melf_x86_64来构建ET_EXEC文件。这可以消除对原生端口的$(KERNEL_CC)的需求(无需再使用一个工具链)。2. -fPIC有效,生成工作二进制文件(但它们没有优势)。我的目标不是实现基于基地址的数据,因此我没有进一步开发它,缺乏必要的知识。3. 当发现未定义符号时,ld会说明它从哪里引用(照常)。这比collect-aros中的“存在未定义符号”要好得多。4. collect-aros的工作降级为收集符号集。它不需要提供其他选项,如-r。顺便说一句,-r没有损坏,它有效,生成部分链接的文件。5. 生成的二进制文件由ELFOSABI_AROS标记。ABI版本字段也可以填写(这就是我们想要的)。
因此,如果您喜欢此结果,并且同意我关于保留可重定位二进制格式的观点,我可以重新实现我的x86-64后端(以比以前稍微好一点的架构方式)并提供作为参考实现。它需要移植到i386、PPC和ARM(新的启蒙collect-aros将无法与旧编译器一起使用,我认为保留两个collect-aros版本是多余的)。
AmigaOS v4可执行文件。是的,它们是ET_EXEC。它们有64KB的间隙。因此AmigaOS v4要么使用虚拟内存(这很可能),要么浪费每个加载的可执行文件的64KB RAM。理论上我们可以实现类似的虚拟内存系统,但是:a)我认为它不适合托管环境b)这将强制使用虚拟内存,并且m68k二进制兼容性将受到影响。没有它,系统将无法工作。c)为什么我们应该重新实现AmigaOS v4内部功能只是为了使用ET_EXEC格式?ET_REL就足够了,我成功地克服了小型链接器的限制。
作为m68k维护人员之一,我说'ET_REL'很棒!保留ET_REL!
只要可执行文件和对象(.o)文件之间存在区别,我不介意它是ET_EXEC还是ET_REL。此外,符号名称字符串不必位于可执行文件中,它们是否与您的新链接器一起存在。
我认为计划是所有新开发现在都将进入ABIv1,而不仅仅是与ABI相关的内容(一些内容根据各个开发人员的意愿回传到v0)。我们确实同意ABI V1将成为主要的开发分支,我认为这也意味着在ABI V0上完成的所有内容都必须合并到ABI V1中。我不确定它是否必须意味着在将其放入ABI V0分支之前,一切都必须先提交到主干。
我预计主干分支未来一段时间会比较混乱;我的一些提交可能会破坏除 i386 宿主机之外的所有平台。因此,我认为对于那些只想改进或添加驱动程序的人来说,在一个稍微稳定一些的地方工作会更好。另一方面,我确实看到,如果开发发生在 ABI_V0 分支上,那么这个分支将不会一直稳定。我两种方案都接受。
鉴于我没有执行“svn switch”或任何其他操作,现在的 nightly 构建是基于 ABIv1 还是 ABIv0?
如果没有任何更改,将构建主干分支,因此这是 ABI V1 开发分支。
当我将 nlist 的先前版本和当前版本之间的差异合并到主干时,它创建了一个 svn:mergeinfo 属性。合并总是发生在工作副本中。它需要额外的提交才能将结果带到存储库中。
最终,不应该再有针对 AROS_FLAVOUR 和 AROS_FLAVOUR_BINCOMPAT 的检查。在我看来,这是一个 ABI 问题。
我建议更改库,不要使用基于寄存器的参数传递,而只使用 C 参数传递,这样就不再需要存根,并且可以直接使用 mesa 源代码。这将是一个更大的变化。实际上,更大的问题是,向前发展创建共享库的*正确*方法是什么?在我看来,从外部代码移植的库应该能够在对源代码进行尽可能少的更改的情况下进行构建,这意味着使用 C 参数传递。
开始着手 Mesa 的工作时,我对共享库几乎一无所知,所以我只是检查了 dos/exec/graphics 的外观并复制了设计。基于寄存器的参数传递在我看来很重要,因为我认为这是为 m68k 编译共享库的要求 - 我理解正确吗?
这不是必需的;arosc.library 在 m68k 上可以正常构建。但我确实认为,对于 C 函数调用,我们能够自动将参数放入寄存器中会很好(例如,不要使用 SYSV m68k 调用约定,而是使用其他约定)。我认为,如果所有 m68k 上的函数调用 - 无论是在库中还是不在库中 - 都首先使用寄存器来传递参数,然后再使用堆栈,那将是理想的。不知道 gcc 是否支持这样。
我正在考虑两件事
- 在 nightly 构建中实现一个检查,以检查上游代码是否有更新到 AROS 供应商分支中的代码。
- 在 nightly 构建中生成 AROS 树中代码与供应商分支的 diff。将其放在网站上,以便上游开发者可以查看我们在其代码中进行了哪些更改以使其在 AROS 上运行。
但正如在另一个线程中讨论的那样,第一个目标是尽可能减少 contrib 中的代码,并尽量避免代码的 AROS 分支。
快速合并计划,但一次只开发一项功能。每个功能的截止日期为开始之日起一个月。如果无法达到截止日期,则将该功能移至分支,回滚主干,然后转到下一项功能。
例如,假设我们明天合并 DOS 数据包更改。完成期限为 2011 年 1 月 1 日。如果我们无法在所有当前维护的端口上获得可工作的 AROS,我们将将其分支出来,回滚,然后转到里程碑列表中的下一项。
在ABI v1 列表上共有 11 项。如果我们每月完成*一项*任务,为每个 ABI v1 任务设定硬性截止日期(我们需要一些人坚持这个截止日期!),并且大家共同努力完成该任务,我认为我们可以在 2011 年内完成。甚至可能更早。
专注。这就是我们需要做的。专注于手头的单一、可测试的任务。
让我们不要急于求成地进行所有更改 - 这是不可测试的,并且会导致很多挫折。一次只进行一项 ABI 更改,我们最终会实现目标。
我想要实现的目标是确保对于此开发,我们不遵循“完成时”模型。上限不应被视为“到 X 日你必须完成 100% 的所需内容”,而是“你最多到 X 日完成你能做的内容 - 确保首先选择最重要的事情”。
合并 ABI V1 后,我们将有效地拥有两条主要线路。有些人会对 -stable 进行更改,因为他们希望用户拥有这些更改。有些人会对主干进行更改,因为他们会使用 ABI V1 代码或只是想使用“未来的 AROS”。还有一些人甚至会推迟进行更改,并等待“直到 AROS 恢复正常”。人们将遇到与您现在遇到的非常类似的同步问题。这就是为什么我觉得明确说明何时“恢复正常”非常重要的原因,即使它不会完全是我们想要的样子。
您将在 -stable 还是主干上工作。
您将如何同步 -stable 和主干。
您是否会在这两条路径之间进行同步?
一种可能性是从现在开始在一个分支中进行进一步的开发:branches/abiv1/trunk-DOS 用于对破坏向后兼容性的 DOS 相关更改。m68k 的 nightly 也可能切换到此分支。在开始使用它之前,我需要先将此分支更新到最新版本。对将“主干”分支到“稳定”并合并 V1 到主干有任何异议吗?
这些方法怎么样
a) 将分支合并到主干 (rom/dos) 中,对 amiga 目标进行不兼容更改的 ifdef 处理。
b) 将分支合并到主干 (arch/amiga-m68k/dos) 中,并在 arch 中继续进行不兼容开发。
将 amiga-m68k 的开发移到分支中,将使项目休眠/无人维护,甚至在中期就变得不兼容,因为没有人会对将主干与该分支同步的额外工作感兴趣。此外,修复 AROS 由于 amiga-m68k 移植而带来的承诺将在很大程度上被取消,因为没有人会对将更改从分支移到主干的额外工作感兴趣。
我完全同意您,将 ABI V1 在主干中开发不是一个好主意,因为 i386 AROS(最常被人们使用的一个)将一直处于破坏向后兼容性的状态。然而,amiga-m68k 移植是一个完全不同的主题,因为它仍然处于开发阶段,并且每个人都期望它会被多次破坏。这就是为什么在我看来,对 i386 不好的东西对 amiga-m68k 是可以接受的。
我还认为,重复将主干合并到分支的成本(合并 + 编译 + 检查所有内容是否正常 + 修复错误)远高于一次性删除 + 修复 amiga-m68k 特定的 dos.library 的成本。
无论如何,决定权在 Toni 手中,以及他如何更容易地工作。我只是想确保在做出最终决定之前,所有可能性都被考虑到了。:)
您是否会考虑将您的工作模型更改为类似以下内容
a) 将所有非破坏性更改提交到主干。
b) 将所有破坏性更改提交到 linux-i386-abiv1 “架构”。
虽然您仍然需要根据其他人的工作修改不兼容的代码,但您不必将更改合并到兼容的文件中,更重要的是,您将使您的工作对其他开发人员可见,以便人们可以看到您使用的代码路径,并会更加注意不要过多地破坏您的工作?
- i386 的 nightly 构建将基于什么:-stable 还是主干?尚待决定,但我们甚至可以同时进行两者。
- aros.org 上将提供哪些源代码下载:-stable 还是主干?
- 其他架构的 nightly 构建将基于什么:-stable、主干还是“维护人员自行决定”?
- ABI V1 linux-i386 的当前可用性状态如何:它可以引导到 Wanderer 吗?核心/contrib 应用程序是否正在运行?它可以引导到 Wanderer,contrib 可以编译,对应用程序进行了有限的测试,但 gcc 应该能够编译一个 hello world 程序。Gallium 尚未转换为使用新的 rellibase 功能,并且由于它是本地的,因此未经测试。
- ABI V1 pc-i385 的当前可用性状态如何:它可以引导到 Wanderer 吗?核心/contrib 应用程序是否正在运行?未编译也未测试。
- 该abi页面列出了 ABI V1 的 11 个主题。目前已实现 3 个,正在进行 2 个。Staf,您能否更新其余 6 个的状态?正在进行的事情大多已经完成。下一个大型任务是 dos.library 的兼容性,但这正是引发整个讨论的原因。其余的尚未开始,我希望其他人能自愿参加其中的一些。
在我看来,在合并到主干之前,不需要完成它们。我认为大多数讨论只有在人们能够看到实际代码时才有意义。
1 how to extend OS3.x libraries 2 SysBase location 3 ABI V1 reference doc 4 varargs handling
此外,对于 i386 原生,我希望其他人进行实现。由于 arch/i386-pc 中的代码未编译也未测试,因此我希望能够使其与 ABI V1 兼容,特别是对于内联汇编等。
- 我们将如何区分 ABI V1 之前和 ABI V1 的二进制文件 - 我主要对核心组件感兴趣。也许我们可以修改所有 .conf 文件以显示常见的 major 版本号(50.0?60.0?)。这个较大的版本号可能会破坏 m68k 上的一些旧应用程序。据我所知,如果版本号与它们期望的不符,则某些程序无法启动。对于可执行文件格式,我希望切换到一个合适的 ELF 程序(尽管包含重定位数据;就像在 OS4.x 上一样),而不是我们现在使用的可重定位对象。
这些分支在存储库的 branches/ABI_V1 中可用 - trunk-DOS:用于对 DOS 进行更改。目前,它对 BSTR/BPTR 进行了更改,使其在 i386 上也基于字而不是基于字节。稍后还应在此分支中删除基于设备的文件系统。
- trunk-Misc:一些杂项更改,主要是结构字段的顺序(struct Node、mouseX、mouseY 等)。
- trunk-rellibbase:使用 %ebx 作为相对寻址的基址。这用于将 libbase 传递给库函数。它允许将 libbase 传递给使用 C 参数传递的库。它尚未实现,但它也应该能够用于生成纯二进制文件。
- trunk-genmodule_pob:扩展了 peropener 基库。现在,具有 per opener 基址的库可以在每次自身被打开时打开其他具有 per opener 基址的库。子库的 libbase 必须存储在父 libbase 中,并且使用 ..._offset 全局变量从父 libbase 中的子库访问 libbase。父库必须链接到子库的特殊链接库,例如 -lmodname_rel(例如 -larosc_rel 或 uselibs=arosc_rel)。通过此更改,应该可以拥有使用 arosc 并从使用此库的每个程序的堆中分配内存的库。此分支基于 trunk-rellibbase。
- trunk-arosc_etask:此补丁将 rellibbase 与每个任务 libbase 结合使用,以使用 %build_module 将 arosc 转换为普通的库。每个任务或每个 ID 的 libbases genmodule 代码已合并到主 trunk 中,但仅当 rellibbase 也可用时,才能用于具有 C 参数传递的函数。使用 rellibbase 也会将 ETask 的一部分移动到 arosc libbase 中。最终目的是完全消除对 ETask 的需求。此分支基于 trunk-genmodule_pob。
- trunk-aroscsplit:这是我按照列表说明拆分 arosc 的分支。目前,arosstdc.library 已从 arosc.library 中分离出来。它包含 ANSI-C 函数。下一步是将当前的 arosc.library 转换为 arosnix.library 以实现 POSIX 功能,并可能提高代码的标准合规性。此分支基于 trunk-arosc_etask。
目前仅测试了 i386 宿主版本,并且可以工作。因此,如果您希望它在其他任何地方都能工作,则首先需要修复它。
更改可能会提交到分支。当您这样做时,请通知我,因为分支中的提交已从 svn 公告列表中过滤掉。此外,我需要将更改合并到更高的分支并对其进行测试。由于我在家使用 SVK,因此我更愿意自己执行此合并以保持 svk 属性的顺序。
trunk-rellibbase 更改需要反映在编译器基础架构中,因为它将生成函数调用。此外,如果生成纯可重入代码,则将应用相同的原理。
如果您一直没有关注我的帖子,我认为 LLVM 将成为 AROS 编译器工具箱的一个很好的补充。它目前在内部使用 3 种调用约定,并允许使用几种特定于系统的约定。C 调用约定支持可变参数,并且是常见的约定。FastCall 使用所有寄存器进行调用,并使用堆栈进行溢出,类似于现在的方式,除了此调用约定不支持可变参数。Cold 调用约定更改尽可能少的寄存器,并且主要使用堆栈帧,以便不经常调用的内容不会过多地干扰调用它们的代码。Cold 调用也不支持可变参数。
除了这些调用约定之外,还允许使用特定于系统的约定。为此,我们需要一个库调用约定,以便使库的基指针及时加载以供使用。纯可重入基址相对调用约定也是如此。
此外,我还需要知道这将如何影响 AROS 的 x86_64 版本。如果它不需要更改,我实际上可能可以从那里开始。
为了做到这一点,我在 LLVM 网站上查找了以下文档:https://llvm.net.cn/releases/2.8/docs/CodeGenerator.html#x86,它简要介绍了 LLVM 上 x86 后端的特性,以及https://llvm.net.cn/releases/2.8/docs/SystemLibrary.html,它介绍了如何在 LLVM 源代码树中的类和 #ifdef 结构中包装特定于系统的代码。
由于我计划支持 ABI v1,因此我需要与 Staf 合作以了解这方面取得的进展。我需要知道的事情包括
Are the FS and GS segment pointers in use in AROS?
对于 i386 ABI V1 则不适用,因为它使托管版本变得复杂。我认为 Michal 正在为 x64_64 本地版本使用它们。
如何处理扩展(例如 AVX、SSE、MMX)?
不知道,可能在最终确定 ABI V1 之前仍需要讨论一些问题。
有这三个库
- arosstdc.library: All C99 functions except those that need dos.library - arosstdcdos.library: All C99 functions that need dos.library - arosnixc.library: POSIX functions
arosstdc.library 作为第一个模块之一包含在 ROM 中,以便我可以删除 librom.a,这会导致 arosstdcdos.library,它只能在 dos.library 之后初始化。arosstdcdos.library 仍然是基于磁盘的库。
将当前位于 compiler/mlib 中的所有数学函数移动到 arosstdc.library 中,因为它们也是 C99 的一部分。这意味着 arosstdc.library 会变得更大,可能会给 ROM 的 m68k 带来问题。鉴于 m68k 不再接受 ROM 模块中的 .bss 部分,如果它还需要在 m68k 上工作,则可能无法再将 arosstdc.library 放入 ROM 中。我可能需要维护我自己的分支,在这个分支中已经发生了这种情况并且 librom.a 被删除了。
尽管当时看起来是可行的,但现在不再需要为包含数学内容的单独基于磁盘的共享库 arosstdm.library 担心了。不过,我更喜欢前者。
那么数学内容应该包含在 arosstdc.library 中还是在单独的库中?
这意味着 exec.library 和 kernel.resource 无法再使用字符串函数,例如 strlen、memset 等。在补丁版本中,是的,因为它在 exec.library 之后立即初始化,如下面的列表所示
&Kernel_ROMTag, /* SingleTask, 127 */ &HostLib_ROMTag, /* SingleTask, 125 */ &Expansion_ROMTag, /* SingleTask, 110 */ &Exec_resident, /* SingleTask, 105 */ &AROSStdC_ROMTag, /* ColdStart, 104 */ ...
考虑将不需要 libbase 或特殊 exec.library 的函数拆分到一个单独的资源中,该资源将作为第一个初始化的模块,但我将所有内容都保留在一起。
另一种拆分方法是:- arosromc.resource:不需要 libbase 或 exec.library 的函数 - arosstdc.library:需要 libbase、exec.library、dos.library 的函数
另一种方法是仍然提供一个 mini librom.a,它_仅_可由在 AROSStdC_ROMTag 之前出现的模块使用。屏幕 C 库和大多数函数可以放在一个资源中,这就是我现在更喜欢 arosstdc.resource 作为 ROM 中第一个模块的原因。从 m68k 的角度来看,这不是一个好主意。
在最坏的情况下,任何在扩展之前运行的模块可能只有缓慢且宝贵的芯片内存可用,如果硬件只有自动配置的快速 RAM 或加速器快速 RAM 位于非标准位置(由卡的诊断引导 ROM 启用)
最大的问题案例是 Blizzard A1200 加速器(非常非常常见)。幸运的是,A3000/A4000 通常始终具有主板快速 RAM 或加速器 RAM 映射到已知的主板 RAM 地址。
所有快速 RAM 都保证在优先级 105 RTF_COLDSTART 之后可用,此时运行诊断初始化模块,它也应该是第一个 RTF_COLDSTART 模块以确保最佳兼容性。
此外,向高优先级(105 或更高)添加额外的模块或调整优先级可能会导致某些板出现问题,例如 CyberStormPPC,因为它添加了多个驻留模块并假设与 OS 模块的正确排序。
拆分保持现状,我们静态链接 exec.library 和在其之前初始化的模块中所需的 C 函数。