精华内容
下载资源
问答
  • copy

    千次阅读 2013-06-21 15:30:09
    PostgreSQL Copy 命令 COPY -- 在表和文件之间拷贝数据 Synopsis COPY tablename [ ( column [, ...] ) ] FROM { 'filename' | STDIN } [ [ WITH ] [ BINARY ] [ OIDS ] [ DELIMI

    PostgreSQL  Copy 命令

    COPY --  在表和文件之间拷贝数据


    Synopsis

    COPY tablename [ ( column [, ...] ) ]
        FROM { 'filename' | STDIN }
        [ [ WITH ]
              [ BINARY ]
              [ OIDS ]
              [ DELIMITER [ AS ] 'delimiter' ]
              [ NULL [ AS ] 'null string' ]
              [ CSV [ QUOTE [ AS ] 'quote' ]
                    [ ESCAPE [ AS ] 'escape' ]
                    [ FORCE NOT NULL column [, ...] ]
    
    COPY tablename [ ( column [, ...] ) ]
        TO { 'filename' | STDOUT }
        [ [ WITH ]
              [ BINARY ]
              [ OIDS ]
              [ DELIMITER [ AS ] 'delimiter' ]
              [ NULL [ AS ] 'null string' ]
              [ CSV [ QUOTE [ AS ] 'quote' ]
                    [ ESCAPE [ AS ] 'escape' ]
                    [ FORCE QUOTE column [, ...] ]
      
    

    描述

    COPY 在 PostgreSQL表和标准文件系统文件之间交换数据。 COPY TO把一个表的所有内容都拷贝一个文件, 而COPY FROM一个文件里拷贝数据到一个表里(把数据附加到表中已经存在的内容里)。

    如果声明了一个字段列表,COPY 将只在文件和表之间拷贝声明的字段的数据。如果表中有任何不在字段列表里的字段,那么 COPY FROM 将为那些字段插入缺省值。

    带文件名的 COPY 指示 PostgreSQL 服务器直接从文件中读写数据。如果声明了文件名,那么该文件必须为服务器可见,而且文件名必须从服务器的角度声明。如果声明的是STDINSTDOUT,数据通过连接在客户前端和服务器之间流动。


    参数

    tablename

    现存表的名字(可以有模式修饰)。

    column

    可选的待拷贝字段列表。如果没有声明字段列表,那么将使用所有字段。

    filename

    输入或输出文件的绝对路径名。

    STDIN

    声明输入是来自客户端应用。

    STDOUT

    声明输入前往客户端应用。

    BINARY

    使用二进制格式存储和读取,而不是以文本的方式。 在二进制模式下,不能声明DELIMITERSNULL 或者 CSV 选项。

    OIDS

    声明为每行拷贝内部对象标识(OID)。 (如果给那些没有 OID 的表声明了 OIDS选项,则抛出一个错误。)

    delimiter

    用于在文件中每行中分隔各个字段的单个字符。 在文本模式下,缺省是水平制表符(tab),在 CSV模式下是一个逗号。

    null string

    个代表 NULL 值的字串。在文本模式下缺省是 \N (反斜杠-N), 在 CSV模式下是一个没有引号的空值。 如果你不想区分空值和空字串,那么即使在文本模式下可能你也会用一个空字串。

    注意: 在使用 COPY FROM 的时候,任何匹配这个字串的字串将被存储为 NULL 值,所以你应该确保你用的字串和COPY TO相同。

    CSV

    打开逗号分隔变量(CSV)模式。

    quote

    声明 CSV 模式里的引号字符。缺省是双引号。

    escape

    声明在 CSV 模式下应该出现在数据里 QUOTE 字符值前面的字符。 缺省是QUOTE 值(通常是双引号)。

    FORCE QUOTE

    CSV COPY TO 模式下,强制在每个声明的字段周围对所有非NULL 值都使用引号包围。 NULL 从不会被引号包围。

    FORCE NOT NULL

    CSV COPY FROM 模式下,把声明的每个字段都当作它们有引号包围来处理,因此就没有 NULL 值。对于在CSV 模式下的缺省空字串(''),这样导致一个缺失的数值当作一个零长字串输入。


    注意

    COPY 只能用于表,不能用于视图。

    BINARY 关键字将强制使用二进制对象而不是文本存储/读取所有数据。这样做在一定程度上比传统的拷贝命令快,但二进制拷贝文件在不同机器体系间的植性不是很好。

    你对任何要COPY TO 出来的数据必须有选取数据的权限,对任何要 COPY FROM入数据的表必须有插入权限。

    COPY 命令里面的文件必须是由服务器直接读或写的文件,而不是由客户端应用读写。因此,它们必须位于数据库服务器上或者可以为数据库服务器所访问,而不是由客户端做这些事情。它们必须是PostgreSQL用户(服务器运行的用户 ID)可以访问到并且可读或者可写,而不是客户端。COPY到一个命名文件是只允许数据库超级用户进行的,因为它允许读写任意服务器有权限访问的文件。

    不要混淆 COPY 和 psql 指令 \copy\copy 调用COPY FROM STDIN 或者COPY TO STDOUT, 然后把数据抓取/存储到一个psql 客户端可以访问的文件中。 因此,使用\copy的时候,文件访问权限是由客户端而不是服务器端决定的。

    我们建议在 COPY 里的文件名字总是使用绝对路径。 在 COPY TO的时候是由服务器强制进行的, 但是对于 COPY FROM,你的确有从一个声明为相对路径的文件里读取的选择。该路径将解释为相对于服务器的工作目录(在数据目录里的什么地方),而不是客户端的工作目录。

    COPY FROM 会激活所有触发器和检查约束。不过,不会激活规则。

    COPY 输入和输出是被 DateStyle 影响的。 为了和其它 PostgreSQL安装移植,(它们可能用的不是缺省DateStyle 设置), 我们应该在使用COPY 前把DateStyle 设置为ISO(set datestyle = iso;)

    COPY 在第一个错误处停下来。这些在 COPY TO中不应该导致问题, 但在COPY FROM 时目的表会已经接收到早先的行, 这些行将不可见或不可访问,但是仍然会占据磁盘空间。如果你碰巧是拷贝很大一块数据文件的话, 积累起来,这些东西可能会占据相当大的一部分磁盘空间。你可以调用VACUUM来恢复那些磁盘空间。


    文件格式


    文本格式

    当不带 BINARY 或者 CSV 选项使用 COPY 时,读写的文件是一个文本文件,每行代表表中一个行。 行中的列(字段)用分隔符分开。字段值本身是由与每个字段类型相关的输出函数生成的字符串, 或者是输入函数可接受的字串。 数据中使用特定的空值字串表示那些为 NULL的字段。 如果输入文件的任意行包含比预期多或者少的字段,那么COPY FROM将抛出一个错误。 如果声明了OIDS,那么 OID 将作为第一个字段读写, 放在所有用户字段前面。

    数据的结束可以用一个只包含反斜杠和句点(\.)的行表示。 如果从文件中读取数据,那么数据结束的标记是不必要的,因为文件结束起的作用就很好了;但是在 3.0 之前的客户端协议里,如果在客户端应用之间拷贝数据, 那么必须要有结束标记。

    反斜杠字符(\)可以用在 COPY里给那些会有歧义的字符进行逃逸(否则那些字符会被当做行或者字段分隔符处理)。特别是下面的字符如果是字段值的一部分时,必须前缀一个反斜杠:反斜杠本身,换行符,回车,以及当前分隔符。

    声明的空字串被 COPY TO 不加任何反斜杠发送;与之相对,COPY FROM在删除反斜杠之前拿它的输入与空字串比较。因此,像\N 这样的空字串不会和实际数据值\N之间混淆(因为后者会表现成 \\N)。

    COPY FROM 识别下列特殊反斜杠序列:


    序列
    代表物

    \b
    反斜杠 (ASCII 8)

    \f
    进纸 (ASCII 12)

    \n
    换行符 (ASCII 10)

    \r
    回车 (ASCII 13)

    \t
    Tab (ASCII 9)

    \v
    垂直制表符 (ASCII 11)

    \digits
    反斜杠后面跟着一到三个八进制数,表示ASCII值为该数的字符

    目前,COPY TO 将绝不会发出一个八进制反斜杠序列,但是它的确使用了上面列出的其它字符用于控制字符。

    绝对不要把反斜杠放在一个数据字符N或者句点(.)前面。这样的组合将分别被误认为是空字串或者数据结束标记。 另外一个没有在上面的表中列出的反斜杠字符就是它自己。

    我们强烈建议生成 COPY 数据的应用把换行符和回车分别转换成 \n\r 序列。 目前我们可以用一个反斜杠和一个回车表示一个数据回车,以及用一个反斜杠和一个换行符表示一个数据换行符。不过,这样的表示在将来的版本中缺省时可能不会被接受。 并且,如果在不同机器之间传递COPY文件,也是非常容易出错的 (比如在 Unix 和 Windows 之间)。

    COPY TO 将再每行的结尾是用一个 Unix风格的换行符("\n"), 或者是在Microsoft Windows上运行的服务器上用("\r\n")标记一行终止,但只是用于COPY到服务器文件里;为了在不同平台之间一致,COPY TO STDOUT 总是发送"\n",不管服务器平台是什么。COPY FROM 可以处理那些以回车符,或者换行符,或者回车换行符作为行结束的数据。为了减少在数据中出现的未逃逸的新行或者回车导致的错误,如果输入的行结尾不像上面这些符号,COPY FROM会发出警告。


    CSV 格式

    这个格式用于输入和输出逗号分隔数值(CSV)文件格式,许多其它程序都用这个文件格式,比如电子报表。这个模式下生成并识别逗号分隔的 CSV 逃逸机制, 而不是使用PostgreSQL标准文本模式的逃逸。

    每条记录的值都是用 DELIMITER 字符分隔的。 如果数值本身包含分隔字符,QUOTE字符,NULL 字串, 一个回车,或者进行字符,那么整个数值用QUOTE字符前缀和后缀(包围), 并且数值里任何QUOTE 字符或者 ESCAPE 字符都前导逃逸字符。你也可以使用FORCE QUOTE 在输出非 NULL 的指定字段值时强制引号包围。

    CSV 格式没有标准的办法区分一个 NULL 值和一个空字串。 PostgreSQL 的COPY 通过引号包围来处理这些。 一个当作NULL 输出的NULL 值是没有引号包围的,而匹配 NULL字串的数据值是用引号包围的。 因此,使用缺省设置时,一个NULL是写做一个无引号包围的空字串,而一个空字串写做双引号包围("")。读取数值也遵循类似的规则。你可以使用FORCE NOT NULL 来避免为特定字段进行NULL 比较。

    注意: CSV 模式可以识别和生成带有引号包围的回车和进行(hang)的 CVS 文件。因此这些文件并不像文本模式的文件那样严格地每个数据行一行。 不过,如果任何字段本身包含并不匹配 CVS 文件本身的换行符序列, 那么PostgreSQL 会拒绝COPY 输入。通常,输入包含行结束符的数据的时候,用文本或者二进制格式比 CSV更安全。

    注意: 许多程序生成奇怪的并且有时候不正确的 CVS 文件, 所以这个文件格式更像一种惯用格式,而不是一种标准。因此你可能碰到一些不能使用这个机制输入的文件,而COPY 也可能生成一些其它程序不能处理的文件。

    二进制格式

    在PostgreSQL 7.4 中的 COPY BINARY的文件格式做了变化。新格式由一个文件头,零或多条元组, 以及文件尾组成。文件头和数据现在是网络字节序。


    文件头

    文件头由 15 个字节的固定域组成,后面跟着一个变长的头扩展区。 固定域是:

    签名

    11-字节的序列 "PGBCOPY\n\377\r\n\0"— 请注意字节零是签名是要求的一部分。 (使用这个签名是为了让我们能够很容易看出文件是否已经被一个非 8 位安全的转换器给糟蹋了。这个签名会被行结尾转换过滤器,删除字节零,删除高位,或者奇偶的改变而改变。)

    标志域

    32 位整数掩码表示该文件格式的重要方面。 位是从 0(LSB)到 31 (MSB)编码的 —请注意这个域是以网络字节序存储的(高位在前), 后继的整数都是如此。位 16 - 31 是保留用做关键文件格式信息的;如果读者发现一个不认识的位出现在这个范围内,那么它应该退出。 位 0-15都保留为标志向后兼容的格式使用;读者可以忽略这个范围内的不认识的位。目前只定义了一个标志位,而其它的必须是零:

    Bit 16

    如果为 1,那么在数据中包括了 OID;如果为 0,则没有

    头扩展范围长度

    32 位整数,以字节计的头剩余长度,不包括自身。目前,它是零,后面紧跟第一条元组。对该格式的更多的修改都将允许额外的数据出现在头中。 读者应该忽略任何它不知道该如何处理的头扩展数据。

    头扩展数据是一个用来保留一个自定义的数据序列块用的。这个标志域无意告诉读者扩展区的内容是什么。头扩展的具体设计内容留给以后的版本用。

    这样设计就允许向下兼容头附加(增加头扩展块,或者设置低位序标志位)以及非向下兼容修改(设置高位标志位以标识这样的修改,并且根据需要向扩展区域增加支持数据)。


    元组

    每条元组都以一个 16 位整数计数开头,该计数是元组中字段的数目。(目前,在一个表里的每条元组都有相同的计数,但可能不会永远这样。) 然后后面不断出现元组中的各个字段,字段先是一个 32位的长度字,后面跟着那么长的字段数据。 (长度字并不包括自己,并且可以为零。)一个特例是:-1 表示一个 NULL 字段值。 在NULL 情况下,后面不会跟着数值字节。

    在数据域之间没有对奇填充或者任何其它额外的数据。

    目前,一个 COPY BINARY 文件里的所有数据值都假设是二进制格式的(格式代码为一)。预计将来的扩展可能增加一个头域,允许为每个字段声明格式代码。

    为了判断实际元组数据的正确的二进制格式,你应该阅读 PostgreSQL 源代码, 特别是该字段数据类型的*send*recv函数(典型的函数可以在源代码的src/backend/utils/adt/ 目录找到)。

    如果在文件中包括了 OID,那么该 OID 域立即跟在字段计数字后面。它是一个普通的字段,只不过它没有包括在字段计数。但它包括长度字 --- 这样就允许我们不用花太多的劲就可以处理 4 字节和 8 字节的OID,并且如果某个家伙允许 OID 是可选的话,那么还可以把 OID 显示成空。


    文件尾

    文件尾包括保存着 -1 的一个 16 位整数字。这样就很容易与一条元组的域计数字相区分。

    如果一个域计数字既不是 -1 也不是预期的字段的数目,那么读者应该报错。 这样就提供了对丢失与数据的同步的额外的检查。


    例子

    下面的例子把一个表拷贝到客户端, 使用竖直条(|)作为域分隔符:

    COPY country TO STDOUT WITH DELIMITER '|';
    

    从一个 Unix 文件中拷贝数据到一个country表中:

    COPY country FROM '/usr1/proj/bray/sql/country_data';
      
    

    下面是一个可以从 STDIN 中拷贝数据到表中的例子:

    AF      AFGHANISTAN
    AL      ALBANIA
    DZ      ALGERIA
    ZM      ZAMBIA
    ZW      ZIMBABWE
    

    请注意在这里每行里的空白实际上是一个水平制表符 tab。

    下面的是同样的数据,在一台 Linux/i586 机器上以二进制形式输出。 这些数据是用 Unix 工具 od-c 过滤之后输出的。 该表有三个字段;第一个是char(2), 第二个是text,第三个是integer。所有的行在第三个域都是一个 null 值。

    0000000   P   G   C   O   P   Y  \n 377  \r  \n  \0  \0  \0  \0  \0  \0
    0000020  \0  \0  \0  \0 003  \0  \0  \0 002   A   F  \0  \0  \0 013   A
    0000040   F   G   H   A   N   I   S   T   A   N 377 377 377 377  \0 003
    0000060  \0  \0  \0 002   A   L  \0  \0  \0 007   A   L   B   A   N   I
    0000100   A 377 377 377 377  \0 003  \0  \0  \0 002   D   Z  \0  \0  \0
    0000120 007   A   L   G   E   R   I   A 377 377 377 377  \0 003  \0  \0
    0000140  \0 002   Z   M  \0  \0  \0 006   Z   A   M   B   I   A 377 377
    0000160 377 377  \0 003  \0  \0  \0 002   Z   W  \0  \0  \0  \b   Z   I
    0000200   M   B   A   B   W   E 377 377 377 377 377 377
    

    兼容性

    在 SQL 标准里没有 COPY 语句。

    PostgreSQL 7.3 以前使用下面的语法,现在仍然支持:

    COPY [ BINARY ] tablename [ WITH OIDS ]
        FROM { 'filename' | STDIN }
        [ [USING] DELIMITERS 'delimiter' ]
        [ WITH NULL AS 'null string' ]
    
    COPY [ BINARY ] tablename [ WITH OIDS ]
        TO { 'filename' | STDOUT }
        [ [USING] DELIMITERS 'delimiter' ]
        [ WITH NULL AS 'null string' ]
    
    展开全文
  • copy_{to, from}_user()的思考

    万次阅读 多人点赞 2019-09-13 00:16:43
    引言我们对copy_{to,from}_user()接口的使用应该是再熟悉不过吧。基本Linux书籍都会介绍它的作用。毕竟它是kernel space和user spac...
        

    引言

    我们对copy_{to,from}_user()接口的使用应该是再熟悉不过吧。基本Linux书籍都会介绍它的作用。毕竟它是kernel space和user space沟通的桥梁。所有的数据交互都应该使用类似这种接口。所以,我们没有理由不知道接口的作用。但是,我也曾经有过以下疑问。

    1. 为什么需要copy_{to,from}_user(),它究竟在背后为我们做了什么?

    2. copy_{to,from}_user()和memcpy()的区别是什么,直接使用memcpy()可以吗?

    3. memcpy()替代copy_{to,from}_user()是不是一定会有问题?

    一下子找回了当年困惑的自己。我所提出的每个问题,曾经我也思考过。还不止一次的思考,每一次都有不同的想法。当然是因为从一开始就我就没有完全理解。现在又重新回到这个沉重的话题,继续思考这曾经的问题。

    温馨提示:文章代码分析基于Linux-4.18.0,部分架构相关代码以ARM64为代表。

    百家争鸣

    针对以上问题当然是先百度。百度对于该问题的博客也是很多,足以看出这个问题肯定困惑着一大批Linux的爱好者。对于我的查阅结果来说,观点主要分成以下两种:

    • copy_{to,from}_user()比memcpy()多了传入地址合法性校验。例如是否属于用户空间地址范围。理论上说,内核空间可以直接使用用户空间传过来的指针,即使要做数据拷贝的动作,也可以直接使用memcpy(),事实上在没有MMU的体系架构上,copy_{to,from}_user()最终的实现就是利用了mencpy()。但是对于大多数有MMU的平台,情况就有了些变化:用户空间传过来的指针是在虚拟地址空间上的,它所指向的虚拟地址空间很可能还没有真正映射到实际的物理页面上。但是这又能怎样呢?缺页导致的异常会很透明地被内核予以修复(为缺页的地址空间提交新的物理页面),访问到缺页的指令会继续运行仿佛什么都没有发生一样。但这只是用户空间缺页异常的行为,在内核空间这种缺页异常必须被显式地修复,这是由内核提供的缺页异常处理函数的设计模式决定的。其背后的思想是:在内核态,如果程序试图访问一个尚未被提交物理页面的用户空间地址,内核必须对此保持警惕而不能像用户空间那样毫无察觉。

    • 如果我们确保用户态传递的指针的正确性,我们完全可以用memcpy()函数替代copy_{to,from}_user()。经过一些试验测试,发现使用memcpy(),程序的运行上并没有问题。因此在确保用户态指针安全的情况下,二者可以替换。

    从各家博客上,观点主要集中在第一点。看起来第一点受到大家的广泛认可。但是,注重实践的人又得出了第二种观点,毕竟是实践出真知。真理究竟是是掌握在少数人手里呢?还是群众的眼睛是雪亮的呢?当然,我不否定以上任何一种观点。也不能向你保证哪种观点正确。因为,我相信即使是曾经无懈可击的理论,随着时间的推移或者特定情况的改变理论也可能不再正确。比如,牛顿的经典力学理论(好像扯得有点远)。如果要我说人话,就是:随着时间的推移,Linux的代码在不断的变化。或许以上的观点在曾经正确。当然,也可能现在还正确。下面的分析就是我的观点了。同样,大家也是需要保持怀疑的态度。下面我就抛砖引玉。

    抛砖引玉

    首先我们看下memcpy()和copy_{to,from}_user()的函数定义。参数几乎没有差别,都包含目的地址,源地址和需要复制的字节size。

    static __always_inline unsigned long __must_check	
    copy_to_user(void __user *to, const void *from, unsigned long n);	
    static __always_inline unsigned long __must_check	
    copy_from_user(void *to, const void __user *from, unsigned long n);	
    void *memcpy(void *dest, const void *src, size_t len);

    但是,有一点我们肯定是知道的。那就是memcpy()没有传入地址合法性校验。而copy_{to,from}_user()针对传入地址进行类似下面的合法性校验(简单说点,更多校验详情可以参考代码)。

    • 如果从用户空间copy数据到内核空间,用户空间地址to及to加上copy的字节长度n必须位于用户空间地址空间。

    • 如果从内核空间copy数据到用户空间,当然也需要检查地址的合法性。例如,是否越界访问或者是不是代码段的数据等等。总之一切不合法地操作都需要立刻杜绝。

    经过简单的对比之后,我们再看看其他的差异以及一起探讨下上面提出的2个观点。我们先从第2个观点说起。涉及实践,我还是有点相信实践出真知。从我测试的结果来说,实现结果分成两种情况。

    第一种情况的结果是:使用memcpy()测试,没有出现问题,代码正常运行。测试代码如下(仅仅展示proc文件系统下file_operations对应的read接口函数):

    static ssize_t test_read(struct file *file, char __user *buf,	
                             size_t len, loff_t *offset)	
    {	
            memcpy(buf, "test\n", 5);    /* copy_to_user(buf, "test\n", 5) */	
            return 5;	
    }

    我们使用cat命令读取文件内容,cat会通过系统调用read调用test_read,并且传递的buf大小是4k。测试很顺利,结果很喜人。成功地读到了“test”字符串。看起来,第2点观点是没毛病的。但是,我们还需要继续验证和探究下去。因为第1个观点提到,“在内核空间这种缺页异常必须被显式地修复”。因此我们还需要验证的情况是:如果buf在用户空间已经分配虚拟地址空间,但是并没有建立和物理内存的具体映射关系,这种情况下会出现内核态page fault。我们首先需要创建这种条件,找到符合的buf,然后测试。这里我当然没测啦。因为有测试结论(主要是因为我懒,构造这个条件我觉得比较麻烦)。这个测试是我的一个朋友,人称宋老师的“阿助教”阿克曼大牛。他曾经做个这个实验,并且得到的结论是:即使是没有建立和物理内存的具体映射关系的buf,代码也可以正常运行。在内核态发生page fault,并被其修复(分配具体物理内存,填充页表,建立映射关系)。同时,我从代码的角度分析,结论也是如此。

    经过上面的分析,看起来好像是memcpy()也可以正常使用,鉴于安全地考虑建议使用copy_{to,from}_user()等接口。

    第二种情况的结果是:以上的测试代码并没有正常运行,并且会触发kernel oops。当然本次测试和上次测试的kernel配置选项是不一样的。这个配置项是 CONFIG_ARM64_SW_TTBR0_PAN或者 CONFIG_ARM64_PAN(针对ARM64平台)。两个配置选项的功能都是阻止内核态直接访问用户地址空间。只不过CONFIG_ARM64_SW_TTBR0_PAN是软件仿真实现这种功能,而CONFIG_ARM64_PAN是硬件实现功能(ARMv8.1扩展功能)。我们以CONFIG_ARM64_SW_TTBR0_PAN作为分析对象(软件仿真才有代码提供分析)。BTW,如果硬件不支持,即使配置CONFIG_ARM64_PAN也没用,只能使用软件仿真的方法。如果需要访问用户空间地址需要通过类似copy_{to,from}_user()的接口,否则会导致kernel oops。

    在打开CONFIG_ARM64_SW_TTBR0_PAN的选项后,测试以上代码就会导致kernel oops。原因就是内核态直接访问了用户空间地址。因此,在这种情况我们就不可以使用memcpy()。我们别无选择,只能使用copy_{to,from}_user()。

    为什么我们需要PAN(Privileged Access Never)功能呢?原因可能是用户空间和内核空间数据交互上容易引入安全问题,所以我们就不让内核空间轻易访问用户空间,如果非要这么做,就必须通过特定的接口关闭PAN。另一方面,PAN功能可以更加规范化内核态和用户态数据交互的接口使用。在使能PAN功能的情况下,可以迫使内核或者驱动开发者使用copy_{to,from}_user()等安全接口,提升系统的安全性。类似memcpy()非规范操作,kernel就oops给你看。

    由于编程的不规范而引入安全漏洞。例如:Linux内核漏洞CVE-2017-5123可以提升权限。该漏洞的引入原因就是是缺少access_ok()检查用户传递地址的合法性。因此,为了避免自己编写的代码引入安全问题,针对内核空间和用户空间数据交互上,我们要格外当心。

    刨根问底

    既然提到了CONFIG_ARM64_SW_TTBR0_PAN的配置选项。当然我也希望了解其背后设计的原理。由于ARM64的硬件特殊设计,我们使用两个页表基地址寄存器ttbr0_el1和ttbr1_el1。处理器根据64 bit地址的高16 bit判断访问的地址属于用户空间还是内核空间。如果是用户空间地址则使用ttbr0_el1,反之使用ttbr1_el1。因此,ARM64进程切换的时候,只需要改变ttbr0_el1的值即可。ttbr1_el1可以选择不需要改变,因为所有的进程共享相同的内核空间地址。

    当进程切换到内核态(中断,异常,系统调用等)后,如何才能避免内核态访问用户态地址空间呢?其实不难想出,改变ttbr0_el1的值即可,指向一段非法的映射即可。因此,我们为此准备了一份特殊的页表,该页表大小4k内存,其值全是0。当进程切换到内核态后,修改ttbr0_el1的值为该页表的地址即可保证访问用户空间地址是非法访问。因为页表的值是非法的。这个特殊的页表内存通过链接脚本分配。

    #define RESERVED_TTBR0_SIZE    (PAGE_SIZE)	
    SECTIONS	
    {	
            reserved_ttbr0 = .;	
            . += RESERVED_TTBR0_SIZE;	
            swapper_pg_dir = .;	
            . += SWAPPER_DIR_SIZE;	
            swapper_pg_end = .;	
    }

    这个特殊的页表和内核页表在一起。和swapper_pg_dir仅仅差4k大小。reserved_ttbr0地址开始的4k内存空间的内容会被清零。

    当我们进入内核态后会通过__uaccess_ttbr0_disable切换ttbr0_el1以关闭用户空间地址访问,在需要访问的时候通过_uaccess_ttbr0_enable打开用户空间地址访问。这两个宏定义也不复杂,就以_uaccess_ttbr0_disable为例说明原理。其定义如下:

    .macro    __uaccess_ttbr0_disable, tmp1	
        mrs    \tmp1, ttbr1_el1                        // swapper_pg_dir (1)	
        bic    \tmp1, \tmp1, #TTBR_ASID_MASK	
        sub    \tmp1, \tmp1, #RESERVED_TTBR0_SIZE      // reserved_ttbr0 just before	
                                                    // swapper_pg_dir (2)	
        msr    ttbr0_el1, \tmp1                        // set reserved TTBR0_EL1 (3)	
        isb	
        add    \tmp1, \tmp1, #RESERVED_TTBR0_SIZE	
        msr    ttbr1_el1, \tmp1                       // set reserved ASID	
        isb	
    .endm
    1. ttbr1_el1存储的是内核页表基地址,因此其值就是swapper_pg_dir。

    2. swapper_pg_dir减去RESERVED_TTBR0_SIZE就是上面描述的特殊页表。

    3. 将ttbr0_el1修改指向这个特殊的页表基地址,当然可以保证后续访问用户地址都是非法的。

    __uaccess_ttbr0_disable对应的C语言实现可以参考这里。如何允许内核态访问用户空间地址呢?也很简单,就是__uaccess_ttbr0_disable的反操作,给ttbr0_el1赋予合法的页表基地址。这里就不必重复了。我们现在需要知道的事实就是,在配置CONFIG_ARM64_SW_TTBR0_PAN的情况下,copy_{to,from}_user()接口会在copy之前允许内核态访问用户空间,并在copy结束之后关闭内核态访问用户空间的能力。因此,使用copy_{to,from}_user()才是正统做法。主要体现在安全性检查及安全访问处理。这里是其比memcpy()多的第一个特性,后面还会介绍另一个重要特性。

    现在我们可以解答上一节中遗留的问题。怎样才能继续使用memcpy()?现在就很简单了,在memcpy()调用之前通过uaccess_enable_not_uao()允许内核态访问用户空间地址,调用memcpy(),最后通过uaccess_disable_not_uao()关闭内核态访问用户空间的能力。

    未雨绸缪

    以上的测试用例都是建立在用户空间传递合法地址的基础上测试的,何为合法的用户空间地址?用户空间通过系统调用申请的虚拟地址空间包含的地址范围,即是合法的地址(不论是否分配物理页面建立映射关系)。既然要写一个接口程序,当然也要考虑程序的健壮性,我们不能假设所有的用户传递的参数都是合法的。我们应该预判非法传参情况的发生,并提前做好准备,这就是未雨绸缪。

    我们首先使用memcpy()的测试用例,随机传递一个非法的地址。经过测试发现:会触发kernel oops。继续使用copy_{to,from}_user()替代memcpy()测试。测试发现:read()仅仅是返回错误,但不会触发kernel oops。这才是我们想要的结果。毕竟,一个应用程序不应该触发kernel oops。这种机制的实现原理是什么呢?

    我们以copy_to_user()为例分析。函数调用流程是:

    copy_to_user()->_copy_to_user()->raw_copy_to_user()->__arch_copy_to_user()

    _arch_copy_to_user()在ARM64平台是汇编代码实现,这部分代码很关键。

    end    .req    x5	
    ENTRY(__arch_copy_to_user)	
            uaccess_enable_not_uao x3, x4, x5	
            add    end, x0, x2	
    #include "copy_template.S"	
            uaccess_disable_not_uao x3, x4	
            mov    x0, #0	
            ret	
    ENDPROC(__arch_copy_to_user)	
            .section .fixup,"ax"	
            .align    2	
    9998:    sub x0, end, dst            // bytes not copied	
            ret	
            .previous
    1. uaccess_enable_not_uao和uaccess_disable_not_uao是上面说到的内核态访问用户空间的开关。

    2. copy_template.S文件是汇编实现的memcpy()的功能,稍后看看memcpy()的实现代码就清楚了。

    3. .section.fixup,“ax”定义一个section,名为“.fixup”,权限是ax(‘a’可重定位的段,‘x’可执行段)。 9998标号处的指令就是“未雨绸缪”的善后处理工作。还记得copy_{to,from}_user()返回值的意义吗?返回0代表copy成功,否则返回剩余没有copy的字节数。这行代码就是计算剩余没有copy的字节数。当我们访问非法的用户空间地址的时候,就一定会触发page fault。这种情况下,内核态发生的page fault并返回的时候并没有修复异常,所以肯定不能返回发生异常的地址继续运行。所以,系统可以有2个选择:第1个选择是kernel oops,并给当前进程发送SIGSEGV信号;第2个选择是不返回出现异常的地址运行,而是选择一个已经修复的地址返回。如果使用的是memcpy()就只有第1个选择。但是copy_{to,from}_user()可以有第2个选择。 .fixup段就是为了实现这个修复功能。当copy过程中出现访问非法用户空间地址的时候,do_page_fault()返回的地址变成 9998标号处,此时可以计算剩余未copy的字节长度,程序还可以继续执行。

    对比前面分析的结果,其实_arch_copy_to_user()可以近似等效如下关系。

    uaccess_enable_not_uao();	
    memcpy(ubuf, kbuf, size);      ==     __arch_copy_to_user(ubuf, kbuf, size);	
    uaccess_disable_not_uao();

    先插播一条消息,解释copy_template.S为何是memcpy()。memcpy()在ARM64平台是由汇编代码实现。其定义在arch/arm64/lib/memcpy.S文件。

    .weak memcpy	
    ENTRY(__memcpy)	
    ENTRY(memcpy)	
    #include "copy_template.S"	
            ret	
    ENDPIPROC(memcpy)	
    ENDPROC(__memcpy)

    所以很明显,memcpy()和__memcpy()函数定义是一样的。并且memcpy()函数声明是weak,因此可以重写memcpy()函数(扯得有点远)。再扯一点,为何使用汇编呢?为何不使用lib/string.c文件的memcpy()函数呢?当然是为了优化memcpy() 的执行速度。lib/string.c文件的memcpy()函数是按照字节为单位进行copy(再好的硬件也会被粗糙的代码毁掉)。但是现在的处理器基本都是32或者64位,完全可以4 bytes或者8 bytes甚至16 bytes copy(考虑地址对齐的情况下)。可以明显提升执行速度。所以,ARM64平台使用汇编实现。这部分知识可以参考这篇博客《ARM64 的 memcpy 优化与实现》。

    下面继续进入正题,再重复一遍:内核态访问用户空间地址,如果触发page fault,只要用户空间地址合法,内核态也会像什么也没有发生一样修复异常(分配物理内存,建立页表映射关系)。但是如果访问非法用户空间地址,就选择第2条路,尝试救赎自己。这条路就是利用 .fixup__ex_table段。如果无力回天只能给当前进程发送SIGSEGV信号。并且,轻则kernel oops,重则panic(取决于kernel配置选项CONFIG_PANIC_ON_OOPS)。在内核态访问非法用户空间地址的情况下,do_page_fault()最终会跳转 no_context标号处的do_kernel_fault()。

    static void __do_kernel_fault(unsigned long addr, unsigned int esr,	
                                  struct pt_regs *regs)	
    {	
            /*	
             * Are we prepared to handle this kernel fault?	
             * We are almost certainly not prepared to handle instruction faults.	
             */	
            if (!is_el1_instruction_abort(esr) && fixup_exception(regs))	
                    return;	
            /* ... */	
    }

    fixup_exception()继续调用search_exception_tables(),其通过查找_extable段。__extable段存储exception table,每个entry存储着异常地址及其对应修复的地址。例如上述的 9998:subx0,end,dst指令的地址就会被找到并修改do_page_fault()函数的返回地址,以达到跳转修复的功能。其实查找过程是根据出问题的地址addr,查找_extable段(exception table)是否有对应的exception table entry,如果有就代表可以被修复。由于32位处理器和64位处理器实现方式有差别,因此我们先从32位处理器异常表的实现原理说起。

    _extable段的首尾地址分别是 __start___ex_table__stop___ex_table(定义在include/asm-generic/vmlinux.lds.h。这段内存可以看作是一个数组,数组的每个元素都是 struct exception_table_entry类型,其记录着异常发生地址及其对应的修复地址。

                            exception tables	
    __start___ex_table --> +---------------+	
                           |     entry     |	
                           +---------------+	
                           |     entry     |	
                           +---------------+	
                           |      ...      |	
                           +---------------+	
                           |     entry     |	
                           +---------------+	
                           |     entry     |	
    __stop___ex_table  --> +---------------+

    在32位处理器上,struct exception_table_entry定义如下:

    struct exception_table_entry {	
            unsigned long insn, fixup;	
    };

    有一点需要明确,在32位处理器上,unsigned long是4 bytes。insn和fixup分别存储异常发生地址及其对应的修复地址。根据异常地址ex_addr查找对应的修复地址(未找到返回0),其示意代码如下:

    unsigned long search_fixup_addr32(unsigned long ex_addr)	
    {	
            const struct exception_table_entry *e;	
            for (e = __start___ex_table; e < __stop___ex_table; e++)	
                    if (ex_addr == e->insn)	
                            return e->fixup;	
            return 0;	
    }

    在32位处理器上,创建exception table entry相对简单。针对copy{to,from}user()汇编代码中每一处用户空间地址访问的指令都会创建一个entry,并且insn存储当前指令对应的地址,fixup存储修复指令对应的地址。

    当64位处理器开始发展起来,如果我们继续使用这种方式,势必需要2倍于32位处理器的内存存储exception table(因为存储一个地址需要8 bytes)。所以,kernel换用另一种方式实现。在64处理器上,struct exception_table_entry定义如下:

    struct exception_table_entry {	
            int insn, fixup;	
    };

    每个exception table entry占用的内存和32位处理器情况一样,因此内存占用不变。但是insn和fixup的意义发生变化。insn和fixup分别存储着异常发生地址及修复地址相对于当前结构体成员地址的偏移(有点拗口)。例如,根据异常地址ex_addr查找对应的修复地址(未找到返回0),其示意代码如下:

    unsigned long search_fixup_addr64(unsigned long ex_addr)	
    {	
            const struct exception_table_entry *e;	
            for (e = __start___ex_table; e < __stop___ex_table; e++)	
                    if (ex_addr == (unsigned long)&e->insn + e->insn)	
                            return (unsigned long)&e->fixup + e->fixup;	
            return 0;	
    }

    因此,我们的关注点就是如何去构建exception_table_entry。我们针对每个用户空间地址的内存访问都需要创建一个exception table entry,并插入_extable段。例如下面的汇编指令(汇编指令对应的地址是随意写的,不用纠结对错。理解原理才是王道)。

    0xffff000000000000: ldr x1, [x0]	
    0xffff000000000004: add x1, x1, #0x10	
    0xffff000000000008: ldr x2, [x0, #0x10]	
    /* ... */	
    0xffff000040000000: mov x0, #0xfffffffffffffff2    // -14	
    0xffff000040000004: ret

    假设x0寄存器保存着用户空间地址,因此我们需要对0xffff000000000000地址的汇编指令创建一个exception table entry,并且我们期望当x0是非法用户空间地址时,跳转返回的修复地址是0xffff000040000000。为了计算简单,假设这是创建第一个entry, __start___ex_table值是0xffff000080000000。那么第一个exception table entry的insn和fixup成员的值分别是:0x80000000和0xbffffffc(这两个值都是负数)。因此,针对copy{to,from}user()汇编代码中每一处用户空间地址访问的指令都会创建一个entry。所以0xffff000000000008地址处的汇编指令也需要创建一个exception table entry。

    所以,如果内核态访问非法用户空间地址究竟发生了什么?上面的分析流程可以总结如下:

    1. 访问非法用户空间地址:

      0xffff000000000000:ldr x1,[x0]

    2. MMU触发异常

    3. CPU调用do_page_fault()

    4. do_page_fault()调用search_exception_table()(regs->pc == 0xffff000000000000)

    5. 查看_extable段,寻找0xffff000000000000 并且返回修复地址0xffff000040000000

    6. do_page_fault()修改函数返回地址(regs->pc = 0xffff000040000000)并返回

    7. 程序继续执行,处理出错情况

    8. 修改函数返回值x0 = -EFAULT (-14) 并返回(ARM64通过x0传递函数返回值)

    总结

    到了回顾总结的时候,copy_{to,from}_user()的思考也到此结束。我们来个总结结束此文。

    • 无论是内核态还是用户态访问合法的用户空间地址,当虚拟地址并未建立物理地址的映射关系的时候,page fault的流程几乎一样,都会帮助我们申请物理内存并创建映射关系。所以这种情况下memcpy()和copy_{to,from}_user()是类似的。

    • 当内核态访问非法用户空间地址的时候,根据异常地址查找修复地址。这种修复异常的方法并不是建立地址映射关系,而是修改do_page_fault()返回地址。而memcpy()无法做到这点。

    • 在使能 CONFIG_ARM64_SW_TTBR0_PAN或者 CONFIG_ARM64_PAN(硬件支持的情况下才有效)的时候,我们只能使用copy_{to,from}_user()这种接口,直接使用memcpy()是不行的。

    最后,我想说,即使在某些情况下memcpy()可以正常工作。但是,这也是不推荐的,不是良好的编程习惯。在用户空间和内核空间数据交互上,我们必须使用类似copy_{to,from}_user()的接口。为什么类似呢?因为还有其他的接口用于内核空间和用户空间数据交互,只是没有copy_{to,from}_user()出名。例如:{get,put}_user()。

    本文转载自蜗窝科技:

    http://www.wowotech.net/memory_management/454.html

    (完)

    更多精彩,尽在"Linux阅码场",扫描下方二维码关注

    640?wx_fmt=png

    感谢您的耐心阅读,请随手转发一下或者点个“在看”吧~

    展开全文
  • Python---copy()、deepcopy()与赋值的区别

    万次阅读 多人点赞 2017-11-22 16:04:35
    copy()与deepcopy()之间的主要区别是python对数据的存储方式。 首先直接上结论: —–我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经...

    copy()与deepcopy()之间的主要区别是python对数据的存储方式。

    首先直接上结论:

    —–深复制即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。 

    —–而等于赋值,并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。

    —–而浅复制要分两种情况进行讨论:

    1)当浅复制的值是不可变对象(数值,字符串,元组)和“等于赋值”的情况一样,对象的id值与浅复制原来的值相同。

    2)当浅复制的值是可变对象(列表和元组)时会产生一个“不是那么独立的对象”存在。有两种情况:

    第一种情况:复制的 对象中无 复杂 子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

    第二种情况复制的对象中有 复杂 子对象 (例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象浅复制的值改变并不会影响原来的值。 但是改变原来的值 中的复杂子对象的值  会影响浅复制的值

    对于简单的 object,例如不可变对象(数值,字符串,元组),用 shallow copy 和 deep copy 没区别

    复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。

    当浅复制的值是不可变对象(数值,字符串,元组)时,代码如下:

    >>> a="1234567"
    >>> b=a
    >>> id(a)
    4367619440
    >>> id(b)
    4367619440
    >>> c=copy.copy(a)
    >>> id(c)
    4367619440
    >>> d=copy.deepcopy(a)
    >>> id(d)
    4367619440

     

    浅复制的值是可变对象(列表,字典)时,改变的值不是 复杂子对象 代码如下:

    >>> l1=[1,2,3]
    >>> l2=l1
    >>> l3=copy.copy(l1)
    >>> id(l1)
    4367654664
    >>> id(l2)
    4367654664
    >>> id(l3)
    4367628616
    >>> l1.append(55)
    >>> print(l1)
    [1, 2, 3, 55]
    >>> print(l3)
    [1, 2, 3]
    
    

    当浅复制的值是可变对象(列表,字典)时,改变的值是 复杂子对象 代码如下:

    >>> import copy
    >>> list1=[1,2,['a','b']]
    >>> list2=list1
    >>> list3=copy.copy(list2)
    >>> list4=copy.deepcopy(list3)
    >>> id(list1)
    4338414656
    >>> id(list2)
    4338414656
    >>> id(list3)
    4338415016
    >>> id(list4)
    4338414368
    >>> list1[2].append('a')
    >>> id(list1)
    4338414656
    >>> print list1
    [1, 2, ['a', 'b', 'a']]
    >>> print list3
    [1, 2, ['a', 'b', 'a']]
    >>> print list4
    [1, 2, ['a', 'b']]
    >>> list1.append(33)
    >>> id(list1)
    4338414656
    >>> id(list3)
    4338415016
    >>> print list1
    [1, 2, ['a', 'b', 'a'], 33]
    >>> print list3
    [1, 2, ['a', 'b', 'a']]


    代码说明:当改变 复杂子对象中的元素时,浅复制值发生了变化; 当改变的值不是复杂子对象,浅复制的值没有发生变化。因为 浅复制 ,复杂子对象的保存方式是 作为 引用 方式存储的,所以修改 浅复制的值 和原来的值都可以 改变 复杂子对象的值。

     

    python的数据存储方式

    Python 存储变量的方法跟其他 OOP 语言不同。它与其说是把值赋给变量,不如说是给变量建立了一个到具体值的 reference。

    当在 Python 中 a = something 应该理解为给 something 贴上了一个标签 a。当再赋值给 a 的时候,就好象把 a 这个标签从原来的 something 上拿下来,贴到其他对象上,建立新的 reference。 这就解释了一些 Python 中可能遇到的诡异情况:

    >> a = [1, 2, 3]
    >>> b = a
    >>> a = [4, 5, 6] //赋新的值给 a
    >>> a
    [4, 5, 6]
    >>> b
    [1, 2, 3]
    # a 的值改变后,b 并没有随着 a 变
    
    >>> a = [1, 2, 3]
    >>> b = a
    >>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素
    >>> a
    [4, 5, 6]
    >>> b
    [4, 5, 6]
    # a 的值改变后,b 随着 a 变了

    上面两段代码中,a 的值都发生了变化。区别在于,第一段代码中是直接赋给了 a 新的值(从 [1, 2, 3] 变为 [4, 5, 6]);而第二段则是把 list 中每个元素分别改变。而对 b 的影响则是不同的,一个没有让 b 的值发生改变,另一个变了。怎么用上边的道理来解释这个诡异的不同呢?首次把 [1, 2, 3] 看成一个物品。a = [1, 2, 3] 就相当于给这个物品上贴上 a 这个标签。而 b = a 就是给这个物品又贴上了一个 b 的标签。 

     
    第一种情况:a = [4, 5, 6] 就相当于把 a 标签从 [1 ,2, 3] 上撕下来,贴到了 [4, 5, 6] 上。在这个过程中,[1, 2, 3] 这个物品并没有消失。 b 自始至终都好好的贴在 [1, 2, 3] 上,既然这个 reference 也没有改变过。 b 的值自然不变。

     
     

    第二种情况:a[0], a[1], a[2] = 4, 5, 6 则是直接改变了 [1, 2, 3] 这个物品本身。把它内部的每一部分都重新改装了一下。内部改装完毕后,[1, 2, 3] 本身变成了 [4, 5, 6]。而在此过程当中,a 和 b 都没有动,他们还贴在那个物品上。因此自然 a b 的值都变成了 [4, 5, 6]。

    搞明白这个之后就要问了,对于一个复杂对象的浅copy,在copy的时候到底发生了什么? 
    再看一段代码:

    >>> import copy
    >>> origin = [1, 2, [3, 4]]
    #origin 里边有三个元素:1, 2,[3, 4]
    >>> cop1 = copy.copy(origin)
    >>> cop2 = copy.deepcopy(origin)
    >>> cop1 == cop2
    True
    >>> cop1 is cop2
    False 
    #cop1 和 cop2 看上去相同,但已不再是同一个object
    >>> origin[2][0] = "hey!" 
    >>> origin
    [1, 2, ['hey!', 4]]
    >>> cop1
    [1, 2, ['hey!', 4]]
    >>> cop2
    [1, 2, [3, 4]]
    #把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2

     

    学过docker的人应该对镜像这个概念不陌生,我们可以把镜像的概念套用在copy上面。

     

    copy(浅复制)对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了

    所以说看这里的origin[2],也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2] 指向的是同一个 list [3, 4]。那么,如果这里我们改变了这个 list,就会导致 origin 和 cop1 同时改变。这就是为什么上边 origin[2][0] = “hey!” 之后,cop1 也随之变成了 [1, 2, [‘hey!’, 4]]。

     

    deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。 
    这时候的 origin[2] 和 cop2[2] 虽然值都等于 [3, 4],但已经不是同一个 list了。即我们寻常意义上的复制。

     

    “无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。”

     

    展开全文
  • DataFrame的copy的用法

    万次阅读 多人点赞 2018-10-12 22:11:26
    pandas.DataFrame.copy¶ DataFrame.copy(deep=True) 当deep=false相当于引用,原值改变复制的结果随着改变。 data=DataFrame.copy(deep=True) 等价于 data=DataFrame 假设有DataFrame: data.loc[["a"...
    pandas.DataFrame.copy
    DataFrame.copy(deep=True)
    

    当deep=false相当于引用,原值改变复制的结果随着改变。

    data=DataFrame.copy(deep=False)
    等价于
    data=DataFrame
    

    假设有DataFrame:
    在这里插入图片描述

    data.loc[["a"],["0"]]=11  #修改data第一行第一列的值为11
    x=data.copy(deep=False) 
    data.loc[["a"],["0"]]=10 #修改data第一行第一列的值为10
    x # 如果x是深拷贝,data 的修改不会影响 x 的值,x 应该还是11
    

    结果,x 也改成了10
    在这里插入图片描述

    data.loc[["a"],["0"]]=11
    x=data.copy(deep=True)
    data.loc[["a"],["0"]]=10
    x
    

    在这里插入图片描述

    展开全文
  • copy既能做名词也能做动词,那么copy做名词和动词分别都有哪些意思呢?下面学习啦小编为大家带来copy的英语意思解释和英语例句,希望对你有所帮助!copy作名词的意思:复制品;一份;(报刊等的)稿件copy作动词的意思:...
  • postgresql copy from、copy to

    千次阅读 2020-10-02 22:47:24
    使用postgresql官方提供的copy来进行数据的高效导入导出,分别介绍命令行使用方法和Java中的使用方法。
  • copy.copy()与copy.deepcopy()的详解

    千次阅读 2018-05-23 17:56:00
    copy.copy() 元组和列表调用这个方法效果也不一样。 元组的效果: a = [1,2,3] b = [4,5,6] c = (a,b) e = copy.copy(c) 可以看到:e和c是指向了同一个地址。 列表的效果: 可以看到:c和e指向了不同的地址,但是c...
  • Shallow Copy and Deep Copy

    千次阅读 热门讨论 2014-02-28 14:17:29
    浅拷贝(Shallow Copy)与深拷贝(Deep Copy),它们是什么?  1.浅拷贝(Shallow Copy)=(bitwise copy:位元逐一复制,按位拷贝)指的是拷贝对象而不拷贝该对象包含的对象,对它的嵌套的对象,仅拷贝其句柄。  2.深...
  • copy关键字

    千次阅读 2017-09-26 18:01:07
    相信对于有一定iOS开发经验的同学来说,对于copy关键字一定不陌生,从字义上来看,应该就是复制一个对象,然后我们对于NSString类型的属性,一般也用copy关键字。但是大家对于copy关键字真正有什么具体了解呢,什么...
  • 一、copy 头文件algorithm template &amp;lt;class InputIterator, class OutputIterator&amp;gt; OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result); 复制元素范围...
  • list.copy方法-深浅copy

    千次阅读 2017-08-05 18:08:58
    list.copy方法-深浅copy list.copy() 列表复制(浅copy) 当列表元素为不可迭代对象时(非列表和字典), list.copy复制了一个副本,对副本列表进行操作时,不会影响原列表 list = [1,2,3] list2 = list.copy print...
  • 列表的复制分为两种:...拷贝时,要先导入import copy这个包 一维列表的深浅拷贝: 二维列表的深浅拷贝: 浅度拷贝:lis.copy() lis为列表 lis = [12,39,10,5,11,66] #一维列表 lis.append(['I','like','python'...
  • STL算法之 copycopy_backward、copy_n

    千次阅读 2016-07-20 23:03:46
    这些函数都定义在stl_algobase.h里面,使用时只需要包含 ...copy :STL 的SGI版本中的copy函数声明如下:template , class OutputIterator> inline OutputIterator copy(InputIterator first, InputIterator last, O
  • python中copy()和deepcopy()详解

    万次阅读 多人点赞 2018-03-30 10:43:40
    参考文章 http://iaman.actor/blog/2016/04/17/copy-in-python **首先直接上结论: —–我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对...
  • PostgreSQL 之 copy 命令基础

    万次阅读 2018-01-26 00:15:48
    COPY命令概述 copy 命令用于表与文件(和标准输出,标准输入)之间的相互拷贝; copy to由表至文件,copy from由文件至表; copy 命令始终是到数据库服务端找文件,以超级用户执行导入导出权限要求很高,适合数据库...
  • python的copy.copy()和copy.deepcopy()方法

    千次阅读 2015-12-09 11:37:12
    python中copy.copy()是浅拷贝,只拷贝父对象,不会拷贝对象的内部的字对象。copy.deepcopy()是深拷贝,会拷贝对象及其字对象。 import copy aList = ['1',2,'a',['b','c']] bList = aList#将aList赋给bList cList...
  • copy vs. uninitialized_copy

    千次阅读 2013-09-16 22:34:59
    copy 是依次调用重载的运算符=, uninitialized_copy是依次调用拷贝构造函数 如果目标区间是未初始化的,应该用uninitialized_copy, 否则用copy
  • 在网上看到很多朋友对Shallow Copy,Deep Copy,Bitwise Copy和Memberwise Copy这4者之间的关系的理解多有混淆,Stanley Lippman的“Inside the C++ Object Model”的讲解也比较晦涩难懂。玄机逸士在此给出一个简单...
  • postgreSQL copy与\copy的区别

    千次阅读 2015-07-07 15:28:55
    copy必须使用能够超级用户使用; copy .. to file ,copy file to ..中的文件都是数据库服务器所在的服务器上的文件。 \copy 一般用户即可执行 \copy 保存或者读取的文件是在客户端所在的服务器  比如当使用192.168....
  • copy_to_user和copy_form_user函数分析

    千次阅读 2018-07-21 14:46:19
    学习驱动比较常用的两个函数,copy_to_user和copy_form_user。   因为传参都是传的地址,单因为内核空间不能直接访问用户空间的内存,所以内核空间要使用用户空间参数。必须进行地址转换。而这连个函数就是负责把...
  • postgres copy命令

    万次阅读 2018-01-29 12:51:49
    1.从本番数据表copy到本番临时文件 copy pacerplayer.mst_app to /tmp/mst_app 2.把本番的临时文件拿到测试环境 scp /tmp/mst_app root@10.100.2.90:/tmp/mst_app 3.把测试环境的临时文件导入到测试数据表...
  • copycopy_backward函数区别

    千次阅读 2015-08-21 22:59:39
    copycopy_backward函数的功能其实是一样的,无非是从第1个开始拷贝和从最后一个开始拷贝的区别。先看一下这两个函数的参数: copy(first,last,result);//first为容器的首迭代器,last为容器的末迭代器,result为...
  • copy thinkphp

    千次阅读 2014-03-07 10:41:44
    copy — 拷贝文件 bool copy ( string $source , string $dest ) 将文件从 source 拷贝到 dest。成功时返回 TRUE, 或者在失败时返回 FALSE. Example #1 copy() 例子 $file = 'example.txt'; $newfile = 'exam
  • Java中类的引用Copy,浅Copy和深Copy

    千次阅读 2009-05-21 08:49:00
    编程语言的Copy基本分为引用Copy,浅Copy和深Copy。 引用Copy比较简单,我们将一个引用指向一个对象,就是一个引用Copy,引用Copy不会新建对象,只会将引用指向已存在的对象,代码如下:Object o1 = new Object();...
  • SV中的shadow copy和deep copy的区别

    千次阅读 2019-01-21 15:20:20
    SV中shadow copy和deep copy的区别 1、shadow copy ,是一种简易复制,类似于原对象的影印本,原对象的值被盲目地写到目的对象中(一般用new操作符) class Transaction; bit[31:0]addr,crc,data[8]; endclass ...
  • copy 以及浅copy

    千次阅读 2012-03-18 14:32:38
    copy: 建立一个索引计数为1的对象,然后释放旧对象retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1 Copy其实是建立了一个相同的对象,而retain不是: 比如一个NSS
  • shutil.copy

    千次阅读 2019-08-08 17:49:31
    shutil.copy(src, dst) 将文件src复制到文件或目录dst, 保留文件许可。 shutil.copytree(src, dst, symlinks[, ignore]) 递归地复制src下的整个目录树。创建目标目录dst(且不应该已存在)。 使用copy2()复制个别...
  • copy_to_user和copy_from_user函数

    千次阅读 2018-06-20 22:31:47
     copy_to_user:从内核空间复制数据到用户空间 copy_from_user:从用户空间复制数据到内核空间。 在进行驱动相关程序设计时,经常会用到这两个函数,因为底层驱动显然是内核空间,调用驱动的是用户空间。 具体函数...
  • C++ copycopy_n、copy_if

    千次阅读 2014-02-08 20:36:02
    #include #include #include #include #include using namespace std; int main(){ ... array test = {3,5,7,11,13,17,19,23};... //copy(test.begin(),test.end(),t2.begin()); //只拷贝小于15的数 //c

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 308,164
精华内容 123,265
关键字:

copy