⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 basics of device drivers.htm

📁 What is this ``device driver stuff anyway? Here s a very short introduction to the concept.
💻 HTM
📖 第 1 页 / 共 3 页
字号:
<html><head><title>Device Driver Basics</title>

<link rel="owner" href="mailto:">
<script language="JavaScript">
<!-- hide this

function help(message) {
  self.status = message;
  return true;
}
// stop hiding -->
</script></head>

<body>
<strong>The
HyperNews <a href="http://tldp.org/LDP/khg/HyperNews/get/khg.html">Linux KHG</a>
Discussion Pages</strong>
<hr>
<h3>Device Driver Basics</h3>

<p>We will assume that you decide that you do not wish to write
a user-space device, and would rather implement your device in
the kernel.  You will probably be writing writing two files, a
<tt>.c</tt> file and a <tt>.h</tt> file, and possibly modifying
other files as well, as will be described below.  We will refer
to your files as foo.c and foo.h, and your driver will be the
<tt>foo</tt> driver.

</p><h4>Namespace</h4>

<p>One of the first things you will need to do, before writing
any code, is to name your device.  This name should be a short
(probably two or three character) string.  For instance, the
parallel device is the ``<tt>lp</tt>'' device, the floppies are
the ``<tt>fd</tt>'' devices, and SCSI disks are the
``<tt>sd</tt>'' devices.  As you write your driver, you will
give your functions names prefixed with your chosen string to
avoid any namespace confusion.  We will call your prefix
<tt>foo,</tt> and give your functions names like
<tt>foo_read(), foo_write(),</tt> etc.

</p><h4>Allocating memory</h4>

