📜 ⬆️ ⬇️

Project "Oberon 2013"

Colleagues here gave me one payload. I will quickly talk with you about the dangers of selective blindness, because with her, damned, in our industry is not very well. So, first and foremost, before dealing with it, with our common enemy - the damned blindness, let us properly discuss and study examples of what the community does not see, so that you can know for sure how it can be.

Story


So, in 1986-89. Niklaus Wirth and colleagues developed the first version of the Oberon system, the machine, the Oberon language compiler, and the System Oberon operating system, which had a graphical user interface, extended concepts for using text in the interface, and, in general, was a tangible proof of the applicability of N. Wirth's concepts. This operating system marked the beginning of an evolutionary branch of products and languages ​​with a difficult fate, each of which is worthy of a separate article.



After nearly 30 years, observing the situation of uncontrolled complication of products in IT, N. Wirth decided to re-present his vision of how to build small, powerful and reliable systems to people. The author’s work itself will serve as proof that such systems exist and work.
')

Everything flows, everything changes


As it turned out, the processor model that was used in the original system has already disappeared from the market. Like its architectural counterparts. Instead of using ready-made commercial solutions, N. Wirth decided to supplement his system with one more component - his version of the FPGA programming language, which was called Lola . Consequently, the processor for the new machine was developed by Wirth himself based on RISC. We will consider it in a separate section of the article. The entire system worked on the Xilinx Spartan-3 devbord with 1 MB of memory and a 25 MHz processor.

That is, we have a project in which the full cycle of developing a computer complex from scratch is implemented, and all the stages of this project available to the engineer / programmer are implemented.

CPU


The RISC architecture is distinguished by a particular sequence of processor operation with data. All computational instructions of the processor work with data located in registers, and the data in memory from memory is transferred by special instructions of the processor, SAVE and LOAD. Thus, the monotony of work is achieved, when the number of cycles per instruction is predictable, and often the majority of instructions fit into one cycle. A feature of the Wirth processor is also the parallel operation of the pipeline for writing data to memory, however, for this it was necessary to reduce the frequency of the resulting processor twice, from 50 to 25 MHz.

The command system is based on a 32-bit word, in which the upper four bits are assigned to the definition of the command format. The command format determines the class of operation, the number of operands and their location.
The ALU commands define a set of arithmetic and logical operations with three registers or two registers and a constant. The ALU commands leave behind themselves additional values ​​of the sign of the result in special one-bit registers.
Memory commands define instructions for loading into memory and reading from a memory of one word or one byte into a register.
Flow control commands allow you to control the value of the PC register, to describe conditional and unconditional jumps based on the value of the register or constant.

The system has 16 registers, with several higher registers reserved by the operating system and the compiler, as service ones, they contain a pointer to the stack, the address of the module table, and so on.

It looks like this:



More information about the instruction set can be found in the generalized and specific documents. They provide a diagram of the processor and peripheral devices in the Verilog language, but in subsequent editions Wirth provided diagrams in the Lola language along with a description of the language, after additional debugging and verification.

Wirth also describes the scheme and implementation of working with some standard interfaces, such as RS232, and also tells about how to connect an SD card as a data warehouse. How the peripheral devices are implemented is described in the project documentation, since I am basically a programmer, not an electronics engineer, I will not describe the details in order to prevent very childish errors. Perhaps more experienced comrades will help fill this gap.

System


The main purpose of the OS is to provide the user and the programmer with a certain level of abstraction. Input / output devices, storage devices and interfaces for interaction with these devices, as well as iron limitations. The flexibility of the abstractions selected in the Oberon system allows you to run it seamlessly as a separate program on various software platforms / OSs, while the processor level can be emulated or the level of software implementations of abstractions can be emulated.

Tongue


Of course, Oberon was chosen as the programming language for creating the future OS. The original language was revised in 2007, and as part of this project, Wirth simplified the language even more, getting rid of features that are questionable in terms of efficiency. The minimal feature set simplifies the implementation of a working compiler and improves control over the code. This is important, because we are writing a system from scratch.

The main features that are important for building the OS. Oberon is modularity, basic meta-information, dynamic loading of modules and a memory model with the need for a garbage collector.
Briefly about each of them.

Modularity


In Oberon, a module is not only a means of structuring algorithms and data structures, but also a unit of compilation, loading and distribution. That is, the module is the minimum entity that the compiler can compile. Dependencies of one module on other modules are calculated automatically, but do not lead to the inclusion of code from one module to another. Only identifiers of imported entities and dependency hashcode are included to control the version of the code.

