padata 并行执行机制

日期:

2020 年 5 月

padata 是一种机制,内核可以通过它将作业分发到多个 CPU 上并行执行,同时可选地保留其顺序。

它最初是为 IPsec 开发的,IPsec 需要对大量数据包进行加密和解密,而无需重新排序这些数据包。目前,它是 padata 序列化作业支持的唯一消费者。

Padata 还支持多线程作业,在线程之间进行负载均衡和协调的同时,均匀地拆分作业。

运行序列化作业

初始化

使用 padata 运行序列化作业的第一步是设置一个 padata_instance 结构体,用于对作业运行方式进行整体控制。

#include <linux/padata.h>

struct padata_instance *padata_alloc(const char *name);

“name” 仅用于标识实例。

然后,通过分配一个 padata_shell 来完成 padata 初始化。

struct padata_shell *padata_alloc_shell(struct padata_instance *pinst);

padata_shell 用于向 padata 提交作业,并允许一系列此类作业独立地进行序列化。一个 padata_instance 可以关联一个或多个 padata_shell,每个都允许一个独立的作业序列。

修改 cpumasks

用于运行作业的 CPU 可以通过两种方式进行更改:通过编程方式使用 padata_set_cpumask() 或通过 sysfs。前者定义于

int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type,
                       cpumask_var_t cpumask);

此处 cpumask_type 为 PADATA_CPU_PARALLEL 或 PADATA_CPU_SERIAL 之一,其中并行 cpumask 描述了将用于并行执行提交到此实例的作业的处理器,串行 cpumask 定义了允许用作序列化回调处理器的处理器。cpumask 指定要使用的新 cpumask。

一个实例的 cpumasks 可能存在 sysfs 文件。例如,pcrypt 的位于 /sys/kernel/pcrypt/<instance-name>。在一个实例的目录下,有两个文件:parallel_cpumask 和 serial_cpumask,任一 cpumask 都可以通过将位掩码回显到文件中进行更改,例如

echo f > /sys/kernel/pcrypt/pencrypt/parallel_cpumask

读取这些文件之一将显示用户提供的 cpumask,它可能与“可用” cpumask 不同。

Padata 内部维护两对 cpumask:用户提供的 cpumask 和“可用” cpumask。(每对包含一个并行和一个串行 cpumask。)用户提供的 cpumask 在实例分配时默认设置为所有可能的 CPU,并可以如上所述进行更改。可用的 cpumask 始终是用户提供的 cpumask 的子集,并且仅包含用户提供的掩码中的在线 CPU;这些是 padata 实际使用的 cpumask。因此,向 padata 提供包含离线 CPU 的 cpumask 是合法的。一旦用户提供的 cpumask 中的一个离线 CPU 上线,padata 就会使用它。

更改 CPU 掩码是昂贵的操作,因此不应频繁进行。

运行作业

实际向 padata 实例提交工作需要创建一个 padata_priv 结构体,它代表一个作业。

struct padata_priv {
    /* Other stuff here... */
    void                    (*parallel)(struct padata_priv *padata);
    void                    (*serial)(struct padata_priv *padata);
};

该结构体几乎肯定会被嵌入到与要完成的工作相关的某个更大的结构体中。它的大多数字段是 padata 私有的,但在初始化时应将结构体清零,并应提供 parallel() 和 serial() 函数。这些函数将在完成工作的过程中被调用,我们稍后将看到。

作业的提交通过以下方式完成:

int padata_do_parallel(struct padata_shell *ps,
                       struct padata_priv *padata, int *cb_cpu);

ps 和 padata 结构体必须如上所述设置;cb_cpu 指向作业完成时用于最终回调的首选 CPU;它必须在当前实例的 CPU 掩码中(如果不在,cb_cpu 指针会更新为指向实际选择的 CPU)。padata_do_parallel() 的返回值为 0 表示成功,表明作业正在进行中。-EBUSY 意味着有人在其他地方修改实例的 CPU 掩码,而 -EINVAL 表示 cb_cpu 不在串行 cpumask 中,并行或串行 cpumask 中没有在线 CPU,或者实例已停止。

提交到 padata_do_parallel() 的每个作业,将依次传递给上述 parallel() 函数的精确一次调用,在一个 CPU 上,因此通过提交多个作业来实现真正的并行性。parallel() 在禁用软件中断的情况下运行,因此不能睡眠。parallel() 函数将其 padata_priv 结构体指针作为其唯一参数;关于实际要完成的工作的信息可能通过使用 container_of() 来查找封装结构体获得。

