0% found this document useful (0 votes)
1 views78 pages

Operating Systems Foundations With Linux On The Raspberry Pi Paperback Wim Vanderbauwhede Jeremy Singer Instant Download

The document is a textbook titled 'Operating Systems Foundations with Linux on the Raspberry Pi' by Wim Vanderbauwhede and Jeremy Singer, which covers fundamental concepts of operating systems using Linux on Raspberry Pi. It includes topics such as memory management, process scheduling, and hardware architecture, along with practical exercises and questions. The book is published by Arm Education Media and is protected under copyright laws.

Uploaded by

ahylintomppo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views78 pages

Operating Systems Foundations With Linux On The Raspberry Pi Paperback Wim Vanderbauwhede Jeremy Singer Instant Download

The document is a textbook titled 'Operating Systems Foundations with Linux on the Raspberry Pi' by Wim Vanderbauwhede and Jeremy Singer, which covers fundamental concepts of operating systems using Linux on Raspberry Pi. It includes topics such as memory management, process scheduling, and hardware architecture, along with practical exercises and questions. The book is published by Arm Education Media and is protected under copyright laws.

Uploaded by

ahylintomppo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 78

Operating Systems Foundations With Linux On The

Raspberry Pi Paperback Wim Vanderbauwhede Jeremy


Singer download

https://ebookbell.com/product/operating-systems-foundations-with-
linux-on-the-raspberry-pi-paperback-wim-vanderbauwhede-jeremy-
singer-23877578

Explore and download more ebooks at ebookbell.com


Here are some recommended products that we believe you will be
interested in. You can click the link to download.

The Netbsd Operating System A Guide Federico Lupi

https://ebookbell.com/product/the-netbsd-operating-system-a-guide-
federico-lupi-1215254

Operating Systems Advanced Concepts Mamoru Maekawa Arthur E Oldehoeft


Rodney R Oldehoeft

https://ebookbell.com/product/operating-systems-advanced-concepts-
mamoru-maekawa-arthur-e-oldehoeft-rodney-r-oldehoeft-49455972

Operating Systems Gary J Nutt

https://ebookbell.com/product/operating-systems-gary-j-nutt-50182994

Operating Systems And Middleware Max Hailperin

https://ebookbell.com/product/operating-systems-and-middleware-max-
hailperin-50318390
Operating Systems Evolutionary Concepts And Modern Design Principles
1st Edition Pranabananda Chakraborty

https://ebookbell.com/product/operating-systems-evolutionary-concepts-
and-modern-design-principles-1st-edition-pranabananda-
chakraborty-53213086

Operating Systems Internals And Design Principles 6th Edition William


Stallings

https://ebookbell.com/product/operating-systems-internals-and-design-
principles-6th-edition-william-stallings-2117752

Operating Systems Internals And Design Principles 8th Ed Remi

https://ebookbell.com/product/operating-systems-internals-and-design-
principles-8th-ed-remi-21347210

Operating Systems Internals And Design Principles 9 Ed Stallings

https://ebookbell.com/product/operating-systems-internals-and-design-
principles-9-ed-stallings-22002718

Operating Systems Design And Implementation Tanenbaum Andrew Swoodhull

https://ebookbell.com/product/operating-systems-design-and-
implementation-tanenbaum-andrew-swoodhull-22104640
Operating Systems
Foundations
with Linux on the Raspberry Pi
TEXTBOOK

Wim Vanderbauwhede
Jeremy Singer
Operating Systems
Foundations
with Linux on the Raspberry Pi
Operating Systems
Foundations
with Linux on the Raspberry Pi
TEXTBOOK

Wim Vanderbauwhede
Jeremy Singer
Arm Education Media is an imprint of Arm Limited, 110 Fulbourn Road, Cambridge, CBI 9NJ, UK

Copyright © 2019 Arm Limited (or its affiliates). All rights reserved.

No part of this publication may be reproduced or transmitted in any form or by any means, electronic
or mechanical, including photocopying, recording or any other information storage and retrieval
system, without permission in writing from the publisher, except under the following conditions:

Permissions
„„You may reprint or republish portions of the text for non-commercial, educational or research
purposes but only if there is an attribution to Arm Education.

This book and the individual contributions contained in it are protected under copyright by the
Publisher (other than as may be noted herein).

Notices
Knowledge and best practice in this field are constantly changing. As new research and experience broaden
our understanding, changes in research methods and professional practices may become necessary.

Readers must always rely on their own experience and knowledge in evaluating and using any
information, methods, project work, or experiments described herein. In using such information or
methods, they should be mindful of their safety and the safety of others, including parties for whom
they have a professional responsibility.

To the fullest extent permitted by law, the publisher and the authors, contributors, and editors shall
not have any responsibility or liability for any losses, liabilities, claims, damages, costs or expenses resulting
from or suffered in connection with the use of the information and materials set out in this textbook.

Such information and materials are protected by intellectual property rights around the world and are
copyright © Arm Limited (or its affiliates). All rights are reserved. Any source code, models or other materials
set out in this textbook should only be used for non-commercial, educational purposes (and/or subject to
the terms of any license that is specified or otherwise provided by Arm). In no event shall purchasing this
textbook be construed as granting a license to use any other Arm technology or know-how.

ISBN: 978-1-911531-21-0

Version: 1.0.0 – PDF

For information on all Arm Education Media publications, visit our website at www.armedumedia.com

To report errors or send feedback please email [email protected]


Contents
Foreword xviii
Disclaimer xix
Preface xx
About the Authors xxiv
Acknowledgments xxv

1. A Memory-centric system model


1.1 Overview 2
1.2 Modeling the system 2
1.2.1 The simplest possible model 2
1.2.2 What is this ‘‘system state’’? 3
1.2.3 Refining non-processor actions 4
1.2.4 Interrupt requests 4
1.2.5 An important peripheral: the timer 5
1.3 Bare-bones processor model 5
1.3.1 What does the processor do? 5
1.3.2 Processor internal state: registers 6
1.3.3 Processor instructions 7
1.3.4 Assembly language 8
1.3.5 Arithmetic logic unit 8
1.3.6 Instruction cycle 8
1.3.7 Bare bones processor model 10
1.4 Advanced processor model 11
1.4.1 Stack support 11
1.4.2 Subroutine calls 12
1.4.3 Interrupt handling 13
1.4.4 Direct memory access 13
1.4.5 Complete cycle-based processor model 14
1.4.6 Caching 15
1.4.7 Running a program on the processor 18
1.4.8 High-level instructions 19
1.5 Basic operating system concepts 20
1.5.1 Tasks and concurrency 20
1.5.2 The register file 21
1.5.3 Time slicing and scheduling 21
1.5.4 Privileges 23

vi
Contents

1.5.5 Memory management 23


1.5.6 Translation look-aside buffer (TLB) 24
1.6 Exercises and questions 25
1.6.1 Task scheduling 25
1.6.2 TLB model 25
1.6.3 Modeling the system 25
1.6.4 Bare-bones processor model 25
1.6.5 Advanced processor model 25
1.6.6 Basic operating system concepts 25

2. A Practical view of the Linux System


2.1 Overview 30
2.2 Basic concepts 30
2.2.1 Operating system hierarchy 31
2.2.2 Processes 31
2.2.3 User space and kernel space 32
2.2.4 Device tree and ATAGs 32
2.2.5 Files and persistent storage 32
Partition 32
File system 33
2.2.6 ‘Everything is a file’ 33
2.2.7 Users 34
2.2.8 Credentials 34
2.2.9 Privileges and user administration 35
2.3 Booting Linux on the Arm (Raspberry Pi 3) 36
2.3.1 Boot process stage 1: Find the bootloader 36
2.3.2 Boot process stage 2: Enable the SDRAM 36
2.3.3 Boot process stage 3: Load the Linux kernel into memory 37
2.3.4 Boot process stage 4: Start the Linux kernel 37
2.3.4 Boot process stage 5: Run the processor-independent kernel code 37
2.3.5 Initialization 37
2.3.6 Login 38
2.4 Kernel administration and programming 38
2.4.1 Loadable kernel modules and device drivers 38
2.4.2 Anatomy of a Linux kernel module 39
2.4.3 Building a custom kernel module 41
2.4.4 Building a custom kernel 42

vii
Contents

2.5 Kernel administration and programming 42


2.5.1 Process management 42
2.5.2 Process scheduling 43
2.5.3 Memory management 43
2.5.4 Concurrency and parallelism 43
2.5.5 Input/output 43
2.5.6 Persistent storage 43
2.5.7 Networking 44
2.6 Summary 44
2.7 Exercises and questions 44
2.7.1 Installing Raspbian on the Raspberry Pi 3 44
2.7.2 Setting up SSH under Raspbian 44
2.7.3 Writing a kernel module 44
2.7.4 Booting Linux on the Raspberry Pi 45
2.7.5 Initialization 45
2.7.6 Login 45
2.7.7 Administration 45

3. Hardware architecture
3.1 Overview 49
3.2 Arm hardware architecture 49
3.3 Arm Cortex M0+ 50
3.3.1 Interrupt control 51
3.3.2 Instruction set 51
3.3.3 System timer 52
3.3.4 Processor mode and privileges 52
3.3.5 Memory protection 52
3.4 Arm Cortex A53 53
3.4.1 Interrupt control 53
3.4.2 Instruction set 54
Floating-point and SIMD support 55
3.4.3 System timer 56
3.4.4 Processor mode and privileges 56
3.4.5 Memory management unit 57
Translation look-aside buffer 58
Additional caches 59

viii
Contents

3.4.6 Memory system 59


L1 Cache 59
L2 Cache 60
Data cache coherency 61
3.5 Address map 61
3.6 Direct memory access 63
3.7 Summary 64
3.8 Exercises and questions 64
3.8.1 Bare-bones programming 64
3.8.2 Arm hardware architecture 65
3.8.3 Arm Cortex M0+ 65
3.8.4 Arm Cortex A53 65
3.8.5 Address map 65
3.8.6 Direct memory access 65

4. Process management
4.1 Overview 70
4.2 The process abstraction 70
4.2.1 Discovering processes 71
4.2.2 Launching a new process 71
4.2.3 Doing something different 73
4.2.4 Ending a process 73
4.3 Process metadata 74
4.3.1 The /proc file system 75
4.3.2 Linux kernel data structures 76
4.3.3 Process hierarchies 77
4.4 Process state transitions 79
4.5 Context switch 81
4.6 Signal communications 83
4.6.1 Sending signals 83
4.6.2 Handling signals 84
4.7 Summary 85
4.8 Further reading 85
4.9 Exercises and questions 85
4.9.1 Multiple choice quiz 85
4.9.2 Metadata mix 86

ix
Contents

4.9.3 Russian doll project 86


4.9.4 Process overload 86
4.9.5 Signal frequency 86
4.9.6 Illegal instructions 86

5. Process scheduling
5.1 Overview 90
5.2 Scheduling overview: what, why, how? 90
5.2.1 Definition 90
5.2.2 Scheduling for responsiveness 90
5.2.3 Scheduling for performance 91
5.2.4 Scheduling policies 91
5.3 Recap: the process lifecycle 91
5.4 System calls 93
5.4.1 The Linux syscall(2) function 94
5.4.2 The implications of the system call mechanism 95
5.5 Scheduling principles 95
5.5.1 Preemptive versus non-preemptive scheduling 96
5.5.2 Scheduling policies 96
5.5.3 Task attributes 96
5.6 Scheduling criteria 96
5.7 Scheduling policies 97
5.7.1 First-come, first-served (FCFS) 97
5.7.2 Round-robin (RR) 98
5.7.3 Priority-driven scheduling 98
5.7.4 Shortest job first (SJF) and shortest remaining time first (SRTF) 99
5.7.5 Shortest elapsed time first (SETF) 100
5.7.6 Priority scheduling 100
5.7.7 Real-time scheduling 100
5.7.8 Earliest deadline first (EDF) 101
5.8 Scheduling in the Linux kernel 101
5.8.1 User priorities: niceness 102
5.8.2 Scheduling information in the task control block (TCB) 102
5.8.3 Process priorities in the Linux kernel 104
Priority info in task_struct 105
Priority and load weight 106

x
Contents

5.8.4 Normal scheduling policies: the completely fair scheduler (CFS) 107
5.8.5 Soft real-time scheduling policies 110
5.8.6 Hard real-time scheduling policy 112
Time budget allocation 113
5.8.7 Kernel preemption models 115
5.8.8 The red-black tree in the Linux kernel 116
Creating a new rbtree 116
Searching for a value in a rbtree 117
Inserting data into a rbtree 117
Removing or replacing existing data in a rbtree 118
Iterating through the elements stored in a rbtree (in sort order) 118
Cached rbtrees 119
5.8.9 Linux scheduling commands and API 119
Normal processes 119
Real-time processes 120
5.9 Summary 120
5.10 Exercises and questions 120
5.10.1 Writing a scheduler 120
5.10.2 Scheduling 120
5.10.3 System calls 121
5.10.4 Scheduling policies 121
5.10.5 The Linux scheduler 121

6. Memory management
6.1 Overview 126
6.2 Physical memory 126
6.3 Virtual memory 127
6.3.1 Conceptual view of memory 127
6.3.2 Virtual addressing 128
6.3.3 Paging 129
6.4 Page tables 130
6.4.1 Page table structure 130
6.4.2 Linux page tables on Arm 132
6.4.3 Page metadata 134
6.4.4 Faster translation 136
6.4.5 Architectural details 137

xi
Contents

6.5 Managing memory over-commitment 138


6.5.1 Swapping 138
6.5.2 Handling page faults 138
6.5.3 Working set size 141
6.5.4 In-memory caches 142
6.5.5 Page replacement policies 142
Random 143
Not recently used (NRU) 143
Clock 143
Least recently used 144
Tuning the system 144
6.5.6 Demand paging 145
6.5.7 Copy on Write (CoW) 146
6.5.8 Out of memory killer 147
6.6 Process view of memory 148
6.7 Advanced topics 149
6.8 Further reading 151
6.9 Exercises and questions 151
6.9.1 How much memory? 151
6.9.2 Hypothetical address space 152
6.9.3 Custom memory protection 152
6.9.4 Inverted page tables 152
6.9.5 How much memory? 153
6.9.6 Tiny virtual address space 153
6.9.7 Definitions quiz 153

7. Concurrency and parallelism


7.1 Overview 158
7.2 Concurrency and parallelism: definitions 158
7.2.1 What is concurrency? 158
7.2.2 What is parallelism? 158
7.2.3 Programming model view 158
7.3 Concurrency 159
7.3.1 What are the issues with concurrency? 159
Shared resources 159
Exchange of information 160

xii
Contents

7.3.2 Concurrency terminology 161


Critical section 161
Synchronization 161
Deadlock 161
7.3.3 Synchronization primitives 163
7.3.4 Arm hardware support for synchronization primitives 163
Exclusive operations and monitors 163
Shareability domains 164
7.3.5 Linux kernel synchronization primitives 165
Atomic primitives 165
Memory operation ordering 169
Memory barriers 170
Spin locks 173
Futexes 174
Kernel mutexes 174
Semaphores 175
7.3.6 POSIX synchronization primitives 177
Mutexes 178
Semaphores 178
Spin locks 179
Condition variables 179
7.4 Parallelism 181
7.4.1 What are the challenges with parallelism? 181
7.4.2 Arm hardware support for parallelism 182
7.4.3 Linux kernel support for parallelism 183
SMP boot process 183
Load balancing 183
Processor affinity control 184
7.5 Data-parallel and task-parallel programming models 184
7.5.1 Data parallel programming 184
Full data parallelism: map 184
Reduction 185
Associativity 185
Binary tree-based parallel reduction 185
7.5.2 Task parallel programming 186

xiii
Contents

7.6 Practical parallel programming frameworks 186


7.6.1 POSIX Threads (pthreads) 186
7.6.2 OpenMP 189
7.6.3 Message passing interface (MPI) 190
7.6.4 OpenCL 191
7.6.5 Intel threading building blocks (TBB) 194
7.6.6 MapReduce 195
7.7 Summary 195
7.8 Exercises and questions 195
7.8.1 Concurrency: synchronization of tasks 196
7.8.1 Parallelism 196

8. Input/output
8.1 Overview 202
8.2 The device zoo 202
8.2.1 Inspect your devices 203
8.2.2 Device classes 203
8.2.3 Trivial device driver 204
8.3 Connecting devices 206
8.3.1 Bus architecture 206
8.4 Communicating with devices 207
8.4.1 Device abstractions 207
8.4.2 Blocking versus non-blocking IO 207
8.4.3 Managing IO interactions 208
Polling 209
Interrupts 209
Direct memory access 210
8.5 Interrupt handlers 210
8.5.1 Specific interrupt handling details 211
8.5.2 Install an interrupt handler 212
8.6 Efficient IO 213
8.7 Further reading 213
8.8 Exercises and questions 213
8.8.1 How many interrupts? 213
8.8.2 Comparative complexity 214
8.8.3 Roll your own Interrupt Handler 214
8.8.4 Morse Code LED Device 214

xiv
Contents

9. Persistent storage
9.1 Overview 218
9.2 User perspective on the file system 218
9.2.1 What is a file? 218
9.2.2 How are multiple files organized? 219
9.3 Operations on files 221
9.4 Operations on directories 222
9.5 Keeping track of open files 222
9.6 Concurrent access to files 223
9.7 File metadata 225
9.8 Block-structured storage 226
9.9 Constructing a logical file system 228
9.9.1 Virtual file system 228
9.10 Inodes 230
9.10.1 Multiple links, single inode 231
9.10.2 Directories 231
9.11 ext4 233
9.11.1 Layout on disk 233
9.11.2 Indexing data blocks 224
9.11.3 Multiple links, single inode 237
9.11.4 Checksumming 237
9.11.5 Encryption 237
9.12 FAT 238
9.12.1 Advantages of FAT 239
9.12.2 Construct a mini file system using FAT 240
9.13 Latency reduction techniques 242
9.14 Fixing up broken file systems 243
9.15 Advanced topics 243
9.16 Further reading 245
9.17 Exercises and questions 245
9.17.1 Hybrid contiguous and linked file system 245
9.17.2 Extra FAT file pointers 245
9.17.3 Expected file size 245
9.17.4 Ext4 extents 245
9.17.5 Access times 245
9.17.6 Database decisions 246

xv
Contents

10. Networking
10.1 Overview 250
10.2 What is networking 250
10.3 Why is networking part of the kernel? 250
10.4 The OSI layer model 251
10.5 The Linux networking stack 252
10.5.1 Device drivers 253
10.5.2 Device-agnostic interface 253
10.5.3 Network protocols 253
10.5.4 Protocol-agnostic interface 253
10.5.5 System call interface 254
10.5.6 Socket buffers 254
10.6 The POSIX standard socket interface library 255
10.6.1 Stream socket (TCP) communications flow 255
10.6.2 Common internet data types 256
Socket address data type: struct sockaddr 256
Internet socket address data type: struct sockaddr_in 257
10.6.3 Common POSIX socket API functions 258
Create a socket descriptor: socket() 258
Bind a server socket address to a socket descriptor: bind() 258
Enable server socket connection requests: listen() 259
Accept a server socket connection request: accept() 259
Client connection request: ‘connect()’ 260
Write data to a stream socket: send() 261
Read data from a stream socket: recv() 261
Setting server socket options: setsockopt() 262
10.6.4 Common utility functions 263
Internet address manipulation functions 263
Internet network/host byte order manipulation functions 263
Host table access functions 264
10.6.5 Building applications with TCP 264
Request/response communication using TCP 264
TCP server 265
TCP client 266
10.6.6 Building applications with UDP 268
UDP server 269

xvi
Contents

UDP client 270


UDP client using connect() 271
10.6.7 Handling multiple clients 271
The select() system call 271
Multiple server processes: fork() and exec() 275
Multithreaded servers using pthreads 276
10.7 Summary 278
10.8 Exercises and questions 278
10.8.1 Simple social networking 278
10.8.2 The Linux networking stack 278
10.8.3 The POSIX socket API 278

11. Advanced topics


11.1 Overview 282
11.2 Scaling down 282
11.3 Scaling up 283
11.4 Virtualization and containerization 285
11.5 Security 287
11.5.1 Rowhammer, Rampage, Throwhammer, and Nethammer 287
11.5.2 Spectre, Meltdown, Foreshadow 288
11.6 Verification and certification 289
11.7 Reconfigurability 291
11.8 Linux development roadmap 292
11.9 Further reading 293
11.10 Exercises and questions 293
11.10.1 Make a minimal kernel 293
11.10.2 Verify important properties 293
11.10.3 Commercial comparison 293
11.10.4 For or against certification 293
11.10.5 Devolved decisions 293
11.10.6 Underclock, overclock 293

Glossary of terms 296


Index 304

xvii
Foreword

In 1983, when I started modeling a RISC processor using a simulator written in BBC Basic on a BBC
Microcomputer, I could hardly have conceived that there would be billions of Arm (then short for
‘Acorn RISC Machine’) processors all over the world within a few decades.

I expect Linus Torvalds has similar feelings, when he thinks back to the early days, crafting a prototype
operating system for his i386 PC. Now Linux runs on a vast array of devices, from smartwatches to
supercomputers. I am delighted that an increasing proportion of these devices are built around Arm
processor cores.

In a more recent tale of runaway success, the Raspberry Pi single-board computer has far exceeded
its designers’ initial expectations. The Raspberry Pi Foundation thought they might sell one thousand
units, ‘maybe 10 thousand in our wildest dreams.’ With sales figures now around 20 million, the
Raspberry Pi is firmly established as Britain’s best-selling computer.

This textbook aims to bring these three technologies together—Arm, Linux, and Raspberry Pi. The
authors’ ambitious goal is to ‘make Operating Systems fun again.’ As a professor in one of the UK’s
largest university Computer Science departments, I am well aware that modern students demand
engaging learning materials. Dusty 900-page textbooks with occasional black and white illustrations
are not well received. Today’s learners require interactive content, gaining understanding through
practical experience and intuitive analogies. My observation applies to students in traditional higher
education, as well as those pursuing blended and fully online education. I am confident this innovative
textbook will meet the needs of the next generation of Computer Science students.

While the modern systems software stack has become large and complex, the fundamental principles
are unchanging. Operating Systems must trade-off abstraction for efficiency. In this respect, Linux
on Arm is particularly instructive. The authors do an excellent job of presenting Operating Systems
concepts, with direct links to concrete examples of these concepts in Linux on the Raspberry Pi.
Please don’t just read this textbook – buy a Pi and try out the practical exercises as you go.

Was it Plutarch who said, ‘The mind is not a vessel to be filled but a fire to be kindled’? We could
translate this into the Operating Systems domain as follows: ‘Learning isn’t just reading source code;
it’s bootstrapping machines.’ I hope that you enjoy all these activities, as you explore Operating
Systems with Linux on Arm using your Raspberry Pi.

Steve Furber CBE FRS FREng


ICL Professor of Computer Engineering
The University of Manchester, UK
February 2019

xviii
Disclaimer
The design examples and related software files included in this book are created for educational
purposes and are not validated to the same quality level as Arm IP products. Arm Education Media
and the author do not make any warranties of these designs.

Note
When we developed the material for this textbook, we worked with Raspberry Pi 3B boards.
However, all our practical exercises should work on other generations and variants of Raspberry Pi
devices, including the more recent Raspberry Pi 4.

xix
Preface

Introduction
Modern computer devices are fabulously complicated both in terms of the processor hardware and
the software they run.

At the heart of any modern computer device sits the operating system. And if the device is a
smartphone, IoT node, datacentre server or supercomputer, then the operating system is very likely to
be Linux: about half of consumer devices run Linux; the vast majority of smartphones worldwide (86%)
run Android, which is built on the Linux kernel. Of the top one million web servers, 98% run Linux.
Finally, the top 500 fastest supercomputers in the world all run Linux.

On the hardware side, Arm has a 95% market share in smartphone and tablet processors as well as
being used in the majority of Internet of Things (IoT) devices such as webcams, wireless routers, etc.
and embedded devices in general.

Since its creation by Linus Torvalds in 1991, the efforts of thousands of people, most of them
volunteers, have turned Linux into a state-of-the-art, flexible and powerful operating system, suitable
for any system from tiny IoT devices to the most powerful supercomputers.

Meanwhile, in roughly the same period, the Arm processor range has expanded to cover an equally
wide gamut of systems and devices, including the remarkably successful Raspberry Pi.

So if you want to learn about Operating Systems but keep a practical, real-world focus, then this book
is an ideal starting point. This book will help you answer questions such as:

„„What is a file, and why is the file concept so important in Linux?

„„What is scheduling and how can knowledge of Linux scheduling help you create a high-throughput
video processor or a mission-critical real-time system?

„„What are POSIX threads, and how can the Linux kernel assist you in making your multithreaded
applications faster and more responsive?

„„How does the Linux kernel support networking, and how do you create network clients and servers?

„„How does the Arm hardware assist the Linux kernel in managing memory and how does
understanding memory management make you a better programmer?

The aim of this book is to provide a practical introduction to the foundations of modern operating
systems, with a particular focus on GNU/Linux and the Arm platform. Our unique perspective is
that we explain operating systems theory and concepts but ground them in practical use through
illustrative examples of their implementation in GNU/Linux, as well as making the connection with
the Arm hardware supporting the OS functionality.

xx
Preface

Is this book suitable for you?


This book does not require prior knowledge of operating systems, but some familiarity with command-
line operations in a GNU/Linux system is expected. We discuss technical details of operating systems,
and we use source code to illustrate many concepts. Therefore, you need to know C and Python, and
you need to be familiar with basic data structures such as arrays, queues, stacks and trees.

This textbook is ideal for a one-semester course introducing the concepts and principles underlying
modern operating systems. It complements the Arm online courses in Real-Time Operating Systems
Design and Programming, and Embedded Linux.

Online additional material


The companion web site of the book (www.dcs.gla.ac.uk/operating-system-foundations) contains:

„„Source code for all original code snippets listed in the book;

„„Answers to questions and exercises;

„„Lab materials;

„„Additional content;

„„Additional teaching materials;

„„Further reading.

Target platform
This textbook focuses on the Raspberry Pi 3, an Arm Cortex-A53 platform running Linux. We use the
Raspbian GNU/Linux distribution. However, the book does not specifically depend on this platform
and distribution, except for the exercises.

If you don’t own a Raspberry Pi 3, you can use the QEMU emulator which supports the Raspberry Pi 3.

Software development environment


The code examples in this book are either in C or Python 3. We assume that the reader has access
to a Linux system with an installation of Python, a C compiler, the make build tool and the git version
control tool.

Structure
The structure of this textbook is based on our many years of teaching operating systems courses at
undergraduate and masters level, taking into account the feedback provided by the reviewers of the
text. The content of the text is closely aligned to the Computing Curricula 2001 Computing Science
report recommendations for teaching Operating Systems, published by the Joint Task Force of the
IEEE Computing Society and the Association for Computing Machinery (ACM).

xxi
Preface

The book is organized into eleven chapters.

Chapters 1 and 2 provide alternate introductory views to operating systems.

