copy_from_user函数详细分析

2023-05-16

copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.这么简单的一个函数却含盖了许多关于内核方面的知识,比如内核关于异常出错的处理.从用户空间拷贝数据到内核中时必须非常小心,如果用户空间的数据地址是个非法的地址,或是超出用户空间的范围,或是那些地址还没有被映射到,都可能对内核产生很大的影响,如oops,或者被造成系统安全的影响.所以copy_from_user函数的功能就不只是从用户空间拷贝数据那样简单了,它还要做一些指针检查以及处理这些问题的方法.
    下面我们来仔细分析下这个函数.函数原型在[arch/i386/lib/usercopy.c]中
 
unsigned long
copy_from_user(void *to, const void __user *from, unsigned long n)
{
  might_sleep(); 
  if (access_ok(VERIFY_READ, from, n))
     n = __copy_from_user(to, from, n);
  else
     memset(to, 0, n);
  return n;
}
 
首先这个函数是可以睡眠的,它调用might_sleep()来处理,它在include/linux/kernel.h中定义,本质也就是调用schedule(),转到其他进程.接下来就要验证用户空间地址的有效性.它在
/include/asm-i386/uaccess.h中定义.
#define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0)),进一步调用__rang_ok函数来处理,它所做的测试很简单,就是比较addr+size这个地址的大小是否超出了用户进程空间的大小,也就是0xbfffffff.可能有读者会问,只做地址范围检查,怎么不做指针合法性的检查呢,如果出现前面提到过的问题怎么办?这个会在下面的函数中处理,我们慢慢看.在做完地址范围检查后,如果成功则调用__copy_from_user函数开始拷贝数据了,如果失败的话,就把从to指针指向的内核空间地址到to+size范围填充为0.
 
__copy_from_user也在uaceess.h中定义,

static inline unsigned long
__copy_from_user(void *to, const void __user *from, unsigned long n)
{
   might_sleep();
   return __copy_from_user_inatomic(to, from, n);
}

这里继续调用__copy_from_user_inatomic.

static inline unsigned long
__copy_from_user_inatomic(void *to, const void __user *from, unsigned long n)
{
   if (__builtin_constant_p(n)) {
   unsigned long ret;
   switch (n) {
    case 1:
       __get_user_size(*(u8 *)to, from, 1, ret, 1);
       return ret;
    case 2:
       __get_user_size(*(u16 *)to, from, 2, ret, 2);
       return ret;
    case 4:
       __get_user_size(*(u32 *)to, from, 4, ret, 4);
       return ret;
    }
  }
  return __copy_from_user_ll(to, from, n);
}

这里先判断要拷贝的字节大小,如果是8,16,32大小的话,则调用__get_user_size来拷贝数据.这样做是一种程序设计上的优化了。
#define __get_user_size(x,ptr,size,retval,errret) \
do { \
retval = 0; \
__chk_user_ptr(ptr); \
switch (size) { \
case 1: __get_user_asm(x,ptr,retval,"b","b","=q",errret);break; \
case 2: __get_user_asm(x,ptr,retval,"w","w","=r",errret);break; \
case 4: __get_user_asm(x,ptr,retval,"l","","=r",errret);break; \
default: (x) = __get_user_bad(); \
} \
} while (0)
#define __get_user_asm(x, addr, err, itype, rtype, ltype, errret) \
__asm__ __volatile__( \
"1: mov"itype" %2,%"rtype"1\n" \
"2:\n" \
".section .fixup,\"ax\"\n" \
"3: movl %3,%0\n" \
" xor"itype" %"rtype"1,%"rtype"1\n" \
" jmp 2b\n" \
".previous\n" \
".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 1b,3b\n" \
".previous" \
: "=r"(err), ltype (x) \
: "m"(__m(addr)), "i"(errret), "0"(err))
实际上在完成一些宏的转换后,也就是利用movb,movw,movl指令传输数据了,对于
内嵌汇编中的.section .fixup, .section __ex_table,我们呆会要仔细讲。
如果不是那些特殊大小时,则调用__copy_from_user_ll处理。
 
unsigned long
__copy_from_user_ll(void *to, const void __user *from, unsigned long n)
{
    if (movsl_is_ok(to, from, n))
        __copy_user_zeroing(to, from, n);
    else
       n = __copy_user_zeroing_intel(to, from, n);
    return n;
}
直接调用__copy_user_zeroing开始真正的拷贝数据了,绕了那么多弯,总算快看到出路了。copy_from_user函数的精华部分也就都在这了。
 
