linux内核中得到进程全路径

简介:

在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中,每件事物的意义都很纯粹,因为它们的设计粒度非常小,而且机制非常正交。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273421


相关文章
|
9天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
2天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
4天前
|
监控 Linux
linux监控指定进程
请注意,以上步骤提供了一种基本的方式来监控指定进程。根据你的需求,你可以选择使用不同的工具和参数来获取更详细的进程信息。
10 0
|
5天前
|
消息中间件 监控 Linux
Linux进程和计划任务管理
通过这些命令和工具,你可以有效地管理Linux系统中的进程和计划任务,监控系统的运行状态并保持系统的稳定和可靠性。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
98 2
|
8天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
18 3
|
11天前
|
监控 Java Linux
linux下监控java进程 实现自动重启服务
linux下监控java进程 实现自动重启服务
|
12天前
|
监控 Linux Shell
初识Linux下进程2
初识Linux下进程2
|
15天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
5月前
|
存储 算法 Linux
探索Linux内核内存伙伴算法:优化系统性能的关键技术!
探索Linux内核内存伙伴算法:优化系统性能的关键技术!
|
11月前
|
编译器 Linux C语言
Linux内核27-优化和内存屏障
Linux内核27-优化和内存屏障