Chapter 1 A memory-centric system model presents a top-down view. In this chapter, we introduce a
number of abstract models for processor-based systems. We use Python code to describe the models
and only use simple data structures and functions. The purpose is to help the student understand that
in a processor-based system, all actions fundamentally reduce to operations on addresses. The models
are gradually being refined as the chapter advances, and by the end, the model integrates the basic
operating system functionality into a runnable Python-based processor model.

Chapter 2 A practical view of the Linux system approaches the Linux system from a practical
perspective: what actually happens when we boot and run the system, how does it work and what is
required to make it work. We first introduce the essential concepts and techniques that the student
needs to know in order to understand the overall system, and then we discuss the system itself.
The aim of this part is to help the student answer questions such as “what happens when the system
boots?” or “how does Linux support graphics?”. This is not a how-to guide, but rather, provides the
student with the background knowledge behind how-to guides.

In Chapter 3 Hardware architecture, we discuss the hardware on which the operating system runs,
the hardware support for operating systems (dedicated registers, MMU, DMA, interrupt architecture,
relevant details about the bus/NoC architecture, ...), the memory subsystem (caches, TLB), high-level
language support, boot subsystem and boot sequence. The purpose is to provide the student with a
useable mental model for the hardware system and to explain the need for an operating system and
how the hardware supports the OS. In particular, we study the Linux view on the hardware system.

The next seven chapters form the core of the book, each of these introduces a core Operating System
concept.

In Chapter 4, Process management, we introduce the process abstraction. We outline the state
that needs to be encapsulated. We walk through the typical lifecycle of a process from forking to
termination. We review the typical operations that will be performed on a process.

Chapter 5 Process scheduling discusses how the OS schedules processes on a processor. This includes
the rationale for scheduling, the concept of context switching, and an overview of scheduling policies
(FCFS, priority, ...) and scheduler architectures (FIFO, multilevel feedback queues, priorities, ...). The
Linux scheduler is studied in detail.

While memory itself is remarkably straightforward, OS architects have built lots of abstraction layers
on top. Principally, these abstractions serve to improve performance and/or programmability. In
Chapter 6 Memory management, we review caches (in hardware and software) to improve access
speed. We go into detail about virtual memory to improve the management of physical memory
resource. We will provide highly graphical descriptions of address translation, paging, page tables,
page faults, swapping, etc. We explore standard schemes for page replacement, copy-on-write, etc.
We will examine concrete examples in Arm architecture and Linux OS.

xxii
Preface

In Chapter 7, Concurrency and parallelism, we discuss how the OS supports concurrency and how the
OS can assist in exploiting hardware parallelism. We define concurrency and parallelism and discuss
how they relate to threads and processes. We discuss the key issue of resource sharing, covering
locking, semaphores, deadlock and livelock. We look at OS support for concurrent and parallel
programming via POSIX threads and present an overview of practical parallel programming techniques
such as OpenMP, MPI and OpenCL.

Chapter 8 Input/output presents the OS abstraction of an IO device. We review device interfacing,


covering topics like Polling, Interrupts and DMA. We will investigate a range of device types, to
highlight their diverse features and behavior. We will cover hardware registers, memory mapping
and coprocessors. Further, we will examine the ways in which devices are exposed to programmers.
We will review the structure of a typical device driver.

Chapter 9 Persistent storage focuses on data storage. We outline the range of use cases for file
systems. We explain how the raw hardware (block- and sector-based 2d storage, etc.) is abstracted at
the OS level. We talk about mapping high-level concepts like files, directories, permissions, etc., down
to physical entities. We review allocation, space management, and recovery from failure. We present
a case study of a Linux file system. We also discuss Windows-style FAT, since this is how USB bulk
storage operates.

Chapter 10 Networking introduces networking from an OS perspective: why is networking treated


differently from other types of IO, what are the OS requirements to support the OSI stack. We
introduce socket programming with a focus of the role the OS plays (e.g. zero-copy buffers, file
abstraction, supporting multiple clients, ...).

Finally, Chapter 11 Advanced topics discusses a number of concepts that go beyond the material of
the previous chapters: The first part of this chapter deals with customisation of Linux for Embedded
Systems, Linux on systems without MMU, and datacentre level operating systems. The second
part discusses the security of Linux-based systems, focusing on validation and verification of OS
components and the analysis of recent security exploits.

We hope that you enjoy both reading our book and doing the exercises – especially if you are trying
them on the Raspberry Pi. Please do let us know what you think about our work and how we could
improve it by sending your comments to Arm Education Media [email protected]

Jeremy Singer and Wim Vanderbauwhede, 2019

xxiii
About the Authors

Wim Vanderbauwhede
School of Computing Science, University of Glasgow, UK

Prof. Wim Vanderbauwhede is Professor in Computing Science at the School of Computing Science
of the University of Glasgow. He has been teaching and researching operating systems for over
a decade. His research focuses on high-level programming, compilation, and architectures for
heterogeneous manycore systems and FPGAs, with a special interest in power-efficient computing
and scientific High-Performance Computing (HPC). He is the author of the book ‘High-Performance
Computing Using FPGAs’. He received his Ph.D. in Electrotechnical Engineering with Specialisation
in Physics from the University of Gent, Belgium in 1996. Before moving into academic research,
Prof. Vanderbauwhede worked as an ASIC Design Engineer and Senior Technology R&D Engineer for
Alcatel Microelectronics.

Jeremy Singer
School of Computing Science, University of Glasgow, UK

Dr. Jeremy Singer is a Senior Lecturer in Systems at the School of Computing Science of the University
of Glasgow. His main research theme involves programming language runtimes, with particular
interests in garbage collection and manycore parallelism. He leads the Federated Raspberry Pi
Micro-Infrastructure Testbed (FRµIT) team, investigating next-generation edge compute platforms.
He received his Ph.D. from the University of Cambridge Computer Laboratory in 2006. Singer and
Vanderbauwhede also collaborated in the design of the FutureLearn ‘Functional Programming in
Haskell’ massive open online course.

xxiv
Acknowledgements

The authors would like to thank the following people for their help:

„„Khaled Benkrid, who made this book possible.

„„Ashkan Tousimojarad, who originally suggested the project.

„„Melissa Good, Jialin Dou and Michael Shuff who kept us on track and assisted us with the process.

„„The reviewers at Arm who provided valuable feedback on our drafts.

„„Tony Garnock-Jones, Dejice Jacob, Richard Mortier, Colin Perkins, and other colleagues who
commented on early versions of the text.

„„Steve Furber, for his kind endorsement of the book.

„„Lovisa Sundin, for her help with illustrations.

„„Jim Garside, Kristian Hentschel, Simon McIntosh-Smith, Magnus Morton and Michèle Weiland for
kindly allowing us to use their photographs.

„„The countless volunteers who made the Linux kernel what it is today.

xxv
Chapter 1
A Memory-centric
system model
Operating Systems Foundations with Linux on the Raspberry Pi

1.1 Overview
In this chapter, we will introduce a number of abstract memory-centric models for processor-based
systems. We will use Python code to describe the models and only use simple data structures and
functions. The models are abstract in the sense that we do not build the processor system starting from
its physical building blocks (transistors, logic gates, etc.), but rather, we model it in a functional way.

The purpose is to help you understand that in a processor-based system, all actions fundamentally
reduce to operations on addresses. This is a very important point: every observable action in a processor-
based system is the result of writing to or reading from an address location.

In particular, this includes all peripherals of the system, such as the network card, keyboard, and display.

What you will learn


After you have studied the material in this chapter, you will be able to:

1. Discuss the importance of state and the address space in a processor-based system.

2. Create a processor-based system model in a high-level language.

3. Implement basic operating system concepts such as time slicing in machine code.

4. Explain how hardware and software features of a processor-based system are designed to handle
I/O, concurrency, and performance.

1.2 Modeling the system

„„A microprocessor is driven by a clock.

„„Our model will describe the actions at every tick of the clock using functions.

„„We will model the system through its state, represented as a simple data structure.

By “state,” we mean information that is persistent, i.e., some form of memory. This is not limited to
actual computer memory. For example, if our system controls a robot arm, then the position of the
arm is part of the state of the system.

1.2.1 The simplest possible model


We start our system model by stating that the action of the processor modifies the system state:

systemState = processorAction(systemState) Python

In practice, the system also interacts with the outside world through peripherals such as the keyboard,
network interface, etc., generally called “I/O devices”, storage devices such as disks, etc. Let's just call
these types of actions to modify the state ‘non-processor actions’. Adding this to our model, we get:
2
Chapter 1 | A Memory-centric system model

Listing 1.2.1: System state with non-processor actions Python

1 systemState = nonProcessorAction(systemState)
2 systemState systemState = processorAction(systemState)

In a real system, these actions happen at the same time (we call concurrent actions), so one of the
questions (that we will address in detail in Chapters 7, ‘Concurrency and parallelism’) is how to make
sure that the system state does not become undetermined as a result of concurrent actions. But first,
let’s look in a bit more detail at the system state.

1.2.2 What is this ‘system state’?


We say that the processor ‘‘modifies the system state’’, so let’s take a closer look at this system
state. From the point of view of the processor, the system state is simply a fixed-size array of unsigned
integers. Nothing more than that. In C syntax, we can express this as shown in Listing 1.2.2:

Listing 1.2.2: System state as C array C

1 int systemState[STATE_SZ]

Which means that manipulation of the system state, and by consequence, anything that happens in
a processor-based system boils down to modifying this array.

So, what does this array actually represent? It represents all of the memory in the system, not just the
actual system memory (DRAM, Dynamic Random Access Memory) but including the I/O devices and
other peripherals such as disks. In system terms, this is known as the ‘physical address space’, and we
will discuss this in detail in Chapter 6, ‘‘Memory management.’’1

In other words, the system state is composed of the states of all the system components, for example
for a system with a keyboard kbd, network interface card nic, solid state disk ssd, graphics processing
unit gpu, and random access memory ram:

systemState = ramState + kbdState + nicState + ssdState + gpuState Python

Where ramState, kbdState, nicState, etc. are all fixed-size arrays of integers.

However, it could of course equally be:

systemState = ssdState + kbdState + nicState + ramState + gpuState Python

The above are two examples of address space layouts. The description of the purpose, size, and
position of the address regions for memory and peripherals is called the address map. As an illustration,
the Arm address map for A-class systems [1] is shown in Figure 1.1.

1
As our model focuses on Arm-based systems, we do not discuss port-mapped I/O.

3
Operating Systems Foundations with Linux on the Raspberry Pi

32-bit 36-bit 40-bit


1024 GB 40-bit
DRAM
32 GB hole or DRAM
512 GB
Mapped I/O
256 GB
128 GB Reserved
64 GB 36-bit
DRAM
Log2 scale
2 GB hole or DRAM
32 GB
Mapped I/O
16 GB
8 GB Reserved

4 GB 32-bit
2 GB of DRAM
2 GB
Mapped I/O
1 GB
ROM & RAM & I/O
0 GB 0
32-bit 36-bit 40-bit

Figure 1.1: Arm 40-bit address map.

Figure 1.1. If the address size is 32 bits, we can address 232 = 4GB of memory. We see from the figure
that different regions are reserved for different purposes, e.g., the second GB is memory mapped I/O,
and the upper 2 GB are random access memory (DRAM).

1.2.3 Refining non-processor actions


Using the more detailed state from above, we can split the non-processor actions into per-peripheral
actions, so that our model becomes:

Listing 1.2.3: Model with per-peripheral actions Python

1 kbdState=kbdAction(kbdState)
2 nicState=nicAction(nicState)
3 ssdState=ssdAction(ssdState)
4 gpuState=gpuAction(gpuState)
5 systemState = ramState+kbdState+nicState+diskState+gpuState
6 systemState = processorAction(systemState)

Each of these actions only affects the state of the peripheral; the rest of the system state remains
unaffected.

1.2.4 Interrupt requests


Let’s return now to the potential problem of state modified by concurrent actions. The way we just
separated the state offers a possible solution. Now we can create a kind of notification mechanism
which lets the processor know that an outside action has modified the state2.

This is exactly what happens in real systems, and the mechanisms used are called interrupts. We will
discuss this in detail in Chapter 8, ‘Input/output’, but it is useful to add an interrupt mechanism to our
abstract model.

A peripheral can send an interrupt request (IRQ) to the processor. We will model the interrupt request as a
boolean flag which is returned by every peripheral action together with its state (as a tuple). The processor

2
We could also let the processor check if the state of a peripheral was changed before acting on it. This approach is called polling and will be discussed in Chapter 8, ‘Input/output’.

4
Chapter 1 | A Memory-centric system model

action receives an array of these interrupt requests and uses the array index to identify the peripheral that
raised the interrupt (‘raising an interrupt’ in our model means setting the boolean flag to True).

In practice, the mechanism is more complicated because many peripherals can raise multiple different
interrupt requests depending on the condition. Typically, a dedicated peripheral called interrupt
controller is used to manage the interrupts from the various devices.

Note that the interrupt mechanism is purely a notification mechanism: it does not stop the processor
from modifying the peripheral state, all it does is notify the processor that the peripheral unilaterally
changed its state. So in principle, the peripheral could still be modifying its state at the very same time
that the processor is modifying it. In what follows, we simply assume that this cannot happen, i.e., if a
peripheral is modifying its state, then the processor can’t change it and vice versa. A possible model for
this is that the peripheral state change and the interrupt request are happening at the same time and
that the processor always needs to process the request before making a state change.

Listing 1.2.4: Model with interrupt requests Python

1 (kbdState,kbdIrq)=kbdAction(kbdState)
2 ...
3
4 irqs=[kbdIrq,...]
5
6 systemState = ramState+kbdState+nicState+diskState+gpuState
7 (systemState,irqs) = processorAction(systemState,irqs)

We will see in the next section how the processor handles interrupts.

1.2.5 An important peripheral: the timer


