← AS/A Level

CS · Chapter 20

Cambridge AS & A Level · Part 3 · Chapter 20

System Software

How the operating system shares one CPU, one memory and slow I/O between many processes, and how a compiler turns source code into object code. Every figure, question, task and exam-style question from the chapter is here, with labs to try each idea.

Guru Jazzy
Chapter 20 opener: system software

🎯 Learning objectives

By the end of this chapter you should be able to:

  • show how an operating system can make the most of the computer's resources
  • describe how the user interface hides the complexity of the hardware from the user
  • Show understanding of process management
  • show understanding of virtual memory, paging and segmentation for memory management
  • show how an interpreter can run a program without producing a translated version
  • show understanding of the stages of compiling a program
  • show how a language's grammar can be written as syntax diagrams or in Backus–Naur Form (BNF)
  • show how Reverse Polish Notation (RPN) can be used to evaluate expressions

🧭 20.01 The purposes of an operating system (OS)

Chapter 8 (Section 8.02) listed what an operating system does. This chapter looks at some of those jobs in more depth. Start with four ideas:

  1. Booting. When the computer is switched on, the OS is still on disk, so there is no OS running yet. ROM holds a BIOS (basic input/output system) that starts a bootstrap program. The bootstrap loads the OS into memory and sets it running.
  2. Several programs at once. The OS can keep more than one program in memory. Only one can use the CPU at any instant; the others wait, ready to run when they get the chance. This is multi-programming (one user). A system with many users logged in at the same time is a time-sharing system.
  3. Two viewpoints. The internal viewpoint is how the OS organises its own work to use the resources well. The external viewpoint is the facilities it gives the user.
  4. Three key resources in the internal view: the CPU, the memory and the I/O system.

Operating system structure

The OS has to manage resources and provide facilities for users, so it runs in two modes:

User mode: the mode available to the user or an application program.
Kernel mode (other names exist): has sole access to part of the memory and to some system functions that user mode cannot reach.

The kernel runs all the time. The rest of the OS runs in user mode, and each part is only loaded when it is needed. One way to arrange this is a layered structure: application programs and utilities make system calls to the kernel, and each layer must be fully serviced by the layer below it, just like a network protocol stack.

Figure 20.01Layered structure for an operating system

A strict layered structure is hard to build in practice. A more flexible approach is a modular structure: the kernel calls on separate service modules (memory, file, device and scheduling management) only when it needs them.

Taken further this becomes a micro-kernel, where the kernel itself is cut down to the absolute minimum and everything else is a module.

Figure 20.02Modular structure for an operating system

🔌 20.02 The input/output (I/O) system

The I/O system is not only about a user typing or reading a screen. It also covers data moving to and from storage devices while a program runs. Each device is reached through its own device driver.

Figure 20.03Main components associated with the I/O system

The bus structure gives the OS a choice. Small transfers can go through the CPU, but for large amounts of data the OS can arrange a direct transfer between memory and the I/O device, leaving the CPU free.

Why does this matter? A CPU typically runs at gigahertz frequencies, thousands of millions of clock cycles every second, so one second is a very long time for a computer system. Compare that with typical I/O speeds:

DeviceData rateTime to transfer 1 byte
Keyboard10 Bps0.1 s
Screen50 MBps2 × 10−8 s
Disk5 MBps2 × 10−7 s

Table 20.01 Typical rates and times for data transfer

Teacher's note: the book says one second sees "more than one trillion clock cycles". At 3 GHz it is 3 × 109, i.e. three thousand million (a US "billion"), so treat that phrase loosely. The point stands: I/O is painfully slow compared with the CPU.

So the CPU must never sit idle while I/O happens. Managing CPU time is the job of the scheduler (next tab).

🔬 Lab: if one clock cycle lasted one second

Choose a clock speed. The table shows how many clock cycles pass while one byte moves, and how long that would feel if each cycle took one second.

DeviceCycles per byteFeels like…

⚙️ 20.03 Process scheduling

Managing the CPU mostly means scheduling it so that it is used efficiently. Here the CPU is treated as a single unit; multiprocessor issues are left out.

Programs start out on disk. A user can submit a program as a job: the program plus instructions on how it should run.

Figure 20.04Components involved in running a program
  • A long-term or high-level scheduler chooses which program on disk is moved into main memory.
  • If memory gets overcrowded, a medium-term scheduler may move a program back out to disk.
  • Once a program is in memory, a short-term or low-level scheduler decides when it gets the CPU.
