Graduate Program KB

Component principles

SLOID tells us how to arrange smaller pieces, component principles tell us how to arrange larger blocks.

Components

Components are the units of deployment, the smallest entities that can be deployed e.g. jar files, dlls. Components can be linked together into a single executable, or an archive like .war. They could also be deployed independently and loaded at runtime. Regardless of which method is chosen well designed components retain the ability to be deployed independently.

History of components

With the start of software (before virtual memory) programs had to be loaded to a specific address. One of the first lines would be an origin statement that declares where it should be loaded.

This is a PDP-8 program with a subroutine called GETSTR which saves a string from keyboard input to a buffer, as well as a test program to use it. The *200 at the start of the program specifies where it should be loaded into memory.

        *200
        TLS
 START, CLA
        TAD BUFR
        JMS GETSTR
        CLA
        TAD BUFR
        JMS PUTSTR
        JMP START
BUFR,   3000
GETSTR, 0
        DCA PTR
NXTCH,  KSF
        JMP -1
        KRB
        DCA I PTR
        TAD I PTR
        AND K177
        ISZ PTR
        TAD MCR
        SZA
        JMP NXTCH
K177,   177
MCR,    -15
  • TLS: Load teleprinter buffer
  • CLA: Clear AC
  • TAD: 2's complement add
  • JMS: Jump to subroutine
  • DCA: Deposit and clear AC
  • KSF: Skip if keyboard flag = 1
  • KRB: Clear AC and read keyboard buffer, also clears keyboard flag
  • ISZ: Increment and skip if zero
  • SZA: Skip on 0 AC

Accessing a library function required compiling the library into your application so you know where it can be found. Computers were slow and memory was expensive, you couldn't fit all the source code into memory at the same time, but compilers needed to make multiple passes. To shorten compile times programmers separated the library code from the application, it was compiled separately and loaded at a known address. A symbol table mapping subroutines names to known locations was created and linked with the source code.

But if the code grows in size that it reaches the function library, either more memory needs to be allocated for it, or it has to jump over the function library.

Relocatability

The compiler was changed to output a linking loader which was told where to load relocatable code. The relocatable code was instrumented with flags so the loader knows what sections need to be modified with the loaded address. It would also accept multiple binaries and only load what was needed.

Compilers had to be changed to emit metadata required for loading:

  • If a library function is called it omits an external reference
  • If it defined a library function it emits an external definition

The loader would then link the external definition references to external definitions at runtime.

Linkers

This worked well when small programs were being linked with small libraries, but in the 60s - 70s programs got a lot bigger. Linking loaders had to read hundreds of binary libraries on slow magnetic tape to work out what to load - it could take a linking loader an hour to load the program. Programmers took the slow part - the linking and put it into a separate application - the linker. The linker would be run after compiling and produce a linked relocatable binary that could load fast. The executable would be prepared with a slow linker, and have a fast loader.

In the 80s program size grew again with higher level languages like C. Source modules were compiled from .c files into .o files which were fed into the linker to create executables. Compiling each module was relatively fast, but compiling all the modules took more time, and the linker took still more.

Murphy's law of program size: Programs will grow to fill all available compile and link time

But disks started to shrink and got faster. Memory got cheaper. Clock speeds increased - Moore's law.

Link time decreased to seconds and linking loaders became feasible again.