Operating Systems Foundations With Linux On The Raspberry Pi Paperback Wim Vanderbauwhede Jeremy Singer Instant Download
Operating Systems Foundations With Linux On The Raspberry Pi Paperback Wim Vanderbauwhede Jeremy Singer Instant Download
https://ebookbell.com/product/operating-systems-foundations-with-
linux-on-the-raspberry-pi-paperback-wim-vanderbauwhede-jeremy-
singer-23877578
https://ebookbell.com/product/the-netbsd-operating-system-a-guide-
federico-lupi-1215254
https://ebookbell.com/product/operating-systems-advanced-concepts-
mamoru-maekawa-arthur-e-oldehoeft-rodney-r-oldehoeft-49455972
https://ebookbell.com/product/operating-systems-gary-j-nutt-50182994
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
https://ebookbell.com/product/operating-systems-internals-and-design-
principles-6th-edition-william-stallings-2117752
https://ebookbell.com/product/operating-systems-internals-and-design-
principles-8th-ed-remi-21347210
https://ebookbell.com/product/operating-systems-internals-and-design-
principles-9-ed-stallings-22002718
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
For information on all Arm Education Media publications, visit our website at www.armedumedia.com
vi
Contents
vii
Contents
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
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
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
xii
Contents
xiii
Contents
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
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.
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 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
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.
Source code for all original code snippets listed in the book;
Lab materials;
Additional content;
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.
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
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 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.
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]
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:
Melissa Good, Jialin Dou and Michael Shuff who kept us on track and assisted us with the process.
Tony Garnock-Jones, Dejice Jacob, Richard Mortier, Colin Perkins, and other colleagues who
commented on early versions of the text.
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.
1. Discuss the importance of state and the address space in a processor-based system.
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.
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.
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
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 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:
Where ramState, kbdState, nicState, etc. are all fixed-size arrays of integers.
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
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. 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 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.
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.
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.
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.
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)
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
We will assume that all instructions take up to three registers as arguments, for example
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.
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.
We have a special non-Arm instruction called SET, which takes a register and a value as arguments, e.g.
We also need some instructions to control the flow of the program, such as branches (B)
where R1 contains the address of the target instruction in the program, and conditional branches
(CBZ, ‘Compare and Branch if Zero’)
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 [
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:
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].
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.
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.
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.
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:
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 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:
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.
11
Operating Systems Foundations with Linux on the Raspberry Pi
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:
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)
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:
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
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.
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)
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:
Counter (DCO)
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:
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 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’.
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
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:
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:
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:
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])
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:
To execute such functions in the processor, we add the doHLI function to the executeInstrucion code:
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:
The key point is that the functions manipulate the system state and registers in the same way as the
individual instructions did.
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).
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:
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:
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
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."
"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.
ebookbell.com