High-level scheduler: makes decisions about which program stored on disk should be moved into memory
Low-level scheduler: makes decisions about which process stored in memory should have access to the CPU

🔄 Process states Figure 20.05

Chapter 8 defined a process as "a program being executed". A better definition includes its state from the moment it arrives in memory. At that point a process control block (PCB) is created, ready to hold the data the process needs while it runs. After that, the process moves between states:

Figure 20.05The five states defined for a process being executed
  • A new process arrives in memory and a PCB is created → ready.
  • The dispatcher gives a ready process the CPU → running.
  • A running process is halted by an interrupt → back to ready.
  • A running process cannot continue until some event happens (I/O, perhaps) → waiting (also called suspended or blocked).
  • A waiting process is told its event has completed → ready.
  • A running process finishes → terminated.

A process can be split into parts that run separately, called threads. Each thread is then handled as an individual process.

Process: a program in memory that has an associated process control block
Process control block (PCB): a complex data structure containing all data relevant to the running of a process
Thread: part of a program which is handled as an individual process when being executed

🔬 Lab: drive a process through its life

Press an event. Only the legal transitions work. Try making a waiting process run directly and see what happens.

admit dispatch interrupt wait for I/O event done exit New Ready Running Waiting Terminated
A new job has been submitted. No PCB yet.

⚡ Interrupts

Some interrupts are caused by errors that end a running process early. Otherwise there are two reasons for an interrupt:

  • A process alternates between bursts of CPU use and I/O. I/O takes far too long for the CPU to wait idle, so when a running process makes a system call that needs I/O, an interrupt moves it to the waiting state.
  • The scheduler decides to halt the process, for one of the reasons in the scheduling algorithms below.

Whatever the reason, the kernel runs an interrupt-handling routine. It may have to decide the interrupt's priority. One thing it must do is save the current register values into the process's PCB, so the process can carry on exactly where it stopped when it next runs.

Discussion Point: What would happen if an interrupt arrived while the interrupt-handling routine was running? Does this mean each interrupt needs a priority?
My thoughts

Without priorities the second interrupt would either be lost or would break into the first routine at an unsafe moment. So interrupts are given priorities. If the new one has a higher priority, the current handler is itself suspended (its registers saved) and the new one is serviced first, then the first resumes. If it has equal or lower priority it is held (masked) until the current routine finishes.

📋 Scheduling algorithms

The high-level scheduler also makes decisions, but here we look at the options for the low-level scheduler.

Preemptive: the algorithm can halt a process that would otherwise carry on running. It may involve priorities.
Non-preemptive: once a process has the CPU it keeps it until it finishes or has to wait.
AlgorithmTypeHow it works
First come first served (FCFS)Non-preemptiveProcesses queue in a first-in first-out (FIFO) queue. Very inefficient on its own, but useful as part of a more complex scheme.
Round robinPreemptiveEach process gets a fixed time slice, then is halted and goes to the back of a FIFO queue. Usually no priorities, but separate queues for different priority levels can each be scheduled round-robin.
Priority-basedUsually preemptiveMore complicated: priorities may need re-working every time a process joins the ready queue or is halted, and judging priority needs some computation.

Possible criteria for priority

  • estimated time the process will take to run
  • Estimated time remaining
  • how long it has already spent in the ready queue
  • whether it is I/O bound or CPU bound

More than one criterion may be used, and estimating run time is not easy. Take a process that prints employees' wage slips: it needs a lot of I/O and very little CPU. It makes sense to give it a high priority so its short CPU bursts happen quickly; it then spends most of its time waiting while the printer works, out of everyone's way.

Teacher's note: exam questions also expect shortest job first (non-preemptive: pick the shortest burst when the CPU is free) and shortest remaining time (preemptive: always run whichever has least time left). They are priority schemes where the priority is "estimated time", so they are included in the lab below.

🔬 Lab: scheduling simulator

Random processes arrive. Pick an algorithm and compare the Gantt chart and the average waiting time. (Priority 1 = highest.)

ProcessArrives atCPU burstPriority
ProcessFinishes atTurnaroundWaiting time

🎮 Preemptive or not?

🧠 20.04 Memory management

