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

📄 (ldd) ch03-字符设备驱动程序(转载).htm

📁 《linux设备驱动程序开发》
💻 HTM
📖 第 1 页 / 共 4 页
字号:
      color=#ffffff size=3>
      <P><BR>系统调用ioctl提供一中调用设备相关命令的方法(如软盘的格式化一个磁道,这既不是<BR>读操作也不是写操作)。另外,内核还识别一部分ioctl命令,而不必调用fops表中的io<BR>ctl。如果设备不提供ioctl入口点,对于任何内核没有定义的请求,ioctl系统调用将返<BR>回-EINVAL。当调用成功时,返回给调用程序一个非负返回值。<BR>&nbsp;<BR>int&nbsp;(*mmap)(struct&nbsp;inode&nbsp;*,&nbsp;struct&nbsp;file&nbsp;*,&nbsp;struct&nbsp;vm_area_struct&nbsp;*);<BR>&nbsp;<BR>mmap用来将设备内存映射到进程内存中。如果设备不支持这个方法,mmap系统调用将返<BR>回-ENODEV。<BR>&nbsp;<BR>int&nbsp;(*open)(struct&nbsp;inode&nbsp;*,&nbsp;struct&nbsp;file&nbsp;*);<BR>&nbsp;<BR>尽管这总是操作在设备节点上的第一个操作,然而并不要求驱动程序一定要声明这个方<BR>法。如果该项为NULL,设备的打开操作永远成功,但系统不会通知你的驱动程序。<BR>&nbsp;<BR>void&nbsp;(*release)(struct&nbsp;inode&nbsp;*,&nbsp;struct&nbsp;file&nbsp;*);<BR>&nbsp;<BR>当节点被关闭时调用这个操作。与open相仿,release也可以没有。在2.0和更早的核心<BR>中,close系统调用从不失败;这种情况在版本2.1.31中有所变化(见第17章)。<BR>&nbsp;<BR>int&nbsp;(*fsync)(struct&nbsp;inode&nbsp;*,&nbsp;struct&nbsp;file&nbsp;*);<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>刷新设备。如果驱动程序不支持,fsync系统调用返回-EINVAL。<BR>&nbsp;<BR>int&nbsp;(*fasync)(struct&nbsp;inode&nbsp;*,&nbsp;struct&nbsp;file&nbsp;*,&nbsp;int);<BR>&nbsp;<BR>这个操作用来通知设备它的FASYNC标志的变化。异步触发是比较先进的话题,将在第5章<BR>的“异步触发”一节中介绍。如果设备不支持异步触发,该字段可以是NULL。<BR>&nbsp;<BR>int&nbsp;(*check_media_change)(kdev_t&nbsp;dev);<BR>&nbsp;<BR>check_media_change只用于块设备,尤其是象软盘这类可移动介质。内核调用这个方法<BR>判断设备中的物理介质(如软盘)自最近一次操作以来发生了变化(返回1)或是没有(<BR>0)。字符设备无需实现这个函数。<BR>&nbsp;<BR>int&nbsp;(*revalidate)(kdev_t&nbsp;dev);<BR>&nbsp;<BR>这是最后一项,与前面提到的那个方法一样,也只适用于块设备。revalidate与缓冲区<BR>高速。缓存有关。我们将在第12章“加载块设备驱动程序”的“可移动设备”中介绍rev<BR>alidate。<BR>&nbsp;<BR>scull驱动程序中适用的file_operations结构如下:<BR>&nbsp;<BR>(代码)<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>(代码)<BR>&nbsp;<BR>在最新的开发用内核中,某些原型已经发生了变化。该列表是从2.0.x的头文件中提炼出<BR>来的,这里给出的原型对于大多数内核而言都是正确的。内核2.1引入的变化(以及为了<BR>使我们的模块可移植所进行的修改)在针对不同操作的每一节和第17章的“文件操作”<BR>中详细介绍。<BR>&nbsp;<BR>file结构<BR>在&lt;linux/fs.h&gt;中定义的struct&nbsp;file是设备驱动程序所适用的又一个最重要的数据结构<BR>。注意,file与用户程序中的FILE没有任何关联。FILE是在C库中定义且从不出现在内核<BR>代码中。而struct&nbsp;file是一个内核结构,从不出现在用户程序中。<BR>&nbsp;<BR>file结构代表一个“打开的文件”。它有内核在open时创建而且在close前做为参数传递<BR>给如何操作在设备上的函数。在文件关闭后,内核释放这个数据结构。一个“打开的文<BR>件”与由struct&nbsp;inode表示的“磁盘文件”有所不同。<BR>&nbsp;<BR>在内核源码中,指向struct&nbsp;file的指针通常称为file或filp(“文件指针”)。为了与<BR>这个结构相混淆,我将一直称指针为filp-flip是一个指针(同样,它也是设备方法的<BR>参数之一),而file是结构本身。<BR>&nbsp;<BR>struct&nbsp;file中的最重要的字段罗列如下。与上节相似,这张列表在首次阅读时可以略过<BR>。在下一节中,我们将看到一些真正的C代码,我将讨论某些字段,到时你可以反过来查<BR>阅这张列表。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>阅这张列表。<BR>&nbsp;<BR>mode_t&nbsp;f_mode;<BR>&nbsp;<BR>文件模式由FMODE_READ和FMODE_WRITE标别。你可能需要在你的ioctl函数中查看这个域<BR>来来检查读/写权限,但由于内核在调用你的驱动程序的read和write前已经检查了权限<BR>,你无需检查在这两个方法中检查权限。例如,一个不允许的写操作在驱动程序还不知<BR>道的情况下就被已经内核拒绝了。<BR>&nbsp;<BR>loff_t&nbsp;f_ops;<BR>&nbsp;<BR>当然读/写位置。loff_t是一个64位数值(用gcc的术语就是long&nbsp;long)。如果驱动程序<BR>需要知道这个值,可以直接读取这个字段。如果定义了lseek方法,应该更新f_pos的值<BR>。当传输数据时,read和write也应该更新这个值。<BR>&nbsp;<BR>unsigned&nbsp;short&nbsp;f_flags;<BR>&nbsp;<BR>文件标志,如O_RDONLY,O_NONBLOCK和O_SYNC。驱动程序为了支持非阻塞型操作需要检<BR>查这个标志,而其他标志很少用到。注意,检查读/写权限应该查看f_mode而不是f_flag<BR>s。所有这些标志都定义在&lt;linux/fcntl.h&gt;中。<BR>&nbsp;<BR>struct&nbsp;inode&nbsp;*f_inode;<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>打开文件所对应的i节点。inode指针是内核传递给所有文件操作的第一个参数,所以你<BR>一般不需要访问file结构的这个字段。在某些特殊情况下你只能访问struct&nbsp;file时,你<BR>可以通过这个字段找到相应的i节点。<BR>&nbsp;<BR>struct&nbsp;file_operations&nbsp;*f_op;<BR>&nbsp;<BR>与文件对应的操作。内核在完成open时对这个指针赋值,以后需要分派操作时就读这些<BR>数据。filp-&gt;f_op中的值从不保存供以后引用;这也就是说你可以在需要的事后修改你<BR>的文件所对应的操作,下一次再操作那个打开文件的相应操作时就会调用新方法。例如<BR>,主设备号为1的设备(/dev/null,/dev/zero等等)的open代码根据要打开的次设备号<BR>替换filp-&gt;f_op中的操作。这种技巧有助于在不增加系统调用负担的情况下方便识别主<BR>设备号相同的设备。能够替换文件操作的能力在面向对象编程技术中称为“方法重载”<BR>。<BR>&nbsp;<BR>void&nbsp;*private_data;<BR>&nbsp;<BR>系统调用open在调用驱动程序的open方法前将这个指针置为NULL。驱动程序可以将这个<BR>字段用于任意目的或者忽略简单忽略这个字段。驱动程序可以用这个字段指向已分配的<BR>数据,但是一定要在内核释放file结构前的release方法中清除它。private_data是跨系<BR>统调用保存状态信息的非常有用的资源,在我们的大部分样例都使用了这个资源。<BR>&nbsp;<BR>实际的结构里还有其他一些字段,但它们对于驱动程序并不是特别有用。由于驱动程序<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>实际的结构里还有其他一些字段,但它们对于驱动程序并不是特别有用。由于驱动程序<BR>从不填写file结构;它们只是简单地访问别处创建的结构,我们可以大胆地忽略这些字<BR>段。<BR>&nbsp;<BR>Open和Close<BR>现在让我们已经走马观花地看了一遍这些字段,下面我们将开始在实际的scull函数中使<BR>用这些字段。<BR>&nbsp;<BR>Open方法<BR>open方法是驱动程序用来为以后的操作完成初始化准备工作的。此外,open还会增加设<BR>备计数,以便防止文件在关闭前模块被卸载出内核。<BR>&nbsp;<BR>在大部分驱动程序中,open完成如下工作:<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;检查设备相关错误(诸如设备未就绪或相似的硬件问题)。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果是首次打开,初始化设备。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;标别次设备号,如有必要更新f_op指针。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;分配和填写要放在filp-&gt;private_data里的数据结构。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;增加使用计数。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;增加使用计数。<BR>&nbsp;<BR>在scull中,上面的大部分操作都要依赖于被打开设备的次设备号。因此,首先要做的事<BR>就是标别要操作的是哪个设备。我们可以通过查看inode-&gt;i_rdev完成。<BR>&nbsp;<BR>我们已经谈到内核是如何不使用次设备号的了,因此驱动程序可以随意使用次设备号。<BR>事实上,利用不同的次设备号访问不同的设备,或以不同的方式打开同一个设备。例如<BR>,/dev/ttyS0和/dev/ttyS1是两个不同的串口,而/dev/cua0的物理设备与/dev/ttyS0相<BR>同,仅仅是操作行为不同。cua是“调出”设备;它们不是终端,而且它们也没有终端所<BR>需要的所有软件支持(即,它们没有加入行律*)。所有的串口设备都有许多不同的次设<BR>备号,这样驱动程序就区分它们了:ttyS与cua不一样。<BR>&nbsp;<BR>驱动程序从来都不知道被打开的设备的名字,它仅仅知道设备号――而且用户可以按照<BR>自己的规范给用设备起别名,而完全不用原有的名字。如果你看看/dev目录就会知道,<BR>你将发现对应相同主/次设备号的不同名字;设备只有一个而且是相同的,而且没有方法<BR>区分它们。例如,在许多系统中,/dev/psaux和/dev/bmouseps2都存在,而且它们有同<BR>样的设备号;它们可以互换使用。后者是“历史遗迹”,你的系统里可以没有。<BR>&nbsp;<BR>scull驱动程序是这样使用次设备号的:最高4位标别设备类型个体(personality),如<BR>果该类型可以支持多实例(scull0-3和scullpipe0-3),低4位可以供你标别这些设备。<BR>因此,scull0的高4位与scullpipe0不同,而scull0的低4位与scull1不同*。源码中定义<BR>了两个宏(TYPE和NUM)从设备号中分解出这些位,我们马上就看到这些宏。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>对于每一设备类型,scull定义了一个相关的file_operations结构,并在open时替换fil<BR>p-&gt;f_op。下面的代码就是位切分和多fops是如何实现的:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>内核根据主设备号调用open;scull用上面给出的宏处理次设备号。接着用TYPE索引scul<BR>l_fop_array数组,从中分解出被打开设备的方法集。<BR>&nbsp;<BR>我在scull中所做的就是根据次设备号的类型给filp-&gt;f_op赋上正确的值。然后调用新的<BR>fops中定义的open方法。通常,驱动程序不必调用自己的fops,它只有内核分配正确的<BR>驱动程序方法时调用。但当你的open方法不得不处理不同设备类型时,在根据被打开设<BR>备次设备号修改fops指针后就需要调用fops-&gt;open了。<BR>&nbsp;<BR>scull_open的实际代码如下。它使用了前面那段代码中定义的TYPE和NUM两个宏来切分次<BR>设备号:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>这里给一点解释。用来保存内存区的数据结构是Scull_Dev,这里简要介绍一下。Scull_<BR>Dev和scull_trim(“Scull的内存使用”一节中讨论)的内部结构这里并没有使用。全<BR>局变量scull_nr_devs和scull_devices[](全部小写)分别是可用设备数和指向Scull_D<BR>ev的指针数组。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>ev的指针数组。<BR>&nbsp;<BR>这段代码看起来工作很少,这是因为当调用open时它没做任何针对某个设备的处理。由<BR>于scull0-3设备被设计为全局的和永久性的,这段代码无需做什么。特别是,由于我们<BR>无法维护scull的打开计数,也就是模块的使用计数,因此没有类似于“首次打开时初始<BR>化设备”这类动作。<BR>&nbsp;<BR>唯一实际操作在设备上的操作是,当设备写打开时将设备截断为长度0。截断是scull设<BR>计的一部分:用一个较短的文件覆盖设备,以便缩小设备数据区,这与普通文件写打开<BR>截断为0很相似。<BR>&nbsp;<BR>但“打开是截断”有一个严重的缺点:若干因为某些原因设备内存正在使用,释放这些<BR>内存会导致系统失效。尽管可能性不大,这种情况总会发生:如果read和write方法在数<BR>据传输时睡眠了,另一个进程可能写打开这个设备,这时麻烦就来了。处理竞争条件是<BR>一个相当高级的主题,我将在第9章的“竞争条件”中讲解。scull处理这个问题的简单<BR>方法就是在内存还是使用时不释放内存,“Scull的内存使用”一节中将说明。<BR>&nbsp;<BR>以后我们看到其他scull个体(personality)时将会看到一个真正的初始化工作如何完<BR>成。<BR>&nbsp;<BR>release方法<BR>release方法的作用正好与open相反。这个设备方法有时也称为close。它应该:<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用计数减1。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;释放open分配在filp-&gt;private_data中的内存。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在最后一次关闭操作时关闭设备。<BR>&nbsp;<BR>scull的基本模型无需进行关闭设备动作,所以所需代码是很少的*:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>使用计数减1是非常重要的,因为如果使用计数不归0,内核是不会卸载模块的。<BR>&nbsp;<BR>如果某个时刻一个从没被打开的文件被关闭了计数将如何保证一致呢?我们都知道,dup<BR>和fork都会在不调用open的情况下,将一个打开文件复制为2个,但每一个都会在程序终<BR>止时关闭。例如,大多数程序从来不打开它们的stdin文件(或设备),但它们都会在终<BR>止关闭它。<BR>&nbsp;<BR>答案很简单。如果open没有调用,release也不会调。内核维护一个file结构被使用了多<BR>少次的使用计数。无论是fork还是dup都不创建新的数据结构;它们仅是增加已有结构的<BR>计数。<BR>&nbsp;<BR>新的struct&nbsp;file仅由open创建。只有在该结构的计数归0时close系统调用才会执行clos<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>新的struct&nbsp;file仅由open创建。只有在该结构的计数归0时close系统调用才会执行clos<BR>e方法,这只有在删除这个结构时才会进行。close方法与close系统调用间的关系保证了<BR>模块使用计数永远是一致的。<BR>&nbsp;<BR>Scull的内存使用<BR>在介绍读写操作以前,我们最好先看看scull如何完成内存分配以及为什么要完成内存分<BR>配。为了全面理解代码我们需要知道“如何分配”,而“为什么”则反映了驱动程序编<BR>写者需要做出的选择,尽管scull绝不是一个典型设备,但同样需要。<BR>&nbsp;<BR>本节只讲解scull中的内存分配策略,而不会讲解你写实际驱动程序时需要的硬件管理技<BR>巧。这些技巧将在第8章“硬件管理”和第9章中介绍。因此,如果你对针对内存操作的s<BR>cull驱动程序的内部工作原理不感兴趣的话,你可以跳过这一节。<BR>&nbsp;<BR>scull使用的内存,这里也称为“设备”,是变长的。你写的越多,它就增长得越多;消<BR>减的过程只在用短文件覆盖设备时发生。<BR>&nbsp;<BR>所选的实现scull的方法不是很聪明。实现较聪明的源码会更难读,而且本节的目的只是<BR>讲解read和write,而不是内存管理。这也就是为什么虽然整个页面分配会更有效,但代<BR>码只使用了kmalloc和kfree,而没有涉及整个页面的分配的操作。<BR>&nbsp;<BR>而另一面,从理论和实际角度考虑,我又不想限制“设备”区的尺寸。理论上将,给所<BR>管理的数据项强加任何限制总是很糟糕的想法。从实际出发,为了测试系统在内存短缺<BR>时的性能,scull可以帮助将系统的剩余内存用光。进行这样的测试有助于你理解系统的<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>时的性能,scull可以帮助将系统的剩余内存用光。进行这样的测试有助于你理解系统的<BR>内部行为。你可以使用命令cp&nbsp;/dev/zero&nbsp;/dev/scull用光所有的物理内存,而且你也可<BR>以用工具dd选择复制到scull设备中多少数据。<BR>&nbsp;<BR>在scull中,每个设备都是一组指针的链表,而每一个指针又指向一个Scull_Dev结构。<BR>每一个这样的结构通过一个中间级指针数组最多可引用4,000,000个字节。发行的源码中<BR>使用了一个有1000个指针的数组,每个指针指向4000个字节。我把每一个内存区称为一<BR>个“量子”,数组(或它的长度)称为“量子集”。scull设备和它的内存区如图3-1所<BR>示。<BR>&nbsp;<BR>所选择的数字是这样的,向scull写一个字节就会消耗内存8000了字节:每个量子4个,<BR>量子集4个(在大多数平台上,一个指针是4个字节;当在Alpha平台编译时量子集本身就<BR>会耗费8000个字节,在Alpha平台上指针是8个字节)。但另一方面,如果你向scull写大<BR>量的数据,由于每4MB数据只对应一个表项,而且设备的最大尺寸只限于若干MB,不可能<BR>超出计算机内存的大小,遍历这张链表的代价不是很大。<BR>&nbsp;<BR>为量子和量子集选择合适的数值是一个策略问题,而非机制问题,而且最优数值依赖于<BR>如何使用设备。源码中为处理这些问题允许用户修改这些值:<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在编译时,可以修改scull.h中的SCULL_QUANTUM和SCULL_QSET。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在加载时,可以利用insmod修改scull_quantum和scull_qset整数值。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在运行时,用ioctl方法改变默认值和当前值。ioctl将在第5章的“ioctl”一<BR>节中介绍。<BR>&nbsp;<BR>使用宏和整数值进行编译时和加载时配置让人想起前面提到的如何选择主设备号。无论<BR>何时驱动程序需要一个随意的数值或这个数值与策略相关,我都使用这种技术。<BR>&nbsp;<BR>留下来的唯一问题就是如何选择默认数值。尽管有时驱动程序编写者也需要事先调整配<BR>置参数,但他们在编写自己的模块时不会碰到同样的问题。在这个特殊的例子里,问题<BR>的关键在于寻找因未填满的量子和量子集导致的内存浪费和量子和量子集太小带来的分<BR>配、释放和指针连接等操作的代价之间的平衡。<BR>&nbsp;<BR>此外,还必须考虑kmalloc的内部设计。现在我们还无法讲述太多的细节,只能简单规定<BR>“比2次幂稍小一点是最佳尺寸”比较好。kmalloc的内部结构将在第7章“Getting<BR>Hold&nbsp;of&nbsp;Memory”的“The&nbsp;Real&nbsp;Story&nbsp;of&nbsp;kmalloc”一节中探讨。<BR>&nbsp;<BR>默认数值的选择基于这样的假设,大部分程序员不会受限与4MB的物理内存,那样大的数<BR>据量有可能会写到scull中。一台内存很多的计算机的属主可能因测试向设备写数十MB的<BR>数据。因此,所选的默认值是为了优化中等规模的系统和大数据量的使用。<BR>&nbsp;<BR>保存设备信息的数据机构如下:<BR>&nbsp;<BR>(代码)<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>(代码)<BR>&nbsp;<BR>下面的代码给出了实际工作时是如何利用Scull_Dev保存数据的。其中给出的函数负责释<BR>放整个数据区,并且在文件写打开时由scull_open调用。如果当前设备内存正在使用,<BR>该函数就不释放这些内存(象“open方法”中所说那样);否则,它简单地遍历链表,<BR>释放所有找到的量子和量子集。<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>读和写<BR>读写scull设备也就意味着要完成内核空间和用户进程空间的数据传输。由于指针只能在<BR>当前地址空间操作,而驱动程序运行在内核空间,数据缓冲区则在用户空间,这一操作<BR>不能通过通常利用指针或memcpy完成。<BR>&nbsp;<BR>由于驱动程序不过怎样都要在内核空间和用户缓冲区间复制数据,如果目标设备不是RAM<BR>而是扩展卡,也有同样的问题。事实上,设备驱动程序的主要作用就是管理设备(内核<BR>空间)和应用(用户空间)间的数据传输。<BR>&nbsp;<BR>在Linux里,跨空间复制是通过定义在&lt;asm/segment.h&gt;里的特殊函数实现的。完成这种<BR>操作函数针对不同数据尺寸(char,short,int,long)进行了优化;它们中的大部分<BR>将在第5章的“使用ioctl参数”一节中介绍。<BR>&nbsp;<BR>scull中read和write的驱动程序代码需要完成到用户空间和来自用户空间的整个数据段<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>scull中read和write的驱动程序代码需要完成到用户空间和来自用户空间的整个数据段<BR>的复制。下面这些提供这些功能,它们可以传输任意字节:<BR>&nbsp;<BR>(代码)<BR>&nbsp;<BR>这两个函数的名字可以追溯到第1版Linux,那时唯一支持的体系结构是i386,而且C代码<BR>中还可以窥见许多汇编码。在Intel平台上,Linux通过FS段寄存器访问用户空间,到Lin<BR>ux&nbsp;2.0时仍沿用了以前的名字。在Linux&nbsp;2.1中它们改变了,但是2.0是本书的主要目标<BR>。详情可见第17章的“访问用户空间”。<BR>&nbsp;<BR>尽管上面介绍的函数看起来很象正常的memcpy函数,但当在内核代码中访问用户空间时<BR>必须额外注意一些问题;正在被访问的用户页面现在可能不在内存中,而且页面失效处<BR>理函数有可能在传送页面的时候让进程进入睡眠状态。例如,必须从交换区读取页面时<BR>会发生这种情况。对驱动程序编写者来说,静效果就是对于任何访问用户空间的函数都<BR>必须是可重入的,而且能够与其他驱动程序函数并发执行。这就是为什么scull实现中不<BR>允许在dev-&gt;usage不为0时释放设备:read和write方法在它们使用memcpy函数前先完成u<BR>sage计数加1。<BR>&nbsp;<BR>现在谈谈实际的设备方法,读方法的任务是将数据从设备复制到用户空间(使用memcpy_<BR>tofs),而写方法必须将数据从用户空间复制到设备(使用memcpy_tofs)。每一个read<BR>或write系统调用请求传输一定量的字节,但驱动程序可以随意传送其中一部分数据――<BR>读与写的具体规则稍有不同。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>允许在dev-&gt;usage不为0时释放设备:read和write方法在它们使用memcpy函数前先完成u<BR>sage计数加1。<BR>&nbsp;<BR>现在谈谈实际的设备方法,读方法的任务是将数据从设备复制到用户空间(使用memcpy_<BR>tofs),而写方法必须将数据从用户空间复制到设备(使用memcpy_tofs)。每一个read<BR>或write系统调用请求传输一定量的字节,但驱动程序可以随意传送其中一部分数据――<BR>读与写的具体规则稍有不同。<BR>&nbsp;<BR>如果有错误发生,read和write都返回一个负值。返回给调用程序一个大于等于0的数值<BR>,告诉它成功传输了多少字节。如果某个数据成功地传输了,随后发生了错误,返回值<BR>必须是成功传输<BR>--<BR>&nbsp;<BR><FONT 
      color=#00ff00>※&nbsp;来源:.华南网木棉站&nbsp;bbs.gznet.edu.cn.[FROM:&nbsp;202.38.196.234]</FONT><BR>--<BR><FONT 
      color=#00ffff>※&nbsp;转寄:.华南网木棉站&nbsp;bbs.gznet.edu.cn.[FROM:&nbsp;211.80.41.106]</FONT><BR>--<BR><FONT 
      color=#0000ff>※&nbsp;转寄:.华南网木棉站&nbsp;bbs.gznet.edu.cn.[FROM:&nbsp;211.80.41.106]</FONT><BR>--<BR><FONT 
      color=#ffff00>※&nbsp;转载:.南京大学小百合站&nbsp;bbs.nju.edu.cn.[FROM:&nbsp;211.80.41.106]</FONT><BR>&nbsp;<BR>--<BR><FONT 
      color=#ff0000>※&nbsp;转载:·饮水思源&nbsp;bbs.sjtu.edu.cn·[FROM:&nbsp;211.80.41.106]</FONT><BR></P></FONT>
      <P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT 
      color=#ffffff size=2>目录页</FONT></A> | <A 
      href="http://joyfire.net/lsdp/4.htm"><FONT color=#ffffff 
      size=2>上一页</FONT></A> | <A href="http://joyfire.net/lsdp/6.htm"><FONT 
      color=#ffffff size=2>下一页</FONT></A></P></SPAN></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>
  <TBODY>
  <TR>
    <TD colSpan=3 height=2>
      <TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#666666 

⌨️ 快捷键说明

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