控制核心分配:利用CPU亲和性最大化速度和效率
一、相关函数
函数原型:
#include <unistd.h>
long sysconf(int name);
描述:
POSIX允许应用程序在编译或运行时测试是否支持某些选项,或者测试某些可配置常量或限制的值。
在编译时,这是通过包含<unistd.h>和、或<limits.h>并测试某些宏的值来完成的。
在运行时,可以使用当前函数sysconf()请求数值。可以通过调用fpathconf和pathconf来请求可能取决于文件所在的文件系统的数值。可以使用confstr请求字符串值。
从这些函数获得的值是系统配置常数。它们在过程的生命周期内不会改变。
对于选项,通常有一个常量_POSIX_FOO,可以在<unistd.h>中定义。
- 如果未定义,则应在运行时询问。
- 如果定义为-1,则不支持该选项。
- 如果将其定义为0,则存在相关函数和标头,但必须在运行时询问可用的支持程度。
- 如果将其定义为-1或0以外的值,则支持该选项。
- 通常,该值(如200112L)表示描述该选项的POSIX修订的年份和月份。只要POSIX版本尚未发布,Glibc就使用值1表示支持。sysconf()参数将是_SC_FOO。
对于变量或限制,通常有一个常量_FOO,可能在<limit.h>中定义 或者_POSIX_FOO,可以在<unistd.h>中定义。如果未指定限制,则不会定义常数。如果定义了常量,它会给出一个保证值,并且实际上可能支持更大的值。如果应用程序希望利用可能在
在系统中,可以调用sysconf()。sysconf()参数将是_SC_FOO。
参数:name可以参看系统调用的宏定义,比如查阅CPU数量的宏为_SC_NPROCESSORS_CONF
返回值:
- 如果名称无效,则返回-1,并将errno设置为EINVAL。
- 否则,返回的值是系统资源的值,errno不会更改。
- 对于选项,如果查询的选项可用,则返回正值,如果不可用,则为-1。在限制的情况下,-1表示没有明确的限制。
函数原型:
#include <unistd.h>
pid_t fork(void);
描述:
fork()通过复制调用进程创建一个新进程。新进程称为子进程。调用进程称为父进程。
子进程和父进程在单独的内存空间中运行。在fork()时,两个内存空间具有相同的内容。其中一个进程执行的内存写入、文件映射【mmap()】和取消映射【munmap()】,不会影响另一个进程。
返回值:
成功时,子进程的PID在父进程中返回,0在子进程中返回。失败时,在父进程中返回-1,不创建子进程,并适当设置errno。
错误:
错误代码 | 含义 |
---|---|
EAGAIN | 遇到系统对线程数量施加的限制。可能触发此错误的限制有很多:已达到RLIMIT_NPROC软资源限制(通过setrlimit()设置),该限制限制了真实用户ID的进程和线程数;已达到内核对进程和线程数的系统范围限制,即/proc/sys/kernel/threads max(请参阅proc());或者达到最大pid数/proc/sys/kernel/pid_max(见proc())。 |
EAGAIN | 调用方在SCHED_DELADATE调度策略下运行,并且未设置reset on fork标志。 |
ENOMEM | fork()无法分配必要的内核结构,因为内存紧张。 |
ENOSYS | 此平台不支持fork()(例如,没有内存管理单元的硬件)。 |
函数原型:
#include <sys/types.h>
pid_t gettid(void);
注意:此系统调用没有glibc包装器。
描述:
返回调用方的线程ID(TID)。在单线程进程中,线程ID等于进程ID(PID,由getpid()返回)。在多线程进程中,所有线程都具有相同的PID,但每个线程都具有唯一的TID。
返回值:
总是成功的,返回调用进程的线程ID。
函数原型:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h> /* For SYS_xxx definitions */
long syscall(long number, ...);
描述:
syscall()是一个小型库函数,用于调用系统调用,该系统调用的汇编语言接口具有指定数量和指定参数。例如,当调用C库中没有包装函数的系统调用时,使用syscall()非常有用。
syscall()在进行系统调用之前保存CPU寄存器,在系统调用返回时恢复寄存器,如果发生错误,则将系统调用返回的任何错误代码存储在errno中。
系统调用号的符号常量可以在头文件<sys/syscall.h>中找到。
返回值:
返回值由调用的系统调用定义。通常,0返回值表示成功。返回值-1表示错误,错误代码存储在errno中。
使用示例:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <signal.h>
int
main(int argc, char *argv[])
{
pid_t tid;
tid = syscall(SYS_gettid);
tid = syscall(SYS_tgkill, getpid(), tid, SIGHUP);
}
函数原型:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
void CPU_ZERO(cpu_set_t *set);
void CPU_SET(int cpu, cpu_set_t *set);
void CPU_CLR(int cpu, cpu_set_t *set);
int CPU_ISSET(int cpu, cpu_set_t *set);
int CPU_COUNT(cpu_set_t *set);
void CPU_AND(cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
void CPU_OR(cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
void CPU_XOR(cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
int CPU_EQUAL(cpu_set_t *set1, cpu_set_t *set2);
cpu_set_t *CPU_ALLOC(int num_cpus);
void CPU_FREE(cpu_set_t *set);
size_t CPU_ALLOC_SIZE(int num_cpus);
void CPU_ZERO_S(size_t setsize, cpu_set_t *set);
void CPU_SET_S(int cpu, size_t setsize, cpu_set_t *set);
void CPU_CLR_S(int cpu, size_t setsize, cpu_set_t *set);
int CPU_ISSET_S(int cpu, size_t setsize, cpu_set_t *set);
int CPU_COUNT_S(size_t setsize, cpu_set_t *set);
void CPU_AND_S(size_t setsize, cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
void CPU_OR_S(size_t setsize, cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
void CPU_XOR_S(size_t setsize, cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
int CPU_EQUAL_S(size_t setsize, cpu_set_t *set1, cpu_set_t *set2);
描述:
cpu_set_t数据结构表示一组CPU。sched_setaffinity()和类似接口使用CPU集。
cpu_set_t数据类型实现为位掩码。然而,被视为不透明的数据结构:所有CPU集的操作都应通过以下中描述的宏完成。
提供以下宏用于在CPU集上操作:
宏 | 含义 |
---|---|
CPU_ZERO | 清除集合,使其不包含CPU。 |
CPU_SET | 将CPU添加到设置。 |
CPU_CLR | 从集合中删除CPU和CPU。 |
CPU_ISSET | 测试CPU CPU是否是集合的成员。 |
CPU_COUNT | 返回集合中的CPU数量。 |
CPU_AND | 将集合srcset1和srcset2的交集存储在destset中(可能是源集合之一)。 |
CPU_OR | 将集合srcset1和srcset2的并集存储在destset中(可能是源集合之一)。 |
CPU_XOR | 将集合srcset1和srcset2的XOR存储在destset(可能是源集合之一)中。XOR表示srcset1或srcset2中的一组CPU,但不是两个CPU。 |
CPU_EQUAL | 测试两个CPU集是否包含完全相同的CPU。 |
以下宏用于分配和解除分配CPU集:
宏 | 含义 |
---|---|
CPU_ALLOC | 分配一个足够大的CPU集,以容纳范围为0到num_CPUs-1的CPU。 |
CPU_ALLOC_SIZE | 返回CPU集的大小(以字节为单位),该大小将用于保存范围为0到num_CPUs-1的CPU。该宏提供了可用于下面描述的CPU_*_S()宏中的setsize参数的值。 |
CPU_FREE | 释放先前由CPU_ALLOC()分配的CPU集。 |
ched_setaffinity():设置线程的CPU关联掩码
sched_ getaffinity():获取线程的CPU关联掩码
函数原型:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize,cpu_set_t *mask);
描述:
线程的CPU关联掩码决定了它有资格运行的CPU集。在多处理器系统上,设置CPU关联掩码可用于获得性能优势。
例如,通过将一个CPU专用于特定线程(即,将该线程的关联掩码设置为指定单个CPU,并将所有其他线程的关联屏蔽设置为排除该CPU),可以确保该线程的最大执行速度。将线程限制为在单个CPU上运行还可以避免因缓存失效而导致的性能成本,当线程停止在一个CPU上执行,然后在另一个CPU重新开始执行时,会发生缓存失效。
CPU亲和性掩码由cpu_set_t结构表示,即“CPU集”,由掩码指向。CPU_SET()中描述了一组用于操作CPU集的宏。
sched_setaffinity()将ID为pid的线程的CPU关联掩码设置为掩码指定的值。如果pid为零,则使用调用线程。参数cpusetsize是掩码指向的数据的长度(以字节为单位)。通常,此参数将指定为sizeof(cpu_set_t)。
如果pid指定的线程当前未在掩码中指定的一个CPU上运行,则该线程将迁移到掩码中指定一个CPU。
sched_getaffinity()将ID为pid的线程的关联掩码写入掩码指向的cpu_set_t结构。cpusetsize参数指定掩码的大小(以字节为单位)。如果pid为零,则返回调用线程的掩码。
返回值:
- 成功时返回0。
- 失败时返回-1,并适当设置errno。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h>
void process_affinity(int num)
{
// pid_t self_id = gettid();
pid_t self_id = syscall(__NR_gettid);
cpu_set_t mask;
CPU_ZERO(&mask);// 初始化为0
// 绑定哪个CPU就将那个bit置 1
CPU_SET(self_id%num, &mask);
// 黏合
sched_setaffinity(self_id,sizeof(mask),&mask);
/*以下为业务逻辑*/
while (1) ;//让CPU爆满,方便看效果
}
int main(int argc, char **argv)
{
int num = sysconf(_SC_NPROCESSORS_CONF);
printf("CPU number: %d\n", num);
int i = 0;
pid_t pid = 0;
for (i = 0; i < num / 2; i++)
{
pid = fork();
if (pid == 0)
break;
}
if (pid == 0) {
process_affinity(num);
}
else{
while (1) usleep(1);// 让主线程做切换,不然执行主线程的CPU也会爆满
}
return 0;
}
CPU亲缘性/粘合,是进程或线程只运行在所设置的CPU上,而不是CPU只运行设置的线程或进程。
进程或线程创建的时候,其实是在内核中创建了一个task_struct数据结构,然后等待内核的任务调度器调度执行。
欢迎关注公众号《Lion 莱恩呀》学习技术,每日推送文章。
- 点赞
- 收藏
- 关注作者
评论(0)