目录

一.题目介绍

二.实验思路

三.核心代码 

四。遇到的问题及一些解决方法

五.参考文献

PS:博客只是提供一个简要的思路,互相学习。

一.题目介绍

        显示当前进程的pid和父进程的pid,主要考察如何获取当前进程的task_struct,并通过task结构体获取指定namespace空间的pid。

二.实验思路

图1.代码分析流程

从最顶层开始,一层层往下剖析,我们的目的要获得当前进程的pid,所以我们首先需要获得我们当前的一个task结构体,通过linux中设置的current宏,我们很轻易的可以获取当前进程的task_struct,为了获取指定pid namespace空间的pid,我们还需要获取指定的pid_namespace *ns,然后通过内核函数pid_nr_ns()获取到指定ns的pid即可。

三.核心代码 

系统调用部分代码:

SYSCALL_DEFINE4(promise,int*,nr,int*,pnr,int*,level,int*,p_level)
{
    enum pid_type type=PIDTYPE_PID;
    struct pid_namespace *ns = NULL;
    struct pid_namespace *pns = NULL;
    struct task_struct *tsk = current;
    struct task_struct *p_tsk = tsk->parent;
    int crt_pid=0,crt_ppid=0;
    int crt_level=0,pcrt_level=0;
    //rcu保护机制
    rcu_read_lock();
    //tsk
    ns = task_active_pid_ns(tsk);
    crt_level=ns->level;
    //根据task找到对应的ns
	if (likely(pid_alive(tsk))) 
    {    
        //get tgid                  
        tsk = tsk->group_leader;
        //pid_nr_ns得到的是指定ns的pid                                                                                                          
        crt_pid = (int)pid_nr_ns(rcu_dereference(tsk->pids[type].pid), ns);
    }
    //parent tsk
    pns=task_active_pid_ns(p_tsk);
    pcrt_level=pns->level;
    if (likely(pid_alive(p_tsk))) 
    {                      
        p_tsk = p_tsk->group_leader;                                                                                                             
        crt_ppid = (int)pid_nr_ns(rcu_dereference(p_tsk->pids[type].pid), pns);
    }
    rcu_read_unlock();
    //copy_to_user()
    copy_to_user(nr,&crt_pid,sizeof(crt_pid));
    copy_to_user(pnr,&crt_ppid,sizeof(crt_ppid));
    copy_to_user(level,&crt_level,sizeof(crt_ppid));
    copy_to_user(p_level,&pcrt_level,sizeof(crt_ppid));
    return 0;
}

test.c

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
 
int main()
{
    int pid,ppid,level,p_level;
    
    syscall(292,&pid,&ppid,&level,&p_level);
    printf("ns_level:%-5d pid:%-5d p_level:%-5d ppid:%-5d\n",level,pid,p_level,ppid);
    while(1);
    return 0;
}

四。遇到的问题及一些解决方法

1.对内核函数传参传入nr和pnr时,想在内核函数中直接将其值修改,但是运行后失败,查询后是发现内核空间和用户空间不能直接互访,所以必须使用copy_to_user()函数来在内核函数内改变用户程序中传入的参数。

2.系统在reboot的时候直接内核崩坏,服务器无法正常使用,只能强制重装系统,没法复现,但猜测是在内核进行make的过程中出现了某个error没有重视,强行进行reboot所导致。

3.最开始使用的时候,没有考虑到内核内不能调用系统调用,以为简单的在系统调用函数内直接使用getpid()就能实现,试了一番,查询后才知道系统调用只能调用内核API。

4.在代码实现的一开始,选择的是直接去返回当前进程的pid,但是后面看了getpid函数后,发现其传入的type是一个tgid,因为linux没有严格的线程概率,所有一个进程里面可能有多个线程有其相对的pid,如果想返回主要这个进程的pid,就应该返回group_leader的pid,也就是实际是得到了tgid。

五.参考文献

1. Linux中的RCU机制[一] - 原理与使用方法[EB/OL]. []. https://zhuanlan.zhihu.com/p/89439043.

2. Linux内核中的RCU[EB/OL]. []. https://zhuanlan.zhihu.com/p/67520807.

3. rcu 机制简介[EB/OL]. []. https://zhuanlan.zhihu.com/p/113999842.

4. linux内核命名空间[EB/OL]. []. https://zhuanlan.zhihu.com/p/136404837.

5. pid namespace详细解读[EB/OL]. []. https://tinylab.org/pid-namespace/.

6. 浅谈current宏[EB/OL]. []. https://www.cnblogs.com/crybaby/p/14082593.html.

7. Linux源码[EB/OL]. []. https://elixir.bootlin.com/linux/v4.16.3/source.

8. list_entry详解[EB/OL]. []. linux源代码中的容器:list_entry_51CTO博客_linux 源代码.