Memory management covers several jobs:

  • providing protected memory for the OS kernel
  • loading a program: deciding the memory addresses for the program, its procedures and its data
  • in a multiprogramming system, coping with fragmentation of main memory (just like files on a hard disk); the medium-term scheduler may move a process out to ease the problem
  • deciding how much memory each process sharing memory should get

Partitions and segments

An early approach was to partition memory and load the whole of a process into one partition. That wasted memory whenever the process was smaller than its partition. Dynamic partitioning let the partition size match the process, but the rule stayed "one process per partition".

Segmentation extended this to larger processes: the process was split into segments, and each segment was loaded into a dynamic partition. Two things limited it:

  • segments did not have to be the same size;
  • there was not always room for all of one process's segments at once, so segments were shuffled between disk and memory.

Together these caused fragmentation of both memory and disk, and the system slowed down.

Segmentation: where a large process is divided into segments for loading into memory but the segments are not constrained to be the same size

📄 Paging and virtual memory

The modern approach is paging. The process is divided into equal-sized pages, and memory is divided into frames of the same size. Secondary storage can be divided into frames too.

Paging: where a large process is divided into pages which have to be the same size

All the pages could be loaded at once, but usually not every part of a program is needed at the same time; large programs have optional routes. A special case is a program whose address space is bigger than the memory itself. Paging then supports virtual memory.

Virtual memory: a paging mechanism that allows a program to use more memory addresses than are available in main memory

The CPU passes addresses to a memory management unit (MMU), which finds the matching place in a page frame. An address has two parts: the page number and the offset from the start of the page. The MMU uses a page table.

Figure 20.06The use of a page table for a paging system
  • Left: a program of 48 instructions occupies three pages, the first 48 logical addresses. With 8-bit logical addresses, the top 4 bits are the page number (so only 16 pages) and the bottom 4 bits are the offset.
  • Right: page frames with physical addresses. The first two program pages have been loaded; the frames used do not have to be next to each other.
  • Centre: this process's page table (every process has its own). The page number is the index; the presence flag says whether the page is in memory; the third column is the frame number (it could instead hold the physical address of the frame's first item).
TIP: Remember that you will often see numbering starting from 0.
Book error (Figure 20.06): two address labels are misprinted. Page 0's top logical address should be 00001111 (not 00011111), and the bottom of page frame 3 should be 00110000 (not 01100000). Frame 3 runs from 00110000 to 00111111.

Page faults and replacement

A process starts with all its pages on disk. One or more are loaded as it moves to the ready state. While it runs it will, sooner or later, need a page the page table says is not in memory: a page fault. Usually a page must be taken out of memory first to make room, chosen by a page replacement algorithm. The simple choice is first-in first-out; a more sensible one is least recently used (LRU), but that needs statistics of page use to be kept.

The overhead of virtual memory can hurt. The worst case is disk thrashing: code on one page needs another page that is on disk; once that is loaded, it almost immediately needs the first page again… pages are loaded and unloaded almost endlessly. Algorithms exist to guard against this, but it can still happen.

Disk thrashing: when paging is being used and a repetitive state has been reached where loading one page causes a need for another page to be loaded almost immediately but the loading of this new page causes the same immediate need

🔬 Lab: be the memory management unit

8-bit logical address = 4-bit page number + 4-bit offset. Use the page table to find the physical address, or spot a page fault.

PagePresence flagFrame
Work out the page number first.

🔬 Lab: FIFO vs LRU page replacement

Each number is a page the process asks for. Green = new page loaded, red column = page fault. Try "thrash" with 2 frames.

First-in first-out

Least recently used

Extension Question 20.01 Find out the virtual memory capacity and page size of a computer you use. In what circumstances might these need to be changed?
How to find out, and an answer

On Windows: Settings → System → About → Advanced system settings → Performance Settings → Advanced → Virtual memory shows the paging-file size. On macOS/Linux, sysctl vm.swapusage or free -h shows swap, and getconf PAGESIZE gives the page size (commonly 4096 bytes; Apple silicon uses 16 KB).

You might enlarge virtual memory when large programs (video editing, big datasets, virtual machines) run out of RAM, or shrink/move it when disk space is short or to reduce wear on an SSD. Constant thrashing is a sign that the real fix is more RAM.

