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

📄 basics of device drivers.htm

📁 What is this ``device driver stuff anyway? Here s a very short introduction to the concept.
💻 HTM
📖 第 1 页 / 共 3 页
字号:
    struct wait_queue * next;
};
</pre>
<tt>state</tt> is either <tt>TASK_INTERRUPTIBLE</tt> or
<tt>TASK_UNINTERUPTIBLE</tt>, depending on whether or not the
sleep should be interruptable by such things as system calls.
In general, the sleep should be interruptible if the device is
a slow one; one which can block indefinitely, including
terminals and network devices or pseudodevices.

<p><tt>add_wait_queue()</tt> turns off interrupts, if they were
enabled, and adds the new <tt>struct wait_queue</tt> declared
at the beginning of the function to the list <tt>p</tt>.  It
then recovers the original interrupt state (enabled or
disabled), and returns.

</p><p><tt>save_flags()</tt> is a macro which saves the process
flags in its argument.  This is done to preserve the previous
state of the interrupt enable flag.  This way, the
<tt>restore_flags()</tt> later can restore the interrupt state,
whether it was enabled or disabled. <tt>sti()</tt> then allows
interrupts to occur, and <tt>schedule()</tt> finds a new
process to run, and switches to it.  Schedule will not choose
this process to run again until the state is changed to
<tt>TASK_RUNNING</tt> by <tt>wake_up()</tt> called on the same
wait queue, <tt>p</tt>, or conceivably by something else.

</p><p>The process then removes itself from the
<tt>wait_queue</tt>, restores the orginal interrupt condition
with <tt>restore_flags()</tt>, and returns.

</p><p>Whenever contention for a resource might occur, there needs
to be a pointer to a <tt>wait_queue</tt> associated with that
resource.  Then, whenever contention does occur, each process
that finds itself locked out of access to the resource sleeps
on that resource's <tt>wait_queue</tt>.  When any process is
finished using a resource for which there is a
<tt>wait_queue</tt>, it should wake up and processes that might
be sleeping on that <tt>wait_queue</tt>, probably by calling
<tt>wake_up()</tt>, or possibly
<tt>wake_up_interruptible()</tt>.

</p><p>If you don't understand why a process might want to sleep,
or want more details on when and how to structure this
sleeping, I urge you to buy one of the operating systems
textbooks listed in the
<a href="http://tldp.org/LDP/khg/HyperNews/get/bib/bib.html">Annotated Bibliography</a> and look up
<b>mutual exclusion</b> and <b>deadlock</b>.

</p><h4>More advanced sleeping</h4>

<p>If the <tt>sleep_on()</tt>/<tt>wake_up()</tt> mechanism in
Linux does not satisfy your device driver needs, you can code
your own versions of <tt>sleep_on()</tt> and <tt>wake_up()</tt>
that fit your needs.  For an example of this, look at the
serial device driver (drivers/char/serial.c) in function
<tt>block_til_ready()</tt>, where quite a bit has to be done
between the <tt>add_wait_queue()</tt> and the
<tt>schedule()</tt>.

</p><h4>The VFS</h4>

<p>The Virtual Filesystem Switch, or <b>VFS</b>, is the
mechanism which allows Linux to mount many different
filesystems at the same time.  In the first versions of Linux,
all filesystem access went straight into routines which
understood the <tt>minix</tt> filesystem.  To make it possible
for other filesystems to be written, filesystem calls had to
pass through a layer of indirection which would switch the call
to the routine for the correct filesystem.  This was done by
some generic code which can handle generic cases and a
structure of pointers to functions which handle specific cases.
One structure is of interest to the device driver writer; the
<tt>file_operations</tt> structure.

</p><p>From /usr/include/linux/fs.h:
</p><pre>struct file_operations {
    int  (*lseek)   (struct inode *, struct file *, off_t, int);
    int  (*read)    (struct inode *, struct file *, char *, int);
    int  (*write)   (struct inode *, struct file *, char *, int);
    int  (*readdir) (struct inode *, struct file *, struct dirent *, int count);
    int  (*select)  (struct inode *, struct file *, int, select_table *);
    int  (*ioctl)   (struct inode *, struct file *, unsigned int, unsigned int);
    int  (*mmap)    (struct inode *, struct file *, unsigned long, size_t, int, unsigned long);
    int  (*open)    (struct inode *, struct file *);
    void (*release) (struct inode *, struct file *);
};
</pre>
Essentially, this structure constitutes a parital list of
the functions that you may have to write to create your driver.

<p>This section details the actions and requirements of the
functions in the <tt>file_operations</tt> structure.  It
documents all the arguments that these functions take.  <b>[It
should also detail all the defaults, and cover more carefully
the possible return values.]</b>

</p><h4>The <tt>lseek()</tt> function</h4>

<p>This function is called when the system call
<tt>lseek()</tt> is called on the device special file
representing your device.  An understanding of what the system
call <tt>lseek()</tt> does should be sufficient to explain this
function, which moves to the desired offset.  It takes these
four arguments:
</p><dl>
<dt><tt>struct inode * inode</tt>
</dt><dd>Pointer to the inode structure for this device.
</dd><dt><tt>struct file * file</tt>
</dt><dd>Pointer to the file structure for this device.
</dd><dt><tt>off_t offset</tt>
</dt><dd>Offset <b>from origin</b> to move to.
</dd><dt><tt>int origin</tt>
</dt><dd>0 = take the offset from absolute offset 0 (the beginning).<br>
1 = take the offset from the current position.<br>
2 = take the offset from the end.
</dd></dl>
<tt>lseek()</tt> returns <tt>-errno</tt> on error, or the absolute
position (&gt;= 0) after the lseek.

