文件:util-affinity.h util-affinity.c
#ifndef __UTIL_AFFINITY_H__
#define __UTIL_AFFINITY_H__
#include "suricata-common.h"
#include "conf.h"
#if defined OS_FREEBSD
#include
#include
#include
#include
#include
#define cpu_set_t cpuset_t
#elif defined __OpenBSD__
#include
#include
#include
#elif defined OS_DARWIN
#include
#include
#include
// thread_affinity_policy_data_t 是一种用于描述线程关联性(affinity)策略的数据结构。
// 关联性是指一种指导操作系统调度器的机制,使特定的线程更倾向于在特定的CPU核心或核心集上运行。这对于某些高性能计算任务或实时任务可能非常有用,因为它可以减少上下文切换和缓存失效的开销。
// thread_affinity_policy_data_t`是一种特定的数据类型,用于设定线程关联性策略。下面是这个结构的定义:
// typedef struct thread_affinity_policy {
// int affinity_tag;
// } thread_affinity_policy_data_t;
// 其中 `affinity_tag` 是一个整数,用于表示线程的关联标签(affinity tag)。线程具有相同关联标签的倾向于在同一处理器核心上运行。
// 当你要设置线程的关联性策略时,你可以创建一个 `thread_affinity_policy_data_t` 结构,设置适当的关联标签,
// 并使用 `thread_policy_set` 函数将其应用于特定线程。
// 示例:
// thread_affinity_policy_data_t policy;
// policy.affinity_tag = 1; // 1 is the affinity tag
// thread_policy_set(thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&policy, THREAD_AFFINITY_POLICY_COUNT);
// 这里的 `THREAD_AFFINITY_POLICY` 和 `THREAD_AFFINITY_POLICY_COUNT` 是描述策略类型和大小的常量。
// 请注意,线程关联性是一个提示,而不是一个硬性规定。即使为线程设置了关联性策略,操作系统调度器仍然可能会在其他处理器上运行该线程。
// 关联性策略的效果也可能因操作系统和硬件架构的不同而有所不同。
// `affinity_tag`是一个抽象的标签,用于将具有相同关联性需求的线程组合在一起。
// 当你为多个线程设置相同的`affinity_tag`时,操作系统的调度器会尽量让这些线程在同一个CPU核心或核心组上运行。
// `affinity_tag`的具体值没有特定的含义。它只是一个标识符或标签,用于将具有相同关联性需求的线程组合在一起。
// 在以下示例中:
// policy.affinity_tag = 1; // 1 is the affinity tag
// 数字`1`只是一个标识符。你可以选择任何整数值作为关联标签,只要你在你的应用程序或系统中一致使用它就可以了。
// 如果你在多个线程之间设置相同的`affinity_tag`值,那么操作系统将尽量让这些线程在同一处理器核心上运行。
// 这样做的目的通常是为了提高缓存效率和性能,因为在同一核心上运行的线程可以共享缓存和其他资源。
// 请注意,线程关联性是一个建议性的机制,而不是强制性的。调度器可能会基于系统的其他需求和负载来忽略或覆盖关联性设置。
// 这条宏定义将 cpu_set_t 重新定义为 thread_affinity_policy_data_t。这样,在代码中使用 cpu_set_t 时,实际上是在使用 thread_affinity_policy_data_t 类型。
#define cpu_set_t thread_affinity_policy_data_t
// 这条宏定义重新定义了 CPU_SET 宏。它接受一个 CPU 编号 cpu_id 和一个 CPU 集合 new_mask 作为参数,
// 并将 new_mask 中的 affinity_tag 设置为 cpu_id + 1。这似乎是一种将 CPU 编号映射到 affinity_tag 的方式。
#define CPU_SET(cpu_id, new_mask) (*(new_mask)).affinity_tag = (cpu_id + 1)
// 这条宏定义重新定义了 CPU_ISSET 宏。它接受一个 CPU 编号 cpu_id 和一个 CPU 集合 new_mask 作为参数,
#define CPU_ISSET(cpu_id, new_mask) ((*(new_mask)).affinity_tag == (cpu_id + 1))
// 这条宏定义重新定义了 CPU_ZERO 宏。它接受一个 CPU 集合 new_mask 作为参数,并将 new_mask 中的 affinity_tag 设置为 THREAD_AFFINITY_TAG_NULL。
#define CPU_ZERO(new_mask) (*(new_mask)).affinity_tag = THREAD_AFFINITY_TAG_NULL
#endif
enum {
RECEIVE_CPU_SET,
WORKER_CPU_SET,
VERDICT_CPU_SET,
MANAGEMENT_CPU_SET,
MAX_CPU_SET
};
// BALANCED_AFFINITY: 这个常量值可能表示一种"平衡"的线程亲和性配置模式。
// 在多线程程序中,线程亲和性指的是将线程绑定到特定的 CPU 核心或处理器,以优化性能。
// "平衡"的线程亲和性可能意味着线程会在多个 CPU 核心之间分配,以实现负载均衡。
// EXCLUSIVE_AFFINITY: 这个常量值可能表示一种"独占"的线程亲和性配置模式。
// 在这种模式下,每个线程会被绑定到一个特定的 CPU 核心或处理器,从而实现更精细的性能控制和优化。
// 这可以用于确保某些关键任务始终在同一核心上执行,避免由于核心切换而引起的性能下降。
// MAX_AFFINITY: 这个常量值可能表示一个枚举值的上限。在这里,它可能被用来表示枚举的最大值或可选项的总数。
enum {
BALANCED_AFFINITY,
EXCLUSIVE_AFFINITY,
MAX_AFFINITY
};
// 用于存储线程亲和性(thread affinity)的相关设置信息。
// 用于保存不同线程亲和性类型的相关信息,包括名称、模式、优先级、线程数目以及允许运行的 CPU 集合等。
// 它可能在代码中用于组织和管理不同线程亲和性设置的数据。
// 从配置中读取节点信息,然后将其保存到这个结构体中。
typedef struc ThreadsAffinityType_ {
const char *name; // 一个指向字符串的指针,用于存储线程亲和性类型的名称。
uint8_t mode_flag; // 一个8位无符号整数,用于存储线程亲和性的模式标志。
int prio; // 一个整数,用于存储线程亲和性的优先级。
uint32_t nb_threads; // 一个32位无符号整数,用于存储线程数目。
SCMutex taf_mutex; // 一个名为 taf_mutex 的互斥锁,用于保护线程亲和性设置的访问。
uint16_t lcpu; /* use by exclusive mode */
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
cpu_set_t cpu_set; // 一个16位无符号整数,在独占模式(exclusive mode)下使用。
cpu_set_t lowprio_cpu; // 一个 CPU 集合,用于存储低优先级线程允许运行的 CPU。
cpu_set_t medprio_cpu; // 一个 CPU 集合,用于存储中优先级线程允许运行的 CPU。
cpu_set_t hiprio_cpu; // 一个 CPU 集合,用于存储高优先级线程允许运行的 CPU。
#endif
} ThreadsAffinityType;
/** store thread affinity mode for all type of threads */
#ifndef _THREAD_AFFINITY
extern ThreadsAffinityType thread_affinity[MAX_CPU_SET];
#endif
void AffinitySetupLoadFromConfig(void);
ThreadsAffinityType * GetAffinityTypeFromName(const char *name);
int AffinityGetNextCPU(ThreadsAffinityType *taf);
void BuildCpusetWithCallback(const char *name, ConfNode *node,
void (*Callback)(int i, void * data),
void *data);
#endif /* __UTIL_AFFINITY_H__ */
#include "suricata-common.h"
#define _THREAD_AFFINITY
#include "util-affinity.h"
#include "util-cpu.h"
#include "util-byte.h"
#include "conf.h"
#include "threads.h"
#include "queue.h"
#include "runmodes.h"
// management-cpu-set - used for management (example - flow.managers, flow.recyclers)
// receive-cpu-set - used for receive and decode
// worker-cpu-set - used for streamtcp,detect,output(logging)
// verdict-cpu-set - used for verdict and respond/reject
// 用于存储不同线程亲和性类型的设置信息。
ThreadsAffinityType thread_affinity[MAX_CPU_SET] = {
{
.name = "receive-cpu-set",
// 表示"独占亲和性模式"。在这种模式下,每个线程都将被分配到特定的CPU核心,不与其他线程共享核心。这可以避免线程之间的干扰,但需要更精细的调度策略来保证性能。
.mode_flag = EXCLUSIVE_AFFINITY,
.prio = PRIO_MEDIUM,
.lcpu = 0,
},
{
.name = "worker-cpu-set",
.mode_flag = EXCLUSIVE_AFFINITY,
.prio = PRIO_MEDIUM,
.lcpu = 0,
},
{
.name = "verdict-cpu-set",
// 表示"平衡亲和性模式"。在这种模式下,线程将在可用的多个CPU核心之间平均分配,以实现负载均衡。这样可以确保各个核心被充分利用,从而提高整体的性能。
.mode_flag = BALANCED_AFFINITY,
.lcpu = 0,
},
{
.name = "management-cpu-set",
.mode_flag = BALANCED_AFFINITY,
.prio = PRIO_MEDIUM,
.lcpu = 0,
},
};
int thread_affinity_init_done = 0;
/**
* \brief find affinity by its name
* \retval a pointer to the affinity or NULL if not found
*/
ThreadsAffinityType * GetAffinityTypeFromName(const char *name)
{
int i;
for (i = 0; i < MAX_CPU_SET; i++) {
if (!strcmp(thread_affinity[i].name, name)) {
return &thread_affinity[i];
}
}
return NULL;
}
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
static void AffinitySetupInit(void)
{
int i, j;
// 使用 UtilCpuGetNumProcessorsConfigured() 函数获取已配置的处理器数量,保存在变量 ncpu 中。
int ncpu = UtilCpuGetNumProcessorsConfigured();
SCLogDebug("Initialize affinity setup\n");
/* be conservative relatively to OS: use all cpus by default */
for (i = 0; i < MAX_CPU_SET; i++) {
// 在循环内部,获取名为 thread_affinity 数组中第 i 个元素的 cpu_set_t 成员的指针 cs。
cpu_set_t *cs = &thread_affinity[i].cpu_set;
CPU_ZERO(cs); // 使用 CPU_ZERO 宏将 cs 初始化为空的 CPU 集合。
// 使用嵌套的 for 循环遍历变量 j 从 0 到 ncpu - 1,然后使用 CPU_SET 宏将编号为 j 的处理器加入到当前的 CPU 集合 cs 中。
for (j = 0; j < ncpu; j++) {
CPU_SET(j, cs);
}
//使用 SCMutexInit 函数初始化名为 thread_affinity 数组中第 i 个元素的 taf_mutex 互斥锁。
SCMutexInit(&thread_affinity[i].taf_mutex, NULL);
}
return;
}
// 该函数用于构建一个 CPU 集合,并在集合中的每个 CPU 上执行一个回调函数。
/**
* 根据配置节点构建一个 CPU 集合,并通过回调函数对每个 CPU 进行处理。
*
* @param name 名称标识,用于错误信息输出
* @param node 配置节点,包含要构建的 CPU 集合的信息
* @param Callback 回调函数,将被用于处理每个 CPU
* @param data 传递给回调函数的额外数据
*/
// 这个函数在处理配置节点中的 CPU 集合信息时,根据输入的字符串表示,解析出要处理的 CPU 范围,
// 并通过回调函数对每个 CPU 进行处理。对于不同的指定情况(如 "all"、范围、单个 CPU),函数会根据不同的情况进行处理,
// 同时还包括了一些错误检查和报错处理。回调函数 Callback 将会在每个 CPU 上调用,并可以用来进行一些特定的处理。
void BuildCpusetWithCallback(const char *name, ConfNode *node,
void (*Callback)(int i, void * data),
void *data)
{
ConfNode *lnode;
TAILQ_FOREACH(lnode, &node->head, next) {
int i;
long int a,b;
int stop = 0;
int max = UtilCpuGetNumProcessorsOnline() - 1;
// 处理特殊情况:指定全部 CPU
if (!strcmp(lnode->val, "all")) {
a = 0;
b = max;
stop = 1;
}// 处理范围指定情况,例如 "1-3"
else if (strchr(lnode->val, '-') != NULL) {
// strchr:这个函数将从字符串的开头开始,寻找第一个匹配-的字符。如果找到,它将返回一个指向该字符的指针。如果找不到,它将返回NULL。
char *sep = strchr(lnode->val, '-');
char *end;
// 这行代码使用C库中的strtoul函数来将字符串lnode->val的内容转换为一个无符号长整型数(unsigned long)。这里的转换是基于10进制的。
a = strtoul(lnode->val, &end, 10);
// 以下代码用于解析和验证范围情况的输入
// 包括判断范围是否合法,是否超出上限等
if (end != sep) {
SCLogError(SC_ERR_INVALID_ARGUMENT,
"%s: invalid cpu range (start invalid): \"%s\"",
name,
lnode->val);
exit(EXIT_FAILURE);
}
b = strtol(sep + 1, &end, 10);
if (end != sep + strlen(sep)) {
SCLogError(SC_ERR_INVALID_ARGUMENT,
"%s: invalid cpu range (end invalid): \"%s\"",
name,
lnode->val);
exit(EXIT_FAILURE);
}
if (a > b) {
SCLogError(SC_ERR_INVALID_ARGUMENT,
"%s: invalid cpu range (bad order): \"%s\"",
name,
lnode->val);
exit(EXIT_FAILURE);
}
if (b > max) {
SCLogError(SC_ERR_INVALID_ARGUMENT,
"%s: upper bound (%ld) of cpu set is too high, only %d cpu(s)",
name,
b, max + 1);
}
} else {
// 处理单个 CPU 指定情况,例如 "1"
char *end;
a = strtoul(lnode->val, &end, 10);
// 以下代码用于解析和验证单个 CPU 指定情况的输入
// 包括判断输入是否为整数等
if (end != lnode->val + strlen(lnode->val)) {
SCLogError(SC_ERR_INVALID_ARGUMENT,
"%s: invalid cpu range (not an integer): \"%s\"",
name,
lnode->val);
exit(EXIT_FAILURE);
}
b = a;
}
// 遍历处理范围内的每个 CPU
for (i = a; i<= b; i++) {
Callback(i, data);// 调用回调函数处理当前 CPU
}
// 如果是 "all" 指定,就退出循环
if (stop)
break;
}
}
static void AffinityCallback(int i, void *data)
{
CPU_SET(i, (cpu_set_t *)data);
}
static void BuildCpuset(const char *name, ConfNode *node, cpu_set_t *cpu)
{
BuildCpusetWithCallback(name, node, AffinityCallback, (void *) cpu);
}
#endif /* OS_WIN32 and __OpenBSD__ */
/**
* \brief Extract cpu affinity configuration from current config file
*/
// 从配置文件中加载线程亲和性
void AffinitySetupLoadFromConfig()
{
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
// 在代码中,使用 ConfNode 类型的指针变量 root 获取配置节点 "threading.cpu-affinity"。
ConfNode *root = ConfGetNode("threading.cpu-affinity");
ConfNode *affinity;
// 如果之前没有进行线程亲和性的初始化(thread_affinity_init_done == 0),
// 则调用 AffinitySetupInit() 函数进行初始化,并将标志 thread_affinity_init_done 设置为 1。
if (thread_affinity_init_done == 0) {
AffinitySetupInit();
thread_affinity_init_done = 1;
}
// 如果无法获取名为 "threading.cpu-affinity" 的配置节点,输出一条信息并返回。
SCLogDebug("Load affinity from config\n");
if (root == NULL) {
SCLogInfo("can't get cpu-affinity node");
return;
}
// 使用 TAILQ_FOREACH 宏遍历名为 root 的配置节点的链表,即遍历配置中 "threading.cpu-affinity" 节点下的子节点。
TAILQ_FOREACH(affinity, &root->head, next) { // affinity被配置为root->head
if (strcmp(affinity->val, "decode-cpu-set") == 0 ||
strcmp(affinity->val, "stream-cpu-set") == 0 ||
strcmp(affinity->val, "reject-cpu-set") == 0 ||
strcmp(affinity->val, "output-cpu-set") == 0) {
continue;
}
// 获取当前子节点的值,并将其保存到名为 setname 的变量中。
const char *setname = affinity->val;
if (strcmp(affinity->val, "detect-cpu-set") == 0)
setname = "worker-cpu-set";
// 使用函数 GetAffinityTypeFromName() 根据 setname 获取与之相关的线程亲和性类型。
ThreadsAffinityType *taf = GetAffinityTypeFromName(setname);
ConfNode *node = NULL;
ConfNode *nprio = NULL;
// 检查从GetAffinityTypeFromName函数获得的线程亲和性类型是否为NULL。如果不是NULL,表示找到了匹配的类型,记录一条配置级别的日志。
if (taf == NULL) {
FatalError(SC_ERR_FATAL, "unknown cpu-affinity type");
} else {
SCLogConfig("Found affinity definition for \"%s\"", setname);
}
// 清空 taf 对象中的 CPU 集合。
CPU_ZERO(&taf->cpu_set);
// 获取名为 "cpu" 的子节点,如果找不到,输出信息。
node = ConfNodeLookupChild(affinity->head.tqh_first, "cpu");
if (node == NULL) {
SCLogInfo("unable to find 'cpu'");
} else {
// 使用函数 BuildCpuset() 根据子节点的信息构建 CPU 集合,并保存到 taf->cpu_set 中。
BuildCpuset(setname, node, &taf->cpu_set);
}
// 初始化线程亲和性类型的cpu_set为零。
CPU_ZERO(&taf->lowprio_cpu);
CPU_ZERO(&taf->medprio_cpu);
CPU_ZERO(&taf->hiprio_cpu);
// 类似上面的步骤,这里初始化lowprio_cpu、medprio_cpu 和 hiprio_cpu 为零。
// 然后,从 "prio" 子节点中查找 "low"、"medium" 和 "high" 节点,用于设置不同优先级的线程亲和性。
// 将不同优先级的节点放入taf不同优先级集合中
nprio = ConfNodeLookupChild(affinity->head.tqh_first, "prio");
if (nprio != NULL) {
node = ConfNodeLookupChild(nprio, "low");
if (node == NULL) {
SCLogDebug("unable to find 'low' prio using default value");
} else {
BuildCpuset(setname, node, &taf->lowprio_cpu);
}
node = ConfNodeLookupChild(nprio, "medium");
if (node == NULL) {
SCLogDebug("unable to find 'medium' prio using default value");
} else {
BuildCpuset(setname, node, &taf->medprio_cpu);
}
node = ConfNodeLookupChild(nprio, "high");
if (node == NULL) {
SCLogDebug("unable to find 'high' prio using default value");
} else {
BuildCpuset(setname, node, &taf->hiprio_cpu);
}
node = ConfNodeLookupChild(nprio, "default");
if (node != NULL) {
if (!strcmp(node->val, "low")) {
taf->prio = PRIO_LOW;
} else if (!strcmp(node->val, "medium")) {
taf->prio = PRIO_MEDIUM;
} else if (!strcmp(node->val, "high")) {
taf->prio = PRIO_HIGH;
} else {
FatalError(SC_ERR_FATAL, "unknown cpu_affinity prio");
}
SCLogConfig("Using default prio '%s' for set '%s'",
node->val, setname);
}
}
node = ConfNodeLookupChild(affinity->head.tqh_first, "mode");
if (node != NULL) {
if (!strcmp(node->val, "exclusive")) {
taf->mode_flag = EXCLUSIVE_AFFINITY;
} else if (!strcmp(node->val, "balanced")) {
taf->mode_flag = BALANCED_AFFINITY;
} else {
FatalError(SC_ERR_FATAL, "unknown cpu_affinity node");
}
}
node = ConfNodeLookupChild(affinity->head.tqh_first, "threads");
if (node != NULL) {
// 线程数,结果从node->val转到taf->nb_threads中
if (StringParseUint32(&taf->nb_threads, 10, 0, (const char *)node->val) < 0) {
FatalError(SC_ERR_INVALID_ARGUMENT, "invalid value for threads "
"count: '%s'", node->val);
}
if (! taf->nb_threads) {
FatalError(SC_ERR_FATAL, "bad value for threads count");
}
}
}
#endif /* OS_WIN32 and __OpenBSD__ */
}
/**
* \brief Return next cpu to use for a given thread family
* \retval the cpu to used given by its id
*/
// 这段代码定义了一个名为 AffinityGetNextCPU 的函数,用于在线程关联类型 (ThreadsAffinityType) 的上下文中获取下一个可用的 CPU 编号。
// 这个函数可以用于设置线程的 CPU 亲和性,确保线程在特定的 CPU 上运行。
int AffinityGetNextCPU(ThreadsAffinityType *taf)
{
// 变量初始化: 初始化变量 ncpu 和 iter,分别用于存储当前选择的 CPU 编号和迭代次数。
int ncpu = 0;
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
int iter = 0;
// 锁定亲和性结构: 使用 SCMutexLock 锁定亲和性结构的互斥锁,确保在多线程环境中对其进行安全访问。
SCMutexLock(&taf->taf_mutex);
// 查找下一个 CPU: 使用 while 循环来找到 CPU 集合中的下一个可用 CPU。
// 循环中的 CPU_ISSET 宏用于检查给定的 CPU 编号是否在 CPU 集合中。
// 如果不在,则增加 CPU 编号并继续循环。如果超过在线 CPU 的数量,则重新开始从第一个 CPU,并增加迭代计数。
ncpu = taf->lcpu;
while (!CPU_ISSET(ncpu, &taf->cpu_set) && iter < 2) {
ncpu++;
if (ncpu >= UtilCpuGetNumProcessorsOnline()) {
ncpu = 0;
iter++;
}
}
if (iter == 2) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "cpu_set does not contain "
"available cpus, cpu affinity conf is invalid");
}
// 更新亲和性结构: 更新 taf 结构的 lcpu 成员,以便下次调用该函数时从下一个 CPU 开始。
taf->lcpu = ncpu + 1;
if (taf->lcpu >= UtilCpuGetNumProcessorsOnline())
taf->lcpu = 0;
SCMutexUnlock(&taf->taf_mutex);
SCLogDebug("Setting affinity on CPU %d", ncpu);
#endif /* OS_WIN32 and __OpenBSD__ */
return ncpu;
}
主要结构:用于存储线程亲和性(thread affinity)的相关设置信息。
typedef struct ThreadsAffinityType_ {const char *name; // 一个指向字符串的指针,用于存储线程亲和性类型的名称。
uint8_t mode_flag; // 一个8位无符号整数,用于存储线程亲和性的模式标志。
int prio; // 一个整数,用于存储线程亲和性的优先级。
uint32_t nb_threads; // 一个32位无符号整数,用于存储线程数目。
SCMutex taf_mutex; // 一个名为 taf_mutex 的互斥锁,用于保护线程亲和性设置的访问。
uint16_t lcpu; /* use by exclusive mode */
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
cpu_set_t cpu_set; // 一个16位无符号整数,在独占模式(exclusive mode)下使用。
cpu_set_t lowprio_cpu; // 一个 CPU 集合,用于存储低优先级线程允许运行的 CPU。
cpu_set_t medprio_cpu; // 一个 CPU 集合,用于存储中优先级线程允许运行的 CPU。
cpu_set_t hiprio_cpu; // 一个 CPU 集合,用于存储高优先级线程允许运行的 CPU。
#endif
} ThreadsAffinityType;
主要函数:
1.AffinitySetupLoadFromConfig函数2.BuildCpusetWithCallback函数
通常情况下的cpu亲和性使用方法为:
#include
int main() {
cpu_set_t cpuset;
CPU_ZERO(&cpuset); // 清空 cpuset 集合
CPU_SET(0, &cpuset); // 将 CPU 0 加入 cpuset 集合
int result = sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
if (result == -1) {
perror("sched_setaffinity");
return 1;
}
return 0;
}
在上面的示例中,CPU_ZERO
用于初始化 cpu_set_t
数据结构并将其所有位清零。然后,使用 CPU_SET
将特定的CPU核心(在这里是CPU 0)添加到集合中。最后,使用 sched_setaffinity
将当前进程绑定到所设置的CPU核心。如果操作成功,函数将返回0,否则将返回-1。