#define __copy_user_zeroing(to,from,size) \
do { \
int __d0, __d1, __d2; \
__asm__ __volatile__( \
" cmp $7,%0\n" \
" jbe 1f\n" \
" movl %1,%0\n" \
" negl %0\n" \
" andl $7,%0\n" \
" subl %0,%3\n" \
"4: rep; movsb\n" \
" movl %3,%0\n" \
" shrl $2,%0\n" \
" andl $3,%3\n" \
" .align 2,0x90\n" \
"0: rep; movsl\n" \
" movl %3,%0\n" \
"1: rep; movsb\n" \
"2:\n" \
".section .fixup,\"ax\"\n" \
"5: addl %3,%0\n" \
" jmp 6f\n" \
"3: lea 0(%3,%0,4),%0\n" \
"6: pushl %0\n" \
" pushl %%eax\n" \
" xorl %%eax,%%eax\n" \
" rep; stosb\n" \
" popl %%eax\n" \
" popl %0\n" \
" jmp 2b\n" \
".previous\n" \
".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 4b,5b\n" \
" .long 0b,3b\n" \
" .long 1b,6b\n" \
".previous" \
: "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2) \
: "3"(size), "0"(size), "1"(to), "2"(from) \
: "memory"); \
} while (0)
 
    这个函数的前一部分比较简单,也就是拷贝数据.关于后一部分就会涉及到我们前面提到过的那些情况了,如果用户空间的地址没被映射怎么办呢?在一些老的内核版本中是用verify_area()来验证地址地址合法性的,比如在早期的linux 0.11内核.
[linux0.11/kenrel/fork.c]
// 进程空间写前验证函数。在现代CPU中,其控制寄存器CR0有个写保护标志位(wp:16),内核可以通过设置
// 该位来禁止特权级0的代码向用户空间只读页面执行写数据,否则将导致写保护异常。
// addr为内存物理地址
void verify_area(void * addr,int size)
{
unsigned long start;
start = (unsigned long) addr;
size += start & 0xfff; // start & 0xfff为起始地址addr在页面中的偏移,2^12=4096
start &= 0xfffff000; // start为页开始地址,即页面边界值。此时start为当前进程空间中的逻辑地址start += get_base(current->ldt[2]); // get_base(current->ldt[2])为进程数据段在线性地址空间中的开始地址,在加上start,变为系统这个线性空间中的地址
页边界 addr ----size----- 页边界
+--------------------------------------------------------+
| ... | start&0xfff | | | ... |
+--------------------------------------------------------+
| start |
start-----------size-------------
while (size>0) {
size -= 4096;
write_verify(start); // 以页为单位,进行写保护验证,如果页为只读,则将其变为可写
start += 4096;
}
}
[linux0.11/mm/memory.c]
// 验证线性地址是否可写
void write_verify(unsigned long address)
{
unsigned long page;
// 如果对应页表为空的话,直接返回
if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
return;
page &= 0xfffff000;
page += ((address>>10) & 0xffc);
// 经过运算后page为页表项的内容,指向实际的一页物理地址
if ((3 & *(unsigned long *) page) == 1) // 验证页面是否可写,不可写则执行un_wp_page,取消写保护.
un_wp_page((unsigned long *) page);
return;
}
 
   但是如果每次在用户空间复制数据时,都要做这种检查是很浪费时间的,毕竟坏指针是很少存在的,在新内核中的做法是,在从用户空间复制数据时,取消验证指针合法性的检查,只多地址范围的检查,就象access_ok()所做的那样,一但碰上了坏指针,就要页异常出错处理程序去处理它了.我们去看看do_page_fault函数.
[arch/asm-i386/mm/fault.c/do_page_falut()]
fastcall void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
...
...
if (!down_read_trylock(&mm->mmap_sem)) {
if ((error_code & 4) == 0 &&
!search_exception_tables(regs->eip))
goto bad_area_nosemaphore;
down_read(&mm->mmap_sem);
}
...
...
bad_area_nosemaphore:
...
no_context:
if (fixup_exception(regs))
return;
...
...
}
 
error_code保存的是出错码,(error_code & 4) == 0代表产生异常的原因是在内核中.它调用fixup_exception(regs)来处理这个问题.既然出错了,那么如何来修复它呢?先看下fixup_exception()函数的实现:
 
[arch/asm-i386/mm/extable.c]
int fixup_exception(struct pt_regs *regs)
{
const struct exception_table_entry *fixup;
...
fixup = search_exception_tables(regs->eip);
if (fixup) {
regs->eip = fixup->fixup;
return 1;
}
...
}
[kernel/extable.c]
const struct exception_table_entry *search_exception_tables(unsigned long addr)
{
const struct exception_table_entry *e;
e = search_extable(__start___ex_table, __stop___ex_table-1, addr);
if (!e)
e = search_module_extables(addr);
return e;
}
[/lib/extable.c]
const struct exception_table_entry *
search_extable(const struct exception_table_entry *first,
const struct exception_table_entry *last,
unsigned long value)
{
while (first <= last) {
const struct exception_table_entry *mid;
mid = (last - first) / 2 + first;
if (mid->insn < value)
first = mid + 1;
else if (mid->insn > value)
last = mid - 1;
else
return mid;
}
return NULL;
}
 
在内核中有个异常出错地址表,在地址表中有个出错地址的修复地址也气对应,它结构如下:

[/include/asm-i386/uaccess.h]
struct exception_table_entry
{
    unsigned long insn, fixup;
};

insn是产生异常指令的地址,fixup用来修复出错地址的地址,也就是当异常发生后,用它的地址来替换异常指令发生的地址。__copy_user_zeroing中的.section __ex_table代表异常出错地址表的地址,.section .fixup代表修复的地址。他们都是elf文件格式中的2个特殊节。

".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 4b,5b\n" \
" .long 0b,3b\n" \
" .long 1b,6b\n"

4b,5b的意思是当出错地址在4b标号对应的地址上时,就转入5b标号对应的地址去接着运行,也就是修复的地址。依次类推。所以理解这一点后,fixup_exception()函数就很容易看明白了就是根据出错地址搜索异常地址表,找到对应的修复地址,跳转到那里去执行就ok了。
   ok,到这里copy_from_user函数也就分析完了,如果有什么不明白的话,可以通过阅读/usr/src/linux/Documentation/exception.txt来得到更多关于异常处理方面的知识。

[转自:http://blog.chinaunix.net/uid-20469034-id-1943565.html]



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

copy_from_user函数详细分析 的相关文章

  • Gradle技术之一 Groovy语法精讲

    Gradle技术之一 Groovy语法精讲 gradle脚本是基于groovy语言开发的 xff0c 想要学好gradle必须先要对groovy有一个基本的认识 1 Groovy特点 groovy是一种DSL语言 xff0c 所谓的DSL语
  • 字符串子串的查找

    1 考虑用标准函数库中 strstr 函数 包含文件 xff1a string h 函数名 strstr 函数原型 xff1a extern char strstr char str1 char str2 功能 xff1a 从字符串str1
  • 大锤老湿教您如何配置TP-Link路由器组建wifi上网

    TP Link路由器设置教程 大家好 xff0c 今天由大锤老湿教大家如何设置使用最广的TP Link路由器 一般家庭都希望能上wifi 那么首先看看我们如何将新买回的或者由于故障已经恢复成重置出厂状态的路由器 xff0c 如何经过重新设置
  • 【ESP01S】使用串口调试助手,发送AT指令收回的是乱码/重复一遍AT指令发回的问题

    调试帮助 span class token punctuation span 技术交流Q xff1a span class token number 1083091092 span xff08 备注CSDN xff09 一 问题描述 在使用
  • 刷leetcode使用python还是c++?

    我身边80 的程序员朋友在刷题的时候会选择Java xff0c 很少有人用C 43 43 来刷题 这两门语言各有特点 xff1a C 43 43 xff1a 从C语言发展过来的一门语言 xff0c 继承了灵活 xff08 可以潜入任何现代的
  • VINS-Mono代码精简版代码详解-后端非线性优化(三)

    非线性优化部分代码解析 之前已经对VINS Mono的初始化部分进行了介绍 xff0c 下面结合代码和公式介绍其非线性优化部分 本文部分参考 https blog csdn net u012871872 article details 78
  • Ubuntu IO占用过多导致文件读取变慢的原因查找方法

    问题描述 xff1a 多用户服务器 xff0c ubuntu系统 xff0c 突然点开文件夹 xff0c 发现变慢 查看方法 xff1a step1 xff1a 进入管理员用户 step2 xff1a 运行iostat x 1 在显示的结果
  • ROS Docker

    Docker 常用指令 docker pull osrf ros galactic desktop 从网络上下载镜像 docker images 查看已加载镜像列表 window docker界面 xff1a 命令行结果 xff1a doc
  • Win10C盘文件夹内容详解(持续更新,欢迎留言)

    本文参考以下博客 Roaming和Local的区别 C Users 用户名 AppData 1 Local和Roaming之间的区别 xff1a Local 比较大 xff0c 非漫游应用数据 Roaming 一般是漫游应用数据 2 Roa
  • STM32运行FreeRTOS

    使用ARM Keil 的 Keil uVision IDE xff0c 在 STM32上运行 FreeRTOS 内核 物料清单 软件 在创建新项目之前 xff0c 我们必须安装软件包 下面是打印屏幕 xff0c 其中包含如何执行此操作的步骤
  • ESP32实践FreeRTOS

    将部分代码作为应用程序中的任务独立执行可以简化大型复杂问题的设计 当有多个 CPU 时 xff0c 任务支持还允许选定的功能并行运行 本文将调查 Arduino 框架对 ESP32 系列设备的 FreeRTOS 任务支持 除了少数例外 xf
  • 黑马程序员—5—Java基础:多态学习笔记和学习心得体会

    lt ahref 61 34 http www itheima com 34 target 61 34 blank 34 gt android 培训 lt a gt lt ahref 61 34 http www itheima com 3
  • 图像去噪算法简介

    一 xff0c 背景 随着各种数字仪器和数码产品的普及 xff0c 图像和视频已成为人类活动中最常用的信息载体 xff0c 它们包含着物体的大量信息 xff0c 成为人们获取外界原始信息的主要途径 然而在图像的获取 传输和存贮过程中常常会受
  • Android 7 Nougat 源码目录结构

    code style margin 0px auto font family none padding 0px color inherit background color transparent art Android Runtime x
  • 【无人驾驶规划】BOSS无人车规划算法

    无人驾驶规划 BOSS无人车规划算法 1 boss运动规划结构2 轨迹生成2 1 状态约束2 2 车辆模型2 3 控制参数化2 4 初始化轨迹2 5 轨迹优化 3 on road模式规划3 1 路径生成3 2 轨迹生成3 3 轨迹速度配置3
  • 这也太全面了 阿里王牌级“Docker全线笔记”,Github已标星80k+,我太爱

    写在开头 司汤达说过 xff1a 一个人只要强烈地坚持不懈地追求 xff0c 他就能达到目的 Docker的创始人Solomon Hykes就是以这样的精神 xff0c 在docker即将坚持不下去的时候 xff0c 选择的不是放弃 xff
  • 如何在keil5中新建.c和.h文件?

    有两种方法 xff1a 方法1 在keil5内部添加两个文件分别为 c和 h文件 xff0c 可以保存在一个新建的文件夹里 xff08 前提是此文件夹是在keil5内部保存时新建的文件夹 xff0c 而不是在keil5软件外自己新建的文件夹
  • CMake(十二):构建类型

    本章和下一章涉及两个密切相关的主题 构建类型 在某些IDE工具中也称为构建配置或构建方案 是一种高级控件 xff0c 它选择不同的编译器和链接器行为集 构建类型的操作是本章的主题 xff0c 而下一章将介绍控制编译器和链接器选项的更具体细节
  • CMake:构建、链接静态库和动态库

    CMake 构建 链接静态库和动态库 导言一 多目录多文件CMake构建方式1 项目结构2 message h3 message cpp4 hello world cpp5 CMakeLists txt6 构建及编译 二 静态库和动态库简介
  • msckf_mono构建运行方法

    背景 博主是在读Davide Scaramuzza投稿到ICRA 2018的VIO综述文章 A Benchmark Comparison of Monocular Visual Odometry Algorithms for Flying

随机推荐

  • IMU相关技术资料整理

    关于IMU噪声参数 xff1a IMU噪声参数模型的参考文档 xff1a https github com ethz asl kalibr wiki IMU Noise Model针对消费级IMU器件的噪声参数进行适度的不确定性放大 xff
  • Shell中空格引起的血案

    最近开始写点shell脚本 xff0c 对linux命令还是比较熟悉的 xff0c 但是shell脚本却没写过 xff0c 没想 xff0c 刚开始写 xff0c 就郁闷重重 各种语法错误 xff01 xff01 xff01 最简单的自定义
  • OpenCV-Python视频分析(移动物体检测,物体追踪)

    1 概述 该文章介绍OpenCV Python中关于视频分析的两个主要内容 xff0c 分别为 xff1a x1f41f 背景差分法移动物体检测 x1f41f Meanshift和Camshift算法物体追踪 PS xff1a 视频分析还要
  • 最新2014欢聚时代(YY)软件研发笔试题

    今天上午刚考完 C C 43 43 题目难度你们感受一下 xff01 总分80 43 60 61 140 题目上的答案请忽略
  • python 计算经纬度之间的距离

    def get distance lon1 lat1 lon2 lat2 lon1 lat1 lon2 lat2 61 map radians lon1 lat1 lon2 lat2 radians 角度转弧度 d lon 61 lon2
  • python多线程爬虫教学,清晰易懂。

    首先需要知道什么是多线程 xff0c 多线程的作用 首先举个例子 xff0c 并发和并行 xff1a 并发 xff1a 并发 xff0c 在操作系统中 xff0c 是指一个时间段中有几个程序都处于已启动运行到运行完毕之间 xff0c 且这几
  • Matlab 读取txt文本中的数据

    使用matlab读取txt文本中的数据 数据最好有一定的规律 我们可以使用函数importdata来导入数据 下面使用一个例子来说明该函数的使用 start path C 设置默认文件夹 filename pathname uigetfil
  • 2018-07-25-github-如何在Github上面创建Release

    github release 看别的Github项目都有一条类似timeline 时间线 的版本列表 xff0c 如下图 xff0c 所以在Github上面摸索了一下 xff0c 弄好了记录一下 创建一个Release TestBefore
  • SSH登录卡在‘Last login‘提示界面的一种原因

    以前解决过SSH登录卡顿的问题 xff0c 它一般来源于 xff1a GSSAPIAuthentication UseDNS 以上设置项被默认打开或意外打开 而这次遇到的问题不是卡顿 xff0c 而是卡在 Last login xff0c
  • 【云原生之Docker实战】使用Docker部署openwrt软路由

    云原生之Docker实战 使用Docker部署openwrt软路由 一 openwrt介绍 二 检查本地docker状态 1 查看docker版本 2 查看docker信息 3 查看本地docker网络 三 安装docker compose
  • 值得收藏:图解算法——动态规划系列

    个人博客导航页 xff08 点击右侧链接即可打开个人博客 xff09 xff1a 大牛带你入门技术栈 小浩 xff1a 宜信科技中心攻城狮一枚 xff0c 热爱算法 xff0c 热爱学习 xff0c 不拘泥于枯燥编程代码 xff0c 更喜欢
  • TensorFlow之循环神经网络&自然语言处理 学习总结

    作者 xff1a jliang https blog csdn net jliang3 junliang 20190303 说明 xff1a 以下所有代码使用版本TensorFlow1 4 0或1 12 0版本 import tensorf
  • Docker镜像容器的迁移问题

    本文是本人项目踩坑经验 xff0c 如有错漏请见谅 xff01 背景需求 xff1a 一个已经配置好的容器 xff08 无build文件 xff09 需要部署到生产环境中 xff0c 容器内有程序绑定了宿主环境的硬件信息 需求部署后能让第三
  • 视觉里程计1 高翔

    小白 xff08 我 xff09 本着学习 xff0c 提高自我 xff0c 增加知识的想法 xff0c 决定认真分析高翔博士slam 在此立下一个flag xff1a 每周进行知识总结 xff08 CSDN xff09 xff1b 每周要
  • InvokeHelper函数的用法

    ActiveX控件的方法和属性操作与生成的C 43 43 类成员函数相关联都是通过InvokeHelper函数的调用来完成的 xff0c InvokeHelper函数的第一个参数是由Component Gallery xff08 控件提供者
  • 前向声明! struct netif; —— 只声明,无具体内部细节

    今天在看到 Linux阅码场 的 宋宝华 xff1a Linux内核编程广泛使用的前向声明 Forward Declaration xff0c 非常感谢 xff01 前向声明 编程定律 先强调一点 xff1a 在一切可能的场景 xff0c
  • MCU初始化流程——从上电到main()之间

    说明 xff1a 以下介绍示例的MCU地址空间如下 xff1a ROM空间为 xff1a 0x0000 0000 0x0000 8000 RAM空间为 xff1a 0x2000 0000 0x2000 2000 堆栈 SP 生长方向为 递减
  • FreeRTOS 启动第一个任务 prvStartFirstTask vPortSVCHandler

    asm void prvStartFirstTask void asm void prvStartFirstTask void PRESERVE8 Use the NVIC offset register to locate the sta
  • 组播知识 - IGMP

    https zhuanlan zhihu com p 258619129 组播初识 一 为什么要启用组播 xff1f 1 节省不必要的数据发送 2 需要发送相同的数据去往多个不同的接收者 3 减少带宽的占用 4 优化网络设备的处理进程 5
  • copy_from_user函数详细分析

    copy from user函数的目的是从用户空间拷贝数据到内核空间 xff0c 失败返回没有被拷贝的字节数 xff0c 成功返回0 这么简单的一个函数却含盖了许多关于内核方面的知识 比如内核关于异常出错的处理 从用户空间拷贝数据到内核中时