<p>If there is no <tt>lseek()</tt>, the kernel will take the
default action, which is to modify the <tt>file-&gt;f_pos</tt>
element.  For an <tt>origin</tt> of 2, the default action is to
return <tt>-EINVAL</tt> if <tt>file-&gt;f_inode</tt> is NULL,
otherwise it sets <tt>file-&gt;f_pos</tt> to
<tt>file-&gt;f_inode-&gt;i_size</tt> + <tt>offset</tt>.  Because of
this, if <tt>lseek()</tt> should return an error for your
device, you must write an <tt>lseek()</tt> function which
returns that error.

</p><h4>The <tt>read()</tt> and <tt>write()</tt> functions</h4>

<p>The read and write functions read and write a character
string to the device.  If there is no <tt>read()</tt> or
<tt>write()</tt> function in the <tt>file_operations</tt>
structure registered with the kernel, and the device is a
character device, <tt>read()</tt> or <tt>write()</tt> system
calls, respectively, will return <tt>-EINVAL</tt>.  If the
device is a block device, these functions should not be
implemented, as the VFS will route requests through the buffer
cache, which will call your strategy routine.  The
<tt>read</tt> and <tt>write</tt> functions take these
arguments:
</p><dl>
<dt><tt>struct inode * inode</tt>
</dt><dd>This is a pointer to the inode of the device special file which was
accessed.  From this, you can do several things, based on the
<tt>struct inode</tt> declaration about 100 lines into
/usr/include/linux/fs.h.  For instance, you can find the minor number
of the file by this construction: <tt>unsigned int minor =
MINOR(inode-&gt;i_rdev);</tt> The definition of the <tt>MINOR</tt> macro is in
<tt><linux></linux></tt>, as are many other useful definitions.  Read fs.h
and a few device drivers for more details, and see
<a href="http://tldp.org/LDP/khg/HyperNews/get/devices/reference.html">Supporting Functions</a>
for a short description.  <tt>inode-&gt;i_mode</tt>
can be used to find the mode of the file, and there are macros
available for this, as well.
</dd><dt><tt>struct file * file</tt>
</dt><dd>Pointer to file structure for this device.
</dd><dt><tt>char * buf</tt>
</dt><dd>This is a buffer of characters to read or write.  It is
located in <i>user-space</i> memory, and therefore must be
accessed using the <tt>get_fs*(), put_fs*(),</tt> and
<tt>memcpy*fs()</tt> macros detailed in <a href="http://tldp.org/LDP/khg/HyperNews/get/devices/reference.html">Supporting Functions</a>.  User-space
memory is inaccessible during an interrupt, so if your driver
is interrupt driven, you will have to copy the contents of your
buffer into a queue.
</dd><dt><tt>int count</tt>
</dt><dd>This is a count of characters in <tt>buf</tt> to
be read or written.  It is the size of <tt>buf</tt>, and is how you know
that you have reached the end of <tt>buf</tt>, as <tt>buf</tt> is not
guaranteed to be null-terminated.
</dd></dl>

<h4>The <tt>readdir()</tt> function</h4>

<p>This function is another artifact of
<tt>file_operations</tt> being used for implementing
filesystems as well as device drivers.  Do not implement it.
The kernel will return <tt>-ENOTDIR</tt> if the system call
<tt>readdir()</tt> is called on your device special file.

</p><h4>The <tt>select()</tt> function</h4>

<p>The <tt>select()</tt> function is generally most useful with
character devices.  It is usually used to multiplex reads
without polling--the application calls the <tt>select()</tt>
system call, giving it a list of file descriptors to watch, and
the kernel reports back to the program on which file descriptor
has woken it up.  It is also used as a timer.  However, the
<tt>select()</tt> function in your device driver is not
directly called by the system call <tt>select()</tt>, and so
the <tt>file_operations</tt> <tt>select()</tt> only needs to do
a few things. Its arguments are:
</p><dl>
<dt><tt>struct inode * inode</tt>
</dt><dd>Pointer to the inode structure for this device.
</dd><dt><tt>struct file * file</tt>
</dt><dd>Pointer to the file structure for this device.
</dd><dt><tt>int sel_type</tt> 
</dt><dd>The select type to perform:
<table border="1">
<tbody><tr><td><tt>SEL_IN</tt></td><td>read</td></tr>
<tr><td><tt>SEL_OUT</tt></td><td>write</td></tr>
<tr><td><tt>SEL_EX</tt></td><td>exception</td></tr>
</tbody></table>
</dd><dt><tt>select_table * wait</tt>
</dt><dd>If <tt>wait</tt> is not NULL and there is no error condition caused by
the select, <tt>select()</tt> should put the process to sleep, and
arrange to be woken up when the device becomes ready, usually through
an interrupt.  If <tt>wait</tt> is NULL, then the driver should quickly
see if the device is ready, and return even if it is not.  The
<tt>select_wait()</tt> function does this already.
</dd></dl>

<p>If the calling program wants to wait until one of the
devices upon which it is selecting becomes available for the
operation it is interested in, the process will have to be put
to sleep until one of those operations becomes available.  This
does <b>not</b> require use of a <tt>sleep_on*()</tt> function,
however.  Instead the <tt>select_wait()</tt> function is used.
(See <a href="http://tldp.org/LDP/khg/HyperNews/get/devices/reference.html">Supporting Functions</a> for the
definition of the <tt>select_wait()</tt> function).  The sleep
state that <tt>select_wait()</tt> will cause is the same as
that of <tt>sleep_on_interruptible()</tt>, and, in fact,
<tt>wake_up_interruptible()</tt> is used to wake up the
process.

</p><p>However, <tt>select_wait()</tt> will not make the process go
to sleep right away.  It returns directly, and the
<tt>select()</tt> function you wrote should then return.  The
process isn't put to sleep until the system call

⌨️ 快捷键说明

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