Aros/开发者/文档/库/Exec
此库管理所有其他库、设备、资源、中断、应用程序和计算机的内存。要查看关系。关于此主题的另一种观点。
操作系统的核心。它提供抢占式多任务处理、内置的链表基元、消息传递基元、IO 基元等,并在整个系统中被大量重用。如果您理解 exec,您将理解系统其余部分的底层内部机制。
实际上,AmigaOS 的大部分以及 AROS 都是基于广泛使用消息传递构建的,消息传递使用信号来指示何时有新消息可用,并使用信号量来安全访问某些数据结构。
这就是 Amiga 如何处理可移植代码问题的方法 - 所有代码都是使用指向库“基础”的指针的偏移量编写的,除了地址 4 之外,没有固定地址。每个库都有一个调用表,每个条目 6 个字节(一个跳转指令),然后跳转到正确的入口点。编写自己的库很容易,前提是您拥有“标头”的正确格式。设备驱动程序“借用”了库的结构,并使用相同的概念来提供默认的 I/O 处理程序。
结构的打包。对于 OS3.x 库的 API 定义的结构,MOS 和 OS4 让这些结构以 m68k 兼容的方式打包(例如,大多数情况下在两个字节边界),而其他结构以 PPC 原生方式打包(4 字节用于 4 字节长的数据等)。我们甚至有一个解决方案通过使用 #include 来实现它,以便在 AROS 包含文件中启用此打包
#include <aros/compat_packing_on.h> /* Definition of structs with compat packing */ #include <aros/compat_packing_off.h>
这些 aros/compat_packing_on.h 和 aros/compat_packing_off.h 的内容可以取决于架构和编译器。
另一个要修复的是 LVO 表的设置方式以及用于伪造库函数的 m68k 条目的存根代码。
总而言之,ppc 架构会对所有结构使用原生打包,并为 LVO 使用指针表,而无需特殊的存根代码。ppcmos 会对需要特殊打包的结构应用特殊打包,并为共享库提供 MOS LVO 表约定。
将 SysBase 移出 4L - 这将另外提供一个完整的调试信息,用于在崩溃回溯中显示 KS 函数名称。
为了使 AROS 的 kickstart 的部分可移植,因为我们的 KS 是模块化的。似乎有三个部分
- BSP - 板级支持包。内核、exec、硬件驱动程序。
- Strap - 这部分只在托管和原生之间有所不同。它包含 bootloader.resource 和 dosboot.resource。在原生上,它还包括驻留文件系统(AFS、FFS、CDVSFS)。
- Base. 核心模块(图形、实用程序、直觉、dos 等)。这部分被认为是完全机器无关的。
目前,所有托管 kickstart 和 x86-64 原生 kickstart 都遵循此方案。它提供更舒适的代码维护。如果您想添加一些新的机器无关模块(上次是 filesystem.resource 例如),您只需在一个地方添加它,所有端口都会收到它。当您在一个端口上测试某样东西时,您可以 100% 确定任何其他端口的行为方式都相同。
其他端口(i386、m68k 和 ppc 原生)将遵循。目前,m68k-amiga 违反了此规范,因为它在 graphics.library 中包含特定于硬件的代码。
- 有关更多信息,请参见此处
当应用程序请求内存时,它可以自己分配一小部分内存,或者要求 Exec 找到满足其要求的合适内存区域。
#include <proto/exec.h> APTR AllocMem( ULONG size, ULONG flags ); void FreeMem( APTR memory, ULONG size ); /* return no status */ APTR AllocVec( ULONG size, ULONG flags ); /* remembers memory set better */ void FreeVec( APTR memory ); /* return no status */
flags MEMF_TOTAL - All available memory . MEMF_CLEAR - The allocated memory area is initialized with zeros. MEMF_LOCAL - Get memory that will not be flushed, if the computer is reset. MEMF_CHIP - Get memory that is accessible by graphics and sound chip. Required for some functions. MEMF_FAST - Get memory that is not accessible by graphics and sound chips. You should normally not set. MEMF_PUBLIC - This flag must be set, if the memory you allocate is to be accessible by other tasks. MEMF_REVERSE - If this flag is set, the order of the search for empty memory blocks is reversed. MEMF_NO_EXPUNGE - If not enough free memory is found, AROS tries to free unused memory.
Exec 提供 AllocEntry() 和 FreeEntry() 例程,以通过 MemList 在一次调用中分配多个内存块。
struct MemList { struct Node ml_Node; UWORD ml_NumEntries; /* number of entries in this struct */ struct MemEntry ml_ME[1]; /* the first entry */ };
struct MemEntry { union { ULONG meu_Reqs; /* the AllocMem requirements */ APTR meu_Addr; /* the address of this memory region */ } me_Un; ULONG me_Length; /* the length of this memory region */ };
使用这些函数分配的内存必须在使用后释放。
KickMemPtr/KickTagPtr/KickCheckSum 东西(复位证明驻留程序)现已实现并提交。忘了提,目前它会立即 InitResident() 它们。它们应该被注入 ResModules 列表(也按正确的顺序)中,这并非易事,因为 KickTags 只能在自动配置设备配置后才能处理(SysBase 或 KickMemTags 可以位于自动配置快速 RAM 中),而 ResModules 列表是一个非常烦人和愚蠢的列表。
实际上,没有 Exec 函数应该填写 Dos/IoErr()。OpenLibrary()(当被 LdDaemon 覆盖时)是一个明显的例外。AllocMem() 不会被 DOS 打补丁,也不会设置任何 DOS 错误代码。只有那些被 DOS 打补丁的 exec 函数可能会更改 IoErr(),但这只是一个副作用,这不是获取错误代码的受支持方法。
新的内存系统似乎将 struct MemHeader 中的 mh_Upper 字段视为内存区域内最顶层的有效地址,而旧的实现和 AOS 将其视为最后一个有效地址的高一个字节。
我们针对 AllocMem() 的 AutoDoc 指出返回的内存与 sizeof(struct MemChunk) 对齐,但 AmigaOS 3.x AutoDoc 只保证“返回的内存块是长字对齐的”。AmigaOS 3.x 确实返回 MemChunk 对齐的块,但我们是否在不经意间改变了 API?MemChunk 对齐是 LONG 对齐的超集(所有 MemChunk 对齐的块也是 LONG 对齐的),因此这不是问题。只有当 MemChunk 对齐不是长字对齐时才会有问题。MemChunk 是 IPTR 的倍数。以前有一组奇怪的宏,但它们现在已被简化。因此,它是 AROS_WORSTALIGN 或 sizeof(struct MemChunk)(取决于哪个更大)。struct MemChunk 故意是一个 APTR 和一个 IPTR,所以它也是 sizeof(IPTR)。请参阅 exec\memory.h 中的 MEMCHUNK_TOTAL 定义。
如果有人想将 AROS 软件移植到其他操作系统,而该操作系统只有 LONG 对齐的内存块,那么如果被移植的软件依赖于 MemChunk 对齐(应用程序程序员不应该需要知道 MemChunk 是什么),则可能会有问题。程序员有责任使其代码与它想要运行的操作系统兼容。我们可以将以下类似的短语添加到 AllocMem() 的自动文档中:'为了与其他类似 Amiga 的操作系统兼容,程序员应该只依赖于 LONG 对齐的内存块,如原始 AmigaOS 3.x 中所述。'
struct MemChunk { struct MemChunk *mc_Next; /* pointer to next chunk */ ULONG mc_Bytes; /* chunk byte size */ };
struct MemHeader { struct Node mh_Node; UWORD mh_Attributes; /* characteristics of this region */ struct MemChunk *mh_First; /* first free region */ APTR mh_Lower; /* lower memory bound */ APTR mh_Upper; /* upper memory bound+1 */ ULONG mh_Free; /* total number of free bytes */ };
MEMF_CHIP 指的是 (amiga) CHIP 集所需的内存。我认为使用它来表示图形卡芯片集所需的内存没有区别。图形内存根本不是系统内存,例如,在大多数情况下,你无法从那里执行代码。
许多程序在为一些平面图形数据分配缓冲区时会提供 MEMF_CHIP 标志。如果我们谈论的是系统友好的软件,那么这些数据就会被馈送到图形/直觉。甚至我们的 graphics.library 在 AllocBitMap() 内部也使用 CHIP 分配,以保持与 Amiga 硬件的兼容性。因此,如果我们不声明任何 RAM 为 CHIP(因此将 CHIP 作为 VRAM 指示器),那么我们就无法进行这些分配。一个替代方案是:我们将所有 RAM 声明为 CHIP,在这种情况下,我们没有问题与 AllocMem()s 的平面图形。在这种情况下,CHIP 标志纯粹是为了向后兼容。是的,需要以其他方式查询 GFX 卡内存。
在分配内存时,exec 函数不会检查所请求的字节大小是否已绕回 (ULONG)。如果有人出于某种未知原因尝试分配太多内存,并且字节大小在内部增长到超过 2^32,就会发生这种情况。分配不会失败,用户可能会完成分配,但字节大小会混乱。不过,这可能不是一个现实世界中的场景,也不是一个真正的问题......
我还想知道是否有人正在扩展内存分配例程。需要一些可以分配对齐的内存并将内存与一些边界相匹配的东西。听起来你需要一个 slab 分配器 - 可以轻松地将其编写为围绕 AllocMem() 的包装器。
a slab is a large chunk of memory, split into equal size blocks, and memory usage is mapped by a bitmap.
AmigaOS 4.x 有一个用于此的接口 - 我们可能想要实现一个 API 兼容的版本。
slab 分配器的主要优点是速度,但它们也会增加相当多的额外内存开销。因此,如果你认为你将解决内存不足问题,我怀疑你会感到惊讶。
不仅如此。slab 分配器如果用于系统对象,则效果很好,但不能真正用于任何其他用途。根据定义,它们旨在简化和加快相同大小对象的分配。
Linux(内核)中使用的伙伴分配器非常快 - 但也浪费了很多内存(我认为它甚至比 slab 分配器更糟糕)。这并不重要,因为内核没有使用大量的内存,并且它没有用作通用分配器。
嗯,伙伴分配器只是用来获取原始内存(MMU 页面),然后将其进一步分配到地址空间。一个很大的好处是,伙伴分配器总是给你与分配大小边界对齐的内存。缺点是,大小是二的幂(从 MMU 页面 - 4K 开始,是分配的最小数量)。
1. Scales nicely from few MB of managed memory up to huge amounts - I've successfully tested it with 12GB AFAIR. 2. The allocation time, including all locking, takes about 250 cpu cycles - less than 0.1 microsecond on 2.5 GHz cpu. 3. Freeing memory is slightly faster - 0.09 microsecond 4. Memory overhead for buddy itself: 0.4%
伙伴分配器本身并不需要浪费太多内存,但它相当慢。最后一个在 4GB 机器上浪费了 16MB 的 RAM......然而,减少它会使整个伙伴分配器非常慢。
以下是分配及其对齐方式和边界的排序列表,为此我需要更好的东西
Max Size Boundary Alignment Device Context Base Address Array 2048 PAGESIZE 64 Device Context 2048 PAGESIZE 64 Input Control Context 64 PAGESIZE 64 Slot Context 64 PAGESIZE 32 Endpoint Context 64 PAGESIZE 32 Stream Context 16 PAGESIZE 16 Stream Array (Linear) 1M None 16 Stream Array (Pri/Sec) 4Kb PAGESIZE 16 Transfer Ring segments 64K 64KB 16 Command Ring segments 64K 64KB 64 Event Ring segments 64K 64KB 64 Event Ring Segment Table 512K None 64 Scratchpad Buffer Array 248 PAGESIZE 64 Scratchpad Buffers PAGESIZE PAGESIZE Page
我们所有的驱动程序都需要自己对齐内存(如果需要),这可能会导致混乱。页面大小可以由用户定义,不一定与 cpu/系统相同,还是在某个地方可用?
驱动程序自行对齐内存会导致比需要更多的内存使用量。如果我要在 64kb 边界上分配 64kb 的内存,并且对齐方式为 16 字节,那么我实际上必须分配至少 128kb。
内存碎片是 AROS 的一个真正问题吗?如果不是,我很乐意使用类似 AllocVecAligned(bytesize, alignment, boundary) 的东西(从 PCI 设备可访问的内存中分配),而不是操作系统窃取它可能永远不会使用的内存。对齐的内存分配主要用于驱动程序代码,这些代码倾向于保留它们的内存并且从未释放它们(直到软启动),但我不确定......
内存碎片在 AROS 中是一个非常大的问题 - 在内存有限的系统上。它只是被大多数 AROS 端口都在具有大量内存的系统上运行的事实所掩盖。
http://www.morphos-team.net/tlsf (但以前也据我所知在 Aminet 或其他地方可用)
void *allocaligned(ULONG size, ULONG flags, ULONG align) { void *ptr = AllocMem(size + align - 1, flags & ~MEMF_CLEAR); if (ptr) { ULONG alignptr = ((ULONG) ptr + align - 1) & (ULONG) -align; Forbid(); FreeMem(ptr, size + align - 1); ptr = AllocAbs(size, (APTR) alignptr); Permit(); if (ptr && (flags & MEMF_CLEAR)) memset(ptr, 0, size); } return ptr; }
恕我直言,64kb 边界具有明确的 16 字节对齐......边界只意味着分配的内存不能跨越某个区域,并不意味着内存需要与边界的尺寸对齐。
硬件使用这些东西,因为它们不需要更新完整的内存地址寄存器,但在 64Kb 边界的情况下,它只需要 16 位,因此它偏移的内存不能跨越该边界。
在我的情况下,有一个“事件环段表”,其中包含 64 位地址(位 0-5 保留且未使用 = 64 字节对齐),指向“事件环段”,现在这些段不能跨越 64Kb 边界,尽管给出了地址的较低 16 位(减去位 0-5)。边界不是对齐。
不要部分地释放内存,因为这会“破坏/使无用”围绕原始分配的 mungwall,即使它不会,它仍然是操作系统的工作来提供合理的操作来干净地分配东西。
更愿意让所有这些都通过一个将标签作为输入的 allocmem 来处理......(已经有了 MEMF_HWALIGNED)......嗯?为什么?!为什么不选择 AllocMemTags 呢?我不太喜欢这种循环依赖关系,即 exec.library 依赖于 utility.library,而 utility.library 依赖于 exec.library......
#define MEMF_TAGGED (1L << 21) /* Additional allocation tags */ APTR AllocMem(IPTR size, flags, ....)
buff = AllocMem(680, MEMF_CLEAR | MEMF_ANY); IPTR hw_addr; hw_buff = AllocMem(1024*4, MEMF_CLEAR | MEMF_TAGGED, MEMT_Alignment, 1024, MEMT_BusDevice, pcidev, MEMT_BusDMAAddress, &hw_addr, TAG_END);
它不需要在边界上分配也不需要浏览内存列表。“prefix”是整个分配过程中必须相同的位,等同于边界。“valid_bits”组合了地址大小和对齐方式。
APTR AllocMemAligned(IPTR size, ULONG flags, UQUAD prefix, UQUAD valid_bits) { APTR mem; /* Check if a full 64-bit pointer is allowed */ if((valid_bits & 0x8000000000000000LL) == 0) { flags |= MEMF_31BIT; valid_bits &= 0x7fffffffLL; } /* Allocate a block large enough to definitely include a suitable sub-block */ mem = AllocMem((size << 1) + ~valid_bits, flags); /* Shift starting position of sub-block to fit alignment etc.; allocate sub-block and give back the rest */ if(mem != NULL) { Forbid(); FreeVec(mem); if((mem & ~valid_bits) != 0) mem = (mem & valid_bits | ~valid_bits) + 1; if((mem & prefix) != (mem + size & prefix)) mem = mem + size & prefix; mem = AllocAbs(size, mem); Permit(); } return mem; }
某种私有的 AllocVecAligned 函数已经编写,但恕我直言,驱动程序代码自己去浏览内存列表是一件坏事,但在目前这是唯一可行的选择(尝试在边界上分配会导致所有适合边界的内存区域可能会在内存非常有限的系统上用尽)?使用 #?Vec#? 函数而不是 #?Mem#? 的问题在于,必须存储大小可能会破坏对齐方式,从而浪费空间。就像 AllocVec() 一样,但恕我直言,#?Mem#? 函数对于分配 DMA 块等并不麻烦,这些块通常是固定大小的。
Nouveau 驱动程序将添加 gfx 卡内存到内存列表中,并且该 (gfx 卡内存) 区域在我的 PCI(-e) 设备不可访问,必须跳过,只使用真正的系统内存。
alignOffset 的原因是什么?它用于你需要在内存中存储对齐的东西,但还需要在对齐区域开始之前存储附加数据的情况。
APTR AllocVecAligned(ULONG bytesize, ULONG requirements, ULONG alignment, ULONG boundary) { D(bug("AllocVecAligned(%ld, %lx, %lx, %lx)\n", bytesize, requirements, alignment, boundary)); UBYTE *ret; ULONG bytesize_adjusted; struct MemHeader *mh; struct MemChunk *mc=NULL, *p1, *p2; if( ((!bytesize) || (bytesize>boundary)) ) return NULL; /* Add room for stored size */ bytesize_adjusted = bytesize + AROS_ALIGN(sizeof(ULONG)); /* Round to a multiple of MEMCHUNK_TOTAL */ bytesize_adjusted = AROS_ROUNDUP2(bytesize_adjusted, MEMCHUNK_TOTAL); /* Be really anal about the size */ if(bytesize_adjusted<bytesize) return NULL; Forbid();
我的对齐首选拟合分配将浏览满足要求的内存列表,然后它将检查块是否包含足够的空间来存放 bytesize_adjusted。如果是,则它将向 ptr 添加 sizeof(ULONG) 并将 ptr 对齐到对齐值,并检查原始字节大小是否不会跨越边界。
为了使这工作,需要对 Bytesize_adjusted 进行一些调整。据我所知,所有分配都在 64 字节边界上,这意味着为了使新的分配不破坏对齐方式,分配必须在该对齐方式上开始和结束。然后开销将是 128 字节,这仍然比不使用 AllocAbs() 手动对齐要好。
对于首选拟合分配方案,对齐分配很慢。
改变分配方案是我的能力范围之外,而且无论如何,如果真的要发生,它需要某种共识。TLSF 类型的分配方案的主要好处(如果我正确理解这个概念)是不需要搜索具有足够空间的块,因为内存列表对不同的空闲空间块有不同的列表,因此它返回最适合分配的块:这会导致分配后返回非常小的空闲块(如果分配的大小小于块大小),而不是首选拟合,它从第一个合适的块中获取内存,剩余部分将被释放。在我的情况下,第一个块是系统内存的剩余部分,因为我的驱动程序非常早加载,并且没有释放内存,它的尺寸几乎是我的 Aros 盒子内存 (MEMF_FAST) 的完整 2Gb。
分配的速度有点用处,为什么有人会在做一些需要速度的事情时分配内存?可以使用内存池来降低碎片级别,但它们不适合对齐的分配。分配速度确实非常重要。想想系统启动和其他所有情况,在这些情况下,会执行数千甚至数百万次内存分配。速度在那儿确实很重要。
MorphOS alignOffset 在我看来仍然是有点无用的,为什么不在最后或其他地方分配额外的内存?因为你可能希望硬件结构是软件结构中的一个字段。例如,在 Poseidon 的 OHCI 实现中,软件 ED 和 TD 结构以仅软件的字段开头,后面跟着硬件结构。只有硬件结构需要对齐。在我正在开发的分离式 OHCI 驱动程序中,这些软件结构现在以 struct MinNodes 开头,因此它们必须位于硬件结构之前,以便在 struct Lists 中轻松放置(除非使用笨拙的解决方法)。
MorphOS 对齐分配也缺乏边界要求,而这对于硬件驱动程序代码来说是必须的。但是,这个限制可能很容易解决。例如,如果对齐方式是 128 字节,边界是 1024 字节,你可以将请求的大小向上舍入到大于或等于对齐方式的下一个二的幂,并将其用作请求的对齐方式。因此,如果所需的大小向上舍入到 512,则请求 512 字节的对齐方式,并且不会跨越边界。
需要对齐分配的 AROS 驱动程序包括:HDAudio、USB 驱动程序、AHCI 等等。它们都需要对齐分配,一些以太网驱动程序也可能需要,现在对齐功能已在它们的代码中实现。一个对齐分配函数很有用。
据我所知,所有图形内存管理目前都是使用 Allocate() 在自己的私有 MemHeader 上实现的,存储在 VRAM 中。好吧,Allocate() 使用的是慢速算法。此外,如果 Nouveau 依赖于自己的分配器呢?在这种情况下 - 如果我们引入一个实现内存池处理程序的能力,例如作为类呢?一个这样的类将是 exec 的池管理器。在这种情况下,AllocPooled()/FreePooled() 可以用来在 VRAM 中执行分配。例如,如果 Nouveau 使用自己的分配器,它会将其公开为一个类加上对象指针(池本身)。这样池函数就可以使用它。
库由四个部分组成:库节点、函数(向量)表、函数集和与库相关的全局数据。
struct Library { struct Node lib_Node; UBYTE lib_Flags; UBYTE lib_pad; UWORD lib_NegSize; UWORD lib_PosSize; UWORD lib_Version; UWORD lib_Revision; APTR lib_IdString; ULONG lib_Sum; UWORD lib_OpenCnt; };
如果没有正确的 AROS_LH 样式寄存器参数,当前库函数将假定基于堆栈的变量。添加 .conf 和 AROS_LH()。一些库使用 SDI 包含以实现可移植性。
谈论库基础中的私有 AROS 结构。(没有人关心一些额外的 LVO,它只有 6 个字节/LVO)。如果它们是 AROS 私有的,那么我们没有理由不能根据需要动态分配它们。但是,如果它们被初始化为某个值,就像大多数 OOP 数组一样,您需要将初始化值存储在某个地方。为它们分配足够的空间来存储它吗?一种(可能很丑陋)的方法是分配存储空间 + 数组,存储初始化值,将指向数组本身的指针记录在所需结构中 - 然后通过宏访问初始化值?
当原始进程退出时,aroscbase 被设置为 NULL(我猜它也被释放了)。当然,这仍然被分离的进程使用.. 该应用程序不使用 detach.o?它应该避免这种问题。
问题不在于 arosc.library 本身,而在于它的自动打开。当然,当程序退出时,符号集会被遍历以关闭所有自动打开的库,这会导致 arosc.library 也被关闭。
SetFunction( struct Library *lib, LONG funcOffset, APTR funcEntry) /* 更改函数查找位置 */
Exec 根据不同任务的优先级和需求安排时间切片(多任务处理)。
struct Task { struct Node tc_Node; UBYTE tc_Flags; UBYTE tc_State; BYTE tc_IDNestCnt; /* intr disabled nesting */ BYTE tc_TDNestCnt; /* task disabled nesting */ ULONG tc_SigAlloc; /* sigs allocated */ ULONG tc_SigWait; /* sigs we are waiting for */ ULONG tc_SigRecvd; /* sigs we have received */ ULONG tc_SigExcept; /* sigs we will take excepts for */ UWORD tc_TrapAlloc; /* traps allocated */ UWORD tc_TrapAble; /* traps enabled */ APTR tc_ExceptData; /* points to except data */ APTR tc_ExceptCode; /* points to except code */ APTR tc_TrapData; /* points to trap code */ APTR tc_TrapCode; /* points to trap data */ APTR tc_SPReg; /* stack pointer */ APTR tc_SPLower; /* stack lower bound */ APTR tc_SPUpper; /* stack upper bound + 2*/ VOID (*tc_Switch)(); /* task losing CPU */ VOID (*tc_Launch)(); /* task getting CPU */ struct List tc_MemEntry; /* allocated memory */ APTR tc_UserData; /* per task data */ };
CHILD_NOTNEW = 1 CHILD_NOTFOUND = 2 CHILD_EXITED = 3 CHILD_ACTIVE = 4
任务有消息端口,可以等待信号和/或消息传递到端口。这会导致 Dos 库中的进程。
每个任务都有自己的一组 32 个信号,其中 16 个留作系统使用。当一个任务向第二个任务发出信号时,它要求操作系统设置第二个任务信号的 32 位长字中特定的位。
使用 Wait() 使任务休眠
mysignals = Wait(1L<<17 | 1L<<19); /* waiting on signal 17 or 19 to wake up */
SetSignal() 可以更改任务的信号位,也可以监控它们。
解决此问题的一个简单方法是,任务使用 timer.device 或图形函数 WaitTOF() 在其轮询循环中短暂休眠。如果它是一个进程,则使用 DOS 库 Delay() 或 WaitForChar() 函数。
- 阅读更多 此处
信号量锁定方法,所有任务在访问共享数据结构之前都同意锁定约定。
struct SignalSemaphore { struct Node ss_Link; WORD ss_NestCount; struct MinList ss_WaitQueue; struct SemaphoreRequest ss_MultipleLink; struct Task *ss_Owner; WORD ss_QueueCount; };
我的建议是尽可能地消除 Forbid()/Permit,并将 Forbid 锁替换为 AROS 中的信号量。保留 Forbid()/Permit 的当前状态,但要么抱怨(#warning)要么惩罚代码(使用调试日志或其他方式)。如果您打算使用 forbid(obj) - 您不妨直接使用信号量,因为一切归结为锁定特定资源访问。
如果使用 C 或其他低级语言编程,程序员应该掌握以下内容。
struct SignalSemaphore mymemory_lock;
...
int init_all(void)
{
...
InitSemaphore(&mymemory_lock);
...
}
int myfunc(...)
{
---
ObtainSemaphore(&mymemory_lock);
/* Critical section */
ReleaseSemaphore(&mymemory_lock);
...
}
您使用 ObtainSemaphore 锁定对象(信号量),使用 ReleaseSemaphore 释放它。
当您想要在关键部分中排除 Forbid() 时,它将在函数中变成类似这样的东西。
int myfunc(...)
{
---
ObtainSemaphoreTags(&mymemory_lock, SEM_DONTFORBID, TRUE, TAG_END);
/* Critical section */
ReleaseSemaphore(&mymemory_lock);
...
}
"Amiga 类" 隐藏信号量 -> 它们通常被隐藏,因为它们不需要被外部代码访问/作为例程的一部分进行处理。
即使在“正常路径”中,ObtainSemaphore 也会使用 Forbid,这意味着信号量可用。信号量函数只在很短的时间内禁止。
性能改进,其背后的想法是内存分配器锁定整个操作。即它在分配开始之前调用 Forbid(),只在完成时调用 Permit()。因此,决定尝试使用信号量而不是禁止,这应该允许其他任务在分配期间工作。
改进的信号量验证。现在,如果在主管模式下调用信号量 API,将发布警报。将内存管理切换为使用全局信号量而不是 Forbid()/Permit() 对。应该会提高多任务处理能力。最好将其设置为运行时(引导选项)选项(默认值为 == 使用 forbid/permit),因为周围有代码不希望 FreeMem() 会破坏 Forbid 状态。
许多程序需要原始行为,例如许多程序执行以下操作
Forbid() Create new task or process. Set some task variables and do other stuff. Permit()
至少 m68k 必须始终使用原始行为。
Morphos 有一个关于此主题的 很好的介绍。
struct Node { struct Node *ln_Succ; struct Node *ln_Pred; UBYTE ln_Type; BYTE ln_Pri; char *ln_Name; };
ln_Name is currently in the wrong position compared to above, will be fixed by AROS ABIv1.
这个基本节点结构是 AmigaOS (TM) 和 AROS 等列表使用的许多结构的起点。AROS 和其他 Amiga 类操作系统中的许多信息都存储在列表中。
在将节点添加到列表之前,必须将其 ln_Type、ln_Pri 和 ln_Name 字段初始化为相应的值。
struct List { struct Node *lh_Head; /*first node in list*/ struct Node *lh_Tail; /*always */ struct Node *lh_TailPred; /*last node in list*/ UBYTE lh_Type; /*type of node*/ UBYTE lh_Pad; /*byte not used*/ };
1. Set the lh_Head field to the address of the lh_Tail. 2. Clear the lh_Tail field. 3. Set the lh_TailPred field to the address of lh_Head. 4. Set lh_Type to the same data type as the nodes to be kept the list.
来自 此处
#include <proto/exec.h> void AddHead( struct List *list, struct Node *node ); void AddTail( struct List *list, struct Node *node ); void Enqueue( struct List *list, struct Node *node ); void Insert( struct List *list, struct Node *node, struct Node *pred );
struct Message { struct Node mn_Node; struct MsgPort *mn_ReplyPort; UWORD mn_Length; };
这会导致 Intuition 中的 IntuiMessage。
CreateMsgPort 即 CreateMessagePort() 完成所有操作
allocate a signal create a message port set the message ports sigbit and task to your allocated signal and self
WaitPort()
listen for that signal in wait respond when messages signal your task
要与设备通信,您必须从程序中打开一个端口。然后您必须初始化一段内存,它将作为您的程序和设备之间的“管道”。在此块中,您放置与要与其通话的设备相关的所有必要信息。作为回报,这段内存将填充您请求的信息。最后一步是打开设备。
完成后,您必须按顺序关闭,以免丢失使用的内存块。注意,您必须按相反的顺序释放任何分配的内存块。
要建立通信端口,您需要命令 CreateMsgPort(),它将通过设置以下结构在您的程序中打开一个“端口消息”(MsgPort)
struct MsgPort ( struct Node mp_Node; UBYTE mp_Flags; UBYTE mp_SigBit; void * mp_SigTask; struct List mp_MsgList; );
通过 PutMsg() 发送,如果需要回复,则先设置 mn_ReplyPort。要等待消息,请使用地址初始化 mp_SigTask 并使用信号编号设置 mp_SigBit。从队列中移除顶层消息,使用 GetMsg(),如果队列为空则返回 0。
每个 SendIO 必须与一个相应的 WaitIO 配对。该程序重用了 timerequest,尽管之前的请求尚未完成。这是不允许的。您不应该在 IORequest 回复端口上使用 GetMsg。收到信号后,调用 WaitIO 将执行所有必要的请求处理,包括隐式 GetMsg。如果请求已得到回复,WaitIO 不会再次等待。手动 GetMsg 是不合适的。
DoIO() 和 WaitIO() 以及两者(DoIO() 实际上跳转到 WaitIO())将字节大小的 io_Error 代码扩展为 LONG,就像 OpenDevice() 一样。
这是低级 IPC 的样子
ssize_t uade_ipc_read(void *f, const struct uade_msg *um, const void *buf) { struct MsgPort *msgport = (struct MsgPort *) f; struct UADEMessage *msg; /*Wait(1 << msgport->mp_SigBit); WaitPort(msgport); msg = (struct UADEMessage *) GetMsg(msgport);*/ /* ugly busy loop, because WaitPort isn't working... */ while (!(msg = (struct UADEMessage *) GetMsg(msgport))) { Delay(1); } CopyMemQuick(&msg->um, um, sizeof(struct uade_msg)); CopyMem(msg->data, buf, ntohl(um->size)); ReplyMsg(msg); return 1; } ssize_t uade_ipc_write(void *f, const struct uade_msg *um, const void *buf) { struct MsgPort *msgport = (struct MsgPort *) f; struct MsgPort *replyport; struct UADEMessage msg; if ((replyport = CreateMsgPort())) { msg.message.mn_Node.ln_Type = NT_MESSAGE; msg.message.mn_Length = sizeof(struct UADEMessage); msg.message.mn_ReplyPort = replyport; CopyMemQuick(um, &msg.um, sizeof(struct uade_msg)); CopyMem(buf, msg.data, ntohl(um->size)); PutMsg(msgport, (struct Message *) &msg); WaitPort(replyport); DeleteMsgPort(replyport); return 1; } return 0; }
uade_ipc_write 总是创建一个新的回复消息端口,这非常低效,但在重复使用同一个端口进行不同的写入操作时遇到了一些问题。可能更大的问题是 uade_ipc_write,因为它无法使用 WaitPort 或 Wait。它们使用第一个消息,但之后 uade_ipc_write 将始终在 WaitPort 上阻塞,无论有多少消息放入端口。不幸的是,Wait 也不好,所以不得不使用那个难看的 while(!GetMsg) 循环,延迟为 1/50 秒。
您的 msgport 是否在不同的任务/进程中分配?如果是,您需要先更新 msgport 中的 mp_SigTask 字段,然后才能使用它。
msgport->mp_SigTask = FindTask(NULL);
还要注意,mp_SigBit 将在错误的任务中分配,因此您可能想分配一个新的,以确保安全。
msgport->mp_SigBit = AllocSignal(-1);
您可以创建没有信号的端口,如下所示
struct MsgPort *CreatePortNoSignal (void) { struct MsgPort *port; port = AllocVec(sizeof(*port), MEMF_CLEAR); if (port) { port->mp_Node.ln_Type = NT_MSGPORT; port->mp_Flags = PA_IGNORE; port->mp_MsgList.lh_Head = (struct Node *)&port->mp_MsgList.lh_Tail; port->mp_MsgList.lh_Tail = NULL; port->mp_MsgList.lh_TailPred = (struct Node *)&port->mp_MsgList.lh_Head; } return port; } void DeletePortNoSignal (struct MsgPort *port) { FreeVec(port); }
只需记住在使用它之前将 mp_SigTask、mp_SigBit 和 mp_Flags 设置为正确的值(mp_Flags 应为 PA_SIGNAL)。
- 需要获取一个消息端口
- 为一个称为 I/O 请求的专用消息包分配内存
- 在 I/O 请求中设置指向消息端口的指针
- 通过打开设备来设置与设备本身的链接
有很多方法可以创建 I/O 请求
- 将其声明为结构体。所需内存将在编译时分配。
- 将其声明为指针并调用 AllocMem() 函数。调用 FreeMem() 函数释放内存
- 将其声明为指针并调用 CreateIORequest() 函数。
返回值 = OpenDevice(device_name,unit_number, struct IORequest,flags)
DoIO() 和 SendIO() 最常使用。
CloseDevice(IORequest)
struct IORequest ( struct message io_Message; struct Device * io_Device / * Pointer to the device * / struct io_Unit Unit * / * unit * / UWORD io_Command / * Command to perform * / UBYTE io_Flags; BYTE io_Error; / * error * / ); struct IOStdReq { struct Message io_Message; struct Device *io_Device; /* device node pointer */ struct Unit *io_Unit; /* unit (driver private)*/ UWORD io_Command; /* device command */ UBYTE io_Flags; BYTE io_Error; /* error or warning num */ ULONG io_Actual; /* actual number of bytes transferred */ ULONG io_Length; /* requested number bytes transferred*/ APTR io_Data; /* points to data area */ ULONG io_Offset; /* offset for block structured devices */ }; /* library vector offsets for device reserved vectors */ #define DEV_BEGINIO (-30) #define DEV_ABORTIO (-36) /* io_Flags defined bits */ #define IOB_QUICK 0 #define IOF_QUICK (1<<0) #define CMD_INVALID 0 #define CMD_RESET 1 #define CMD_READ 2 #define CMD_WRITE 3 #define CMD_UPDATE 4 #define CMD_CLEAR 5 #define CMD_STOP 6 #define CMD_START 7 #define CMD_FLUSH 8 #define CMD_NONSTD 9
struct unit { struct MsgPort unit_MsgPort; UBYTE unit_Flags; UBYTE unit_pad; UWORD unit_OPENCNT; };
您可以在“Snoopy”中找到使用 SetFunction() 进行修补的示例。在退出应用程序之前,您必须重置修补程序(通过使用旧函数的地址调用 SetFunction())。但是,您无法捕获另一个应用程序修补了相同函数的情况。
您可以通过检查函数地址是否位于所讨论资源占用的内存中来确定函数是否已被修补。也许这是 AROS 可以尝试改进的一个领域,即让 setfunction 代码位于更灵活的底层实现之上,该实现确实为感知应用程序提供了查找其他修补代码的方法——可能有一些方法可以仲裁访问权限。例如,应用的修补程序代码可以声明它只想要读取生成的数据(因此导致实际操作数据的修补程序优先执行)。
处理 I/O 请求和其他信号的正确方法是
OpenDevice(); // put first command in iorequest SendIO (iorequest) while (running) { received = Wait (othersignal | iosignal); if (received & iosignal) { WaitIO (iorequest); // handle data in iorequest if needed // put next command in iorequest SendIO (iorequest); } if (received & othersignal) { // handle other signal } } if (CheckIO (iorequest) == NULL) AbortIO (iorequest); WaitIO (iorequest); CloseDevice();
/*
* This is an example of using the serial device.
* First, we will attempt to create a message port with CreateMsgPort()
* Next, we will attempt to create the I/O request with CreateIORequest()
* Then, we will attempt to open the serial device with OpenDevice()
* If successful, we will send the SDCMD_QUERY command to it and reverse our steps.
* If we encounter an error at any time, we will gracefully exit.
*
* Run from CLI only
*/
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <devices/serial.h>
#include <exec/exec_protos.h>
#include <stdio.h>
void main(void)
{
struct MsgPort *SerialMP; /* pointer to our message port */
struct IOExtSer *SerialIO; /* pointer to our I/O request */
/* Create the message port */
if (SerialMP=CreateMsgPort())
{
/* Create the I/O request */
if (SerialIO = CreateIORequest(SerialMP,sizeof(struct IOExtSer)))
{
/* Open the serial device */
if (OpenDevice(SERIALNAME,0,(struct IORequest *)SerialIO,0L))
/* Inform user that it could not be opened */
printf("Error: %s did not open\n",SERIALNAME);
else
{
/* device opened, send query command to it */
SerialIO->IOSer.io_Command = SDCMD_QUERY;
if (DoIO((struct IORequest *)SerialIO))
/* Inform user that query failed */
printf("Query failed. Error - %d\n",SerialIO->IOSer.io_Error);
else
/* Print serial device status - see include file for meaning */
printf("\n\tSerial device status: %x\n\n",SerialIO->io_Status);
/* Close the serial device */
CloseDevice((struct IORequest *)SerialIO);
}
/* Delete the I/O request */
DeleteIORequest(SerialIO);
}
else
/* Inform user that the I/O request could be created */
printf("Error: Could create I/O request\n");
/* Delete the message port */
DeleteMsgPort(SerialMP);
}
else
/* Inform user that the message port could not be created */
printf("Error: Could not create message port\n");
}
struct Interrupt ( struct Node is_Node; APTR is_Data; VOID (*is_code)(); };
Amiga 设备与 Amiga 库非常相似,只是设备通常控制某种 I/O 硬件,并且通常包含一组有限的标准函数,这些函数接收用于控制 I/O 的命令。
struct DeviceList { BPTR dl_Next; /* bptr to next device list */ LONG dl_Type; /* see DLT below */ struct MsgPort * dl_Task; /* ptr to handler task */ BPTR dl_Lock; /* not for volumes */ struct DateStamp dl_VolumeDate; /* creation date */ BPTR dl_LockList; /* outstanding locks */ LONG dl_DiskType; /* 'DOS', etc */ LONG dl_unused; BSTR dl_Name; /* bptr to bcpl name */ };
在 AmigaOS 文件系统(也称为“处理程序”)中,文件系统被实现为单个任务,等待接收来自应用程序(或来自 DOS,通过 Open() 等调用)的“数据包”。每个数据包都包含一个针对文件系统任务的命令(例如“打开文件”或“创建文件夹”)以及命令所需的任何数据(例如文件名)。
因此,当文件系统任务接收到数据包时,它无法执行任何操作,直到发送数据包的任务被中断。然后它将处理数据包。同时,发送任务(应用程序)可以继续执行其他工作。文件系统任务在命令完成时向发送任务发出信号。
使用 CreateIORequest() 函数。完成此操作后,使用 OpenDevice() 打开设备。
Error = OpenDevice (Name, Unit, RequestIO Flags)
名称:设备的名称。单元:默认值为 0。错误 =? (能够始终打开一个错误返回 NULL)
首先关闭最后打开的内容。实际上,如果我们关闭第一个通信端口 (MsgPort),然后关闭查询结构 (IORequest),预计会丢失内存或更糟的是导致系统崩溃。
CloseDevice ()
使用 IORequest DeleteIORequest() 释放结构,最后执行 DeleteMsgPort()。
现在我们知道了用于通信(同步和异步)的命令(DoIO、SendIO、AbortIO 和 CheckIO),我们必须知道在 IOStdReq 结构中填写什么。
struct IOStdReq ( ... struct Device * io_Device / * Pointer to the device * / struct Unit * io_Unit / * Unit * / UWORD io_Command / * Command * / UBYTE io_Flags / * IOF_QUICK or not * / BYTE io_Error / * error * / ULONG io_Actual / * Number of bytes transferre * / ULONG io_Length / * Number of bytes to transfer * / TRPA io_Data / * Pointer to memory area * / ... );
它应该用 io_Command 字段填写要执行的命令,用 io_Length 字段填写要传输的总字节数,用 io_Data 字段填写内存区域或数据的起始位置。
AROS console.device 和 console-handler,两者都旨在将其升级到 3.x 版本,并添加额外的功能(有效地重新实现 KingCON 的许多重要功能,更清晰地分层在 console.device/console-handler 上,而不是将其全部塞入替换的 console-handler 中)。
console.device/handler 具有私有 API 用于与 ConClip 通信。AROS ConClip 使用直觉编辑挂钩从字符串小部件获取剪切/粘贴事件,并将它们转换为发送到 ConClip 任务的消息;我将让 console.device/handler 使用相同的格式。
打开控制台设备需要四个步骤
- 使用 CreatePort() 创建一个消息端口。
- 创建一个类型为 IOStdReq 的 I/O 请求结构。
- 打开一个直觉窗口,并在 IOStdReq 的 io_Data 字段中设置指向它的指针,并在 io_Length 字段中设置窗口的大小。该窗口必须为 SIMPLE_REFRESH,才能与 CONU_CHARMAP 和 CONU_SNIPMAP 单元一起使用。
- 通过调用 OpenDevice() 打开控制台设备
控制台设备单元
CONU_LIBRARY - return device pointer, no console is opened
CONU_STANDARD - synchronous communication to open a standard console
CONU_CHARMAP - asynchronous mode to open a console with a character map
CONU_SNIPMAP - open a console with a character map and copy-and-paste support
控制台设备标志
CONFLAG_DEFAULT - redraw the window when it is resized CONFLAG_NODRAW_ON_NEWSIZE - will not redraw the window when it is resized
字符映射单元 CONU_CHARMAP 和 CONU_SNIPMAP 是唯一使用 flags 参数来设置字符映射使用方式的单元。CONU_STANDARD 单元忽略 flags 参数。
调用 setbuf() 来关闭 stdout 的缓冲。在退出时,流保持为无缓冲的(在调试打开的情况下,我看到所有后续命令都执行而没有缓冲——不好)。
console.device 确实可以在没有它的情况下正常工作——它只是回退到其内部复制/粘贴缓冲区。但是 ConClip 在经典系统上实现的是将 console.device/handler 与读取/写入 clipboard.device 的路径分离。对于那些始终在 RAM 中拥有剪贴板的人来说,这几乎没有意义。如果您执行大型复制/粘贴操作并将剪贴板存储在速度较慢的磁盘上,则它并不那么没有意义。
已经存在指定文本或背景的笔(调色板条目)编号的 ESC 序列。就像在原始 AmigaOS 上所做的那样。但是有一个小问题——在深度屏幕(>3bpp)上,只有前四个笔被定义。以及最后四个笔(这是为了让像素反转可以工作)。它们之间的笔未定义。它们可以是未初始化的(设置为黑色)或由其他应用程序动态分配并设置为它想要的值。
高级 shell(如 VinCED 和 MUICon)只分配它们想要的笔,并将颜色编号映射到分配的笔编号。这是在公共屏幕上使用调色板的标准方法。在直接颜色屏幕上,调色板仍然有效,这是由 graphics.library 处理的。因此,当您需要使用少量固定数量的定义颜色时(例如在您的情况下),使用调色板是可以的。
shell 不会像您想象的那样简单地从命令行解析参数,然后将缓冲区传递给调用的程序。相反,它将数据推送到用作“字符串流”的文件句柄中,从中可以从内存缓冲区读取数据。此类技巧没有记录。实际上,shell 的行为没有记录,它是一堆令人难过的 hack。
它与 rastport 唯一的交互应该是 ScrollRaster、RectFill()、Text()、SetAPen()、SetABPenDrMd() 和 SetSoftStyles()。
更兼容的 KS 替代方案,用于模拟。不喜欢预先分配过多内存(甚至更糟的是,使用静态数组)而不是在真正需要内存时才分配内存的 rom 代码。但我不会进行会使代码更难读且更混乱的更改,仅仅因为这样做会将内存使用量减少 100 字节或类似的值 :)。
扩展 RawDoFmt() 是一个糟糕的主意。RawDoFmt() 应该只做原始自动文档所说的,所有 AROS 程序应该使用 VNewRawDoFmt()。这些常量来自 MorphOS,并且 NULL 指针 (RAWFMTFUNC_STRING) 也受 AmigaOS4 支持。这些系统上的 m68k 代码可以利用它们。
locale.library 修补了 VNewRawDoFmt,因此实际上使用了 LocVNewRawDoFmt。当使用原始 VNewRawDoFmt 时,不会发生崩溃。我做了一些代码比较并得出了一些结论
首先,数据流在内存中的设置方式如下
i : 4 字节处理器 : 从第 5 个字节开始
即使 i 被降级((UWORD)i),它仍然占用 4 个字节,这就是为什么此更改没有帮助的原因。
接下来,这两个函数都认为一个裸 '%d' 应该为 2 个字节/sizeof(UWORD) 长,而 "%ld" 被认为是 4 个字节/sizeof(ULONG)。然而,不同之处在于这两个函数如何从 va 流读取数据。
VNewRawDoFmt 将任何小于或等于 int 的数据读取为 int,而 LocVNewRawDoFmt 将 UWORD 读取为 2 个字节。
另一个显着差异是 VNewRawDoFmt 具有两个获取宏(fetch_va_arg 和 fetch_mem_arg),而 LocVNewRawDoFmt 对这两种情况(va 流和内存流)使用相同的宏。
- 哪种行为是正确的(fetch_va_arg vs stream_addr)?
最初的原因是 CON: 是编译器/autoinit/stdiowin.c,它始终将输入和输出流设置为“CON://///AUTO/CLOSE”。这将强制在程序从 Input()/Output() 进行任何读写操作时打开控制台窗口。
对原始 AOS 进行了一些测试,发现当从 WB 启动时调用 ReadArgs 时,没有 CON 窗口。
- 在 stdiowin 中执行类似于读取 oldin[检查拼写] 并重新注入到新的输入中。然后 ReadArgs 应该获取最初由 createnewproc 或 runcommand 注入的 EOF。
- 将 __stdiowin 设置为 NIL:作为默认值,这似乎与我刚测试的结果一样有效。
在 UNIX 主机上引入了 kernel.resource,现在正在逐步将其功能迁移到该资源中。首先是统一 InternalLoadSeg()。现在 dos.library 在 kernel.resource 中注册已加载的可执行文件,而不是使用自己的私有结构。kernel.resource 导出一个静态变量,以便让 gdb 访问此列表。
使用 --enable-debug=modules,symbols 重新配置 AROS。我猜想 "symbols" 应该向内核可执行文件添加完整的调试信息。但在这样做之后,gdb 只打印了错误,类似于 "Dwarf error: failed to resolve reference no 28"。并且它再次无法读取类型信息。
顺便说一句,新代码不再需要 SET_START_ADDR(),这意味着 gdb 调试现在将在所有架构上工作。
我们已经拥有了可工作的 tc_TrapCode 字段。如果我们将其指向某些代码,当 CPU 遇到陷阱时,该代码将在监督模式下执行。至少在 i386 原生和 Windows 主机上是如此。但是,当前代码只接收警报号,这还不够。为了能够进行任何高级诊断,我们需要访问 CPU 上下文。我们也应该能够修改它。在经典 AmigaOS 中,此处理程序使用 C 约定(参数在堆栈上)调用。堆栈布局如下:
0(sp).l = trap# 4(sp) Processor dependent exception frame
AROS 不会直接在堆栈中保存 CPU 上下文。相反,它使用 ETask 结构的私有部分中的独立存储。这里最简单的方法是将 TrapCode 入口点声明为以下内容:
void TrapCode(ULONG trapNum, struct ExceptionContext *ctx);
这样我们就可以在堆栈上拥有两个指针。如果我们进一步发展这个想法,我们会发现,如果我们将其声明为以下内容,就可以轻松地提供与 m68k AROS 端口的二进制和源代码兼容性:
void TrapCode(ULONG trapNum, APTR ContextHandle);
并在代码内部,我们使用宏来访问实际的 CPU 上下文:
struct ExceptionContext *ctx = GET_CONTEXT(ContextHandle);
其中,GET_CONTEXT 在 m68k 上扩展为以下内容:
#define GET_CONTEXT(x) ((struct ExceptionContext *)&x)
在其他系统上扩展为以下内容:
#define GET_CONTEXT(x) ((struct ExceptionContext *)x)
现在我们有了上下文指针。但是,为了能够对它做些什么,我们需要声明上下文的实际结构。我建议在 aros/cpu.h 中进行声明。对于同一个 CPU,所有版本(主机和原生)的上下文应该保持一致。
我选择了 ExceptionContext 作为结构的名称,以便与 AmigaOS4 兼容(也许将来也能实现二进制兼容)。MorphOS 缺少陷阱处理功能(不是真的,但机制完全不同,通过消息传递工作),所以没有东西可以借鉴。
在 AmigaOS4 中,ExceptionContext 具有以下定义:
struct ExceptionContext { uint32 Flags; /* Flags, describing the context (READ-ONLY)*/ uint32 Traptype; /* Type of trap (READ-ONLY) */ uint32 msr; /* Machine state */ uint32 ip; /* Return instruction pointer */ uint32 gpr[32]; /* r0 - r31 */ uint32 cr; /* Condition code register */ uint32 xer; /* Extended exception register */ uint32 ctr; /* Count register */ uint32 lr; /* Link register */ uint32 dsisr; /* DSI status register. Only set when valid */ uint32 dar; /* Data address register. Only set when valid */ float64 fpr[32]; /* Floating point registers */ uint64 fpscr; /* Floating point control and status register */ /* The following are only used on AltiVec */ uint8 vscr[16]; /* AltiVec vector status and control register */ uint8 vr[512]; /* AltiVec vector register storage */ uint32 vrsave; /* AltiVec VRSAVE register */ };
Flags 是一个特殊字段,它告诉哪些字段实际存在于此结构中。这样就提供了与可能未来 CPU 中更多寄存器的向后兼容性。
enum enECFlags { ECF_FULL_GPRS = 1<<0, /* Set if all register have been saved */ /* If this flag is not set, gpr[14] through */ /* gpr[31] are invalid */ ECF_FPU = 1<<1, /* Set if the FPU registers have been saved */ ECF_FULL_FPU = 1<<2, /* Set if all FPU registers have been saved */ ECF_VECTOR = 1<<3, /* Set if vector registers have been saved */ ECF_VRSAVE = 1<<4 /* Set if VRSAVE reflects state of vector */ /* registers saved */ };
我建议将相同的结构用于 PPC AROS。我已经查看了任务切换器代码,可以重新排列寄存器以匹配此布局。无论如何,此布局比当前使用的布局更好,因为它支持 AltiVec。
至于 i386,我看到了当前的 AROS 上下文(定义不完善)和 Windows 上下文。这里有趣的是,AROS 保存 8087 FPU 寄存器集或 SSE2 寄存器集。这真的是互斥的吗?SSE 不能与 8087 FPU 同时使用吗?这就是我们在 Windows 中所拥有的:
#define MAXIMUM_SUPPORTED_EXTENSION 512 typedef struct _FLOATING_SAVE_AREA { IPTR ControlWord; IPTR StatusWord; IPTR TagWord; IPTR ErrorOffset; IPTR ErrorSelector; IPTR DataOffset; IPTR DataSelector; UBYTE RegisterArea[80]; IPTR Cr0NpxState; } FLOATING_SAVE_AREA;
struct AROSCPUContext { IPTR ContextFlags; IPTR Dr0; IPTR Dr1; IPTR Dr2; IPTR Dr3; IPTR Dr6; IPTR Dr7; FLOATING_SAVE_AREA FloatSave; IPTR SegGs; IPTR SegFs; IPTR SegEs; IPTR SegDs; IPTR Edi; IPTR Esi; IPTR Ebx; IPTR Edx; IPTR Ecx; IPTR Eax; IPTR Ebp; IPTR Eip; IPTR SegCs; IPTR EFlags; IPTR Esp; IPTR SegSs; BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; };
ContextFlags 是:
#define CONTEXT_i386 0x10000 #define CONTEXT_i486 0x10000 #define CONTEXT_CONTROL (CONTEXT_i386|0x00000001L) #define CONTEXT_INTEGER (CONTEXT_i386|0x00000002L) #define CONTEXT_SEGMENTS (CONTEXT_i386|0x00000004L) #define CONTEXT_FLOATING_POINT (CONTEXT_i386|0x00000008L) #define CONTEXT_DEBUG_REGISTERS (CONTEXT_i386|0x00000010L) #define CONTEXT_EXTENDED_REGISTERS (CONTEXT_i386|0x00000020L) #define CONTEXT_FULL (CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_SEGMENTS)
如您所见,SSE 和 8087 有两个独立的区域。我们应该为 i386 AROS 采用相同的结构,还是模棱两可?无论如何,i386 AROS 上下文也应该可扩展,并且应该包含一些标志。我没有查看 x86-64 端口,但它应该以类似的方式实现。我也没有查看 UNIX 主机端口。它们的上下文是一个黑暗森林,但想法是类似的,至少它可以转换为我们选择的通用形式。
我突然想到了一个替代想法。上下文结构可以保持为私有 blob,我们可以在 kernel.resource 中添加一些函数来解析它。比如 KrnGetContextReg() 和 KrnSetContextReg()。这样更灵活,但可能更慢,更复杂。
- 启动时间。
- 工作时间双重故障或监督模式故障。
如果您阅读并研究 Alert() 和底层代码,您会看到一个相当复杂的涉及 ETask 的状态机。我付出了很多努力,才让它变得像现在这样健壮。基本思想是,每个任务都可以有一个 "Crashed" 状态,并具有一些上下文。在最近的实现中,上下文有几种类型。它可以是 CPU 上下文或 mungwall 上下文。更多上下文即将到来(例如内存管理器上下文)。每当任务崩溃时,崩溃上下文都会记录在 ETask 中。然后控制权传递给 Alert()(或更准确地说是 Exec_ExtAlert())。Exec_ExtAlert() 首先检查当前的 CPU 权限级别。如果是用户级别,那么我们很可能正在运行通常的任务上下文(要么是直接的 Alert() 调用,要么是 exec 的处理程序利用的 CPU 异常)。在这种情况下,会调用 Exec_UserAlert()。它尝试通过 intuition.library 启动一个请求器。这个过程以几种方式结束:
- 成功完成,用户响应 "继续" 以恢复警报。崩溃状态清除,Alert() 返回。
- 双重警报。Alert() 再次进入。在这种情况下,新的崩溃上下文不会被记住。Exec_UserAlert()(如果进入)
再次)会发现任务已经处于崩溃状态,并且将执行权交给 Exec_SystemAlert(),将警报级别提升到 DEADEND。注意,"缺少显示驱动程序"(AN_SysScrn)也算在内。由于崩溃上下文没有被覆盖,所以我们仍然会代表问题的最初原因。当然,这是对任务的死胡同,而不是对整个系统的死胡同。您会看到 Suspend/Reboot 选项,而不是 Continue 按钮,仅此而已。
有三个子系统特定的 ShutdownA() 实现,放置在不同的组件中:
- 原始 IBM AT 硬件的默认实现。通过 AT 键盘控制器执行冷重启。
- ACPI 实现。目前只支持通过 ACPI 重置寄存器执行冷重启(不需要 AML 解释器)。
- EFI 实现,使用 EFI 运行时接口。
这些组件修补 ShutdownA() 向量,以安装自己的例程(如果可用)。EFI 具有最高优先级(如果存在 efi.resource,ACPI 不会安装自己的修补程序)。
一开始,它是没有修补程序完成的。exec.library 代码看起来像这样:
if (EFIBase) { ... efi shutdown... } if (ACPIBase) { .... acpi shutdown... } ... kbc shutdown ...
真的不喜欢这种模式。第四种方法会增加一个 'if',等等。这段代码不受任何合并的影响。这就是为什么我认为没有用于关闭机制的抽象层的原因。
如果 Alert() 在监督级别被调用,我们建议我们在中断处理程序中崩溃。我们简单地调用 Exec_SystemAlert()。现在,Exec_SystemAlert() 必须尽一切可能通知用户问题。警报在这里 99% 是死胡同,系统处于不可用状态(它无法启动 Intuition 请求器)。我们可以在这里做任何事情。覆盖屏幕、VRAM 等。然后会重启。
我的实验代码通过 libbootconsole 在 VESA 帧缓冲区上实现了 Exec_SystemAlert()。
我相信有人谈论过保护 kickstart 代码区域免遭写入。应用程序试图写入此区域会导致 GURU 还是可恢复的软件故障(我实际上希望是后者)。死胡同,但软件故障。在任何主机或 x64-64-原生上尝试 tests/exec/traptest illegal <address>。
您所说的 "设置帧缓冲区" 是什么意思?您指的是 "强制从原生模式切换到 VGA 模式" 吗?无论驱动程序的编写者想要什么。
在 "强制帧缓冲区" 上显示某些内容的代码在哪里。这段代码如何知道 "帧缓冲区" 的组织方式(宽度/高度/bpp/对齐)。回调可以填写一些结构,类似于引导加载程序提供的结构。内核会将指向此结构的指针提供给此回调。
每个驱动程序是否都需要提供一个预定义组织的帧缓冲区?我目前的建议是将其留给驱动程序决定。它可以提供任何东西,只要 libbootconsole 支持即可。目前,此库支持 EGA/VGA 硬件文本模式或块状帧缓冲区(LUT 和直接颜色)。
所以驱动程序本身会接收包含要显示的文本的结构?驱动程序会接收一个结构,它会将帧缓冲区参数(类型、分辨率、地址)放入该结构中。libbootconsole 然后会使用它。
您如何设想驱动程序注册此回调?是否会在 exec 中添加一个新函数来执行此操作?在内核中。类似于 KrnAddVideoReserCallback()。它是特定于视频的。
还有一个替代想法 - 使用机器的 BIOS。有人知道,是否可以在 AROS 内部恢复功能性 BIOS 状态?再说一次,不用担心内存覆盖等。只要初始化一些视频模式,显示文本并停止即可。除了跳转到 16 位实模式,还需要什么?我是否必须恢复中断向量等?EFI 似乎更礼貌。至少它的运行时服务提供了一些控制台。
这样的定义是错误的 - 软件应该在运行时确定有多少内存可用,并进行相应的设置。顺便说一句,这些哈希东西都是多余的。我们可以使用 exec 的 AVL 例程来进行关联。graphics.library 和 oop.library 也适用。当库基地址具有 "巨大" 的静态数组时,很难动态更改内核内存使用情况。
execbase AlertBuffer 也相对较大。(从 A500 的角度来看)。它真的需要这么大吗?实际上,它只需要保存完整的警报文本(包括回溯)。打印文本后,系统会重启。因此,理论上,只需要一个指针即可。在这里破坏内存是安全的(好吧,几乎,我们不应该破坏重置生存代码)。顺便说一句,在原生端口上,零页可以用于此。据我所知,它本来就是保留的。
AROS 的使用范围从在 Amiga 500 克隆上运行来自软盘的一两个程序,到在价值 2000 美元的 PC 上进行繁重的多任务桌面使用。
由于 dosboot.resource 现在不再包含特定机器的代码,因此将从规范中删除 strap。这两个模块将被放入 base。文件系统将保留在原生上的 'FS' 包中。
AROS 没有 PThread 库。但是,C 运行时库的一部分在共享库中,这些库本质上是可重入的代码。只有静态链接的 C 运行时库的一部分可能是线程不安全的。
当前的 C 库为每个任务(例如线程)创建了一个 C 上下文,以便 malloc/free 能正确执行。我认为,如果你开始在线程之间共享文件句柄,你会遇到问题。我认为你可以配置 C 库使其与子进程共享上下文;例如,所有线程将使用同一个上下文,但这样我认为 malloc/free 就不会再是多线程安全的。对于 ABI V1 中 C 库的当前实现,malloc/free 不是线程安全的。
目前,malloc/free 基于信号量保护 (MEMF_SEM_PROTECTED) 的 Exec 内存池,因此它是线程安全的。因此,这意味着 ABI V1 对 mem 函数的实现也是线程安全的。
但是,正如你几周前已经指出的,当前的 C 库在使用 FILE* 句柄的 fopen()/fread()/fwrite() 等函数方面不是线程安全的。这实际上是 AROS 对最新的 YAM 夜间构建的支持目前失效的原因,因为在 YAM 2.7 中,许多东西现在被放在了单独的线程中,这需要所有这些函数都能够线程安全地工作。在其他平台(OS4/newlib、OS3/clib2)上,这种情况已经存在。
void Disable( void ); 为当前任务禁用中断。中断不应被禁用太长时间。Wait() 会从禁用状态退出,但当任务恢复执行时,中断将再次被禁用。每次调用 Disable() 都会增加中断禁用计数。
void Enable( void ); 会减少中断禁用计数。当计数达到零时,将启用中断。后续调用 Enable() 不会有任何作用,禁用嵌套计数将保持为零。
void Forbid( void ); 会增加任务切换禁用计数。Wait() 会从禁止状态退出,但当任务恢复执行时,任务切换将再次被禁用。基本上,Forbid() 禁用了任务切换。只有在无法使用信号量时才使用它!
void Permit( void ); 会减少任务切换禁用计数。当计数达到零时,切换将再次被启用。后续调用 Permit() 不会有任何作用。
void AddIntServer( long vector (D0), struct Interrupt *server (A0) ); 会将一个全局中断服务器添加到给定的异常向量中。链中所有中断服务器将按顺序被调用。中断服务器的调用方式为
is_Code( ULONG is_Data (D0), UWORD *StackFrame (A1) );
参数既在寄存器中,也在 C 语言处理程序的堆栈中。StackFrame 包含一个长字,其中包含异常号,然后是“正常”的 68000 堆栈帧。
可以使用 tc_ExceptData 和 tc_ExceptCode 安装私有(特定于任务的)异常处理程序。如果未为异常安装全局处理程序,则将调用特定于任务的处理程序。如果也没有特定于任务的处理程序(tc_ExceptCode = NULL),则该任务将被挂起。
void RemIntServer( long vector (D0), struct Interrupt *server (A0) ); 会从中断服务器链中删除指定的中断服务器。请记住,为处理程序生成中断的源必须首先被禁用。
void Cause( long vector (D0) ); 在监督模式下调用中断服务器。它的行为就像一个真实的中断。如果未为该异常安装全局处理程序,也未安装特定于任务的处理程序,则该任务将被挂起。
Cause() 由消息传递系统使用,以实现每当消息到达时都会创建软中断的消息端口。
void *AllocMem( ULONG size (D0), ULONG flags (D1) ); 分配一块请求大小的内存。返回指向内存块的指针,该指针至少与长字对齐。flags 可以设置为 MEMF_CLEAR 以将分配的内存初始化为全零。
使用的分配方法是“首次匹配”。返回第一个足够大的空闲内存列表块。
void FreeMem( void *block (A0), ULONG size (D0) ); 会释放一块内存。Size 必须是对应分配中使用的 size,block 应是分配返回的值。
如果被释放的块与其他空闲内存块相邻,则它们将被合并。
ULONG AvailMem( ULONG flags (D0) ); 会返回剩余空闲内存的大小。由于空闲内存碎片,可能无法分配所有剩余内存。可以使用标志 MEMF_LARGEST 来找出最大连续空闲内存块的大小。
void AllocRemember( struct List *memlist (A0), ULONG size (D0), ULONG flags(D1) ); 分配一块至少为请求大小的内存块,并将记账信息添加到指定的列表 (memlist) 中,以便可以同时释放该列表中的所有分配的块。AllocRemember() 返回指向内存块的指针,该指针至少与长字对齐。Size 和 flags 与 AllocMem() 相同。
NULL memlist 会将分配添加到任务的分配列表中,如果这些分配未在之前被释放,则在任务退出时会自动释放它们。
由于从 shell 运行的程序在同一个任务/进程上下文环境中同步运行,因此通常不建议使用任务的内存列表。使用自己的列表,在使用前使用 NewList(memlist),并在最后使用 FreeRememberAll(memlist)。
void FreeRemember( void *block (A0) ); 释放之前通过 AllocRemember() 分配的内存。内存将从分配列表中删除并返回给系统。
不要对不是通过 AllocRemember() 分配的内存块使用 FreeRemember()!
void FreeRememberAll( struct List *memlist (A0) ); 释放与 memlist 关联的所有分配。在 FreeRememberAll() 之后,该列表将为空,可以在不使用 NewList() 的情况下重复使用。
在向列表添加任何内容之前,必须初始化列表头 (NewList())。
一个节点一次只能存在于一个列表中。在将节点添加到另一个列表之前,必须从当前列表中删除该节点。此外,如果节点当前不在列表中,则绝对不能删除该节点。void Insert( struct List *list (A0), struct Node *node (A1), struct Node *pred (A2) ); 将节点添加到列表中 pred 节点之后。如果 pred 为 NULL,则添加到列表的头部。
void AddHead( struct List *list (A0), struct Node *node (A1)); 将节点添加到列表的头部。
void AddTail( struct List *list (A0), struct Node *node (A1) ); 将节点添加到列表的尾部。
void Remove( struct Node *node (A1) ); 从该节点所在的列表中删除该节点。如果节点不在列表中,则不要使用它,否则会导致内存损坏!
struct Node *RemHead( struct List *list (A0) ); 从列表中删除第一个节点。如果列表为空,则返回 NULL。
struct Node *RemTail( struct List *list (A0) ); 从列表中删除最后一个节点。如果列表为空,则返回 NULL。
void Enqueue( struct List *list (A0), struct Node *node (A1) ); 根据 ln_Pri 字段将节点添加到列表中。数字越小表示优先级越低。优先级较高的节点最终会进入列表的头部,但会在所有优先级相同的节点之后。这也意味着 RemHead()/ Enqueue() 会循环遍历优先级最高的节点。
struct Node *FindName( struct List *list (A0), UBYTE *name (A1) ); 在列表中按名称查找节点。如果未找到指定名称的节点,则返回 NULL。如果 ln_Name 字段未初始化,则不要使用!!
请记住,节点名称可能不唯一。由于 FindName() 的操作方式,您可以将节点结构传递给它。您可以使用以下构造来查找列表中所有具有特定名称的条目。
struct List *mylist; struct Node *node; node = (struct Node *)mylist; while((node = FindName((struct List *)node, name))) { /* Do something with the node. */ }
void NewList( struct List *list (A0) ); 将列表头初始化为空列表状态。
struct Node *HeadNode( struct List *list (A0) ); 返回列表中的第一个节点,如果列表为空,则返回 NULL。
struct Node *NextNode( struct Node *list (A0) ); 返回下一个节点,如果节点是最后一个节点,则返回 NULL。
struct Node *TailNode( struct List *list (A0) ); 返回列表中的最后一个节点,如果列表为空,则返回 NULL。
struct Node *PrevNode( struct Node *list (A0) ); 返回下一个节点,如果节点是第一个节点,则返回 NULL。
void AddTask( struct Task *task (A1), long InitialPC (D0), long FinalPC (D1) ); 将任务添加到系统。执行将从 InitialPC 开始,当任务退出时,执行将跳转到 FinalPC。如果 FinalPC 为 NULL,则使用默认的 Expunge 例程(推荐)。任务结构应包含已初始化的 tc_StackSize 和 tc_Stack。结构的其他成员由 AddTask() 初始化。新任务将获得其父任务的 Task 指针作为参数。
struct Task *FindTask( char *name (A1) ); 将按名称查找任务。如果 name 为 NULL,则将返回当前任务的任务结构指针。如果未找到与名称匹配的任务,则返回 NULL。
请记住,任务名称不唯一,任务列表中可能存在许多具有相同名称的任务。
WORD SetTaskPri( struct Task *task (A0), WORD priority (D1) ); 设置任务的优先级。值越高,优先级越高。此调用将返回旧的优先级。实际上,优先级只是一个 BYTE 值。-128 的优先级最低,127 的优先级最高。
观察:目前,新的优先级将在任务下次获得 CPU 时生效。这可以被认为是一个错误特征,但由于对其他任务的优先级更改并不经常进行,因此这种可预测的延迟被认为是可以接受的。
ULONG SetSignal( ULONG newsigs (D0), ULONG sigmask (D1) ); 将设置和重置信号。仅更改 sigmask 中设置的信号位。Newsigs 包含 sigmask 中定义的信号的新信号状态。将返回信号的旧状态。
SetSignal(0, 0) 可用于读取当前信号状态,而不影响信号。
ULONG Wait( ULONG mask (D0) ); 等待 mask 中定义的任何信号。如果 mask 中没有一个信号处于活动状态,则挂起任务,直到收到 mask 中的任何信号。此调用将返回导致唤醒的信号。如果 mask 中定义的信号已设置,则不会进行等待。在返回之前,将清除 mask 中定义的信号。
void Signal( struct Task *task (A0), ULONG sigmask (D0) ); 向指定的任务发送一个或多个信号。如果任务正在等待信号,则将其唤醒。
Signal() 可以从中断中调用。
ULONG AllocSignal( void ); 为任务分配一个信号位。将返回位的编号。要获得信号掩码,您必须执行 (1L<<bitnumber)。
VOID FreeSignal( ULONG sigbitnum (D0) ); 释放先前分配的信号位。使用位的编号。参数应该是 AllocSignal() 调用返回的位编号。
void AddProgram( struct Program *program (A1) ); 将程序添加到系统。程序结构包含程序重定位所需的所有信息。当程序被添加到系统中时,可以启动多个程序副本。为 struct Program 分配的内存将成为系统所有,因此必须使用 AllocMem() 分配。
使用 AddProgram() 添加的程序可以通过 dos.library 调用 ROMLoadSeg() 加载和重新定位。
struct Program *RemProgram( char *name (A1) ); 从系统中删除程序。此调用不会释放为程序分配的内存。使用该调用的任务成为数据的拥有者,应在退出之前释放内存。
void AddPort( struct MsgPort *port (A1) ); 将公共端口添加到系统。端口应正确初始化,并且不能是已经存在的公共端口。请参阅 CreatePort()。
void RemPort( struct MsgPort *port (A1) ); 从系统中删除公共端口。仅当端口是公共端口时才使用此调用。请参阅 DeletePort()。
观察:删除公共端口是不安全的,因为没有记录持有对端口引用的任务。一个半安全的解决方案是 1) 从公共端口列表中删除端口 2) 等待一段时间以接收任何消息,并且只有在没有消息到达时,才 3) 删除端口。这假设如果一个任务获取到对公共端口的引用,它很可能会在不久的将来使用该引用将消息发送到该端口。
如果您需要可以安全删除的公共端口,请使用公共信号量,因为它们具有引用计数。
struct MsgPort *CreatePort( char *name (A1) ); 分配并初始化消息端口。如果 name 不为 NULL,则将端口添加到系统公共端口列表。如果 name 为 NULL,则端口将成为私有端口,如果需要,可以稍后将其添加到公共端口列表。(在这种情况下,mp_Node.ln_Name 必须在调用 AddPort() 之前初始化。)
void DeletePort( struct MsgPort *port (A1) ); 释放 CreatePort() 创建的消息端口。如果端口是公共端口,它也将从系统列表中删除。
请记住,删除公共端口并不完全安全。请参阅 RemPort() 下的说明。
struct MsgPort *FindPort( char *name (A1) ); 在端口列表中搜索指定端口名称。如果未找到端口,则返回 NULL。
请记住,端口名称不唯一。
void WaitPort( struct MsgPort *port (A0) ); 等待端口中有消息。不从端口中删除消息。使用 GetMsg() 获取消息。如果已经有消息在等待,则不会进行等待。
void PutMsg( struct MsgPort *port (A0), struct Message *message (A1) ); 将消息放到指定的端口。在此调用之后,消息将成为接收者的财产,在通过 replyport 收回之前,不应对其进行修改。mn_Node.ln_Type 将变为 NT_MESSAGE。
单向通信也是可能的。在这种情况下,接收者不会回复消息,并且必须释放或重新使用接收到的消息。这只有在像这样编写的任务之间才有可能。
struct Message *GetMsg( struct MsgPort *port (A0) ); 从端口获取消息。如果没有消息在等待,则返回 NULL。
请记住,可能有多条消息已到达消息端口,而您只获得了一条信号。使用 while() 来处理所有消息,然后再等待。如果您使用 WaitPort(),它会为您处理这个问题(在等待之前检查端口)。此外,必须特别注意确保在退出之前消息端口为空。
void ReplyMsg( struct Message *message (A1) ); 将消息返回其回复端口。mn_Node.ln_Type 将变为 NT_REPLYMSG。
限制:ReplyPort 永远不能是软中断端口。(但是您无法使用 CreatePort() 创建一个:-))。
void SendTimerReq( struct TimerReq *request (A1) ); 将计时器请求发送到系统。
void AddDevice( struct Device *device (A1) ); 将设备添加到系统。设备结构应初始化并准备接受 Open() 调用。
struct Device *RemDevice( struct Device *device (A1) ); 从系统中删除设备。如果设备被任何人打开,则该调用返回 NULL。
LONG OpenDevice( char *name (A1), ULONG unit (D0), struct StdIOReq *req (A0), ULONG flags (D1)); 打开指定的设备。struct StdIOReq 由 OpenDevice() 调用和内部设备 Open() 调用填充。设备本身维护 lib_OpenCnt。如果打开成功,则返回 0。
unit、req 和 flags 被推入堆栈,用于 C 语言 Open() 处理程序。
void CloseDevice( struct StdIOReq *req (A1) ); 关闭由 OpenDevice() 打开的设备(对设备的 Close() 入口进行内部调用)。设备本身维护 lib_OpenCnt。
req 被推入堆栈,用于 C 语言 Close() 处理程序。
LONG DoIO( struct StdIOReq *req (A1) ); 启动并等待 IO 请求完成。(内部调用 SendIO()/ WaitIO())。
LONG SendIO( struct StdIOReq *req (A1) ); 向设备发送请求,但不等待回复。内部调用设备的 BeginIO() 入口。
req 被推入堆栈,用于 C 语言 BeginIO() 处理程序。
struct StdIOReq *CheckIO( struct StdIOReq *req (A1) ); 检查请求是否已返回。仅检查 mn_Node.ln_Type == NT_REPLYMSG。
LONG WaitIO( struct StdIOReq *req (A1) ); 等待请求完成并将其从回复端口中删除。不要在未发送的请求上调用 WaitIO()!
void AbortIO( struct StdIOReq *req (A1) ); 尝试通过内部调用设备的 AbortIO() 入口来中止请求的处理。设备需要实现此功能。您应该在 AbortIO() 之后调用 WaitIO()。
req 被推入堆栈,用于 C 语言 AbortIO() 处理程序。
void AddLibrary( struct Library *library (A1) ); 将一个库添加到系统中。库应该正确初始化并准备好接收 Open() 调用。
struct Library *RemLibrary( struct Library *library (A1) ); 从系统中移除一个库。如果库当前被任何用户打开,则调用返回 NULL。
void InitLibFuncs( struct Library *lib (A0), ULONG *funcs (A1), ULONG num (D0) ); 将库调用向量初始化为 funcs 数组中指定的地址。数组的第一个元素成为偏移量 -6 中跳转的地址,下一个成为偏移量 -12 中的地址,以此类推。库结构应该具有至少 6*num 的负大小,以确保此调用不会覆盖任何内容。
struct Library *OpenLibrary( char *name (A1) ); 打开指定的库。如果库不存在或无法打开,则返回 NULL。否则返回库的库基址。在内部,库的 Open() 入口被调用,并检查返回值以确定打开是否成功。库本身维护 lib_OpenCnt。
void CloseLibrary( struct Library *library (A1) ); 关闭之前打开的库。在内部,库的 Close() 入口被调用。库本身维护 lib_OpenCnt。
ULONG SetFunction( struct Library *library (A1), LONG offset (D0), ULONG val (D1) ); 将指定的库调用向量更改为指向新的例程。返回向量的老值。offset 是调用库时使用的负调用偏移量。
不要尝试修补 Disable()/Enable() 或者 Forbid()/Permit()。
BOAR 时钟运行 UTC (GMT) 时间。shell 变量 TIMEZONE 应该用于进行调整以获得本地时间。 void DateStamp( struct DateStamp *date (A0) ); 将返回当前系统时间。这是一个原子操作,不需要禁用中断。
void SetDate( struct DateStamp *date (A0) ); 将设置新的系统时间。这是一个原子操作,不需要禁用中断。
信号量函数使用 C 语言调用约定(参数在堆栈中)。 void InitSemaphore( struct SignalSemaphore *sem ); 初始化一个用于使用的私有信号量。
void AddSemaphore( struct SignalSemaphore *sem, const char *name, BYTE pri ); 将一个信号量添加到系统的公共信号量列表中。所有初始化都由 AddSemaphore() 完成。
void RemSemaphore( struct SignalSemaphore *sem ); 从系统的公共信号量列表中移除一个信号量。
注意:信号量上可能仍然存在锁,并且可能还有待处理的请求。要移除一个公共信号量,请使用引用计数,如下所示
Forbid(); while(1) { if(sem->ss_Public == 0) { RemSemaphore(sem); break; } /* Delay() breaks Forbid() state */ Delay(100); } Permit();
struct SignalSemaphore *FindSemaphore( const char *name ); 在系统的公共信号量列表中搜索一个信号量。如果找不到指定的信号量,则返回 NULL。使用 FreeSemaphore() 来放弃获取的信号量句柄。
请记住,系统不保证公共信号量名称是唯一的。
void FreeSemaphore( struct SignalSemaphore *reference ); 结束对通过 FindSemaphore() 获取的公共信号量的使用。
LONG AttemptSemaphore( struct SignalSemaphore *sem ); 尝试在不阻塞的情况下获取信号量的独占锁。失败返回零,成功返回非零值。
LONG AttemptSemaphoreShared( struct SignalSemaphore *sem ); 尝试在不阻塞的情况下获取信号量的共享锁。失败返回零,成功返回非零值。
void ObtainSemaphore( struct SignalSemaphore *sem ); 获取信号量的独占(写)锁。阻塞(等待)直到获取锁。
void ObtainSemaphoreShared( struct SignalSemaphore *sem ); 获取信号量的共享(读)锁。阻塞(等待)直到获取锁。
void ReleaseSemaphore( struct SignalSemaphore *sem ); 释放获取的信号量锁,无论是共享的还是独占的。
我还检查了 AmigaOS v4 SDK,并找到了以下函数
exec.library/GetCPUInfo exec.library/GetCPUInfo NAME GetCPUInfo -- Get information about the current CPU (V50) SYNOPSIS void GetCPUInfo(struct TagItem *tagList); void GetCPUInfoTags(ULONG tag1, ...); FUNCTION This function is used to retrieve information about the CPU(s) installed. This function replaces the ExecBase attention flag mechanism. INPUTS Input to this function is a tag list containing the items to be queried. Each tag item's data must point to a sufficiently large storage where the result is copied to. The list of tag items below lists the size of the required storage in brackets. For example, GCIT_NumberOfCPUs requires a pointer to an uint32, GCIT_ProcessorSpeed requires a pointer to a variable which is of type uint64. Currently, the following items are available: GCIT_NumberOfCPUs (uint32 *) Number of CPUs available in the system. This is likely to be 1 at the moment. GCIT_Family (uint32 *) CPU family as a symbolic constant. Currently, these are defined: CPUFAMILY_UNKNOWN - Unknown CPU CPUFAMILY_60X - All PowerPC 60x, like 603 and 604 CPUFAMILY_7X0 - All G3 PowerPC 7X0, like 740, 750, 750CXe, 750FX CPUFAMILY_74xx - All G4 PowerPC 74xx, like 7400, 7410, 7441 GCIT_Model (uint32 *) CPU model as a symbolic constant. Currently, these are defined: CPUTYPE_UNKNOWN - Unknown CPU CPUTYPE_PPC603E - PowerPC 603e CPUTYPE_PPC604E - PowerPC 604e CPUTYPE_PPC750CXE - PowerPC 750CXe CPUTYPE_PPC750FX - PowerPC 750FX CPUTYPE_PPC750GX - PowerPC 750GX CPUTYPE_PPC7410 - PowerPC 7410 CPUTYPE_PPC74XX_VGER - PowerPC 7440, 7441, 7450, 7451 (Vger types) CPUTYPE_PPC74XX_APOLLO - PowerPC 7445, 7447, 7455, 7457 (Apollo 6/7 types) GCIT_ModelString (CONST_STRPTR *) CPU model as a read-only string. For example, the 604e would be returned as "PowerPC 604e". GCIT_Version (uint32 *) CPU version and revision. The major and minor numbers are returned as a number with the lower 16 bit as 0xVV.R, where VV is the version number, and R is the revision. For example, on a PPC750FX DD2, the result would be 0x0201, depicting a PowerPC 750FX V2.1. Note: If a version is not available, the value returned is 0. GCIT_VersionString (CONST_STRPTR *) CPU version and revision as a read-only string, in the form "major.minor". GCIT_FrontsideSpeed (uint64 *) CPU frontside bus speed. Note: This is actually a 64 bit number. GCIT_ProcessorSpeed (uint64 *) CPU internal frequency. Note: This is actually a 64 bit number. GCIT_L1CacheSize (uint32 *) GCIT_L2CacheSize (uint32 *) GCIT_L3CacheSize (uint32 *) Size of the appropriate cache, if available, otherwise 0. GCIT_CacheLineSize (uint32 *) Size of a cache line. Note that this is also the alignment used by CacheClearE/CacheClearU. GCIT_VectorUnit (uint32 *) CPU's vector unit, as a symbolic constant. Currently defined are: VECTORTYPE_NONE - No vector unit VECTORTYPE_ALTIVEC - Motorola AltiVec (tm) Unit (VECTORTYPE_VMX - IBM VMX Unit) GCIT_Extensions (CONST_STRPTR *) CPU feature string. The result is a read-only string that describes nonstandard features of the CPU. GCIT_CPUPageSize (uint32 *) GCIT_ExecPageSize (uint32 *) Physical and logical page sizes. CPUPageSize determines the supported sizes of the CPU, while ExecPageSize determines the supported Exec (i.e. "virtual" page sizes).The latter is the size supported by Exec API functions. In general, these tags return a bit mask. If bit n is set, a page size of 2^n is supported. For example, GCIT_CPUPageSize might return 0x1000, i.e. bit 12 is set, hence the CPU supports hardware pages of 4096 bytes. GCIT_TimeBaseSpeed (uint64 *) Speed of the CPU timer. Note: This is actually a 64 bit number. EXAMPLE /* Query model and version */ CONST_STRPTR Model; CONST_STRPTR Version; IExec->GetCPUInfoTags( GCIT_ModelString, &Model, GCIT_VersionString, &Version, TAG_DONE); printf("CPU: %s V%s\n", Model, Version);
MorphOS 在 exec.library 中具有此函数
exec.library/NewGetSystemAttrsA NAME NewGetSystemAttrsA -- Get Exec Info (V50) NewGetSystemAttrs -- Varargs Stub for NewGetSystemAttrsA() SYNOPSIS Result NewGetSystemAttrsA(Data,DataSize,Type,Tags ) D0 A0 D0 D1 A1 ULONG NewGetSystemAttrsA(void*,ULONG,ULONG,struct TagItem*); ULONG NewGetSystemAttrs(void*,ULONG,ULONG,...); FUNCTION Allows you to get informations about the system like cpu type, caches and so on. INPUTS Data - Ptr to a buffer DataSize - size of the buffer Type - Information Type Tags - Additional argument buffer for the type o SYSTEMINFOTYPE_SYSTEM returns the System family. Tags: none Data: String[DataSize] o SYSTEMINFOTYPE_VENDOR returns the System Vendor. Tags: none Data: String[DataSize] o SYSTEMINFOTYPE_REVISION returns the System revision *string*. Sorry, this is Openfirmware heritage. Tags: none Data: String[DataSize] o SYSTEMINFOTYPE_MAGIC1 returns the Magic1 field in ExecBase. Tags: none Data: u_int32_t o SYSTEMINFOTYPE_MAGIC2 returns the Magic2 field in ExecBase. Tags: none Data: u_int32_t o SYSTEMINFOTYPE_MACHINE returns the CPU family. Currently only PowerPC Tags: none Data: u_int32_t o SYSTEMINFOTYPE_PAGESIZE returns the mmu page size which is needed for mmu related routines. Can return 0 if no mmu is there in some embedded systems. Data: u_int32_t o SYSTEMINFOTYPE_PPC_CPUVERSION returns the CPU type. Depends on CPU family. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CPUREVISION returns the CPU revision. Depends on CPU family. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CPUCLOCK returns the CPU clock. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int64_t o SYSTEMINFOTYPE_PPC_BUSCLOCK returns the CPU bus clock. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int64_t o SYSTEMINFOTYPE_PPC_CACHEL1TYPE returns the CPU L1 Type. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CACHEL1FLAGS returns the CPU L1 Flags. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL1SIZE returns the CPU L1 instruction cache size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL1LINES returns the CPU L1 instruction cache line count. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL1LINESIZE returns the CPU L1 instruction cache line size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL1SIZE returns the CPU L1 data cache size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL1LINES returns the CPU L1 data cache line count. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL1LINESIZE returns the CPU L1 data cache line size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CACHEL2TYPE returns the CPU L2 Type. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CACHEL2FLAGS returns the CPU L2 Flags. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL2SIZE returns the CPU L2 instruction cache size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL2LINES returns the CPU L2 instruction cache line count. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL2LINESIZE returns the CPU L2 instruction cache line size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL2SIZE returns the CPU L2 data cache size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL2LINES returns the CPU L2 data cache line count. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL2LINESIZE returns the CPU L2 data cache line size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CACHEL3TYPE returns the CPU L3 Type. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CACHEL3FLAGS returns the CPU L3 Flags. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL3SIZE returns the CPU L3 instruction cache size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL3LINES returns the CPU L3 instruction cache line count. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ICACHEL3LINESIZE returns the CPU L3 instruction cache line size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL3SIZE returns the CPU L3 data cache size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL3LINES returns the CPU L3 data cache line count. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DCACHEL3LINESIZE returns the CPU L3 data cache line size. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_FPU returns >0 if the CPU supports FPU instructions. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_ALTIVEC returns >0 if the CPU supports Altivec instructions. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_PERFMONITOR returns 1 if the CPU supports the Performance Monitor Unit. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DATASTREAM returns 1 if the CPU supports datastream instructions. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_RESERVATIONSIZE returns the alignment size of the reservation instructions like lwarx. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_BUSTICKS returns the bus ticks the cpu needs to increase the timer. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_CPUTEMP returns the cpu temperature in 8.24 fixedpoint celcius degrees. might not be implemented for all cpu types and MorphOS versions. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_PPC_DABR returns 1 if the CPU supports Data Address Breakpoint Register. Tags: SYSTEMINFOTAG_CPUINDEX (optional) for the CPU Number Data: u_int32_t o SYSTEMINFOTYPE_TBCLOCKFREQUENCY returns the timebase clock frequency used for system timing. this is the same value as returned by timer.device/ReadCPUClock base. Data: u_int32_t o SYSTEMINFOTYPE_UPTIMETICKS returns the total system uptime in timebase ticks. Data: u_int64_t o SYSTEMINFOTYPE_LASTSECTICKS returns number of timebase ticks for 'lastsec' measurement. To get accurate measurements, you should Forbid(), read this tag value, read other *LASTSEC* values, Permit(). Then do the math. Data: u_int64_t o SYSTEMINFOTYPE_RECENTTICKS returns number of timebase ticks for 'recent' measurement. To get accurate measurements, you should Forbid(), read this tag value, read other *RECENT* values, Permit(). Then do the math. Data: u_int64_t o SYSTEMINFOTYPE_CPUTIME returns the total system cpu usage in timebase ticks for SYSTEMINFOTYPE_UPTIMETICKS time. Data: u_int64_t o SYSTEMINFOTYPE_LASTSECCPUTIME returns the system cpu usage in timebase ticks for SYSTEMINFOTYPE_LASTSECTICKS time. Data: u_int64_t o SYSTEMINFOTYPE_RECENTCPUTIME returns the decaying average system cpu usage in timebase ticks for SYSTEMINFOTYPE_RECENTTICKS time. Data: u_int64_t o SYSTEMINFOTYPE_VOLUNTARYCSW returns the total number of voluntary task context switches (task called Wait(), or RemTask()ed self). Data: u_int64_t o SYSTEMINFOTYPE_INVOLUNTARYCSW returns the total number of involuntary task context switches (task was busy and ran for Quantum slice and other task at the same priority was made running, or higher priority task appeared and was made running). Data: u_int64_t o SYSTEMINFOTYPE_LASTSECVOLUNTARYCSW returns the number of voluntary task context switches for SYSTEMINFOTYPE_LASTSECTICKS time. Data: u_int32_t o SYSTEMINFOTYPE_LASTSECINVOLUNTARYCSW returns the number of involuntary task context switches for SYSTEMINFOTYPE_LASTSECTICKS time. Data: u_int32_t o SYSTEMINFOTYPE_LOADAVG1 returns the average system load for the last minute. The returned value is 10.11 fixedpoint value. To get floating point value use: (load / 2048.0f); Data: u_int32_t o SYSTEMINFOTYPE_LOADAVG2 returns the average system load for the last three minutes. The returned value is 10.11 fixedpoint value. To get floating point value use: (load / 2048.0f); Data: u_int32_t o SYSTEMINFOTYPE_LOADAVG3 returns the average system load for the last fifteen minutes. The returned value is 10.11 fixedpoint value. To get floating point value use: (load / 2048.0f); Data: u_int32_t o SYSTEMINFOTYPE_TASKSCREATED returns the total number of tasks created. Data: u_int64_t o SYSTEMINFOTYPE_TASKSFINISHED returns the total number of tasks deleted. Data: u_int64_t o SYSTEMINFOTYPE_TASKSRUNNING returns the total number of running tasks. Data: u_int32_t o SYSTEMINFOTYPE_TASKSLEEPING returns the total number of waiting tasks. Data: u_int32_t o SYSTEMINFOTYPE_LAUNCHTIMETICKS returns the timebase for system (sheduler) startup, starting from 0. Data: u_int64_t o SYSTEMINFOTYPE_LAUNCHTIMETICKS1978 returns the timebase for system (sheduler) startup, starting from Jan 1st 1978. this is useful for formatting system boottime with dos/DateToStr. Data: u_int64_t o SYSTEMINFOTYPE_EMULHANDLESIZE returns the emulhandle's size. Data: u_int32_t o SYSTEMINFOTYPE_EXCEPTIONMSGPORT returns the global native exception handler's msgport. Data: u_int32_t o SYSTEMINFOTYPE_TASKEXITCODE returns the global native task's exitcode. Data: u_int32_t o SYSTEMINFOTYPE_TASKEXITCODE_M68K returns the global m68k task's exitcode. Data: u_int32_t o SYSTEMINFOTYPE_EMULATION_START returns the address of the abox emulation area. Data: u_int32_t o SYSTEMINFOTYPE_EMULATION_SIZE returns the size of the abox emulation area. Data: u_int32_t o SYSTEMINFOTYPE_MODULE_START returns the address of the module area. Data: u_int32_t o SYSTEMINFOTYPE_MODULE_SIZE returns the size of the module area. Data: u_int32_t o SYSTEMINFOTYPE_EXCEPTIONMSGPORT returns the native Machine Exception MsgPort Data: struct MsgPort* o SYSTEMINFOTYPE_EXCEPTIONMSGPORT_68K returns the 68K Exception MsgPort. This is the msgport the default system 68k traphandler sends its msgs. If the system 68k traphandler is replaced msgs to this exception msgport aren't guranteed. Usually the msgport is controlled by the ABox Log Server. Data: struct MsgPort* o SYSTEMINFOTYPE_ALERTMSGPORT returns the Alert MsgPort. Usually the msgport is controlled by the ABox Log Server. Data: struct MsgPort* o SYSTEMINFOTYPE_MAXHITCOUNT returns the maxhit default for tasks. Data: ULONG o SYSTEMINFOTYPE_MAXALERTCOUNT returns the max alerts default for tasks. Data: ULONG o SYSTEMINFOTYPE_REGUSER returns the registered user Tags: none Data: String[DataSize] o SYSTEMINFOTYPE_FREEBLOCKS returns information about the free memory blocks Tags: SYSTEMINFOTAG_MEMHEADER (optional) to specify single memheader, defaults to all system memory SYSTEMINFOTAG_HOOK (optional) call hook rather than filling array. See exec/system.h Data: struct MemEntry [] RESULT Result is the amount of bytes returned at Data.
如果您有兴趣实现它,我会向您提供其余的信息(LVO 和定义)。但是请注意,目前除了 PPC 之外的其他 CPU 不存在任何定义。
- 它在 Exec 设备中使用 DOS 错误代码。
- 它添加了一个意大利面条式的 goto 语句。
- 它将 iotd 转换为它本来就有的类型。
- 其他一些看似无意义的代码更改,例如用 iotd 参数替换 eject 参数,在单个项周围添加括号等等。