Linux设备驱动程序设计(17.2)(完)
2010年06月05日
[b]访问用户空间[/b][b][/b]
核心的第一个2.1版引入了一种从核心代码访问用户空间的新(更好)方法。这个改变修正了一个长期存在的错误行为并增强了系统的性能。
当你位核心2.1编译代码,并需要访问用户空间时,你需要包含,而不是。你还必须使用一个与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,除非你选择这样做。
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 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,和strlen_user。我不打算讨论它们了,因为Linux2.0中没有这些函数,并且驱动程序的代码也很少用到它们。有兴趣的读者可以看看。
[b]使用新的接口[/b][b][/b]
访问用户空间的新的函数集初看起来可能有点令人失望,但它们的确使程序员的日子好过的多了。在Linux2.1上,不再需要显式地检查用户空间;access_ok一般不需要调用。使用新接口的代码可以直接进行数据传送。_ret函数在实现系统调用时证明是相当有用的,因为一个用户空间的失败通常导致系统调用的一个返回-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实现看起来如下:
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
2010年06月05日
[b]访问用户空间[/b][b][/b]
核心的第一个2.1版引入了一种从核心代码访问用户空间的新(更好)方法。这个改变修正了一个长期存在的错误行为并增强了系统的性能。
当你位核心2.1编译代码,并需要访问用户空间时,你需要包含,而不是。你还必须使用一个与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,除非你选择这样做。
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 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,和strlen_user。我不打算讨论它们了,因为Linux2.0中没有这些函数,并且驱动程序的代码也很少用到它们。有兴趣的读者可以看看。
[b]使用新的接口[/b][b][/b]
访问用户空间的新的函数集初看起来可能有点令人失望,但它们的确使程序员的日子好过的多了。在Linux2.1上,不再需要显式地检查用户空间;access_ok一般不需要调用。使用新接口的代码可以直接进行数据传送。_ret函数在实现系统调用时证明是相当有用的,因为一个用户空间的失败通常导致系统调用的一个返回-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实现看起来如下:
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