cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

assoc_array.rst (19546B)


      1.. include:: ../disclaimer-zh_CN.rst
      2
      3:Original: Documentation/core-api/assoc_array.rst
      4
      5:翻译:
      6
      7 司延腾 Yanteng Si <siyanteng@loongson.cn>
      8
      9:校译:
     10
     11
     12
     13.. _cn_core-api_assoc_array:
     14
     15==================
     16通用关联数组的实现
     17==================
     18
     19简介
     20====
     21
     22这个关联数组的实现是一个具有以下属性的对象容器:
     23
     241. 对象是不透明的指针。该实现不关心它们指向哪里(如果有的话)或它们指向什么(如果有的
     25   话)。
     26
     27   .. note::
     28
     29      指向对象的指针 *必须* 在最小有效位为零。
     30
     312. 对象不需要包含供数组使用的链接块。这允许一个对象同时位于多个数组中。相反,数组是
     32   由指向对象的元数据块组成的。
     33
     343. 对象需要索引键来定位它们在阵列中的位置。
     35
     364. 索引键必须是唯一的。插入一个与已经在数组中的且具有相同键值的对象将取代旧的对象。
     37
     385. 索引键可以是任何长度,也可以是不同的长度。
     39
     406. 索引键应该在早期就对长度进行编码,即在任何由于长度引起的变化出现之前。
     41
     427. 索引键可以包括一个哈希值,以便将对象分散到整个数组中。
     43
     448. 该数组可以迭代。对象不一定会按索引键的顺序出现。
     45
     469.  数组可以在被修改的时候进行迭代,只要RCU的读锁被迭代器持有。然而,请注意,在这种情
     47    况下,一些对象可能会被看到不止一次。如果这是个问题,迭代器应该锁定以防止修改。然
     48    而,除非删除,否则对象不会被错过。
     49
     5010. 数组中的对象可以通过其索引键进行查询。
     51
     5211. 当数组被修改时,对象可以被查询,前提是进行查询的线程持有RCU的读锁。
     53
     54该实现在内部使用了一棵由16个指针节点组成的树,这些节点在每一层都由索引键的小数点进行索
     55引,其方式与基数树相同。为了提高内存效率,可以放置快捷键,以跳过本来是一系列单占节点的地
     56方。此外,节点将叶子对象指针打包到节点的空闲空间中,而不是做一个额外的分支,直到有对象
     57需要被添加到一个完整的节点中。
     58
     59公用API
     60=======
     61
     62公用API可以在 ``<linux/assoc_array.h>`` 中找到。关联数组的根是以下结构::
     63
     64    struct assoc_array {
     65            ...
     66    };
     67
     68该代码是通过启用 ``CONFIG_ASSOCIATIVE_ARRAY`` 来选择的,以::
     69
     70    ./script/config -e ASSOCIATIVE_ARRAY
     71
     72
     73编辑脚本
     74--------
     75
     76插入和删除功能会产生一个“编辑脚本”,以后可以应用这个脚本来实现更改,而不会造成 ``ENOMEM``
     77风险。这保留了将被安装在内部树中的预分配的元数据块,并跟踪应用脚本时将从树中删除的元数
     78据块。
     79
     80在脚本应用后,这也被用来跟踪死块和死对象,以便以后可以释放它们。释放是在RCU宽限期过后
     81进行的--因此允许访问功能在RCU读锁下进行。
     82
     83脚本在API之外显示为一个类型为::
     84
     85    struct assoc_array_edit;
     86
     87有两个处理脚本的功能:
     88
     891. 应用一个编辑脚本::
     90
     91    void assoc_array_apply_edit(struct assoc_array_edit *edit);
     92
     93这将执行编辑功能,插值各种写屏障,以允许在RCU读锁下的访问继续进行。然后,编辑脚本将被
     94传递给 ``call_rcu()`` ,以释放它和它所指向的任何死的东西。
     95
     962. Cancel an edit script::
     97
     98    void assoc_array_cancel_edit(struct assoc_array_edit *edit);
     99
    100这将立即释放编辑脚本和所有预分配的内存。如果这是为了插入,新的对象不会被这个函数释放,
    101而是必须由调用者释放。
    102
    103这些函数保证不会失败。
    104
    105
    106操作表
    107------
    108
    109各种功能采用了一个操作表::
    110
    111    struct assoc_array_ops {
    112            ...
    113    };
    114
    115这指出了一些方法,所有这些方法都需要提供:
    116
    1171. 从调用者数据中获取索引键的一个块::
    118
    119    unsigned long (*get_key_chunk)(const void *index_key, int level);
    120
    121这应该返回一个由调用者提供的索引键的块,从level参数给出的 *比特* 位置开始。level参数将
    122是 ``ASSOC_ARRAY_KEY_CHUNK_SIZE`` 的倍数,该函数应返回 ``ASSOC_ARRAY_KEY_CHUNK_SIZE``
    123位。不可能出现错误。
    124
    125
    1262. 获取一个对象的索引键的一个块::
    127
    128    unsigned long (*get_object_key_chunk)(const void *object, int level);
    129
    130和前面的函数一样,但是从数组中的一个对象而不是从调用者提供的索引键中获取数据。
    131
    132
    1333. 看看这是否是我们要找的对象::
    134
    135    bool (*compare_object)(const void *object, const void *index_key);
    136
    137将对象与一个索引键进行比较,如果匹配则返回 ``true`` ,不匹配则返回 ``false`` 。
    138
    139
    1404. 对两个对象的索引键进行比较::
    141
    142    int (*diff_objects)(const void *object, const void *index_key);
    143
    144返回指定对象的索引键与给定索引键不同的比特位置,如果它们相同,则返回-1。
    145
    146
    1475. 释放一个对象::
    148
    149    void (*free_object)(void *object);
    150
    151释放指定的对象。注意,这可能是在调用 ``assoc_array_apply_edit()`` 后的一个RCU宽限期内
    152调用的,所以在模块卸载时可能需要 ``synchronize_rcu()`` 。
    153
    154
    155操控函数
    156--------
    157
    158有一些函数用于操控关联数组:
    159
    1601. 初始化一个关联数组::
    161
    162    void assoc_array_init(struct assoc_array *array);
    163
    164这将初始化一个关联数组的基础结构。它不会失败。
    165
    166
    1672. 在一个关联数组中插入/替换一个对象::
    168
    169    struct assoc_array_edit *
    170    assoc_array_insert(struct assoc_array *array,
    171                       const struct assoc_array_ops *ops,
    172                       const void *index_key,
    173                       void *object);
    174
    175这将把给定的对象插入数组中。注意,指针的最小有效位必须是0,因为它被用来在内部标记指针的类
    176型。
    177
    178如果该键已经存在一个对象,那么它将被新的对象所取代,旧的对象将被自动释放。
    179
    180``index_key`` 参数应持有索引键信息,并在调用OPP表中的方法时传递给它们。
    181
    182这个函数不对数组本身做任何改动,而是返回一个必须应用的编辑脚本。如果出现内存不足的错误,会
    183返回 ``-ENOMEM`` 。
    184
    185调用者应专门锁定数组的其他修改器。
    186
    187
    1883. 从一个关联数组中删除一个对象::
    189
    190    struct assoc_array_edit *
    191    assoc_array_delete(struct assoc_array *array,
    192                       const struct assoc_array_ops *ops,
    193                       const void *index_key);
    194
    195这将从数组中删除一个符合指定数据的对象。
    196
    197``index_key`` 参数应持有索引键信息,并在调用OPP表中的方法时传递给它们。
    198
    199这个函数不对数组本身做任何改动,而是返回一个必须应用的编辑脚本。 ``-ENOMEM`` 在出现内存不足
    200的错误时返回。如果在数组中没有找到指定的对象,将返回 ``NULL`` 。
    201
    202调用者应该对数组的其他修改者进行专门锁定。
    203
    204
    2054. 从一个关联数组中删除所有对象::
    206
    207    struct assoc_array_edit *
    208    assoc_array_clear(struct assoc_array *array,
    209                      const struct assoc_array_ops *ops);
    210
    211这个函数删除了一个关联数组中的所有对象,使其完全为空。
    212
    213这个函数没有对数组本身做任何改动,而是返回一个必须应用的编辑脚本。如果出现内存不足
    214的错误,则返回 ``-ENOMEM`` 。
    215
    216调用者应专门锁定数组的其他修改者。
    217
    218
    2195. 销毁一个关联数组,删除所有对象::
    220
    221    void assoc_array_destroy(struct assoc_array *array,
    222                             const struct assoc_array_ops *ops);
    223
    224这将破坏关联数组的内容,使其完全为空。在这个函数销毁数组的同时,不允许另一个线程在RCU读锁
    225下遍历数组,因为在内存释放时不执行RCU延迟,这需要分配内存。
    226
    227调用者应该专门针对数组的其他修改者和访问者进行锁定。
    228
    229
    2306. 垃圾回收一个关联数组::
    231
    232    int assoc_array_gc(struct assoc_array *array,
    233                       const struct assoc_array_ops *ops,
    234                       bool (*iterator)(void *object, void *iterator_data),
    235                       void *iterator_data);
    236
    237这是对一个关联数组中的对象进行迭代,并将每个对象传递给 ``iterator()`` 。如果 ``iterator()`` 返回
    238true,该对象被保留。如果它返回 ``false`` ,该对象将被释放。如果 ``iterator()`` 函数返回 ``true`` ,它必须
    239在返回之前对该对象进行适当的 ``refcount`` 递增。
    240
    241如果可能的话,内部树将被打包下来,作为迭代的一部分,以减少其中的节点数量。
    242
    243``iterator_data`` 被直接传递给 ``iterator()`` ,否则会被函数忽略。
    244
    245如果成功,该函数将返回 ``0`` ,如果没有足够的内存,则返回 ``-ENOMEM`` 。
    246
    247在这个函数执行过程中,其他线程有可能在RCU读锁下迭代或搜索阵列。调用者应该专门针对数组的其他
    248修改者进行锁定。
    249
    250
    251访问函数
    252--------
    253
    254有两个函数用于访问一个关联数组:
    255
    2561. 遍历一个关联数组中的所有对象::
    257
    258    int assoc_array_iterate(const struct assoc_array *array,
    259                            int (*iterator)(const void *object,
    260                                            void *iterator_data),
    261                            void *iterator_data);
    262
    263这将数组中的每个对象传递给迭代器回调函数。 ``iterator_data`` 是该函数的私有数据。
    264
    265在数组被修改的同时,可以在数组上使用这个方法,前提是RCU读锁被持有。在这种情况下,迭代函数有
    266可能两次看到某些对象。如果这是个问题,那么修改应该被锁定。然而,迭代算法不应该错过任何对象。
    267
    268如果数组中没有对象,该函数将返回 ``0`` ,否则将返回最后一次调用的迭代器函数的结果。如果对迭代函数
    269的任何调用导致非零返回,迭代立即停止。
    270
    271
    2722. 在一个关联数组中寻找一个对象::
    273
    274    void *assoc_array_find(const struct assoc_array *array,
    275                           const struct assoc_array_ops *ops,
    276                           const void *index_key);
    277
    278这将直接穿过数组的内部树,到达索引键所指定的对象。
    279
    280这个函数可以在修改数组的同时用在数组上,前提是RCU读锁被持有。
    281
    282如果找到对象,该函数将返回对象(并将 ``*_type`` 设置为对象的类型),如果没有找到对象,将返回 ``NULL`` 。
    283
    284
    285索引键形式
    286----------
    287
    288索引键可以是任何形式的,但是由于算法没有被告知键有多长,所以强烈建议在任何由于长度而产生的变化
    289对比较产生影响之前,索引键应该很早就包括其长度。
    290
    291这将导致具有不同长度键的叶子相互分散,而具有相同长度键的叶子则聚集在一起。
    292
    293我们还建议索引键以键的其余部分的哈希值开始,以最大限度地提高整个键空间的散布情况。
    294
    295分散性越好,内部树就越宽,越低。
    296
    297分散性差并不是一个太大的问题,因为有快捷键,节点可以包含叶子和元数据指针的混合物。
    298
    299索引键是以机器字的块状来读取的。每个块被细分为每层一个nibble(4比特),所以在32位CPU上这适合8层,
    300在64位CPU上适合16层。除非散布情况真的很差,否则不太可能有超过一个字的任何特定索引键需要被使用。
    301
    302
    303内部工作机制
    304============
    305
    306关联数组数据结构有一个内部树。这个树由两种类型的元数据块构成:节点和快捷键。
    307
    308一个节点是一个槽的数组。每个槽可以包含以下四种东西之一:
    309
    310* 一个NULL的指针,表示槽是空的。
    311* 一个指向对象(叶子)的指针。
    312* 一个指向下一级节点的指针。
    313* 一个指向快捷键的指针。
    314
    315
    316基本的内部树形布局
    317------------------
    318
    319暂时不考虑快捷键,节点形成一个多级树。索引键空间被树上的节点严格细分,节点出现在固定的层次上。例如::
    320
    321 Level: 0               1               2               3
    322        =============== =============== =============== ===============
    323                                                        NODE D
    324                        NODE B          NODE C  +------>+---+
    325                +------>+---+   +------>+---+   |       | 0 |
    326        NODE A  |       | 0 |   |       | 0 |   |       +---+
    327        +---+   |       +---+   |       +---+   |       :   :
    328        | 0 |   |       :   :   |       :   :   |       +---+
    329        +---+   |       +---+   |       +---+   |       | f |
    330        | 1 |---+       | 3 |---+       | 7 |---+       +---+
    331        +---+           +---+           +---+
    332        :   :           :   :           | 8 |---+
    333        +---+           +---+           +---+   |       NODE E
    334        | e |---+       | f |           :   :   +------>+---+
    335        +---+   |       +---+           +---+           | 0 |
    336        | f |   |                       | f |           +---+
    337        +---+   |                       +---+           :   :
    338                |       NODE F                          +---+
    339                +------>+---+                           | f |
    340                        | 0 |           NODE G          +---+
    341                        +---+   +------>+---+
    342                        :   :   |       | 0 |
    343                        +---+   |       +---+
    344                        | 6 |---+       :   :
    345                        +---+           +---+
    346                        :   :           | f |
    347                        +---+           +---+
    348                        | f |
    349                        +---+
    350
    351在上述例子中,有7个节点(A-G),每个节点有16个槽(0-f)。假设树上没有其他元数据节点,那么密钥空间
    352是这样划分的::
    353
    354    KEY PREFIX      NODE
    355    ==========      ====
    356    137*            D
    357    138*            E
    358    13[0-69-f]*     C
    359    1[0-24-f]*      B
    360    e6*             G
    361    e[0-57-f]*      F
    362    [02-df]*        A
    363
    364因此,例如,具有以下示例索引键的键将在适当的节点中被找到::
    365
    366    INDEX KEY       PREFIX  NODE
    367    =============== ======= ====
    368    13694892892489  13      C
    369    13795289025897  137     D
    370    13889dde88793   138     E
    371    138bbb89003093  138     E
    372    1394879524789   12      C
    373    1458952489      1       B
    374    9431809de993ba  -       A
    375    b4542910809cd   -       A
    376    e5284310def98   e       F
    377    e68428974237    e6      G
    378    e7fffcbd443     e       F
    379    f3842239082     -       A
    380
    381为了节省内存,如果一个节点可以容纳它的那部分键空间中的所有叶子,那么这个节点将有所有这些叶子,而不
    382会有任何元数据指针——即使其中一些叶子想在同一个槽中。
    383
    384一个节点可以包含叶子和元数据指针的异质性混合。元数据指针必须在与它们的关键空间的细分相匹配的槽中。
    385叶子可以在任何没有被元数据指针占据的槽中。保证一个节点中没有一个叶子会与元数据指针占据的槽相匹配。
    386如果元数据指针在那里,任何键与元数据键前缀相匹配的叶必须在元数据指针指向的子树中。
    387
    388在上面的索引键的例子列表中,节点A将包含::
    389
    390    SLOT    CONTENT         INDEX KEY (PREFIX)
    391    ====    =============== ==================
    392    1       PTR TO NODE B   1*
    393    any     LEAF            9431809de993ba
    394    any     LEAF            b4542910809cd
    395    e       PTR TO NODE F   e*
    396    any     LEAF            f3842239082
    397
    398和节点B::
    399
    400    3	PTR TO NODE C	13*
    401    any	LEAF		1458952489
    402
    403
    404快捷键
    405---------
    406
    407快捷键是跳过一块键空间的元数据记录。快捷键是一系列通过层次上升的单占节点的替代物。快捷键的存在是
    408为了节省内存和加快遍历速度。
    409
    410树的根部有可能是一个快捷键——比如说,树至少包含17个节点,都有键前缀 ``1111`` 。插入算法将插入一个快捷键,
    411以单次跳过 ``1111`` 的键位,并到达第四层,在这里,这些键位实际上变得不同。
    412
    413
    414拆分和合并节点
    415------------------------------
    416
    417每个节点的最大容量为16个叶子和元数据指针。如果插入算法发现它正试图将一个第17个对象插入到一个节点中,
    418该节点将被拆分,使得至少两个在该层有一个共同的关键段的叶子最终在一个单独的节点中,该共同的关键段的根
    419在该槽上。
    420
    421如果一个完整的节点中的叶子和被插入的叶子足够相似,那么就会在树中插入一个快捷键。
    422
    423当根植于某个节点的子树中的对象数量下降到16个或更少时,那么该子树将被合并成一个单独的节点——如果可能的
    424话,这将向根部扩散。
    425
    426
    427非递归式迭代
    428------------
    429
    430每个节点和快捷键都包含一个指向其父节点的后置指针,以及该父节点中指向它的槽数。非递归迭代使用这些来
    431通过树的根部进行,前往父节点,槽N+1,以确保在没有堆栈的情况下取得进展。
    432
    433然而,反向指针使得同时改变和迭代变得很棘手。
    434
    435
    436同时改变和迭代
    437--------------
    438
    439有一些情况需要考虑:
    440
    4411. 简单的插入/替换。这涉及到简单地将一个NULL或旧的匹配叶子的指针替换为屏障后的新叶子的指针。否则元数
    442   据块不会改变。一个旧的叶子直到RCU宽限期过后才会被释放。
    443
    4442. 简单删除。这只是涉及到清除一个旧的匹配叶子。元数据块不会有其他变化。旧的叶子直到RCU宽限期之后才会
    445   被释放。
    446
    4473. 插入,替换我们还没有进入的子树的一部分。这可能涉及到替换该子树的一部分——但这不会影响迭代,因为我们
    448   还没有到达它的指针,而且祖先块也不会被替换(这些块的布局不会改变)。
    449
    4504. 插入替换了我们正在处理的节点。这不是一个问题,因为我们已经通过了锚定指针,直到我们跟随后面的指针才
    451   会切换到新的布局上——这时我们已经检查了被替换节点的叶子(在跟随任何元数据指针之前,我们会迭代一个节
    452   点的所有叶子)。
    453
    454   然而,我们可能会重新看到一些叶子,这些叶子已经被分割成一个新的分支,而这个分支的位置比我们之前的位
    455   置更远。
    456
    4575. 插入替换了我们正在处理的依赖分支的节点。这不会影响到我们,直到我们跟随后面的指针。与(4)类似。
    458
    4596. 删掉我们下面的一个分支。这不会影响我们,因为在我们看到新节点之前,回溯指针会让我们回到新节点的父节
    460   点。整个崩溃的子树被扔掉了,没有任何变化——而且仍然会在同一个槽上生根,所以我们不应该第二次处理它,
    461   因为我们会回到槽+1。
    462
    463.. note::
    464
    465    在某些情况下,我们需要同时改变一个节点的父指针和父槽指针(比如说,我们在它之前插入了另一个节点,
    466    并把它往上移了一层)。我们不能在不锁定读取的情况下这样做——所以我们必须同时替换该节点。
    467
    468    然而,当我们把一个快捷键改成一个节点时,这不是一个问题,因为快捷键只有一个槽,所以当向后遍
    469    历一个槽时,不会使用父槽号。这意味着先改变槽位号是可以的——只要使用适当的屏障来确保父槽位号在后
    470    退指针之后被读取。
    471
    472过时的块和叶子在RCU宽限期过后会被释放,所以只要任何进行遍历或迭代的人持有RCU读锁,旧的上层建筑就不
    473应该在他们身上消失。