目录
- 主要机制
- 测试功能
- 测试1
- 测试2
- 测试3
主要机制
测试功能
测试1
用户态测试-task1是主进程,先创建mygroup组,将task1的pid号加入到mygroup/cgroup.procs中,用task1调用system启动子进程task2,task2再调用system启动它的子进程task3,最后观察mygroup/cgroup.procs,可以发现task2和task3的pid被自动加入其中,证明了在用户态cgroup机制的可行性
/*---------------------------task1.c------------------------------*/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>#define CGROUP_PATH "/sys/fs/cgroup/mygroup"int add_pid_to_cgroup(pid_t pid) {char path[256];snprintf(path, sizeof(path), "%s/cgroup.procs", CGROUP_PATH);FILE *fp = fopen(path, "w");if (!fp) {perror("Failed to open cgroup.procs");return -1;}fprintf(fp, "%d", pid);fclose(fp);return 0;
}int main(){pid_t pid = getpid();printf("task1 PID: %d\n", pid);// 把主PID加入cgroupif (add_pid_to_cgroup(pid) != 0) {fprintf(stderr, "Failed to add main pid to cgroup\n");return -1;}sleep(5);printf("执行task2\n");system("./task2"); //system()自带同步阻塞机制,不用担心task2和task1进程会争抢cpureturn 0;
}/*------------------------------------task2.c---------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>int main(){pid_t pid = getpid();printf("task2 PID: %d\n", pid);sleep(5);printf("执行task3\n");system("./task3");//system(task2);
}/*-----------------------------------------------task3.c-------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>int main(){pid_t pid = getpid();printf("task3 PID: %d\n", pid);sleep(5);printf("结束\n");//system(task2);
}
/*----------------------------------------monitor.c----------------------------------*/
//用于监控/sys/fs/cgroup/mygroup/cgroup.procs#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#define CGROUP_PROCS_PATH "/sys/fs/cgroup/mygroup/cgroup.procs"void print_cgroup_procs() {FILE *fp = fopen(CGROUP_PROCS_PATH, "r");if (!fp) {perror("Failed to open cgroup.procs");return;}printf("=== cgroup.procs snapshot ===\n");int pid;while (fscanf(fp, "%d", &pid) == 1) {printf("PID in cgroup: %d\n", pid);}fclose(fp);printf("=============================\n\n");
}int main() {printf("Monitoring %s\n", CGROUP_PROCS_PATH);while (1) {print_cgroup_procs();sleep(1); // 每秒打印一次}return 0;
}
测试结果,taks2和task3自动继承到mygroup中,pid自动写入了文件/sys/fs/cgroup/mygroup/cgroup.procs
中
测试2
用户态测试-task1是主进程,先创建mygroup组,将task1的pid号加入到mygroup/cgroup.procs中,然后在线程中启动task2,观察mygroup/cgroup.procs,其中并没有task2的pid号,因为线程启动的进程pid和之前task1的是同一个,如果观察mygroup/cgroup.threads是有task2的启动记录的
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>#define CGROUP_PATH "/sys/fs/cgroup/mygroup"/* ---------- 把 task2 的代码改造成线程函数 ---------- */
void* task2_thread(void* arg)
{pid_t pid = getpid(); // 注意:线程里 PID 与主线程相同printf("task2 thread running, PID: %d TID: %ld\n",pid, (long)pthread_self());sleep(20);return NULL;
}
/* -------------------------------------------------- */int add_pid_to_cgroup(pid_t pid)
{char path[256];snprintf(path, sizeof(path), "%s/cgroup.procs", CGROUP_PATH);FILE *fp = fopen(path, "w");if (!fp) {perror("Failed to open cgroup.procs");return -1;}fprintf(fp, "%d", pid);fclose(fp);return 0;
}int main(void)
{pid_t pid = getpid();printf("task1 PID: %d\n", pid);if (add_pid_to_cgroup(pid) != 0) {fprintf(stderr, "Failed to add main pid to cgroup\n");return EXIT_FAILURE;}sleep(5);printf("创建线程执行 task2 逻辑...\n");pthread_t tid;if (pthread_create(&tid, NULL, task2_thread, NULL) != 0) {perror("pthread_create");return EXIT_FAILURE;}pthread_join(tid, NULL); // 主线程等待 task2 线程结束return 0;
}
测试结果:如果是主进程用线程启动一个任务,这个新任务的pid和主进程的pid是一样的,所以procs文件中没有新的pid号,但是threads文件中会有新任务的tid号
测试3
测试步骤:
- 1.用户态下,手动创建目录/sys/fs/cgroup/mygroup/ 因为尝试了程序里面调用api创建mygroup,失败了,好多api是v1层级下使用的,也有好多函数没有导出符号给我们使用
- 2.用cgroup_attach_task将主进程加入到mygroup,并且打印自身pid以及找出自身所在的cgroup的相对路径
- 3.然后打印procs里面的内容
- 4.kthread打印自身所在cgroup的相对路径
- 5.kthread调用cgroup_attach_task将自己加入主进程组中,再次打印相对路径
- 6.再次打印procs里面的内容
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/cgroup.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/kernel_read_file.h>#define BUF_LEN 256
#define NEW_CGROUP_PATH "mygroup" /* 相对于根,不含前导 / *//* ---------- 回显 cgroup.procs ---------- */static void show_procs(void)
{struct file *filp;loff_t pos = 0;char buf[256];ssize_t ret;filp = filp_open("/sys/fs/cgroup/mygroup/cgroup.procs",O_RDONLY, 0);if (IS_ERR(filp)) {pr_err("open cgroup.procs failed: %ld\n", PTR_ERR(filp));return;}ret = kernel_read(filp, buf, sizeof(buf) - 1, &pos);if (ret >= 0) {buf[ret] = '\0';pr_info("cgroup.procs: %s", buf);}filp_close(filp, NULL);
}/* ---------- 子线程 ---------- */
static int child_fn(void *arg)
{char *buf;struct task_struct *parent = arg;buf = kmalloc(BUF_LEN, GFP_KERNEL);if (!buf)return -ENOMEM;cgroup_path(current->cgroups->dfl_cgrp, buf, BUF_LEN);pr_info("child[%d] (before): %s\n", current->pid, buf);show_procs();if (!cgroup_attach_task(parent->cgroups->dfl_cgrp, current, false))pr_info("child[%d] moved\n", current->pid);cgroup_path(current->cgroups->dfl_cgrp, buf, BUF_LEN);pr_info("child[%d] (after): %s\n", current->pid, buf);show_procs();kfree(buf);return 0;
}/* ---------- 初始化 ---------- */
static int __init whoami_init(void)
{struct cgroup *cgrp;char *buf;/* 1. 用户态必须提前建好 /sys/fs/cgroup/mygroup */cgrp = cgroup_get_from_path(NEW_CGROUP_PATH);if (IS_ERR(cgrp)) {pr_err("cannot find %s (%ld)\n", NEW_CGROUP_PATH, PTR_ERR(cgrp));return PTR_ERR(cgrp);}/* 2. 迁移主进程 */if (!cgroup_attach_task(cgrp, current, false))pr_info("parent[%d] moved to %s\n", current->pid, NEW_CGROUP_PATH);cgroup_put(cgrp);buf = kmalloc(BUF_LEN, GFP_KERNEL);if (buf) {cgroup_path(current->cgroups->dfl_cgrp, buf, BUF_LEN);pr_info("parent[%d] now in: %s\n", current->pid, buf);show_procs();kfree(buf);}/* 3. 启动子线程 */kthread_run(child_fn, current, "whoami_child");return 0;
}static void __exit whoami_exit(void)
{pr_info("module unloaded\n");
}module_init(whoami_init);
module_exit(whoami_exit);
MODULE_LICENSE("GPL");
将编译好的模块加入到内核中,测试结果如下:
sudo dmesg | tail[ 2875.672622] parent[4779] moved to mygroup
[ 2875.672629] parent[4779] now in: /mygroup
[ 2875.672670] cgroup.procs: 4779
[ 2875.672890] child[4780] (before): /
[ 2875.672910] cgroup.procs: 4779
[ 2875.672934] child[4780] moved
[ 2875.672935] child[4780] (after): /mygroup
[ 2875.672948] cgroup.procs: 47794780
可以看到,一开始procs里面只有parent进程的pid,后面在把child拉进mygroup之前,child默认加入的cgroup组是根目录"/sys/fs/cgroup/",在使用cgroup_attach_task
函数将child拉进mygroup之后,procs里面变成了parent和child的两个进程pid