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,每个 shell 允许独立的作业序列。
修改 CPU 掩码¶
用于运行作业的 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 中的一个,其中并行 CPU 掩码描述将用于并行执行提交给此实例的作业的处理器,而串行 CPU 掩码定义允许用作序列化回调处理器的处理器。cpumask 指定要使用的新 CPU 掩码。
实例的 CPU 掩码可能存在 sysfs 文件。例如,pcrypt 的文件位于 /sys/kernel/pcrypt/<instance-name> 中。在实例的目录中,有两个文件,parallel_cpumask 和 serial_cpumask,可以通过将位掩码回显到文件中来更改任何一个 CPU 掩码,例如
echo f > /sys/kernel/pcrypt/pencrypt/parallel_cpumask
读取其中一个文件会显示用户提供的 CPU 掩码,它可能与“可用的”CPU 掩码不同。
Padata 在内部维护两对 CPU 掩码:用户提供的 CPU 掩码和“可用的”CPU 掩码。(每对都由一个并行 CPU 掩码和一个串行 CPU 掩码组成。)用户提供的 CPU 掩码在实例分配时默认为所有可能的 CPU,并且可以按上述方式更改。可用的 CPU 掩码始终是用户提供的 CPU 掩码的子集,并且仅包含用户提供的掩码中在线的 CPU;这些是 padata 实际使用的 CPU 掩码。因此,向 padata 提供包含离线 CPU 的 CPU 掩码是合法的。一旦用户提供的 CPU 掩码中的离线 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()
的返回值在成功时为零,表示作业正在进行中。-EBUSY 表示其他地方的某些人在修改实例的 CPU 掩码,而 -EINVAL 是对 cb_cpu 不在串行 CPU 掩码中、并行或串行 CPU 掩码中没有在线 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 实例的可预测步骤包括反向调用与分配对应的两个 free 函数
void padata_free_shell(struct padata_shell *ps);
void padata_free(struct padata_instance *pinst);
用户有责任确保在调用上述任何函数之前所有未完成的作业都已完成。
运行多线程作业¶
多线程作业有一个主线程和零个或多个辅助线程,主线程参与作业,然后等待所有辅助线程完成。padata 将作业拆分为称为块的单元,其中块是一个线程在一次调用线程函数中完成的作业的一部分。
用户必须执行三项操作才能运行多线程作业。首先,通过定义 padata_mt_job 结构来描述作业,这将在接口部分中进行说明。这包括一个指向线程函数的指针,padata 每次将作业块分配给线程时都会调用该函数。然后,定义线程函数,该函数接受三个参数,start
、end
和 arg
,其中前两个参数限定了线程操作的范围,最后一个参数是指向作业共享状态的指针(如果有)。准备共享状态,该状态通常在主线程的堆栈上分配。最后,调用 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¶
每个 CPU 的 padata 串行队列
定义:
struct padata_serial_queue {
struct padata_list serial;
struct work_struct work;
struct parallel_data *pd;
};
成员
serial
重新排序后等待序列化的列表。
work
用于序列化的 work 结构。
pd
指向内部控制结构的后向指针。
-
struct padata_cpumask¶
并行/串行工作者的 CPU 掩码
定义:
struct padata_cpumask {
cpumask_var_t pcpu;
cpumask_var_t cbcpu;
};
成员
pcpu
并行工作者的 CPU 掩码。
cbcpu
串行(回调)工作者的 CPU 掩码。
-
struct parallel_data¶
内部控制结构,涵盖所有依赖于所使用 CPU 掩码的内容。
定义:
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
每个 CPU 的重新排序列表
squeue
用于序列化的每个 CPU padata 队列。
refcnt
持有对此 parallel_data 引用的对象数。
seq_nr
并行化数据对象的序列号。
processed
已处理对象的数量。
cpu
下一个要处理的 CPU。
cpumask
用于并行和串行工作者的 CPU 掩码。
reorder_work
用于重新排序的 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
padat 实例。
pd
实际的 parallel_data 结构,可以动态替换。
opd
指向旧的 pd 的指针,将由 padata_replace 释放。
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
用于并行工作的 workqueue。
serial_wq
用于串行工作的 workqueue。
pslist
附加到此实例的 padata_shell 对象列表。
cpumask
用户提供的用于并行和串行工作的 cpumask。
kobj
padata 实例内核对象。
lock
padata 实例锁。
flags
padata 标志。
-
int padata_do_parallel(struct padata_shell *ps, struct padata_priv *padata, int *cb_cpu)¶
padata 并行化函数
参数
struct padata_shell *ps
padatashell
struct padata_priv *padata
要并行化的对象
int *cb_cpu
指向应运行序列化回调函数的 CPU 的指针。如果它不在 **pinst** (即 cpumask.cbcpu) 的串行 cpumask 中,则此函数选择一个备用 CPU,如果未找到,则返回 -EINVAL。
说明
并行化回调函数将在关闭 BH 的情况下运行。
注意
每个由 padata_do_parallel 并行化的对象都必须被 padata_do_serial 看到。
返回
成功时返回 0,否则返回负错误代码。
-
void padata_do_serial(struct padata_priv *padata)¶
padata 序列化函数
参数
struct padata_priv *padata
要序列化的对象。
说明
必须为每个并行化的对象调用 padata_do_serial。序列化回调函数将在关闭 BH 的情况下运行。
-
void padata_do_multithreaded(struct padata_mt_job *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