请注意,parallel() 没有返回值;padata 子系统假定 parallel() 从此点开始负责该作业。作业无需在此调用期间完成,但如果 parallel() 留下未完成的工作,则应准备好在上一作业完成之前再次被调用来处理新作业。

序列化作业

当作业确实完成时,parallel()(或任何实际完成工作的函数)应通过调用以下函数通知 padata:

void padata_do_serial(struct padata_priv *padata);

在未来的某个时候,padata_do_serial() 将触发对 padata_priv 结构体中的 serial() 函数的调用。该调用将发生在最初调用 padata_do_parallel() 时请求的 CPU 上;它也将在禁用局部软件中断的情况下运行。请注意,此调用可能会延迟一段时间,因为 padata 代码会努力确保作业按照提交的顺序完成。

销毁

清理 padata 实例通常涉及到反向调用与分配对应的两个释放函数。

void padata_free_shell(struct padata_shell *ps);
void padata_free(struct padata_instance *pinst);

用户有责任确保所有未完成的作业在调用上述任何函数之前都已完成。

运行多线程作业

一个多线程作业有一个主线程和零个或多个辅助线程,主线程参与作业,然后等待所有辅助线程完成。padata 将作业分成称为“块”(chunks)的单位,其中一个块是作业的一部分,一个线程通过一次调用线程函数来完成它。

用户需要做三件事来运行一个多线程作业。首先,通过定义一个 padata_mt_job 结构体来描述作业,这在接口(Interface)部分有所解释。这包括一个指向线程函数的指针,padata 每次将作业块分配给线程时都会调用该函数。然后,定义线程函数,它接受三个参数:startendarg,其中前两个参数界定线程操作的范围,最后一个参数是指向作业共享状态的指针(如果有的话)。准备共享状态,它通常在主线程的栈上分配。最后,调用 padata_do_multithreaded(),该函数在作业完成后返回。

接口

struct padata_priv

代表一个作业

定义:

struct padata_priv {
    struct list_head        list;
    struct parallel_data    *pd;
    int cb_cpu;
    unsigned int            seq_nr;
    int info;
    void (*parallel)(struct padata_priv *padata);
    void (*serial)(struct padata_priv *padata);
};

成员

list

列表项,用于附加到 padata 列表。

pd

指向内部控制结构体的指针。

cb_cpu

用于序列化的回调 CPU。

seq_nr

并行化数据对象的序列号。

info

用于将信息从并行函数传递到串行函数。

parallel

并行执行函数。

serial

串行完成函数。

struct padata_list

每个 CPU 每种工作类型一个

定义:

struct padata_list {
    struct list_head        list;
    spinlock_t lock;
};

成员

list

列表头。

lock

列表锁。

struct padata_serial_queue

percpu padata 串行队列

定义:

struct padata_serial_queue {
    struct padata_list    serial;
    struct work_struct    work;
    struct parallel_data *pd;
};

成员

serial

重排序后等待序列化的列表。

work

用于序列化的工作结构体。

pd

指向内部控制结构体的反向指针。

struct padata_cpumask

并行/串行工作者的 cpumasks

定义:

struct padata_cpumask {
    cpumask_var_t pcpu;
    cpumask_var_t cbcpu;
};

成员

pcpu

用于并行工作者的 cpumask。

cbcpu

用于串行(回调)工作者的 cpumask。

struct parallel_data

内部控制结构体,涵盖所有依赖于正在使用的 cpumask 的内容。

定义:

struct parallel_data {
    struct padata_shell             *ps;
    struct padata_list              __percpu *reorder_list;
    struct padata_serial_queue      __percpu *squeue;
    refcount_t refcnt;
    unsigned int                    seq_nr;
    unsigned int                    processed;
    int cpu;
    struct padata_cpumask           cpumask;
    struct work_struct              reorder_work;
    spinlock_t lock;
};

成员

ps

padata_shell 对象。

reorder_list

percpu 重排序列表

squeue

用于序列化的 percpu padata 队列。

refcnt

持有此 parallel_data 引用的对象数量。

seq_nr

并行化数据对象的序列号。

processed

已处理对象的数量。

cpu

下一个待处理的 CPU。

cpumask

并行和串行工作者使用的 cpumasks。

reorder_work

用于重排序的工作结构体。

lock

重排序锁。

struct padata_shell

包装器,用于 struct parallel_data,其目的是允许使用 RCU 实时替换底层控制结构体。

定义:

struct padata_shell {
    struct padata_instance          *pinst;
    struct parallel_data __rcu      *pd;
    struct parallel_data            *opd;
    struct list_head                list;
};

