From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,88c174f3c455ecdc,start X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 2001-09-06 11:18:12 PST Path: archiver1.google.com!newsfeed.google.com!newsfeed.stanford.edu!news-spur1.maxwell.syr.edu!news.maxwell.syr.edu!newspeer.monmouth.com!news.monmouth.com!shell.monmouth.com!not-for-mail From: ka@sorry.no.email (Kenneth Almquist) Newsgroups: comp.lang.ada Subject: Thoughts on the ADaOS user program interface Date: 6 Sep 2001 14:18:04 -0400 Organization: A poorly-installed InterNetNews site Message-ID: <9n8eks$qsp$1@shell.monmouth.com> NNTP-Posting-Host: shell.monmouth.com Xref: archiver1.google.com comp.lang.ada:12830 Date: 2001-09-06T14:18:04-04:00 List-Id: I've been thinking about what the system call architecture for an Ada OS might look like. I would want the interface to be specified in a collection of packages, all descendents of a package named AdaOS or somthing similar. The Intel x86 architecture supports procedure calls across protection boundaries (so that user code can invoke procedures in the kernel), but the design is not secure if you have multiple tasks because the kernel mode procedure runs on the same stack as its caller, which allows another task running concurrently to alter the stack frame of the kernel mode procedure. So calling a system procedure requires a trap to allow us to switch to a kernel stack. We could generate a stub for each call which executes a trap instruction (that is what Linux does), or we could tell the linker that the system procedures are at addresses which are not actually mapped, and have the trap handler invoke the actual kernel routine. Some things are best modelled using tagged types; the obvious example being the AdaOS analogue to UNIX file descriptors. These will be implemented as tagged types inside the kernel, but we also want them to appear as tagged types to applications. One idea is to have the compiler implement pragma Separate_Tag(Tagged_Type); which specifies that the named tagged type doesn't contain a tag field. Instead, pointers to Tagged_Type'Class are "fat pointers" containing both the address of an object and its associated tag. This allows user code to transparently use pointers to objects which are not in the user's address space. An alterative is to create a "proxy" object in user space for each kernel object that a program uses. This complicates the interface to the kernel but avoids compiler changes. (Note: In addition to the use suggested here, pragma Separate_Tag would improve the performance of types like Ada.Strings.Unbounded, where a tagged type is required but the tag is rarely used. When this pragma is applied to a type, it is not possible to redispatch in the primitive subprograms for that type, because that would require using the tag in a context where there is no class-wide pointer to the object.) The UNIX ioctl system call bypasses the C type system. Using tagged types lets us provide similar functionality while staying within the Ada type system. Code that operates on tty devices might look like: if F in Tty_Devices then Tty = Tty_Devices(F); Get_Terminal_Modes(Tty, Modes); ... end if; Furthermore, users can implement their own data types which have the same interface as system types, and use their types interchangably with system types. So you can write versions of the "file" data type that do things like perform compression or read/write to in-memory data structures rather than files, without involving the operating system. Contrast this to UNIX, where a file descriptor can refer to a variety of entities, but extending this set requires kernel modifications. Error reporting should be done by throwing exceptions with a descriptive error message attached. My experience is that more often than not, if a system call fails the program logic doesn't care why it fails. Once the error is reported or logged, the recovery action is the same. Therefore, I would suggest that exceptions be defined which relate to the type of operation which failed, rather than the reason the operation failed. The exception message associated with the exception should consist of an error code followed by descriptive text. The error code allows a program to take actions that depend on the precise reason that an operation failed, when this is necessary. It also simplifies the task of converting error messages to a language other than English. There should be a stub generator to create the linkage code to allow kernel subroutines to be called from user mode. The linkage code needs to handle arguments as follows: * Parameters passed by value (e.g. integers) need to be copied into kernel space. * Parameters passed by reference do not need to be copied (assuming that the address mapping is set up appropriately so that user data can be read and written from kernel mode), but the address of the data has to be validated to ensure that it actually is used data and not kernel data. Also, the bounds of an array or string need to be copied into kernel space so that they don't change after address validation is performed. * Pointers to kernel data structures need to be validated to ensure that they actually point to an object of the appropriate type. It is probably most efficient to make the pointer value used in user space actually be an index into a table of kernel objects. The validation process would look up the actual kernel address in this table. An initial implementation of these ideas could be done by adding code to Linux or BSD. This would allow an initial implementation to be put together fairly quickly, allowing experience to be gained. Kenneth Almquist