This article describes a C code static analyser that detects mis- .... Parsing the kernel C code creates some difficulties of its own. ..... #include .
Static Deadlock Detection in the Linux kernel Peter T. Breuer and Marisol Garc´ıa Valls Universidad Carlos III de Madrid
Abstract. This article describes a C code static analyser that detects misuse of spinlocks in the Linux kernel. Spinlock misuse is difficult to detect by testing, relatively common, and leads to runtime deadlocks in the Linux operating system kernel on multiprocessor architectures.
1
Introduction
The Linux operating system kernel currently deploys on 15 different architectural platforms, ranging from embedded ARM systems through Sun SPARC to the ubiquitous IBM PC. As distributed, the kernel source now comprises some 3.5 million lines of code. The code is mostly C, plus some assembler. Of this total, some 85% is architecture-independent, and the remaining 15% provides low-level support for the various architectures, including the multiprocessor support. The latter importantly comprises assembler level implementations for the “spinlock” primitives: lock, and unlock. Spinlocks are the discretionary mutual exclusion mechanism of choice in the kernel. A spinlock will keep other kernel threads out of a spinlocked region of code while that code is being executed. A spinlock is a busy wait on a counter in the spinlock structure in memory. When that counter drops to zero, the busy loop simultaneously exits and sets the counter to one via machine code that achieves the required atomicity in a multiprocessor context. Setting the counter is called “locking” or “taking” the spinlock, and no other thread of control can take the spinlock via the lock primitive while the counter remains set. A thread which tries will spin in a busy loop until the counter is reset. To “unlock” or “release” the spinlock, the counter is (atomically) set to zero again by the same thread of control that took the spinlock. The spinlock mechanism is effective for guarding short stretches of code that do not call other routines and which take a very short time to process, because it is faster to spinlock the other processor out, and take the penalty of spinning the processor uselessly until the short stretch of code has been processed, than it would be to select and swap in some other code and context for the other processor to execute meanwhile. But spinlocks are easy to accidentally misuse. Spinlocks can give rise to runtime deadlocks in various ways. For example, attempting to take the same spinlock again while already within a spinlocked region of code is a deadlock. The processor will spin in the busy loop within the spinlock lock call, waiting for itself to release its own spinlock, which it will never do. Linux spinlocks are by design not recursive; that is, they do lock against a
thread of execution taking the very same spinlock that it has already taken. One reason for this design decision is the extra operational overhead of the accounting that would be required for recursive spinlocks. Another reason is the processengineering argument that non-recursive spinlocks make coding errors easier to find, because they can cause deadlocks much more easily. Both arguments have historically been prominent in debates over spinlocks on the Linux kernel mailing list, the principal communication channel among kernel developers, so they should be accepted. Whatever the rationale for it, the decision has been in force since multiprocessor support was first introduced into the Linux kernel, and as a result, Linux spinlocks are non-recursive. It is certainly the case, however, that there are some debugging mechanisms that can be activated in the kernel that can detect spinlock deadlocks at runtime. In order to enable these mechanisms, the kernel has to be compiled in debugging mode for spinlocks, and with certain watchdog functions and monitors included. But in practice, these detection mechanisms are rarely used by authors – often authors will be unaware of their existence, or will not have a multiprocessor platform on which to test. They are also often not used simply because they require recompiling the whole kernel and extra drivers in an inconvenient and binary incompatible way. And of course the mechanisms only detect spinlock deadlocks that occur, not those that do not occur during the testing. In practice, deadlocks are encountered not by the authors who might be able to apply the debugging techniques, but by users, who are not able to apply them. Even recursive spinlocks would not liberate the kernel from runtime deadlocks through many of the common mistakes of coding or design. For a counterexample, it suffices to consider two threads which take each others spinlocks. For example, given two spinlocks A and B, and two threads, if thread 1 takes spinlock A then B (while still under A) on one processor, and thread 2 takes spinlock B then A (while still under B) on another processor, there is a small deadlock window. The deadlock occurs if thread 1 takes A, then thread 2 takes B before thread 1 does. In that situation thread 1 (and the processor it occupies) spin waiting for B to be released, but thread 2 (and its processor) spin waiting for A to be released, and neither can progress. The latter deadlock is relatively easy to evoke via coding errors, because different authors may decide to take two common spinlocks in a different order. But there are still easier and more common deadlocks – which will be described later in this article. All in all, the kernel source code is rife with hazard for authors in the multiprocessor setting, but at least some of the hazards can be reliably detected by code analysis - a virtual“dry run” that can detect possible deadlocks through the appropriate abstract interpretation [6] of the kernel C code. This article describes 1. a prototype tool which detects certain types of deadlock in the kernel code, and 2. the several different deadlock mechanisms detected and the technique used to detect them.
Parsing the kernel C code creates some difficulties of its own. The variant of C used in the Linux kernel is not ANSI C but ”Gnu C”, an extension of ANSI C the syntax of which is only well-documented by the code of the Gnu C compiler itself, and these particular extensions make a single token lookahead (1TLA) parse particularly difficult. (The Gnu C compiler code is freely available under the conditions of the General Public License (GPL) version 2 from the Free Software Foundation (FSF)). The Linux kernel code is heavily dependent on several of the Gnu extensions, notably “ attribute ” modifiers for variables and functions, and the “ asm ” assembler inserts, so it is impossible to avoid treating the Gnu C extensions. A top-down unbounded lookahead C parser [1] for Gnu C based on a context-free grammar was rapidly developed as the basis for the syntax analyser (development time for the grammar was about 3 man-weeks, comprising 24 revisions, for 844 lines of code and 59 grammar productions). The principal algorithms of this paper are presented in Section 2, as implemented in the present prototype analyser. Examples of use are given in Section 3. Section 4 discusses future planned extensions which detect more types of coded deadlocks.
2
Sleep under spinlock
The most common mistake leading to deadlock in kernel programming is rescheduling a running thread while it holds a spinlock. This is also called “sleeping under spinlock”. “Scheduling” refers to removing the thread from the processor, and passing control to another thread of execution instead. In this situation, the only thread that will release the spinlock is not running. If another thread tries to take the spinlock, it will busy-wait on the spinlock in the processor, keeping the owner thread out and preventing any further progress. The only hope is for the owner thread to be brought back in by an interrupt or to be brought back in by the process scheduler on another processor, and release the spinlock. But it may well be the case that interrupts are being held off, or that all processors too rapidly become occupied by threads spinning on the blocked spinlock. This is the case if the spinlock is ubiquitously used in the kernel, such as the I/O spinlock for example (in the 2.4 series kernels, there is a single global I/O spinlock). “Scheduling” is often called “sleeping” because from the thread’s point of view it looks as though it has gone to sleep for a while. The common way of causing a thread to sleep is to call one of the many kernel functions with “sleep” or “wait” in their name (or simply, call “schedule()”). That works by directly or indirectly causing the thread to be scheduled out of the processor. Thus sleep under spinlock happens when the author calls a function that sleeps while still holding a spinlock. To avoid the possibility, experienced kernel authors try never to hold a spinlock over any stretch of code that calls any other function at all. But they can be deceived by macros, and by some common exceptions – some kernel functions are intended to be called under spinlock and this encourages the occasional call under spinlock. The standard kernel end request
function (which is used to signal that processing of a kernel read/write request has completed) is perhaps the most common example – it is intended to be called with the I/O spinlock held. The existence of such conventions and the occasional changes to them that take place can cause sufficient confusion that sleep under spinlock does occur often in practice. Usually, the author has called some function under spinlock that was at one time safe to call under spinlock (because it did not sleep) and the function has since been altered so that it now does sleep. Simply asking for or freeing kernel heap memory is enough to allow a function to sleep. So the replacement in the code of a statically preallocated structure by one obtained from the heap on first demand is enough to provoke this kind of error. Semi-formally: Definition 1. It is said that a kernel function may sleep if, informally, 1. it is one of a certain set of basic kernel functions, including, among others, kmalloc, kfree, schedule, interruptible sleep on, interruptible sleep on timeout, down, down interruptible; or 2. it calls a function that may sleep. To make the following discussion easier, we will use the term “piece of code” in a formally defined way: Definition 2. A piece of code here means a linear sequence of (semicolon terminated) statements. A “statement” may be a labelled statement, switch statement or conditional, for or while or do loop, a goto, continue, break or return, an inline assembler statement, a block of statements, an expression used as a statement (including an assignment or function call), or an empty statement (semicolon). Gotos and returns indicate termination of a linear code sequence, and may only appear at the end of a sequence, or at the end of a block within the sequence. We say that a piece of code may sleep when its set of execution paths includes a call to a function that may sleep. Formally: Definition 3. A piece of code may sleep if it contains a call to a kernel function that may sleep. A piece of code will take and release spinlocks along its execution paths. A section that begins with a lock call is spinlocked until the following unlock call: Definition 4. It is said of a piece of code that it is spinlocked (once) if, informally, 1. it begins with a call to one of a certain set of basic kernel functions (the spinlock functions), including, among others, spin lock, spin try lock, spin lock irqsave, read lock, write lock, write lock irqsave, read lock irqsave; and
2. it does not contain a call to any of a certain distinct set of basic kernel functions (the spinunlock functions), including, among others, spin unlock, spin unlock irq, spin unlock irqrestore, read unlock, write unlock, read unlock irqrestore, write unlock irqrestore. A spinlocked piece of code begins with a spinlock call and thereafter has an excess of at least one spinlock call over spinunlock calls throughout its length. That is to say, at least one spinlock is actively held through all its execution paths. Definition 5. A piece of code may sleep under spinlock if it is both spinlocked and may sleep. A function may sleep under spinlock if its body contains a piece of code that may sleep under spinlock. A well-formed piece of kernel code will have balanced spinlock and spinunlock calls. In the region under spinlock, the definitions above make a call to a function that may sleep computably a “sleep under spinlock”, as will be detailed below. In the absence of gotos a compositional analysis suffices. Gotos are not common in the kernel code, but they do occur. As to whether they should be treated or ignored, it is a question of how accurate and in what direction the analysis should err on the side of, and that question will be dealt with later. In the following paragraphs we present three syntactic computations, N [. . .], R[. . .], B[. . .], which together determine the spinlock imbalance in a piece of structured code, if there is any imbalance. They respectively represent the spinlock imbalance through to a normal exit (falling off the end of a piece of code), through to a return statement, and to a break statement. This analysis is based on a generic 3-phase programming logic [2, 3]. The logic binds a pre-condition for execution, a post-condition, and an exception-condition to every imperative statement. In the present case, the except-condition represents the state on break out from the normal sequential programming flow via a continue, break or similar imperative. The post-condition represents the state on reaching the end of a piece of code. Given that n is the spinlock call imbalance measured to a certain point in the code, N [. . .]n is the function that measures the imbalance that has been attained by time of the normal exit that occurs through control falling off the end of the piece of code. While-loops and the continue and break statements they may contain will in a few paragraphs time be treated by recursion through computations with B and R, but here first of all for expositional purposes is an approximate stand-alone computation with N alone. Alternatively, it may be regarded as a computation that supposes that there are no continues and breaks within loops. The difference between it and the (refined) computations that follow is in the treatment of loops:
N [a; b; ...; z; ]n = N [b; . . . ; z; ]N [a;]n
(1)
N [spinlock(. . .)]n = n + 1 N [spinunlock(. . .)]n = n − 1 N [if (a) b; else c; ]n = max(N [b; ]n , N [c; ]n ) N [while (a) b; ]n = max(n, N [b; ]n ∗ ∞) N [a = b; ]n = n N [f (a, b, . . . , z); ]n = n The computation on expressions (apart from assignments and function calls used as statements) is suppressed in the above. In Gnu C, expressions can be a sequence of statements ({a; b;. . . z; }) in parentheses and braces, so the computation on expressions need not be completely trivial, but the result of the computation on expressions will always be zero in practice, so we represent it as such above. Incidentally, the value of such a “statement expression” (a block of code in parentheses and braces) is that bound to the last statement in the sequence of statements, which can be and usually is a simple expression. The reason that function calls are evaluated as zero above is that functions are normally already balanced as a whole with respect to spinlock calls, so there will be no imbalance imported by a function call. If there were an imbalance, the error, and it is a coding style error, would already have been flagged by the spinlock imbalance calculation on the function concerned. But of course different spinlock imbalance values than zero can be assigned to particular functions, if necessary. The spinlock call itself is an example of such. On any other statements than those listed explicitly above in (1), the computation evaluates as NaN (Not a Number). That is true of particular of continue and break statements and returns. The convention is that NaN does not count in a max and combines to form another NaN in a sum. This means that paths leading to an early continue or break or return are not counted in the N [. . .] calculation. Only those paths which reach and fall off the end of the code are counted in N [. . .]. Modulo the obvious provisos due to possible invisible aliasing in C (a spinlock call can be invisible if a renamed pointer to the function is used instead): Proposition 6. The computation (1) above overestimates the imbalance in spinlock calls over a piece of code, if there exists any. It is an abstract interpretation [6] of structured C code into the domain of integers. In particular, the N [. . .] measure (1) across a loop whose body is unbalanced is infinite. This is in order to make the abstract interpretation across loops always correct – it is not known a priori how often the loop will cycle, so any positive imbalance must be overestimated to infinity. In normal circumstances a loop body will be balanced, because a spinlock will be taken and released during every turn through the loop. However, the following form is common when the test must be made under spinlock, and it always evaluates (inappropriately) to infinity under the computation above. It is an infinite loop with a conditional break-out:
spinlock(...); while (1) { ... if (...) { spinunlock(...); break; } ... }
(∗)
This example makes it clear that the pieces of code in the loop leading inevitably to a break out of the loop should not be counted in the loop body, since they cannot repeat, but without a full control-flow analysis it is difficult to determine which pieces of code should be counted and which should not. That is why the N calculation given above in general needs refinement in order to be useful. In the following it is refined making use of the promised B and R calculations. The approach simplifies particularly if we make a simplifying assumption: Assumption 1. There is at most one spinlock imbalance in any code considered, and it is positive (i.e. more spinlock than spinunlock calls). Then it is not necessary to overestimate loop imbalances out to infinity, because if the imbalance is in the loop it will be counted as one and not masked by other contributions elsewhere – since they will all be zero –, and if it is elsewhere, then the loop contribution will not mask it because the loop contribution will be zero. In particular, the spinlock call imbalance in an infinite while loop can be calculated looking along the paths to an exceptional break exit from the loop, calculated using the B[. . .] computation given in the following paragraphs.
N [while (1) b; ]n = B[b; ]n
(10 )
and in the more general case: N [while (a) b; ]n = N [while (1) {if (˜a) break; b; }]n = B[if (˜a) break; b; ]n The calculation (10 ) amounts to supposing that each while-loop is traversed exactly once, thus treating it like an if-statement. The formal statement that this calculation suffices is: Proposition 7. Under the simplifying assumption of only at most one spinlock call imbalance, the computation (10 ) elaborated by (2,3) in the following paragraphs overestimates the imbalance of spinlock calls if there is any. The modified calculation gives the example code fragment (∗) an evaluation of zero, which is correct. The loop evaluates as -1, since there is a spinunlock call on the way out of it, and there is a spinlock call before the loop, which counts
as +1. The B[. . .] (break) calculation on the loop discards the branch that does not lead to the break, leaving only the -1 on the break path. Picking out the code sequences which lead to a break within a structured loop is done by the parser. The imbalance to an exceptional exit via break is given in full by the following computation: B[a; b; . . . ; z; ]n = max(B[a]n , B[b; . . . ; z]N [a;]n )
(2)
B[if (a) b; else c; ]n = max(B[b; ]n , B[c; ]n ) B[break; ]n = n Any other statement than those listed simply gets the value NaN (Not a Number), and by convention, max(x, NaN) = x. So a code path that does not terminate in a break is simply not counted here. In similar style, the following computation calculates the spinlock call imbalance along a path leading to a return statement: R[a; b; . . . ; z; ]n = max(R[a]n , R[b; . . . ; z]N [a;]n )
(3)
R[if (a) b; else c; ]n = max(R[b; ]n , R[c; ]n ) R[while (a) b; ]n = R[b; ]n R[return a; ]n = n and any other statement gets the value NaN. This gives the spinlock imbalance in a function definition: it is R[b; ] where b; is the body of the function. If the function has a void return type, then it may be necessary to add an extra return at the end of the code body before processing. The main point of the (slightly contorted) computations above, is, however, that they can be carried out in a side-effecting way, as stated below: Proposition 8. The subscript n in the computations for N , B and R above represents the spinlock imbalance at the start of the code that corresponds to the argument of N , B, R. It suffices to apply R to a function body, and maintain the count n, starting from 0. As the code is deconstructed syntactically by the recursion, the computation (of all three measures at once) moves down various code paths, incrementing and decrementing n. Those paths leading to an if-statement one of whose branches leads to a return and the other to a break, for example, will still be traversed exactly once by the calculation, which will then split into sub-calculations for the two branches. The sub-calculations may eventually be discarded (if for example, the top-level calculation is an N and neither branch from the if-statement terminates “normally”), but the value of n at any point on the linear sequence leading to the if-statement will not change as the calculation progresses. Proposition 9. Sleep under spinlock is detected when a call to a function that sleeps is made at a point in the code where the value of n in the computation above for the spinlock imbalance is positive.
What to do about gotos? The approach described can be extended to deal with any finite number of labelled statements that are the target of gotos. But once gotos become an influential factor in the evaluation of code, the situation is already desperate for the code! Their use in the Linux kernel is generally restricted to code for state-machines, in which the jumps are all similar to the cases in a switch. However there are other occasions where they are difficult to treat. One approach is to perform a full analysis along those execution paths that lead forwards, discarding the backwards-going paths. Another approach is to treat each labelled statement as the start of an (interior) subroutine and gotos as calls to that subroutine, at which point the analysis given so far in this paper applies.
3
Example
The following code comes from the request-handling routine of a network-adapted version of Alessandro Rubini’s sbull device, a simple block device that stores data in a block of reserved memory and returns it on request. The code deals with the queue of requests sent by the kernel to the device. while(1) { INIT_REQUEST;
/* returns when queue is empty */
/* Which "device" are we using? */ device = sbull_locate_device (CURRENT); if (device == NULL) { end_request(0); continue; } /* Perform the transfer and clean up. */ wake_up_interruptible(&cli); interruptible_sleep_on(&dbr); spin_lock(&device->lock); status = sbull_transfer(device, CURRENT); spin_unlock(&device->lock); end_request(status); }
Among the dangerous coding practices in this code is that it calls a routine, sbull transfer, under spinlock, so that routine must not sleep. However, the whole routine here is itself called under spinlock – the global kernel “I/O” spinlock, and it is indeed essential that the end request routine be called with that spinlock held. There is no call to unlock the I/O spinlock here, so it is held throughout, so the interruptible sleep on routine (which, as the name implies, does sleep) is implicitly being called under the I/O spinlock! Running the prototype code analyser over the 592 lines (16K characters) of the sbull driver code, it first of all expands the code to 28165 lines (34K characters) by pulling in the following #include header files, and their #includes, and expanding the macros:
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
/* printk() */ /* kmalloc() */ /* vmalloc() */ /* everything... */ /* error codes */ /* size_t */ /* O_ACCMODE */ /* HDIO_GETGEO */ /* cli(), *_flags */ /* access_ok() */ "sbull.h" /* local definitions */ /* blk_ioctl() */
The analyser detects over 10000 references to functions and local variables in the expanded code, which it resolves down to base definition locations, and it finds six new inline functions in the #include chain that can sleep: smb_lock_server lock_parent double_down triple_down double_lock lock_super
is is is is is is
sleepy sleepy sleepy sleepy sleepy sleepy
because because because because because because
it it it it it it
calls calls calls calls calls calls
down down down down double_down down
and identifies three functions in the sbull driver that can sleep, of which the first is the function whose code body is largely quoted above: sbull_request sbull_init sbull_cleanup
is sleepy because it calls interruptible_sleep_on is sleepy because it calls kfree is sleepy because it calls kfree
It identifies no “sleep under spinlock” errors. Without knowing that the sbull request function is called under the I/O spinlock, the analyser has nothing to flag, but it does notice that the routine can sleep. If the request function code is itself altered by interchanging just two lines so that it reads (line numbers added at left, lines 418 and 419 are the interchanged pair): 416 417 418 419
... /* Perform the transfer and clean up. */ wake_up_interruptible(&cli); spin_lock(&device->lock); interruptible_sleep_on(&dbr); ...
then the analyser spots the problem and flags it:
*** *** sleep under spinlock at file="sbull.c" about line=419 ***
(the location is sometimes approximate for implementational reasons).
4
Other spinlock coding errors
In this section deadlock detection techniques not yet implemented in the prototype analyser are discussed. There are two further common coding errors by authors leading to deadlock in the Linux kernel: 1. taking a spinlock again while still under the same spinlock; 2. taking two different spinlocks in different orders, one under the other. The algorithm of the last section can be altered to indicate which spinlock, given a suitable classification method, is currently active at any point in the code. But finding out that a called function takes a particular spinlock is type information. The information on which spinlocks are taken by a function can be collected in the same way as information on whether a function sleeps or not is collected – by looking for instances of spinlock calls within the body, but taking note of the parameter to the spinlock call and constructing different counts for the different objects referenced. There are generally two types of object to which the parameter resolves: (i) the address of a global, itself a spinlock structure; (ii) the address of a spinlock field in a structure passed by address as a parameter to the function. What can be said in the later case is usually “the function takes a spinlock referenced in field x of the first parameter to the function”, or similar. The “first parameter” will usually be a structure representing the whole device to a device driver, or some other equally pivotal object for the code module in question. There are two typing techniques that can be applied to the spinlocks encountered: (a) type as object – using the built-in resolution capabilities of the analyser to resolve spinlock references back to an original, also extending the resolution mechanism to trace back through function call parameters; (b) sub-classing the spinlock type – assigning a special type to the spinlock field in a given structure type, and modify the type mechanism of C so that functions are determined to have the following kind of extra trait (italics): int f(*A,...) takes spinlock of type *A lock, ...;
The case (i), global spinlock references, is handled well enough via the mechanism (a), aliasing in C notwithstanding. A modified spinlock imbalance calculation can determine if a call to a function that takes a spinlock x is made while the spinlock x is already being held. But, particularly when resolving spinlock references back through function parameters in function calls, the technique is not guaranteed to detect a real error even when it is there. That is because opaque aliasing in C can easily hide the identity of the spinlocks. If an error is detected, however, then it is guaranteed to be real. That is, false negatives are possible, but not false positives. In the case (ii), spinlocks chiefly passed through the fields of structs passed by reference, the technique (b) may be more suitable. A modified spinlock imbalance calculation can determine if a call to a function that takes a spinlock of type t is ever made while a spinlock of type t is being held. This test does not necessarily represent a real programming error when it triggers, but if there is a real error, then this analysis technique detects it. That is, false positives are possible, but there are no false negatives. Together, the two tests are very informative. Detecting inconsistent uses of spinlock A under spinlock B versus spinlock B under spinlock A is a question of typing the spinlocks, so that they get the following kind of extra trait (italics): spinlock &lo->lock is used under spinlock &io lock, ...; This is a call graph, and it can be constructed as the spinlock calls are encountered during a run of a modified spinlock imbalance counting algorithm, one that counts per-spinlock. If the spinlock call graph contains a cycle, then there is a possible deadlock in the code. The technique gives no false positives, but does allow false negatives, through opaque C aliasing. This technique can also be adapted to work with types. It suffices to note the calls of which type of spinlock under which other type of spinlock: spinlock *A lock is used under spinlock *B lock, ...; In that case the results will show false positives, but no false negatives.
5
Source Code
Source code for the prototype C analyser discussed in this article can be found at ftp://oboe.it.uc3m.es/pub/Programs/c-1.0.tgz. The code was released under GPL on the Linux kernel mailing list in March 2003. In an earlier correspondence, Linus Torvalds had stated to one of the authors of the present paper that it was his aim also to produce such an analyser. The publication of the code excited comment from at least one other group aiming at analysing kernel C code. David Wagner remarked that the group were producing an alpha release of their own design [5] (MOPS – MOdelchecking Programs for Security properties, see http://www.cs.berkeley.edu/~daw/mops/ – is a modelchecker which checks for violation of certain “defensive programming” rules in C code).
6
Summary
This article has described a C code static analyser which detects code authoring abuses that lead to certain types of deadlock in the Linux operating system kernel on multiprocessor architectures. An example run has been illustrated. This is deliberately a “cheap” implementation of a formal method that adds an assurance of safety to Linux kernel code. It is intended to be widely accessible – it requires no special tool support, just a C compiler and Makefile. The underlying thinking here is that it is much more important in the context of open source to be 1% useful to 10,000 authors, than to be 100% useful to only 10 of them. It does not matter if there are better techniques “out there” because they are neither cheap (in the appropriate sense, whether it be financial or intellectual investment) nor widely available in the Linux context. It is important to set an extremely low entry barrier for the use of a formal method in that context.
References 1. P.T. Breuer and J.P. Bowen. A PREttier Compiler-Compiler: Generating higher order parsers in C. Software — Practice & Experience, 25(11):1263–1297, November 1995. 2. P.T. Breuer, N. Mart´ınez Madrid, L. S´ anchez, A. Mar´ın, and C. Delgado Kloos. A formal method for specification and refinement of real-time systems. In Proc. 8’th EuroMicro Workshop on Real Time Systems, pages 34–42. IEEE Press. July 1996. L’aquilla, Italy. 3. P.T. Breuer, C. Delgado Kloos, N. Mart´ınez Madrid, A. L´ opez Marin, L. S´ anchez. A Refinement Calculus for the Synthesis of Verified Digital or Analog Hardware Descriptions in VHDL. ACM Transactions on Programming Languages and Systems (TOPLAS) 19(4):586–616, July 1997. 4. P.T. Breuer. A Formal Model for the Block Device Subsystem of the Linux Kernel. In Proc. 5th International Conference on Formal Engineering Methods, ICFEM 2003, pages 599-619. J.S. Dong and J. Woodcock (Eds), Springer-Verlag, LNCS 2885. Singapore, 5-7 Nov. 2003. 5. Hao Chen and David Wagner. MOPS: An infrastructure for examining security properties of software. In Proc. Ninth ACM Conference on Computer and Communications Security (CCS-9). Washington DC, 18-22 November 2002. 6. P. Cousot and R. Cousot, Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints, In Proc. 4th ACM Symposium on the Principles of Programming Languages, pages 238–252, 1977. 7. E.S. Raymond. The Cathedral and the Bazaar, Cambridge: O’Reilly & Associates, 1999. 8. A. Rubini. Linux Device Drivers. O’Reilly, Sebastopol CA, Feb. 1998.