0%

使用Cgroup设置Ubuntu实时进程和独占CPU

使用进程组将特定的CPU留给特定的进程执行

  • 以上使用的是cgroup v1,V2比较难用

  • 安装cgroup工具

    sudo apt install cgroup-tools

    (可能需要)将计算机的Cgroup版本切换到v1

  • 检查当前计算机使用的cgroup配置

  • bashmount | grep cgroup

    • 如果你看到 cgroup2,说明你的系统使用的是 cgroups v2
    • 切换:
      • sudo nano /etc/default/grub
      • GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"修改为GRUB_CMDLINE_LINUX_DEFAULT="systemd.unified_cgroup_hierarchy=0"
      • 更新grub并重启
      • sudo update-grub
      • sudo reboot
  • 给进程组分配适当的核心

    sudo cgcreate -g cpuset:/<专属CPU组名>
    sudo cgset -r cpuset.cpus="0,1" <专属CPU组名>
    sudo cgset -r cpuset.mems="0" <专属CPU组名>
  • 使用在该CPU组下执行进程

    sudo cgexec -g cpuset:/<专属CPU组名> <command>
  • 结果

    • picture 1

    • picture 2

    • 可见,虽然进程开启了4个线程,但是只占用了两个CPU核心,而且进程是两个两个执行的

  • 创建额外的进程组

    sudo cgcreate -g cpuset:/<其他进程共享剩余CPU核心的组>
    # 将其他CPU分配给这个进程组
    sudo cgset -r cpuset.cpus="2-11" <其他进程共享剩余CPU核心的组>
    sudo cgset -r cpuset.mems="0" <其他进程共享剩余CPU核心的组>
  • 将系统剩余的进程划归这个进程组管理

    sudo cgclassify -g cpuset:/<其他进程共享剩余CPU核心的组> $(pgrep -u $(whoami))
  • 此时直接使用命令行执行一个具有20个线程的程序

  • CPU使用情况如图

    • picture 3
    • 虽然其他核心都被占满仍然不够,但是不会影响上述两个核心
  • 也就是被上述进程独占的CPU不会受到任何影响

  • 关于sudo cgclassify -g cpuset:/<其他进程共享剩余CPU核心的组> $(pgrep -u $(whoami))的解释

    • $(pgrep -u $(whoami)) 将所有当前用户的进程 ID 列出来,并将这些进程传递给 cgclassify
    • 这样,cgclassify 会将这些进程添加到 <其他进程共享剩余CPU核心的组> cgroup 中。
    • 如果当前用户的所有进程都被分配到 <其他进程共享剩余CPU核心的组> cgroup 中,那么新创建的进程(由这些进程派生的子进程默认也会属于同一个 cgroup
    • 因此,执行上述命令之后,即使不显式的指定进程组创建的进程,也会在<其他进程共享剩余CPU核心的组>运行

      测试用的C语言程序

      #include <stdio.h>
      #include <pthread.h>
      #include <time.h>
      #include <stdlib.h>
      #include <sched.h>
      #include <unistd.h>
      #include <errno.h>
      #include <string.h>

      #define NUM_THREADS 20 // 线程数量
      #define ACC_RANGE 900000000

      typedef struct {
      long long start;
      long long end;
      long long sum;
      } ThreadData;

      // 计算时间差,以毫秒为单位
      long get_time_diff_ms(struct timespec start, struct timespec end) {
      return (end.tv_sec - start.tv_sec) * 1000 + (end.tv_nsec - start.tv_nsec) / 1000000;
      }

      long get_time_diff_ns(struct timespec start, struct timespec end) {
      return (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;
      }

      // 设置进程为实时进程的函数
      int set_realtime(int priority) {
      struct sched_param param;

      // 实时优先级范围通常为 1 到 99,99 为最高优先级
      if (priority < 1 || priority > 99) {
      fprintf(stderr, "Priority must be between 1 and 99.\n");
      return -1;
      }

      // 设置调度参数的优先级
      param.sched_priority = priority;

      // 将进程设置为 SCHED_FIFO 调度策略
      if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
      fprintf(stderr, "Failed to set realtime priority: %s\n", strerror(errno));
      return -1;
      }

      printf("Process is now running with real-time priority %d\n", priority);
      return 0;
      }

      // 累加函数,计算从 start 到 end 的累加和
      void* accumulateRange(void* arg) {
      ThreadData* data = (ThreadData*)arg;
      struct timespec startTime, endTime;
      // 记录开始时间
      clock_gettime(CLOCK_MONOTONIC, &startTime);
      data->sum = 0;
      for (long long i = data->start; i <= data->end; ++i) {
      data->sum += i;
      }
      printf("Thread calculating sum from %lld to %lld: %lld\n", data->start, data->end, data->sum);
      // 记录结束时间
      clock_gettime(CLOCK_MONOTONIC, &endTime);
      long elapsedTime = get_time_diff_ns(startTime, endTime);
      printf("Thread execution time: %ld (ns)\n", elapsedTime);
      pthread_exit(NULL);
      }

      int main() {
      long long rangeStart = 1; // 累加范围起始值
      long long rangeEnd = ACC_RANGE; // 累加范围终止值
      // int rangePerThread = (rangeEnd - rangeStart + 1) / NUM_THREADS;
      set_realtime(90);
      pthread_t threads[NUM_THREADS];
      ThreadData threadData[NUM_THREADS];

      struct timespec startTime, endTime;

      // 记录开始时间
      clock_gettime(CLOCK_MONOTONIC, &startTime);

      // 创建线程并分配累加范围
      for (int i = 0; i < NUM_THREADS; ++i) {
      threadData[i].start = rangeStart;
      threadData[i].end = rangeEnd;

      pthread_create(&threads[i], NULL, accumulateRange, (void*)&threadData[i]);
      }

      // 等待所有线程完成,并汇总结果
      long long totalSum = 0;
      for (int i = 0; i < NUM_THREADS; ++i) {
      pthread_join(threads[i], NULL);
      // totalSum += threadData[i].sum;
      }

      // 记录结束时间
      clock_gettime(CLOCK_MONOTONIC, &endTime);

      // 计算并输出总运行时间
      // long elapsedTime = get_time_diff_ms(startTime, endTime);
      // printf("Total sum from %d to %d: %d\n", rangeStart, rangeEnd, totalSum);
      // printf("Total execution time: %ld (ms)\n", elapsedTime);

      long elapsedTime = get_time_diff_ns(startTime, endTime);
      // printf("Total sum from %lld to %lld: %lld\n", rangeStart, rangeEnd, totalSum);
      printf("Total execution time: %ld (ns)\n", elapsedTime);

      return 0;
      }

      如何删除进程组cgroup

  • 查看当前的进程组结构systemd-cgls

    • picture 4
  • 查看进程组中所有进程

  • cat /sys/fs/cgroup/<group name>/cgroup.procs

  • 将所有进程移回根组之中sudo cgclassify -g cpuset:/ $(cat /sys/fs/cgroup/<group name>/cgroup.procs

  • 然后执行cat /sys/fs/cgroup/<group name>/cgroup.procs发现没有输出,证明当前组没有还在执行的进程了

  • 执行sudo rmdir /sys/fs/cgroup/<group name>删除对应的进程组

  • 然后查看ls /sys/fs/cgroup可见该进程组对应的CPU目录确实被删除了

python和C++如何将自己切换到某个组

python

  • 不要直接使用类似于os.system("sudo cgclassify -g cpuset:/<某个组> $$")
  • 因为os.system是启动了一个新的进程,可能无法正确切换当前的进程到某个组
  • 要使用os.system("sudo cgclassify -g cpuset:/ignite %d" % os.getpid())
  • 使用os.system("sudo cat /proc/self/cgroup | grep cpuset")查看在哪个组

C++

  • 示例代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>

    #define CGROUP_PATH "/sys/fs/cgroup/cpuset/<组名>/cgroup.procs"

    int main() {
    pid_t pid = getpid(); // 获取当前进程的 PID

    // 打开 cgroup.procs 文件
    int fd = open(CGROUP_PATH, O_WRONLY);
    if (fd == -1) {
    perror("Failed to open cgroup.procs");
    return 1;
    }

    // 将 PID 写入 cgroup.procs
    char pid_str[20];
    snprintf(pid_str, sizeof(pid_str), "%d", pid);
    if (write(fd, pid_str, sizeof(pid_str)) == -1) {
    perror("Failed to write PID to cgroup.procs");
    close(fd);
    return 1;
    }

    close(fd);
    printf("Process %d moved to ignite cgroup.\n", pid);

    // 显示当前进程的 cgroup 信息
    system("cat /proc/self/cgroup | grep cpuset");

    return 0;
    }