forked from markkampe/Operating-Systems-Reading
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdriverclasses.html
253 lines (253 loc) · 9.92 KB
/
driverclasses.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<html>
<head>
<title>Device Drivers: Classes and Services</title>
</head>
<body>
<center><h1>Device Drivers: Classes and Services</h1></center>
<H2>Introduction</h2>
<P>
Device drivers represent both:
<ul><dl>
<dt>generalizing abstractions</dt>
<P>
<dd> gathering a myriad of very different devices together
and synthesizing a few general classes
(e.g. disks, network interfaces, graphics adaptors)
and standard models, behaviors and interfaces to
be implemented by all drivers for a given class.</dd>
</p>
<dt>simplifying abstractions</dt>
<P>
<dd> providing an implementation of standard class interfaces
while opaquely encapsulating the details of how to
effectively and efficiently use a particular
device.</dd>
</p>
</dl></ul>
</p>
<P>
For reasons of performance and control, Operating Systems tend not to be
implemented in object oriented languages (does anybody remember JavaOS?).
Yet despite being implemented in simpler languages (often C), Operating
Systems, in device drivers, offer highly evolved examples of a different
realization of class interfaces, derivation, and inheritance.
</P>
<P>
Whether we are talking about storage, networking, video, or human-interface,
the number of available devices is huge and growing. The number and diversity
of these devices creates tremendous demands for object oriented code reuse:
<ul>
<li> We want the system to behave similarly, no matter
what the underlying devices were being used to provide
storage, networking, etc. To ensure this, we would like
most of the higher level functionality to be implemented
in common, higher level modules.</li>
<li> We would like to minimize the cost of developing drivers
to support new devices. This is most easily done if the
majority of the functionality is implemented in common
code that can be inherited by the individual drivers.</li>
<li> As system functionality and performance are improved, we
would like to ensure that those benefits accrue not only
to new device drivers, but also to older device drivers.</li>
</ul>
</P>
<P>
These needs can be satisfied by implementing the higher level
functionality (associated with each general class of device)
in common code that uses per-device implementations of
a standard sub-class driver to operate over a particular device.
This requires:
<ul>
<li> deriving device-driver sub-classes for each of the
major classes of device.</li>
<li> defining sub-class specific interfaces to be implemented
by the drivers for all devices in each of those classes.</li>
<li> creating per-device implementations of those standard
sub-class interfaces.</li>
</ul>
</P>
<H2>Major Driver Classes</h2>
<P>
In the earliest versions of Unix, all devices were divided into
two fundamental classes, which are still present in all Unix
derivatives:
<dl>
<dt>block devices</dt>
<dd>
<P>
These are random-access devices, addressable in
fixed size (e.g. 512 byte, 4K byte) blocks.
Their drivers implement a <em>request</em> method to
enqueue asynchronous DMA requests.
The request descriptor included information about
the desired operation
(e.g. byte count, target device, disk address and
in-memory buffer address) as well as completion
information (how much data was
transferred, error indications) and a condition
variable the requestor could use to await the
eventual completion of the request.
</p>
<p>
A read or write request could be issued for any number
of blocks, but in most cases a large request would be broken
into multiple single-block requests, each of which would
be passed, block at a time, through the system buffer cache.
For this reason block device drivers also implement a
<em>fsync</em> method to flush out any buffered writes.
</p>
</dd>
<dt>character devices</dt>
<P>
<dd>
<P>
These devices may be sequential access, or may be
byte-addressable. They support the standard
synchronous
<em>read(2)</em>, <em>write(2)</em> and (indirectly)
<em>seek(2)</em> operations.
</P>
<P>
For devices that supported DMA, read and write
operations were expected to be done as a single
(potentially very large) DMA transfer between the
device and the buffers in user address space.
</P>
</dd>
</p>
</dl>
<P>
A key point here is that, even in the oldest Unix systems,
device drivers were divided into distinct classes (implementing
different interfaces) based on the needs of distinct classes
of clients:
<ul>
<li> Block devices were designed to be used, within the operating
system, by file systems, to access disks. Forcing all I/O
to go through the system buffer cache is almost surely the
right thing to do with file system I/O.
</li>
<li> Character devices were designed to be used directly by
applications. The potential for large DMA transfers
directly between the device and user-space buffers
meant that character (or <em>raw</em>) I/O operations
might be much more efficient than the corresponding
requests to a block device.
</li>
</ul>
</p>
<P>
These two major classes of device were not mutually exclusive.
A single driver could export both block and character interfaces.
A file system would be mounted on top of the block device, while
back-up and integrity-checking software might access the disk
through its (potentially much more efficient) character device*.
All device drivers support <em>initialize</em> and <em>cleanup</em>
methods (for dynamic module loading and unloading),
<em>open</em> and <em>release</em> methods (roughly corresponding
to the <em>open(2)</em> and <em>close(2)</em> system calls),
and an optional catch-all <em>ioctl(2)</em> method.
</p>
<center>
<img src="driver_ddi.jpg" alt="block/char Venn diagram">
</center>
<P>
*In more contemporary systems, it is possible for a client to specify
that block I/O should not be passed through the system buffer cache.
</p>
<H2>Driver sub-classes</h2>
<P>
Given the fundamental importance of file systems and disks to operating
systems, it is not surprising that block device drivers would have been
singled out as a special sub-class in even the earliest versions of
Unix. But as system functionality evolved, the operating system
began to implement higher level services for other sub-classes of
devices:
<UL>
<li> input editing and output translation for terminals and virtual terminals</li>
<li> address binding and packet sending/receipt for network interfaces</li>
<li> display mapping and window management for graphics adaptors</li>
<li> character-set mapping for keyboards</li>
<li> cursor positioning for pointing devices</li>
<li> sample mixing, volume and equalization for sound devices</li>
</UL>
</P>
<center>
<img src="driver_clients.jpg" height="400">
</center>
<P>
And as each of these sub-systems evolved, new device driver methods*
were defined to enable more effective communication between the
higher level frameworks and the lower level device drivers in each
sub-class. Each of these sub-class specific interfaces is referred
to as a <em>Device Driver Interface</em> (DDI).
</p>
<center>
<img src="driver_sub_ddi.jpg" height="400">
</center>
<P>
*It should be noted that in some cases, the higher frameworks have
been implemented in user-mode, so that some of the new interfaces
have been specified as behavior rather than new methods.
</P>
<P>
The rewards for this structure are:
<ul>
<li> Sub-class drivers become easier to implement because so much
of the important functionality is implemented in higher level
software.</li>
<li> The system behaves identically over a wide range of different
devices.</li>
<li> Most functionality enhancements will be in the higher
level code, and so should automatically work on all devices
within that sub-class.</li>
</ul>
<p>
But the price for these rewards is that all device drivers must implement
exactly the same interfaces:
<ul>
<li> if a driver does not (correctly) implement the standard interfaces
for its device sub-class, it will not work with the higher level
software.</li>
<li> if a driver implements additional functionality (not defined in
the standard interfaces for its device sub-class), those features
will not be exploited by the standard higher level software.</li>
</ul>
</p>
<h2>Services for Device Drivers</h2>
<P>
It is nearly impossible to implement a completely self-contained device
driver. Most device drivers are likely to require a range of resources
and services from the operating system:
<ul>
<li> dynamic memory allocation </li>
<li> I/O and bus resource allocation and management</li>
<li> condition variable operations (wait and signal) </li>
<li> mutual exclusion </li>
<li> control of, and being called to service interrupts </li>
<li> DMA target pin/release, scatter/gather map management </li>
<li> configuration/registry services</li>
</ul>
The collection of services, exposed by the operating system for
use by device drivers is sometimes referred to as the
<em>Driver-Kernel Interface</em> (DKI).
Interface stability for DKI functions is every bit as important as it is
for the DDI entry points. If an operating system eliminates or incompatibly
changes a DKI function, device drivers that depend on that function may
cease working. The requirement to maintain stable DKI entry points may
greatly constrain our ability to evolve our operating system implementation.
Similar issues arise for other classes of dynamically loadable kernel
modules (such as network protocols and file systems).
</p>
<h2>Conclusion</h2>
<P>
Device drivers demonstrate an evolution from a basic super-class (character
devices) into an ever-expanding hierarchy of derived sub-classes. But unlike
traditional class derivation, where sub-class implementations inherit most of
their implementation from their parent, we see a different sort of inheritance.
While each new sub-class and instance is likely to be a new implementation,
what they inherit is pre-existing higher level frameworks that do
do most of their work for them.
</P>
</body>
<html>