Delimited Continuations in Operating Systems
Delimited Continuations in Operating Systems
1 Introduction
One notion of context that pervades programming-language research is that of
evaluation contexts. If one part of a program is currently running (that is, being
evaluated), then the rest of the program is expecting the result from that part,
typically waiting for it. This rest of the program is the evaluation context of the
running part. For example, in the program “1 + 2 × 3”, the evaluation context
of the multiplication “2 × 3” is the rest of the program “1 + ”.
The meaning of an evaluation context is a function that maps a result value
to an answer. For example, the meaning of the evaluation context “1 + ” is the
increment function, so it maps the result value 6 to the answer 7. Similarly, in
a program that opens a file and summarizes its contents, the meaning of the
evaluation context of the opening of the file is a function that maps a handle for
a file to a summary of its contents. This function is called a continuation.
A continuation is delimited when it produces an intermediate answer rather
than the final outcome of the entire computation. For example, the increment
function is a delimited continuation when taken as the meaning of “1 + ” in
the program “print(1 + 2 × 3)”. Similarly, we treat a function from file handles
to content summaries as a delimited continuation when we view the summa-
rization program as part of an operating system that reaches its final outcome
only when the computer shuts down months later. The delimiter (or prompt)
is the boundary between the producer of the intermediate answer (such as the
summarization program) and the rest of the system.
Many uses have been discovered for the concept of continuations [33]: in
the semantic theory of programming languages [12, 42], as a practical strategy
for their design and implementation [20, 41], and in natural-language semantics
[2, 36]. In operating-system research, continuations are poorly known and seldom
used explicitly. In this paper, we cross the boundary between operating systems
and programming languages to argue by examples that continuations, especially
delimited ones, pervade operating systems—if only implicitly. We contend that
systems programmers should recognize the applications of delimited continua-
tions, so as to design systems with sensible defaults and implement them using
efficient techniques from the literature.
One example of delimited continuations appears in the interaction between
an operating system and a user program running under its management. From
time to time, the user program may request a service from the kernel of the
operating system, for example to read a file. When the kernel receives a request
for a system service, it first saves the state, or execution context, of the user
process. After processing the request, the kernel resumes the process, passing it
the reply. If the request takes some time to process, such as when data must be
fetched from a hard drive, the operating system may let some other user process
run in the meantime and only resume the original user process when the hard
drive is done. We can think of the execution context as a function that maps the
kernel’s reply to the outcome of the user process. This function is a delimited
continuation; the delimiter in this case is the boundary between the user process
and the rest of the system.
Saving the execution context for a process to be resumed later is called cap-
turing the continuation of the process [45]. Usually a captured continuation is
invoked exactly once, but sometimes it is invoked multiple times. For example, a
typical operating system offers services for a process to duplicate (“fork”) itself
into two parallel processes or to save a snapshot of itself to be restored in the
future. Other times the captured continuation is never invoked, such as when a
process invokes the “exit” service to destruct itself. Two or more continuations
that invoke each other once each are called coroutines. For example, in the PDP-
7 Unix operating system, the shell and other user processes transfer control to
each other as coroutines (using the “exec” system service [34]).
The concept of an operating-system kernel has found its way into the pro-
gramming-language literature, for instance to describe in a modular and rigorous
way what side effects such as state, exceptions, input/output, and backtracking
mean [22, 23, 40]. A recurring idea in that work is that of a central authority [5],
mediating interactions between a program, which performs computations, and
the external world, which provides resources such as files. A computation yields
either a value or a side effect. A side effect is a request to the central authority
to perform an action (such as reading a file), paired with a continuation function
that accepts the result of the action and resumes the computation.
In practical systems programming, continuations are best known for writ-
ing concurrent programs [1, 9, 10, 16, 25, 27, 38, 45], distributed programs
[29, 35, 43], and Web programs [3, 14, 24, 31, 32, 39]. In these and many other
applications, the programmer codes the handling of events [46] in continua-
tion-passing style, whether or not the programmer is aware of the fact. With
awareness, continuations have guided the design of a network protocol that does
not require the server to track the state of each connection, and is thus more
scalable, easier to migrate, and more resistant to denial-of-service attacks [37].
This paper focuses on a less-known use of continuations: file systems. We
stress transactional file systems, which treat each operation such as changing,
deleting, or renaming a file as a transaction, and where a transaction can be
undone (that is, rolled back). Our Zipper File System manages each connection
between it and its users as a delimited continuation, so it is natural and easy to
implement copy on write: each user appears to have exclusive use of a separate
file system, but the parts that are identical across users are actually stored only
once and shared until one user changes its “copy” to be different.
Section 2 gives two examples of delimited continuations in systems program-
ming in more detail. Section 3 describes our Zipper File System. Section 4 reviews
the benefits we reap of recognizing continuations explicitly.
2 Instances of continuations
We give two examples of delimited continuations: a user process requesting a
system service, and traversing a data structure. The examples seem unrelated,
yet use the same programming-language facility (notated CC below), thus simpli-
fying their implementation. We have built the Zipper File System as a working
prototype of both examples. Our prototype and illustrative code below are writ-
ten in Haskell, a high-level general-purpose programming language, because it
is suitable for operating systems [15] and its notation is concise and close to
mathematical specification.
The function service initiates a system call : cat invokes service to request
reading and writing services from the kernel.
The variable p above is a control delimiter: it represents the boundary be-
tween the user process and the kernel, delimiting the continuation in a request
from the user process to the kernel. In the definition of service above, the
expression shiftP p (\k -> ...) means for the user process to capture the
delimited continuation up to the delimiter p and call it k. Because p delimits the
user process from the kernel, the delimited continuation k is precisely the exe-
cution context of the user process. The subexpression return (req k) means
for the user process to exit to the kernel with a new request data structure
containing the captured delimited continuation k.
We now turn from how a user process initiates a request to how the operating-
system kernel handles the request. The kernel handles system calls in a function
called interpret, which takes three arguments.
1. The record world represents the state of the whole operating system. It
includes, among other fields, the job queue, a collection of processes waiting
to run.
2. The process control block pcb describes various resources allocated to the
current process, such as network connections called sockets. Sockets consti-
tute the input and output channels of the process.
3. The request from the process, of type Req r, specifies how the process exited
along with whether and how it should be resumed.
The kernel keeps track of the process waiting for a character only by storing the
process’ continuation in the record of pending read. When the kernel receives
data from a socket, it locates any associated read-pending request in the job
queue and resumes the blocked process by invoking the function resume below.
The function extracts the continuation k of the suspended process and passes
it the received_character, thus resuming the process. The process eventually
returns another request req, which is interpreted as above.
This example shows how a process that just yielded control (to the kernel)
is a continuation [25]. We have in fact implemented delimited continuations in
the Perl 5 programming language by representing them as server processes that
yield control until they receive a client connection. Although the mathematical
meaning of a delimited continuation is a function that maps request values from
a client to response answers from the server, the function is represented by data
structures [7] and so can be saved into a file or sent to remote hosts. To save a
captured continuation to be reused later is to take a snapshot of a process, or to
checkpoint it.
The control delimiter p in the code above delimits the kernel from a user
process. The same kind of delimiters can be used by a user process such as a
debugger to run a subcomputation in a sandbox and intercept requests from the
sandbox before forwarding them to the kernel. This interposition facility falls
out from our view of requests as containing delimited continuations.
traverse (\n -> return (if n < 2 then Just 5 else Nothing)) tree1
The update is nondestructive: the old tree1 is intact and may be regarded
as a snapshot of the data before the update. If tree1 is not used further in the
computation, the system will reclaim the storage space it occupies. To use tree1
further, on the other hand, is to “undo” the update. The nondestructive update
takes little more memory than a destructive update would, because the new
tree shares any unmodified data with the old tree. That is, traverse performs
copy-on-write. (The code above actually only shares unmodified leaves among
traversals. A slight modification of the code, implemented in the Zipper File
System, lets us share unmodified branches as well.)
Another benefit of the nondestructive update performed by traverse is iso-
lation: any other computation using tree1 at the same time will be unaffected
by our update and may proceed concurrently. Two concurrent traversals that
wish to know of each other’s updates must exchange them, possibly through a
common arbiter—the operating-system kernel—using the same system-call in-
terface based on delimited continuations discussed in Section 2.1. The arbiter
may reconcile or reject the updates and report the result to the concurrent
traversals. The outcome does not depend on the order in which the updates are
performed—that is, we avoid race conditions—because nondestructive updates
do not modify the same original version of the data that they share. Nonde-
structive updates of the same sort are used in distributed revision control and
in robust distributed telecom software [30].
For reading and updating a file, file system, process tree, or database, an
interface like traverse is a more appropriate access primitive than the cursor -
based (or handle-based) interface more prevalent today, in that the traversal
interface eliminates the risk of forgetting to dispose of a cursor or trying to use
a cursor already disposed of [21]. The traversal interface is no less expressive:
when the cursor-based access is truly required, it can be automatically obtained
from the traversal interface using delimited continuations, as we now explain.
The zipper [17] data-type Z r is what is commonly called a database cursor
or file handle.
References
[1] Adya, A., Howell, J., Theimer, M., Bolosky, W. J., and Douceur,
J. R. Cooperative task management without manual stack management,
or, event-driven programming is not the opposite of threaded programming.
In Proceedings of the 2002 USENIX Annual Technical Conference (10–15
June 2002), USENIX, pp. 289–302.
[2] Barker, C., and Shan, C.-c. Types as graphs: Continuations in type
logical grammar. Journal of Logic, Language and Information 15, 4 (Nov.
2006), 331–370.
[3] Belapurkar, A. Use continuations to develop complex Web applications.
IBM developerWorks, 21 Dec. 2004.
[4] Bruggeman, C., Waddell, O., and Dybvig, R. K. Representing con-
trol in the presence of one-shot continuations. In ACM SIGPLAN 1996
Conference on Programming Language Design and Implementation (June
1996).
[5] Cartwright, R., and Felleisen, M. Extensible denotational language
specifications. In Theoretical Aspects of Computer Software: International
Symposium (1994), M. Hagiya and J. C. Mitchell, Eds., no. 789 in Lecture
Notes in Computer Science, Springer-Verlag, pp. 244–272.
[6] Clinger, W. D., Hartheimer, A., and Ost, E. M. Implementation
strategies for continuations. Higher-Order and Symbolic Computation Vol.
12, No. 1 (1999), 7–45.
[7] Danvy, O., and Nielsen, L. R. Defunctionalization at work. In Pro-
ceedings of the 3rd International Conference on Principles and Practice of
Declarative Programming (Sept. 2001), ACM Press, pp. 162–174.
[8] Derrin, P., Elphinstone, K., Klein, G., Cock, D., and
Chakravarty, M. M. T. Running the manual: an approach to high-
assurance microkernel development. In Haskell ’06: Proceedings of the 2006
ACM SIGPLAN workshop on Haskell (2006), ACM Press, pp. 60–71.
[9] Dybvig, R. K., and Hieb, R. Engines from continuations. Journal of
Computer Languages 14, 2 (1989), 109–123.
[10] Dybvig, R. K., and Hieb, R. Continuations and concurrency. In Proceed-
ings of the Second ACM SIGPLAN Symposium on Principles and Practice
of Parallel Programming (Mar. 1990), pp. 128–136.
[11] Ernst, E. Method mixins. Report PB-557, Department of Computer
Science, University of Aarhus, Denmark, Mar. 2002.
[12] Fischer, M. J. Lambda-calculus schemata. Lisp and Symbolic Computa-
tion 6, 3–4 (1993), 259–288.
[13] Gasbichler, M., and Sperber, M. Final shift for call/cc: Direct im-
plementation of shift and reset. In ICFP ’02: Proceedings of the ACM
International Conference on Functional Programming (2002), ACM Press,
pp. 271–282.
[14] Graunke, P. T. Web Interactions. PhD thesis, College of Computer
Science, Northeastern University, June 2003.
[15] Hallgren, T., Jones, M. P., Leslie, R., and Tolmach, A. P. A prin-
cipled approach to operating system construction in Haskell. In Proceedings
of the 10th ACM SIGPLAN International Conference on Functional Pro-
gramming, ICFP 2005, Tallinn, Estonia, September 26-28, 2005 (2005),
O. Danvy and B. C. Pierce, Eds., ACM, pp. 116–128.
[16] Haynes, C. T., Friedman, D. P., and Wand, M. Obtaining coroutines
with continuations. Journal of Computer Languages 11, 3/4 (1986), 143–
153.
[17] Huet, G. The zipper. Journal of Functional Programming 7, 5 (Sept.
1997), 549–554.
[18] ICFP ’05: Proceedings of the ACM International Conference on Functional
Programming (2005), ACM Press.
[19] Jones, I., et al. Halfs, a Haskell filesystem. http://www.haskell.org/
halfs/, 2006.
[20] Kelsey, R., Clinger, W. D., Rees, J., Abelson, H., Dybvig, R. K.,
Haynes, C. T., Rozas, G. J., Adams, IV, N. I., Friedman, D. P.,
Kohlbecker, E., Steele, G. L., Bartley, D. H., Halstead, R., Ox-
ley, D., Sussman, G. J., Brooks, G., Hanson, C., Pitman, K. M.,
and Wand, M. Revised5 report on the algorithmic language Scheme.
Higher-Order and Symbolic Computation 11, 1 (1998), 7–105. Also as ACM
SIGPLAN Notices 33(9):26–76.
[21] Kiselyov, O. General ways to traverse collections. http://
okmij.org/ftp/Scheme/enumerators-callcc.html; http://okmij.org/
ftp/Computation/Continuations.html, 1 Jan. 2004.
[22] Kiselyov, O. How to remove a dynamic prompt: Static and dynamic
delimited continuation operators are equally expressible. Tech. Rep. 611,
Computer Science Department, Indiana University, Mar. 2005.
[23] Kiselyov, O., Shan, C.-c., Friedman, D. P., and Sabry, A. Back-
tracking, interleaving, and terminating monad transformers (functional
pearl). In ICFP [18], pp. 192–203.
[24] Krishnamurthi, S., Hopkins, P. W., McCarthy, J., Graunke, P. T.,
Pettyjohn, G., and Felleisen, M. Implementation and use of the PLT
Scheme Web server. Higher-Order and Symbolic Computation (2007).
[25] Kumar, S., Bruggeman, C., and Dybvig, R. K. Threads yield contin-
uations. Lisp and Symbolic Computation 10, 2 (May 1998), 223–236.
[26] Launchbury, J., and Peyton Jones, S. L. State in Haskell. Lisp and
Symbolic Computation 8, 4 (Dec. 1995), 293–341.
[27] Li, P., and Zdancewic, S. A language-based approach to unifying events
and threads. http://www.cis.upenn.edu/∼stevez/papers/LZ06b.pdf,
Apr. 2006.
[28] Moggi, E., and Sabry, A. Monadic encapsulation of effects: A revised
approach (extended version). Journal of Functional Programming 11, 6
(2001), 591–627.
[29] Murphy, VII, T., Crary, K., and Harper, R. Distributed control flow
with classical modal logic. In Computer Science Logic: 19th International
Workshop, CSL 2005 (22–25 Aug. 2005), C.-H. L. Ong, Ed., no. 3634 in
Lecture Notes in Computer Science, Springer-Verlag, pp. 51–69.
[30] Nyström, J. H., Trinder, P. W., and King, D. J. Are high-
level languages suitable for robust telecoms software? In Computer
Safety, Reliability, and Security, 24th International Conference, SAFE-
COMP 2005, Fredrikstad, Norway, September 28-30, 2005, Proceedings
(2005), R. Winther, B. A. Gran, and G. Dahll, Eds., vol. 3688 of Lecture
Notes in Computer Science, Springer, pp. 275–288.
[31] Ocsigen: a programming framework providing a new way to create dynamic
web sites. http://www.ocsigen.org.
[32] Queinnec, C. Continuations and web servers. Higher-Order and Symbolic
Computation 17, 4 (Dec. 2004), 277–295.
[33] Reynolds, J. C. The discoveries of continuations. Lisp and Symbolic
Computation 6, 3–4 (1993), 233–247.
[34] Ritchie, D. M. The Evolution of the Unix Time-sharing System. AT&T
Bell Laboratories Technical Journal 63, No. 6 part 2 (Oct. 1984), 1577–93.
[35] Sewell, P., Leifer, J. J., Wansbrough, K., Zappa Nardelli, F.,
Allen-Williams, M., Habouzit, P., and Vafeiadis, V. Acute: High-
level programming language design for distributed computation. In ICFP
[18], pp. 15–26.
[36] Shan, C.-c., and Barker, C. Explaining crossover and superiority as
left-to-right evaluation. Linguistics and Philosophy 29, 1 (2006), 91–134.
[37] Shieh, A., Myers, A., and Sirer, E. G. Trickles: A stateless transport
protocol. Summaries of OSDI’04. USENIX ;login: vol. 30, No. 2, 2005, p. 66,
2004. 6th Symposium on Operating Systems Design and Implementation,
OSDI’04. Work-in-Progress Reports.
[38] Shivers, O. Continuations and threads: Expressing machine concurrency
directly in advanced languages. In Proceedings of the Second ACM SIG-
PLAN Workshop on Continuations (Jan. 1997).
[39] SISCweb: a framework to facilitate writing stateful scheme web applications
in a J2EE environment. http://siscweb.sf.net/.
[40] Sitaram, D., and Felleisen, M. Control delimiters and their hierarchies.
Lisp and Symbolic Computation 3, 1 (Jan. 1990), 67–99.
[41] Steele, Jr., G. L. RABBIT: A compiler for SCHEME. Master’s thesis,
Department of Electrical Engineering and Computer Science, Massachusetts
Institute of Technology, May 1978. Also as Memo 474, Artificial Intelligence
Laboratory, Massachusetts Institute of Technology.
[42] Strachey, C., and Wadsworth, C. P. Continuations: A mathematical
semantics for handling full jumps. Higher-Order and Symbolic Computation
13, 1–2 (Apr. 2000), 135–152.
[43] Sumii, E. An implementation of transparent migration on standard
Scheme. In Proceedings of the Workshop on Scheme and Functional Pro-
gramming (Sept. 2000), M. Felleisen, Ed., no. 00-368 in Tech. Rep., Depart-
ment of Computer Science, Rice University, pp. 61–63.
[44] Van Roy, P. Convergence in language design: A case of lightning striking
four times in the same place. In Proceedings of FLOPS 2006: 8th Inter-
national Symposium on Functional and Logic Programming (24–26 Apr.
2006), M. Hagiya and P. Wadler, Eds., no. 3945 in Lecture Notes in Com-
puter Science, Springer-Verlag, pp. 2–12.
[45] Wand, M. Continuation-based multiprocessing revisited. Higher-Order
and Symbolic Computation, 12(3) (Oct. 1999), 283.
[46] Williams, N. J. An implementation of scheduler activations on the
NetBSD operating system. In Proceedings of the FREENIX Track: 2002
USENIX Annual Technical Conference (Berkeley, CA, 10–15 June 2002),
USENIX, pp. 99–108.