A timer is a peripheral that counts time in terms of the system clock. It can be programmed to ‘fire’
periodically at given intervals, or after a one-off interval. When a timer ‘fires’ it raises an interrupt
request. The timer is particularly important because it is the principal mechanism used by the
operating system to track the progress of time and allows it to schedule tasks.

(timerState, timerIrq)=timerAction(timerState) Python

1.3 Bare-bones processor model


To gain more insight into the way the processor modifies the system state, we will build a simple processor
model which models how the processor changes the system state at every clock cycle. The purpose of
this model is to make the introduction of the more abstract model in Section 1.4 easier to understand.

1.3.1 What does the processor do?


The processor is a machine to modify the system state. You need to know that …

„„A key feature of a processor is the ability to run arbitrary programs.

„„A program consists of a series of instructions.

5
Operating Systems Foundations with Linux on the Raspberry Pi

„„An instruction determines how the processor interacts with the system through the address space: it can
read values at given addresses, compute new values and addresses, and write values to given addresses.

Note that the program is itself part of the system state. The program running on the processor can control
which part of the entire program code to access. This is what allows us to create an operating system.

1.3.2 Processor internal state: registers


Although in principle, a processor could directly manipulate the system state, this is not practical
because DRAM memory access is quite slow. Therefore, in practice, processors have a dedicated
internal state known as the register file, an array of words called registers which you can consider
as a small but very fast memory. The register file is separate from the rest of the system state (it is
a ‘separate address space’). This means we have to refine our model to separate the register file from
the rest of the system state, which we will call systemState. We do this using a tuple3:

(systemState,irqs,registers) = processorAction(systemState,irqs,registers) Python

For convenience, registers often have names (mnemonics). For example, Figure 1.2 shows the core
AArch32 register set of the Arm Cortex-A53 [2].

There are 16 ordinary registers (and five special ones which we have omitted). Registers R0-R12 are
the ‘General-purpose registers’. Then there are three registers with special names: the Stack Pointer
(SP), the Link Register (LR) and the Program Counter (PC).

R0
R1
R2
R3
Low registers
R4
R5
General-purpose
R6
registers
R7
R8
R9
R10 High registers
R11
R12
Stack Pointer SP (R13)
Link Register LR (R14)
Program Counter PC (R15)

Figure 1.2: Arm Cortex-A53 AArch32 register set.

3
Alternatively, we could make the registers part of the system state similar to the state of the peripherals. Our choice is purely for convenience because it makes it easier
to manipulate the registers in the Python code.

6
Chapter 1 | A Memory-centric system model

1.3.3 Processor instructions


A typical processor can perform a wide range of instructions on memory addresses and/or register
values. We will use a simple list-based notation for all instructions. We will use the (uppercase) Arm
mnemonics for registers and instructions; in Python, these are simply variables; their definitions can
be found in the code repository in file abstract_system_constants.py.

We will assume that all instructions take up to three registers as arguments, for example

add_instr = [ADD,R3,R1,R2] Python

which means that the result of ADD operating on registers R1 and R2 is stored in register R3.

Apart from computational (arithmetic and logic) instructions we also introduce the instructions LDR, e.g.

load_instr = [LDR,R1,R2] Python

and STR, e.g.

store_instr=[STR,R1,R2] Python

which respectively load the content of a memory address stored in R2 into register R1 and store the
content of register R1 at the address location given in R2.

We also have MOV, which copies data between two registers, e.g.

set_instr = [MOV,R1,R2] Python

will set the content of R1 to the content of R2.

We have a special non-Arm instruction called SET, which takes a register and a value as arguments, e.g.

set_instr = [SET,R1,42] Python

will set the content of R1 to 42.

We also need some instructions to control the flow of the program, such as branches (B)

goto_instr = [B,R1] Python

where R1 contains the address of the target instruction in the program, and conditional branches
(CBZ, ‘Compare and Branch if Zero’)

if_instr = [CBZ,R1,R2] Python

where register R1 contains the condition variable (0 or 1) and the program branches to the address in R2 if
R1=0 and continues on the next line otherwise. We also have CBNZ, ‘Compare and Branch if Non-Zero’.

7
Operating Systems Foundations with Linux on the Raspberry Pi

Finally, we have two instructions which take no arguments: NOP does nothing, and WFI stops the
processor until an interrupt occurs.

1.3.4 Assembly language


To write instructions for actual processors, a similar, but more expressive, notation called assembly
language is used. For example, consider the following program that reads two values from memory,
stores them in registers, adds them, and writes the result back:

Listing 1.3.1: Example program Python

1 [
2 [LDR,R1,R4],
3 [LDR,R2,R5],
4 [ADD,R3,R1,R2],
5 [STR,R3,R6]
6 ]

In the assembly language for the Arm processor [3], this code would look as follows:

Listing 1.3.2: Example Arm assembly program Python

1 ldr r1, r4
2 ldr r2, r5
3 add r3, r1, r2
4 str r3, r6

Assembly languages have many other features, such as a rich set of addressing mechanisms, labeling
options, etc. However, for our current purpose, our simple function-based notation is sufficient. For
more details, see, e.g., [4].

1.3.5 Arithmetic logic unit


The part of a processor that performs computations is known as the arithmetic logic unit (ALU).
We can create a simple ALU in Python as follows:

Listing 1.3.3: ALU model Python

1 from operator import *


2
3 alu = [
4 add,
5 sub,
6 mul,
7 ...
8 ]

This is simply an array of functions; more instructions can be added trivially.

1.3.6 Instruction cycle


A processor operates what is known as the instruction cycle or fetch-decode-execute cycle. We can
define each of these operations as follows. First, we define fetchInstruction. This function fetches an

8
Chapter 1 | A Memory-centric system model

instruction from memory. To determine which instruction to fetch, it uses a dedicated register known
as the program counter, which has address PC in our register file. Then we also need to know where in
our memory space, we can find the program code. We use CODE to denote the starting address of the
program in the system state. After reading the instruction, we increment the program counter, so it
points to the next instruction in the program.

Listing 1.3.4: Instruction fetch model Python

1 def fetchInstruction(registers,systemState):
2 # get the program counter
3 pctr = registers[PC]
4 # get the corresponding instruction
5 ir = systemState[CODE+pctr]
6 # increment the program counter
7 registers[PC]+=1
8 return ir

The instruction is stored in the temporary instruction register (ir in our code). The processor now has to
decode this instruction, i.e., extract the register addresses and instruction opcode from the instruction
word. Remember that the state stores unsigned integers, so an instruction is encoded as an unsigned
integer. The details of the implementation can be found in the repository in file abstract_system_cpu_
decode.py. For this discussion, the important point is that the function returns a tuple opcode,args
where args is a tuple containing the decoded arguments (registers, addresses or constants). In the
code, if an element of a tuple is unused, we used _ as variable name to indicate this.

Listing 1.3.5: Instruction decode model Python

1 def decodeInstruction(ir):
2 ...
3 return (opcode,args)

Finally, the processor executes the decoded instruction. In our model, we implement instruction using
a function. The load instruction (mnemonic LDR) is simply an array read operation, store (mnemonic
STR) is simply an array write operation. The B and CBZ branching instructions only modify the program
counter. By using an array of functions alu as discussed above, the ALU execution is very simple too.
The complete code can be found in the repository in file abstract_- system_cpu_execute.py.

Listing 1.3.6: Individual instruction execute model Python

1 def doLDR(registers,systemState,args):
2 (r1,addr,_)=args
3 registers[r1] = systemState[addr]
4 return (registers,systemState)
5
6 def doSTR(registers,systemState,args)
7 (r1,addr,_)=args
8 systemState[addr]=registers[r1]
9 return (registers,systemState)

9
Operating Systems Foundations with Linux on the Raspberry Pi

10
11 def doB(registers,args):
12 (_,addr,_)=args
13 registers[PC] = addr
14 return registers
15
16 def doCBZ(registers,args):
17 (r1,addr1,addr2)=args
18 if registers[r1]:
19 registers[PC] = addr1
20 else:
21 registers[PC] = addr2
22 return registers
23
24 def doALU(instr,registers,args):
25 (r1,r2,r3)=args
26 registers[r3] = alu[instr](registers[r1],registers[r2])
27 return registers

The executeInstruction function simply calls the appropriate handler function via a condition on the
instruction:

Listing 1.3.7: Instruction execute model Python

1 def executeInstruction(instr,args,registers,systemState):
2 if instr==LDR:
3 (registers,systemState)=doLDR(registers,systemState,args)
4 elif instr==STR:
5 (registers,systemState)=doSTR(registers,systemState,args)
6 elif ...
7 else:
8 registers = doALU(instr,registers,args)
9 return (registers,systemState)

1.3.7 Bare bones processor model


With these definitions, we can build a very simple processor model:

Listing 1.3.8: Simple processor model Python

1 def processorAction(systemState,registers):
2 # fetch the instruction
3 ir = fetchInstruction(registers,systemState)
4 # decode the instruction
5 (instr,args) = decodeInstruction(ir)
6 # execute the instruction
7 (registers,systemState)= executeInstruction(instr,args,registers,systemState)
8 return (systemState,registers)

10
Chapter 1 | A Memory-centric system model

In the source code, we have also provided an encodeInstruction in file abstract_system_en- coder.py.
We can encode an instruction using this function, assuming the mnemonics have been defined:

Listing 1.3.9: Instruction encoding Python

1 # multiply value in R1 with value in R2


2 # store result in R3
3 instr=[MUL,R3,R1,R2]
4
5 iw=encodeInstruction(instr)

Now you can run this as follows:

Listing 1.3.10: Running the code Python

1 # Set the program counter relative to the location of the code


2 registers[PC]=0
3 # Set the registers
4 registers[R1]=6
5 registers[R2]=7
6
7 # Store the encoded instructions in memory
8 systemState[CODE] = iw
9
10 # Now run this
11 (systemState,registers) = processorAction(systemState,registers)
12
13 # Inspect the result
14 print( registers[R3] )
15 # prints 42

You can find the complete Python code for this bare-bones model in the folder bare-bones-model,
have a look and try it out. The file to run is bare-bones-model/abstract_- system_model.py.

1.4 Advanced processor model


The bare-bones model is missing a number of features that are essential to support an operating
system; in this section, we introduce these features and add them to the model.

1.4.1 Stack support


A stack is a contiguous block of memory that is accessed in LIFO (last in, first out) fashion. Data is
added to the top of the stack using a ‘push’ operation and taken from the top of stack using a ‘pop’
operation. Stacks are used to store temporary data, and they are commonly used to handle function
calls. Most computer architectures include at least a register that is usually reserved for the stack
pointer (e.g., as we have seen the Arm processor has a dedicated ‘SP’ register) as well as ‘PUSH’ and
‘POP’ instructions to access the stack. In our model, we will implement the stack as part of the RAM
memory, and we define the push and pop instructions as in the Arm instruction set, for example:

11
Operating Systems Foundations with Linux on the Raspberry Pi

Listing 1.4.1: Example stack instructions Python

1 push_pop=[
2 [PUSH,R1],
3 [POP,R2]
4 ]

would push the content of R1 onto the stack and then pop it into R2. The PUSH and POP instructions
are encoded similar to the LDR and STR memory operations. We extend the executeInstruction
definition to support the stack with the following functions:

Listing 1.4.2: Push/pop implementation Python

1 def doPush(registers,systemState,args):
2 sptr = registers[SP]
3 (r1,_,_)=args
4 systemState[sptr]=registers[r1]
5 registers[SP]+=1
6 return (registers,systemState)
7
8 def doPop(registers,systemState,args):
9 sptr = registers[SP]
10 (r1,_,_)=args
11 registers[r1] = systemState[sptr]
12 registers[SP]-=1
13 return (registers,systemState)

1.4.2 Subroutine calls


One of the main reasons for having a stack is so that the processor can handle subroutine calls, and
in particular, subroutines that call other subroutines or call themselves (recursive call). This is because
whenever we call a subroutine, the code in the subroutine will overwrite the register file, so we need
to store the registers somewhere before we call a subroutine.

To support this mechanism, most processors have instructions to change the control flow: a first
instruction, the call instruction changes the program counter to the location of the subroutine to be called.
A second instruction, the return instruction, returns the location after the subroutine call instruction.
These instructions can use either the stack or a dedicated register to save the program counter.

In the Arm 32-bit instruction set the call and return instructions are usually implemented using BL and
BX; the Arm convention is to store the return address in the link register LR, and we will use the same
convention in our model. We extend the executeInstruction definition to support subroutine call and
return as follows:

Listing 1.4.3: Call/return implementation Python

1 def doCall(registers,args):
2 pctr = registers[PC]
3 (_,sraddr,_)=args
4 registers[LR] = pctr
5 registers[PC]=sraddr

12
Chapter 1 | A Memory-centric system model

6 return registers
7
8 def doReturn(registers,args):
9 lreg = registers[LR]
10 registers[PC]=lreg
11 return registers

1.4.3 Interrupt handling


Now let’s extend the processor model to support interrupts. When the processor receives an interrupt
request, it must take some specific actions. These actions are simply special small programs called
interrupt handlers or interrupt service routines (ISR). The processor uses a region of the main memory
called the interrupt vector table (IVT) to link the interrupt requests to interrupt handlers.

How does the processor handle interrupts? On every clock tick (i.e., on every call to processorAction
in our model), if an interrupt was raised, the processor has to run the corresponding ISR. In our model,
this means the processor needs to inspect irqs, get the corresponding ISR from the ivt (which in our
model is a slice of the systemState array), and execute it. So in fact, the call to the ISR is a normal
subroutine call, but one that does not have a corresponding CALL instruction in the code. Before
executing the ISR, the processor typically stores some register values on the stack, e.g., the Arm
Cortex-M3 stores R0-R3, R12, PC, and LR [5]. According to the Arm Architecture Procedure Call
Standard [6], the called subroutine is responsible for storing R4-R11. In our simple model, we only
store the PC, extending it to support the AAPCS is a trivial exercise.