<p>Memory allocation in the kernel is a little different from
memory allocation in normal user-level programs.  Instead of
having a <tt>malloc()</tt> capable of delivering almost
unlimited amounts of memory, there is a <tt>kmalloc()</tt>
function that is a bit different:
</p><ul>
<li>Memory is provided in pieces whose size is a power of 2, except
that pieces larger than 128 bytes are allocated in blocks whose size
is a power of 2 minus some small amount for overhead.
You can request any odd size, but memory will not be used any more
efficiently if you request a 31-byte piece than it will if you request
a 32 byte piece.  Also, there is a limit to the amount of memory that
can be allocated, which is currently 131056 bytes.
</li><li><tt>kmalloc()</tt> takes a second argument, the priority.  This is
used as an argument to the <tt>get_free_page()</tt> function, where it
is used to determine when to return.  The usual priority is
<tt>GFP_KERNEL</tt>.  If it may be called from within an interrupt, use
<tt>GFP_ATOMIC</tt> and be truly prepared for it to fail (don't panic).
This is because if you specify <tt>GFP_KERNEL</tt>, <tt>kmalloc()</tt> may
sleep, which cannot be done on an interrupt.  The other option is
<tt>GFP_BUFFER</tt>, which is used only when the kernel is allocating buffer
space, and never in device drivers.
</li></ul>

<p>To free memory allocated with <tt>kmalloc()</tt>, use one of
two functions: <tt>kfree()</tt> or <tt>kfree_s()</tt>.  These
differ from <tt>free()</tt> in a few ways as well:
</p><ul>
<li><tt>kfree()</tt> is a macro which calls <tt>kfree_s()</tt> and acts
like the standard <tt>free()</tt> outside the kernel.
</li><li>If you know what size object you are freeing, you can speed
things up by calling <tt>kfree_s()</tt> directly.  It takes two
arguments:  the first is the pointer that you are freeing, as in the
single argument to <tt>kfree()</tt>, and the second is the size of the
object being freed.
</li></ul>

<p>See <a href="http://tldp.org/LDP/khg/HyperNews/get/devices/reference.html">Supporting Functions</a> for
more information on <tt>kmalloc()</tt>, <tt>kfree()</tt>, and
other useful functions.

</p><p>Be gentle when you use kmalloc.  Use only what you have to.
Remember that kernel memory is unswappable, and thus allocating
extra memory in the kernel is a far worse thing to do in the
kernel than in a user-level program.  Take only what you need,
and free it when you are done, unless you are going to use it
right away again.

</p><h4>Character vs. block devices</h4>

<p>There are two main types of devices under all Unix systems,
character and block devices.  Character devices are those for
which no buffering is performed, and block devices are those
which are accessed through a cache.  Block devices must be
random access, but character devices are not required to be,
though some are.  Filesystems can only be mounted if they are
on block devices.

</p><p>Character devices are read from and written to with two
function: <tt>foo_read()</tt> and <tt>foo_write()</tt>.  The
<tt>read()</tt> and <tt>write()</tt> calls do not return until
the operation is complete.  By contrast, block devices do not
even implement the <tt>read()</tt> and <tt>write()</tt>
functions, and instead have a function which has historically
been called the ``strategy routine.'' Reads and writes are done
through the buffer cache mechanism by the generic functions
<tt>bread(),</tt> <tt>breada(),</tt> and <tt>bwrite()</tt>.
These functions go through the buffer cache, and so may or may
not actually call the strategy routine, depending on whether or
not the block requested is in the buffer cache (for reads) or
on whether or not the buffer cache is full (for writes).  A
request may be asyncronous: <tt>breada()</tt> can request the
strategy routine to schedule reads that have not been asked
for, and to do it asyncronously, in the background, in the
hopes that they will be needed later.

</p><p>The sources for character devices are kept in drivers/char/,
and the sources for block devices are kept in drivers/block/.
They have similar interfaces, and are very much alike, except
for reading and writing.  Because of the difference in reading
and writing, initialization is different, as block devices have
to register a strategy routine, which is registered in a
different way than the <tt>foo_read()</tt> and
<tt>foo_write()</tt> routines of a character device driver.
Specifics are dealt with in
<a href="http://tldp.org/LDP/khg/HyperNews/get/devices/char.html#init">Character Device Initialization</a> and
<a href="http://tldp.org/LDP/khg/HyperNews/get/devices/block.html#init">Block Device Initialization</a>.

</p><h4>Interrupts vs. Polling</h4>

<p>Hardware is slow.  That is, in the time it takes to get
information from your average device, the CPU could be off
doing something far more useful than waiting for a busy but
slow device.  So to keep from having to <b>busy-wait</b> all
the time, <b>interrupts</b> are provided which can interrupt
whatever is happening so that the operating system can do some
task and return to what it was doing without losing
information.  In an ideal world, all devices would probably
work by using interrupts.  However, on a PC or clone, there are
only a few interrupts available for use by your peripherals, so
some drivers have to poll the hardware: ask the hardware if it
is ready to transfer data yet.  This unfortunately wastes time,
but it sometimes needs to be done.

</p><p>Some hardware (like memory-mapped displays) is as fast
as the rest of the machine, and does not generate output
asyncronously, so an interrupt-driven driver would be rather
silly, even if interrupts were provided.

</p><p>In Linux, many of the drivers are interrupt-driven, but some
are not, and at least one can be either, and can be switched
back and forth at runtime.  For instance, the <tt>lp</tt>
device (the parallel port driver) normally polls the printer to
see if the printer is ready to accept output, and if the
printer stays in a not ready phase for too long, the driver
will sleep for a while, and try again later.  This improves
system performance.  However, if you have a parallel card that
supplies an interrupt, the driver will utilize that, which will
usually make performance even better.

</p><p>There are some important programming differences between
interrupt-driven drivers and polling drivers.  To understand
this difference, you have to understand a little bit of how
system calls work under Unix.  The kernel is not a separate
task under Unix. Rather, it is as if each process has a copy of
the kernel.  When a process executes a system call, it does not
transfer control to another process, but rather, the process
changes execution modes, and is said to be ``in kernel mode.''
In this mode, it executes kernel code which is trusted to be
safe.

</p><p>In kernel mode, the process can still access the user-space
memory that it was previously executing in, which is done
through a set of macros: <tt>get_fs_*()</tt> and
<tt>memcpy_fromfs()</tt> read user-space memory, and
<tt>put_fs_*()</tt> and <tt>memcpy_tofs()</tt> write to
user-space memory.  Because the process is still running, but
in a different mode, there is no question of where in memory to
put the data, or where to get it from.  However, when an
interrupt occurs, any process might currently be running, so
these macros cannot be used--if they are, they will either
write over random memory space of the running process or cause
the kernel to panic.

</p><p>Instead, when scheduling the interrupt, a driver must also
provide temporary space in which to put the information, and
then sleep.  When the interrupt-driven part of the driver has
filled up that temporary space, it wakes up the process, which
copies the information from that temporary space into the
process' user space and returns.  In a block device driver,
this temporary space is automatically provided by the buffer
cache mechanism, but in a character device driver, the driver
is responsible for allocating it itself.

</p><h4>The sleep-wakeup mechanism</h4>

<b>[Begin by giving a general description of how sleeping is used
and what it does.  This should mention things like all processes
sleeping on an event are woken at once, and then they contend for the
event again, etc...]</b>

<p>Perhaps the best way to try to understand the Linux
sleep-wakeup mechanism is to read the source for the
<tt>__sleep_on()</tt> function, used to implement both the
<tt>sleep_on()</tt> and <tt>interruptible_sleep_on()</tt>
calls.
</p><pre>static inline void __sleep_on(struct wait_queue **p, int state)
{
    unsigned long flags;
    struct wait_queue wait = { current, NULL };

    if (!p)
        return;
    if (current == task[0])
        panic("task[0] trying to sleep");
    current-&gt;state = state;
    add_wait_queue(p, &amp;wait);
    save_flags(flags);
    sti();
    schedule();
    remove_wait_queue(p, &amp;wait);
    restore_flags(flags);
}
</pre>

<p>A <tt>wait_queue</tt> is a circular list of pointers to task
structures, defined in <tt>&lt;linux/wait.h&gt;</tt> to be
</p><pre>struct wait_queue {
    struct task_struct * task;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -