linux内核中得到进程全路径

晨曦之光 发布于 2012/04/10 15:00
阅读 1K+
收藏 0

在windows当中,你可以很简单利用GetModuleHandle函数得到当前进程的全路径,在linux中你可以不用写程序,更简单的从 /proc/pid/exe链接得到进程的全路径(守护进程情况特殊),可是在内核当中怎么样呢?在linux内核中得到进程全路径的方式显现了 linux内核设计的特点。
在linux内核中没有明显的方式可以得到进程的全路径以及文件的全路径,linux中每一个进程都有一个task_struct结构体,那么想当然的思考一下,这个结构体当中是否会存在进程全路径信息呢?毕竟该结构体内部的很多字段都是代表进程的性质,全路径当然也是一种性质,因此,在 task_struct中存在进程全路径的想法是很合理的,但是不幸的是,task_struct中没有这个信息,为何?linux将进程的位置和进程的 执行分离开来了,也就是说,进程执行的时候并不依赖程序的位置,位置信息仅仅是一个静态的信息,而程序执行的时候完全可以忽略掉它所在的位置,进程是什么?进程就是一个执行绪,仅此而已,一个执行绪并没有位置信息,它只有执行时的行为信息,理解了这一点之后,我们就不用抱怨为何linux的 task_struct中没有进程全路径信息了,另一方面,linux的内核设计(当然还包括用户空间的设计,比如发行版的设计)是由很多小粒度的颗粒组合而成的,粒度越小,颗粒越多,因而排列组合的总数就越多,最终功能就越强大,我就不再举DNA的例子了,在linux中,没有什么是必然联系的,唯一的 耦合点就是接口,别的地方尽量少的进行结构耦合,因此linux没有牵一发而动全身的劣性。仔细读一下linux源代码就会发现,内核中的很多数据结构的联系都是类型无关的,就是说和具体的数据类型没有关系,比如list_head和kobject就是很好的例子,这些连接结构的作用仅仅是关联若干数据结构而不带有任何类型信息,这种机制非常好,就好像一个透明的代理透明的充当二传手,我们的邮递员实际上做的就是类似的工作,他们只负责送信而不知道信的内 容。
说了这么多看似无关的内容,现在到了正题。在linux中vfs是一个设计的相当好的馍块,它的一些数据结构真是让人叫绝,一点也不亚于Sun的vfs数 据结构,在linux中,文件以inode表示,inode有两种类型,一个是内存中的inode,一种是磁盘的inode,这里,内存中的inode真 正统一了vfs的接口,各种不同的文件系统拥有不同的磁盘inode,甚至有些文件系统没有磁盘inode结构,但是一旦它们被读入内存以后就成了统一的 内存inode结构,所有的inode组合成了整个文件系统,一个文件系统是一个整体,拥有一个挂载点,所有的结构彼此独立又相互联系,比如文件 /home/zhaoya/test/abc文件不再是一个整体,而是由/,home,zhaoya,test,abc组成的,因此不要指望文件abc的 内存inode结构中包含有/,home,zhaoya等等信息,这样的话,粒度就大了,我们说过linux是小粒度的,每个实体都会被分解到不能再分解 为止,然后这些被分解的元素彼此链接形成这个实体。文件被进程访问,因此每个进程都会有一个打开文件的数组,每个数组元素代表一个被打开的文件,每个文件对应一个内存inode,在vfs中还有一个重要的dentry目录项结构,当中存有一些该inode的信息,还以/home/zhaoya/test /abc为例,abc的dentry和test的dentry有何联系呢?显而易见,test是abc的parent,因此, /,home,zhaoya,test,abc的关系就是祖先和孩子的关系,前面的是后一个的parent,于是当要得到一个文件的全路径时不能在该文件 的inode或与之相关的dentry中直接得到这个全路径,而是应该逐级的向parent追溯,最终将结果拼接成一个字符串就是结果。
     linux这么实现是不是很麻烦呢?不是的。如果认为这样实现麻烦不直观,那么他可能还是没有理解linux的设计,如果可以通过若干机制组合实现的机制,linux就没有必要重新单独的实现一个机制。
现在知道了怎样得到全路径的原理,那么代码怎么实现呢?简单的看一下:

while(1)