A module is a unit of load, that is, except in special cases, the module code is a complete program that has an entry point and can run for an infinitely long time. That is, a full program. Even the OS kernel is just the first module loaded into memory.

The same module assumes that it will be distributed not only in the form of a source code, but also in the form of a binary, as well as in the form of an interface part, and to launch it you will need only a specific platform or several platforms. In general, these concepts are included in the concept of modularity in Oberon and constitute a modular-oriented programming.

Basic Meta-Information


The concept of modularity assumes that any number of modules can be simultaneously present in the memory, and it is not assumed that they all can know something about each other. In this case, the user or the programmer may need access to the entities in such modules. To this end, the requirement for the Oberon language execution environment introduces the requirement for meta-information. Since specific entities in the modular system may also be unknown, in this project, meta-information is only required to transfer the control to any exported procedure of any module by its full name.

Dynamic modularity


The need for such a transfer of control arises from the need for the OS to have not just modularity, but dynamic modularity, because the OS is an infrastructure product, and by itself does not have specific applications for users, users should be able to load the modules they need into . In this case, the module has an entry point and a body, which will be executed once, while the important point is the fact that the module will remain loaded into memory, and can provide its own types and procedures for further work of other modules. So, in order for the OS to operate, control could be transferred to already loaded modules, and at the same time to load modules and execute their procedures, it was necessary to be able to perform the procedures by name. Of course, this feature has incredibly complex analogs in the mainstream, but at the moment this feature is enough. All the same, it is implemented libraryly, and nothing needs to be added to the language.

Garbage collection


In the Oberon operating system, garbage collection is performed at the kernel level of the OS, which controls the placement of dynamic objects on the heap. For the component environment, garbage collection is an obvious necessity, since the component that provides dynamic objects to clients cannot decide when to remove them from memory.
Here is a quote from Wirth himself about this:
The Oberon System doesn’t need to be referenced. This is a programmer. And secondly, this "hint" could
not be taken as trustworthy. There is a possibility of a number of decisions. Hence, it is truly reusable.


So, when the requirements for the language and its environment (it is the OS, it’s also runtime) are defined, you can start writing the code. In fact, the OS for a set of functions can be described by just a few modules. The remaining functions can take on third-party components. The compiler does not have to be part of the OS, but since without it you cannot create a single command to the processor, we will consider it first.

Compiler


A programming language is an infinite set of characters from a set of keywords that are bound by a limited set of expressions that make up the syntax. Each expression is a syntactic construction with the rules for its writing. The meaning of what is written is determined by a set of semantic rules associated with each construction.

Oberon


Language Oberon from the very first edition was conceived as a language that has nothing superfluous in its composition. To solve the obvious problems associated with the minimalism of the language, the concept of modularity was proposed. Thus, it was assumed that the sets of modules of different functionality can effectively replace the expansive growth of the capabilities of the language itself. The language description takes less than 20 pages, the compiler is implemented fairly quickly, in general, Wirth emphasized that he had an excellent language for learning (better than Pascal). Of course, evil tongues immediately picked up the phrase and rush with her, arguing that Oberon is good only for learning. Well, slander is easier to sow than to eradicate.

As you can see in this project, Oberon and his concepts are suitable for implementing code at any level, low-level, middleware, high-level schema editors, mailers, etc. Wirth implemented all these applications as proof of concept in his OS.