🧑‍💻 20.05 Operating system facilities provided for the user

  • User interface: a command line, a graphical display or voice recognition. Whatever the form, it lets the user interact with running programs.
  • Device drivers: when a program uses a device, the OS provides the driver; the user just expects the device to work. (You might argue that printers don't always live up to this!)
  • File system: the user chooses file names and a directory (folder) structure, but never has to organise the physical storage on the disk.
  • Programming environment: a program can be written and run without the programmer knowing how the processor works.
  • System calls: a running program is a kind of user too. If it needs to read a file, the request becomes a system call: the OS takes charge, finds the file and makes it available.
  • Application programming interface (API): a set of calls, each doing a specific job such as creating a screen icon, each using one or more system calls. An API aims to make programs portable, so they run on different operating systems with minimal changes.

🛠️ 20.06 Translation software

Chapter 8 (Section 8.05) gave an overview of compilers and interpreters. Here we look inside a compiler, with a brief note on interpreters. Writing one is a specialist job and every team has its own methods, so these are general principles.

A compiler has a front end and a back end. The front end analyses the source code and, unless it finds errors, produces intermediate code that fully expresses the meaning (semantics) of the source. The back end takes that intermediate code and synthesises object code: the analysis–synthesis model.

Figure 20.07Analysis–synthesis model for a compiler

Figure 20.07 assumes no errors. The source is read line by line and each line becomes matching intermediate code. An interpreter has the same analysis front end, but once a line is error-free and converted to intermediate code, it is executed straight away, so no translated version of the whole program is ever produced.

Front-end analysis stages

  1. Lexical analysis
  2. Syntax analysis
  3. Semantic analysis
  4. Intermediate code generation
Figure 20.08Front-end analysis

1. Lexical analysis

The source code is just a sequence of characters. Each meaningful character or group of characters is a lexeme: an identifier chosen by the programmer, or a keyword, operator or symbol defined by the language. One approach is to remove all white space and comments, then take each line and identify every lexeme, a pattern-matching job that needs knowledge of the language.

Var Count : integer;

has five lexemes: Var Count : integer ;. And

PercentMark[Count] := Score * 10

has eight: PercentMark [ Count ] := Score * 10.

Each lexeme is then categorised so the line can be tokenised: Var and integer are keywords, Count is an identifier, and : and ; are distinct lexemes.

Every identifier gets an entry in the symbol table (it could have been called the identifier table). It holds attributes such as the data type, where it is declared and where it is assigned a value. Figure 20.08 shows it feeding syntax analysis, but later stages use it too, and most compilers are multi-pass, so the table is updated often.

Symbol table: a data structure in which each record contains the name and attributes of an identifier
Question 20.01 A symbol table is used by an assembler and by a compiler or interpreter. What are the differences between these?
Model answer
  • Assembler: the table holds labels (and named variables/constants) with the address each one stands for. It is built in the first pass and used in the second to replace labels with addresses. Few attributes.
  • Compiler: each record holds an identifier's name plus many attributes: data type, scope, where it is declared, where it is assigned, its eventual memory location. It is used and updated by several stages (syntax, semantic, code generation).
  • Interpreter: similar attributes to a compiler's table, but built and updated on the fly as each line is analysed and executed, and it also holds the current values of variables while the program runs.

Another table supplies the token for each lexeme that is not an identifier; sometimes identifiers are replaced by tokens too. Whatever the scheme, the output of lexical analysis is a tokenised version of the source code.

Question 20.02 Can you think of any reason why a compiler might wish to tokenise white space rather than removing it all?
Model answer
  • In some languages white space is meaningful, e.g. indentation marks blocks in Python, and line ends end statements.
  • Spaces inside string literals must be kept exactly.
  • White space separates lexemes: Var Count is two lexemes but VarCount is one identifier.
  • Keeping positions lets error messages report the exact line and column, and lets tools re-display or pretty-print the code.

🔬 Lab: lexical analyser

Type a line of code. Comments (//) and white space are removed, each lexeme is categorised and identifiers go into the symbol table.

IndexIdentifierFirst seen at lexemeAttributes (filled in later)

2. Syntax analysis (parsing)

Parsing analyses the program's constructs. The result is a syntax tree or parse tree. For

y := 2 * x + 4
Figure 20.09Parse tree for an assignment statement

Read correctly, the shape of the tree makes sure that 2 * x is worked out before 4 is added.

3. Semantic analysis

Semantic analysis establishes the full meaning of the code. It builds an annotated abstract syntax tree: each identifier in the tree gets attributes, such as its data type, which are also recorded in the symbol table.

4. Intermediate code generation

A common intermediate code is three-address code. This statement uses five identifiers, so it needs five addresses:

y := a + (b * c - d) / e

It can be rewritten as four statements, each using at most three addresses:

temp := b * c
temp := temp - d
temp := temp / e
y := a + temp

🔬 Lab: three-address code generator

Type an assignment with single-letter or named variables, numbers, + − * / ^ and brackets.

 

📐 Representation of the grammar of a language

Every programming language has a defined grammar, which both the programmer and the compiler writer must understand.

Figure 20.10Syntax diagram defining an identifier

A syntax diagram is one way to present a grammar rule. Figure 20.10 says an identifier must start with a letter, followed by any combination of none or more letters or digits. The convention here: options are drawn above the main flow line and repetitions below it.

The alternative is Backus–Naur Form (BNF). A possible BNF definition of an identifier:

<Identifier> ::= <Letter>|<Identifier><Letter>|<Identifier><Digit>
<Digit> ::= 0|1|2|3|4|5|6|7|8|9
<Letter> ::= <UpperCaseLetter>|<LowerCaseLetter>
<UpperCaseLetter> ::= A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z
<LowerCaseLetter> ::= a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z
  • | separates the options; ::= reads "is defined as".
  • The definition of <Identifier> is recursive: it uses itself. Without recursion it would have to list every possible combination after the first letter.
Syntax diagram: only used for a language; it can't be built into a compiler as an algorithm.
BNF: a general approach that can describe any assembly of data, and can be the basis of an algorithm.

🔬 Lab: does it match the grammar?

Choose a rule, type a string and test it. The checker follows the BNF one symbol at a time.

 

⚙️ Back-end synthesis stages

If the front end found syntax errors, the only back-end job is to list them, each with an explanation and its location in the source.

With no errors, the main back-end stage is machine code generation from the intermediate code. This may include optimisation, which aims to make the program efficient. One kind tidies up inefficiencies that were in the source and were carried into the intermediate code:

x := (a + b) * (a - b)
y := (a + 2 * b) * (a - b)
temp := (a - b)
x := (a + b) * temp
y := x + temp * b
Question 20.03 Check the maths for the efficient code defined above.
Model answer

y = (a + 2b)(a − b) = ((a + b) + b)(a − b) = (a + b)(a − b) + b(a − b) = x + temp × b. ✔ The optimised version works out (a − b) once and reuses x, saving operations.

Another example: a statement inside a loop that does the same thing every time round. Optimisation moves it to just before the loop.

The other kind of optimisation happens after the machine code has been created, e.g. making efficient use of registers or memory.

🎮 Which stage does this?

🔁 Evaluation of expressions

An assignment statement often has an algebraic expression. It can be evaluated by first converting its infix form into Reverse Polish Notation (RPN). RPN is a postfix notation: it never needs brackets and has no rules of precedence.

Worked Example 20.01

Manually converting an expression between RPN and infix

Converting to RPN. Take a + b * c. Precedence matters, so convert b * c first:

a + b c *

then convert the two terms to get the final RPN:

a b c * +

Had it been (a + b) * c, where the brackets matter, the RPN would be a b + c *.

Converting from RPN. Take x 2 * y 3 * + 6 /. Scan until two operands are followed by an operator and combine them (brackets just for clarity):

(x * 2) y 3 * + 6 /
(x * 2) (y * 3) + 6 /
(x * 2) + (y * 3) 6 /
((x * 2) + (y * 3)) / 6

Precedence makes some brackets unnecessary, so the final version is (x * 2 + y * 3) / 6.

Worked Example 20.02

Using a syntax tree to convert an expression to RPN

Figure 20.11Syntax tree for an infix expression

In syntax analysis, a + b * c becomes a syntax tree. The lowest-precedence operator (+) goes at the root; if several share the same precedence, the first one is used.

The RPN is read off by a post-order traversal: start at the lowest leaf left of the root and visit left – right – root each time, giving a b c * +.

Worked Example 20.03

Using a stack with an RPN expression

Figure 20.12Shunting-yard algorithm

To convert infix to RPN with a stack, use the shunting-yard algorithm. Picture the tokens as railway wagons shunted from the infix line to the RPN line, with a siding (the stack) in between. Examine each token in turn:

  • An identifier goes straight through to the RPN line.
  • An operator:
    • if the stack is empty or its top operator has lower precedence, the operator goes onto the stack;
    • if the top has equal or higher precedence, that operator is popped to the RPN line and the new one takes its place on the stack.
  • When the infix line is empty, the operators left on the stack are popped one by one onto the RPN line.
Infix lineStack lineRPN line
a + b * c
+ b * ca
b * c+a
* c+a b
c+ *a b
+ *a b c
+a b c *
a b c * +

Table 20.02 Trace of the conversion process

Here + enters the stack first; * has higher precedence, so it joins it. At the end * is popped, then +. Had the expression been a * b + c, * would have entered first but been popped before + could enter.

Book wording fixed: the book says "equal or higher preference"; it means precedence. Also, the full algorithm keeps popping while the top of the stack has equal or higher precedence (try a * b - c + d below), and it handles brackets by stacking ( and popping back to it at ). The lab does both.

🔬 Lab: shunting-yard step by step

Type an infix expression (single letters, names or numbers; + − * / ^ and brackets). Step through and watch the three lines, then see the syntax tree and its post-order traversal.

Infix lineStack lineRPN lineWhy

🥞 Evaluating an RPN expression

A stack can evaluate RPN. Take x 2 * y 3 * + 6 / with x = 3 and y = 4. Values are pushed in turn. When the next item is an operator, the top two items are popped, the operator combines them, and the result is pushed. Then carry on.

Figure 20.13Evaluating a Reverse Polish expression using a stack

The figure shows the stack after each step, with the operator above when one is used. (States with two values popped are not shown.) The answer is 3.

Order matters: for −, / and ^, the first value popped is the right-hand operand. With 18 then 6 on the stack, / gives 18 / 6, not 6 / 18.

🔬 Lab: RPN stack machine

Press Load.
TASK 20.01 Practise your understanding of RPN.
  1. Convert these infix expressions into RPN using the methods of Worked Examples 20.01, 20.02 and 20.03:
    (x - y) / 4   and   3 * (2 + x / 7)
  2. Convert these RPN expressions into infix (^ means "to the power of"):
    4 a b + c + d + e + *   and   y 2 ^ z 3 ^ + 6 /
  3. Choose simple values for each variable in part 2, evaluate the infix version, then use the stack method on the RPN and check you get the same result.
Model answers
  1. x y - 4 /   and   3 2 x 7 / + *
  2. 4 * (a + b + c + d + e)   and   (y ^ 2 + z ^ 3) / 6
  3. e.g. a = 1, b = 2, c = 3, d = 4, e = 5: infix 4 × 15 = 60; stack: 4, 1, 2 → 3, 3 → 6, 4 → 10, 5 → 15, then 4 × 15 = 60 ✔. And y = 4, z = 2: (16 + 8) / 6 = 4; stack: 4, 2 → 16, 2, 3 → 8, + → 24, 6, / → 4 ✔. (Paste them into the stack machine to check.)

🎲 Practice generator

RPN would be of little use on the simple processor of Chapter 6. Modern processors have stack instructions, so a compiler can convert expressions to RPN knowing the machine code can use the stack directly.

Reflection Point: Have you noticed that two data structures used here, the binary tree and the stack, have many different uses in computer science?

📌 Summary

  • The operating system provides resource management, including scheduling of processes, memory management and control of the I/O system.
  • For the user, the OS provides an interface, a file system and application programming interfaces.
  • A modular approach gives a flexible structure for the OS.
  • A process has five states: new, ready, running, waiting and terminated.
  • A process may be interrupted by an error, a need for I/O, or the scheduling algorithm.
  • A virtual memory system uses paging and a memory management unit that uses a page table.
  • A compiler has a front end that analyses and a back end that synthesises.
  • Backus–Naur Form represents the rules of a grammar.
  • Reverse Polish Notation is used to evaluate expressions.

🃏 Key-term flashcards

Tap a card to flip it.

✅ Quick quiz

📝 Exam-style questions

Write your answer first, then open the model answer. These are my mark-scheme-style points, not official mark schemes.

(i) A process is a program that is being executed / loaded into memory, with its own process control block holding its state, register values and resources.

(ii) Any three, 2 marks each:

  • New: the program has just been loaded into memory and its PCB created.
  • Ready: in memory, able to run, waiting in the ready queue for the CPU.
  • Running: currently has the CPU and is executing.
  • Waiting / blocked: cannot continue until an event, e.g. I/O, completes.
  • Terminated: has finished; its resources are being released.

(i) Any two: first come first served: non-preemptive · round robin: preemptive · shortest job first: non-preemptive · shortest remaining time: preemptive · priority-based: (usually) preemptive.

(ii) Any two: estimated execution time · estimated remaining time · time already spent in the ready queue · whether it is I/O bound or CPU bound.

Note: "scheduling" here is almost certainly a misprint for segmentation, the third technique in Section 20.04. The answer treats it that way.

(i)

  • Partitioning: memory is divided into partitions (fixed or dynamic) and a whole process is loaded into one partition.
  • Segmentation: a process is divided into segments of different sizes, each loaded into a dynamic partition.
  • Paging: a process is divided into equal-sized pages that are loaded into page frames of the same size, not necessarily next to each other.

(ii) Any two: segments loaded into dynamic partitions · segments divided into pages (segmented paging) · paging used with virtual memory so only some pages/segments are in memory at once.

(i) Paging.

(ii) Any two, explained: a program larger than physical memory can still run, because only the pages in use need to be in memory · more processes can be in memory at once, improving multiprogramming · less loading time because unused parts of a program are never loaded · the programmer need not worry about how much physical memory there is.

(iii) Disk thrashing: loading one page forces out a page that is needed almost straight away, so pages are swapped in and out continually and the system spends its time paging instead of doing useful work.

(i) Front end: analyse the source code, report errors and produce intermediate code that expresses its full meaning. Back end: synthesise (optimised) object code from the intermediate code.

(ii) Any two: lexical analysis · syntax analysis · semantic analysis · intermediate code generation.

(iii) Any two: code optimisation · machine/object code generation · producing the error list · register allocation.

<Digit> ::= 0|1|2|3|4|5|6|7|8|9
<Sign> ::= +|-
<Unsigned integer> ::= <Digit>|<Unsigned integer><Digit>
<Signed integer> ::= <Sign><Unsigned integer>

Test it in the grammar lab on the Translation tab.

(c) a 6 + b c / +

(d) a + (3 * b - 6 * c), i.e. a + 3 * b - 6 * c

Question 4Syntax diagrams for an assignment statement, a variable, a letter and an operator
  • (i) There is no semicolon at the end.
  • (ii) 2 is not a variable: a variable can only be made of letters.
  • (iii) cce contains e, which is not one of the allowed letters a–d.
<assignmentstatement> ::= <variable> = <variable> <operator> <variable> ;
<variable> ::= <letter>|<letter><letter>|<letter><letter><letter>
<letter> ::= a|b|c|d
<operator> ::= +|-|*|÷

(c) <variable> ::= <letter>|<variable><letter> (a recursive definition).

(i) The interpreter stops at the line with the error, so it can be fixed and the program continued straight away without recompiling everything (and variable values can be inspected).

(ii) Only the object code is distributed; machine code is very hard to read or reverse-engineer, so customers never see the source code.

Cambridge International AS & A Level Computer Science 9608, Paper 31 Q1, June 2015

A program is a static set of instructions, e.g. stored in a file on disk. A process is a program in execution: loaded into memory with a PCB, a state, and allocated resources. One program can give rise to several processes.

  • (i) The process is interrupted by the low-level scheduler: its time slice has run out, or a higher-priority process needs the CPU. It still has work to do, so it goes back to the ready queue.
  • (ii) The CPU becomes free and the low-level scheduler (dispatcher) picks this process, e.g. it is next in the ready queue or has the highest priority.
  • (iii) The process makes a request it must wait for, e.g. I/O or a resource that is not available, so it cannot continue even if given the CPU.

(c) When the event a blocked process is waiting for completes, it only becomes able to run. Another process is usually using the CPU, and the scheduler only chooses from the ready queue, so the process must join the ready queue and wait its turn by the scheduling algorithm. Moving it straight to running would bypass the scheduler and be unfair to processes already waiting.

(d) It decides which jobs on disk are loaded into memory (admitted to the ready queue), and so controls how many processes are in memory at once, aiming for a good mix of CPU-bound and I/O-bound processes.

Cambridge International AS & A Level Computer Science 9608, Paper 31 Q6, November 2015

Guru Jazzy · Cambridge AS & A Level Computer Science · Chapter 20 System Software · Figures from the Cambridge coursebook (Langfield & Duddell)