背景
有时候,某些进程可能会出现一些异常,需要排查定位异常原因。
获取异常进程的信息是比较容易的,ps top这些都可以简单查看,更详细的信息也可以通过kcore来分析。
但是,如果需要做一些实验修改进程来排查异常原因呢,比如需要修改进程的state或者尝试唤醒进程或者其他操作呢?
学习
可以写一个内核模块,通过加载模块+指定pid的方法来对指定的进程进行修改
userspace最容易获取到的进程的特定标识是什么呢?pid
那接下来就需要查看怎么通过pid来获取task_struct
可以看一下include/linux/pid.h中有一些函数声明
//通过pid获取pid struct
extern struct pid *find_get_pid(int nr);
//通过pid struct获取task_struct
extern struct task_struct *get_pid_task(struct pid *pid, enum pid_type);
//通过task_strcut获取pid struct
extern struct pid *get_task_pid(struct task_struct *task, enum pid_type type);
接下来看一下这几个函数的实现:
- find_get_pid
//find_get_pid的实现
//看一下主要的实现在get_pid(find_vpid(nr))
struct pid *find_get_pid(pid_t nr)
{
struct pid *pid;
rcu_read_lock();
pid = get_pid(find_vpid(nr));
rcu_read_unlock();
return pid;
}
struct pid *find_vpid(int nr)
{
return find_pid_ns(nr, task_active_pid_ns(current));
}
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
return idr_find(&ns->idr, nr);
}
void *idr_find(const struct idr *idr, unsigned long id)
{
return radix_tree_lookup(&idr->idr_rt, id - idr->idr_base);
}
static inline struct pid *get_pid(struct pid *pid)
{
if (pid)
refcount_inc(&pid->count);
return pid;
}
//相关结构体
struct pid_namespace {
struct idr idr;
struct rcu_head rcu;
unsigned int pid_allocated;
...
}
struct idr {
struct radix_tree_root idr_rt;
unsigned int idr_base;
unsigned int idr_next;
};
以上可以看到从pid获取pid struct的过程为:获取当前ns(这块就需要注意了,这里直接获取的current的ns,如果是docker之类的有ns隔离的环境,需要确定调用查询pid的进程和要查询的pid所属的进程是不是处于相同的ns中)->获取ns的idr->根据pid号和idr来查找基数(现在应该是使用xarray了)
所以这里我们也能看出,每个ns结构体使用一个基树/xarrya维护着所有的pid->pid struct的映射
噢,btw,看了一下,IDA和IDR可不只是用于namespace的pid管理呢?这俩还算一个比较高等级的抽象呢,可以用于很多需要分配id/id到指针映射的场景呢。其中IDR用于维护一个ID到指针的映射,IDA只用于分配ID
牛的牛的,之前没注意这块,后面还得单开一篇文章看看这块内容
注意,这里的get_pid对pid struct增加引用计数了,因此,使用完后还需要释放引用计数,使用put_pid
- get_pid_task
struct task_struct *get_pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result;
rcu_read_lock();
result = pid_task(pid, type);
if (result)
get_task_struct(result);
rcu_read_unlock();
return result;
}
struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result = NULL;
if (pid) {
struct hlist_node *first;
first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
lockdep_tasklist_lock_is_held());
if (first)
result = hlist_entry(first, struct task_struct, pid_links[(type)]);
}
return result;
}
static inline struct task_struct *get_task_struct(struct task_struct *t)
{
refcount_inc(&t->usage);
return t;
}
//相关结构体
struct pid
{
refcount_t count;
unsigned int level;
spinlock_t lock;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
struct hlist_head inodes;
/* wait queue for pidfd notifications */
wait_queue_head_t wait_pidfd;
struct rcu_head rcu;
struct upid numbers[];
};
可以看到,其实get_pid_task就是通过去遍历查找pid struct中的tasks链表来查找符合条件的任务
注意这里引用计数也增加了,所以不用的时候应该释放掉,通过put_task_struct
不过这块有些疑问,写在后面
- get_task_pid
struct pid *get_task_pid(struct task_struct *task, enum pid_type type)
{
struct pid *pid;
rcu_read_lock();
pid = get_pid(rcu_dereference(*task_pid_ptr(task, type)));
rcu_read_unlock();
return pid;
}
static struct pid **task_pid_ptr(struct task_struct *task, enum pid_type type)
{
return (type == PIDTYPE_PID) ?
&task->thread_pid :
&task->signal->pids[type];
}
类似的,在task_struct中存着pid struct相关的信息呢,获取到这个这个之后再执行一下get_pid就好了
内核模块
通过以上学习,已经知道了pid号大概是怎么和task_struct相互转换的,所以剩下的就是转换成代码就好了
这里提供个大概框架
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/pid.h>
#include <linux/sched.h>
#include <linux/sched/task.h>
static int target_pid = -1;
module_param(target_pid, int, 0);
static int __init XXX(void)
{
struct task_struct *t;
struct pid *p;
if (target_pid < 0) {
pr_err("target pid < 0\n");
return -EINVAL;
}
p = find_get_pid(target_pid);
if (!p) {
pr_err("get pid failed\n");
return -EINVAL;
}
t = get_pid_task(p, PIDTYPE_PID);
if (!t) {
pr_err("get task_struct failed\n");
put_pid(p);
return -EINVAL;
}
/*
*这里可以添加对task_struct的修改内容
*比如修改状态、唤醒进程之类的操作
*/
put_pid(p);
put_task_struct(t);
return -EINVAL;
}
module_init(XXX);
MODULE_LICENSE("GPL");
后记
- 再次注意,使用get_XXX等函数的时候,需要注意下是不是增加了引用计数,等不再使用的时候需要释放引用计数。pid struct、task_struct还有之前遇到的net_device、gendisk这些都是一样的。
- 为什么struct pid中的task不直接做成一个task_struct *类型而是搞一个链表呢?可能是为了进程组、多线程考虑?
- 接上面2,那如果在一个struct pid中有一个task链表中有几个相同的pid_type的任务,get_pid_task会返回什么呢?应该是返回的链表的第一个task。是否会出现一个task链表中有几个相同的pid_type的任务的情况?以及get_pid_task直接返回第一个task是否合理?
这个后续还得继续研究一下
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 857879363@qq.com