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 網站上查找了以下文件:http://llvm.org/releases/2.8/docs/CodeGenerator.html#x86,它簡要介紹了 LLVM 上 x86 後端的特性,以及http://llvm.org/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 函式。