线程与cpu进行绑定——006非常全面

本文介绍了如何通过设置线程亲和性来绑定线程到特定的CPU核心上,以减少线程在多核间的切换,提高嵌入式设备上的应用性能。文中详细解释了使用`pthread_setaffinity_np`和`pthread_getaffinity_np`函数的方法,并提供了一个测试代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.youkuaiyun.com/jxnu_xiaobing/article/details/44955613



最近在对项目进行性能优化,由于在多核平台上,所以了解了些进程、线程绑定cpu核的问题,在这里将所学记录一下。
不管是线程还是进程,都是通过设置亲和性(affinity)来达到目的。对于进程的情况,一般是使用sched_setaffinity这个函数来实现,网上讲的也比较多,这里主要讲一下线程的情况。
与进程的情况相似,线程亲和性的设置和获取主要通过下面两个函数来实现:
    int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
const cpu_set_t *cpuset);
    int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, 
cpu_set_t *cpuset);
  • 1
  • 2
  • 3
  • 4
从函数名以及参数名都很明了,唯一需要点解释下的可能就是cpu_set_t这个结构体了。这个结构体的理解类似于select中的fd_set,可以理解为cpu集,也是通过约定好的宏来进行清除、设置以及判断:
   //初始化,设为空
      void CPU_ZERO (cpu_set_t *set); 
      //将某个cpu加入cpu集中 
       void CPU_SET (int cpu, cpu_set_t *set); 
       //将某个cpu从cpu集中移出 
       void CPU_CLR (int cpu, cpu_set_t *set); 
       //判断某个cpu是否已在cpu集中设置了 
       int CPU_ISSET (int cpu, const cpu_set_t *set); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
cpu集可以认为是一个掩码,每个设置的位都对应一个可以合法调度的 cpu,而未设置的位则对应一个不可调度的 CPU。换而言之,线程都被绑定了,只能在那些对应位被设置了的处理器上运行。通常,掩码中的所有位都被置位了,也就是可以在所有的cpu中调度。       
  以下为测试代码:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>

void *myfun(void *arg)
{
    cpu_set_t mask;
    cpu_set_t get;
    char buf[256];
    int i;
    int j;
    int num = sysconf(_SC_NPROCESSORS_CONF);
    printf("system has %d processor(s)\n", num);

    for (i = 0; i < num; i++) {
        CPU_ZERO(&mask);
        CPU_SET(i, &mask);
        if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) {
            fprintf(stderr, "set thread affinity failed\n");
        }
        CPU_ZERO(&get);
        if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0) {
            fprintf(stderr, "get thread affinity failed\n");
        }
        for (j = 0; j < num; j++) {
            if (CPU_ISSET(j, &get)) {
                printf("thread %d is running in processor %d\n", (int)pthread_self(), j);
            }
        }
        j = 0;
        while (j++ < 100000000) {
            memset(buf, 0, sizeof(buf));
        }
    }
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, (void *)myfun, NULL) != 0) {
        fprintf(stderr, "thread create failed\n");
        return -1;
    }
    pthread_join(tid, NULL);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
 gcc test.c -o test -lpthread
  • 1
这段代码将使myfun线程在所有cpu中依次执行一段时间,在我的四核cpu上,执行结果为  :
       system has 4 processor(s)        
       thread 1095604544 is running in processor 0        
       thread 1095604544 is running in processor 1        
       thread 1095604544 is running in processor 2        
       thread 1095604544 is running in processor 3 
  • 1
  • 2
  • 3
  • 4
  • 5
在一些嵌入式设备中,运行的进程线程比较单一,如果指定进程线程运行于特定的cpu核,减少进程、线程的核间切换,有可能可以获得更高的性能。

