修改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。

posted @ 2025-08-05 09:58  爱吃鸡魔人zf  阅读(4)  评论(0)    收藏  举报