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