mirror of
https://github.com/checkpoint-restore/criu
synced 2025-08-28 12:57:57 +00:00
cgroup-v2: Dump cgroup controllers of every threads in a process
Currently, we assume all threads in process are in the same cgroup controllers. However, with threaded controllers, threads in a process may be in different controllers. So we need to dump cgroup controllers of every threads in process and fixup the procfs cgroup parsing to parse from self/task/<tid>/cgroup. Signed-off-by: Bui Quang Minh <minhquangbui99@gmail.com>
This commit is contained in:
parent
ad3936e81e
commit
17d1d8810e
@ -174,6 +174,7 @@ struct cg_controller *new_controller(const char *name)
|
|||||||
nc->n_controllers = 1;
|
nc->n_controllers = 1;
|
||||||
|
|
||||||
nc->n_heads = 0;
|
nc->n_heads = 0;
|
||||||
|
nc->is_threaded = false;
|
||||||
INIT_LIST_HEAD(&nc->heads);
|
INIT_LIST_HEAD(&nc->heads);
|
||||||
|
|
||||||
return nc;
|
return nc;
|
||||||
@ -371,7 +372,8 @@ static void free_all_cgroup_props(struct cgroup_dir *ncd)
|
|||||||
ncd->n_properties = 0;
|
ncd->n_properties = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dump_cg_props_array(const char *fpath, struct cgroup_dir *ncd, const cgp_t *cgp)
|
static int dump_cg_props_array(const char *fpath, struct cgroup_dir *ncd, const cgp_t *cgp,
|
||||||
|
struct cg_controller *controller)
|
||||||
{
|
{
|
||||||
int j;
|
int j;
|
||||||
char buf[PATH_MAX];
|
char buf[PATH_MAX];
|
||||||
@ -422,6 +424,13 @@ static int dump_cg_props_array(const char *fpath, struct cgroup_dir *ncd, const
|
|||||||
prop->value = new;
|
prop->value = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the is_threaded flag if cgroup.type's value is threaded,
|
||||||
|
* ignore all other values.
|
||||||
|
*/
|
||||||
|
if (!strcmp("cgroup.type", prop->name) && !strcmp("threaded", prop->value))
|
||||||
|
controller->is_threaded = true;
|
||||||
|
|
||||||
pr_info("Dumping value %s from %s/%s\n", prop->value, fpath, prop->name);
|
pr_info("Dumping value %s from %s/%s\n", prop->value, fpath, prop->name);
|
||||||
list_add_tail(&prop->list, &ncd->properties);
|
list_add_tail(&prop->list, &ncd->properties);
|
||||||
ncd->n_properties++;
|
ncd->n_properties++;
|
||||||
@ -437,7 +446,7 @@ static int add_cgroup_properties(const char *fpath, struct cgroup_dir *ncd, stru
|
|||||||
for (i = 0; i < controller->n_controllers; ++i) {
|
for (i = 0; i < controller->n_controllers; ++i) {
|
||||||
const cgp_t *cgp = cgp_get_props(controller->controllers[i]);
|
const cgp_t *cgp = cgp_get_props(controller->controllers[i]);
|
||||||
|
|
||||||
if (dump_cg_props_array(fpath, ncd, cgp) < 0) {
|
if (dump_cg_props_array(fpath, ncd, cgp, controller) < 0) {
|
||||||
pr_err("dumping known properties failed\n");
|
pr_err("dumping known properties failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -445,12 +454,12 @@ static int add_cgroup_properties(const char *fpath, struct cgroup_dir *ncd, stru
|
|||||||
|
|
||||||
/* cgroup v2 */
|
/* cgroup v2 */
|
||||||
if (controller->controllers[0][0] == 0) {
|
if (controller->controllers[0][0] == 0) {
|
||||||
if (dump_cg_props_array(fpath, ncd, &cgp_global_v2) < 0) {
|
if (dump_cg_props_array(fpath, ncd, &cgp_global_v2, controller) < 0) {
|
||||||
pr_err("dumping global properties v2 failed\n");
|
pr_err("dumping global properties v2 failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dump_cg_props_array(fpath, ncd, &cgp_global) < 0) {
|
if (dump_cg_props_array(fpath, ncd, &cgp_global, controller) < 0) {
|
||||||
pr_err("dumping global properties failed\n");
|
pr_err("dumping global properties failed\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -735,9 +744,9 @@ static int collect_cgroups(struct list_head *ctls)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dump_task_cgroup(struct pstree_item *item, u32 *cg_id, struct parasite_dump_cgroup_args *args)
|
int dump_thread_cgroup(const struct pstree_item *item, u32 *cg_id, struct parasite_dump_cgroup_args *args, int id)
|
||||||
{
|
{
|
||||||
int pid;
|
int pid, tid;
|
||||||
LIST_HEAD(ctls);
|
LIST_HEAD(ctls);
|
||||||
unsigned int n_ctls = 0;
|
unsigned int n_ctls = 0;
|
||||||
struct cg_set *cs;
|
struct cg_set *cs;
|
||||||
@ -750,8 +759,13 @@ int dump_task_cgroup(struct pstree_item *item, u32 *cg_id, struct parasite_dump_
|
|||||||
else
|
else
|
||||||
pid = getpid();
|
pid = getpid();
|
||||||
|
|
||||||
pr_info("Dumping cgroups for %d\n", pid);
|
if (id < 0)
|
||||||
if (parse_task_cgroup(pid, args, &ctls, &n_ctls))
|
tid = pid;
|
||||||
|
else
|
||||||
|
tid = item->threads[id].real;
|
||||||
|
|
||||||
|
pr_info("Dumping cgroups for thread %d\n", tid);
|
||||||
|
if (parse_thread_cgroup(pid, tid, args, &ctls, &n_ctls))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
cs = get_cg_set(&ctls, n_ctls, item);
|
cs = get_cg_set(&ctls, n_ctls, item);
|
||||||
@ -764,9 +778,10 @@ int dump_task_cgroup(struct pstree_item *item, u32 *cg_id, struct parasite_dump_
|
|||||||
pr_info("Set %d is criu one\n", cs->id);
|
pr_info("Set %d is criu one\n", cs->id);
|
||||||
} else {
|
} else {
|
||||||
if (item == root_item) {
|
if (item == root_item) {
|
||||||
BUG_ON(root_cgset);
|
if (!root_cgset) {
|
||||||
root_cgset = cs;
|
root_cgset = cs;
|
||||||
pr_info("Set %d is root one\n", cs->id);
|
pr_info("Set %d is root one\n", cs->id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
struct cg_ctl *root, *stray;
|
struct cg_ctl *root, *stray;
|
||||||
|
|
||||||
@ -913,6 +928,7 @@ static int dump_controllers(CgroupEntry *cg)
|
|||||||
list_for_each_entry(cur, &cgroups, l) {
|
list_for_each_entry(cur, &cgroups, l) {
|
||||||
cg_controller_entry__init(ce);
|
cg_controller_entry__init(ce);
|
||||||
|
|
||||||
|
ce->is_threaded = cur->is_threaded;
|
||||||
ce->cnames = cur->controllers;
|
ce->cnames = cur->controllers;
|
||||||
ce->n_cnames = cur->n_controllers;
|
ce->n_cnames = cur->n_controllers;
|
||||||
ce->n_dirs = cur->n_heads;
|
ce->n_dirs = cur->n_heads;
|
||||||
|
@ -759,6 +759,7 @@ static int dump_task_core_all(struct parasite_ctl *ctl, struct pstree_item *item
|
|||||||
pid_t pid = item->pid->real;
|
pid_t pid = item->pid->real;
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
struct parasite_dump_cgroup_args cgroup_args, *info = NULL;
|
struct parasite_dump_cgroup_args cgroup_args, *info = NULL;
|
||||||
|
u32 *cg_set;
|
||||||
|
|
||||||
BUILD_BUG_ON(sizeof(cgroup_args) < PARASITE_ARG_SIZE_MIN);
|
BUILD_BUG_ON(sizeof(cgroup_args) < PARASITE_ARG_SIZE_MIN);
|
||||||
|
|
||||||
@ -804,13 +805,23 @@ static int dump_task_core_all(struct parasite_ctl *ctl, struct pstree_item *item
|
|||||||
*/
|
*/
|
||||||
if (item->ids->has_cgroup_ns_id && !item->parent) {
|
if (item->ids->has_cgroup_ns_id && !item->parent) {
|
||||||
info = &cgroup_args;
|
info = &cgroup_args;
|
||||||
|
strcpy(cgroup_args.thread_cgrp, "self/cgroup");
|
||||||
ret = parasite_dump_cgroup(ctl, &cgroup_args);
|
ret = parasite_dump_cgroup(ctl, &cgroup_args);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't support multithreads zombie tasks so there is
|
||||||
|
* no thread_core in zombie tasks, store the cg_set in
|
||||||
|
* task_core in these cases.
|
||||||
|
*/
|
||||||
|
cg_set = &core->thread_core->cg_set;
|
||||||
|
if (item->pid->state == TASK_THREAD) {
|
||||||
core->tc->has_cg_set = true;
|
core->tc->has_cg_set = true;
|
||||||
ret = dump_task_cgroup(item, &core->tc->cg_set, info);
|
cg_set = &core->tc->cg_set;
|
||||||
|
}
|
||||||
|
ret = dump_thread_cgroup(item, cg_set, info, -1);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
@ -1409,6 +1420,38 @@ err:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int dump_task_cgroup(struct parasite_ctl *parasite_ctl, const struct pstree_item *item)
|
||||||
|
{
|
||||||
|
struct parasite_dump_cgroup_args cgroup_args, *info;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
BUILD_BUG_ON(sizeof(cgroup_args) < PARASITE_ARG_SIZE_MIN);
|
||||||
|
for (i = 0; i < item->nr_threads; i++) {
|
||||||
|
CoreEntry *core = item->core[i];
|
||||||
|
|
||||||
|
/* Leader is already dumped */
|
||||||
|
if (item->pid->real == item->threads[i].real)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* For now, we only need to dump the root task's cgroup ns, because we
|
||||||
|
* know all the tasks are in the same cgroup namespace because we don't
|
||||||
|
* allow nesting.
|
||||||
|
*/
|
||||||
|
info = NULL;
|
||||||
|
if (item->ids->has_cgroup_ns_id && !item->parent) {
|
||||||
|
info = &cgroup_args;
|
||||||
|
sprintf(cgroup_args.thread_cgrp, "self/task/%d/cgroup", item->threads[i].ns[0].virt);
|
||||||
|
if (parasite_dump_cgroup(parasite_ctl, &cgroup_args))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dump_thread_cgroup(item, &core->thread_core->cg_set, info, i))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int pre_dump_one_task(struct pstree_item *item, InventoryEntry *parent_ie)
|
static int pre_dump_one_task(struct pstree_item *item, InventoryEntry *parent_ie)
|
||||||
{
|
{
|
||||||
pid_t pid = item->pid->real;
|
pid_t pid = item->pid->real;
|
||||||
@ -1681,6 +1724,12 @@ static int dump_one_task(struct pstree_item *item, InventoryEntry *parent_ie)
|
|||||||
goto err_cure;
|
goto err_cure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = dump_task_cgroup(parasite_ctl, item);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("Dump cgroup of threads in process (pid: %d) failed with %d\n", pid, ret);
|
||||||
|
goto err_cure;
|
||||||
|
}
|
||||||
|
|
||||||
ret = compel_stop_daemon(parasite_ctl);
|
ret = compel_stop_daemon(parasite_ctl);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("Can't stop daemon in parasite (pid: %d)\n", pid);
|
pr_err("Can't stop daemon in parasite (pid: %d)\n", pid);
|
||||||
|
@ -228,7 +228,7 @@ int prepare_inventory(InventoryEntry *he)
|
|||||||
|
|
||||||
if (!opts.unprivileged)
|
if (!opts.unprivileged)
|
||||||
he->has_root_cg_set = true;
|
he->has_root_cg_set = true;
|
||||||
if (dump_task_cgroup(NULL, &he->root_cg_set, NULL))
|
if (dump_thread_cgroup(NULL, &he->root_cg_set, NULL, -1))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
he->root_ids = crt.i.ids;
|
he->root_ids = crt.i.ids;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
struct pstree_item;
|
struct pstree_item;
|
||||||
struct parasite_dump_cgroup_args;
|
struct parasite_dump_cgroup_args;
|
||||||
extern u32 root_cg_set;
|
extern u32 root_cg_set;
|
||||||
int dump_task_cgroup(struct pstree_item *, u32 *, struct parasite_dump_cgroup_args *args);
|
int dump_thread_cgroup(const struct pstree_item *, u32 *, struct parasite_dump_cgroup_args *args, int id);
|
||||||
int dump_cgroups(void);
|
int dump_cgroups(void);
|
||||||
int prepare_task_cgroup(struct pstree_item *);
|
int prepare_task_cgroup(struct pstree_item *);
|
||||||
int prepare_cgroup(void);
|
int prepare_cgroup(void);
|
||||||
@ -60,6 +60,9 @@ struct cg_controller {
|
|||||||
|
|
||||||
/* for cgroup list in cgroup.c */
|
/* for cgroup list in cgroup.c */
|
||||||
struct list_head l;
|
struct list_head l;
|
||||||
|
|
||||||
|
/* controller is a threaded cgroup or not */
|
||||||
|
int is_threaded;
|
||||||
};
|
};
|
||||||
struct cg_controller *new_controller(const char *name);
|
struct cg_controller *new_controller(const char *name);
|
||||||
|
|
||||||
@ -87,7 +90,8 @@ struct cg_ctl {
|
|||||||
*/
|
*/
|
||||||
struct list_head;
|
struct list_head;
|
||||||
struct parasite_dump_cgroup_args;
|
struct parasite_dump_cgroup_args;
|
||||||
extern int parse_task_cgroup(int pid, struct parasite_dump_cgroup_args *args, struct list_head *l, unsigned int *n);
|
extern int parse_thread_cgroup(int pid, int tid, struct parasite_dump_cgroup_args *args, struct list_head *l,
|
||||||
|
unsigned int *n);
|
||||||
extern void put_ctls(struct list_head *);
|
extern void put_ctls(struct list_head *);
|
||||||
|
|
||||||
int collect_controllers(struct list_head *cgroups, unsigned int *n_cgroups);
|
int collect_controllers(struct list_head *cgroups, unsigned int *n_cgroups);
|
||||||
|
@ -241,7 +241,12 @@ struct parasite_dump_cgroup_args {
|
|||||||
*
|
*
|
||||||
* The string is null terminated.
|
* The string is null terminated.
|
||||||
*/
|
*/
|
||||||
char contents[1 << 12];
|
char contents[(1 << 12) - 32];
|
||||||
|
/*
|
||||||
|
* Contains the path to thread cgroup procfs.
|
||||||
|
* "self/task/<tid>/cgroup"
|
||||||
|
*/
|
||||||
|
char thread_cgrp[32];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* !__ASSEMBLY__ */
|
#endif /* !__ASSEMBLY__ */
|
||||||
|
@ -513,6 +513,7 @@ int parasite_dump_cgroup(struct parasite_ctl *ctl, struct parasite_dump_cgroup_a
|
|||||||
struct parasite_dump_cgroup_args *ca;
|
struct parasite_dump_cgroup_args *ca;
|
||||||
|
|
||||||
ca = compel_parasite_args(ctl, struct parasite_dump_cgroup_args);
|
ca = compel_parasite_args(ctl, struct parasite_dump_cgroup_args);
|
||||||
|
memcpy(ca->thread_cgrp, cgroup->thread_cgrp, sizeof(ca->thread_cgrp));
|
||||||
ret = compel_rpc_call_sync(PARASITE_CMD_DUMP_CGROUP, ctl);
|
ret = compel_rpc_call_sync(PARASITE_CMD_DUMP_CGROUP, ctl);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("Parasite failed to dump /proc/self/cgroup\n");
|
pr_err("Parasite failed to dump /proc/self/cgroup\n");
|
||||||
|
@ -745,7 +745,7 @@ static int parasite_dump_cgroup(struct parasite_dump_cgroup_args *args)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cgroup = sys_openat(proc, "self/cgroup", O_RDONLY, 0);
|
cgroup = sys_openat(proc, args->thread_cgrp, O_RDONLY, 0);
|
||||||
sys_close(proc);
|
sys_close(proc);
|
||||||
if (cgroup < 0) {
|
if (cgroup < 0) {
|
||||||
pr_err("can't get /proc/self/cgroup fd\n");
|
pr_err("can't get /proc/self/cgroup fd\n");
|
||||||
|
@ -2549,7 +2549,8 @@ err:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parse_task_cgroup(int pid, struct parasite_dump_cgroup_args *args, struct list_head *retl, unsigned int *n)
|
int parse_thread_cgroup(int pid, int tid, struct parasite_dump_cgroup_args *args, struct list_head *retl,
|
||||||
|
unsigned int *n)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
int ret;
|
int ret;
|
||||||
@ -2557,7 +2558,7 @@ int parse_task_cgroup(int pid, struct parasite_dump_cgroup_args *args, struct li
|
|||||||
unsigned int n_internal = 0;
|
unsigned int n_internal = 0;
|
||||||
struct cg_ctl *intern, *ext;
|
struct cg_ctl *intern, *ext;
|
||||||
|
|
||||||
f = fopen_proc(pid, "cgroup");
|
f = fopen_proc(pid, "task/%d/cgroup", tid);
|
||||||
if (!f)
|
if (!f)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ message cgroup_dir_entry {
|
|||||||
message cg_controller_entry {
|
message cg_controller_entry {
|
||||||
repeated string cnames = 1;
|
repeated string cnames = 1;
|
||||||
repeated cgroup_dir_entry dirs = 2;
|
repeated cgroup_dir_entry dirs = 2;
|
||||||
|
required bool is_threaded = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message cg_member_entry {
|
message cg_member_entry {
|
||||||
|
@ -105,6 +105,7 @@ message thread_core_entry {
|
|||||||
optional string comm = 13;
|
optional string comm = 13;
|
||||||
optional uint64 blk_sigset_extended = 14;
|
optional uint64 blk_sigset_extended = 14;
|
||||||
optional rseq_entry rseq_entry = 15;
|
optional rseq_entry rseq_entry = 15;
|
||||||
|
required uint32 cg_set = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message task_rlimits_entry {
|
message task_rlimits_entry {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user