Listing 1.4.4: Interrupt handling Python

1 def checkIrqs(registers,ivt,irqs):
2 idx=0
3 for irq in irqs:
4 if irq :
5 # Save the program counter in the link register
6 registers[LR] = registers[PC]
7 # Set program counter to ISR start address
8 registers[PC]=ivt[idx]
9 # Clear the interrupt request
10 irqs[idx]=False
11 break
12 idx+=1
13 return (registers,irqs)

1.4.4 Direct memory access


Another important component of a modern processor-based system is support for Direct Memory Access
(DMA). This is a mechanism that allows peripherals to transfer data directly into the main memory without
going through the processor registers. In Arm systems, the DMA controller unit is typically a peripheral
(e.g., the PrimeCell DMA Controller), so we will implement our DMA model as a peripheral as well.

The principle of a DMA transfer is that the CPU initiates the transfer by writing to the DMA unit’s
registers, then runs other instructions while the transfer is in progress, and finally receives an interrupt
from the DMA controller when the transfer is done.
13
Operating Systems Foundations with Linux on the Raspberry Pi

Typically, a DMA transfer is a transfer of a large block of data, which would otherwise keep the
processor occupied for a long time. In our simple model, the DMA controller has four registers:

„„Source Address Register (DSR)

„„Destination Address Register (DDR)

„„Counter (DCO)

„„Control Register (DCR)

This peripheral is different from the others in our model because it can manipulate the entire system
state. In a way, we can view a DMA controller as a special type of processor that only performs
memory transfer operations. The model implementation is:

Listing 1.4.5: DMA model Python

1 def dmaAction(systemState):
2 dmaIrq=0
3 # DMA is the start of the address space
4 # DCR values: 1 = do transfer, 0 = idle
5 if systemState[DMA+DCR]!=0:
6 if systemState[DMA+DCO]!=0:
7 ctr = systemState[DMA+DCO]
8 to_addr = systemState[DMA+DDR]+ctr
9 from_addr = systemState[DMA+DSR]+ctr
10 systemState[to_addr] = systemState[from_addr]
11 systemState[DMA+DCO]=-1
12 systemState[DMA+DCR]=0
13 dmaIrq=1
14 return (systemState,dmaIrq)

To initiate a memory transfer using the DMA controller, the processor writes the source and destination
addresses to DSR and DDR, and the size of the transfer to DCO (the ‘counter’). Then the status is set to
1 in the DCR. The DMA controller then starts the transfer and decrements the counter for every word
transferred. When the counter reaches zero, an interrupt is raised (count-zero interrupt).

1.4.5 Complete cycle-based processor model


By including this interrupt support, the complete cycle-based processor model now becomes:

Listing 1.4.6: Complete cycle-based processor model Python

1 def processorAction(systemState,irqs,registers):
2 ivt = systemState[IVT:IVTsz]
3 # Check for interrupts
4 (registers,irqs)=checkIrqs(registers,ivt,irqs)
5 # Fetch the instruction
6 ir = fetchInstruction(registers,systemState)
7 # Decode the instruction
8 (instr,args) = decodeInstruction(ir)
9 # Execute the instruction
10 (registers,systemState)= executeInstruction(instr,args,registers,systemState)
11 return (systemState,irqs,registers)

14
Chapter 1 | A Memory-centric system model

1.4.6 Caching
In an actual system, accessing DRAM memory requires many clock cycles. To limit the time spent in
waiting for memory access, processors have a cache, a small but fast memory. For every memory read
operation, first the processor checks if the data is present in the cache, and if so (this is called a ‘cache
hit’) it uses that data rather than accessing the DRAM. Otherwise (‘cache miss’) it will fetch the data
from memory and store it in the cache.

For a single-core processor, memory write operations are treated in the same way. Real-life caches are very
complicated and will be discussed in more detail in Chapters 3 ‘Hardware architecture’ and 6 ‘Memory
management’. Here we will create a simple conceptual model of a cache to illustrate the key points.

First of all, as a cache is limited in size, how do we store portions of the DRAM content in it? Like the
other memories, we will model the storage part of the cache as an array of fixed size. So if we want to
store some data in the cache, we find a free location and copy the data into it. At some point, the data
will be removed from the cache, freeing up this location. So we need a data structure, e.g., a stack to
keep track of the free locations.

So what happens when the cache is full (so the stack is empty)? We need to free up space by evicting data
from the cache. As we will see in Chapter 6 ‘Memory management’, there are several different policies
to do this. The simplest one (but certainly not the best one) is to evict data from the most recently used
location because all it requires is that we keep track of that single location. When we evict data from the
cache, it needs to be written back to the DRAM memory. Conversely, the data that we put into the cache
was read from an address location in the DRAM memory. Therefore the cache must not only keep track
of the data but also of its original address. In other words, we need a lookup between the address in the
DRAM and the corresponding address in the cache. In Python, we can use a dictionary for this, a data
structure that associates keys with values. A cache which behaves like a dictionary – in that it allows us to
store any memory address at any cache location – is called ‘fully associative’.

In Python, we can write such a cache model as follows:

Listing 1.4.7: Cache model: initialization and helper functions Python

1 # Initialise the cache


