Linux 下 进程 or 线程的遍历

内核 API 进程线程 遍历那点事

  • 资料来源:

    <>

  • 更新

    1
    2023.12.20 初始

导语

最近遇到个 bug 挺恼人的, 一些情况下就是没有特定 task 的信息… 但实际上是已经获取到了…

归根结底实际上是原来代码中遍历 task_struct 选错了循环结构.. 故总结一下:

涉及到遍历 task_struct 的宏基本都在 include/linux/sched/signal.hinclude/linux/pid.h 下定义

  • 不同 linux 版本可能有变化, 这里是 5.17.15 ;
  • 例子来自 gpt

基本分为两种类型: do 循环和 for 循环

  • do 循环下的 while 宏,一般不建议单独使用,非常容易漏掉个别循环条件.
    • 我遇到的 bug 就是 原代码中单独用了 while 没有配合 do, 漏掉了 第一次循环的 task;
  • 需要注意同步和锁, 一般需要 rcu 锁;
  • 个别是 两层循环, break 不管用,要 goto 才能跳出;

正文

do_each_pid_task(pid, Type, task) { … } while_each_pid_task(pid, Type, task);

遍历 pid 下的 type 类型 的 所有 线程和进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define do_each_pid_task(pid, type, task)				\
do { \
if ((pid) != NULL) \
hlist_for_each_entry_rcu((task), \
&(pid)->tasks[type], pid_links[type]) {

/*
* Both old and new leaders may be attached to
* the same pid in the middle of de_thread().
*/
#define while_each_pid_task(pid, type, task) \
if (type == PIDTYPE_PID) \
break; \
} \
} while (0)

struct task_struct _task;
pid_t pid = /_ 目标 PID */;
enum pid_type type = PIDTYPE_TGID; // 或 PIDTYPE_TGID 等

do_each_pid_task(pid, type, task) {
// 可以在这里操作 task
} while_each_pid_task(pid, type, task);

do_each_pid_thread(pid, Type, task) { … } while_each_pid_thread(pid, Type, task);

类似于 do_each_pid_task,但专门用于遍历具有特定 PID 的线程。

do_each_thread(g, t) { … } while_each_thread(g, t);

用途: 遍历全部进程的全部线程; 双循环, break 不管用,需要使用 goto;

示例:

1
2
3
4
5
6
7
8
9
10
#define do_each_thread(g, t) \
for (g = t = &init_task ; (g = t = next_task(g)) != &init_task ; ) do

#define while_each_thread(g, t) \
while ((t = next_thread(t)) != g)

struct task_struct *g, *t;
do_each_thread(g, t) {
// 在这里,t 指向当前的线程
} while_each_thread(g, t);

for_each_process(p) { … }

遍历系统中的每个进程, 不包括线程

示例:

1
2
3
4
5
6
7
#define for_each_process(p) \
for (p = &init_task ; (p = next_task(p)) != &init_task ; )

struct task_struct *p;
for_each_process(p) {
// 在这里处理每个进程的 task_struct
}

for_each_process_thread(p, t) { … }

遍历系统中每个进程的所有线程, 双重循环 break 不行,要用 goto

示例

1
2
3
4
5
6
7
8
#define for_each_process_thread(p, t)	\
for_each_process(p) for_each_thread(p, t)

struct task_struct *p, *t;

for_each_process_thread(p, t) {
// p 进程; t线程
}

for_each_thread(p, t) { … }

遍历给定进程的所有线程

示例:

1
2
3
4
5
6
7
8
9
10
11
12
#define __for_each_thread(signal, t)	\
list_for_each_entry_rcu(t, &(signal)->thread_head, thread_node)

#define for_each_thread(p, t) \
__for_each_thread((p)->signal, t)

struct task_struct *p, *t;
p = current; // 当前进程

for_each_thread(p, t) {
// t 线程
}

结束语

linux kernel 的琐碎让人心力交瘁 😒😒😒😒