这篇文章主要介绍“Linux内核怎么判断地址是否位于用户空间”,在日常操作中,相信很多人在Linux内核怎么判断地址是否位于用户空间问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux内核怎么判断地址是否位于用户空间”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
access_ok函数是什么原理?
问题
我们在内核空间和用户空间进行数据拷贝的时候必须判断用户空间地址是否合法。主要通过偶函数access_ok来判断。
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。
通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。
进程寻址空间0~4G
进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G
进程通过系统调用进入内核态
每个进程虚拟空间的3G~4G部分是相同的
进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变
原型:
access_ok ( type,addr,size);
功能:
access_ok — 检查用户空间指针是否有效 注意,根据体系结构的不同,这个函数可能只是检查指针是否在用户空间范围内,在调用这个函数之后,内存访问函数可能仍然返回 -EFAULT
参数说明:
typeType of access: VERIFY_READ or VERIFY_WRITE. 请注意,VERIFY_WRITE是VERIFY_READ的超集——如果写入一个块是安全的,那么从它读取总是安全的。addr要检查的块的开始的用户空间指针size要检查的块的大小
返回值:
此函数检查用户空间中的内存块是否可用。如果可用,则返回真(非0值),否则返回假 (0) 。
#define access_ok(type, addr, size) (__range_ok(addr, size) == 0)
/* We use 33-bit arithmetic here... */ #define __range_ok(addr, size) ({ \ unsigned long flag, roksum; \ __chk_user_ptr(addr); \ __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \ : "=&r" (flag), "=&r" (roksum) \ : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \ : "cc"); \ flag; })
static inline void __chk_user_ptr(const volatile void *p, size_t size) { assert(p >= __user_addr_min && p + size <= __user_addr_max); }
其中__range_ok详解如下:参数对应:
flag -------- %0 roksum -------- %1 addr -------- %2 size -------- %3
adds %1, %2, %3
等价于:
rosum = addr + size
这个操作会影响状态位(目的是影响是进位标志C)。
以下的两个指令都带有条件CC,也就是当C=0的时候才执行;如果上面的加法指令进位了(C=1),则以下的指令都不执行,flag就为初始值current_thread_info()->addr_limit(非0),并返回。如果没有进位(C=0),就执行下面的指令:
sbcccs %1, %1, %0
该指令等价于
rosum = rosum - flag - 1
也就是(addr + size) - (current_thread_info()->addr_limit) - 1,操作影响符号位。.
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,则C=1 如果(addr + size) < (current_thread_info()->addr_limit) - 1,则C=0 当C=0的时候执行以下指令,否则跳过(flag非零)。
movcc %0, #0
等价于
flag = 0,给flag赋值0。
综上所述:__range_ok宏等价于:
如果(addr + size) >= (current_thread_info()->addr_limit) - 1,返回非零值 如果(addr + size) < (current_thread_info()->addr_limit),返回零
而access_ok就是检验将要操作的用户空间的地址范围是否在当前进程的用户地址空间限制中。这个宏的功能很简单,完全可以用C实现,不是必须使用汇编。 由于这两个函数使用频繁,就使用汇编来实现部分功能来增加效率。
我们在内核拷贝数据到用户空间或者从用户空间拷贝数据到内核空间,都需要判断用户空间地址是否在用户空间。
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n) { if (access_ok(VERIFY_READ, from, n)) n = __copy_from_user(to, from, n); else /* security hole - plug it */ memset(to, 0, n); return n; } static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n) { if (access_ok(VERIFY_WRITE, to, n)) n = __copy_to_user(to, from, n); return n; }
到此,关于“Linux内核怎么判断地址是否位于用户空间”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。