修改kill_all,kill_all_by_cmdline,group_feed
kill_all
void kill_all(pid_t pid)
{
struct pid *spid =find_get_pid(pid);
struct task_struct *task = pid_task(spid, PIDTYPE_PID);
if(task == NULL ) {put_pid(spid); return;}
seaway_debug(DEBUG_INFO, "kill thread by pid %d\n", task->pid);
kill_children(task);
put_pid(spid); //杀掉任务后释放调用
send_sig(SIGKILL, task, 1); //kill current task
}
kill_children
void kill_children(struct task_struct *parent)
{
struct process_node *node, *tmp;
struct list_head to_kill; // 待终止进程列表
struct list_head process_queue; // 进程队列
//链表头初始化
INIT_LIST_HEAD(&to_kill);
INIT_LIST_HEAD(&process_queue);
//保护系统关键进程
if (unlikely(parent->pid == 1 || parent->pid == 2)) {
seaway_debug(DEBUG_ERROR,"Cannot kill descendants of init process!\n");
return;
}
rcu_read_lock();
// 收集所有子孙进程
struct task_struct *child;
list_for_each_entry_rcu(child, &parent->children, sibling) {
// 为每个子进程创建节点
node = kmalloc(sizeof(*node), GFP_ATOMIC);
if (!node) goto out_of_memory;
// 增加任务引用计数
get_task_struct(child);
node->task = child;
list_add_tail_rcu(&node->list, &process_queue);
}
while (!list_empty(&process_queue)) {
// 从队列取出第一个节点
node = list_first_entry(&process_queue, struct process_node, list);
list_del(&node->list);
// 添加到终止列表
list_add_tail_rcu(&node->list, &to_kill);
// 将节点的所有子进程加入队列
struct task_struct *child_task = node->task;
struct task_struct *grandchild;
list_for_each_entry_rcu(grandchild, &child_task->children, sibling) {
struct process_node *child_node = kmalloc(sizeof(*child_node), GFP_ATOMIC);
if (!child_node) goto out_of_memory;
get_task_struct(grandchild);
child_node->task = grandchild;
list_add_tail_rcu(&child_node->list, &process_queue);
}
}
//node->task = parent;
//list_add_tail_rcu(&node->list,&to_kill);
rcu_read_unlock();
// 终止所有进程
list_for_each_entry_safe(node, tmp, &to_kill, list) {
struct task_struct *task = node->task;
bool alive = pid_alive(task);
// 终止进程
put_task_struct(task); // 释放任务引用
if (alive) {
if (task->flags & PF_KTHREAD) {
seaway_debug(DEBUG_INFO,"Killing kernel thread [%d] %s\n", task->pid, task->comm);
} else {
seaway_debug(DEBUG_INFO,"Killing process [%d] %s\n", task->pid, task->comm);
}
send_sig(SIGKILL, task, 1);
}
// 释放节点
list_del(&node->list);
kfree(node);
}
return;
out_of_memory:
seaway_debug(DEBUG_ERROR,"Memory allocation failed in process termination\n");
rcu_read_unlock(); //修复rcu未解引用的bug
// 清理所有节点
list_for_each_entry_safe(node, tmp, &process_queue, list) {
list_del(&node->list);
put_task_struct(node->task);
kfree(node);
}
list_for_each_entry_safe(node, tmp, &to_kill, list) {
list_del(&node->list);
put_task_struct(node->task);
kfree(node);
}
}
list_head结构体
list_head 是 Linux 内核中广泛使用的一个双向链表结构体
struct list_head {
struct list_head *next, *prev;
};
list_head 是 Linux 内核中广泛使用的一个双向链表结构体,定义在头文件 <linux/list.h> 中。它的设计非常简洁,用于构建通用的双向链表,而不需要为每种数据类型单独实现链表逻辑。
结构体定义(简化版)
struct list_head {
struct list_head *next, *prev;
};
特点
- 无数据域:list_head 本身不包含任何数据,只是作为链表的“钩子”(hook)嵌入到用户定义的结构体中。
- 双向循环链表:初始化后,next 和 prev 都指向自身,形成一个空链表。
- 类型无关:通过 container_of 宏可以从链表节点反推出包含它的结构体地址。
关键宏
- INIT_LIST_HEAD(ptr):初始化链表头。
- list_add(new, head):将新节点添加到链表头部。
- list_add_tail(new, head):将新节点添加到链表尾部。
- list_del(entry):从链表中删除节点。
- list_for_each_entry(pos, head, member):遍历链表并获取包含结构体的指针。
RCU
(Read-Copy Update)是 Linux 内核里一种 极其高效的“无锁”同步机制,核心思想一句话:
- 读者无锁并发读,写者异步延迟回收旧数据。
为什么要用 RCU?
| 场景 | 传统锁 | RCU |
|---|---|---|
| 读远多于写 | 锁竞争严重 | 读者几乎零成本 |
| 对延迟敏感 | 上下文切换开销大 | 无需上下文切换 |
| 数据结构大 | 加锁粒度难控 | 读侧无锁,天然可扩展 |
list_for_each_entry_rcu
是一个 在 RCU(Read-Copy-Update)临界区内安全遍历链表 的宏,本质上就是遍历一个由 struct list_head 组成的双向循环链表,但它比普通的 list_for_each_entry 多了 RCU 读侧保护,用于在 读多写少、允许延迟回收 的场景下安全访问链表。
list_for_each_entry_rcu(pos, head, member) {
// 使用 pos 访问链表元素
}
list_for_each_entry_rcu(child, &parent->children, sibling) {...}
sibling 是 task_struct 中用于 把自己挂到父进程 children 链表 的 list_head 成员。
在 list_for_each_entry_rcu 里它只是 “链表节点” 的名字,不代表真正的“兄弟”关系
struct task_struct {
...
struct list_head children; // 父进程的角度
struct list_head sibling; // 子进程的角度
...
};
- 父进程:parent->children 是链表头
- 每个子进程:child->sibling 是 链入 父进程 children 链表的节点
广度优先遍历进程树
struct task_struct *child;
list_for_each_entry_rcu(child, &parent->children, sibling) {//先构建前两层初始的进程树
// 为每个子进程创建节点
node = kmalloc(sizeof(*node), GFP_ATOMIC);
if (!node) goto out_of_memory;
// 增加任务引用计数
get_task_struct(child);
node->task = child;
list_add_tail_rcu(&node->list, &process_queue);
}
while (!list_empty(&process_queue)) {
// 从队列取出第一个节点
node = list_first_entry(&process_queue, struct process_node, list);
list_del(&node->list);
// 添加到终止列表
list_add_tail_rcu(&node->list, &to_kill);
// 将节点的所有子进程加入队列
struct task_struct *child_task = node->task;
struct task_struct *grandchild;
list_for_each_entry_rcu(grandchild, &child_task->children, sibling) {
struct process_node *child_node = kmalloc(sizeof(*child_node), GFP_ATOMIC);
if (!child_node) goto out_of_memory;
get_task_struct(grandchild);
child_node->task = grandchild;
list_add_tail_rcu(&child_node->list, &process_queue);
}
}
这就是一棵“进程树”的广度优先(BFS)遍历:
- process_queue 充当队列。
- 每轮循环:
- 取出队头节点(某个进程)。
- 把它移到 to_kill(标记“待处理”)。
- 把这个进程的所有 子进程 再 kmalloc 成新节点,塞回 process_queue 尾部。
- 每轮循环:
只要队列里还有节点,就说明还有后代没处理完;直到队列为空,整棵子树就被全部枚举完毕。
send_sig(SIGKILL, task, 1)
send_sig(SIGKILL, task, 1) 是内核代码里向指定 task 发送 9 号信号SIGKILL的 API。
它最终会让目标进程进入 do_exit() → do_group_exit() → … → 回收资源 → 彻底退出 的流水线。
send_sig(SIGKILL, task, 1) 把 9 号信号挂到目标线程组,一旦有机会运行,就触发 do_group_exit(),最终让进程/线程进入完整的 “退出 → 回收 → 僵尸” 生命周期。
用cgroup机制更改kill_all函数实现
完全可以绕过“遍历 task->children 链表”这一套麻烦又容易出错的逻辑。
思路:把“要杀谁”这件事交给 cgroup 本身去做——cgroup 已经替你维护了那个进程及其所有子孙的 membership,你只要让内核一次性向该 cgroup 里的所有 task 发 SIGKILL 即可。Linux 从 4.19 起就提供了这种“批量”接口。
把目标进程(及其未来将派生的子进程)事先 attach 到 mycgroup,echo
代码里不再遍历 children,而是用 cgroup_kill_sb() / cgroup1_procs_write() 或 kill_on_exit 等机制。
如果只想“一键杀光”而不想再动 task_struct,可直接向 cgroup 的 cgroup.kill 文件写 1(内核 5.14+):echo 1 > /sys/fs/cgroup/mycgroup/cgroup.kill
内核会遍历该 cgroup 的 css_set 并给每个 task 发 SIGKILL,原子、无锁,无需你操心 RCU 或内存分配。
想在代码里做,可以用内核 API:
/* 先根据名字拿到 cgroup 的 kn */
struct kernfs_node *kn = kernfs_find_and_get(
root_cgroup->kn, "mycgroup");
struct cgroup *cg = kn ? cgroup_kn_lock_live(kn) : NULL;
if (cg) {
/* 向 cgroup 发 kill 信号 */
cgroup_kill(cg);
cgroup_kn_unlock(kn);
}
内核内部 cgroup_kill() 会遍历 css_set 并调用 do_send_sig_info(SIGKILL, …),完全等价于 shell 写 1。

浙公网安备 33010602011771号