实验目的

​ 通过/proc/<PID>/ctx,查看进程被调用的次数。认识linux中的进程结构。

实验环境

  • 宿主机:Ubuntu 20.04
  • 内核版本:v6.1

实验内容

task_struct

​ 在linux中使用task_struct作为进程描述符,用于进程管理。我们可以在include/linux/sched.h中看到它的结构。下面是参考的报告中的内容:

struct task_struct {
    int ctx;   //add a type
    
    int on_rq;
    
  	int prio;
    int static_prio;
    int normal_prio;
    unsigned int rt_priority;
    //...
}

进程的创建

linux进程创建的代码位于kernel/fork.c中。

​ 首先分析Linux 中三个系统调用forkvfork, clone,这三个系统调用都在设置参数后,通过_do_fork返回。

long _do_fork(struct kernel_clone_args *args){
    //...
}

​ 可以看出_do_fork()会完成进程的复制。_do_fork()会调用copy_process函数,接着添加lantent entropy,随后调用trace_sched_process_fork()函数唤醒新的线程。

p = copy_process(NULL, trace, NUMA_NO_NODE, args);
add_latent_entropy();
if(IS_ERR(p))
    return PTR_ERR(p);

/*
*	Do this prior waking up the new thread - the thread pointer
*   might get invalid affter that point, if the thread exits quickly.
*/
trace_sched_process_fork(current, p);

​ 加下来是copy_process的函数说明, 我们在这一步进行ctx变量的初始化。

static __latent_entropy struct task_struct *copy_process(
		struct pid *pid,
    	int trace,
    	int node,
    	struct kernel_clone_args *args)
{
    p->ctx = 0;
    
    retval = sched_fork(clone_flags, p);
    if(retval)
        goto bad_fork_cleanup_policy;
    //...
}

​ 每次进程被调度时,我们要对其进行加1的操作,我们定位到kernel/sched/core.c中的schedule函数,我们在每次调用时,对ctx进行加1的操作。

asmlinkage __visible void __sched schedule(void){
    struct task_struct *tsk = current;
    
    tsk->ctx ++;
    
    sched_submit_work(tsk);
    do{
        preempt_disable();
        __schedule(false);
        sched_preempt_enable_no_resched();
    } while(need_resched());
    sched_update_worker(tsk);
}
EXPORT_SYMBOL(schedule);

​ 该函数会创建指向当前进程的指针tsk,并调用sched_submit_work()死锁检测,然后在循环中进行该进程的调度,preempt_disable()用于禁用内核抢占,然后__schedule()函数进行调度。

伪文件接口

linuxprocpid对应的目录文件实现在fs/proc/base.c中,其中pid_entry数组tgid_base_stuff[]定义了可以访问的pid,我们参照personality的声明格式,定义一个handle function, 最后我们只需要调用cat /proc/[PID]/ctx就可以进行ctx输出

static int proc_pid_ctx(struct seq_file*m, struct pid_namespace *ns, 
                       struct pid *pid, struct task_struct *task){
    int err = lock_trace(task);
    if(!err){
        seq_printf(m, "%d\n", task->ctx);
        unlock_trace(task);
    }
    
    return err;
}

说明

​ 该实验说明书写时,笔者实验环境已经被拆除,并没有进行重现,报告参考[DicardoX/SJTU_CS353_Linux_Kernel (github.com)](https://github.com/DicardoX/SJTU_CS353_Linux_Kernel)