<think>我们正在处理用户的问题:如何将设备驱动绑定到指定CPU核心(Linux和Windows)根据用户提供的引用,我们主要关注Linux部分,因为引用中提供了Linux下的方法(Windows部分可能需要另外的知识,但用户要求中提到了Windows,所以需要补充)在Linux中,我们可以通过以下方式绑定进程(包括驱动程序)到特定的CPU核心:1.使用taskset命令:这是Linux下常用的工具,可以设置或查看进程的CPU亲和性(affinity)。2.使用cset命令:这是一个更高级的工具,可以用于管理cgroup,从而更精细地控制CPU分配。3.在程序代码中设置:通过调用系统函数(如sched_setaffinity)来实现。另外,对于设备中断的绑定,用户引用[3]提到了smp_affinity,这是一个设置中断亲和性的方法。但是,用户的问题是关于“设备驱动”的绑定。这里需要区分:-驱动程序的进程(内核线程)的绑定-设备中断的绑定通常,驱动程序可能包含内核线程,这些线程可以绑定到特定CPU。同时,设备产生的中断也可以绑定到特定CPU。因此,我们分两部分回答:Linux下驱动程序线程绑定和中断的绑定,以及Windows下的方法。由于用户引用中主要涉及Linux,且没有提供Windows的引用,所以Windows部分需要依靠我们的知识库。步骤:1.Linux下绑定驱动程序线程到指定CPU核心:-使用taskset命令:例如,将进程ID为1234的进程绑定CPU0和CPU1上:`taskset-p0x31234`(0x3是掩码,代表CPU0和CPU1)-在代码中:使用sched_setaffinity函数(在C/C++中)。2.Linux下绑定设备中断到指定CPU核心:-查看中断号:`cat/proc/interrupts`-设置中断的smp_affinity:例如,将中断号为55的中断绑定CPU0和CPU1:`echo3>/proc/irq/55/smp_affinity`(注意:这里的3是十进制还是十六进制?实际上,/proc/irq/55/smp_affinity文件需要的是十六进制掩码,但写入时可以直接写十六进制数或十进制数?实际上,内核文档说明这是一个位掩码,通常用十六进制表示,但在写入时也可以使用十进制。不过,为了准确,我们通常使用十六进制。但注意,在echo命令中,我们可以直接写十进制数,因为内核会将其解释为位掩码。但更常见的是使用十六进制前缀0x。然而,在写入/proc文件时,我们通常不写0x,而是直接写十六进制数字字符串。但实际测试发现,写入十进制和十六进制都可以,因为内核会按位掩码解析。例如,写入3(十进制)表示二进制0011,即使用CPU0和CPU1。而写入0x3也是同样的效果。但是,为了清晰,建议使用十六进制。但注意:该文件在读取时显示的是十六进制,而写入时可以使用十进制或十六进制(不带0x)?实际上,写入时直接写数字(可以是十进制、八进制或十六进制,但通常用十进制或十六进制)。更安全的做法是使用十六进制,因为掩码可能很长。例如,对于8核CPU,要绑定CPU3(从0开始),掩码是8(即1000二进制),可以写8(十进制)或者8(十六进制)?实际上,写入8(十进制)和0x8(十六进制字符串)都可以,但是写入文件时不能带0x,所以直接写8(十六进制)是不行的,因为8在字符串中就是数字8。所以,我们通常写十进制数。但注意:掩码的每一位对应一个CPU,所以第0位对应CPU0,第1位对应CPU1,以此类推。例如,绑定CPU3,掩码是8(十进制)=1000二进制。3.Windows下绑定进程到指定CPU核心:-使用任务管理器:在“详细信息”选项卡中,右键点击进程,选择“设置相关性”。-使用命令行工具:可以使用PowerShell命令(例如,使用Get-Process和Set-ProcessAffinity)或者使用Start命令的/Affinity参数(例如:start/Affinity0x1notepad.exe,表示将记事本绑定CPU0)。-在代码中:使用SetProcessAffinityMask函数。但是,用户的问题是关于设备驱动的绑定。在Windows中,设备驱动运行在内核模式,通常由系统管理,用户一般不能直接设置设备驱动的CPU亲和性。但可以设置设备中断的亲和性吗?在Windows中,中断的亲和性可以通过设备管理器的“高级”设置来调整(如果设备支持)。另外,对于驱动程序创建的线程,可以在驱动代码中调用KeSetSystemAffinityThread函数来设置。因此,我们回答需要分清楚是驱动程序的线程还是中断。由于用户问题没有明确,我们提供两部分的信息。回答结构:1.Linux下设备驱动线程CPU绑定2.Linux下设备中断的CPU绑定3.Windows下设备驱动线程CPU绑定(可能需要在驱动代码中实现)4.Windows下设备中断的CPU绑定(通过设备管理器)注意:在Linux中,驱动程序通常以内核线程的形式运行,因此我们可以像绑定普通进程一样绑定它们(使用taskset或代码)。但在Windows中,驱动程序的线程设置需要在内核编程时完成。我们根据以上思路组织回答。同时,根据系统指令,我们需要生成相关问题。注意:用户引用[1]提到使用isolcpus参数启动内核,这样指定的CPU就不会被调度器使用,只能通过绑定来使用。这是提高绑定效果的一种方法。下面开始回答:</think>### 如何将设备驱动绑定到指定CPU核心(Linux/Windows) #### **Linux 系统实现** ##### 1. **驱动线程绑定** 设备驱动的内核线程可通过 **`taskset` 命令**或 **编程接口** 绑定: - **`taskset` 命令** 示例(将进程绑定CPU0 和 CPU1): ```bash taskset -cp 0,1 <驱动进程PID> # 查看PID: ps aux | grep 驱动名 ``` - 掩码格式:`taskset -p 0x3 <PID>`(`0x3` 对应二进制 `0011`,绑定 CPU0 和 CPU1)[^1][^2]。 - **编程实现(C/C++)** 在驱动代码中使用 `sched_setaffinity`: ```c #include <sched.h> cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); // 绑定CPU0 sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); ``` ##### 2. **设备中断绑定** 通过 **`smp_affinity`** 设置中断的 CPU 亲和性: 1. 查询设备中断号: ```bash cat /proc/interrupts | grep <设备名> ``` 2. 绑定中断到 CPU(例如绑定CPU0): ```bash echo 1 > /proc/irq/<中断号>/smp_affinity # 1 对应二进制 0001(CPU0) ``` - 掩码规则:十六进制位掩码,如 `0xF`(二进制 `1111`)绑定到所有 CPU[^3]。 ##### 3. **高级隔离(`isolcpus`)** 启动内核时隔离 CPU 核心,禁止系统调度: ```bash # 在 GRUB 配置中添加 GRUB_CMDLINE_LINUX="isolcpus=2,3" # 隔离 CPU2 和 CPU3 ``` 隔离后需手动绑定驱动进程到隔离核心[^1]。 --- #### **Windows 系统实现** ##### 1. **驱动线程绑定** 需在驱动代码中调用 **`KeSetSystemAffinityThread`**: ```c #include <wdm.h> KAFFINITY affinity = 1; // 二进制 0001,绑定CPU0 KeSetSystemAffinityThread(affinity); ``` ##### 2. **设备中断绑定** 通过设备管理器设置: 1. 打开 **设备管理器** → 目标设备 → **属性** → **资源** 选项卡。 2. 修改 **中断亲和性**(需硬件支持,部分设备不可调)。 ##### 3. **进程绑定(用户层工具)** - **任务管理器**: 进程详情页 → 右键 **设置相关性** → 选择 CPU。 - **命令行**: ```cmd start /Affinity 0x1 <驱动进程.exe> # 0x1 绑定CPU0 ``` --- #### **关键注意事项** 1. **权限要求**:Linux 需 `root`;Windows 需管理员权限。 2. **硬件支持**:中断绑定需设备驱动支持。 3. **性能影响**: - 减少 CPU 缓存失效,提升性能[^2]。 - 过度绑定可能导致负载不均衡。 4. **调试工具**: - Linux:`top -H` 或 `htop`(按 `F2` 显示 CPU 亲和性)。 - Windows:Process Explorer(查看线程亲和性)。 --- ### 相关问题 1. 如何验证 Linux 中 CPU 绑定是否生效? 2. Windows 驱动开发中如何动态切换线程CPU 亲和性? 3. 绑定设备中断到特定 CPU 有哪些实际应用场景? 4. 在多核系统中,CPU 隔离(`isolcpus`)对实时性任务有何优势? [^1]: Linux性能优化(十五)——CPU绑定。一旦Linux Kernel使用isolcpus参数启动,Linux Kernel任务均衡调度器不会再将进程调度给指定CPU核心。 [^2]: Linux 中用c++实现线程绑定CPUCPU的亲和性,就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器。 [^3]: Linux将中断绑定到指定多个CPU。注意smp_affinity是一个十六进制的bitmask,它和CPU序列的“”运算结果决定中断亲和性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值