INITIALIZATION Knowledgebase: ki-dev-large Base Query: what are some add on mods available? Model: gemini-1.5-pro-preview-0409 Use Curl?: None ================================================== **Elapsed Time: 0.00 seconds** ================================================== ROUTING Query type: list ================================================== **Elapsed Time: 2.05 seconds** ================================================== RAG PARAMETERS Max Context To Include: 100 Lowest Score to Consider: 0.1 ================================================== **Elapsed Time: 0.14 seconds** ================================================== VECTOR SEARCH ALGORITHM TO USE Use MMR search?: True Use Similarity search?: False ================================================== **Elapsed Time: 0.10 seconds** ================================================== VECTOR SEARCH DONE ================================================== **Elapsed Time: 6.39 seconds** ================================================== FILENAMES AND KEYWORDS Keywords Found: ['available'] All file names: ['arsnouveau-wiki-44789.txt', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Introduction%20to%20Quantum%20Information%20-%20Stephen%20M.%20Barnett%20%28PDF%29.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'Web%20API%20Design%20-%20Brian%20Mulloy%20%28PDF%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf'] File names to FILTER: ['arsnouveau-wiki-44789.txt', 'Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf', 'The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf'] ================================================== **Elapsed Time: 1.05 seconds** ================================================== PRIMER Primer: You are Simon, a highly intelligent personal assistant in a system called KIOS. You are a chatbot that can read knowledgebases through the "CONTEXT" that is included in the user's chat message. Your role is to act as an expert at summarization and analysis. In your responses to enterprise users, prioritize clarity, trustworthiness, and appropriate formality. Be honest by admitting when a topic falls outside your scope of knowledge, and suggest alternative avenues for obtaining information when necessary. Make effective use of chat history to avoid redundancy and enhance response relevance, continuously adapting to integrate all necessary details in your interactions. Use as much tokens as possible to provide a detailed response. ================================================== **Elapsed Time: 0.00 seconds** ================================================== FINAL QUERY Final Query: CONTEXT: ########## File: arsnouveau-wiki-44789.txt Page: 1 Context: ARS NOUVEAU WIKI Bailey Ars Nouveau Team Addon Selection English Spanish Spanish Catalan Japanese Korean Russian Chinese - Simplified Chinese - Traditional Afrikaans Arabic Czech Danish German Greek Finish French Hebrew Hungarian Italian Korean Dutch Norwegiah Polish Portuguese (Brazil) Portuguese (Portugal) Romanian Russian Serbian Swedish Turkish Ukrainian Vietnamese GETTING STARTED RESOURCES AND DECORATION TIER 1 GLYPHS TIER 2 GLYPHS TIER 3 GLYPHS SOURCE MACHINES AUTOMATION ENCHANTING MAGICAL EQUIPMENT RITUALS FAMILIARS ARMOR AND PERKS MOD NEWS Welcome to the automatically generated Wiki of Ars Nouveau! This Wiki is automatically generated from the Patchouli Files in the Ars Nouveau Repository. Therefore, there's some things that just can't be displayed, like the GeckoLib-Rendered Spellbook, Blocks, Keybinds and other things. It can also be a bit janky, with missing names or unexpected errors. If you encounter any issues, please reach out in the Ars Nouveau Discord found here or submit an issue here! If you got taken to this page automatically when trying to open the Worn notebook in Minecraft version 1.20 and you don't want this to happen, add Patchouli to your modpack You can now select which Addon Mods you want to include in the wiki! Currently available: Ars Elemental Ars G.E.A.R.S Ars Instrumentum Ars Mage Fight Ars Ocultas Ars Omega Ars Scalaes Starbuncle Mania Tome of Blood Rebirth Too Many Glyphs (Beta, repository doesn't have all the necessary information) Not all Addons are available for the current version and addon documentation functionality is less tested than Ars Nouveau Wiki functionality. Use at your own risk. Direct links to addon pages or refreshing addon pages is currently not supported, since the server doesn't know which addons to load. Ars Nouveau Wiki #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 2 Context: I'm unable to assist with that. #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 3 Context: ```markdown # Contents 1. [Introduction](#introduction) 7 - [About the Book](#about-the-book) 7 - [The Reader](#the-reader) 8 - [Credits, Thanks and Acknowledgements](#credits-thanks-and-acknowledgements) 8 - [Contributors](#contributors) 8 - [Changes and Corrections](#changes-and-corrections) 8 - [Issues and where to get help](#issues-and-where-to-get-help) 9 - [License](#license) 9 2. [First Steps](#first-steps) 11 - [Tools](#tools) 11 - [Quick Setup](#quick-setup) 11 - [Programming Languages](#programming-languages) 11 - [Host Operating System](#host-operating-system) 12 - [Build System](#build-system) 12 - [Virtual Machine](#virtual-machine) 12 - [Booting](#booting) 12 - [BIOS](#bios) 12 - [The Bootloader](#the-bootloader) 13 - [The Operating System](#the-operating-system) 13 - [Hello Cafebabe](#hello-cafebabe) 13 - [Compiling the Operating System](#compiling-the-operating-system) 13 - [Linking the Kernel](#linking-the-kernel) 14 - [Obtaining GRUB](#obtaining-grub) 15 - [Building an ISO Image](#building-an-iso-image) 15 - [Running Bochs](#running-bochs) 16 3. [Further Reading](#further-reading) 17 ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 4 Context: ```markdown # 3 Getting to C 19 - **Setting Up a Stack** .......................................... 19 - **Calling C Code From Assembly** .................................. 20 - Packing Structs ............................................... 20 - **Compiling C Code** ............................................... 21 - **Build Tools** ................................................... 21 - **Further Reading** ................................................ 22 # 4 Output 23 - **Interacting with the Hardware** .................................. 23 - **The Framebuffer** ................................................ 23 - Writing Text .................................................... 23 - Moving the Cursor ............................................... 25 - The Driver ..................................................... 26 - **The Serial Ports** ............................................... 26 - Configuring the Serial Port ..................................... 27 - Configuring the Line ............................................ 27 - Configuring the Buffers ......................................... 29 - Configuring the Modem .......................................... 29 - Writing Data to the Serial Port ................................ 30 - Configuring Bochs .............................................. 31 - The Driver ..................................................... 31 - **Further Reading** ............................................... 31 # 5 Segmentation 33 - **Accessing Memory** ............................................... 33 - **The Global Descriptor Table (GDT)** ................................ 35 - **Loading the GDT** ............................................... 36 - **Further Reading** ............................................... 37 # 6 Interrupts and Input 39 - **Interrupts Handlers** ............................................ 39 - **Creating an Entry in the IDT** .................................. 39 - **Handling an Interrupt** .......................................... 40 - **Creating a Generic Interrupt Handler** .......................... 41 - **Loading the IDT** ............................................... 43 ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 8 Context: ```markdown # Reference Material The OS was developed in about six weeks of full-time work. The implementation was done in many small steps, and after each step the OS was tested manually. By developing in this incremental and iterative way, it was often easier to find any bugs that were introduced, since only a small part of the code had changed since the last known good state of the code. We encourage the reader to work in a similar way. During the six weeks of development, almost every single line of code was written by the authors together (this way of working is also called pair-programming). It is our belief that we managed to avoid a lot of bugs due to this style of development, but this is hard to prove scientifically. ## The Reader The reader of this book should be comfortable with UNIX/Linux, systems programming, the C language, and computer systems in general (such as hexadecimal notation [3]). This book could be a way to get started learning those things, but it will be more difficult, and developing an operating system is already challenging on its own. Search engines and other tutorials are often helpful if you get stuck. ## Credits, Thanks and Acknowledgements We'd like to thank the OSDev community [4] for their great wiki and helpful members, and James Malloy for his eminent kernel development tutorial [5]. We'd also like to thank our supervisor Torbjørn Granlund for his insightful questions and interesting discussions. Most of the CSS formatting of the book is based on the work by Scott Chacon for the book Pro Git, [http://progit.org](http://progit.org). ## Contributors We are very grateful for the patches that people send us. The following users have all contributed to this book: - alexschneider - Arvidanborisov - nirs - kedarambaswade - vamanaea - ansjob ## Changes and Corrections This book is hosted on GitHub - if you have any suggestions, comments or corrections, just fork the book, write your changes, and send us a pull request. We'll happily incorporate anything that makes this book better. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 9 Context: ```markdown # Issues and where to get help If you run into problems while reading the book, please check the issues on GitHub for help: [GitHub Issues](https://github.com/littleosbook/littleosbook/issues). # License All content is under the Creative Commons Attribution Non Commercial Share Alike 3.0 license: [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/). The code samples are in the public domain - use them however you want. References to this book are always received with warmth. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 12 Context: ```markdown # Host Operating System All the code examples assume that the code is being compiled on a UNIX-like operating system. All code examples have been successfully compiled using Ubuntu [6] versions 11.04 and 11.10. # Build System Make [13] has been used when constructing the Makefile examples. # Virtual Machine When developing an OS, it is very convenient to be able to run your code in a virtual machine instead of on a physical computer, since starting your OS in a virtual machine is much faster than getting your OS onto a physical medium and then running it on a physical machine. Bochs [14] is an emulator for the x86 (IA-32) platform which is well suited for OS development due to its debugging features. Other popular choices are QEMU [15] and VirtualBox [7]. This book uses Bochs. By using a virtual machine, we cannot ensure that our OS works on real physical hardware. The environment simulated by the virtual machine is designed to be very similar to their physical counterparts, and the OS can be tested on one by just copying the executable to a CD and finding a suitable machine. # Booting Booting an operating system consists of transferring control along a chain of small programs, each one more "powerful" than the previous one, where the operating system is the last "program". See the following figure for an example of the boot process: ``` BIOS → GRUB1 → GRUB2 → OS ``` **Figure 2.1**: An example of the boot process. Each box is a program. # BIOS When the PC is turned on, the computer will start a small program that adheres to the Basic Input Output System (BIOS) [16] standard. This program is usually stored on a read-only memory chip on the motherboard of the PC. The original role of the BIOS program was to export some library functions for printing to the screen, reading keyboard input, etc. Modern operating systems do not use the BIOS' functions; they use... ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 14 Context: ```markdown ```assembly dd CHECKSUM ; and the checksum loader: ; the loader label (defined as entry point in linker script) mov eax, 0xCAFEBABE ; place the number 0xCAFEBABE in the register eax .loop: jmp .loop ; loop forever ``` The only thing this OS will do is write the very specific number 0xCAFEBABE to the eax register. It is very unlikely that the number 0xCAFEBABE would be in the eax register if the OS did not put it there. The file `loader.s` can be compiled into a 32 bits ELF [18] object file with the following command: ```bash nasm -f elf32 loader.s ``` ## Linking the Kernel The code must now be linked to produce an executable file, which requires some extra thought compared to when linking most programs. We want GRUB to load the kernel at a memory address larger than or equal to `0x00100000` (1 megabyte (MB)), because addresses lower than 1 MB are used by GRUB itself, BIOS and memory-mapped I/O. Therefore, the following linker script is needed (written for GNU LD [20]): ```ld ENTRY(loader) /* the name of the entry label */ SECTIONS { . = 0x00100000; /* the code should be loaded at 1 MB */ .text ALIGN(0x1000) : /* align at 4 KB */ { *(.text) /* all text sections from all files */ } .rodata ALIGN(0x1000) : /* align at 4 KB */ { *(.rodata) /* all read-only data sections from all files */ } .data ALIGN(0x1000) : /* align at 4 KB */ { *(.data) /* all data sections from all files */ } .bss ALIGN(0x1000) : /* align at 4 KB */ { *(.COMM) /* all COMMON sections from all files */ *(.bss) /* all bss sections from all files */ } } ``` Save the linker script into a file called `link.ld`. The executable can now be linked with the following command: ```bash ld -o kernel.bin -T link.ld loader.o ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 16 Context: ```markdown genisoimage -R \ -b boot/grub/stage2_eltorito \ -no-emul-boot \ -boot-load-size 4 \ -boot-info-table \ -input-charset utf8 \ -quiet \ -o os.iso \ iso For more information about the flags used in the command, see the manual for `genisoimage`. The ISO image `os.iso` now contains the kernel executable, the GRUB bootloader, and the configuration file. ## Running Bochs Now we can run the OS in the Bochs emulator using the `os.iso` ISO image. Bochs needs a configuration file to start and an example of a simple configuration file is given below: ``` megs: 32 display_library: sdl romimage: file=/usr/share/bochs/BIOS-bochs-latest vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest ata0-master: type=disk, path=./os.iso, status=inserted boot: cdrom log: bochlog.txt clock: sync=realtime, time=local cpu: count=1, ips=1000000 ``` You might need to change the path to `romimage` and `vgaromimage` depending on how you installed Bochs. More information about the Bochs config file can be found at Bochs's website [23]. If you saved the configuration in a file named `bochsrc.txt`, then you can run Bochs with the following command: ``` bochs -f bochsrc.txt -q ``` The flag `-f` tells Bochs to use the given configuration file and the flag `-q` tells Bochs to skip the interactive start menu. You should now see Bochs starting and displaying a console with some information from GRUB on it. After quitting Bochs, display the log produced by Bochs: ``` cat bochlog.txt ``` You should now see the contents of the registers of the CPU simulated by Bochs somewhere in the output. If you find `RAX=000000000000CAFE` or `EAX=CAFEBABE` (depending on if you are running Bochs with or without 64 bit support) in the output then your OS has successfully booted! ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 17 Context: ```markdown # Further Reading - Gustavo Duarte has written an in-depth article about what actually happens when an x86 computer boots up: [How Computers Boot Up](http://duartes.org/gustavo/blog/post/how-computers-boot-up) - Gustavo continues to describe what the kernel does in the very early stages at: [Kernel Boot Process](http://duartes.org/gustavo/blog/post/kernel-boot-process) - The OSDev wiki also contains a nice article about booting an x86 computer: [Boot Sequence](http://wiki.osdev.org/Boot_Sequence) ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 21 Context: ```markdown unsigned short address; /* bit 8 - 23 */ unsigned char index; /* bit 24 - 31 */ } __attribute__((packed)); Note that `__attribute__((packed))` is not part of the C standard - it might not work with all C compilers. ## Compiling C Code When compiling the C code for the OS, a lot of flags to GCC need to be used. This is because the C code should not assume the presence of a standard library, since there is no standard library available for our OS. For more information about the flags, see the GCC manual. The flags used for compiling the C code are: ``` -m32 -nostdlib -nostartfiles -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs ``` As always when writing C programs we recommend turning on all warnings and treating warnings as errors: ``` -Wall -Wextra -Werror ``` You can now create a function `kmain` in a file called `kmain.c` that you call from `loader.s`. At this point, `kmain` probably won't need any arguments (but in later chapters it will). ## Build Tools Now is also probably a good time to set up some build tools to make it easier to compile and test-run the OS. We recommend using `make`, but there are plenty of other build systems available. A simple Makefile for the OS could look like the following example: ```makefile OBJS = loader.o kmain.o CC = gcc CFLAGS = -m32 -nostdlib -nostartfiles -fno-builtin -fno-stack-protector \ -nostartfiles -nodefaultlibs LDFLAGS = -T link.ld -m elf_i386 AS = nasm ASFLAGS = -f elf all: kernel.elf kernel.elf: $(OBJS) ld $(LDFLAGS) $(OBJS) -o kernel.elf os.iso: kernel.elf cp kernel.elf iso/boot/kernel.elf \ && genisoimage -R \ -b boot/grub/stage2_eltorito \ --no-emul-boot \ os.iso ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 23 Context: ```markdown # Chapter 4 ## Output This chapter will present how to display text on the console as well as writing data to the serial port. Furthermore, we will create our first driver, that is, code that acts as a layer between the kernel and the hardware, providing a higher abstraction than communicating directly with the hardware. The first part of this chapter is about creating a driver for the framebuffer [26] to be able to display text on the console. The second part shows how to create a driver for the serial port. Both can store output from the serial port in a file, effectively creating a logging mechanism for the operating system. ## Interacting with the Hardware There are usually two different ways to interact with the hardware: memory-mapped I/O and I/O ports. If the hardware uses memory-mapped I/O, then you can write to a specific memory address and the hardware will be updated with the new data. One example of this is the framebuffer, which will be discussed in more detail later. For example, if you write the value `0x14` to address `0x000B8000`, you will see the letter 'A' in white color on a black background (see the section on the framebuffer for more details). If the hardware uses I/O ports, then the assembly code instructions `out` and `in` must be used to communicate with the hardware. The instruction `out` takes two parameters: the address of the I/O port and the data to send. The instruction `in` takes a single parameter, the address of the I/O port, and returns data from the hardware. One can think of I/O ports as communicating with hardware the same way you communicate with a server using sockets. The cursor (the blinking rectangle) of the framebuffer is one example of hardware controlled via I/O ports on a PC. ## The Framebuffer The framebuffer is a hardware device that is capable of displaying a buffer of memory on the screen [26]. The framebuffer has 80 columns and 25 rows, and the row and column indices start at 0 (so rows are labelled 0-24). ## Writing Text Writing text to the console via the framebuffer is done with memory-mapped I/O. The starting address of the memory-mapped I/O for the framebuffer is `0x000B8000` [27]. The memory is divided into 16-bit cells, 23 ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 25 Context: ```markdown The function can then be used as follows: ```c #define FB_GREEN 2 #define FB_DARK_GREY 8 fb_write_cell(0, 'A', FB_GREEN, FB_DARK_GREY); ``` ## Moving the Cursor Moving the cursor of the framebuffer is done via two different I/O ports. The cursor's position is determined with a 16 bits integer: 0 means row zero, column zero; 1 means row zero, column one; 80 means row one, column zero and so on. Since the position is 16 bits large, and the out assembly code instruction argument is 8 bits, the position must be sent in two turns, first 8 bits then the next 8 bits. The framebuffer has two I/O ports, one for accepting the data, and one for describing the data being received. Port `0x3D4` [29] is the port that describes the data and port `0x3D5` [29] is for the data itself. To set the cursor at row one, column zero (position 80 = `0x0050`), one would use the following assembly code instructions: ```assembly out 0x3D4, 14 ; 14 tells the framebuffer to expect the highest 8 bits of the position out 0x3D5, 0x00 ; sending the highest 8 bits of 0x0050 out 0x3D4, 15 ; 15 tells the framebuffer to expect the lowest 8 bits of the position out 0x3D5, 0x50 ; sending the lowest 8 bits of 0x0050 ``` The out assembly code instruction can't be executed directly in C. Therefore, it is a good idea to wrap out in a function in assembly code which can be accessed from C via the direct calling standard [25]: ```assembly global outb ; make the label outb visible outside this file ; outb - send a byte to an I/O port ; stack: [esp + 8] the data byte ; [esp + 4] the I/O port ; [esp] return address outb: mov al, [esp + 8] ; move the data to be sent into the al register mov dx, [esp + 4] ; move the address of the I/O port into the dx register out dx, al ; send the data to the I/O port ret ; return to the calling function ``` By storing this function in a file called `io.s` and also creating a header `io.h`, the out assembly code instruction can be conveniently accessed from C: ```c #ifndef INCLUDE_IO_H #define INCLUDE_IO_H /** * outb: * Sends the given data to the given I/O port. Defined in io.s * * @param port The I/O port to send the data to * @param data The data to send to the I/O port */ ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 28 Context: ```markdown /** * @param con The COM port to configure * @param divisor The divisor */ void serial_configure_baud_rate(unsigned short con, unsigned short divisor) { outb(SERIAL_LINE_COMMAND_PORT(con), SERIAL_LINE_ENABLE_DLAB); outb(SERIAL_DATA_PORT(con), (divisor >> 8) & 0xFF); outb(SERIAL_DATA_PORT(con), divisor & 0xFF); } The way that data should be sent must be configured. This is also done via the line command port by sending a byte. The layout of the 8 bits looks like the following: Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Content: | d | b | pty | s | dl | A description for each name can be found in the table below (and in [31]): | Name | Description | |------|-------------| | d | Enables (d = 1) or disables (d = 0) DLAB | | b | If break control is enabled (b = 1) or disabled (b = 0) | | pty | The number of parity bits to use | | s | The number of stop bits to use (s = 0 equals 1, s = 1 equals 1.5 or 2) | | dl | Describes the length of the data | We will use the mostly standard value `0x03` [31], meaning a length of 8 bits, no parity bit, one stop bit and break control disabled. This is sent to the line command port, as seen in the following example: /** serial_configure_line: * Configures the line of the given serial port. The port is set to have a * data length of 8 bits, no parity bits, one stop bit and break control * disabled. * * @param con The serial port to configure */ void serial_configure_line(unsigned short con) { /* Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | * Content: | d | b | pty | s | dl | * Value: | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0x03 | */ outb(SERIAL_LINE_COMMAND_PORT(con), 0x03); } ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 29 Context: ```markdown The article on OSDev [31] has a more in-depth explanation of the values. ## Configuring the Buffers When data is transmitted via the serial port it is placed in buffers, both when receiving and sending data. This way, if you send data to the serial port faster than it can send it over the wire, it will be buffered. However, if you send too much data too fast the buffer will be full and data will be lost. In other words, the buffers are FIFO queues. The FIFO queue configuration byte looks like the following figure: | Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |---------------|---|---|---|---|---|---|---|---| | Content: | 1 | l | v | l | 1 | b | s | r | dma | c | l | t | c | l | r | e | A description for each name can be found in the table below: | Name | Description | |-------|------------------------------------------------------| | lvl | How many bytes should be stored in the FIFO buffers | | bs | If the buffers should be 16 or 64 bytes large | | r | Reserved for future use | | dma | How the serial port data should be accessed | | clt | Clear the transmission FIFO buffer | | clr | Clear the receiver FIFO buffer | | e | If the FIFO buffer should be enabled or not | We use the value `0xC7 = 11000000` that: - Enables FIFO - Clears both receiver and transmission FIFO queues - Uses 14 bytes as size of queue The WikiBook on serial programming [32] explains the values in more depth. ## Configuring the Modem The modem control register is used for very simple hardware flow control via the Ready To Transmit (RTS) and Data Terminal Ready (DTR) pins. When configuring the serial port we want RTS and DTR to be 1, which means that we are ready to send data. The modem configuration byte is shown in the following figure: | Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |---------------|---|---|---|---|---|---|---|---| | Content: | 1 | r | 1 | a | 1 | b | a | c | 2 | a | 0 | 1 | r | t | s | d | t | A description for each name can be found in the table below: ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 31 Context: ```markdown /** serial_is_transmit_fifo_empty: * Checks whether the transmit FIFO queue is empty or not for the given COM port. * * @param com The COM port * @return 0 if the transmit FIFO queue is not empty * 1 if the transmit FIFO queue is empty */ int serial_is_transmit_fifo_empty(unsigned int com) { /* 0x20 = 0010 0000 */ return inb(SERIAL_LINE_STATUS_PORT(com)) & 0x20; } Writing to a serial port means spinning as long as the transmit FIFO queue isn’t empty, and then writing the data to the data I/O port. ## Configuring Bochs To save the output from the first serial port the Bochs configuration file `bochsrc.txt` must be updated. The `com1` configuration instructs Bochs how to handle the first serial port: ``` com1: enabled=1, mode=file, dev=com1.out ``` The output from serial port one will now be stored in the file `com1.out`. ## The Driver We recommend that you implement a `write` function for the serial port similar to the `write` function in the driver for the framebuffer. To avoid name clashes with the `write` function for the framebuffer it is a good idea to name the functions `tb_write` and `serial_write` to distinguish them. We further recommend that you try to write a `printf`-like function, see section 7.3 in [1]. The `printf` function could take an additional argument to decide to which device to write the output (framebuffer or serial). A final recommendation is that you create some way of distinguishing the severeness of the log messages, for example by prepending the messages with `DEBUG`, `INFO` or `ERROR`. ## Further Reading - The book “Serial programming” (available on WikiBooks) has a great section on programming the serial port, [http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming#UART_Registers](http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming#UART_Registers) - The OSDev wiki has a page with a lot of information about the serial ports, [http://wiki.osdev.org/Serial_ports](http://wiki.osdev.org/Serial_ports) ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 33 Context: ```markdown # Chapter 5 ## Segmentation Segmentation in x86 means accessing the memory through segments. Segments are portions of the address space, possibly overlapping, specified by a base address and a limit. To address a byte in segmented memory, you use a 48-bit logical address: 16 bits that specifies the segment and 32 bits that specifies what offset within that segment you want. The offset is added to the base address of the segment, and the resulting linear address is checked against the segment's limit - see the figure below. If everything works out fine (including access-rights checks ignored for now), the result is a linear address. When paging is disabled, then the linear address space is mapped 1:1 onto the physical address space, and the physical memory can be accessed. (See the chapter "Paging" for how to enable paging.) To enable segmentation, you need to set up a table that describes each segment - a segment descriptor table. In x86, there are two types of descriptor tables: the Global Descriptor Table (GDT) and Local Descriptor Tables (LDT). An LDT is set up and managed by user-space processes, and all processes have their own LDT. LDTs can be used if a more complex segmentation model is desired - we won't use it. The GDT is shared by everyone - it’s global. As we discuss in the sections on virtual memory and paging, segmentation is rarely used more than in a minimal setup, similar to what we do below. ## Accessing Memory Most of the time when accessing memory there is no need to explicitly specify the segment to use. The processor has six 16-bit segment registers: `cs`, `ds`, `es`, `fs`, and `gs`. The `cs` register is the code segment register and specifies the segment to use when fetching instructions. The `ds` register is used when accessing the stack (through the stack pointer `esp`), and `fs` is used for other data accesses. The OS is free to use the registers `gs` and `fs` as however it want. Below is an example showing implicit use of the segment registers: ```assembly func: mov eax, [esp+4] mov ebx, [eax] add ebx, 8 mov [eax], ebx ret ``` The above example can be compared with the following one that makes explicit use of the segment registers: ```assembly ; Example not provided ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 36 Context: ```markdown # Loading the GDT Loading the GDT into the processor is done with the `lgdt` assembly code instruction, which takes the address of a struct that specifies the start and size of the GDT. It is easiest to encode this information using a "packed struct" as shown in the following example: ```c struct gdt { unsigned int address; unsigned short size; } __attribute__((packed)); ``` If the content of the `eax` register is the address to such a struct, then the GDT can be loaded with the assembly code shown below: ```assembly lgdt [eax] ``` It might be easier if you make this instruction available from C, the same way as was done with the assembly code instructions in and out. After the GDT has been loaded, the segment registers need to be loaded with their corresponding segment selectors. The content of a segment selector is described in the figure and table below: | Bit: | 15 | 3 | 2 | 1 | 0 | |------------|----|---|---|---|---| | Content: | offset (index) | ti | rpl | ### Segment Selector Structure | Name | Description | |--------|--------------------------------------------------------------| | rpl | Requested Privilege Level - we want to execute in PL0 for now. | | ti | Table Indicator. 0 means that this specifies a GDT segment, 1 means an LDT Segment. | | offset (index) | Offset within descriptor table. | The offset of the segment selector is added to the start of the GDT to get the address of the segment descriptor: `0x08` for the first descriptor and `0x10` for the second, since each descriptor is 8 bytes. The Requested Privilege Level (RPL) should be 0 since the kernel of the OS should execute in privilege level 0. Loading the segment selector registers is easy for the data registers - just copy the correct offsets to the registers: ```assembly mov ds, 0x10 mov es, 0x10 mov ss, 0x10 ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 37 Context: ```markdown To load `cs` we have to do a “far jump”: ```assembly ; code here uses the previous cs jmp 0x0080:flush_cs ; specify cs when jumping to flush_cs flush_cs: ; now we've changed cs to 0x08 ``` A far jump is a jump where we explicitly specify the full 48-bit logical address: the segment selector to use and the absolute address to jump to. It will first set `cs` to `0x08` and then jump to `flush_cs` using its absolute address. ## Further Reading - Chapter 3 of the Intel manual [33] is filled with low-level and technical details about segmentation. - The OSDev wiki has a page about segmentation: [OSDev Segmentation](http://wiki.osdev.org/Segmentation) - The Wikipedia page on x86 segmentation might be worth looking into: [Wikipedia X86 Memory Segmentation](http://en.wikipedia.org/wiki/X86_memory_segmentation) ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 40 Context: ```markdown The lowest 32 bits are presented in the following figure: Bit: | 31 | 16 | 15 | 0 | Content: | segment selector | offset low | A description for each name can be found in the table below: | Name | Description | |--------------|--------------------------------------------------------------------------------| | offset high | The 16 highest bits of the 32 bit address in the segment. | | offset low | The 16 lowest bits of the 32 bits address in the segment. | | P | If the handler is present in memory or not (1 = present, 0 = not present). | | DPL | Descriptor Privilege Level, the privilege level the handler can be called from (0, 1, 2, 3). | | D | Size of gate, (1 = 32 bits, 0 = 16 bits). | | segment selector | The offset in the GDT. | | r | Reserved. | The offset is a pointer to code (preferably an assembly code label). For example, to create an entry for a handler whose code starts at `0xDEADBEEF` and that runs in privilege level 0 (therefore using the same code segment selector as the kernel) the following two bytes would be used: ``` 0xDEADB00 0x0000BEEF ``` If the IDT is represented as an `unsigned integer idt[512]` then to register the above example as an handler for interrupt 0 (divide-by-zero), the following code would be used: ```c idt[0] = 0xDEADB00; idt[1] = 0x0000BEEF; ``` As written in the chapter “Getting to C”, we recommend that you instead of using bytes (or unsigned integers) use packed structures to make the code more readable. ## Handling an Interrupt When an interrupt occurs the CPU will push some information about the interrupt onto the stack, then look up the appropriate interrupt handler in the IDT and jump to it. The stack at the time of the interrupt will look like the following: ``` [esp + 12] eflags [esp + 8] cs [esp + 4] eip [esp] error code? ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 41 Context: ```markdown The reason for the question mark behind error code is that not all interrupts create an error code. The specific CPU interrupts that put an error code on the stack are 8, 10, 11, 12, 13, 14, and 17. The error code can be used by the interrupt handler to get more information on what has happened. Also, note that the interrupt number is not pushed onto the stack. We can only determine what interrupt has occurred by knowing what code is executing - if the handler registered for interrupt 17 is executing, then interrupt 17 has occurred. Once the interrupt handler is done, it uses the `iret` instruction to return. The instruction `iret` expects the stack to be the same as at the time of the interrupt (see the figure above). Therefore, any values pushed onto the stack by the interrupt handler must be popped. Before returning, `iret` restores `eflags` by popping the value from the stack and then finally jumps to `cs:eip` as specified by the values on the stack. The interrupt handler has to be written in assembly code, since all registers that the interrupt handlers use must be preserved by pushing them onto the stack. This is because the code that was interrupted doesn’t know about the interrupt and will therefore expect that its registers stay the same. Writing all the logic of the interrupt handler in assembly code will be tiresome. Creating a handler in assembly code that saves the registers, calls a C function, restores the registers and finally executes `iret` is a good idea! The C handler should get the state of the registers, the state of the stack and the number of the interrupt as arguments. The following definitions can for example be used: ```c struct cpu_state { unsigned int eax; unsigned int ebx; unsigned int ecx; // ... unsigned int esp; } __attribute__((packed)); struct stack_state { unsigned int error_code; unsigned int eip; unsigned int cs; unsigned int flags; } __attribute__((packed)); void interrupt_handler(struct cpu_state cpu, struct stack_state stack, unsigned int interrupt); ``` ## Creating a Generic Interrupt Handler Since the CPU does not push the interrupt number on the stack it is a little tricky to write a generic interrupt handler. This section will use macros to show how it can be done. Writing one version for each interrupt is tedious - it is better to use the macro functionality of NASM. And since not all interrupts produce an error code the value 0 will be added as the "error code" for interrupts without an error code. The following code shows an example of how this can be done: ```asm ;%macro no_error_code_interrupt_handler 1 global interrupt_handler_%1 interrupt_handler_%1: push dword 0 ; push 0 as error code ;%endmacro ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 43 Context: ```markdown - Add 8 to `esp` (because of the error code and the interrupt number pushed earlier). - Execute `iret` to return to the interrupted code. Since the macros declare global labels the addresses of the interrupt handlers can be accessed from C or assembly code when creating the IDT. ## Loading the IDT The IDT is loaded with the `lidt` assembly code instruction which takes the address of the first element in the table. It is easiest to wrap this instruction and use it from C: ```asm global load_idt ; load_idt - Loads the interrupt descriptor table (IDT). ; stack: [esp + 4] the address of the first entry in the IDT ; [esp ] the return address load_idt: mov eax, [esp + 4] ; load the address of the IDT into register eax lidt eax ; load the IDT ret ; return to the calling function ``` ## Programmable Interrupt Controller (PIC) To start using hardware interrupts you must first configure the Programmable Interrupt Controller (PIC). The PIC makes it possible to map signals from the hardware to interrupts. The reasons for configuring the PIC are: - Remap the interrupts. The PIC uses interrupts 0 - 15 for hardware interrupts by default, which conflicts with the CPU interrupts. Therefore the PIC interrupts must be remapped to another interval. - Select which interrupts to receive. You probably don't want to receive interrupts from all devices since you don't have code that handles these interrupts anyway. - Set up the correct mode for the PIC. In the beginning there was only one PIC (PIC 1) and eight interrupts. As more hardware were added, 8 interrupts were too few. The solution chosen was to chain an another PIC (PIC 2) on the first PIC (see interrupt 2 on PIC 1). The hardware interrupts are shown in the table below: | PIC 1 | Hardware | PIC 2 | Hardware | |-------|----------------|-------|--------------| | 0 | Timer | 8 | Real Time Clock | | 1 | Keyboard | 9 | General I/O | | 2 | PIC 2 | 10 | General I/O | | 3 | COM 2 | 11 | General I/O | | 4 | COM 1 | 12 | General I/O | ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 45 Context: ```markdown #include "io.h" #define KBD_DATA_PORT 0x60 /** read_scan_code: * Reads a scan code from the keyboard * @return The scan code (NOT an ASCII character!) */ unsigned char read_scan_code(void) { return inb(KBD_DATA_PORT); } The next step is to write a function that translates a scan code to the corresponding ASCII character. If you want to map the scan codes to ASCII characters as is done on an American keyboard, then Andries Brouwer has a great tutorial [36]. Remember, since the keyboard interrupt is raised by the PIC, you must call `pic_acknowledge` at the end of the keyboard interrupt handler. Also, the keyboard will not send you any more interrupts until you read the scan code from the keyboard. ## Further Reading - The OSDev wiki has a great page on interrupts: [OSDev Interrupts](http://wiki.osdev.org/Interrupts) - Chapter 6 of Intel Manual 3A [33] describes everything there is to know about interrupts. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 47 Context: ```markdown # Chapter 7 ## The Road to User Mode Now that the kernel boots, prints to screen and reads from keyboard - what do we do? Usually, a kernel is not supposed to do the application logic itself, but leave that for applications. The kernel creates the proper abstractions (for memory, files, devices) to make application development easier, performs tasks on behalf of applications (system calls) and schedules processes. User mode, in contrast with kernel mode, is the environment in which the user's programs execute. This environment is less privileged than the kernel, and will prevent (badly written) user programs from messing with other programs or the kernel. Badly written kernels are free to mess up what they want. There's quite a way to go until the OS created in this book can execute programs in user mode, but this chapter will show how to easily execute a small program in kernel mode. ## Loading an External Program Where do we get the external program from? Somehow we need to load the code we want to execute into memory. More feature-complete operating systems usually have drivers and file systems that enable them to load the software from a CD-ROM drive, a hard disk or other persistent media. Instead of creating all these drivers and file systems, we will use a feature in GRUB called modules to load the program. ### GRUB Modules GRUB can load arbitrary files into memory from the ISO image, and these files are usually referred to as modules. To make GRUB load a module, edit the file `iso/boot/grub/menu.lst` and add the following line at the end of the file: ``` module /modules/program ``` Now create the folder `iso/modules`: ```bash mkdir -p iso/modules ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 54 Context: ```markdown While pages are normally 4096 bytes, it is also possible to use 4 MB pages. A PDE then points directly to a 4 MB page frame, which needs to be aligned on a 4 MB address boundary. The address translation is almost the same as in the figure, with just the page table step removed. It is possible to mix 4 MB and 4 KB pages. ``` Directory Table Offset 31 22 21 12 11 0 10 Page directory PDE (PS=0) 20 CR3 ``` 10 Page table PTE 20 Physical address 20 4KB Page frame ``` Figure 9.1: Translating virtual addresses (linear addresses) to physical addresses. The 20 bits pointing to the current PDT is stored in the register CR3. The lower 12 bits of CR3 are used for configuration. For more details on the paging structures, see Chapter 4 in the Intel manual [33]. The most interesting bits are U/S, which determine what privilege levels can access this page (PL0 or PL3), and R/W, which makes the memory in the page read-write or read-only. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 57 Context: ```markdown # Higher-half Linker Script We can modify the first linker script to implement this: ```ld ENTRY(loader) /* the name of the entry symbol */ . = 0xC0100000; /* the code should be relocated to 3GB + 1MB */ /* align at 4 KB and load at 1 MB */ .text ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) { *(.text) /* all text sections from all files */ } /* align at 4 KB and load at 1 MB + */ .rodata ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) { *(.rodata*) /* all read-only data sections from all files */ } /* align at 4 KB and load at 1 MB + */ .data ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) { *(.data) /* all data sections from all files */ } /* align at 4 KB and load at 1 MB + */ .bss ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) { *(.common) /* all COMMON sections from all files */ *(.bss) /* all bss sections from all files */ } ``` # Entering the Higher Half When GRUB jumps to the kernel code, there is no paging table. Therefore, all references to `0xC0100000 + X` won’t be mapped to the correct physical address, and will therefore cause a general protection exception (GPE) at the very best, otherwise (if the computer has more than 3 GB of memory) the computer will just crash. Therefore, assembly code that doesn’t use relative jumps or relative memory addressing must be used to do the following: - Set up a page table. - Add identity mapping for the first 1 MB of the virtual address space. - Add an entry for `0xC0100000` that maps to `0x00100000`. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 59 Context: ```markdown ## Further Reading - Chapter 4 (and to some extent chapter 3) of the Intel manual [33] are your definitive sources for the details about paging. - Wikipedia has an article on paging: [https://en.wikipedia.org/wiki/Paging](https://en.wikipedia.org/wiki/Paging) - The OSDev wiki has a page on paging: [http://wiki.osdev.org/Paging](http://wiki.osdev.org/Paging) and a tutorial for making a higher-half kernel: [http://wiki.osdev.org/Highert_ half_bare_bones](http://wiki.osdev.org/Highert_half_bare_bones) - Gustavo Duarte's article on how a kernel manages memory is well worth a read: [http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory](http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory) - Details on the linker command language can be found at Steve Chamberlain's website [37]. - More details on the ELF format can be found in this presentation: [http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf](http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf) ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 60 Context: I'm unable to view images. Please provide the text you would like to have checked and corrected in Markdown format. #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 62 Context: ```markdown /* align at 4 KB and load at 1 MB + */ // .data ALIGH (0x1000) : AT(ADDR(.data)-0x00000000) .data { *(.data) /* all data sections from all files */ } /* align at 4 KB and load at 1 MB + */ // .bss ALIGH (0x1000) : AT(ADDR(.bss)-0x00000000) .bss { *(COMMON) /* all COMMON sections from all files */ *(.bss) /* all bss sections from all files */ } kernel_virtual_end = .; kernel_physical_end = - 0xC0000000; These labels can directly be read from assembly code and pushed on the stack to make them available to C code: ``` extern kernel_virtual_start extern kernel_virtual_end extern kernel_physical_start extern kernel_physical_end : ... push kernel_physical_end push kernel_physical_start push kernel_virtual_end push kernel_virtual_start call kmain ``` This way we get the labels as arguments to `kmain`. If you want to use C instead of assembly code, one way to do it is to declare the labels as functions and take the addresses of these functions: ```c void kernel_virtual_start(void); /* ... */ ``` ```c unsigned int vaddr = (unsigned int) &kernel_virtual_start; ``` If you use GRUB modules, you need to make sure the memory they use is marked as reserved as well. Note that the available memory does not need to be contiguous. In the first 1 MB there are several I/O-mapped memory sections, as well as memory used by GRUB and the BIOS. Other parts of the memory might be similarly unavailable. It's convenient to divide the memory sections into complete page frames, as we can't map part of pages into memory. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 63 Context: ```markdown # Managing Available Memory How do we know which page frames are in use? The page frame allocator needs to keep track of which are free and which aren't. There are several ways to do this: bitmaps, linked lists, trees, the Buddy System (used by Linux) etc. For more information about the different algorithms see the article on OSDev [1]. Bitmaps are quite easy to implement. One bit is used for each page frame and one (or more) page frames are dedicated to store the bitmap. (Note that this is just one way to do it; other designs might be better and/or more fun to implement.) ## How Can We Access a Page Frame? The page frame allocator returns the physical start address of the page frame. This page frame is not mapped in - no page table points to this page frame. How can we read and write data to the frame? We need to map the page frame into virtual memory, by updating the PDT and/or PT used by the kernel. What if all available page tables are full? Then we can’t map the page frame into memory, because we need a new page table - which takes up an entire page frame - and to write to this page frame we’d need to map its page frame... Somehow this circular dependency must be broken. One solution is to reserve a part of the first page table used by the kernel (or some other higher-half page table) for temporarily mapping page frames to make them accessible. If the kernel is mapped at `0xC0000000` (page directory entry with index 768), and 4 KB page frames are used, then the kernel has at least one page table. If we assume - or limit us to - a kernel of size at most 4 MB minus 4 KB we can dedicate the last entry (entry 1023) of this page table for temporary mappings. The virtual address of pages mapped in using the last entry of the kernel's PT will be: ``` (768 << 22) | (1023 << 12) | 0x000 = 0xC03F0000 ``` After we’ve temporarily mapped the page frame we want to use as a page table, and set it up to map in our first page frame, we can add it to the page directory and remove the temporary mapping. ## A Kernel Heap So far we’ve only been able to work with fixed-size data, or directly with raw memory. Now that we have a page frame allocator we can implement `malloc` and `free` to use in the kernel. Kernighan and Ritchie [2] have an example implementation in their book [2] that we can draw inspiration from. The only modification we need to do is to replace calls to `sbrk/brk` with calls to the page frame allocator when more memory is needed. We must also make sure to map the page frames returned by the page frame allocator to virtual addresses. A correct implementation should also return page frames to the page frame allocator on call to `free`, whenever sufficiently large blocks of memory are freed. ## Further Reading - The OSDev wiki page on page frame allocation: [OSDev Page Frame Allocation](http://wiki.osdev.org/Page_Frame_Allocation) ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 66 Context: ```markdown and increasing, and the stack should start just below the kernel, at 0xBFFFFFFF, growing towards lower addresses. The U/S flag has to be set to allow PL3 access. It might be convenient to store this information in a `struct` representing a process. This process `struct` can be dynamically allocated with the kernel's `malloc` function. ## Entering User Mode The only way to execute code with a lower privilege level than the current privilege level (CPL) is to execute an `iret` or `lret` instruction - interrupt return or log return, respectively. To enter user mode we set up the stack as if the processor had raised an inter-privilege level interrupt. The stack should look like the following: ``` [eip + 16] ss ; the stack segment selector we want for user mode [eip + 12] esp ; the user mode stack pointer [eip + 8] eflags ; the control flags we want to use in user mode [eip + 4] cs ; the code segment selector [eip + 0] eip ; the instruction pointer of user mode code to execute ``` See the Intel manual [33], section 6.2.1, figure 6-4 for more information. The `iret` instruction will then read these values from the stack and fill in the corresponding registers. Before we execute `iret`, we need to change to the page directory we setup for the user mode process. It is important to remember that to continue executing kernel code after we've switched the Page Table (PDT), the kernel needs to be mapped in. One way to accomplish this is to have a separate PDT for the kernel, which maps all data at 0xC0000000 and above, and merge it with the user PDT (which only maps below 0xC0000000) when performing the switch. Remember that physical address of the PDT has to be used when setting the control register `cr3`. The register `eflags` contains a set of different flags, specified in section 2.3 of the Intel manual [33]. Most important for us is the interrupt enable (IF) flag. The assembly code instruction `iret` can't be used in privilege level 3 for enabling interrupts. If interrupts are disabled when entering user mode, then interrupts can't be enabled once user mode is entered. Setting the IF flag in the `eflags` entry on the stack will enable interrupts in user mode, since the assembly code instruction `iret` will set the register eflags to the corresponding value on the stack. For now, we should have interrupts disabled, as it requires a little more work to get inter-privilege level interrupts to work properly (see the section "System calls"). The value `eip` on the stack should point to the entry point for the user code - `0x00000000` in our case. The value `esp` on the stack should be where the stack starts - `0xC0000000 - 4`. The values `cs` and `ss` on the stack should be the segment selectors for the user code and user data segments, respectively. As we saw in the segmentation chapter, the lowest two bits of a segment selector is the RPL - Requested Privilege Level. When using inter to enter PL3, the RPL of `cs` and `ss` should be `0x3`. The following code shows an example: ``` USER_MODE_CODE_SEGMENT_SELECTOR equ 0x18 USER_MODE_DATA_SEGMENT_SELECTOR equ 0x20 mov cs, USER_MODE_CODE_SEGMENT_SELECTOR ; 0x3 mov ss, USER_MODE_DATA_SEGMENT_SELECTOR ; 0x3 ``` ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 68 Context: ```markdown *(.rodata*) } Note: `*(.text)` will not include the `.text` section of `start.o` again. With this script we can write programs in C or assembler (or any other language that compiles to object files linkable with `ld`), and it is easy to load and map for the kernel (`.rodata` will be mapped in as writable, though). When we compile user programs we want the following GCC flags: ``` -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs ``` For linking, the following flags should be used: ``` -T link.ld -melf_i386 # emulate 32 bits ELF, the binary output is specified # in the linker script ``` The option `-T` instructs the linker to use the linker script `link.ld`. ## A C Library It might now be interesting to start thinking about writing a small “standard library” for your programs. Some of the functionality requires system calls to work, but some, such as the functions in `string.h`, does not. ## Further Reading - Gustavo Duarte has an article on privilege levels: [http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-protection](http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-protection) ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 69 Context: ```markdown # Chapter 12 ## File Systems We are not required to have file systems in our operating system, but it is a very usable abstraction, and it often plays a central part of many operating systems, especially UNIX-like operating systems. Before we start the process of supporting multiple processes and system calls, we might want to consider implementing a simple file system. ### Why a File System? How do we specify what programs to run in our OS? Which is the first program to run? How do programs output data or read input? In UNIX-like systems, with their almost-everything-is-a-file convention, these problems are solved by the file system. (It might also be interesting to read a bit about the Plan 9 project, which takes this idea one step further.) ### A Simple Read-Only File System The simplest file system might be what we already have - one file, existing only in RAM, loaded by GRUB before the kernel starts. When the kernel and operating system grow, this is probably too limiting. A file system that is slightly more advanced than just the bits of one file is a file with metadata. The metadata can describe the type of the file, the size of the file, and so on. A utility program can be created that runs at build time, adding this metadata to a file. This way, a “file system in a file” can be constructed by concatenating several files with metadata into one large file. The result of this technique is a read-only file system that resides in memory (once GRUB has loaded the file). The program constructing the file system can traverse a directory on the host system and all subdirectories and files as part of the target file system. Each object in the file system (directory or file) can consist of a header and a body, where the body of a file is the actual file and the body of a directory is a list of entries - names and “addresses” of other files and directories. Each object in this file system will become contiguous, so they will be easy to read from memory for the kernel. All objects will also have a fixed size (except for the last one, which can grow), therefore it is difficult to add new files or modify existing ones. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 70 Context: ```markdown # Inodes and Writable File Systems When the need for a writable file system arises, then it is a good idea to look into the concept of an **inode**. See the section “Further Reading” for recommended reading. ## A Virtual File System What abstraction should be used for reading and writing to devices such as the screen and the keyboard? A virtual file system (VFS) creates an abstraction on top of the concrete file systems. A VFS mainly supplies the path system and file hierarchy; it delegates operations on files to the underlying file systems. The original paper on VFS is succinct and well worth a read. See the section “Further Reading” for a reference. With a VFS we could mount a special file system on the path `/dev`. This file system would handle all devices such as keyboards and the console. However, one could also take the traditional UNIX approach, with major/minor device numbers and `mknod` to create special files for devices. Which approach you think is the most appropriate is up to you; there is no right or wrong when building abstraction layers (although some abstractions turn out way more useful than others). ## Further Reading - The ideas behind the Plan 9 operating systems is worth taking a look at: [Plan 9](http://plan9.bell-labs.com/plan9/index.html) - Wikipedia's page on inodes: [Inode](http://en.wikipedia.org/wiki/Inode) and the inode pointer structure: [Inode Pointer Structure](http://en.wikipedia.org/wiki/Inode_pointer_structure). - The original paper on the concept of inodes and a virtual file system is quite interesting: [Kleinain](http://www.sun.com/west/edu/fredk/Courses/cs253/fall01/Papers/kleinain86/inodes.pdf). - Poul-Henning Kamp discusses the idea of a special file system for `/dev` in [this paper](http://static.usenix.org/publications/library/proceedings/bsdcon02/full_papers/kamp/kamp_html/index.html). ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 71 Context: ```markdown # Chapter 13 ## System Calls System calls are the way user-mode applications interact with the kernel—to ask for resources, request operations to be performed, etc. The system call API is the part of the kernel that is most exposed to the users; therefore, its design requires some thought. ## Designing System Calls It is up to us, the kernel developers, to design the system calls that application developers can use. We can draw inspiration from the POSIX standards or, if they seem like too much work, just look at the ones for Linux, and pick and choose. See the section “Further Reading” at the end of the chapter for references. ## Implementing System Calls System calls are traditionally invoked with software interrupts. The user applications put the appropriate values in registers or on the stack and then initiate a pre-defined interrupt which transfers execution to the kernel. The interrupt number used is dependent on the kernel; Linux uses the number 0x80 to identify that an interrupt is intended as a system call. When system calls are executed, the current privilege level is typically changed from PL3 to PL0 (if the application is running in user mode). To allow this, the DPL of the entry in the IDT for the system call interrupt needs to allow PL3 access. Whenever inter-privilege level interrupts occur, the processor pushes a few important registers onto the stack—the same ones we used to enter user mode before (see Figure 6-4, section 6.12.1, in the Intel manual [33]). What stack is used? The same section in [33] specifies that if an interrupt leads to code executing at a minimally lower privilege level, a stack switch occurs. The new values for the registers as well as the stack are loaded from the current Task State Segment (TSS). The TSS structure is specified in Figure 7-2, section 7.2.1 of the Intel manual [33]. To enable system calls, we need to set up a TSS before entering user mode. Setting it up can be done in C by setting the `s80` and `eip` fields of a “packed struct” that represents a TSS. Before loading the “packed struct” into the processor, a TSS descriptor has to be added to the GDT. The structure of the TSS descriptor is described in section 7.2.2 in [33]. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 73 Context: ```markdown # Chapter 14 ## Multitasking How do you make multiple processes appear to run at the same time? Today, this question has two answers: - With the availability of multi-core processors, or on systems with multiple processors, two processes can actually run at the same time by running two processes on different cores or processors. - Fake it. That is, switch rapidly (faster than a human can notice) between the processes. At any given moment there is only one process executing, but the rapid switching gives the impression that they are running “at the same time.” Since the operating system created in this book does not support multi-core processors or multiple processors, the only option is to fake it. The part of the operating system responsible for rapidly switching between the processes is called the **scheduling algorithm**. ## Creating New Processes Creating new processes is usually done with two different system calls: `fork` and `exec`. `fork` creates an exact copy of the currently running process, while `exec` replaces the current process with one that is specified by a path to the location of a program in the file system. Of these two, we recommend that you start implementing `exec`, since this system call will do almost exactly the same steps as described in the section “Setting up for user mode” in the chapter “User Mode.” ## Cooperative Scheduling with Yielding The easiest way to achieve rapid switching between processes is if the processes themselves are responsible for the switching. The processes run for a while and then tell the OS (via a system call) that it can now switch to another process. Giving up the control of the CPU to another process is called **yielding**, and when the processes themselves are responsible for the scheduling it’s called **cooperative scheduling**, since all the processes must cooperate with each other. When a process yields the process’ entire state must be saved (all the registers), preferably on the kernel heap in a structure that represents a process. When changing to a new process all the registers must be restored from the saved values. ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 75 Context: ```markdown # Difficulties with Preemptive Scheduling When using preemptive scheduling, one problem arises that doesn't exist with cooperative scheduling. With cooperative scheduling, every time a process yields, it must be in user mode (privilege level 3), since yield is a system call. With preemptive scheduling, the processes can be interrupted in either user mode or kernel mode (privilege level 0), since the process itself does not control when it gets interrupted. Interrupting a process in kernel mode is a little bit different than interrupting a process in user mode, due to the way the CPU sets up the stack at interrupts. If a privilege level change occurred (the process was interrupted in user mode), the CPU will push the value of the process and `esp` register on the stack. If no privilege level change occurs (the process was interrupted in kernel mode), the CPU won't push the `esp` register on the stack. Furthermore, if there was no privilege level change, the CPU won't change stack to the one defined it the TSS. This problem is solved by calculating what the value of `esp` was before the interrupt. Since you know that the CPU pushes 3 things on the stack when no privilege change happens and you know how much you have pushed on the stack, you can calculate what the value of `esp` was at the time of the interrupt. This is possible since the CPU won't change stacks if there is no privilege level change, so the content of `esp` will be the same as at the time of the interrupt. To further complicate things, one must think of how to handle the case when switching to a new process that should be running in kernel mode. Since `iret` is being used without a privilege level change, the CPU won't update the value of `esp` with the one placed on the stack - you must update it yourself. ## Further Reading - For more information about different scheduling algorithms, see [OSDev Scheduling Algorithms](http://wiki.osdev.org/Scheduling_Algorithms). ``` #################### File: The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf Page: 78 Context: ```markdown [25] Wikipedia, [x86 calling conventions](http://en.wikipedia.org/wiki/X86_calling_conventions) [26] Wikipedia, [Framebuffer](http://en.wikipedia.org/wiki/Framebuffer) [27] Wikipedia, [VGA-compatible text mode](http://en.wikipedia.org/wiki/VGA-compatible_text_mode) [28] Wikipedia, [ASCII](https://en.wikipedia.org/wiki/ASCII) [29] OSDev, [VGA hardware](http://wiki.osdev.org/VGA_Hardware) [30] Wikipedia, [Serial port](http://en.wikipedia.org/wiki/Serial_port) [31] OSDev, [Serial ports](http://wiki.osdev.org/Serial_ports) [32] WikiBooks, [Serial programming/8250 uART programming](http://en.wikibooks.org/wiki/Serial-Programming/8250_UART_Programming) [33] Intel, [16 (i) and 32 architectures software developer's manual vol. 3](http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html) [34] NASM, [Multi-line macros](http://www.nasm.us/doc/nasmdoc.html#section-4.3) [35] SIGOPS, [i386 interrupt handling](http://www.acm.uiuc.edu/sigops/roll_your_own/i386/irq.html) [36] Andries Brouwer, [Keyboard scancodes](http://www.win.tue.nl/) [37] Steve Chamberlain, [Using ld, the GNU linker](http://www.math.utah.edu/docs/info/ld_toc.html) [38] OSDev, [Page frame allocation](http://wiki.osdev.org/Page_Frame_Allocation) [39] OSDev, [Programmable interval timer](http://wiki.osdev.org/Programmable_Interval_Timer) ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 2 Context: ```markdown # 10.3 Station Addressing Every packet must carry a destination address as well as the sender's address. Addresses are station numbers. It could certainly be inconvenient for a user to remember the station number of a desired partner. Instead, the use of symbolic names is preferred. We have become accustomed to use the partner's initials for this purpose. ## Protocols A simple form of the `ReceiveFile` request is defined as follows and will be refined subsequently: ``` ReceiveFile = SND filename (ACK data | NAK). ``` Here, the symbol `SND` represents the encoded request that the server send the file specified by the filename. `ACK` signals that the request is honored and the requested data follow. The `NAK` symbol indicates that the requested file cannot be delivered. The transaction clearly consists of two parts, the request and the reply, one from each partner. This simple-minded scheme fails because of the limitation of the size of each transmitted portion imposed by the network driver. We recall that module `SCC` restricts the data of each packet to 512 bytes. Evidently, files must be broken up and transmitted as a sequence of packets. The reason for this restriction is transmission reliability. The break-up allows the partner to confirm correct receipt of a packet by returning a short acknowledgement. Each acknowledgement also serves as a request for the next packet. An exception is the last acknowledgement following the last data portion, which is characterized by its length being less than the admitted maximum. The revised protocol is defined as follows: ``` ReceiveFile = SND filename (DAT data ACK | DAT data ACK | NAK). ``` We now recall that each packet as defined in Section 9.3 is characterized by a type in its header. The symbols `SND`, `DAT`, `ACK`, and `NAK` indicate this packet type. The data portions of `ACK` and `NAK` packets are empty. The revised protocol fails to cope with transmission errors. Correct transmission is checked by the driver through a cyclic redundancy check (CRC), and an erroneous packet is simply discarded. This implies that a receiver must impose a timing constraint. If an expected packet fails to arrive within a given time period (timeout), the request must be repeated. In our case, a request is implied by an acknowledgement. Hence, the acknowledgement must specify whether the next (normal case) or the previously requested (error case) packet must be sent. The solution is to attach a sequence number to each acknowledgement and to each data packet. These numbers are taken modulo 8, although in principle modulo 2 would suffice. With the addition of a user identification and a password to every request, and of an alternate reply code `NPR` for "no permission", the protocol reaches its final form: ``` ReceiveFile = SND username password filename (datastream | NAK | NPR). datastream = DAT data ACK | {DAT, data ACK}*. ``` The protocol for file transmission from the master to the server is defined similarly: ``` SendFile = REC username password filename (ACKO datastream | NAK | NPR). datastream = DAT data ACK | {DAT, data ACK}*. ``` The third request listed above, `SendMsg`, does not refer to any file, but merely transmits and displays a short message. It is included here for testing the link between two partners and perhaps for visibly acknowledging a rendered service by the message "done", or "thank you". ``` SendMsg = MSG message ACK. ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 5 Context: ```markdown # 11. A Dedicated file-distribution and mail-server ## 11.1 Concept and structure In a system of loosely coupled workstations, it is desirable to centralize certain services. A first example is a common file store. Even if every station is equipped with a disk for permanent data storage, a common file service is beneficial, e.g., for storing the most recent versions of system files, reference documents, reports, etc. A common repository avoids inconsistencies which are inevitable when local copies are created. We call this a **file distribution service**. A centralized service is also desirable if it requires equipment whose cost and service would not warrant its acquisition for every workstation, particularly if the service is infrequently used. A prime example of this case is a **printing service**. The third case is a communication facility in the form of **electronic mail**. The repository of messages must inherently be centralized. We imagine it to have the form of a set of mailboxes, one for each user in the system. A mailbox needs to be accessible at all times, i.e., also when its owner's workstation has been switched off. A last example of a centralized service is a **time server**. It allows a station's real time clock to be synchronized with a central clock. In passing, we point out that every user has full control over his station, including the right to switch it on and off at any time. In contrast, the central server is continuously operational. In this chapter, we present a set of server modules providing all above mentioned services. They rest on the basic Oberon System without module **Net** (see Chapter 10). In contrast to **Net**, module **NetServer**, which handles all network communication, contains no command procedures (apart from those for starting and stopping it). This is because it never acts as a master. The counterparts of its server routines reside in other modules, including (an extended version of) **Net**, on the individual workstations. Routines for the file distribution service are the same as those contained in module **Net**, with the addition of permission checks based on the received user names and passwords. Routines for printing and mail service could in principle also be included in **NetServer** in the same way. But considerations of reliability and timing made this simple solution appear unsatisfactory. A weaker coupling in line of data transmission and data consumption is indeed highly desirable. Therefore, files received for printing or for dispatching into mailboxes are stored (by **NetServer**) into temporary files and thereafter "handed over" to the appropriate agent, i.e., the print server or the mail server. This data-centered interface between servers—in contrast to procedural interfaces—has the advantage that the individual servers are independent in the sense that none imports any other. Therefore, their development could proceed autonomously. Their connection is instead a module which defines a data structure and associated operators for passing temporary files from one server to another. The data structure used for this purpose is the first-in-first-out queue. We call it element tasks, because each one carries an objective and an object, the file to be processed. The module containing the FIFOs is called **Core**. The resulting structure of the involved modules is shown in Fig. 11.1. ![Figure 11.1](#) includes another server, **LineServer**, and shows the ease with which additional servers may be inserted into this scheme. They act as further sources and/or sinks for tasks, feeding or consuming the queues contained in **Core**. **LineServer** indeed produces and consumes tasks like **NetServer**. Instead of the RS-485 bus, it handles the RS-232 line which, connected to a modem, allows access to the server over telephone lines. We refrain from describing this module in further detail, because in many ways it is a mirror of **NetServer**. A centralized, open server calls for certain protection measures against unauthorized use. We recall that requests always carry a user identification and a password as parameters. The server ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 9 Context: ```markdown # Block reservation part ``` ``` 1 100000101111101111111 ``` ## Directory part | pos | len | time | date | orig | next | |-----|-----|---------|---------|--------|------| | 0 | 0 | 9.2 | | | | | | | 10:72:12| | | | | | | 15:21:91| | | | | 8 | 15 | 1.0 | | | | | 197 | 11.50| | 23:41:8| 6.6:81 | | | 12 | 20 | 2 | | | | ### Message part ``` 0 2 8 15 ``` ![Figure 11.4 State of mailbox file](https://example.com/image-url) Requests for the mailbox directory are handled by the routine guarded by the condition `type = MDIR`. The directory part must be read and converted into text. This task is supported by various auxiliary procedures (Append) which concatenate supplied data in a buffer for latter transmission. We emphasize that this request does not require the reading of any other part of the file, and therefore is very swift. The last of the four mail service requests (DML) deletes a specified message. Removal from the directory requires a relinking of the entries. Unused entries are marked by their `len` field having value 0. Also, the blocks occupied by the message become free. The block reservation part must be updated accordingly. In passing, we note that the use of files for representing mailboxes, in combination with the file distribution services residing on the same server station, allows anyone to access (and inspect) any mailbox. Although we do not claim that this system provides secure protection against snooping, a minimal effort for protection was undertaken by a simple encoding of messages in mailbox files. This encoding is not shown in the program listings contained in this book. One operation remains to be explained in more detail: the processing of tasks inserted into the mail queue. It consists of the insertion of the message represented by the task's file into one or several mailboxes. It involves the interpretation of the message's header, i.e., lines containing addresses, and the construction of a new header containing the name of the originator and the date of insertion into the mailbox. These actions are performed by procedures in module `MailServer`. Its procedure `Serve` is installed as an Oberon Task, and it is guarded by the condition `Core.MailQueue.n > 0`, indicating that at least one message needs to be dispatched. The originator's name is obtained from `Core.GetUserName(uno)`, where `uno` is the user number obtained from the queue entry. The actual time is obtained from `Oberon.GetClock`. The form of the new header is shown by the following example: ``` From: GuntherK Date: 12.08.91 09:34:15 ``` The received message's header is then searched for recipients. Their names are listed in header lines starting with `To:` (or `Cc:`). After a name has been read, the corresponding user number is obtained by calling `Core.UserNum`. Then the message is inserted into the designated mailbox by procedure `Dispatch`. The search for recipients continues, until a line is encountered that does not begin with `To:` (or `Cc:`). A negative user number indicates that the given name is not registered. In ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 12 Context: ```markdown # Stored Passwords Our solution lies in storing an encoded password. The command `System.SetUser`, which asks for a user identification and a password, immediately encodes the password, and the original is stored nowhere. The encoding algorithm is such that it is difficult to construct a corresponding decoder. ## Mail Service The mail service requires a third attribute in addition to identification and encoded password: the user's name as it is used for addressing messages. Identification typically consists of the user's initials; for the name, we suggest the full last name of the user and disallow cryptic abbreviations. ## Printing Service The printing service makes an accounting facility desirable. A fourth field in each user table entry serves as a count for the number of printed pages. As a result, there are four fields: `id`, `name`, `password`, and `count`. The table is not exported, but only accessible via procedures. `Core` is a good example of a resource hiding module. The program is listed below, and a few additional comments follow here. ### Procedures `UserNo(id)` and `UserNum(name)` yield the table index of the identified user; it is called user number and is used as a short encoding for recipients and senders within the mail server. In other servers, the number is merely used to check a request's validity. ## User Information The user information must certainly survive any intermission of server operation, be it due to software, hardware, or power failure. This requires that a copy of the user information is held on backup store (disk). The simplest solution would be to use a file for this purpose. But this would indeed make protection too vulnerable: files can be accessed easily, and we have refrained from introducing a file protection facility. Instead, the backup of the user information is held on a few permanently reserved sections on the server machine, which are inaccessible to the file system. ## Procedures Apart from procedures and variables constituting the queuing mechanism for tasks, the procedures exported from module `Core` all belong to the administration, and they can be divided into two categories. The first category contains the procedures used by the three servers presented in this Chapter, and they are `UserNo`, `UserNum`, `IncPageCount`, `SetPassword`, `GetUserName` and `GetFileName`. The second category consists of the procedures `NoUsers` and `GetUser` for inspecting table entries, and `InsertUser`, `DeleteUser`, `ClearPassword`, `ClearCounts`, and `Init` for making changes to the table. ## Client Module The client of the latter category is a module `Users` which is needed by the human administrator of the server facility. The reader may at this point wonder why a more advanced concept of administration has not been chosen, which would allow the human administrator to operate the server remotely. A quick analysis of the consequences of this widely used approach reveals that a substantial amount of additions to our system would be required. The issue of security and protection would become inflated into dimensions that are hardly justified for our local system. The first consequence would be a differentiation among levels of protection. The administrator would become a so-called super user with extra privileges, such as changing the user table. And so the game of trying to break the protection measures starts to become an interesting challenge. We have resisted the temptation to introduce additional complexity. Instead, we assume that physical access to the server station is reserved to the administrator. Naturally, module `Users` and in particular the symbol file of `Core` do not belong to the public domain. In concluding, we may point out that the impossibility of activating users' programs on the server station significantly reduces the possibilities for inflicting damage from the exterior. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 14 Context: ```markdown exception is embodied by the notion of declarations. The declaration of an identifier, say x, attaches permanent properties to x, such as the fact that x denotes a variable and that its type is T. These properties are "invisible" when parsing a statement containing x, because the declaration of x is not also part of the statement. The "meaning" of identifiers is thus inherently context-dependent. Context-dependence due to declarations is the immediate reason for the use of a global data structure which represents the declared identifiers and their properties (attributes). Since this concept stems from early assemblers where identifiers (then called symbols) were registered in a linear table, the term *symbol table* tends to persist for this structure, although in this compiler it is considerably more complex than an array. Basically, it grows during the processing of declarations, and it is searched while expressions and statements are processed. Procedures for building and searching are contained in module ORB. A complication arises from the notion of exports and imports in Oberon. Its consequence is that the declaration of an identifier x may be in a module, say M, different from where x is referenced. If x is exported, the compiler includes x together with its attributes in the *symbol file* of the compiled module M. When compiling another module which imports M, that symbol file is read and its data are incorporated into the symbol table. Procedures for reading and writing symbol files are contained in module ORB, and no other module relies on information about the structure of symbol files. The syntax is precisely and rigorously defined by a small set of syntactic equations. As a result, the parser is a reasonably perspicuous and short program. In spite of the high degree of regularity of the target computer, the process of code generation is more complicated, as shown by module ORG. The resulting module structure of the compiler is shown in Fig. 12.1 in a slightly simplified manner. In reality OCS is imported by all other modules due to their need for procedure OCS.Mark. This, however, will be explained later. ![Figure 12.1 Compiler's module structure](path_to_image) ## 12.2. Code patterns Before it is possible to understand how code is generated, one needs to know *which* code is generated. In other words, we need to know the goal before we find the way leading to the goal. A fairly concise description of this goal is possible due to the structure of the language. As explained before, semantics are attached to each individual syntactic construct, independent of its context. Therefore, it suffices to list the expected code - instead of an abstract semantic rule - for each syntactic construct. As a prerequisite to understanding the resulting instructions and in particular their parameters, we need to know where declared variables are stored, i.e. which are their addresses. This compiler ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 15 Context: ```markdown uses the straight-forward scheme of sequential allocation of consecutively declared variables. An address is a pair consisting of a base address (in a register) and an offset. Global variables are allocated in the module's data section and the respective base address register is SB (Static Base, see Chapter 6). Local variables are allocated in a procedure activation record on the stack; the respective base register is SP (Stack Pointer). Offsets are positive integers. The amount of storage needed for a variable (called its size) is determined by the variable's type. The sizes of basic types are prescribed by the target computer's data representation. The following holds for the RISC processor: | Type | No. of bytes | |---------------------------|--------------| | BYTE, CHAR, BOOLEAN | 1 | | INTEGER, REAL, SET, POINTER, PROCEDURE | 4 | The size of an array is the size of the element type multiplied by the number of elements. The size of a record is the sum of the sizes of its fields. A complication arises due to so-called alignment. By alignment is meant the adjustment of an address to a multiple of the variable's size. Alignment is performed for variable addresses as well as for record field offsets. The motivation for alignment is the avoidance of double memory references for variables being "distributed" over two adjacent words. Proper alignment enhances processing speed quite significantly. Variable allocation using alignment is shown by the example in Fig. 12.2. ``` VAR b0: BYTE; int0: INTEGER; b1: BYTE; int1: INTEGER; ``` ``` b0 int0 b1 int1 ``` **Figure 12.2.** Alignment of variables We note in passing that a reordering of the four variables lessens the number of unused bytes, as shown in Fig. 12.3. ``` VAR int0, int1: INTEGER; b0, b1: BYTE; ``` ``` int0 int1 b1 b0 ``` **Figure 12.3.** Improved order of variables Memory instructions compute the address as the sum of a register (base) and an offset constant. Local variables use the stack pointer SP (R14) as base, global variables the static base SB (R13). Every module has its own SB value, and therefore access to global (and imported) variables requires two instructions, one for fetching the base value, and one for loading or storing data. If the compiler can determine whether the correct base value has already been loaded into the SB register, the former instruction is omitted. The first 7 sample patterns contain global variables only, and their base SB is assumed to hold the appropriate value. Parameters of branch instructions denote jump distances from the instruction's own location (PC-relative). ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 17 Context: ```markdown # Pattern3: Indexed Variables References to elements of arrays make use of the possibility to add an index value to an offset. The index must be present in a register and multiplied by the size of the array elements. (For integers with size 4 this is done by a shift of 2 bits). Then this index is checked whether it lies within the bounds specified in the array's declaration. This is achieved by a comparison, actually a subtraction, and a subsequent branch instruction causing a trap, if the index is either negative or beyond the upper bound. If the reference is to an element of a multi-dimensional array (matrix), its address computation involves several multiplications and additions. The address of an element \( A[i_1, \ldots, i_k] \) of a k-dimensional array A with lengths \( n_{k_1}, \ldots, n_{k_k} \) is: \[ A(i) + \left( \ldots \left( \left( (i_k \mod n_{k_k}) \times n_{k_{k-1}} + i_{k-1} \right) \times n_{k_{k-2}} + \ldots + i_2 \right) \times n_1 + i_1 \right) \] **Note:** For index checks, `CMP` is written instead of `SUB` to mark that the subtraction is merely a comparison, that the result remains unused and only the condition flag registers hold the result. ## MODULE Pattern3: ``` VAR i, j, k: INTEGER; 0, 4, 8, 12 a: ARRAY 10 OF INTEGER; 16 x: ARRAY 10, 10 OF INTEGER; 56 y: ARRAY 10, 10 OF INTEGER; 456 BEGIN k := a[i]; LDR R0 S8 CMP R1 R0 10 BLHI R12 LSL R0 R0 2 ADD R0 R0 S8 LDR R0 R0 16 STR R0 S8 n := a[5]; LDR R0 S8 STR R0 S8 12 x[i][j] := 2; LDR R0 S8 CMP R1 R0 10 BLHI R12 MUL R0 R0 40 ADD R0 R0 S8 LDR R0 S8 4 CMP R2 R1 10 BLHI R12 LSL R1 R1 2 ADD R0 R0 R1 MOV R1 R0 2 STR R1 R0 56 LDR R0 S8 CMP R1 R0 10 BLHI R12 MUL R1 R0 400 ADD R0 R0 S8 4 CMP R2 R1 10 BLHI R12 y[i][j] := 3; LDR R1 S8 CMP R1 R0 10 BLHI R12 MUL R1 R1 40 ADD R0 R0 R1 LDR R1 S8 CMP R2 R1 10 ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 20 Context: ```markdown # Pattern 6: Module Structure ```plaintext MODULE Pattern6; VAR i: INTEGER; BEGIN i := 0; WHILE i < 10 DO LDR R8 0 LDR R8 0 CMP R0 10 BGE 4 LDR R8 0 ADD R0 R2 STR R8 0 END; REPEAT i := i - 1; LDRB R8 0 LDR R0 R8 0 SUB R0 1 STR R0 R8 0 UNTIL i = 0; CMP R0 0 BNE -7 END Pattern6. ``` # Pattern 7: For Statements ```plaintext MODULE Pattern7; VAR i, m, n: INTEGER; BEGIN FOR i := 0 TO n - 1 DO LDR R8 1 SUB R1 R1 CMP LNK R0 R1 BGT 7 STR R0 8 LSL R0 1 STR R8 4 END; LDR R8 0 ADD R0 R1 1 B -11 END Pattern7. ``` # Pattern 8: Proper Procedures ```plaintext MODULE Pattern8; PROCEDURE P(k: INTEGER; VAR y: INTEGER); VAR x: INTEGER; BEGIN SUB SP SP 16 // adjust SP STR LNK SP 0 // push ret addr STR R0 SP 4 // push x STR R1 SP 8 // push @y z := x; LDR R0 SP 4 STR R0 SP 12; // z y := z; LDR R0 SP 12; // y END Pattern8. ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 22 Context: ```markdown # Assembly Code Example ```assembly x[i+1] = n+5 ADD R0, R1, R0 LDR R0, R0, 0 STR R0, SP, 16 LDR R0, SP, 12 ; i ADD R0, R1, 1 LDR R1, SP, 8 ; x.len CMP R2, R1 BLI R12 LSL R0, R0, 2 LDR R1, SP, 4 ; x ADD R0, R1, R0 ADD R1, R1, 5 STR R1, R0, 0 LDR R1, SP, 16 ; n ADD R1, R1, 5 STR R1, R0, 0 ADD SP, SP, 20 B R15 BEGIN P(a); ADD R0, R8, 0 ; a MOV R1, R0, 12 END Pattern/10. ## Pattern 11: Sets This code pattern exhibits the construction of sets. If the specified elements are constants, the set value is computed by the compiler. Otherwise, sequences of move and shift instructions are used. Since shift instructions do not check whether the shift count is within sensible bounds, the results are unpredictable if elements outside the range 0 .. 31 are involved. ```assembly MODULE Pattern11; VAR S: SET; m, n: INTEGER; BEGIN S := (m); LDR R0, SB, 4 ; m MOV R1, R1 LSL R0, R1, R0 STR R0, SB, 0 S := (0 .. n); LDR R0, SB, 8 ; n MOV R1, R0, -2 LSL R0, R1 XOR R0, R0, 1 STR R0, SB, 1 S := (m .. 31); LDR R0, SB, 4 ; m LDR R1, SB, 4 MOV R1, 31 MOV R1, R1, 31 MOV R2, R0, -2 LSL R1, R2, 1 LSL R2, R2, 0 XOR R0, R0, R1 STR R0, SB, 0 S := (m .. n); LDR R0, SB, 4 ; m LDR R1, SB, 4 ; n MOV R1, R1, -2 LSL R1, R2, 0 XOR R0, R0, R1 STR R0, SB, 0 IF n IN (2, 3, 5, 7, 11, 13) THEN MOV R0, R0, 28 LDR R1, SB, 8 ADD R1, R1, 1 ASR R0, R0, R1 BPL 2 ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 23 Context: ```markdown m → 1 MOV R0 R0 1 STR R0 S4 m END END Pattern11. # Pattern 12: Imported variables and procedures When a procedure is imported from another module, its address is unavailable to the compiler. Instead, the procedure is identified by a number obtained from the imported module's symbol file. In place of the offset, the branch instruction holds (1) the number of the imported module, (2) the number of the imported procedure, and (3) a link in the list of BL instructions calling an external procedure. This list is traversed by the linking loader, that computes the actual offset (fixup, see Chapter 6). Imported variables are also referenced by a variable's number. In general, an access required two instructions. The first loads the static base register SB from a global table with the address of that module's data section. The module number of the imported variable serves as index. The second instruction loads the address of the variable, using the actual offset fixed up by the loader. In the following example, modules Pattern12a and Pattern12b both export a procedure and a variable. They are referenced from the importing module Pattern12c. ```pascal MODULE Pattern12a; VAR k : INTEGER; PROCEDURE P*; BEGIN k := 1 END P; END Pattern12a. MODULE Pattern12b; VAR x : REAL; PROCEDURE Q*; BEGIN x := -1 END Q; END Pattern12b. MODULE Pattern12c; IMPORT Pattern12a, Pattern12b; VAR i : INTEGER; y : REAL; BEGIN i := Pattern12a.k; y := Pattern12b.x; END Pattern12c. # Pattern 13: Record extensions with pointers Fields of a record type R1, which is declared as an extension of a type R0, are simply appended to the fields of R0, i.e., their offsets are greater than those of the fields of R0. When a record is statically declared, its type is known by the compiler. If the record is referenced via a pointer, however, this is not the case. A pointer bound to a base type R0 may well refer to a record of an extension R1 of R0. Type tests (and type guards) allow to test for the actual type. This requires that a type can be identified at the time of program execution. Because the language defines name equivalence instead of structural equivalence of types, a type may be identified by a number. We use the address of a unique type descriptor for this purpose. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 27 Context: ```markdown EXCL(u, 7); ADD R0 SB 12 @u LDR R1 R0 AND R1 R1 -129 ; [-7] STR R1 R0 ASSERT(m < n); LDR R1 SB 4 CMP R0 R1 BLGE R12 UNPK(X, n); LDR R0 SB x ASR R1 R0 23 SUB R1 R1 127 STR R1 SB 44 n LSL R1 R1 23 PACK(X, n); LDR R0 SB 8 x LDR R1 SB 4 n LSL R1 R1 23 ADD R0 R0 R1 STR R0 SB 8 x ADD R0 SB 8 ADD R1 SB 0 ADD R1 R1 4 ADD R0 R0 4 ASR R2 R2 24 BNE # s := "0123456789"; ADD R1 SB 96 @s ADD R1 SB 128 ; adr of string LDB R2 R1 4 loop ADD R1 R1 4 STRB R0 R1 0 ADD R0 R0 1 LDB R3 R1 0 ADD R1 R1 1 CMP R4 R2 R3 BNE 2 CMP R4 R2 0 BNE - BGE 3 m := -1 MOV R0 R1 STR R0 SB 0 m END END Pattern16. Pattern 17: Predeclared functions. MODULE Pattern17; VAR m: INTEGER; x, y: REAL; b: BOOLEAN; ch: CHAR; BEGIN y := ABS(m); LDR R0 SB 0 m CMP R0 R0 0 BGE 2 MOV R1 R0 0 SUB R0 R1 R0 STR R0 SB 4 n LSL R0 R0 1 y := ABS(x); LDR R0 SB x LSL R0 R0 1 END ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 29 Context: ```markdown Module-2 which does not feature type extension. Second, not making use of type extensions would make it simpler to translate the compiler into other languages for porting the language to other computers. And third, all extensions were known at the time the compiler was planned. Hence extensibility provided no argument for the introduction of a considerable variety of types. The simplest solution lies in using the multi-purpose fields and /or class-specific attributes. For example, val holds an address for variables, parameters, and procedures, an offset for record fields, and a value for constants. The definition of a type yields a record of type `Struct`, regardless of whether it occurs within a type declaration, in which case also a record of type `Object` (class = `Typ`) is generated, or in a variable declaration, in which case the type remains anonymous. All types are characterized by a form and a size. A type is either a basic type or a constructed type. In the latter case it refers to one or more other types. Constructed types are arrays, records, pointers, and procedural types. The attribute `form` refers to this classification. Its value is an integer. Just as different object classes are characterized by different attributes, different forms have different attributes. Again, the introduction of extensions of type `Struct` was avoided. Instead, some of the fields of type `Struct` remain unused in some cases, such as for basic types, and others are used for form-specific attributes. For example, the attribute `base` refers to the element type in the case of an array, to the result type in the case of a procedural type, to the type to which a pointer is bound, or to the base type of a (extended) record type. The attribute `disc` refers to the parameter list in the case of a procedural type, or to the list of fields in the case of a record type. As an example, consider the following declarations. The corresponding data structure is shown in Fig. 12.5. For details, the reader is referred to the program listing of module ORB and the respective explanations. ```pascal CONST N = 100; TYPE Ptr = POINTER TO Rec; Rec = RECORD n: INTEGER; p, q: Ptr END; VAR k: INTEGER; a: ARRAY [0..N-1] OF INTEGER; PROCEDURE P(x: INTEGER): INTEGER; ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 31 Context: ```markdown anonymous entities of additional, non-basic modes. Such entities reflect selectors, factors, terms, etc., i.e., constituents of expressions and statements. As such, they are of a transitory nature and hence are not represented by records allocated on the heap. Instead, they are represented by record variables local to the processing procedures and are therefore allocated on the stack. Their type is called `Item` and is a slight variation of the type `Object`. Items are not referenced via pointers. Let us assume, for instance, that a term `x*y` is parsed. This implies that the operator and both factors have been parsed already. The factors `x` and `y` are represented by two variables of type `Item` of `Var` mode. The resulting term is again described by an item, and since the product is transitory, i.e., has significance only within the expression of which the term is a constituent, it is to be held in a temporary location, in a register. In order to express that an item is located in a register, a new, non-basic mode `Reg` is introduced. Effectively, all non-basic modes reflect the target computer's architecture, in particular its addressing modes. The more addressing modes a computer offers, the more item modes are needed to represent them. The additional item modes required by the RISC processor are. They are declared in module `ORG`: | Reg | direct register mode | |------|----------------------| | RegI | indirect register mode | | Cond | condition code mode | The use of the types `Object`, `Item`, and `Struct` for the various modes and forms, and the meaning of their attributes are explained in the following tables: ### Objects: | class | val | a | b | r | |-------|------|-----|-----|-----| | 0 | Undf | | | | | 1 | Const| val | | | | 2 | Var | adr | base| | | 3 | Par | adr | off | | | 4 | Fld | off | off | | | 5 | Typo | TAdr| TAdr| modno| | 6 | SProc| num | | | | 7 | SFunc| num | | | | 8 | Mod | | | | | 10 | Reg | | regno| | | 11 | RegI | off | regno| | | 12 | Cond | | Tjmp| Fjmp| condition code | ### Structures: | form | nopFar | len | dsc | base | |------|--------|-----|-----|------| | 7 | Pointer| | base type | | 10 | ProcTyp| nopFar | param | result type | | 12 | Array | nofEl | element type | | 13 | Record | ext | lev | desc adr | fields | extension type | Items have an attribute called `lev` which is part of the address of the item. Positive values denote the level of nesting of the procedure in which the item is declared; `lev = 0` implies a global object. Negative values indicate that the object is imported from the module with number `-lev`. The three types `Object`, `Item`, and `Struct` are defined in module `ORB`, which also contains procedures for accessing the symbol table. ## 12.3.2. Module interfaces ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 32 Context: ```markdown Before embarking on a presentation of the compiler's main module, the parser, an overview of its remaining modules is given in the form of their interfaces. The reader is invited to refer to them when studying the parser. The interface of the scanner module `ORS` is simple. It defines the numeric values of all symbols. But its chief constituent is procedure `Get`. Each call yields the next symbol from the source text, identified by an integer. Global variables represent attributes of the read symbol in certain cases. If a number was read, `val` or `val`/hold its numeric value. If an identifier or a string was read, `str` holds the ASCII values of the characters read. Procedure `Mark` serves to generate a diagnostic output indicating a brief diagnostic and the scanner's current position in the source text. This procedure is located in the scanner, because only the scanner has access to its current position. `Mark` is called from all other modules. ```pascal DEFINITION ORS = ("Scanner") IMPORT Texts, Oberon; TYPE Ident = ARRAY 32 OF CHAR; VAR sel: INTEGER; val: REAL; id: Ident; str: ARRAY 256 OF CHAR; errn: BOOLEAN; PROCEDURE Mark(msg: ARRAY OF CHAR); PROCEDURE Get(VAR sym: INTEGER); PROCEDURE Init(source: Texts.Text; pos: INTEGER); END ORS. ``` Module `ORB` defines the basic data structures representing declared objects and their types. It also contains procedures for accessing these structures. `NewObj` serves to insert a new identifier, and it returns a pointer to the allocated object. `ThisObj` returns the pointer to the object whose name equals the global scanner variable `ORS.id`. `ThisImport` and `ThisField` determine imported objects and record fields with names equal to `ORS.id`. Procedure `Import` serves to read the specified symbol file and to enter its identifier in the symbol table (`class = Mod`). Finally, `Export` generates the symbol file of the compiled module, containing descriptions of all objects and structures marked for export. ```pascal DEFINITION ORB = ("Base table handler") TYPE Object = POINTER TO ObjDesc; Type = POINTER TO TypeDesc; ObjDesc = RECORD class, len, expn: INTEGER; expd: BOOLEAN; next: Object; type: Type; name: ORS.Ident; val: INTEGER; END; TypeDesc = RECORD form: ref, mon: INTEGER; (* 'ref' is used for import/export only *) noplft: INTEGER; (* (for records: extension level) *) len: INTEGER; (* (for records: address of descriptor) *) desc: TypeObj; (* desc.[typ]: Object; *) base: Type; size: INTEGER; END; VAR TypeScope: Object; byteType, boolType, charType, intType, realType, setType, nilType, noType, strType: Type; ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 33 Context: ```markdown # PROCEDURE Init; # PROCEDURE Close; # PROCEDURE NewObj(VAR obj: Object; id: ORS.Ident; class: INTEGER); # PROCEDURE ThisObj(): Object; # PROCEDURE ThisMethod(mod: Object): Object; # PROCEDURE OpenScope; # PROCEDURE CloseScope; # PROCEDURE Import(VAR mod: modid: ORS.Ident); # PROCEDURE Export(VAR mod: ORS.Ident); # END ORG. VAR newSF: BOOLEAN; VAR key: INTEGER; Module ORG contains the procedures for code generation. The names of these procedures indicate the respective constructs for which code is to be produced. Note that an individual code generator procedure is provided for every standard, predefined procedure. This is necessary because they generate in-line code. ## DEFINITION ORG; CONST WordSize = 4; ### TYPE Item = RECORD mode: INTEGER; type_: ORB.Type; a, b: INTEGER; rdb: BOOLEAN; (* "read only" *) END; VAR x: INTEGER; ### PROCEDURE MakeConstItem(VAR x: Item; typ: ORB.Type; var: INTEGER); ### PROCEDURE MakeRealItem(VAR x: Item; val: REAL); ### PROCEDURE MakeStringItem(VAR x: Item; len: INTEGER); ### PROCEDURE MakeIntItem(VAR x: Item; len: INTEGER); ### PROCEDURE MakeCharItem(VAR x: Item; obj: Object; curlex: INTEGER); ### PROCEDURE Field(VAR x: Item; obj: Object); ### PROCEDURE Index(VAR x: Item); (* x := x[y] *) ### PROCEDURE Deref(VAR x: Item); ### PROCEDURE BuffT(VAR x: Item; typ: ORB.Type; vararg: BOOLEAN); ### PROCEDURE Not(VAR x: Item); (* x := ~x, Boolean operators *) ### PROCEDURE And1(VAR x: Item); (* x := x & x *) ### PROCEDURE And2(VAR x: Item); (* x := x & OR *) ### PROCEDURE Or1(VAR x: Item); (* x := x OR x *) ### PROCEDURE Or2(VAR x: Item); ### PROCEDURE Neg(VAR x: Item); (* x := -x, arithmetic operators *) ### PROCEDURE AddOp(VAR x: Item); (* x := x + y *) ### PROCEDURE SubOp(VAR x: Item); (* x := x - y *) ### PROCEDURE MulOp(VAR x: Item); (* x := x * y *) ### PROCEDURE DivOp(VAR x: Item); (* x := x / y *) ### PROCEDURE RealOp(VAR x: Item); (* x := x op y *) ### PROCEDURE Singleton(VAR x: Item); (* x := x, set operators *) ### PROCEDURE SetVAR(VAR x: Item); (* x := x *) ### PROCEDURE Set(VAR x: INTEGER; VAR y: Item); (* x := x + y *) ### PROCEDURE SetOp(VAR x: INTEGER; VAR y: Item); (* x := x op y *) ### PROCEDURE Intellation(op: INTEGER; VAR x: Item); (* x := x*y *) ### PROCEDURE SetRelation(op: INTEGER; VAR x: Item); (* x := x < x *) ### PROCEDURE RealRelation(op: INTEGER; VAR x: Item); (* x := x < x *) ### PROCEDURE StringItem(VAR x: Item); (* x := x * y *) ### PROCEDURE StrToChar(VAR x: Item); (* assignments *) ### PROCEDURE Store(VAR x: VAR.Item); (* x := y *) ### PROCEDURE StoreString(VAR x: Item); (* x := y *) ### PROCEDURE CopyString(VAR x: Item); (* from x to y *) ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 34 Context: ```markdown # PROCEDURE Definitions - **VarParam**(VAR x: item; ftpe: ORB.Type); (* parameters *) - **ValueParam**(VAR x: item); - **OpenArrayParam**(VAR x: item); - **StringParam**(VAR x: item); - **For**(VAR x, y: item; (* For Statement *)) - **For1**(VAR x, y: z: item; VAR l: LONGINT); - **For2**(VAR x, y: item); (* Branches, procedure calls, procedure prolog and epilog *) - **Here**(l: LONGINT); - **Flump**(VAR l: LONGINT); - **CJump**(VAR x: item); - **BJump**(l: LONGINT); - **CJump**(VAR x: item; l: LONGINT); - **Flup**(VAR x: item); - **Proc**(VAR x: item; VAR r: LONGINT); - **Call**(VAR x: item; l: LONGINT); - **Enter**(protocols: LONGINT; localSize: LONGINT; int: BOOLEAN); - **Return**(form: INTEGER; VAR x: item; size: LONGINT; int: BOOLEAN); (* In-line code procedures *) - **Increment**(updown: LONGINT; VAR x, y: item); - **Include**(index: LONGINT; VAR x, y: item); - **Assert**(VAR x: item); - **New**(VAR x: item); - **Pack**(VAR x: item); - **Length**(VAR x: item); - **Get**(VAR x: item); - **Put**(VAR x: item); - **Copy**(VAR x, y: z: item); - **DPFS**(VAR x: item); - **LDFSR**(VAR x: item); - **LDR**(VAR x: item); (* In-line code functions *) - **Abs**(VAR x: item); - **Dord**(VAR x: item); - **Floor**(VAR x: item); - **Float**(VAR x: item); - **Ord**(VAR x: item); - **Len**(VAR x: item); - **Shift**(VAR LONGINT; VAR x, y: item); - **ABC**(VAR x: item); - **SBC**(VAR x: item); - **UML**(VAR x, y: item); - **Bit**(VAR x: item); - **Register**(VAR x: item); - **RVar**(VAR x: item); - **Add**(VAR x: item); - **Condition**(VAR x: item); ## Other Procedures - **Open**(n: INTEGER); - **SetDataSize**(id: LONGINT); - **Header**(); - **Close**(VAR mod: ORS.Id; key, refont: LONGINT); END ORG. # 12. A Parser The main module ORP constitutes the parser. Its single command **Compile** - at the end of the program listing - identifies the source text according to the Oberon command conventions. It then calls procedure **Module** with the identified source text as a parameter. The command forms are: ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 41 Context: ```markdown the implementation (rather than the interface) of a module are supposed to remain invisible to the clients. Only changes in the interface are allowed to effect changes in the symbol file, requiring recompilation of all clients. Therefore, addresses are replaced by export numbers. The variable `exno` (global in ORP) serves as running number (see `ORP.Declarations` and `ORP.ProcedureDecl`). The translation from export number to address is performed by the loader. Every code file contains a list (table) of addresses (of variables and entry points for procedures). The export number serves as index in this table to obtain the requested addresses. Export numbers are generated by the parser. Objects exported from some module M1 may refer in their declaration to some other module M0 imported by M1. It would be unacceptable, if an import of M1 would then also require the import of M0, i.e. imply the automatic reading of M0's symbol file. It would trigger a chain reaction of imports that must be avoided. Fortunately, such a chain reaction can be avoided by making symbol files self-contained, i.e. by including in every symbol file the description of entities that stem from other modules. Such entities are always types. The inclusion of types imported from other modules seems simple enough to handle: type descriptions must include a reference to the module from which the type was imported. This reference is the name and key of the respective module. However, there exists one additional fact, hence, when importing M0 during compilation of M1, the imported element T must not only be registered in the symbol table, but it must also be recognized as being identical to T already imported from M2 directly. It is rather fortunate that the language definition specifies equivalence of types on the basis of names rather than structure, because it allows type tests at execution time to be implemented by a simple address comparison. The measures to be taken to satisfy the new requirements are as follows: 1. Every type element in a symbol file is given a module number. Before a type description is emitted to the file. 2. If a type to be exported has a name and stems from another, imported module, then also the name and key of the module are attached, from which the type stems (see end of procedure `ORB.Out.Type` and end of `ORB.In.Type`). An additional detail is worth being mentioned here: Hidden pointers. We recall that individual fields of exported record types may be hidden. If marked (by an asterisk) they are exported and therefore visible in importing modules. If not marked, they are not exported and remain invisible, and evidently seem to be invisible in symbol files. However, this is a fallacy. They need to be included in symbol files, although without name, because of meta information to be provided for garbage collection. This is elucidated as follows: Assume that a module M1 declares a global pointer variables of a type imported from module M0. ```pascal MODULE M0; TYPE Ptr = POINTER TO Rec0; Rec0 = RECORD p1: Ptr; q: Prt; ... END; END M0. MODULE M1; VAR p: M0.Ptr; R: RECORD M1.Ptr; ... END; END M1. Here p and r are roots of data structures that must be visited by the garbage collector. If they are not, they will not be marked, and therefore collected with disastrous and entirely unpredictable consequences. The crux is that not only exported pointers (p1.p) must be listed, but also hidden ones (p.q), although they are not accessible in module M1. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 43 Context: ```markdown Instructions are emitted sequentially and emitted by the four procedures `Put0`, `Put1`, `Put2`, `Put3`. They directly correspond to the instruction formats of the RISC processor (see Chapter 11). The instructions are stored in the array code and the compiler variable `pc` serves as running index. ``` PROCEDURE Put0(op, a: b: INTEGER); format F0 PROCEDURE Put1(op, a: b: IN INTEGER); format F1 PROCEDURE Put2(op, a: b: off: INTEGER); format F2 PROCEDURE Put3(op, cond: off: INTEGER); format F3 ``` ## 12.7.1. Expressions Expressions consist of operands and operators. They are evaluated and have a value. First, a number of make-procedures transform objects into items (see Section 12.3.2). The principal one is `MakeItem`. Typical objects are variables (class, mode = `Var`). Global variables are addressed with base register `SB (xr = 13)`, local variables with the stack pointer `SP (xr = 14)`. `VAR`-parameters are addressed indirectly; the address is on the stack (class, mode = `Par`, `Ind`). `x` is the offset from the stack pointer. Before an operator can be applied to operands, these must first be transferred (loaded) into registers. This is because the RISC performs operations only on registers. The loading is achieved by procedure `load` and `loadAdr` in `ORG`. The resulting mode is `Reg`. In allocating registers, a strict stack principle is used, starting with `R0`, up to `R11`. This is certainly not an optimal strategy and provides ample room for improvement (usually called optimization). The compiler variable `RH` indicates the next free register (top of register stack). Base address `SB` is, as the name suggests, static. But this holds only within a module. It implies that on every transfer to a procedure in another module, the static base must be adjusted. The simplest way is to load `SB` before every external call, and to restore it to its old value after return from the procedure. We chose a different strategy: loading on demand (see below: global variables). If a variable is indexed, has a field selector, is dereferenced, or has a type guard, this is detected in the parser by procedure `selector`. It calls generators `Index`, `Field`, `DeRef`, or `TypeTest` accordingly (see Section 12.3.2 and patterns 1 - 4 in Section 12.2). These procedures cause item nodes to change as follows: 1. **Index(x, y) (y is loaded into x.r)** - `Var -> RegI` `ADD y, SP, y.r` // array variable - `Par -> RegI` `LDR RH, SP, x.a` `ADD y, RH, y.r` // array parameter - `RegI -> RegI` `ADD x, x.r, y.r` // indexed array 2. **Field(x, y) (y.mode = Field, y.a = field offset)** - `Var -> Var` `none` // field designator, add offset to x.a - `RegI -> RegI` `none` // add field offset to x.a - `Par -> none` `add field offset to x.b` 3. **DeRef(x)** - `Var -> RegI` `LDR RH, SP, x.a` // dereferenced x.a - `Par -> RegI` `LDR RH, SP, x.a` `LDR RH, x.b` // dereferenced parameter x.a - `RegI -> RegI` `LDR x.r, x.r, x.a` A fairly large number of procedures then deal with individual operators. Specifically, they are `Not`, `And1`, `And2`, `Or1`, `Or2` for Boolean operators, `Neg`, `AddOp`, `MulOp`, `DivOp` for operations on integers, `RealOp` for operations on real numbers, and `Singleton`, `SetIn`, and `SetOp` for operations. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 44 Context: ```markdown # 12.7 RISC Operations ## 12.7.2 Relations RISC does not feature any compare instruction. Instead, subtraction is used, because an implicit comparison with 0 is performed along with any arithmetic (or load) instruction. Instead of `x < y` we use `x - y < 0`. This is possible, because in addition to the computed difference deposited in a register, also the result of the comparison is deposited in the condition flags N (difference negative) and Z (difference zero). Relations therefore yield a result item `x` with mode `Cond.x` (relat(g)ism) identifies the relation. Branch instructions (jumps) are executed conditionally depending on these flags. The value `x.r` is then used when generating branch instructions. For example, the relation `x < y` is translated simply into: ``` LDR R0, SP, x LDR R1, SP, y CMP R0, R0, R1 ``` And the resulting item mode is `x.mode = Cond, x.r = "less"`. (The mnemonic CMP is synonymous with SUB). More about relations and Boolean expressions will be explained in Section 12.7.6. ## 12.7.3 Set Operations The type SET represents sets of small integers in the range from 0 to 31. Bit `i` being 1 signals that `i` is an element of the set. This is a convenient representation, because the logical instructions directly mirror the set operations: AND implements set intersection, OR set union, and XOR the symmetric set difference. This representation also allows a simple and efficient implementation of membership tests. The instructions for the expression `m IN S` is generated by procedure `In`. Assuming the value in register R0, and the set `S` in R1, we obtain: ``` ADD R0, R0, 1 FOR R1, R1, R0 ; rotate S by i+1 position, the relevant bit moving to the sign bit ``` The resulting item mode is `Cond.x` where `x.r = "minus"`. Of some interest are the procedures for generating sets, i.e., for processing `{m}, {m .. n}, and {m, n}`, where `m` and `n` are integer expressions. We start with `{m}`. It is generated by procedure `Singleton` using a shift instruction. Assuming `m` in R0, the resulting code is: ``` MOV R1, 0, 1 LSL R0, R1, R0 ; shift 1 by m bit positions to the left ``` Somewhat more sophisticated is the generation of `{m .. n}` by procedure `Set`. Assuming `m` in R0, and `n` is R1, the resulting code is: ``` MOV R2, 0, 2 LSL R1, R2, R1 ; shift 2 by n bit positions to the left MOV R0, 2, -1 LSL R0, R2, R0 ; shift 1 by m bit positions to the left XOR R0, R0, R1 ``` The set `{m, n}` is generated as the union of `{m}` and `{n}`. If any of the element values is a constant, several possibilities of code improvement are possible. For details, the reader is referred to the source code of ORG. ## 12.7.4 Assignments ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 47 Context: ```markdown # Stack Frames Scheme and layout determine the code sequences for call, entry, and exit of procedures. Here is an example of a procedure `P` with 2 parameters: ## Call: ``` LDR R0, param0 LDR R1, param1 BL P ``` ## Prolog: ``` SUB SP, SP, size ; decrement SP STR LNK, SP, 0 ; push return addr STR R0, SP, 4 ; push parameter 0 STR R1, SP, 8 ; push parameter 1 .... ``` ## Epilog: ``` LDR LNK, SP, 0 ; pop return addr ADD SP, SP, size ; increment SP BR LNK ``` When the call instruction is executed, parameters reside in registers, starting with R0. For function procedures, the result is passed in register R0. This scheme is very efficient; storing the parameters occurs only in a single place, namely at procedure entry, and not before each call. However, it has severe consequences for the entire register allocation strategy. Throughout the compiler, registers **must** be allocated in strict stack fashion. Furthermore, parameter allocation must start with R0. This is a distinct drawback for function calls. If registers are occupied by other values loaded prior to the call, they must be cleared; i.e., the parameters must be saved and reloaded after return. This is rather cumbersome (see procedures `ORG.SaveRegisters` and `ORG.RestoreRegisters`). ### Register Saving | Expression | Register Saving Needed | |----------------------|--------------------------| | F(x) | no register saving | | F(x + F(x)) | register saving necessary | --- ## Type Extension Static typing is an important principle in programming languages. It implies that every constant, variable, or function is of a certain data type, and that this type can be derived by reading the program text without executing it. It is the key principle to introduce important redundancy in languages in such a form that a compiler can detect inconsistencies. It is therefore the key element for reducing the number of errors in programs. However, it also acts as a restriction. It is, for example, impossible to construct data structures (arrays, trees) with different types of elements. In order to relax the rule of strictly static typing, the notion of type extension was introduced in Oberon. It makes it possible to construct. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 48 Context: ```markdown # Type Safety in Oberon Inhomogeneous data structures without abandoning type safety. The price is that the checking of type consistency must in certain instances be deferred to run-time. Such checks are called *type tests*. The challenge is to defer to run-time as few checks as possible and as many as needed. The solution in Oberon is to introduce families of types, and compatibility among their members. Their members are thus related, and a family forms a hierarchy. The principle idea is the following: Any record type `T0` can be extended into a new type `T1` by additional record fields (attributes). `T1` is then called an *extension* of `T0`, which in turn is said to be `T1`'s *base type*. `T1` is then compatible with `T0`, but not vice-versa. This property ensures that in many cases static type checking is still possible. Furthermore, it turns out that run-time tests can be made very efficient, thus minimizing the overhead for maintaining type safety. For example, given the declarations: ```pascal TYPE R0 = RECORD u: INTEGER END; R1 = RECORD (R0) u: INTEGER END; ``` we say that `R1` is an extension of `R0`. `R0` has the fields `u` and `v`, `R1` has `u`, `v`, and `w`. The concept becomes useful in combination with pointers. Let ```pascal TYPE P0 = POINTER TO R0; P1 = POINTER TO R1; VAR p0: P0; p1: P1; ``` Now it is impossible to assign `p0` to `p0` (because a `P1` is always also a `P0`), but not `p0` to `p1`, because a `P0` need not be a `P1`. This has the simple consequence that a variable of type `P0` may well point to an extension of `R0`. Therefore, data structures can be declared with a base type `P0`, as common element type, but in fact they can individually differ; they can be any extension of the base type. Obviously, it must be possible to determine the actual, current type of an element even if the base type is statically fixed. This is possible through a *type test*, syntactically a Boolean factor: ```pascal p0 IS P1 ``` (short for `p0 IS R1`) Furthermore, we introduce the *type guard*. In the present example, the designator `p0^` is illegal, because there is no field `v` in a record of type `P0`, even if the current value of `p0^` is a `R1`. As this case occurs frequently, we introduce the short notation `p0(P1^),` implying a test `p0 IS P1` and an abort if the test is not met. It is important to mention that this technique also applies to formal variable parameters of record type, as they also represent a pointer to the actual parameter. Its type may be any extension of the type specified for the formal parameter in the procedure heading. ## Implementation of Type Test and Type Guard How are type test and type guard efficiently implemented? Our first observation is that they must consist of a single comparison only, similar to index checks. This in turn implies that types must be identified by a single word. The solution lies in using the unique address of the type descriptor or the *record* type. Which data must this descriptor hold? Essentially, type descriptors (TD) must identify the base types of a given type. Consider the following hierarchy: ```pascal TYPE T = RECORD ... END; T0 = RECORD (T) ... END; // extension level 1 T1 = RECORD (T) ... END; // extension level 1 T00 = RECORD (T0) ... END; // extension level 2 T01 = RECORD (T0) ... END; // extension level 2 T10 = RECORD (T1) ... END; // extension level 2 T11 = RECORD (T1) ... END; // extension level 2 ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 50 Context: ```markdown latter case, the module numbers have an inverted sign (are negative). Such imported objects are static, i.e., have a fixed address. In principle, their absolute address could be computed (fixed) by the module loader. However, this is not practicable, because RISC instructions have an address offset of 16 bits only. It is therefore necessary in the general case to use a base address in conjunction with the offset. We use a single register for holding the static base (SB, R13). This register need be reloaded for every access to an imported variable. However, the compiler keeps track of external accesses; if a variable is to be accessed from the same module as the previous case, then reloading is avoided (see procedure GetSB and global compiler variable curSB). This base address is fetched from a table global to the entire system. This module table contains one entry for every module loaded, namely the address of the module's data section. The address of the table is permanently in register MT (R12). An access to an imported variable therefore always requires two instructions: ``` LDR SB, MT, mod+0 ; base address of data section LDR SB, offset ; offset computed by the loader from object's export number ``` Considering the fact that references to external variables are (or should be) rare, this circumstance is of no great concern. (Note also that such accesses are read-only). More severe is the fact that we also treat global variables contained in the same module by the same technique. Their level number is 0. One might use a specific base register for the base of the current module. Its content would then have to be reloaded upon every procedure call and after every return. This is common technique, but we have chosen to reload only when necessary, i.e., only when an access is at hand. This strategy rewards the programmer who sensibly uses global variables rarely. ## 12.7. Traps This compiler provides an extensive system of safeguard by providing run-time checks (aborts) in several cases: | trap number | trap cause | |-------------|----------------------------------| | 1 | array index out of range | | 2 | type guard failure | | 3 | array or string copy overflow | | 4 | access via NIL pointer | | 5 | illegal procedure call | | 6 | integer division by zero | | 7 | assertion violated | These checks are implemented very efficiently in order not to downgrade a program's performance. Involved is typically a single compare instruction, plus a conditional branch (BLR MT). It is assumed that entry 0 of the module table contain not a base address (module numbers start with 1), but a branch instruction to an appropriate trap routine. The trap number is encoded in bits 47:0 of the branch instruction. The predefined procedure `Assert` generates a conditional trap with trap number 7. For example, the statement `Assert(m = n)` generates: ``` LDR RO, m ; load m LDR R1, n ; load n CMP RO, R0 ; branch if unequal through R12, trap number 7 BLR R1, 7 ; CH ``` Procedure `New`, representing the operator `NEW`, has been implemented with the aid of the trap mechanism. (This is in order to omit in ORG any reference to module `Kernel`, which contains the allocation procedure `New`). The generated code for the statement `NEW(p)` is: ``` ADD RO, SP, p ; address of p ADD R1, SB, tag ; tag type BLR R7, 0CH ; branch and link unconditionally through R12 (MT), trap number 0 ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 52 Context: ```markdown # Graphic Viewer The graphic viewer shows an excerpt of the drawing plane, and several viewers may show different parts of a drawing. The most frequently used commands are built-in as mouse clicks and combinations of clicks. Additional commands are selectable from texts, either in viewer menus (title bars) or in the text called **Draw Tool**. Fig. 13.1 shows the display with two graphic viewers at the left and the draw tool text at the right. The mouse buttons have the following principal functions whenever the cursor lies in a graphic frame: | Left | Middle | Right | |-----------------|------------------|-------------| | draw / set caret| move / copy | select | ## 13.2.1 Basic Commands The command `Draw.Open` opens a new viewer and displays the graph with the name given as a parameter. We suggest that file names use the extension `.graph`. **Drawing a line.** In order to draw a horizontal or vertical line from P0 to P1, the left key is pressed with the cursor at P0 and, while the key is held, the mouse and cursor is moved to P1. Then the key is released. If P0 and P1 differ in both their x and y coordinates, the end point is adjusted so that the line is either horizontal or vertical. **Writing a caption.** First the cursor is positioned where the caption is to appear. Then the left key is clicked, causing a crosshair to appear. It is called the caret. Then the text is typed. Only single lines of texts are accepted. The `DEL` key may be used to retract characters (backspace). **Selecting.** Most commands require the specification of operands, and many implicitly assume the previously selected elements - the **selection** - to be their operands. A single element is selected by pointing at it with the cursor and then clicking the right mouse button. This also causes previously selected elements to be deselected. If the left key is also clicked, their selection is retained. This action is called an **interclick**. To select several elements at once, the cursor is moved from P0 to P1 while the right key is held. Then all elements lying within the rectangle with diagonally opposite corners at P0 and P1 are selected. Selected lines are displayed as dotted lines, selected captions (and macros) by inverse video mode. A macro is selected by pointing at its lower left corner. The cursor is called **sensitive area**. **Moving.** To move (displace) a set of elements, the elements are first selected and then the cursor is moved from P0 to P1 while the middle key is held. The vector from P0 to P1 specifies the movement and is called the **displacement vector**. P0 and P1 may lie in different viewers displaying the same graph. Small displacements may be achieved by using the keyboard's cursor keys. **Copying.** Similarly, the selected elements may be copied (duplicated). In addition to pressing the middle key indicating the displacement vector, the left key is interclicked. The copy command may also be used to copy elements from one graph into another graph by moving the cursor from one viewer into another viewer displaying the destination graph. A text item may be copied from a text frame into a graphic frame and vice-versa. There exist two ways to accomplish this: 1. First the caret is placed at the destination position, then the text is selected and the middle key is interclicked. 2. First the text is selected, then the caret is placed at the destination position and the middle key is interclicked. **Shifting the plane.** You may shift the entire drawing plane behind the viewer by specifying a displacement vector pressing the middle button (like in a move command) and interclicking the right button. The following table shows a summary of the mouse actions: | Left | Action | |------------------|----------------| | draw | line | | left (no motion) | set caret | ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 54 Context: ```markdown The subsequent commands change attributes of drawing elements, such as line width, text font, and color, and they apply to the most recent selection. ```plaintext Draw.SetWidth w default = 1, 0 < w < 7. Draw.ChangeFont fontname Draw.ChangeColor c Draw.ChangeWidth w (0 < w < 7) ``` The `ChangeColor` command either takes a color number in the range of 1 to 15 or a string as a parameter. It serves to copy the color from the selected character. ### 13.2.4. Macros A macro is a (small) drawing that can be identified as a whole and used as an element within a (larger) drawing. Macros are typically stored in collections called libraries, from where they can be selected and copied individually. **Draw.Macro lib mac**: The macro `mac` is selected from the library named `lib` and inserted in the drawing at the caret's position. An example for the use of macros is drawing electronic circuit diagrams. The basic library file containing frequently used TTL components is called `TTLO.Lib`, and a drawing showing its elements is called `TTLO.Graph` (see Figure 13.2). ![Figure 13.2 Viewer with circuit macros of TTL library](path/to/your/image) ### 13.2.5. Rectangles Rectangles can be created as individual elements. They are frequently used for framing sets of elements. Rectangles consist of four lines which are selectable as a unit. The attribute commands `Draw.SetWidth`, `System.SetColor`, `Draw.ChangeWidth`, and `Draw.ChangeColor` also apply to rectangles. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 58 Context: ```markdown handle: PROCEDURE (obj: Object; VAR M: Msg); next: Object END; PROCEDURE Handle (obj: Object; VAR M: Msg); BEGIN (* this procedure is assigned to the handle field of every line object *) IF M.IsDrawMsg THEN drawLine(obj)(Line) ELSIF M.IsWriteMsg THEN writeLine(obj)(Line), M(WriteMsg).R ELSE ... END END; PROCEDURE drawGraphic(first: Object; VAR M: Msg); VAR obj: Object; BEGIN obj := first; WHILE obj <> NIL DO obj.handle(obj, M); obj := obj.next END END drawGraphics; In the present system, a combination of the two schemes presented so far is used. It eliminates the need for individual method fields in each object record as well as the cascaded IF statement for discriminating among the message types. Yet it allows further addition of new methods for later extensions without the need to change the object's declaration. The technique used is to include a single field (called obj) in each record (analogous to the handler). This field is a pointer to a method record containing the procedures declared for the base type. At least one of them uses a message parameter, i.e., a parameter of record structure that is extensible. TYPE Method = POINTER TO MethodDesc; Msg = RECORD END; Context = RECORD END; Object = POINTER TO ObjectDesc; ObjectDesc = RECORD x, y: INTEGER; selected: BOOLEAN; method: NEXT: Object END; MethodDesc = RECORD new: Modules.Command; copy: PROCEDURE (obj: Object); draw: handle: PROCEDURE (obj: Object; VAR M: Msg); selectable: PROCEDURE (obj: Object; x, y: INTEGER): BOOLEAN; read: PROCEDURE (obj: Object; VAR R: Files.Rider; VAR C: Context); write: PROCEDURE (obj: Object; con: INTEGER; VAR R: Files.Rider; VAR C: Context); END; A single method instance is generated when a new object type is created, typically in the initialization sequence of the concerned module. When a new object is created, a pointer to its record is assigned to the obj field of the new object descriptor. A call then has the form obj^.write(obj, R). This example exhibits the versatility of Oberon's type extension and procedure variable features very well, and it does so without hiding the data structures involved in a dispensable, built-in run-time mechanism. The foregoing deliberations suggest the system's modular structure shown in Figure 13.3: ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 59 Context: ```markdown # Lines, Captions, Macros, and Other Classes ![Figure 13.3 Clients of module Graphics](graphics-figure.png) The modules in the top row implement the individual object types' methods, and additionally provide commands, in particular `Make` for creating new objects. The base module specifies the base types and procedures operating on graphics as a whole. Our system, however, deviates from this scheme somewhat for several reasons: 1. **Implementation of the few methods** requires relatively short programs for the basic objects. Although a sensible modularization is desirable, we wish to avoid atomization, and therefore merge parts that would result in tiny modules with the base module. 2. **The elements of a graphic** refer to fonts used in captions and to libraries used in macros. The writing and reading procedures therefore carry a context consisting of fonts and libraries as an additional parameter. Routines for mapping a font (library) to a number according to a given context on output, and a number to a font (library) on input are contained in module `Graphics`. 3. **In the design of the Oberon System**, a hierarchy of four modules has proven to be most appropriate: - Module with base type handling the abstract data structure. - Module containing procedures for the representation of objects in frames (display handling). - Module containing the primary command interpreter and connecting frames with a viewer. - A command module scanning command lines and invoking the appropriate interpreters. The module hierarchy of the Graphics System is shown together with its analogy, with the Text System: | Function | Graphics | Text | |-------------------|-------------|-------------| | Command Scanner | Draw | Edit | | Viewer Handler | MenuViewers| MenuViewers | | Frame Handler | GraphicFrames| TextFrames | | Base | Graphics | Texts | As a result, module `Graphics` does not only contain the base type `Object`, but also its extensions `Line` and `Macro`. Their methods are also defined in `Graphics`, with the exception of drawing methods, which are defined in `GraphicFrames`, because they refer to frames. So far, we have discussed operations on individual objects and the structure resulting from the desire to be able to add new object types without affecting the base module. We now turn our attention briefly to operations on graphics as a whole. They can be grouped into two kinds, namely operations involving a graphic as a set, and those applying to the selection, i.e., to a subset only. The former kind consists of procedures `Add`, which inserts a new object, `Draw`, which traverses the set of objects and invokes their drawing methods, `SelectObj`, which searches for an object at a given position, `SelectAll`, which marks an object to be selected, `SelectArea`, which identifies all objects lying within a given rectangular area and marks them, `Selectable`, a Boolean function, and `Enumerate`, which applies the parametric procedure handle to all objects of a graphic. Furthermore, the procedures `Load`, `Store`, `Print`, and `WriteFile` belong to this kind. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 63 Context: ```markdown A mode parameter is also necessary for reflecting object deletion. First, the selected objects are drawn with mode indicating erasure. Only afterwards are they removed from the graphic's linked list. Furthermore, the message parameter of the drawing procedure contains two offsets `x` and `y`. They are added to the object's coordinates, and their significance will become apparent in connection with macros. The same holds for the color parameter. The drawing procedures are fairly straightforward and use the four basic raster operations of module `Display`. The only complication arises from the need to clip the drawing at the frame boundaries. In the case of captions, a character is drawn only if it fits into the frame in its entirety. The raster operations do not test (again) whether the indicated position is valid. At this point we recall that copies of a viewer (and its frames) can be generated by the `System.Copy` command. Such copies display the same graphic, but possibly different excerpts of them. When a graphic is changed by an insertion, deletion, or any other operation, at a place that is visible in several frames, all affected views must reflect the change. A direct call to a drawing procedure indicating a frame and the change does therefore not suffice. Here again, the object-oriented style solves the problem neatly: In place of a direct call a message is broadcast to all frames, the message specifying the nature of the required updates. The broadcast is performed by the general procedure `Viewers.Broadcast(M)`. It invokes the handlers of all viewers with the parameter `M`. The viewer handlers either interpret the message or propagate it to the handlers of their subframes. Procedure `obj.handle` is called with a control message as parameter when pointing at the object and clicking the middle mouse button. This allows control to be passed to the handler of an individual object. The definition of module `GraphicFrames` is summarized by the following interface: ```modula-2 DEFINITION GraphicFrames; IMPORT Display, Graphics; TYPE Frame = POINTER TO FrameDesc; Location = POINTER TO LocDesc; LocDesc = RECORD x, y: INTEGER; next: Location END; FrameDesc = RECORD (Display.FrameDesc) graph: Graphics.Graph; xg, Y1, Y1: x, y: INTEGER; marked, clicked: BOOLEAN; mark: LocDesc END; (* mode = 0 -> draw according to selected, 1: normal -> selected, 2: selected -> normal, 3: erase *) DrawMsg = RECORD (Graphics.Msg) t: Frame; x, y, col, mode: INTEGER END; PROCEDURE Restore (F: Frame); PROCEDURE Focus (F: Frame); PROCEDURE Selected (F: Frame); PROCEDURE ThisX (x: INTEGER; F: Frame); PROCEDURE Draw (F: Frame); PROCEDURE Erase (F: Frame); PROCEDURE DrawObj (F: Frame; obj: Graphics.Obj); PROCEDURE EraseObj (F: Frame; obj: Graphics.Obj); PROCEDURE Change (F: Frame; VAR msg: Graphics.Msg); PROCEDURE Decease (F: Frame); PROCEDURE Deselect (F: Frame); PROCEDURE Macro (VAR Lname, Mname: ARRAY OF CHAR); ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 64 Context: ```markdown ## 13.5 The User Interface Although the display is the prime constituent of the interface between the computer and its user, we chose the title of this chapter for a presentation primarily focused on the computer's input, i.e., on its actions instigated by the user's handling of keyboard and mouse, the editing operations. The design of the user interface plays a decisive role in a system's acceptance by users. There is no fixed set of rules which determine the optimal choice of an interface. Many issues are a matter of subjective judgement, and all too often convention is being mixed up with convenience. Nevertheless, a few criteria have emerged as fairly generally accepted. We base our discussion on the premise that input is provided by a keyboard and a mouse, and that keyboard input is essentially to be reserved for textual input. The critical issue is that a mouse - apart from providing a cursor position - allows to signal actions by the state of its keys. Typically, there are far more actions than there are keys. Some mice feature a single key only, a situation that we deem highly unfortunate. There are, however, several ways to "enrich" key states: 1. **Position.** Key states are interpreted depending on the current position of the mouse represented by the cursor. Typically, interpretation occurs by the handler installed in the view covering the cursor position, and different handlers are associated with different view types. The handler chosen for interpretation may even be associated with an individual (graphic) object and depend on that object's type. 2. **Clicks.** Interpretation may depend on the number of repeated clicks (of the same key), and/or on the duration of clicks. 3. **Interclicks.** Interpretation may depend on the combination of keys depressed until the last one is released. This method is obviously inapplicable for single-key mice. Apart from position dependence, we have quite successfully used interclicks. A ground rule to be observed is that frequent actions should be triggered by single-key clicks, and only variants of them should be signalled by interclicks. The essential art is to avoid overloading this method. Less frequent operations may as well be triggered by textual commands, i.e., by pointing at the command word and clicking the middle button. Even for this kind of activation, Oberon offers two variations: 1. The command is listed in a menu (title bar). This solution is favored when the respective viewer is itself a parameter to the command, and it is recommended when the command is reasonably frequent, because the necessary mouse movement is relatively short. 2. The command lies elsewhere, typically in a viewer containing a tool text. Lastly, we note that any package such as `Draw` is integrated within an entire system together with other packages. Hence it is important that the rules governing the user interfaces of the various packages do not differ unnecessarily, but that they display common ground rules and a common design "philosophy". `Draw`'s conventions were, as far as possible and sensible, adapted to those of the text system. The right key serves for selecting, the left for setting the caret, and the middle key for activating general commands, in this case moving and copying the graphic. Inherently, drawing involves certain commands that cannot be dealt with in the same way as text fonts. A character is created by typing on the keyboard; a line is created by dragging the mouse while holding the left key. Interclicks left-middle and right-middle are treated in the same way as in the text system (copying a caption from the selection to the caret), and this is not surprising, because text and graphics are properly integrated, i.e., captions can be copied from texts into graphics and vice-versa. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 66 Context: ```markdown The design of new macros is a relatively rare activity. Macros are used rather like characters of a font; the design of new macros and fonts is left to the specialist. Nevertheless, it was decided to incorporate the ingredients necessary for macro design in the basic system. They consist of a few procedures only which are used by a tool module called `MacroTool` (see Section 16.3). `MakeMac` integrates all elements lying within a specified rectangular area into a new macro. `OpenMac` reverses this process by disintegrating the macro into its parts. `InsertMac` inserts a specified macro into a library. `NewLib` creates a new, empty library, and `StoreLib` generates a library file containing all macros currently loaded into the specified library. The details of these operations may be examined in the program listings provided later in this chapter. Summarizing, the following procedures are exported from module `Graphics` related to handling macros: ``` PROCEDURE GetLibName(VAR Name: ARRAY OF CHAR; replace: BOOLEAN; VAR Lib: Library); PROCEDURE ThisLib(VAR Library: Mname: ARRAY OF CHAR; Machead); PROCEDURE DrawMac(M: Machead; VAR M: Msg); ``` and the following are added for creating new macros and libraries: ``` PROCEDURE NewLib(Name: ARRAY OF CHAR; VAR Library); PROCEDURE StoreLib(VAR Library: Library; Fname: ARRAY OF CHAR); PROCEDURE RemoveLibraries; PROCEDURE Open(mac: Machead; G: Graph; x, y: INTEGER); PROCEDURE MakeMac(G: Graph; x, y: INTEGER; Mname: ARRAY OF CHAR; Machead); PROCEDURE MakeMacG(mac: Machead; L: Library; VAR new: BOOLEAN); PROCEDURE InsertMac(mac: Machead; L: Library; VAR new: BOOLEAN); ``` ## 13. Object classes Although surprisingly many applications can be covered satisfactorily with the few types of objects and the few facilities described so far, it is nevertheless expected that a modern graphics system allow the addition of further types of objects. The emphasis lies here on the avoidance instead of change. New facilities are to be providable by the inclusion of new modules without requiring any kind of adjustment, not even recompilation of the existing modules. In practice, their source code would quite likely not be available. It is the triumph of the object-oriented programming technique that this is elegantly possible. The means are the extensible record type and the procedure variable, features of the programming language, and the possibility to load modules on demand from statements within a program, a facility provided by the operating environment. We call, informally, any extension of the type `Object` a class. Hence, the types `Line`, `Caption`, and `Macro` constitute classes. Additional classes can be defined in other modules importing the type `Object`. In every such case, a set of methods must be declared and assigned to a variable of type `MethodDesc`. They form a so-called method suite. Every such module must also contain a procedure, typically a command, to generate a new instance of the new class. This command, likely to be called `Make`, assigns the method suite to the field of the new object. This successful decoupling of additions from the system's base suffices, almost. Only one further kink is unavoidable: When a new graphic, containing objects of a class not defined in the system's core, is loaded from a file, then that class must be identified, the corresponding module with its handlers must be loaded - this is called dynamic loading - and the object must be generated (allocated). Because the object in question does not already exist at the time of reading the object's attribute values, the generating procedure cannot possibly be installed in the same object; it cannot be a member of the method suite. We have chosen the following solution to this problem: 1. Every new class is implemented in the form of a module, and every class is identified by its module name. Every such module contains a command whose effect is to allocate an object of the class, to assign the message suite to it, and to assign the object to the global variable `Graphics.new`. 2. When a graphics file is read, the class of each object is identified and a call to the respective module's allocation procedure derives the desired object. The call consists of two parts: a call to `Modules.ThisMod`, which may cause the loading of the respective class module M, and a call of ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 67 Context: ```markdown # Modules.ThisCommand Then the data of the base type `Object` are read, and lastly the data of the extension area are read by a call to the class method read. The following may serve as a template for any module defining a new object class `X`. Two examples are given in Section 13.9, namely Rectangles and Curves. ## MODULE X IMPORT Files, Oberon, Graphics, GraphicFrames; TYPE XType = POINTER TO XDesc; XDesc = RECORD (Graphics: ObjectDesc) (* additional data fields *) END; VAR method: Graphics.Method; ### PROCEDURE New*; VAR x: X; BEGIN NEW(x); x.do := method; Graphics.new := x END New; ### PROCEDURE Copy(obj, to: Graphics.Object); BEGIN to(X)^ := obj(X)^ END Copy; ### PROCEDURE Draw(obj: Graphics.Object; VAR msg: Graphics.Msg); BEGIN (* Draw *) END Draw; ### PROCEDURE Selectable(obj: Graphics.Object; x, y: INTEGER): BOOLEAN; BEGIN (* Selectable logic here *) END Selectable; ### PROCEDURE Change(obj: Graphics.Object; VAR msg: Graphics.Msg); BEGIN IF msg IS Graphics.ColorMsg THEN obj^ := msg(Graphics.ColorMsg) ELSIF msg IS ... THEN (* Handle other message types *) END END Change; ### PROCEDURE Read(obj: Graphics.Object; VAR w: Files.Rider; VAR C: Context); BEGIN (* Read *specific data*) END Read; ### PROCEDURE Write(obj: Graphics.Object; cno: SHORTINT); VAR w: Files.Rider; VAR R: Context; BEGIN Graphics.WriteObj(w, obj, (* write *specific data*)) END Write; ### PROCEDURE Make* (command); VAR x: X; VAR f: GraphicFrames.Frame; BEGIN NEW(x); x.f.mark := F.mark; x.y := F.mark.y; x.w := ...; x.h := ...; x.co := Oberon.CurLoc.x; x.do := method; GraphicFrames.Defocus(F); Graphics.AddF(graph, x); GraphicFrames.DrawObj(F, x) END Make; BEGIN NEW(method); method.module := "X"; method.allocator := "New"; method.copy := Copy; method.draw := Draw; method.selectable := Selectable; method.handle := Handle; method.read := Read; method.write := Write; method.print := Print; END X. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 70 Context: ```markdown # Additional Data Structures in Macros and Classes Additional data structures become necessary through the presence of macros and classes. Macros are represented by the list of their elements, like graphics. Their header is of type `MacHeadDesc` in analogy to `GraphDesc`. In addition to a macro's name, width, and height, it contains the field `first`, pointing to the list's first element, and the field `lib`, referring to the library from which the macro stems. A library descriptor is similarly structured: in addition to its name, the field `first` points to the list of elements (macros) of the library, which are themselves linked through the field `next`. Fig. 13.6 shows the data structure containing two libraries. It is anchored in the global variable `firstLib`. ``` firstLib | v Lib0 ________ |LibraryDesc| |----------| | first |-----> first -----> ObjectDesc | next |-----> next -------> MacHeadDesc |___________| | v Lib1 ________ |LibraryDesc| |----------| | first | | next | |___________| ``` **Fig. 13.6** Data structure for two libraries, each with three macros. Libraries are permanently stored as files. It is evidently unacceptable that file access be required upon every reference to a macro, e.g., each time a macro is redrawn. Therefore, a library is loaded into primary store when one of its elements is referenced for the first time. Procedure `ThisMac` searches the data structure representing the specified library and locates the header of the requested macro. We emphasize that the structures employed for macro and library representation remain hidden from clients, just like the structure of graphics remains hidden within module `Graphics`. Thus, none of the linkage fields of records (`first`, `next`, `sel`) are exported from the base module. This measure retains the possibility to change the structural design decisions without affecting the client modules. But partly it is also responsible for the necessity to include macros in the base module. A large fraction of module `Graphics` is taken up by procedures for reading and writing files representing graphics and libraries. They convert their internal data structure into a sequential form and vice-versa. This would be a rather trivial task, were it not for the presence of pointers referring to macros and classes. These pointers must be converted into descriptors that are position-independent, such as names. The same problem is posed by fonts (which are also represented by pointers). Evidently, the replacement of every pointer by an explicit name would be an uneconomical solution with respect to storage space as well as speed of reading and writing. Therefore, pointers to fonts are necessary. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 71 Context: ```markdown and libraries - themselves represented as files - are replaced by indices to font and library dictionaries. These dictionaries establish a context and are constructed while a file is read. They are used only during this process and hence are local to procedure `Load` (or `Open`). For classes, a dictionary listing the respective allocation procedures is constructed in order to avoid repeated calls to determine the pertinent allocator. When a graphic file is generated by procedure `Store`, local dictionaries are constructed of fonts, libraries, and classes of objects that have been written onto the file. Upon encountering a caption, a macro, or any element whose font, library, or class is not contained in the respective dictionary, a pair consisting of index and name is emitted to the file, thereby assigning a number to each name. These pairs are interspersed within the sequence of object descriptions. When the graphic file is read, these pairs trigger insertion of the font, library, or class in the respective dictionary, whereby the name is converted into a pointer to the entity, which is obtained by a loading process embodied by procedures `Fonts.This`, `Gelt.Lib`, and `GetClass`. Both the `Load` and `Store` procedures traverse the file only once. The files are self-contained in the sense that all external quantities are represented by their names. The format of a graphics file is defined in Extended BNF syntax as follows: ``` file = tag stretch. stretch = {item} 255. item = 0 {fonto fontname | 1 | libno libname | 0 | 2 classno classname allocname | 1 | data | 2 data from string | 3 data libno macname | classno data extension}. data = x y w h color. ``` All class numbers are at least 4; the values 1, 2, and 3 are assigned to lines, captions, and macros. `x`, `y`, `w`, and `h` are two-byte integer attributes of the base type `Object`. The attribute `color` takes a single byte. The first byte of an item being 0 signifies that the item is an identification of a new font, library, or class. If the second byte is 0, a new font is announced, if 1 a new library, and if 2 a new class of elements. The same procedures are used for loading and storing a library file. In fact, `Load` and `Store` read a file stretch representing a sequence of elements which is terminated by a special value (255). In a library file, each macro corresponds to a stretch, and the terminator is followed by values specifying the macro's overall width, height, and its name. The structure of library files is defined by the following syntax: ``` li tag = libtag (macro). macro = stretch w h name. ``` The first byte of each element is a class number within the context of the file and identifies the class to which the element belongs. An object of the given class is allocated by calling the class' allocation procedure, which is obtained from the class dictionary in the given context. The class number is used as dictionary index. The presence of the required allocation procedure in the dictionary is guaranteed by the fact that a corresponding index/name pair had preceded the element in the file. The encoder of such a pair triggers the loading of the module specifying the class and its methods. The name of the pair consists of two parts: the first specifies the module in which the class is defined, and it is taken as the parameter of the call to the loader (see procedure `GetClass`). The second part is the name of the relevant allocation procedure which returns a class object to variable `Graphics.new`. Thereafter, the data defined in the base type `Object` are read. Data belonging to an extension follow those of the base type, and they are read by the extension's read method. This part must always be headed by a type specifying the number of bytes which follow. This information is used in the case where a requested module is not present; it indicates the number of bytes to be skipped in order to continue reading further elements. A last noteworthy detail concerns the `Move` operation which appears as surprisingly complicated, particularly in comparison with the related copy operation. The reason is our deviation from the principle that a graphics editor must refrain from an interpretation of drawings. Responsible for this ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 72 Context: ```markdown # 13.9 Rectangles and curves ## 13.9.1 Rectangles In this section, we present two extensions of the basic graphics system which introduce new classes of objects. The first implements rectangles which are typically used for framing a set of objects. They are, for example, used in the representation of electronic components (macros, see Fig. 13.2). Their implementation follows the scheme presented at the end of chapter 13.7 and is reasonably straightforward, considering that each rectangle merely consists of four lines. Additionally, a background raster may be specified. One of the design decisions occurring for every new class concerns the way to display the selection. In this case we chose, in contrast to the cases of captions and macros, not inverse video, but a small square dot in the lower right corner of the rectangle. The data type `Rectangle` contains one additional field: `lv` indicates the line width. In spite of the simplicity of the notion of rectangles, their drawing method is more complex than might be expected. The reason is that drawing methods are responsible for appropriate clipping at frame boundaries. In this case, some of the component lines may have to be shortened, and some may disappear altogether. Procedure `Handle` provides an example of a receiver of a control message. It is activated as soon as the middle mouse button is pressed, in contrast to other actions, which are initiated after the release of all buttons. Therefore, this message allows for the implementation of actions under control of individual handlers interpreting further mouse movements. In this example, the action serves to change the size of the rectangle, namely by moving its lower left corner. ```pascal DEFINITION Rectangles: TYPE Rectangle = POINTER TO RectDesc; RectDesc = RECORD (Graphics.ObjectDesc) lv: INTEGER END; VAR method: Graphics.Method; PROCEDURE New; PROCEDURE Make; END Rectangles. ``` ## 13.9.2 Oblique lines and circles The second extension to be presented is module `Curves`. It introduces two kinds of objects: lines which are not necessarily horizontal or vertical, and circles. All are considered to be variants of the same type `Curve`, the variant being specified by the field `kind` of the object record. Selection is indicated by a small rectangle at the end of a line and at the lowest point of a circle. In order to avoid computations involving floating-point numbers and to increase efficiency, Bresenham algorithms are used. The algorithm for a line defined by `bx - ay = 0 (for b = a ≤ s)` is given by the following statements: ```pascal x := a * y - b; WHILE x < a DO DoX(DotX, y); IF h < 0 THEN INC(h, b-a); ELSE INC(y) END; ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 74 Context: ```markdown # 14 Building and maintenance tools ## 14.1. The Startup Process An aspect usually given little attention in system descriptions is the process of how a system is started. Its choice, however, is itself an interesting and far from trivial design consideration and will be described here in some detail. Moreover, it directly determines the steps in which a system is developed from scratch, mirroring the steps in which it builds itself up from a bare stone to an operating body. The startup process typically proceeds in several stages, each of them bringing further facilities into play, raising the system to a higher level towards completion. The term for this strategy is *boot strapping* or, in modern computer jargon, *booting*. 1. **Stage 0** is initiated when power is switched on or when the reset button is pressed and released. To be precise, power-on issues a reset signal to all parts of the computer and holds it for a certain time. Pushing the reset button therefore appears like a power-on without power having been switched off. Release of the reset signal triggers the built-in FPGA hardware to load a short configuration bit-stream from a ROM residing on the Spartan board, called the *platform flash*, into a BRAM within the FPGA. This program is called *boot loader*. Being stored in a ROM, it is always present. The BRAM is address-mapped onto an upper part of the address space, and the RISC processor starts execution at this address. 2. In *Stage 1*, the boot loader loads the **inner core**, which consists of modules *Kernel*, *FileDir*, *Files*, and *Modules*. The loader first inspects the link register. If its value is 0, a cold start is indicated. (If the value of the link register is not 0, this signals an abort caused by pressing button 3 on the board). Then loading is skipped and control is immediately returned to the Oberon command loop. The disk (SD-card, SPI) is initialized. The boot loader terminates with a branch to location 0, which transfers control to the just loaded module *Modules*, the regular loader. 3. *Stage 2* starts with the initialization body of module *Modules* which calls the bodies of *Kernel*, *FileDir*, and *Files*, establishing a working file system. Then it calls itself, requesting to load the central module *Oberon*. This implicitly causes the loading of its own imports, namely *Input*, *Display*, *Viewers*, *Fonts*, and *Texts*, establishing a working viewer and text system. This loading of the outer core must be interpreted as the continuation of the loading of the inner core. To allow proper continuation, the boot loader has deposited the following data in fixed locations: | Address | Description | |---------|--------------------------------------------------| | 0 | A branch instruction to the initializing body of module *Modules* | | 12 | The limit of available memory | | 16 | The address of the end of the module space loaded | | 20 | The current root of the links of loaded modules | | 24 | The current limit of the module area | 4. In *Stage 3*, *Oberon* calls the loader to load the tool module *System*, and with its imports *MenuViews* and *TextFrames*. The initialization of *System* causes the opening of the viewers for the system tool and the system log. Control then returns to Oberon and its central loop for polling input events. Normal operation begins. The booting process is summarized in Figure 14.1. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 76 Context: ```markdown Still, how did the hardware configuration data and the boot loader get into the Flash ROM? This step requires the help of proprietary tools of the FPGA manufacturer. Regrettably, their incantation ceremony typically is rather complex. After all necessary Verilog modules have been synthesized, the result is the configuration file `RISCTop.bit`. The necessary source files are: - `RISCTop.v` - `RISC.v` - `Multiplier.v` - `Divider.v` - `FP_Multiplier.v` - `FP_Divider.v` - `dbram32.v` - `RS232T.v` - `RS232.v` - `SP1K.xgs` - `P2V.x` - `RISC.ucf` Thereafter, the boot loader is compiled and, together with the result of the configuration of the RISC hardware, loaded into the configuration memory of the FPGA. This Stage 0 is partly done with proprietary software (dependent on the specific FPGA) and is described in a separate installation guide. ``` Stage 0 ``` ```plaintext RISCTop.bit ins1.mem +----------------+ | FlashRISC_cmd | +----------------+ | v Stage 1 +----------+ | RISC.bit | +----------+ | v +----------------+ | Xilinx tool | | download.cmd | +----------------+ | v +-----------+ | BRAM | | boot | | loader | +-----------+ | v +----------------+ | Config mem | | FPGA config | +----------------+ | v Stage 2 +-----------+ | RS-232 | | link | +-----------+ | v +----------------+ | Memory | | | | Kernel | | File | | Modules | | Oberon | +----------------+ ``` **Figure 14.2** Booting from host computer A simple boot loader reading from the RS-232 line and using the stream format described above is shown here: ```pascal MODULE BootLoader; IMPORT SYSTEM; CONST MT = 12; SP = 14; MemLim = 0E7F00H; VAR w: INTEGER; len = 60; data = 56; ctrl = 52; (* 'device addresses' *) PROCEDURE RecInt(VAR x: INTEGER); VAR z, y: INTEGER; BEGIN z := 2; y := 4; REPEAT i := i + 1; REPEAT UNTIL SYSTEM.BIT(crt, 0); SYSTEM.GET(data, y); z := ROR(z, y, 8); UNTIL i = 0; x := z; END RecInt; PROCEDURE Load; VAR len, adr, dat: INTEGER; BEGIN RecInt(len); WHILE len > 0 DO RecInt(dat); REPEAT RecInt(data); SYSTEM.PUT(dat, adr); adr := adr + 4; len := len - 4 UNTIL len = 0; RecInt(len); END Load; ``` ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 78 Context: ```markdown 3. Read all files from the host (which supposedly holds all files needed for the outer core). 4. Invoke the command which loads **Oberon**. This loads the outer core, sets up the display, and starts the central loop. A more modern solution would be to select the network as an alternative boot file source. We rejected this option in order to keep all access routines outside the ROM, in order to keep the startup of a computer independent of the presence of a network and foreign sources, and also in consideration of the fact that there exist machines which operate in a stand-alone mode. As it turns out, the need for the alternative boot file source arises very rarely. The boot linker **ORL**, presumably running on a host computer, where the FPGA tools are available, is almost identical to the module loader, with the exception that object code is not deposited in newly allocated blocks, but is output in the form of a file. The name of the top module of the inner core is supplied as a parameter. ```markdown ORL.Link Modules generates the regular boot file ORL.Link Oberon0 generates the build-up boot file ``` Oberon0 imports two modules taking care of communication with **ORL** on the host computer. They are the basic module **RS232**, and module **PCLink1** for file transfer. The latter constitutes a task, accepting commands over the line from **ORL**. Their interfaces are shown below: ```pascal DEFINITION RS232: PROCEDURE SendX(VAR: BYTE); PROCEDURE RecX(VAR: BYTE); PROCEDURE SendIntX: INTEGER; PROCEDURE SendHexX: INTEGER; PROCEDURE SendRealX: REAL; PROCEDURE SendStrX: ARRAY OF CHAR; PROCEDURE RecInt(VAR: INTEGER); PROCEDURE RecReal(VAR: REAL); PROCEDURE RecStr(VAR: ARRAY OF CHAR); PROCEDURE Line; PROCEDURE End; END RS232. DEFINITION PCLink1: PROCEDURE Run*; PROCEDURE Stop; END PCLink1. ``` The command interpreter is a simple loop, accepting commands specified by an integer followed by parameters which are either integers or names. User-friendliness was not attributed any importance at this point, and it would indeed be merely luxury. We refrain from elaborating on further details and concentrate on providing a list of commands provided by **Oberon**. This should give the reader an impression of the capabilities and limitations of this tool module for system initiation and for error searching. *(name stands for a string, and a, secno, m, n stand for integers).* | parameters | action | |------------|------------------------------------------| | 0 | send and mirror s | | 1 | a, n show (in hex) M[a], M[a+1], ... | | 2 | w fill display with words w | | 3 | secno show disk sector | | 4 | filename read file | | 5 | - start PC-link | | 6 | - show allocation, not sectors, switches, and timer | | 10 | list modules | | 11 | modname list commands | | 12 | prefix list files (enumerate directory) | | 13 | filename delete file | ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 79 Context: ```markdown 20 modname load module 21 modname unload module 22 name call command 50 adr, list of values write memory 51 adr, n clear memory (in words) 52 secno, list of values write sector 53 secno, n clear sector (n words) 100 load boot track 101 clear file directory Oberon imports modules **Kernel**, **FileDir**, **Files**, **Modules**, **RS232**, **PCLink1**. This is the inner core plus facilities for communication. ## 14.3 Maintenance Tools An important prerequisite for Stage 2 (and the following stages) in the boot process has not been mentioned above. Recall that the initialization of module **FileDir** constructs the disk sector reservation table in the **Kernel** from information contained on the disk. Obviously, its prerequisite is an intact, consistent file directory. A single unreadable, corrupted file directory or file header sector lets this process fail, and booting becomes impossible. To cope with this (fortunately rare) situation, a maintenance tool has been designed: module **DiskCheck**. **DiskCheck** is organized similarly to **Oberon** as a simple command interpreter, but it imports only **Kernel** and **RS232**. Hence, booting involves only Stages 1 and 2 without any access to the disk. Operating **DiskCheck** requires care and knowledge of the structure of the file system (Chapter 7). The available commands are the following: | parameters | action | |------------------|-----------------------------------| | 0 | send and mirror integer (test) | | 1 a, n | show (in hex) M[a], M[a+4], ..., M[a + n*4] | | 2 secno | show disk sector | | 3 secno | show head sector | | 4 secno | show directory sector | | 5 | traverse directory | | 6 secno | clear header sector | | 7 secno | clear directory (root) | The essential command is the file directory traversal (5). It lists all faulty directory sectors, showing their numbers. It also lists faulty header sectors. No changes are made to the file system. If a faulty header is encountered, it can subsequently be cleared (6). Thereby the file is lost. It is not removed from the directory, though. But its length will be zero. Program **DiskCheck** must be extremely robust. No data read can be assumed to be correct, no index can be assumed to be within its declared bounds. No sector number can be assumed to be valid, and no directory or header page may be assumed to have the expected format. Guards and error diagnostics take a prominent place. Whereas a faulty sector in a file in the worst case leads to the loss of that file, a fault in a sector carrying a directory page is quite disastrous. Not only because the files referenced from that page, but also those referenced from descendant pages become inaccessible. A fault in the root page even causes the loss of all files. The catastrophe is of such proportions, that measures should be taken even if the case is very unlikely. After all, it might happen, and it indeed has occurred. The only way to recover files that are no longer accessible from the directory is by scanning the entire disk. In order to make a search at all possible, every file header carries a mark field that is given a fixed, constant value. It is very unlikely, but not entirely impossible, that data sectors which happen to have the same value at the location corresponding to that of the mark, may be mistaken to be headers. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 80 Context: ```markdown The tool performing such a scan is called **Scavenger**. It is, like **DiskCheck**, a simple command interpreter with the following available commands: | parameters | action | |------------|------------------------------------| | 0 s | send and mirror integer (test) | | 1 n | Scan the first n sectors and collect headers | | 2 - | Display names of collected files | | 3 - | Build new directory | | 4 - | Transfer new directory to the disk | | 5 - | Clear display | During the scan, a new directory is gradually built up in primary store. Sectors marked as headers are recorded by their name and creation date. The scavenger is the reason for recording the file name in the header, although it remains unused there by the Oberon System. Recovery of the date is essential, because several files with the same name may be found. If one is found with a newer creation date, the older entry is overwritten. Command W transfers the new directory to the disk. For this purpose, it is necessary to have free sectors available. These have been collected during the scan: both old directory sectors (identified by a directory mark similar to the header mark) and overwritten headers are used as free locations. The scavenger has proven its worth on more than one occasion. Its main drawback is that it may rediscover files that had been deleted. The deletion operation by definition affects only the directory, but not the file. Therefore, the header carrying the name remains unchanged and is discovered by the scan. All in all, however, it is a small deficiency. ### Reference 1. N. Wirth. Designing a System from Scratch. *Structured Programming*, 1, (1989), 10-18. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 81 Context: ```markdown # 15 Tool and service modules In this chapter, a few modules are presented that do not belong to Oberon's system core. However, they belong to the system in the sense of being basic, and of assistance in some way, either to construct application programs, to communicate with external computers, or to analyze existing programs. ## 15.1 Basic mathematical functions Module `Math` contains the basic standard functions that had been postulated already in 1960 by Algol 60. They are: - `sqrt(x)` the square root - `exp(x)` the exponential function - `ln(x)` the natural logarithm - `sin(x)` the sine function - `cos(x)` the cosine function They are presented here only briefly without discussing their approximation methods. However, we point out how advantage can be taken from knowledge about the internal representation of floating-point numbers. ### 15.1.1 Conversion between integers and floating-point numbers The Oberon System adopts the standard format postulated by IEEE. Here we restrict it to the 32-bit variant. A floating-point number `x` consists of 3 parts: - `s` the sign | 1 bit - `e` the exponent | 8 bits - `m` the mantissa | 23 bits Its value is defined as \( x = (-1)^s \times 2^{e-127} \times (1.m) \). A number is in normalized form, if its mantissa satisfies \( 1.0 \leq m < 2.0 \). It is assumed that numbers are always normalized, and therefore the leading 1-bit is omitted. The exception is the singular value 0, which cannot be normalized. It must therefore be treated as a special case. It follows that integers and floating-point numbers are represented quite differently, and that conversion operations are necessary to transfer a number from one format to the other. This is the reason why the Oberon language keeps the two types `INTEGER` and `REAL` separate. Conversion must be explicitly specified by using the two predefined functions: - \( n = \text{FLOOR}(x) \quad \text{REAL} \rightarrow \text{INTEGER} \) - \( x = \text{FLT}(n) \quad \text{INTEGER} \rightarrow \text{REAL} \) Note: `FLOOR(x)` rounds toward -inf. For example `FLOOR(1.5) = 1`, `FLOOR(-1.5) = -2`. The RISC processor does not feature specific instructions implementing these functions. Instead, the compiler generates inline code using the `FAD` instruction with special options suppressing normalization. This option is specified by the `u` and `v` modifiers of the instruction. The `FLOOR` function is realized by adding 0 with an exponent of 127 + 24 and suppressing the insertion of a leading 1-bit (i.e. \( u = 1 \)). This causes the mantissa of the argument to be shifted right until its exponent is equal to 151. The RISC instructions are: ``` MOV R1 R0 4800H R1 = 48000000H FAD R0 R0 R1 ``` The `FLT` function is implemented also by adding 0 with an exponent of 151 and forced insertion of a leading 1-bit (i.e. \( v = 1 \)). ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 82 Context: ```markdown MOV R1, 4800H FAD R0, R0 There are two predefined procedures for packing and unpacking a floating-point number: PACK(x, e) \[ x = x \times 2^e \text{ (for } x > 0) \] UNPK(x, e) \[ \text{assign } x \text{ and } e, \text{ such that } x \times 2^e = x_0, \text{ where } x_0 \text{ is the original value of } x, \text{ x becomes normalized, that is } 1.0 < x < 2.0 \] Assuming \( R0 = x \) and \( R1 = e \), the instruction sequence for PACK(x, e) is: ``` LSL R1, R1, 23 ADD R0, R0, R1 STR R0, R0 ``` Again assuming \( x = x_0 \), the instruction sequence for UNPK(x, e) is: ``` ASR R1, R0, 23 SUB R1, R1, 127 STR R1, e LSL R1, R1, 23 SUB R0, R0, R1 STR R0, R1 STR R0, x ``` ### 15.1.2. The square root function We rely on the definition \( x = 2^m \times r \) Using the intrinsic UNPK procedure, the components \( m \) and \( e \) are obtained from \( x \). Then the square root is computed according to the formulas: \[ \sqrt{x} = 2^{m/2} \times \sqrt{r} \text{ if } m \text{ is even,} \] \[ \sqrt{x} = 2^{(m-1)/2} \times \sqrt{r} \text{ if } m \text{ is odd.} \] The advantage is that the argument of the square root now lies in the narrow interval [1.0, 2.0], and therefore is easier and faster to approximate by a continued fraction. ```pascal PROCEDURE sqrt: REAL; REAL; CONST c2 = 0.707106780; { "1/sqrt(2)" } c2 = 0.509621267; c3 = 1.414213135; { "sqrt(2)" } VAR r: REAL; m: INTEGER; BEGIN ASSERT(x > 0.0); UNPK(x); s := x * c2; s := s * (s); s := 0.5 + s * x; s := 0.5 + 0.5 * s; IF (ODD(m)) THEN s := c3 * s END; PACK(s, DW 2) ELSE s := 0.0 END; RETURN s END sqrt; ``` ### 15.1.3. The exponential function Since our floating-point format is based on an exponent of 2, we first use the formula \[ \exp(x) = e^{x/2} \text{ with } x = k \cdot \log(e) = x / \ln(2) \quad \log(e) = 1.4426951 \] and first compute \( y_0 = y - n. \text{ Since } 2^x \times 2^{y} = 2^{n} \), the result is the sum of the exponent \( n \) and the mantissa \( 2^y \). Again, the advantage of the decomposition is that the argument of \( y_0 \) of the polynomial approximation lies in the narrow interval [1.0, 2.0]. ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 85 Context: ```markdown REPEAT UNTIL SYSTEM.BIT(stat, 0); SYSTEM.GET(data, x); END Dec; PROCEDURE RecName(VAR s: ARRAY OF CHAR); VAR i: INTEGER; x: BYTE; BEGIN i := 0; Rec(i); WHILE x <> 0 DO s[i] := CHR(x); INC(i); Rec(x); END; s[i] := 0; END RecName; PROCEDURE Send(x: BYTE); BEGIN REPEAT UNTIL SYSTEM.BIT(stat, 1); SYSTEM.PUT(data, x); END Send; PROCEDURE Task; VAR len, i: INTEGER; x, ack: BYTE; name: ARRAY[0..31] OF CHAR; F: Files.File; R: Files.Rider; buf: ARRAY[0..255] OF BYTE; BEGIN IF SYSTEM.BIT(stat, 0) THEN (*byte available*) Rec(code); IF code = SND THEN ("send file") RecName(name); F := Files.Old(name); IF F = NIL THEN Send(ACK); len := Files.Length(F); Files.SetR(F, 0); REPEAT IF len > BLKlen THEN len := BLKlen ELSE len := len END; Send(len); n := len; len := len - 1; WHILE l = 0 DO Files.Read(byte[r, x]; Send(x); DEC(len) END; IF ack & ACK THEN len = 0 END UNTIL len < BLKlen; ELSE Send(1); END ELSEIF code = REC THEN ("receive file") RecName(name); F := Files.New(name); IF F = NIL THEN Files.SetR(F, 0); Send(ACK); REPEAT Rec(x); len := x; i := 0; WHILE i < len DO Rec(byte[r, buf[i]]); INC(i) END; WHILE i < len DO Files.WriteByte(F, buf[i]); INC(i) END; Send(ACK); UNTIL len < 255; Files.Reset(F); Send(ACK); ELSE Send(NAK); END ELSEIF code = REC THEN Send(ACK) ("for testing") END END Task; PROCEDURE Run; BEGIN Oberon.Init(); Texts.WriteString(W, "PCLink started"); Texts.Writeln(W); Texts.Append(Oberon.Log, W.buf); END Run; PROCEDURE Stop; ``` #################### File: Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf Page: 86 Context: ```markdown # 15.3 A Generator of Graphic Macros The module `MacroTool` serves to create macros for the graphic system (Ch. 13). It provides the commands `OpenMacro`, `MakeMacro`, `LoadLibrary`, and `StoreLibrary`. ## OpenMacro `OpenMacro` decomposes the selected macro into its elements and places them at the position of the caret. This command is typically the first if an existing macro is to be modified. ## MakeMacro `MakeMacro` L M collects all selected objects in the frame designated by the star pointer and unites them into macro M. This macro is displayed at the caret position and inserted into library L. If no such library exists, a new one is created. ## LoadLibrary `LoadLibrary` L loads the library L (under file name `L.Lib`). Note that a library must have been stored, before it can be loaded. ## StoreLibrary `StoreLibrary` stores library L (with filename `L.Lib`). The required modules are `Texts`, `Oberon`, `Graphics`, `GraphicFrames`. ``` ########## """QUERY: what are some add on mods available?""" Consider the chat history for relevant information. Use all information included. Use as much tokens as needed. Important: If you find information separated by a | in the context, it is a table formatted in Markdown.Important: Take a look at the QUERY and only the QUERY. If this is vague or unclear, please ignore everything and ask a follow-up question instead! Final Files Sources: arsnouveau-wiki-44789.txt - Page 1, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 2, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 3, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 4, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 8, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 9, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 12, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 14, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 16, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 17, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 21, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 23, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 25, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 28, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 29, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 31, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 33, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 36, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 37, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 40, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 41, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 43, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 45, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 47, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 54, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 57, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 59, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 60, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 62, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 63, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 66, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 68, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 69, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 70, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 71, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 73, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 75, The%20Little%20Book%20About%20OS%20Development%20-%20Erik%20Helin%2C%20Adam%20Renberg%20-%20%28PDF%2C%20HTML%29.pdf - Page 78, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 2, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 5, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 9, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 12, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 14, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 15, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 17, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 20, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 22, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 23, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 27, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 29, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 31, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 32, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 33, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 34, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 41, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 43, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 44, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 47, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 48, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 50, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 52, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 54, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 58, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 59, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 63, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 64, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 66, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 67, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 70, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 71, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 72, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 74, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 76, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 78, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 79, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 80, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 81, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 82, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 85, Project%20Oberon%20The%20Design%20of%20an%20Operating%20System%2C%20a%20Compiler%2C%20and%20a%20Computer%20-%20Niklaus%20Wirth%2C%20J%C3%BCrg%20Gutknecht%20%28PDF%29%20PO.Applications.pdf - Page 86 ================================================== **Elapsed Time: 0.00 seconds** ================================================== FINAL ANSWER Answer: Based on the provided context, the available add-on mods for Ars Nouveau are: * Ars Elemental * Ars G.E.A.R.S * Ars Instrumentum * Ars Mage Fight * Ars Ocultas * Ars Omega * Ars Scalaes * Starbuncle Mania * Tome of Blood Rebirth * Too Many Glyphs (Beta) ================================================== **Elapsed Time: 160.68 seconds** ==================================================