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

📄 (ldd) ch17-最新进展(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 3 页
字号:
      poll方法
       
      2.1.23引入了poll系统调用,它是system V中select的对应者(由BSD Unix引入)。不
      幸的是不可能在select设备方法之上实现poll的功能,所以整个实现用不同的一个代替
      ,它作为select和poll的后端。
       

       
      在当前版本的核心中,file_operations中的设备方法也叫poll,与系统调用类似,因为
      其内部模仿这个系统调用。这个方法的原型是:
       
             unsigned int (*poll) (struct file *, poll_table *);
       
      驱动程序中设备特定的实现主要完成两个任务:
       
      l         在一个可能在将来唤醒它的等待队列中将当前进程排队。通常,这意味着同
      时在输入和输出队列中对进程排队。函数poll_wait被用于这个目的,其工作方式与sele
      ct_wait非常类似(细节请看第五章“增强的字符设备驱动程序操作”中“select”一节
      )。
       
      l         构造一个位掩码描述设备的状态,并将其返回给调用者。这些位的值是平台
      特定的,在<linux/poll.h>中定义,它必须被包含在驱动程序中。
       
      在讲述位掩码的每一位前,我想给出一个典型的实现。下面的函数是v2.1/scull/pipe.c
      的一部分,是/dev/scullpipe的poll方法的实现。scullpipe的内部在第五章介绍过。
       
      (代码389)
       
      如你所看到的,这个代码相当简单。它比对应的select方法要容易。至于select,状态
      位计为“可读”、“可写”,或“发生例外”(这是select的第三个条件)。

      位计为“可读”、“可写”,或“发生例外”(这是select的第三个条件)。
       
      poll各位的完全列表在下面给出。“输入”位列在前面,然后是“输出”,一个“例外
      ”位列在最后。
       
      POLLIN
       
      如果设备可以被无阻塞地读,那么这个位必须被设置。
       
      POLLRDNORM
       
      如果“一般”数据可以被读,这个位必须被设。一个可读设备返回(POLLIN |
      POLLRDNORM)。
       
      POLLRDBAND
       
      在目前的核心源码中这个位不被使用。Unix System V使用这个位报告非0优先级的数据
      可读。数据优先级的概念与“Streams”包相关。
       
      POLLHUP
       
      当一个读设备的进程看到文件结尾时,驱动程序必须设置POLLHUP(挂起)。一个调用sele
      ct的进程将被告知设备可读,这由select的功能说明。

      ct的进程将被告知设备可读,这由select的功能说明。
       
      POLLERR
       
      设备上发生了一个错误条件。当poll被select系统调用调用时,设备被报告为既可读又
      可写,因为read或write将无阻塞地返回一个错误代码。
       
      POLLOUT
       
      如果设备可以被无阻塞地写,这个位在返回值中被设置。
       
      POLLWRNORM
       
      这个位与POLLOUT有相同的含义,有时甚至的确为同一个数。一个可写的设备返回(POLL
      OUT | POLLWRNORM)。
       
      POLLWRBAND
       
      与POLLRDBAND类似,这个位意味着非0优先级的数据可以被写到设备。只有poll的“数据
      报”实现用到着位,因为一个数据报可以传送“无团队数据(out-of-band data)”。s
      elect报告设备是可写的。
       
      POLLPRI

      POLLPRI
       
      高优先级的数据(“无团队的”)可以被无阻塞地读取。这个位导致select报告文件上
      发生了一个例外条件,因为select将无团队包作为一个例外条件报告。
       
       
       
      poll的主要问题是它与2.0核心所使用的select方法没有任何关系。因此,处理这个不同
      的最好方法是使用条件编译来编译合适的函数,而且同时将它们都包含在源文件中。
       
      如果当前版本支持select而不是poll,那么头文件sysdep-2.1.h定义符号__USE_OLD_SEL
      ECT__。这将你从在源码中必须引用LINUX_VERSION_CODE中解脱出来。v2.1目录下的示例
      驱动程序使用了与下面类似的代码:
       
      (代码390)
       
      (代码391)
       
      这两个函数用同样的名字调用,因为在结构sample_fops中sample_poll被引用,那里pol
      l文件操作代替了select方法。
       
       
       

       
      访问用户空间
       
      核心的第一个2.1版引入了一种从核心代码访问用户空间的新(更好)方法。这个改变修
      正了一个长期存在的错误行为并增强了系统的性能。
       
      当你位核心2.1编译代码,并需要访问用户空间时,你需要包含<asm/uaccess.h>,而不
      是<asm/segment.h>。你还必须使用一个与2.0不同的函数集。不用说,头文件sysdep-2.
      1.h尽可能地照顾了这些不同,允许你在2.0上编译时使用2.1的语义。
       
      在用户访问中最令人注意的不同时verify_area没有了,因为多数验证都由CPU完成了。
      关于这个主题的细节见本章后面的“处理核心空间错误”。
       
      可被用来访问用户空间的新的函数集是:
       
      int access_ok(int type, unsigned long addr, unsigned long size);
       
      如果当前进程被允许访问地址addr处的内存,函数返回真(1),否则为假(0)。这个
      函数取代verify_area,尽管它进行较少的检查。和老的verify_area接收一样的参数,
      但是要快的多。在你复引用一个用户空间地址之前,这个函数应该被调用对之进行检查
      ;如果你没有检查,用户有可能会访问和修改核心内存。本章后面的“虚拟内存”一节
      更细致地解释了这个问题。幸运的是,下面描述的大多数函数都替你进行了这个检查,
      因此你实际上并不需要调用access_ok,除非你选择这样做。

      因此你实际上并不需要调用access_ok,除非你选择这样做。
       
      int get_user(lvalue, address);
       
      在2.1核心中使用的宏get_user与我们在2.0中使用的并不相同。其返回值在成功时为0,
      否则为一个负的错误代码(总是-EFAULT)。这个函数的净效果是将从地址address取得
      的数据赋给lvalue。在通常的C语言含义中,这个宏的第一个参数必须是一个lvalue*。
      与2.0版中的这个函数类似,数据项的实际大小依赖于address参数类型。这个函数在内
      部调用access_ok。
       
      int __get_user(lvalue, address);
       
      这个函数完全类似get_user,但它不内部调用access_ok。当你访问一个已经从同一核心
      函数内部检查过的用户地址时,你应该调用__get_user。
       
      get_user_ret(lvalue, address, retval);
       
      这个宏是调用get_user的快捷方式,如果函数失败则返回retval。
       
      int put_user(expression, address);
       
      int __put_user(expression, address);
       

       
      put_user_ret(expression, address, retval);
       
      这些函数与它们的get_对应者非常类似,只是它们是向用户空间写,而不是读。成功时
      ,值expression被写到地址address。
       
      unsigned long copy_from_user(unsigned long to, unsigned long from, 
unsigned
      long len);
       
      这个函数从用户空间复制数据到核心空间。它代替旧的memcpy_tofs调用。这个函数内部
      调用access_ok。返回值是未能传送的字节数。这样,如果发生错误,返回值必然大于0
      ;在那种情况下,驱动程序返回-EFAULT,因为错误是由错误的内存访问引起的。
       
      unsigned long __copy_from_user(unsigned long to, unsigned long from,
      unsigned long len);
       
      这个函数与copy_from_user一样,但它不内部调用access_ok。
       
      caopy_from_user_ret(to, from, len, retval);
       
      这个宏是内部调用copy_from_user的快捷方式;如果失败,则从当前函数返回。
       
      unsigned long copy_to_user(unsigned long to, unsigned long from, unsigned

      unsigned long copy_to_user(unsigned long to, unsigned long from, unsigned
      long len);
       
      unsigned long __copy_to_user(unsigned long to, unsigned long from, 
unsigned
      long len);
       
      copy_to_user(to, from, len, retval);
       
      这些函数被用来将数据复制到用户空间,它们的行为非常类似于它们的copy_from的对应
      者。
       
      2.1版核心还定义了其它访问用户空间的函数:clear_user,strncpy_from_user,和str
      len_user。我不打算讨论它们了,因为Linux2.0中没有这些函数,并且驱动程序的代码
      也很少用到它们。有兴趣的读者可以看看<asm/access.h>。
       
       
       
      使用新的接口
       
      访问用户空间的新的函数集初看起来可能有点令人失望,但它们的确使程序员的日子好
      过的多了。在Linux2.1上,不再需要显式地检查用户空间;access_ok一般不需要调用。
      使用新接口的代码可以直接进行数据传送。_ret函数在实现系统调用时证明是相当有用
      的,因为一个用户空间的失败通常导致系统调用的一个返回-EFAULT的失败。

      的,因为一个用户空间的失败通常导致系统调用的一个返回-EFAULT的失败。
       
      因此,一个典型的read实现,看起来如下:
       
             long new_read(struct inode *inode, struct file *filp, char *buf,
      unsigned long count);
       
             {
       
             /* identify your data (device-specific code) */
       
             if (__copy_to_user(buf, new_data, count))
       
                    return -EFAULT;
       
             return count;
       
      }
       
      注意使用不进行检查的__copy_to_user是因为调用者在把数据传输分派到文件操作之前
      已经检查了用户空间。这就象2.0,read和write不需要调用verify_area。
       
      类似地,典型的ioctl实现看起来如下:

      类似地,典型的ioctl实现看起来如下:
       
             int new_ioctl(struct inode *inode, struct file *filp, unsigned int
      cmd, unsigned long arg);
       
             {
       
      /* device-specific checks, if needed */
       
      switch(cmd){
       
             case NEW_GETVALUE:
       
                    put_user_ret(new_value, (int *)arg, -EFAULT);
       
                    break;
       
             case NEW_SETVALUE:
       
                    get_user_ret(new_value, (int *)arg, -EFAULT);
       
             default:
       

       
                    return –EINVAL;
       
             }
       
      return 0;
       
      }
       
      于版本2.0的对应者不同的是,这个函数在switch语句之前并不需要检查参数,因为每个
      get_user或put_user会进行检查。另一种实现方式如下:
       
      (代码394 #2)
       
      另一方面,当你想写可以同时在2.0和2.1上编译的代码时,问题变得稍微复杂一些,因
      为在老的核心上,你不能用C预处理器伪装新的行为。你不能简单地#define一个接收两
      个参数的get_user宏,因为实际的get_user实现在2.0中已经是个宏。
       
      我在写既可移植有高效率的代码的选择是设置sysdep-2.1.h以提供具有下列函数的源码
      。下面只列出了读取数据的函数;写数据的函数行为完全一样。
       
      int access_ok(type, address, size);
       

       
      当在2.0上编译时,这个函数以verify_area的名义实现。
       
      int verify_area_20(type, address, size);
       
      通常,当为Linux2.1写代码时,你不需调用access_ok。另一方面,当在Linux2.0上编译
      时,则必须调用verify_area。这个函数就是要填平这个不同:当为Linux2.1编译时,它
      扩展为空;而为2.0编译时,它扩展为原来的verify_area。这个函数不能被称做verify_
      area,因为2.1已经有一个宏叫这个名字了。在2.1中定义的verify_area宏实现了access
      _ok的老的语义,它的存在是为了简化源码从2.0到2.1的转换。(从理论上说,你可以在
      你的模块中留下verify_area,只是将函数名改一下;这种简单移植技巧的缺点是新版本
      不能在2.0上编译。)
       
      int GET_USER(var, add);
       
      int __GET_USER(var, add);
       
      GET_USER_RET(var, add, ret);
       
      当在2.1上编译时,这些宏扩展为实际的get_user函数,即上面解释过的那些。当在2.0
      上编译时,get_user的2.0实现被用来实现与2.1中同样的功能。
       
      int copy_from_user(to, from, size);

      int copy_from_user(to, from, size);
       
      int __copy_from_user(to, from, size);

⌨️ 快捷键说明

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