An example of a low-level code in the Oberon language.
Kernel module, garbage collector. github.com/ilovb/ProjectOberon2013/blob/master/Sources/Kernel.Mod#L82
(* ---------- Garbage collector ----------*)

  PROCEDURE Mark*(pref: LONGINT);
    VAR pvadr, offadr, offset, tag, p, q, r: LONGINT;
  BEGIN SYSTEM.GET(pref, pvadr); (*pointers < heapOrg considered NIL*)
    WHILE pvadr # 0 DO
      SYSTEM.GET(pvadr, p); SYSTEM.GET(p-4, offadr);
      IF (p >= heapOrg) & (offadr = 0) THEN q := p;   (*mark elements in data structure with root p*)
        REPEAT SYSTEM.GET(p-4, offadr);
          IF offadr = 0 THEN SYSTEM.GET(p-8, tag); offadr := tag + 16 ELSE INC(offadr, 4) END ;
          SYSTEM.PUT(p-4, offadr); SYSTEM.GET(offadr, offset);
          IF offset # -1 THEN (*down*)
            SYSTEM.GET(p+offset, r); SYSTEM.GET(r-4, offadr);
            IF (r >= heapOrg) & (offadr = 0) THEN SYSTEM.PUT(p+offset, q); q := p; p := r END
          ELSE (*up*) SYSTEM.GET(q-4, offadr); SYSTEM.GET(offadr, offset);
            IF p # q THEN SYSTEM.GET(q+offset, r); SYSTEM.PUT(q+offset, p); p := q; q := r END
          END
        UNTIL (p = q) & (offset = -1)
      END ;
      INC(pref, 4); SYSTEM.GET(pref, pvadr)
    END
  END Mark;

  PROCEDURE Scan*;
    VAR p, q, mark, tag, size: LONGINT;
  BEGIN p := heapOrg;
    REPEAT SYSTEM.GET(p+4, mark); q := p;
      WHILE mark = 0 DO
        SYSTEM.GET(p, tag); SYSTEM.GET(tag, size); INC(p, size); SYSTEM.GET(p+4, mark)
      END ;
      size := p - q; DEC(allocated, size);  (*size of free block*)
      IF size > 0 THEN
        IF size MOD 64 # 0 THEN
          SYSTEM.PUT(q, 32); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list3); list3 := q; INC(q, 32); DEC(size, 32)
        END ;
        IF size MOD 128 # 0 THEN
          SYSTEM.PUT(q, 64); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list2); list2 := q; INC(q, 64); DEC(size, 64)
        END ;
        IF size MOD 256 # 0 THEN
          SYSTEM.PUT(q, 128); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8,  list1); list1 := q; INC(q, 128); DEC(size, 128)
        END ;
        IF size > 0 THEN
          SYSTEM.PUT(q, size); SYSTEM.PUT(q+4, -1); SYSTEM.PUT(q+8, list0); list0 := q; INC(q, size)
        END
      END ;
      IF mark > 0 THEN SYSTEM.GET(p, tag); SYSTEM.GET(tag, size); SYSTEM.PUT(p+4, 0); INC(p, size)
      ELSE (*free*) SYSTEM.GET(p, size); INC(p, size)
      END
    UNTIL p >= heapLim
  END Scan;


Texts, . github.com/ilovb/ProjectOberon2013/blob/master/Sources/Texts.Mod#L127
  PROCEDURE Store* (VAR W: Files.Rider; T: Text);
    VAR p, q: Piece;
      R: Files.Rider;
      off, rlen, pos: LONGINT;
      N, n: INTEGER;
      ch: CHAR;
      Dict: ARRAY 32, 32 OF CHAR;
  BEGIN pos := Files.Pos(W); Files.WriteInt(W, 0); (*place holder*)
    N := 1; p := T.trailer.next;
    WHILE p # T.trailer DO
      rlen := p.len; q := p.next;
      WHILE (q # T.trailer) & (q.fnt = p.fnt) & (q.col = p.col) & (q.voff = p.voff) DO
        rlen := rlen + q.len; q := q.next
      END;
      Dict[N] := p.fnt.name;
      n := 1;
      WHILE Dict[n] # p.fnt.name DO INC(n) END;
      Files.WriteByte(W, n);
      IF n = N THEN Files.WriteString(W, p.fnt.name); INC(N) END;
      Files.WriteByte(W, p.col); Files.WriteByte(W, p.voff); Files.WriteInt(W, rlen);
      p := q
    END;
    Files.WriteByte(W, 0); Files.WriteInt(W, T.len);
    off := Files.Pos(W); p := T.trailer.next;
    WHILE p # T.trailer DO
      rlen := p.len; Files.Set(R, p.f, p.off);
      WHILE rlen > 0 DO Files.Read(R, ch); Files.Write(W, ch); DEC(rlen) END ;
      p := p.next
    END ;
    Files.Set(W, Files.Base(W), pos); Files.WriteInt(W, off); (*fixup*)
    T.changed := FALSE;
    IF T.notify # NIL THEN T.notify(T, unmark, 0, 0) END
  END Store;


(Active Oberon), (Component Pascal, Zonnon), (JVM, CLR, JS), Java. Microsoft Singularity. , , , .


. , , , .

:



symbol character, , .

. . , RISC. .

, .


, , , . . .

, , , - . — -, ROM .

, , , .

, - FPGA .


. , , , . . , . .

, , .


, , , . , SD SPI. , - .

Modules. ( ) , . , , . .

Files, SD-. Files . , , .

, . , , . .

image

:



, . , , .

, . . , . BlackBox Windows, .

, , .


, , , — . , .

, , ( ), , . , , . .

, , , . , .



, , , .

.


, , , , , .

, , . , , , .

, . ? — , , , , .

, , , , , , , , - , . , , , .

, , , GNU/Linux , ( , ).

, , , , , -, . , , , . — , , , . , .

. , – . . …( ), – , , . , . … , . …


Source: https://habr.com/ru/post/258727/


All Articles