2 def init_cache():
3 # Cache of size CACHE_SZ
4 cache_storage=[]
5 location_stack_storage=range(0,CACHE_SZ)
6 location_stack_ptr=CACHE_SZ-1
7 last_used_loc = location_stack[location_stack_ptr]
8 location_stack = (location_stack_storage,location_stack_ptr,last_used_loc)
9 address_to_cache_loc={}
10 cache_loc_to_address={}
11 cache_lookup=(address_to_cache_loc,cache_loc_to_address)
12 cache = (cache_storage, address_to_cache_loc,cache_loc_to_address,location_stac
13 return cache
14
15 # Some helper functions
16 def get_next_free_location(location_stack):
17 (location_stack_storage,location_stack_ptr,last_used_loc) = location_stack
18 loc = location_stack_storage[location_stack_ptr]
19 location_stack_ptr-=1

15
Operating Systems Foundations with Linux on the Raspberry Pi

20 location_stack = (location_stack_storage,location_stack_ptr,last_used_loc)
21 return (location,location_stack)
22
23 def evict_location(location_stack):
24 (location_stack_storage,location_stack_ptr,last_used_loc) = location_stack
25 location_stack_ptr+=1
26 location_stack[location_stack_ptr] = last_used
27 location_stack = (location_stack_storage,location_stack_ptr,last_used_loc)
28 return location_stack
29
30 def cache_is_full(location_stack_ptr):
31 if location_stack_ptr==0
32 return True
33 else
34 return False

Listing 1.4.8: Cache model: cache read and write functions Python

1 def write_data_to_cache(memory, address, cache):


2 (cache_storage, address_to_cache_loc,cache_loc_to_address, location_stack) = cache
3 (location_stack_storage,location_stack_ptr,last_used_loc) = location_stack
4 # If the cache was full, evict first
5 if cache_is_full(location_stack_ptr):
6 location_stack = evict_location(location_stack)
7 evicted_address = cache_loc_to_address[last_used]
8 memory[evicted_address]=cache_storage[last_used]
9 # Get a free location.
10 (loc,location_stack) = get_next_free_location(location_stack)
11 # Get the DRAM content and write it to the cache storage
12 data = memory[address]
13 cache_storage[loc] = data
14 # Update the lookup table and the last used location
15 address_to_cache_loc[address]=loc
16 cache_loc_to_address[loc] = address
17 last_used=loc
18 location_stack = (location_stack_storage,location_stack_ptr,last_used_loc)
19 cache = (cache_storage,address_to_cache_loc,cache_loc_to_address,location_stack)
20 return (memory,cache)
21
22 def read_data_from_cache(memory,address,cache):
23 (cache_storage, address_to_cache_loc,cache_loc_to_address,location_stack) = cache
24 location_stack = evict_location(location_stack)
25 # If the data is not yet in the cache, fetch it from the DRAM
26 # Note this may result in eviction, which could modify the memory
27 if address not in address_to_cache_loc:
28 (memory, cache) = write_data_to_cache(memory,address,cache):
29 # Get the data from the cache
30 loc = address_to_cache_loc[address]
31 data = cache_storage[loc]
32 cache = (cache_storage, address_to_cache_loc,cache_loc_to_address, location_stack)
33 return (data,memory,cache)

The problem with the above model is that for a cache of a given size, we need a location stack and two
lookup tables of the same size. This requires a lot of silicon. Therefore, in practice, the cache will not
simply fetch the content of a single memory address, but a contiguous block of memory called a cache
line. For example, the Arm Cortex-A53 has a 64-byte cache line. Assuming that our memory stores 32-
bit words, then the size of the location stack and lookup tables is 16x smaller than the actual cache size.
16
Chapter 1 | A Memory-centric system model

There is another reason for the use of cache lines: when a given address is accessed, subsequent
memory accesses are frequently to neighboring addresses. So fetching an entire cache line on a cache
miss tends to reduce the number of subsequent cache misses. Adapting our model to use cache lines
is straightforward:

Listing 1.4.9: Cache model with cache lines Python

1 # Initialise the cache


2 def init_cache():
3 # Cache of size CACHE_SZ, cache line = 64 bytes = 16 words
4 cache_storage=[[0]*16]*(CACHE_SZ/16)
5 location_stack_storage=range(0,CACHE_SZ/16)
6 location_stack_ptr=(CACHE_SZ/16)-1
7 last_used_loc = location_stack[location_stack_ptr]
8 location_stack = (location_stack_storage,location_stack_ptr,last_used_loc)
9 address_to_cache_loc={}
10 cache_loc_to_address={}
11 cache_lookup=(address_to_cache_loc,cache_loc_to_address)
12 cache = (cache_storage,address_to_cache_loc,cache_loc_to_address,location_stack)
13 return cache
14
15 # The helper functions remain the same
16
17 def write_data_to_cache(memory,address,cache):
18 (cache_storage, address_to_cache_loc,cache_loc_to_address,location_stack) = cache
19 (location_stack_storage,location_stack_ptr,last_used_loc) = location_stack
20 # If the cache was full, evict first
21 if cache_is_full(location_stack_ptr):
22 location_stack = evict_location(location_stack)
23 evicted_address = cache_loc_to_address[last_used]
24 cache_line = cache_storage[last_used]
25 for i in range(0,16):
26 data = cache_line[i]
27 memory[(evicted_address<<4) + i]=data
28 # Get a free location.
29 (loc,location_stack) = get_next_free_location(location_stack)
30 # Get the DRAM content and write it to the cache storage
31 cache_line = []
32 for i in range(0,16):
33 cache_line.append(memory[((address>>4)<<4)+i]
34 cache_storage[loc] = cache_line
35 # Update the lookup table and the last used location
36 address_to_cache_loc[address>>4]=loc
37 cache_loc_to_address[loc] = address>>4
38 last_used=loc
39 location_stack = (location_stack_storage,location_stack_ptr,last_used_loc)
40 cache = (cache_storage,address_to_cache_loc,cache_loc_to_address,location_stack)
41 return (memory,cache)
42
43 def read_data_from_cache(memory,address,cache):
44 (cache_storage,address_to_cache_loc,cache_loc_to_address,location_stack) = cache
45 location_stack = evict_location(location_stack)
46 # If the data is not yet in the cache, fetch it from the DRAM
47 # Note this may result in eviction, which could modify the memory
48 if address not in address_to_cache_loc:
49 (memory,cache) = write_data_to_cache(memory,address,cache):
50 # Get the data from the cache
51 loc = address_to_cache_loc[address>>4]
52 cache_line = cache_storage[loc]
53 data = cache_line[addres & 0xF]
54 cache = (cache_storage,address_to_cache_loc,cache_loc_to_address,location_stack)
55 return (data,memory,cache)

17
Operating Systems Foundations with Linux on the Raspberry Pi

The only complication in the cache line-based model is that we need to manipulate the memory
address to determine the start of the cache line and the location of the data inside the cache line. Do
this using bit shift and bit mask operations: the first 4bits of the address identify the position of the
data in the cache line. We don’t need to store these bits in the lookup tables of the cache because the
cache stores only whole cache lines. In other words, from the perspective of the cache, the memory
consists of cache lines rather than individual locations. So we have the following formulas:

data_position_in_cache line = address & 0xF


cache_line_address = address >> 4

address = (cache_line_address << 4) + data_position_in_cache line

1.4.7 Running a program on the processor


The processor model is complete and can run arbitrary programs. For example, the following program
generates the first 10 Fibonacci numbers greater than 1 and writes them to main memory:

Listing 1.4.10: Fibonacci code Python

1 fib_prog=[
2 [SET,R1,1],
3 [SET,R2,1],
4 [SET,R3,0],
5 [SET,R4,10],
6 [SET,R5,1],
7 (‘loop’,[ADD,R3,R1,R2]),
8 [MOV,R1,R2],
9 [MOV,R2,R3],
10 [SUB,R4,R4,R5],
11 [STR,R3,R4],
12 [CBNZ,R4,’loop’],
13 [WFI]
14 ]

Note: the encodeProgram function from abstract_model_encoder.py supports strings as labels for
instructions as shown above. Similar to Arm assembly language, the instructions CBZ, CBNZ, ADR, BL,
and B actually take labels rather than explicit addresses.

To run this program, we need to encode it, load it into memory, and ensure that the program counter
points to the start of code in the memory:

Listing 1.4.11: Running a program on the processor Python

1 # Encode the program


2 fib_iws=encodeProgram(fib_prog)
3
4 # Write the program to RAM memory
5 pc=0
6 for iw in fib_iws:
7 ramState[CODE+pc] = iw
8 pc+=1
9
10 # Initialise the processor state
11 registers[PC]=CODE

18
Chapter 1 | A Memory-centric system model

12
13 # Run the system for a given number of cycles
14 MAX_NCYCLES=50
15 for ncycles in range(1,MAX_NCYCLES):
16 # Run the peripheral actions
17 (kbdState,kbdIrq)=kbdAction(kbdState)
18 (nicState,nicIrq)=nicAction(nicState)
19 (ssdState,ssdIrq)=ssdAction(ssdState)
20 (gpuState,gpuIrq)=gpuAction(gpuState)
21 (systemState,dmaIrq)=dmaAction(systemState)
22
23 # The RAM does not have any action,
24 # it is just a slice of the full address space
25 ramState=systemState[0:MEMTOP]
26 # Collect the IRQs
27 irqs=[kbdIrq,nicIrq,ssdIrq,gpuIrq,dmaIrq]
28 # Compose the system state
29 systemState = ramState+timerState+kbdState+nicState+ssdState+gpuState+dmaState
30 # Run the processor action
31 (systemState,irqs,registers) = processorAction(systemState,irqs,registers)
32
33 # Print the portion of memory that holds the results
34 print(systemState[0:10])

1.4.8 High-level instructions


The model introduced in the previous section is cycle-based, i.e., it models all actions and state
changes on a cycle-by-cycle, instruction-by-instruction basis. To simplify the explanations in what
follows and to speed up the execution of the model code, we add support for direct execution of high-
level Python code using the HLI instruction. This allows us to work at a higher level of abstraction,
while still preserving the low-level features of the system that are used by the operating system.

The previous model required us to write individual instructions and encode them. The HLI
instruction allows us to use Python functions that will replace groups of instructions, as follows:

Listing 1.4.12: Multi- instruction action Python

1 def multi_instruction_action( systemState,registers ):


2 .... (arbitrary Python code) ...
3 return ( systemState,registers )
4
5 hli_prog = [...,
6 [HLI,multi_instruction_action],
7 ...
8 ]

To execute such functions in the processor, we add the doHLI function to the executeInstrucion code:

Listing 1.4.13: Adapting push for high-level instructions Python

1 def doHLI(registers,systemState,args)
2 (hl_instr,_,_)=args
3 (systemState,registers) = hl_instr(systemState,registers)
4 return (registers,systemState)

19
Operating Systems Foundations with Linux on the Raspberry Pi

To illustrate the approach, the Fibonacci example from the previous section could become a single HLI
instruction:

Listing 1.4.14: Fibonacci with high-level instructions Python

1 def fib_hl( systemState,registers ):


2 (r1,r2,r4)=(1,1,10)
3 while r4!=0:
4 r3=r1+r2
5 r1=r2
6 r2=r3
7 r4-=1
8 systemState[r4]=r3
9 registers[1:5]=[r1,r2,r3,r4]
10 return ( systemState,registers )

The key point is that the functions manipulate the system state and registers in the same way as the
individual instructions did.

1.5 Basic operating system concepts


In this section, we use the abstract system model to introduce a number of fundamental operating
system concepts that will be discussed in detail in the following chapters.

1.5.1 Tasks and concurrency


One of the main tasks of an operating system is to support multiple tasks at the same time
(‘concurrently’). If there is only one processor, it means that the code that implements these tasks must
time-share the processor. Let us assume that we have two programs in memory and we want to run
them concurrently so that each running program is a single task, Task 1 and Task 2.

We have seen in Section 1.4.7 how we run a program: set the program counter to the starting address,
then the fetch-decode-execute cycle will execute each instruction on subsequent clock ticks until the
program is finished.

Now we want to run two programs at the same time. Therefore, we will need a mechanism to run
instructions of each program alternatingly. This mechanism translates to managing the state. As we
have seen before, the state of a running program consists in principle of the complete system state.
In practice, each program should have its own section of memory, as we don’t want one program to
modify the memory of another program.

We start, therefore, by assuming that when the program code is loaded into memory, it is part of
a region of memory that the program is allowed to use when it is running. We will see in Chapter 6
‘Memory management’ that this is indeed the case in Linux. As shown in Figure 1.3, this region (called
‘user space’) contains the program code, the stack for the program and the random-access memory
for the program, commonly known as the ‘heap’. Typically, each task gets a fixed amount of memory
allocated to it, and in the code, this memory is referenced relative to the program counter.

20
Chapter 1 | A Memory-centric system model

4 GB
kernel space

3 GB
user space

stack

heap

program code
0 GB
Figure 1.3: Task memory space (Linux).

1.5.2 The register file


However, as we have seen, the processor also has some state, namely the register file. So if we want
to run two tasks alternately, we need to ensure that the register file contains the correct state for each
task. So conceptually, we can store a snapshot of the register file contents for Task 1, then load the
previous snapshot of the register file contents for Task 2.

1.5.3 Time slicing and scheduling


So how can we make two tasks alternate? The code to do this will be the core of our Operating System
kernel and is called a ‘task scheduler’ or scheduler for short. Let’s assume we will simply alternate two
(or more) tasks for fixed amounts of time (this is called ‘round-robin scheduling’). For example, on the
Raspberry Pi 3, the Linux real-time scheduler uses an interval (also called ‘time slice’ or ‘quantum’) of
10 ms. For comparison, the average duration of an eye blink is 100 ms. Note that at a typical clock
speed of 1 GHz, this means a task can execute 10 million (single-cycle) instructions in this time.

The duration of a time slice is controlled by a system timer. As we have seen before, a timer can be
configured to fire periodically, so in our case, the system timer will raise an interrupt request every
10 ms. On receiving this request, the processor will execute the corresponding Interrupt Service
Routine (ISR). It is this ISR that will take care of the time slicing; in other words, the interrupt service
routine is actually our operating system kernel.

In the Python model, the timer peripheral has a register to store the interval and a control register.
We can set the timer as follows:

Listing 1.5.1: Timer Python

1 # Set timer to periodic with 100-ticks interval


2 set_timer=[
3 [SET,R1,100],
4 [SET,R2,100], # start periodic timer
5 [STR,R1,TIMER],
6 [STR,R2,TIMER+1]
7 ]

21
Operating Systems Foundations with Linux on the Raspberry Pi

On running this program, the timer will fire every 100 clock ticks and raise an interrupt request. Let’s
have a look at the interrupt handler. What should this routine do to achieve time slicing between two
tasks? Let’s assume Task 1 has been running and we now want to run Task 2.

„„First, save the register file for Task 1, we do this by pushing all register contents onto the stack.
(If you spot an issue here, well done! We’ll get back to this in Section 1.5.4.)

„„Then determine which task has to be run next (i.e., Task 2). We can identify each task using a small
integer (the ‘task identifier’) that we store in the memory accessible by the kernel. We load the task
identifier for Task 2 into a register and update the memory with the task identifier for the next task
(in our case, again Task 1).

„„We now move the register file of Task 1 from the stack to kernel memory. In practice, the kernel
uses a special data structure, the Task Control Block (TCB), for this purpose.

„„Now we can read the register file contents for Task 2 from its TCB. Again, we have to do this via the
stack (why?).

„„Once this is done, Task 2 will start running from the location indicated by PC and run until the next
timer interrupt.

We can express this sequence of actions in high-level Python code for our processor model:

Listing 1.5.2: Time slicing model Python

1 def time_slice(systemState,registers ):
2 # Push registers onto the stack
3 for r in range(0,16):
4 systemState[registers[MSP]]]=registers[r]
5 registers[MSP]+=1
6 # Get next task
7 pid1 = systemState[PID] # 0 or 1
8 pid2 = 1-pid1
9 systemState[PID]=pid2
10 tcb1= TCB_OFFSET+pid1*TCB_SZ
11 tcb2= TCB_OFFSET+pid2*TCB_SZ
12 # Pop registers from stack and store to tcb1
13 # We use r0 to show that in actual code we’d need to read into a tempory register
14 for r in range(0,16):
15 r0=systemState[registers[MSP]]
16 systemState[tcb1+r]=r0
17 registers[MSP]-=1
18 # Push registers for Task 2 from tcb2 onto stack
19 for r in range(0,16):
20 r0=systemState[tcb2+r]
21 systemState[registers[MSP]]=r0
22 registers[MSP]+=1
23 # Pop registers for Task 2 from stack
24 for r in range(0,16):
25 registers[r]=systemState[registers[MSP]]
26 registers[MSP]-=1

This code is a minimal example of a round-robin scheduler for two tasks.

22
Other documents randomly have
different content
30
KING ARTHUR AND KING
CORNWALL
Percy MS., p. 24. Hales & Furnivall, I, 61; Madden's Syr
Gawayne, p. 275.
The mutilation of the earlier pages of the Percy manuscript leaves us
in possession of only one half of this ballad, and that half in eight
fragments, so that even the outline of the story cannot be fully made
out.[259] We have, to be sure, the whole of a French poem which
must be regarded as the probable source of the ballad, and, in view
of the recklessness of the destroyer Time, may take comfort; for
there are few things in this kind that the Middle Ages have
bequeathed which we could not better spare. But the losses from
the English ballad are still very regrettable, since from what is in our
hands we can see that the story was treated in an original way, and
so much so that comparison does not stead us materially.
'King Arthur and King Cornwall' is apparently an imitation, or a
traditional variation, of Charlemagne's Journey to Jerusalem and
Constantinople, a chanson de geste of complete individuality and of
remarkable interest. This all but incomparable relic exists in only a
single manuscript,[260] and that ill written and not older than the
end of the thirteenth century, while the poem itself may be assigned
to the beginning of the twelfth, if not to the latter part of the
eleventh.[261] Subsequently, the story, with modifications, was
introduced into the romance of Galien, and in this setting it occurs in
three forms, two manuscript of the fifteenth century, and the third a
printed edition of the date 1500. These are all in prose, but betray
by metrical remains imbedded in them their descent from a romance
in verse, which there are reasons for putting at least as early as the
beginning of the fourteenth century.[262]
A very little of the story, and this little much changed, is found in
Italian romances of Charles's Journey to Spain and of Ogier the
Dane. The derivation from Galien is patent.[263]
The Journey of Charlemagne achieved great popularity, as it needs
must. It forms a section of the Karlamagnus Saga, a prose
translation into Norse of gestes of Charles and his peers, made in
the thirteenth century, and probably for King Hákon the Old, though
this is not expressly said, as in the case of the 'Mantle.' Through the
Norwegian version the story of Charles's journey passed into the
other Scandinavian dialects. There is a Swedish version, slightly
defective, existing in a manuscript earlier than 1450, and known to
be older than the manuscript, and a Danish abridgment, thought to
have been made from the Swedish version, is preserved in a
manuscript dated 1480, which again is probably derived from an
elder. Like the 'Mantle,' the Journey of Charlemagne is treated in
Icelandic Rímur, the oldest manuscript being put at about 1500.
These Rhymes (Geiplur, Gabs, Japes), though their basis is the
Norwegian saga, present variations from the existing manuscripts of
this saga. There is also a Färöe traditional ballad upon this theme,
'Geipa-táttur.' This ballad has much that is peculiar to itself.[264]
Charlemagne's Journey was also turned into Welsh in the thirteenth
century. Three versions are known, of which the best is in the Red
Book of Hergest.[265]
Let us now see what is narrated in the French poem.
One day when Charlemagne was at St Denis he had put on his
crown and sword, and his wife had on a most beautiful crown, too.
Charles took her by the hand, under an olive-tree, and asked her if
she had ever seen a king to whom crown and sword were so
becoming. The empress was so unwise as to reply that possibly he
thought too well of himself: she knew of a king who appeared to
even better advantage when he wore his crown. Charles angrily
demanded where this king was to be found: they would wear their
crowns together, and if the French sided with her, well; but if she
had not spoken truth, he would cut off her head. The empress
endeavored to explain away what she had said: the other king was
simply richer, but not so good a knight, etc. Charles bade her name
him, on her head. There being no escape, the empress said she had
heard much of Hugo, the emperor of Greece and Constantinople.
"By my faith," said Charles, "you have made me angry and lost my
love, and are in a fair way to lose your head, too. I will never rest till
I have seen this king."

The emperor, having made his offering at St Denis, returned to Paris,


taking with him his twelve peers and some thousand of knights. To
these he announced that they were to accompany him to Jerusalem,
to adore the cross and the sepulchre, and that he would incidentally
look up a king that he had heard of. They were to take with them
seven hundred camels, laden with gold and silver, and be prepared
for an absence of seven years.
Charlemagne gave his people a handsome equipment, but not of
arms. They left behind them their lances and swords, and took the
pilgrim's staff and scrip. When they came to a great plain it
appeared that the number was not less than eighty thousand: but
we do not have to drag this host through the story, which concerns
itself only with Charles and his peers. They arrived at Jerusalem one
fine day, selected their inns, and went to the minster. Here Jesus and
his apostles had sung mass, and the chairs which they had occupied
were still there. Charles seated himself in the middle one, his peers
on either side. A Jew came in, and, seeing Charles, fell to trembling;
so fierce was the countenance of the emperor that he dared not look
at it, but fled from the church to the patriarch, and begged to be
baptized, for God himself and the twelve apostles were come. The
patriarch went to the church, in procession, with his clergy. Charles
rose and made a profound salutation, the priest and the monarch
embraced, and the patriarch inquired who it was that had assumed
to enter that church as he had done. "Charles is my name," was the
answer. "Twelve kings have I conquered, and I am seeking a
thirteenth whom I have heard of. I have come to Jerusalem to adore
the cross and the sepulchre." The patriarch proving gracious, Charles
went on to ask for relics to take home with him. "A plentet en
avrez," says the patriarch; "St Simeon's arm, St Lazarus's head, St
Stephen's—" "Thanks!" "The sudarium, one of the nails, the crown
of thorns, the cup, the dish, the knife, some of St Peter's beard,
some hairs from his head—" "Thanks!" "Some of Mary's milk, of the
holy shift—" And all these Charles received.[266] He stayed four
months in Jerusalem, and began the church of St Mary. He
presented the patriarch with a hundred mule-loads of gold and silver,
and asked "his leave and pardon" to return to France: but first he
would find out the king whom his wife had praised. They take the
way through Jericho to gather palms. The relics are so strong that
every stream they come to divides before them, every blind man
receives sight, the crooked are made straight, and the dumb speak.
[267] On reaching Constantinople they have ample reason to be
impressed with the magnificence of the place. Passing twenty
thousand knights, who are playing at chess and tables, dressed in
pall and ermine, with fur cloaks training at their feet, and three
thousand damsels in equally sumptuous attire, who are disporting
with their lovers, they come to the king, who is at that moment
taking his day at the plough, not on foot, goad in hand, but seated
most splendidly in a chair drawn by mules, and holding a gold wand,
the plough all gold, too; none of this elegance, however, impairing
the straightness of his majesty's furrow. The kings exchange
greetings. Charles tells Hugo that he is last from Jerusalem, and
should be glad to see him and his knights. Hugo makes him free to
stay a year, if he likes, unyokes the oxen, and conducts his guests to
the palace.
The palace is gorgeous in the extreme, and, omitting other
architectural details, it is circular, and so constructed as to turn like a
wheel when the wind strikes it from the west. Charles thinks his own
wealth not worth a glove in comparison, and remembers how he had
threatened his wife. "Lordings," he says, "many a palace have I
seen, but none like this had even Alexander, Constantine, or Cæsar."
At that moment a strong wind arose which set the palace in lively
motion; the emperor was fain to sit down on the floor; the twelve
peers were all upset, and as they lay on their backs, with faces
covered, said one to the other, "This is a bad business: the doors are
open, and yet we can't get out!" But as evening approached the
wind subsided; the Franks recovered their legs, and went to supper.
At the table they saw the queen and the princess, a beautiful
blonde, of whom Oliver became at once enamored. After a most
royal repast, the king conducted Charles and the twelve to a bed-
chamber, in which there were thirteen beds. It is doubtful whether
modern luxury can vie with the appointments in any respect, and
certain that we are hopelessly behind in one, for this room was
lighted by a carbuncle. But, again, there was one luxury which Hugo
did not allow them, and this was privacy, even so much privacy as
thirteen can have. He had put a man in a hollow place under a
marble stair, to watch them through a little hole.
The Franks, as it appears later, had drunk heavily at supper, and this
must be their excuse for giving themselves over, when in a foreign
country, to a usage or propensity which they had no doubt indulged
in at home, and which is familiar in northern poetry and saga, that
of making brags (gabs, Anglo-Saxon beót, gilp[268]). Charles began:
Let Hugo arm his best man in two hauberks and two helms, and set
him on a charger: then, if he will lend me his sword, I will with a
blow cut through helms, hauberks, and saddle, and if I let it have its
course, the blade shall never be recovered but by digging a spear's
depth in the ground. "Perdy," says the man in hiding, "what a fool
King Hugo was when he gave you lodging!"
Roland followed: Tell Hugo to lend me his horn, and I will go into
yon plain and blow such a blast that not a gate or a door in all the
city shall be left standing, and a good man Hugo will be, if he faces
me, not to have his beard burned from his face and his fur robe
carried away. Again said the man under the stair, "What a fool was
King Hugo!"
The emperor next called upon Oliver, whose gab was:
'Prenget li reis sa fille qui tant at bloi le peil,
En sa chambre nos metet en un lit en requeit;
Se jo n'ai testimoigne de li anuit cent feiz,
Demain perde la teste, par covent li otrei.'

"You will stop before that," said the spy; "great shame have you
spoken."
Archbishop Turpin's brag was next in order: it would have been more
in keeping for Turpin of Hounslow Heath, and we have all seen it
performed in the travelling circus. While three of the king's best
horses are running at full speed on the plain, he will overtake and
mount the foremost, passing the others, and will keep four big
apples in constant motion from one hand to the other; if he lets one
fall, put out his eyes.[269] "A good brag this," is the comment of the
simple scout (l'escolte), "and no shame to my lord."
William of Orange will take in one hand a metal ball which thirty men
have never been able to stir, and will hurl it at the palace wall and
bring down more than forty toises of it. "The king is a knave if he
does not make you try," says l'escolte.
The other eight gabs may be passed over, save one. Bernard de
Brusban says, "You see that roaring stream? To-morrow I will make
it leave its bed, cover the fields, fill the cellars of the city, drench the
people, and drive King Hugo into his highest tower, from which he
shall never come down without my leave." "The man is mad," says
the spy. "What a fool King Hugo was! As soon as morning dawns
they shall all pack."
The spy carries his report to his master without a moment's delay.
Hugo swears that if the brags are not accomplished as made, his
guests shall lose their heads, and orders out a hundred thousand
men-at-arms to enforce his resolution.
When the devout emperor of the west came from mass the next
morning (Hugo was evidently not in a state of mind to go), he
advanced to meet his brother of Constantinople, olive branch in
hand; but Hugo called out from far off, "Charles, why did you make
me the butt of your brags and your scorns?" and repeated that all
must be done, or thirteen heads would fall. Charles replied that they
had drunk a good deal of wine the night before, and that it was the
custom for the French when they had gone to bed to allow
themselves in jesting. He desired to speak with his knights. When
they were together, the emperor said that they had drunk too much,
and had uttered what they ought not. He caused the relics to be
brought, and they all fell to praying and beating their breasts, that
they might be saved from Hugo's wrath, when lo, an angel
appeared, who bade them not be afraid; they had committed a great
folly yesterday, and must never brag again, but for this time, "Go,
begin, not one of them shall fail."[270]
Charles returned to Hugo master of the situation. He repeated that
they had drunk too much wine the night before, and went on to say
that it was an outrage on Hugo's part to set a spy in the room, and
that they knew a land where such an act would be accounted
villainy: "but all shall be carried out; choose who shall begin." Hugo
said, Oliver; and let him not fall short of his boast, or I will cut off his
head, and the other twelve shall share his fate. The next morning, in
pursuance of an arrangement made between Oliver and the
princess, the king was informed that what had been undertaken had
been precisely discharged. "The first has saved himself," says Hugo;
"by magic, I believe; now I wish to know about the rest." "What
next?" says Charlemagne. William of Orange was called for, threw off
his furs, lifted the huge ball with one hand, hurled it at the wall, and
threw down more than forty toises. "They are enchanters," said the
king to his men. "Now I should like to see if the rest will do as much.
If one of them fails, I will hang them all to-morrow." "Do you want
any more of the gabs?" asked Charles. Hugo called upon Bernard to
do what he had threatened. Bernard asked the prayers of the
emperor, ran down to the water, and made the sign of the cross. All
the water left its bed, spread over the fields, came into the city, filled
the cellars, drenched the people, and drove King Hugo into his
highest tower; Charles and the peers being the while ensconced in
an old pine-tree, all praying for God's pity.
Charles in the tree heard Hugo in the tower making his moan: he
would give the emperor all his treasure, would become his man and
hold his kingdom of him. The emperor was moved, and prayed that
the flood might stop, and at once the water began to ebb. Hugo was
able to descend from his tower, and he came to Charles, under an
"ympe tree," and repeated what he had uttered in the moment of
extremity. "Do you want the rest of the gabs?" asked Charles. "Ne de
ceste semaine," replied Hugo. "Then, since you are my man," said
the emperor, "we will make a holiday and wear our crowns together."
When the French saw the two monarchs walking together, and
Charles overtopping Hugo by fifteen inches, they said the queen was
a fool to compare anybody with him.
After this promenade there was mass, at which Turpin officiated, and
then a grand dinner. Hugo once more proffered all his treasures to
Charles, but Charles would not take a denier. "We must be going,"
he said. The French mounted their mules, and went off in high
spirits. Very happy was Charles to have conquered such a king
without a battle. Charles went directly to St Denis, and performed
his devotions. The nail and the crown he deposited on the altar,
distributed the other relics over the kingdom, and for the love of the
sepulchre he gave up his anger against the queen.
The story in the English ballad, so far as it is to be collected from our
eight fragments, is that Arthur, represented as King of Little Britain,
while boasting to Gawain of his round table, is told by Guenever that
she knows of one immeasurably finer; the very trestle is worth his
halls and his gold, and the palace it stands in is worth all Little
Britain besides; but not a word will she say as to where this table
and this goodly building may be. Arthur makes a vow never to sleep
two nights in one place till he sees that round table; and, taking for
companions Gawain, Tristram, Sir Bredbeddle, and an otherwise
unknown Sir Marramiles, sets out on the quest.
The pilgrimage which, to save his dignity, Charles makes a cover for
his visit to the rival king forms no part of Arthur's programme.[271]
The five assume a palmer's weed simply for disguise, and travel east
and west, in many a strange country, only to arrive at Cornwall, so
very little a way from home.
The proud porter of Cornwall's gate, a minion swain, befittingly clad
in a suit of gold, for his master is the richest king in Christendom, or
yet in heathenness, is evidently impressed with Arthur's bearing, as
is quite the rule in such cases:[272] he has been porter thirty years
and three, but [has never seen the like]. Cornwall would naturally
ask the pilgrims some questions. From their mentioning some shrine
of Our Lady he infers that they have been in Britain,—Little Britain
we must suppose to be meant. Cornwall asks if they ever knew King
Arthur, and boasts that he had lived seven years in Little Britain, and
had had a daughter by Arthur's wife, now a lady of radiant beauty,
and Arthur has none such.[273] He then sends for his steed, which
he can ride three times as far in a day as Arthur can any of his, and
we may suppose that he also exhibits to his guests a horn and a
sword of remarkable properties, and a Bur-low-Beanie, or Billy-Blin,
a seven-headed, fire-breathing fiend whom he has in his service.
Arthur is then conducted to bed, and the Billy-Blin, shut up, as far as
we can make out, in some sort of barrel, or other vessel,[274] is set
by Arthur's bed-side to hear and report the talk of the pilgrims. Now,
it would seem, the knights make each their vow or brag. Arthur's is
that he will be the death of Cornwall King before he sees Little
Britain. Gawain, who represents Oliver, will have Cornwall's daughter
home with him. Here there is an unlucky gap. Tristram should
undertake to carry off the horn, Marramiles the steed, and Sir
Bredbeddle the sword. But first it would be necessary to subdue the
loathly fiend. Bredbeddle goes to work without dallying, bursts open
the rub-chadler with his sword, and fights the fire-breathing monster
in a style that is a joy to see; but sword, knife, and axe all break,
and he is left without a weapon. Yet he had something better to fall
back on, and that was a little book which he had found by the
seaside, no doubt in the course of those long travels which
conducted the pilgrims from Little Britain to Cornwall. It was
probably a book of Evangiles; our Lord had written it with his hands
and sealed it with his blood. With this little book, which in a manner
takes the place of the relics in the French tale, for the safety of the
pilgrims and the accomplishment of their vows are secured through
it, Bredbeddle conjures the Burlow-beanie, and shuts him up till
wanted in a "wall of stone," which reminds us of the place in which
Hugo's spy is concealed. He then reports to Arthur, who has a great
desire to see the fiend in all his terrors, and, upon the king's
promising to stand firm, Bredbeddle makes the fiend start out again,
with his seven heads and the fire flying out of his mouth. The Billy-
Blin is now entirely amenable to command: Bredbeddle has only to
"conjure" him to do a thing, and it is done. First he fetches down the
steed. Marramiles, who perhaps had vowed to bring off the horse,
considers that he is the man to ride him, but finds he can do nothing
with him, and has to call on Bredbeddle for help. The Billy-Blin is
required to tell how the steed is to be ridden, and reveals that three
strokes of a gold wand which stands in Cornwall's study-window will
make him spring like spark from brand. And so it comes out that
Cornwall is a magician. Next the horn has to be fetched, but, when
brought, it cannot be sounded. For this a certain powder is required.
This the fiend procures, and Tristram blows a blast which rends the
horn up to the midst.[275] Finally the Billy-Blin is conjured to fetch
the sword, and with this sword Arthur goes and strikes off Cornwall's
head. So Arthur keeps his vow, and, so far as we can see, all the
rest are in a condition to keep theirs.
The English ballad retains too little of the French story to enable us
to say what form of it this little was derived from. The poem of
Galien would cover all that is borrowed as well as the Journey of
Charlemagne. It may be regarded as an indication of late origin that
in this ballad Arthur is king of Little Britain, that Bredbeddle and
Marramiles are made the fellows of Gawain and Tristram, Bredbeddle
carrying off all the honors, and that Cornwall has had an intrigue
with Arthur's queen. The name Bredbeddle is found elsewhere only
in the late Percy version of the romance of the Green Knight, Hales
and Furnivall, II, 56, which version alludes to a custom of the
Knights of the Bath, an order said to have been instituted by Henry
IV at his coronation, in 1399.
The Färöe ballad, 'Geipa-táttur,' exists in four versions: A, Svabo's
manuscript collection, 1782, III, 1, 85 stanzas; B, Sandøbog, 1822,
p. 49, 140 stanzas; C, Fugløbog, c. 1840, p. 9, 120 stanzas; D,
Syderø version, obtained by Hammershaimb, 1848, 103 stanzas.[276]
It repeats the story of the Norse saga, with a moderate number of
traditional accretions and changes. The emperor, from his throne,
asks his champions where is his superior [equal]. They all drop their
heads; no one ventures to answer but the queen, who better had
been silent. "The emperor of Constantinople" (Hákin, D), she says,
"is thy superior." "If he is not," answers Karl, "thou shalt burn on
bale." In B, when they have already started for Constantinople,
Turpin persuades them to go rather to Jerusalem: in the other
versions it must be assumed that the holy city was on the route. As
Karl enters the church the bells ring and the candles light of
themselves, C, D. There are thirteen seats in the choir: Karl takes
the one that Jesus had occupied, and the peers those of the
apostles. A heathen tells the patriarch[277] that the Lord is come
down from heaven, C, D. The patriarch proceeds to the church, with
no attendance but his altar-book [singing from his altar-book]; he
asks Karl what he has come for, and Karl replies, to see the
halidoms, A, C, D. In B the patriarch presents himself to the
emperor at his lodging, and inquires his purpose; and, learning that
he is on his way to Constantinople, for glory, advises him first to go
to the church, where the ways and means of success are to be
found. The patriarch gives Karl some of the relics: the napkin on
which Jesus had wiped his hands, cups from which he had drunk,
etc. Karl, in A, C, now announces that he is on his way to
Constantinople; the patriarch begs him not to go, for he will have
much to suffer. At the exterior gate of the palace will be twelve
white bears, ready to go at him; the sight of his sword [of the holy
napkin, B] will cause them to fall stone-dead, or at least harmless,
B. At the gate next within there will be twelve wolf-dogs[278] [and
further on twelve toads, B], which must be disposed of in like wise:
etc. The castle stands on a hundred pillars, A, and is full of
ingenious contrivances: the floor goes up to the sky, and the roof
comes down to the ground, B. Karl now sets out, with the patriarch's
blessing and escort. Before they reach the palace they come upon
three hundred knights and ladies dancing, which also had been
foretold, and at the portals of the palace they find and vanquish the
formidable beasts. The palace is to the full as splendid and as
artfully constructed as they had been informed: the floor goes up
and the roof comes down, B; there are monstrous figures (?), with
horns at their mouths, and upon a wind rising the horns all sound,
the building begins to revolve, and the Frenchmen jump up, each
clinging to the other, B, C, D. Karl remembers what his wife had
said, A, D.
Of the reception by the monarch of Constantinople nothing further is
said. We are immediately taken to the bedroom, in which there are
twelve beds, with a thirteenth in the middle, and also a stone arch,
or vault, inside of which is a man with a candle. Karl proposes that
they shall choose feats, make boasts, rouses [skemtar, jests, C].
These would inevitably be more or less deranged and corrupted in
the course of tradition. A and C have lost many. Karl's boast,
dropped in B, C, is that he will smite King Hákin, so that the sword's
point shall stick in the ground, D; hit the emperor on the neck and
knock him off his horse, A. Roland, in all, will blow the emperor's
hair off his head with the blast of his horn. Oliver's remains as in the
French poem. William of Orange's ball is changed to a bolt. The
exploit with the horses and apples is assigned to Bernard in D, the
only version which preserves it, as in the Norse saga; and, as in the
saga again, it is Turpin, and not Bernard, who brings in the river
upon the town, and forces the king to take refuge in the tower.
Early in the morning the spy reports in writing, and King Hákin, D,
says that Karl and his twelve peers shall burn on the bale, A, C, D, if
they cannot make good their boasts, B. Karl's queen appears to him
in his sleep, A, and bids him think of last night's words. It is the
queen of Constantinople in B, C, D who rouses Karl to a sense of his
plight; in B she tells him that the brags have been reported, and
that burning will be the penalty unless they be achieved. Karl then
sees that his wife knew what she was saying, and vows to give her
Hildarheim and a scarlet cloak if he gets home alive. He hastens to
church; a dove descends from heaven and sits on his arm [in B a
voice comes from heaven]; he is assured that the boasts shall all be
performed, but never let such a thing be done again. In A three of
the feats are executed, in D four, in C seven, Oliver's in each case
strictly, and Turpin's, naturally, last. The king in C does the feat
which is proposed by Eimer in the saga. A and C end abruptly with
Turpin's exploit. In D Karl falls on his knees and prays, and the water
retires; Karl rides out of Constantinople, followed three days on the
road by Koronatus, as Hákin is now called, stanza 103: it is
Karlamagnús that wears his crown higher. B takes a turn of its own.
Roland, Olger and Oliver are called upon to do their brags. Roland
blows so that nobody in Constantinople can keep his legs, and the
emperor falls into the mud, but he blows not a hair off the emperor's
head; Olger slings the gold-bolt over the wall, but breaks off none;
Oliver gives a hundred kisses, as in the saga. The emperor remarks
each time, I hold him no champion that performs his rouse that way.
But Turpin's brag is thoroughly done; the emperor is driven to the
tower, and begs Karl to turn off the water; no more feats shall be
exacted. Now the two kaisers walk in the hall, conferring about
tribute, which Karl takes and rides away. When he reaches home his
queen welcomes him, and asks what happened at Constantinople:
"Hvat gekk af?" "This," says Karl; "I know the truth now; you shall
be queen as before, and shall have a voice in the rule."
It is manifest that Charlemagne's pilgrimage to Jerusalem and the
visit to the king of Constantinople, though somewhat intimately
combined in the old French geste, were originally distinct narratives.
As far as we can judge, nothing of the pilgrimage was retained by
the English ballad. We are not certain, even, that it is Charlemagne's
visit to Hugo upon which the ballad was formed, though the great
popularity of the French poem makes this altogether likely. As M.
Gaston Paris has said and shown,[279] the visit to Hugo is one of a
cycle of tales of which the framework is this: that a king who
regards himself as the richest or most magnificent in the world is
told that there is somebody that outstrips him, and undertakes a
visit to his rival to determine which surpasses the other, threatening
death to the person who has disturbed his self-complacency, in case
the rival should turn out to be his inferior. A familiar example is
afforded by the tale of Aboulcassem, the first of the Mille et un
Jours. Haroun Alraschid was incessantly boasting that no prince in
the world was so generous as he.[280] The vizier Giafar humbly
exhorted the caliph not to praise himself, but to leave that to others.
The caliph, much piqued, demanded, Do you then know anybody
who compares with me? Giafar felt compelled to reply that there was
a young man at Basra, who, though in a private station, was not
inferior even to the caliph in point of generosity. Haroun was very
angry, and, on Giafar's persisting in what he had said, had the vizier
arrested, and finally resolved to go to Basra to see with his own
eyes: if Giafar should have spoken the truth, he should be rewarded,
but in the other event he should forfeit his life.[281]
This story, it is true, shows no trace of the gabs which Charlemagne
and the peers make, and which Hugo requires to be accomplished
on pain of death. The gabs are a well-known North-European
custom, and need not be sought for further; but the requiring by
one king of certain feats to be executed by another under a heavy
penalty is a feature of a large class of Eastern tales of which there
has already been occasion to speak: see 'The Elfin Knight,' p. 11.
The demand in these, however, is made not in person, but through
an ambassador. The combination of a personal visit with a task to be
performed under penalty of death is seen in the Vafþrúðnismál,
where Odin, disguised as a traveller, seeks a contest in knowledge
with the wisest of the giants.[282]
The story of the gabs has been retold in two modern imitations: very
indifferently by Nivelle de la Chaussée, 'Le Roi Hugon,' Œuvres, t. V,
supplément, p. 66, ed. 1778, and well by M. J. Chénier, 'Les
Miracles,' III, 259, ed. 1824.[283] Uhland treated the subject
dramatically in a composition which has not been published: Keller,
Altfranzösische Sagen, 1876, Inhalt (Koschwitz).
Percy MS., p. 24. Hales and Furnivall, I, 61; Madden's Syr
Gawayne, p. 275.

* * * * *

1
[Saies, 'Come here, cuzen Gawaine so gay,]
My sisters sonne be yee;
Ffor you shall see one of the fairest round tables
That euer you see with your eye.'

2
Then bespake Lady Queen Gueneuer,
And these were the words said shee:
'I know where a round table is, thou noble king,
Is worth thy round table and other such three.

3
'The trestle that stands vnder this round table,' she
said,
'Lowe downe to the mould,
It is worth thy round table, thou worthy king,
Thy halls, and all thy gold.

4
'The place where this round table stands in,
. . . . . . .
It is worth thy castle, thy gold, thy fee,
And all good Litle Britaine.'

5
'Where may that table be, lady?' quoth hee,
'Or where may all that goodly building be?'
'You shall it seeke,' shee says, 'till you it find,
For you shall neuer gett more of me.'

6
Then bespake him noble King Arthur,
These were the words said hee:
'He make mine avow to God,
And alsoe to the Trinity,

7
'He never sleepe one night there as I doe another,
Till that round table I see:
Sir Marramiles and Sir Tristeram,
Fellowes that ye shall bee.

8
. . . . . . .
. . . . . . .
'Weele be clad in palmers weede,
Fiue palmers we will bee;

9
'There is noe outlandish man will vs abide,
Nor will vs come nye.'
Then they riued east and thé riued west,
In many a strange country.

10
Then they tranckled a litle further,
They saw a battle new sett:
'Now, by my faith,' saies noble King Arthur,
. . . . . . well .

* * * * *

11
But when he cam to this ... c ...
And to the palace gate,
Soe ready was ther a proud porter,
And met him soone therat.

12
Shooes of gold the porter had on,
And all his other rayment was vnto the same:
'Now, by my faith,' saies noble King Arthur,
'Yonder is a minion swaine.'

13
Then bespake noble King Arthur,
These were the words says hee:
'Come hither, thou proud porter,
I pray thee come hither to me.

14
'I haue two poore rings of my finger,
The better of them Ile giue to thee;
Tell who may be lord of this castle,' he sayes,
'Or who is lord in this cuntry?'

15
'Cornewall King,' the porter sayes,
'There is none soe rich as hee;
Neither in christendome, nor yet in heathen-nest,
None hath soe much gold as he.'

16
And then bespake him noble King Arthur,
These were the words sayes hee:
'I haue two poore rings of my finger,
The better of them Ile giue thee,
If thou wilt greete him well, Cornewall King,
And greete him well from me.
17
'Pray him for one nights lodging and two meales
meate,
For his love that dyed vppon a tree;
Of one ghesting and two meales meate,
For his loue that dyed vppon a tree.

18
'Of one ghesting, of two meales meate,
For his love that was of virgin borne,
And in the morning that we may scape away,
Either without scath or scorne.'

19
Then forth is gone this proud porter,
As fast as he cold hye,
And when he came befor Cornewall King,
He kneeled downe on his knee.

20
Sayes, 'I haue beene porter-man, at thy gate,
This thirty winter and three ...
. . . . . . .
. . . . . . .

* * * * *

21
. . . . . . .
. . . . . . .
Our Lady was borne; then thought Cornewall King
These palmers had beene in Brittaine.

22
Then bespake him Cornwall King,
These were the words he said there:
'Did you euer know a comely king,
His name was King Arthur?'

23
And then bespake him noble King Arthur,
These were the words said hee:
'I doe not know that comly king,
But once my selfe I did him see.'
Then bespake Cornwall King againe,
These were the words said he:

24
Sayes, 'Seuen yeere I was clad and fed,
In Litle Brittaine, in a bower;
I had a daughter by King Arthurs wife,
That now is called my flower;
For King Arthur, that kindly cockward,
Hath none such in his bower.

25
'For I durst sweare, and saue my othe,
That same lady soe bright,
That a man that were laid on his death bed
Wold open his eyes on her to haue sight.'
'Now, by my faith,' sayes noble King Arthur,
'And that's a full faire wight!'

26
And then bespake Cornewall [King] againe,
And these were the words he said:
'Come hither, fiue or three of my knights,
And feitch me downe my steed;
King Arthur, that foule cockeward,
Hath none such, if he had need.

27
'For I can ryde him as far on a day
As King Arthur can doe any of his on three;
And is it not a pleasure for a king
When he shall ryde forth on his iourney?

28
'For the eyes that beene in his head,
Thé glister as doth the gleed.'
'Now, by my faith,' says noble King Arthur,
'That is a well faire steed.'

* * * * *

29
. . . . . . .
. . . . . . .
'Nobody say . . . .
But one that's learned to speake.'

30
Then King Arthur to his bed was brought,
A greeiued man was hee;
And soe were all his fellowes with him,
From him thé thought neuer to flee.

31
Then take they did that lodly groome,
And under the rub-chadler closed was hee,
And he was set by King Arthurs bed-side,
To heere theire talke and theire comunye;

32
That he might come forth, and make proclamation,
Long before it was day;
It was more for King Cornwalls pleasure,
Then it was for King Arthurs pay.

33
And when King Arthur in his bed was laid,
These were the words said hee:
'Ile make mine avow to God,
And alsoe to the Trinity,
That Ile be the bane of Cornwall Kinge,
Litle Brittaine or euer I see!'

34
'It is an vnaduised vow,' saies Gawaine the gay,
'As ever king hard make I;
But wee that beene fiue Christian men,
Of the christen faith are wee,
And we shall fight against anoynted king
And all his armorie.'

35
And then bespake him noble Arthur,
And these were the words said he:
'Why, if thou be afraid, Sir Gawaine the gay,
Goe home, and drinke wine in thine owne country.'

36
And then bespake Sir Gawaine the gay,
And these were the words said hee:
'Nay, seeing you have made such a hearty vow,
Heere another vow make will I.

37
'Ile make mine avow to God,
And alsoe to the Trinity,
That I will haue yonder faire lady
To Litle Brittaine with mee.

38
'Ile hose her hourly to my heart,
And with her Ile worke my will;'
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.

More than just a book-buying platform, we strive to be a bridge


connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.

Join us on a journey of knowledge exploration, passion nurturing, and


personal growth every day!

ebookbell.com

You might also like