{

    if(dentry == NULL)

        break;

    if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) //这是为什么呢?因为一个文件系统有一个挂载点,dentry的祖先孩子关系到这个挂载点结束,不会在往上进行追溯了,因此每当碰到挂载点的时候就要想办法越过它,否则就到此为止了。

    {

        if (vfsmnt->mnt_parent == vfsmnt)

        {

            break;

        }

        dentry = vfsmnt->mnt_mountpoint;

        vfsmnt = vfsmnt->mnt_parent;

        continue;

    }

    name = (char*)(dentry->d_name.name);

    if(!name)

        break;

    printk("%s-",name);

    if( (dparent = dentry->d_parent) )  //得到此dentry的parent

    {

        dentry = dparent;

        pinode = dentry->d_inode;

    }

    else

        break;

}

现 在解释最初提出的问题:怎样得到当前进程的全路径?既然不能指望在task_struct里面找到这个信息,那么该指望谁呢?办法有二,为了能顺理成章的 想到这两种方法,首先考虑一下哪里需要进程的全路径,第一个想到的就是程序被exec的时候,exec的第一个参数就是,其实也就是程序main函数的参 数的第一个元素,那么参考一下我的另一篇文章《linux程序的命令行参数》就可以知道这些参数实际上是被存放在进程的堆栈的,那么第一种方式就是直接访问进程的mm_struct进而访问表示堆栈中main参数的vm_area_struct,这样的话就可以得到进程路径了。那么第二种方式呢?我们知道 在执行一个程序的时候首先要打开这个程序文件,然后把文件映射到进程的地址空间,这时进程的地址空间中就有一个vm_area_struct表示这个程序 文件映射的地址空间即代码段,而此vm_area_struct中的vm_file字段就表示这个磁盘程序文件,接下来就可以访问这个文件的 dentry,进而可以访问此dentry的parent,然后再parent,最后将一系列结果拼接,最终得到全路径。下面看看这两种方式的代码:
第二种方式,注意当时我测试时pid为5971的是vsftpd进程:

task_t *task = find_task_by_pid(5971);

struct mm_struct * mm;

struct dentry * dentry;

struct vfsmount * mnt;

struct vm_area_struct * vma;

mm = task->mm;

if (mm)

    atomic_inc(&mm->mm_users);

down_read(&mm->mmap_sem);

vma = mm->mmap;

while (vma)

{

    if ((vma->vm_flags & VM_EXECUTABLE) &&  vma->vm_file)  //得到代码段

    {

        mnt = mntget(vma->vm_file->f_vfsmnt);

        dentry = dget(vma->vm_file->f_dentry);  //代码段映射文件的dentry

        break;

    }

    vma = vma->vm_next;

}

up_read(&mm->mmap_sem);

mmput(mm);

printk("%s/n",dentry->d_name.name);//既然得到了dentry,那么就可以一直parent得到最终结果了,以下略。

//以下为第二种方式

char buffer[128];

unsigned int len = mm->arg_end - mm->arg_start; //得到main参数的长度

int res = access_pro(task, mm->arg_start, buffer, len);

printk("aaaa:%s/n",buffer);

//access_pro函数实际上为access_process_vm:

int access_pro(struct task_struct *tsk, unsigned long addr, void *buf, int len)

{

         struct mm_struct *mm;

         struct vm_area_struct *vma;

         struct page *page;

         void *old_buf = buf;

         mm = get_task_mm(tsk);

         if (!mm)

                 return 0;

         down_read(&mm->mmap_sem);

         while (len) {

                 int bytes, ret, offset;

                 void *maddr;

                 ret = get_user_pages(tsk, mm, addr, 1, 0, 1, &page, &vma);

                 if (ret <= 0)

                         break;

                 bytes = len;

                 offset = addr & (PAGE_SIZE-1);

                 if (bytes > PAGE_SIZE-offset)

                         bytes = PAGE_SIZE-offset;

                 maddr = kmap(page);

                 copy_from_user_page(vma, page, addr,buf, maddr + offset, bytes);

                 kunmap(page);

                 page_cache_release(page);

                 len -= bytes;

                 buf += bytes;

                 addr += bytes;

         }

         up_read(&mm->mmap_sem);

         mmput(mm);        

         return buf - old_buf;

}

以上代码实际上是从内核代码当中摘抄改编的,虽然有现成的函数可用,但是为了彻底理解linux内核,还是最好自己实现一番,反过来仅仅为了开发的话,就拿来主义吧,自己实现纯粹是浪费时间。
从linux得到进程或者文件全路径的方式我们可以看出linux设计上的一些思路,在linux当中,进程的意义更加纯粹,就是一个执行绪,文件的意义也更加纯粹,实际上linux中,每件事物的意义都很纯粹,因为它们的设计粒度非常小,而且机制非常正交。


原文链接:http://blog.csdn.net/dog250/article/details/5303652
加载中
返回顶部
顶部