0001 .. SPDX-License-Identifier: GPL-2.0
0002 .. include:: ../disclaimer-zh_CN.rst
0003
0004 :Original: Documentation/core-api/protection-keys.rst
0005
0006 :翻译:
0007
0008 司延腾 Yanteng Si <siyanteng@loongson.cn>
0009
0010 :校译:
0011
0012 吴想成 Wu XiangCheng <bobwxc@email.cn>
0013
0014 .. _cn_core-api_protection-keys:
0015
0016 ============
0017 内存保护密钥
0018 ============
0019
0020 用户空间的内存保护密钥(Memory Protection Keys for Userspace,PKU,亦
0021 即PKEYs)是英特尔Skylake(及以后)“可扩展处理器”服务器CPU上的一项功能。
0022 它将在未来的非服务器英特尔处理器和未来的AMD处理器中可用。
0023
0024 对于任何希望测试或使用该功能的人来说,它在亚马逊的EC2 C5实例中是可用的,
0025 并且已知可以在那里使用Ubuntu 17.04镜像运行。
0026
0027 内存保护密钥提供了一种机制来执行基于页面的保护,但在应用程序改变保护域
0028 时不需要修改页表。它的工作原理是在每个页表项中为“保护密钥”分配4个以
0029 前被忽略的位,从而提供16个可能的密钥。
0030
0031 还有一个新的用户可访问寄存器(PKRU),为每个密钥提供两个单独的位(访
0032 问禁止和写入禁止)。作为一个CPU寄存器,PKRU在本质上是线程本地的,可能
0033 会给每个线程提供一套不同于其他线程的保护措施。
0034
0035 有两条新指令(RDPKRU/WRPKRU)用于读取和写入新的寄存器。该功能仅在64位
0036 模式下可用,尽管物理地址扩展页表中理论上有空间。这些权限只在数据访问上
0037 强制执行,对指令获取没有影响。
0038
0039
0040 系统调用
0041 ========
0042
0043 有3个系统调用可以直接与pkeys进行交互::
0044
0045 int pkey_alloc(unsigned long flags, unsigned long init_access_rights)
0046 int pkey_free(int pkey);
0047 int pkey_mprotect(unsigned long start, size_t len,
0048 unsigned long prot, int pkey);
0049
0050 在使用一个pkey之前,必须先用pkey_alloc()分配它。一个应用程序直接调用
0051 WRPKRU指令,以改变一个密钥覆盖的内存的访问权限。在这个例子中,WRPKRU
0052 被一个叫做pkey_set()的C函数所封装::
0053
0054 int real_prot = PROT_READ|PROT_WRITE;
0055 pkey = pkey_alloc(0, PKEY_DISABLE_WRITE);
0056 ptr = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
0057 ret = pkey_mprotect(ptr, PAGE_SIZE, real_prot, pkey);
0058 ... application runs here
0059
0060 现在,如果应用程序需要更新'ptr'处的数据,它可以获得访问权,进行更新,
0061 然后取消其写访问权::
0062
0063 pkey_set(pkey, 0); // clear PKEY_DISABLE_WRITE
0064 *ptr = foo; // assign something
0065 pkey_set(pkey, PKEY_DISABLE_WRITE); // set PKEY_DISABLE_WRITE again
0066
0067 现在,当它释放内存时,它也将释放pkey,因为它不再被使用了::
0068
0069 munmap(ptr, PAGE_SIZE);
0070 pkey_free(pkey);
0071
0072 .. note:: pkey_set()是RDPKRU和WRPKRU指令的一个封装器。在tools/testing/selftests/x86/protection_keys.c中可以找到一个实现实例。
0073 tools/testing/selftests/x86/protection_keys.c.
0074
0075 行为
0076 ====
0077
0078 内核试图使保护密钥与普通的mprotect()的行为一致。例如,如果你这样做::
0079
0080 mprotect(ptr, size, PROT_NONE);
0081 something(ptr);
0082
0083 这样做的时候,你可以期待保护密钥的相同效果::
0084
0085 pkey = pkey_alloc(0, PKEY_DISABLE_WRITE | PKEY_DISABLE_READ);
0086 pkey_mprotect(ptr, size, PROT_READ|PROT_WRITE, pkey);
0087 something(ptr);
0088
0089 无论something()是否是对'ptr'的直接访问,这都应该为真。
0090 如::
0091
0092 *ptr = foo;
0093
0094 或者当内核代表应用程序进行访问时,比如read()::
0095
0096 read(fd, ptr, 1);
0097
0098 在这两种情况下,内核都会发送一个SIGSEGV,但当违反保护密钥时,si_code
0099 将被设置为SEGV_PKERR,而当违反普通的mprotect()权限时,则是SEGV_ACCERR。