成员

pinst

padata 实例。

pd

实际的 parallel_data 结构体,它可以在运行时被替换。

opd

指向将被 padata_replace 释放的旧 pd 的指针。

list

padata_instance 列表中的列表项。

struct padata_mt_job

代表一个多线程作业

定义:

struct padata_mt_job {
    void (*thread_fn)(unsigned long start, unsigned long end, void *arg);
    void *fn_arg;
    unsigned long           start;
    unsigned long           size;
    unsigned long           align;
    unsigned long           min_chunk;
    int max_threads;
    bool numa_aware;
};

成员

thread_fn

每次 padata 线程完成一个工作块时调用。

fn_arg

线程函数参数。

start

作业的开始(单位是作业特定的)。

size

此节点工作的大小(单位是作业特定的)。

align

传递给线程函数的范围落在此边界上,作业的开始和结束除外。

min_chunk

以作业特定单位表示的最小块大小。这允许客户端传递适合一个工作线程一次完成的最小工作量。

max_threads

作业使用的最大线程数,实际数量可能因任务大小和最小块大小而异。

numa_aware

以轮询方式将作业分配给具有 CPU 的不同节点。

struct padata_instance

整体控制结构体。

定义:

struct padata_instance {
    struct hlist_node               cpu_online_node;
    struct hlist_node               cpu_dead_node;
    struct workqueue_struct         *parallel_wq;
    struct workqueue_struct         *serial_wq;
    struct list_head                pslist;
    struct padata_cpumask           cpumask;
    struct kobject                   kobj;
    struct mutex                     lock;
    u8 flags;
#define PADATA_INIT     1;
#define PADATA_RESET    2;
#define PADATA_INVALID  4;
};

成员

cpu_online_node

CPU 上线回调的链接。

cpu_dead_node

CPU 离线回调的链接。

parallel_wq

用于并行工作的工作队列。

serial_wq

用于串行工作的工作队列。

pslist

附加到此实例的 padata_shell 对象列表。

cpumask

用户为并行和串行工作提供的 cpumasks。

kobj

padata 实例内核对象。

lock

padata 实例锁。

flags

padata 标志。

int padata_do_parallel(struct padata_shell *ps, structpadata_priv *padata, int *cb_cpu)

padata 并行化函数

参数

struct padata_shell *ps

padata shell

struct padata_priv *padata

要并行化的对象

int *cb_cpu

指向序列化回调函数应在其上运行的 CPU 的指针。如果它不在 pinst 的串行 cpumask 中(即 cpumask.cbcpu),此函数会选择一个备用 CPU,如果没有找到,则返回 -EINVAL。

描述

并行化回调函数将在 BHs 禁用时运行。

注意

每个由 padata_do_parallel 并行化的对象都必须被 padata_do_serial 处理。

返回

成功返回 0,否则返回负错误码。

void padata_do_serial(struct padata_priv *padata)

padata 序列化函数

参数

struct padata_priv *padata

要序列化的对象。

描述

每个并行化对象都必须调用 padata_do_serial。序列化回调函数将在 BHs 禁用时运行。

void padata_do_multithreaded(struct padata_mt_job *job)

运行一个多线程作业

参数

struct padata_mt_job *job

作业描述。

描述

有关更多详细信息,请参阅 struct padata_mt_job 的定义。

int padata_set_cpumask(struct padata_instance *pinst, int cpumask_type, cpumask_var_t cpumask)

将由 cpumask_type 指定的 cpumask 设置为与 cpumask 等效的值。

参数

struct padata_instance *pinst

padata 实例

int cpumask_type

PADATA_CPU_SERIAL 或 PADATA_CPU_PARALLEL,分别对应并行和串行 cpumask。

cpumask_var_t cpumask

要使用的 cpumask

返回

成功返回 0,否则返回负错误码

struct padata_instance *padata_alloc(const char *name)

分配并初始化一个 padata 实例

参数

const char *name

用于标识实例

返回

成功返回新实例,错误返回 NULL

void padata_free(struct padata_instance *pinst)

释放一个 padata 实例

参数

struct padata_instance *pinst

要释放的 padata 实例

struct padata_shell *padata_alloc_shell(struct padata_instance *pinst)

分配并初始化 padata shell。

参数

struct padata_instance *pinst

父 padata_instance 对象。

返回

成功返回新 shell,错误返回 NULL

void padata_free_shell(struct padata_shell *ps)

释放一个 padata shell

参数

struct padata_shell *ps

要释放的 padata shell