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

📄 mousedrivers.tmpl

📁 嵌入式系统设计与实例开发实验教材二源码 多线程应用程序设计 串行端口程序设计 AD接口实验 CAN总线通信实验 GPS通信实验 Linux内核移植与编译实验 IC卡读写实验 SD驱动使
💻 TMPL
📖 第 1 页 / 共 3 页
字号:
  <programlisting>        save_flags(flags);        cli();        while(!mouse_event)        {                if(file-&gt;f_flags&amp;O_NDELAY)                {                        restore_flags(flags);                        return -EAGAIN;                }                interruptible_sleep_on(&amp;mouse_wait);                if(signal_pending(current))                {                        restore_flags(flags);                        return -ERESTARTSYS;                }        }        restore_flags(flags);  </programlisting>  <para>    This is the sledgehammer approach. It works but it means we spend a     lot more time turning interrupts on and off. It also affects     interrupts globally and has bad properties on multiprocessor machines     where turning interrupts off globally is not a simple operation, but     instead involves kicking each processor, waiting for them to disable     interrupts and reply.  </para>  <para>    The real problem is the race between the event testing and the sleeping.     We can avoid that by using the scheduling functions more directly.     Indeed this is the way they generally should be used for an interrupt.  </para>  <programlisting>        struct wait_queue wait = { current, NULL };        add_wait_queue(&amp;mouse_wait, &amp;wait);        set_current_state(TASK_INTERRUPTIBLE);                while(!mouse_event)        {                if(file-&gt;f_flags&amp;O_NDELAY)                {                        remove_wait_queue(&amp;mouse_wait, &amp;wait);                        set_current_state(TASK_RUNNING);                        return -EWOULDBLOCK;                }                if(signal_pending(current))                {                        remove_wait_queue(&amp;mouse_wait, &amp;wait);                        current-&gt;state = TASK_RUNNING;                        return -ERESTARTSYS;                }                schedule();                set_current_state(TASK_INTERRUPTIBLE);        }                remove_wait_wait(&amp;mouse_wait, &amp;wait);        set_current_state(TASK_RUNNING);  </programlisting>  <para>    At first sight this probably looks like deep magic. To understand how     this works you need to understand how scheduling and events work on     Linux. Having a good grasp of this is one of the keys to writing clean     efficient device drivers.  </para>  <para>    <function>add_wait_queue</function> does what its name suggests. It adds     an entry to the <varname>mouse_wait</varname> list. The entry in this     case is the entry for our current process (<varname>current</varname>    is the current task pointer).   </para>  <para>    So we start by adding an entry for ourself onto the     <varname>mouse_wait</varname> list. This does not put us to sleep     however. We are merely tagged onto the list.   </para>  <para>    Next we set our status to <constant>TASK_INTERRUPTIBLE</constant>. Again     this does not mean we are now asleep. This flag says what should happen     next time the process sleeps. <constant>TASK_INTERRUPTIBLE</constant> says     that the process should not be rescheduled. It will run from now until it     sleeps and then will need to be woken up.  </para>  <para>    The <function>wakeup_interruptible</function> call in the interrupt     handler can now be explained in more detail. This function is also very     simple. It goes along the list of processes on the queue it is given and     any that are marked as <constant>TASK_INTERRUPTIBLE</constant> it changes     to <constant>TASK_RUNNING</constant> and tells the kernel that new     processes are runnable.  </para>  <para>    Behind all the wrappers in the original code what is happening is this  </para>  <procedure>   <step>    <para>      We add ourself to the mouse wait queue    </para>   </step>   <step>    <para>      We mark ourself as sleeping    </para>   </step>   <step>    <para>      We ask the kernel to schedule tasks again    </para>   </step>   <step>    <para>      The kernel sees we are asleep and schedules someone else.    </para>   </step>   <step>    <para>      The mouse interrupt sets our state to <constant>TASK_RUNNING</constant>       and makes a note that the kernel should reschedule tasks    </para>   </step>   <step>    <para>      The kernel sees we are running again and continues our execution    </para>   </step>  </procedure>  <para>    This is why the apparent magic works. Because we mark ourself as    <constant>TASK_INTERRUPTIBLE</constant> and as we add ourselves     to the queue before we check if there are events pending, the race     condition is removed.  </para>  <para>    Now if an interrupt occurs after we check the queue status and before     we call the <function>schedule</function> function in order to sleep,     things work out. Instead of missing an event, we are set back to     <constant>TASK_RUNNING</constant> by the mouse interrupt. We still call     <function>schedule</function> but it will continue running our task.     We go back around the loop and this time there may be an event.  </para>  <para>    There will not always be an event. Thus we set ourselves back to    <constant>TASK_INTERRUPTIBLE</constant> before resuming the loop.     Another process doing a read may already have cleared the event flag,     and if so we will need to go back to sleep again. Eventually we will     get our event and escape.  </para>  <para>    Finally when we exit the loop we remove ourselves from the     <varname>mouse_wait</varname> queue as we are no longer interested    in mouse events, and we set ourself back to     <constant>TASK_RUNNABLE</constant> as we do not wish to go to sleep     again just yet.  </para>  <note>   <title>Note</title>    <para>     This isn't an easy topic. Don't be afraid to reread the description a      few times and also look at other device drivers to see how it works.      Finally if you can't grasp it just yet, you can use the code as      boilerplate to write other drivers and trust me instead.   </para>  </note> </chapter> <chapter id="asyncio">  <title>Asynchronous I/O</title>  <para>    This leaves the missing feature - Asynchronous I/O. Normally UNIX     programs use the <function>poll</function> call (or its variant form     <function>select</function>) to wait for an event to occur on one of     multiple input or output devices. This model works well for most tasks     but because <function>poll</function> and <function>select</function>     wait for an event isn't suitable for tasks that are also continually     doing computation work. Such programs really want the kernel to kick     them when something happens rather than watch for events.  </para>  <para>    Poll is akin to having a row of lights in front of you. You can see at a    glance which ones if any are lit. You cannot however get anything useful    done while watching them. Asynchronous I/O uses signals which work more     like a door bell. Instead of you watching, it tells you that something     is up.  </para>  <para>    Asynchronous I/O sends the signal SIGIO to a user process when the I/O     events occur. In this case that means when people move the mouse. The     SIGIO signal causes the user process to jump to its signal handler and     execute code in that handler before returning to whatever was going on     previously. It is the application equivalent of an interrupt handler.  </para>  <para>    Most of the code needed for this operation is common to all its users.     The kernel provides a simple set of functions for managing asynchronous     I/O.  </para>  <para>    Our first job is to allow users to set asynchronous I/O on file handles.     To do that we need to add a new function to the file operations table for     our mouse:  </para>  <programlisting>struct file_operations our_mouse_fops = {        owner: THIS_MODULE        read:  read_mouse,      /* You can read a mouse */        write: write_mouse,     /* This won't do a lot */        poll:  poll_mouse,      /* Poll */        open:  open_mouse,      /* Called on open */        release: close_mouse,   /* Called on close */        fasync: fasync_mouse,   /* Asynchronous I/O */};  </programlisting>  <para>    Once we have installed this entry the kernel knows we support     asynchronous I/O and will allow all the relevant operations on the     device. Whenever a user adds or removes asynchronous I/O notification     on a file handle it calls our <function>fasync_mouse</function> routine     we just added. This routine uses the helper functions to keep the queue     of handles up to date:  </para>  <programlisting>static struct fasync_struct *mouse_fasync = NULL;static int fasync_mouse(int fd, struct file *filp, int on){         int retval = fasync_helper(fd, filp, on, &amp;mouse_fasync);         if (retval &lt; 0)                 return retval;        return 0;}  </programlisting>  <para>    The fasync helper adds and deletes entries by managing the supplied     list. We also need to remove entries from this list when the file is     closed. This requires we add one line to our close function:  </para>  <programlisting>static int close_mouse(struct inode *inode, struct file *file){        fasync_mouse(-1, file, 0)        if(--mouse_users)                return 0;        free_irq(OURMOUSE_IRQ, NULL);        MOD_DEC_USE_COUNT;        return 0;}  </programlisting>  <para>    When we close the file we now call our own fasync handler as if the     user had requested that this file cease to be used for asynchronous     I/O. This rather neatly cleans up any loose ends. We certainly don't     wait to deliver a signal for a file that no longer exists.  </para>  <para>    At this point the mouse driver supports all the asynchronous I/O     operations, and applications using them will not error. They won't     however work yet. We need to actually send the signals. Again the     kernel provides a function for handling this.  </para>  <para>    We update our interrupt handler a little:  </para>  <programlisting>static void ourmouse_interrupt(int irq, void *dev_id, struct pt_regs *regs){        char delta_x;        char delta_y;        unsigned char new_buttons;        delta_x = inb(OURMOUSE_BASE);        delta_y = inb(OURMOUSE_BASE+1);        new_buttons = inb(OURMOUSE_BASE+2);        if(delta_x || delta_y || new_buttons != mouse_buttons)        {                /* Something happened */                spin_lock(&amp;mouse_lock);                mouse_event = 1;                mouse_dx += delta_x;                mouse_dy += delta_y;                if(mouse_dx &lt; -4096)                        mouse_dx = -4096;                if(mouse_dx &gt; 4096)                        mouse_dx = 4096;                if(mouse_dy &lt; -4096)                        mouse_dy = -4096;                if(mouse_dy &gt; 4096)                        mouse_dy = 4096;                mouse_buttons = new_buttons;                spin_unlock(&amp;mouse_lock);                /* Now we do asynchronous I/O */                kill_fasync(&amp;mouse_fasync, SIGIO);                                 wake_up_interruptible(&amp;mouse_wait);        }}  </programlisting>  <para>    The new code simply calls the <function>kill_fasync</function> routine    provided by the kernel if the queue is non-empty. This sends the     required signal (SIGIO in this case) to the process each file handle     says should be informed about the exciting new mouse movement that     just happened.  </para>  <para>    With this in place and the bugs in the original version fixed, you now     have a fully functional mouse driver using the bus mouse protocol. It     will work with the <application>X window system</application>, will work     with <application>GPM</application> and should work with every other     application you need. <application>Doom</application> is of course the     ideal way to test your new mouse driver is functioning properly. Be sure     to test it thoroughly.  </para> </chapter></book>

⌨️ 快捷键说明

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