Linux 下获查看某个进程(PID)所属的目录

在维护服务器的过程中,有时候会出现一些进程没人认领,这个时候会影响资源的合理分配,也担心系统被人入侵。使用nvidia-smi 以及htop,top也只能知道是哪个进程(PID)占用的资源,但是并不能知道是谁的程序。此时可以通过ll /proc/PID 指令来查看进程所属的目录从而就可以知道是谁的程序了。

nvidia-smi
Thu Feb 22 09:16:48 2024 
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.154.05 Driver Version: 535.154.05 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA GeForce RTX 4090 Off | 00000000:01:00.0 On | Off |
| 31% 59C P2 347W / 450W | 8845MiB / 24564MiB | 98% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 2178 G /usr/lib/xorg/Xorg 116MiB |
| 0 N/A N/A 2410 G /usr/bin/gnome-shell 69MiB |
| 0 N/A N/A 2017416 C python 8642MiB |
+---------------------------------------------------------------------------------------+
top
2017416 ai        20   0   27.1g   4.2g   2.0g S 113.3  13.6 783:50.75 python                                                                                                                                                             
2018057 ai        20   0   14.7g   2.0g  96436 S  40.0   6.4  61:15.49 python                                                                                                                                                             
      1 root      20   0  166760   8448   5632 S   0.0   0.0   0:03.58 systemd                                                                                                                                                            
      2 root      20   0       0      0      0 S   0.0   0.0   0:00.07 kthreadd                                                                                                                                                           
      3 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_gp                                                                                                                                                             
      4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_par_gp                                                                                                                                                         
      5 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 slub_flushwq                                                                                                                                                       
      6 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 netns                                                                                                                                                              
      8 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/0:0H-events_highpri                                                                                                                                        
     11 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 mm_percpu_wq                                                                                                                                                       
     12 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_kthread                                                                                                                                                  
     13 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_rude_kthread                                                                                                                                             
     14 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_trace_kthread                                                                                                                                            
     15 root      20   0       0      0      0 S   0.0   0.0   0:05.19 ksoftirqd/0                                                                                                                                                        
     16 root      20   0       0      0      0 I   0.0   0.0   3:19.23 rcu_preempt                                                                                                                                                        
     17 root      rt   0       0      0      0 S   0.0   0.0   0:01.70 migration/0                                                                                                                                                        
     18 root     -51   0       0      0      0 S   0.0   0.0   0:00.00 idle_inject/0                                                                                                                                                      
     19 root      20   0       0      0      0 S   0.0   0.0   0:00.00 cpuhp/0                                                                                                                                                            
     20 root      20   0       0      0      0 S   0.0   0.0   0:00.00 cpuhp/2                                                                                                                                                            
     21 root     -51   0       0      0      0 S   0.0   0.0   0:00.00 idle_inject/2                                                                                                                                                      
     22 root      rt   0       0      0      0 S   0.0   0.0   0:01.61 migration/2                                                                                                                                                        
     23 root      20   0       0      0      0 S   0.0   0.0   0:03.92 ksoftirqd/2                                                                                                                                                        
     25 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/2:0H-events_highpri                                                                                                                                        
     26 root      20   0       0      0      0 S   0.0   0.0   0:00.00 cpuhp/4                                                                                                                                                            
     27 root     -51   0       0      0      0 S   0.0   0.0   0:00.00 idle_inject/4                                                                                                                                                      
     28 root      rt   0       0      0      0 S   0.0   0.0   0:01.69 migration/4                                                                                                                                                        
     29 root      20   0       0      0      0 S   0.0   0.0   0:04.04 ksoftirqd/4                                                                                                                                                        
     31 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/4:0H-events_highpri                                                                                                                                        
     32 root      20   0       0      0      0 S   0.0   0.0   0:00.00 cpuhp/6                                                                                                                                                            
     33 root     -51   0       0      0      0 S   0.0   0.0   0:00.00 idle_inject/6  
htop

0[|| 1.3%] 4[|| 2.6%] 8[|||||| 20.3%] 12[||| 7.7%] 16[| 0.7%] 20[ 0.0%] 24[ 0.0%] 28[ 0.0%]
1[| 0.7%] 5[ 0.0%] 9[||||||||| 36.8%] 13[ 0.0%] 17[ 0.0%] 21[ 0.0%] 25[ 0.0%] 29[ 0.0%]
2[| 2.6%] 6[||| 5.8%] 10[|||||||||||||| 54.6%] 14[||| 7.1%] 18[| 0.7%] 22[ 0.0%] 26[ 0.0%] 30[ 0.0%]
3[|| 1.3%] 7[ 0.0%] 11[|| 3.3%] 15[ 0.0%] 19[ 0.0%] 23[ 0.0%] 27[ 0.0%] 31[ 0.0%]
Mem[||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||18.0G/31.1G] Tasks: 166, 511 thr, 349 kthr; 0 running
Swp[ 0K/0K] Load average: 1.36 2.26 11.68
Uptime: 2 days, 21:36:55

[Main] [I/O]
PID USER PRI NI VIRT RES SHR S CPU%▽MEM% TIME+ Command
2017416 ai 20 0 27.1G 4315M 2063M R 75.0 13.6 10h20:48 python train.py
2018408 ai 20 0 27.1G 4315M 2063M S 26.1 13.6 1h47:53 python train.py
2017993 ai 20 0 14.6G 1954M 96436 S 6.5 6.1 1h02:37 python train.py
2018025 ai 20 0 14.5G 1894M 96436 S 5.9 5.9 56:29.18 python train.py
2018057 ai 20 0 14.7G 2014M 96436 R 4.6 6.3 57:31.94 python train.py
2017929 ai 20 0 14.5G 1894M 96436 S 3.3 5.9 58:48.62 python train.py
2018089 ai 20 0 14.8G 2159M 97460 S 3.3 6.8 59:42.36 python train.py
2018121 ai 20 0 14.5G 1896M 97204 S 3.3 6.0 1h00:38 python train.py
2018153 ai 20 0 14.8G 2173M 97204 S 3.3 6.8 58:03.87 python train.py
2017961 ai 20 0 14.5G 1895M 97460 S 2.6 6.0 1h01:09 python train.py
2018154 ai 20 0 27.1G 4315M 2063M S 2.0 13.6 27:33.90 python train.py
2051932 ai 20 0 7264 5376 3072 R 2.0 0.0 0:00.13 /snap/htop/4079/usr/local/bin/htop
47203 ai 20 0 703M 10132 6656 S 0.7 0.0 0:30.88 ./natapp -authtoken=6067e3183f0dba3e
2018225 ai 20 0 14.7G 2014M 96436 S 0.7 6.3 3:32.35 python train.py
2018226 ai 20 0 14.8G 2173M 97204 S 0.7 6.8 3:40.85 python train.py
2018227 ai 20 0 14.5G 1894M 96436 S 0.7 5.9 3:53.54 python train.py
2018228 ai 20 0 14.6G 1954M 96436 S 0.7 6.1 3:32.01 python train.py
2018229 ai 20 0 14.5G 1894M 96436 S 0.7 5.9 3:44.15 python train.py
2018230 ai 20 0 14.5G 1896M 97204 S 0.7 6.0 3:33.56 python train.py
2018236 ai 20 0 14.8G 2159M 97460 S 0.7 6.8 3:47.90 python train.py
2018238 ai 20 0 14.5G 1895M 97460 S 0.7 6.0 3:35.48 python train.py
2023173 ai 20 0 18236 8120 5632 S 0.7 0.0 0:05.17 sshd: ai@pts/1,pts/2
2023241 ai 20 0 17644 4864 2816 S 0.7 0.0 0:02.69 top
2024703 ai 20 0 18076 8376 5632 S 0.7 0.0 0:03.87 sshd: ai@pts/3,pts/4
1 root 20 0 162M 8448 5632 S 0.0 0.0 0:03.57 /sbin/init splash
F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit

一、应用层

1.1 /proc/pid/exe

以top进程为例:

[root@localhost ~]# ps -ef | grep top
root     31386 15859  0 14:58 pts/2    00:00:00 top

top进程的pid为31386 ,可以通过查看 /proc/pid/exe:

在Linux系统中,每个进程都有一个/proc/pid/exe文件,它是一个符号链接文件,指向当前进程的可执行文件。

更具体地说,/proc/pid/exe文件是一个符号链接文件,它的内容是一个指向当前进程可执行文件的绝对路径的符号链接。例如,如果当前进程的可执行文件是/usr/bin/myprogram,那么/proc/pid/exe文件的内容将是/usr/bin/myprogram的绝对路径。

通过访问/proc/pid/exe文件,可以快速获取当前进程的可执行文件路径。

[root@localhost ~]# ls -l /proc/31386/exe
lrwxrwxrwx. 1 root root 0 5月  18 14:59 /proc/31386/exe -> /usr/bin/top

其中/usr/bin/top为top进程可执行文件所在的绝对文件路径。

由于/proc/31386/exe是符号链接,直接调用 readlink 命令获取该进程可执行文件所在的绝对文件路径:

[root@localhost ~]# readlink /proc/31386/exe
/usr/bin/top

其中/usr/bin/top为top进程可执行文件所在的绝对文件路径。

由于/proc/31386/exe是符号链接,直接调用 readlink 命令获取该进程可执行文件所在的绝对文件路径:

[root@localhost ~]# readlink /proc/31386/exe
/usr/bin/top
NAME
       readlink - print resolved symbolic links or canonical file names
       
        Print value of a symbolic link or canonical file name
#include 
#include <linux/limits.h>

int main()
{
    char task_absolute_path[PATH_MAX];
 
    int cnt = readlink( "/proc/self/exe", task_absolute_path, PATH_MAX);
    if (cnt < 0){
        printf("readlink is error\n");
        return -1;
    }
    task_absolute_path[cnt] = '\0';
    printf("task absolute path:%s\n", task_absolute_path);

    return 0;
}

readlink()函数用于读取符号链接文件的内容,符号链接文件的内容就是一个路径,即解析符号链接。使用 readlink 读取符号链接,获取的就是符号链接的内容,符号链接的内容的就是目标文件的路径。

当然对于top这种系统shell命令,可以用which和whereis查看:

[root@localhost ~]# which top
/usr/bin/top
[root@localhost ~]# whereis top
top: /usr/bin/top /usr/share/man/man1/top.1.gz

对于普通程序通常用 /proc/pid/exe进行查看。

1.2 /proc/pid/cwd
在Linux系统中,每个进程都有一个/proc/pid/cwd文件,它是一个符号链接文件,指向当前进程的工作目录。
如下所示,我在 /root/link/test1/ 目录下运行 top 命令。

[root@localhost ~]# ls -l /proc/31386/cwd
lrwxrwxrwx. 1 root root 0 5月  18 15:02 /proc/31386/cwd -> /root/link/test1
[root@localhost ~]# readlink /proc/31386/cwd
/root/link/test1

更具体地说,/proc/pid/cwd文件是一个符号链接文件,它的内容是一个指向进程号为 pid 工作目录的绝对路径的符号链接。例如,如果当前进程的工作目录是/home/user,那么/proc/pid/cwd文件的内容将是/home/user的绝对路径。

通过访问/proc/pid/cwd文件,可以快速获取当前进程的工作目录路径,而无需在程序中调用getcwd()函数等获取当前工作目录的系统调用。

需要注意的是,/proc/pid/cwd文件是一个符号链接文件,它指向的路径可能会随着进程的工作目录的变化而变化,因此在读取它的内容时,应该对返回值进行错误检查,以确保它确实指向当前进程的工作目录。

比如函数 getcwd():

NAME
       getcwd, getwd, get_current_dir_name - get current working directory

SYNOPSIS
       #include 

       char *getcwd(char *buf, size_t size);

1.3 代码示例

接下来给出一段代码,根据输入的参数进程pid号,开获取该进程可执行程序的绝对路径和当前在哪个目录执行:

#include 
#include 
#include 
#include 
#include <linux/limits.h>

#define PROC_PATH_LEN 64

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char proc_path[PROC_PATH_LEN] = {0};
    snprintf(proc_path, sizeof(proc_path), "/proc/%s/exe", argv[1]);

    char exe_path[PATH_MAX] = {0};
    ssize_t len = readlink(proc_path, exe_path, sizeof(exe_path));
    if (len == -1) {
        perror("readlink");
        exit(EXIT_FAILURE);
    }
    exe_path[len] = '\0';
    printf("Executable path: %s\n", exe_path);

    memset(proc_path, 0, PROC_PATH_LEN);
    snprintf(proc_path, sizeof(proc_path), "/proc/%s/cwd", argv[1]);
    char cwd_path[PATH_MAX] = {0};
    len = readlink(proc_path, cwd_path, sizeof(cwd_path));
    if (len == -1) {
        perror("readlink");
        exit(EXIT_FAILURE);
    }
    cwd_path[len] = '\0';
    printf("pid current path: %s\n", cwd_path);

    return 0;
}

二、内核态获取

2.1 相对应的函数与结构体

struct fs_struct *fs 描述了文件系统和进程相关的信息:

// linux-3.10/include/linux/sched.h

struct task_struct {
	......
	/* filesystem information */
	struct fs_struct *fs;
	......
}
// linux-3.10/include/linux/fs_struct.h

struct fs_struct {
	int users;
	spinlock_t lock;
	seqcount_t seq;
	int umask;
	int in_exec;
	struct path root, pwd;
};

其中 struct path root 表示根目录路径,通常都是 / 目录,但是通过chroot系统调用后,对于进程来说会将 / 目录变成了某个子目录,那么相应的进程就是使用该子目录而不是全局的根目录,该进程会将该子目录当作其根目录。

chroot - run command or interactive shell with special root directory

Run COMMAND with root directory set to NEWROOT.

struct path pwd就是当前工作目录。

// linux-3.10/include/linux/path.h

struct path {
	struct vfsmount *mnt;
	struct dentry *dentry;
};
// linux-3.10/include/linux/dcache.h

/*
 * "quick string" -- eases parameter passing, but more importantly
 * saves "metadata" about the string (ie length and the hash).
 *
 * hash comes first so it snuggles against d_parent in the
 * dentry.
 */
struct qstr {
	......
	const unsigned char *name;
};

struct dentry {
	......
	struct qstr d_name;
	......
}

// linux-3.10/include/linux/sched.h

struct task_struct {
	......
	struct mm_struct *mm;
	......
}

从task_struct获取路径基本通过mm_struct这个结构,从中可以获取进程全路径。

// 获取进程全路径
task_struct->mm->exe_file->f_path

将进程的所在的文件路径存储到 /proc//exe symlink中:

// linux-3.10/include/linux/mm_types.h

struct mm_struct {
	......
	/* store ref to file /proc//exe symlink points to */
	struct file *exe_file;
	......
}
// linux-3.10/include/linux/fs.h

struct file {
	......
	struct path		f_path;
	......
}

(1) 通过dentry_path_raw获取文件的全路径,低版本比如2.6.32没有该API

// linux-3.10/fs/dcache.c

static int prepend(char **buffer, int *buflen, const char *str, int namelen)
{
	*buflen -= namelen;
	if (*buflen < 0) return -ENAMETOOLONG; *buffer -= namelen; memcpy(*buffer, str, namelen); return 0; } static int prepend_name(char **buffer, int *buflen, struct qstr *name) { return prepend(buffer, buflen, name->name, name->len);
}

/*
 * Write full pathname from the root of the filesystem into the buffer.
 */
static char *__dentry_path(struct dentry *dentry, char *buf, int buflen)
{
	char *end = buf + buflen;
	char *retval;

	prepend(&end, &buflen, "\0", 1);
	if (buflen < 1) goto Elong; /* Get '/' right */ retval = end-1; *retval = '/'; while (!IS_ROOT(dentry)) { struct dentry *parent = dentry->d_parent;
		int error;

		prefetch(parent);
		spin_lock(&dentry->d_lock);
		error = prepend_name(&end, &buflen, &dentry->d_name);
		spin_unlock(&dentry->d_lock);
		if (error != 0 || prepend(&end, &buflen, "/", 1) != 0)
			goto Elong;

		retval = end;
		dentry = parent;
	}
	return retval;
Elong:
	return ERR_PTR(-ENAMETOOLONG);
}

char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen)
{
	char *retval;

	write_seqlock(&rename_lock);
	retval = __dentry_path(dentry, buf, buflen);
	write_sequnlock(&rename_lock);

	return retval;
}
EXPORT_SYMBOL(dentry_path_raw);

struct file *filp;
dentry_path_raw(filp->f_path.dentry,buf,buflen);

(2)通过d_path获取文件的全路径

// linux-3.10/fs/dcache.c

/**
 * d_path - return the path of a dentry
 * @path: path to report
 * @buf: buffer to return value in
 * @buflen: buffer length
 *
 * Convert a dentry into an ASCII path name. If the entry has been deleted
 * the string " (deleted)" is appended. Note that this is ambiguous.
 *
 * Returns a pointer into the buffer or an error code if the path was
 * too long. Note: Callers should use the returned pointer, not the passed
 * in buffer, to use the name! The implementation often starts at an offset
 * into the buffer, and may leave 0 bytes at the start.
 *
 * "buflen" should be positive.
 */
char *d_path(const struct path *path, char *buf, int buflen)
{
	char *res = buf + buflen;
	struct path root;
	int error;

	/*
	 * We have various synthetic filesystems that never get mounted.  On
	 * these filesystems dentries are never used for lookup purposes, and
	 * thus don't need to be hashed.  They also don't need a name until a
	 * user wants to identify the object in /proc/pid/fd/.  The little hack
	 * below allows us to generate a name for these objects on demand:
	 */
	if (path->dentry->d_op && path->dentry->d_op->d_dname)
		return path->dentry->d_op->d_dname(path->dentry, buf, buflen);

	get_fs_root(current->fs, &root);
	br_read_lock(&vfsmount_lock);
	write_seqlock(&rename_lock);
	error = path_with_deleted(path, &root, &res, &buflen);
	write_sequnlock(&rename_lock);
	br_read_unlock(&vfsmount_lock);
	if (error < 0)
		res = ERR_PTR(error);
	path_put(&root);
	return res;
}
EXPORT_SYMBOL(d_path);

调用d_path函数文件的路径时,应该使用返回的指针而不是转递进去的参数 buf 。
原因是该函数的实现通常从缓冲区的偏移量开始。

内核中用到d_path的例子:

// linux-3.10/include/linux/mm_types.h

/*
 * This struct defines a memory VMM memory area. There is one of these
 * per VM-area/task.  A VM area is any part of the process virtual memory
 * space that has a special rule for the page-fault handlers (ie a shared
 * library, the executable area etc).
 */
struct vm_area_struct {
	......
	struct file * vm_file;		/* File we map to (can be NULL). */
	......
}
// linux-3.10/include/linux/fs.h

struct file {
	......
	struct path		f_path;
	......
}
// linux-3.10/mm/memory.c

/*
 * Print the name of a VMA.
 */
void print_vma_addr(char *prefix, unsigned long ip)
{
	struct mm_struct *mm = current->mm;
	struct vm_area_struct *vma;

	/*
	 * Do not print if we are in atomic
	 * contexts (in exception stacks, etc.):
	 */
	if (preempt_count())
		return;

	down_read(&mm->mmap_sem);
	vma = find_vma(mm, ip);
	if (vma && vma->vm_file) {
		struct file *f = vma->vm_file;
		//使用伙伴系统接口,分配一个物理页,返回一个内核虚拟地址
		char *buf = (char *)__get_free_page(GFP_KERNEL);
		if (buf) {
			char *p;

			p = d_path(&f->f_path, buf, PAGE_SIZE);
			if (IS_ERR(p))
				p = "?";
			printk("%s%s[%lx+%lx]", prefix, kbasename(p),
					vma->vm_start,
					vma->vm_end - vma->vm_start);
			free_page((unsigned long)buf);
		}
	}
	up_read(&mm->mmap_sem);
}

2.2 API演示
在这里只是简单的给出怎么在内核态获取进程所在文件的路径,详细的话请参考内核源码,在第三节给出内核源码获取进程所在文件的路径的方法。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/fs.h>
#include <linux/fs_struct.h>
#include <linux/path.h>

#define TASK_PATH_MAX_LENGTH 512

//内核模块初始化函数
static int __init lkm_init(void)
{
    struct qstr root_task_path;
    struct qstr current_task_path;

    char buf_1[TASK_PATH_MAX_LENGTH] = {0};
    char *task_path_1 = NULL;

    char buf_2[TASK_PATH_MAX_LENGTH] = {0};
    char *task_path_2 = NULL;

	//获取当前目录名
    current_task_path = current->fs->pwd.dentry->d_name;
    //获取根目录
    root_task_path = current->fs->root.dentry->d_name;

	//内核线程的 mm 成员为空,这里没做判断
	
    //2.6.32 没有dentry_path_raw API
    //获取文件全路径
    task_path_1 = dentry_path_raw(current->mm->exe_file->f_path.dentry, buf_1, TASK_PATH_MAX_LENGTH);

	//获取文件全路径
	//调用d_path函数文件的路径时,应该使用返回的指针:task_path_2 ,而不是转递进去的参数buf:buf_2
    task_path_2 = d_path(&current->mm->exe_file->f_path, buf_2, TASK_PATH_MAX_LENGTH);
    if (IS_ERR(task_path_2)) {
        printk("Get path failed\n");
        return -1;
    }

    printk("current path = %s\n", current_task_path.name);
    printk("root path = %s\n", root_task_path.name);
    printk("task_path_1 = %s\n", task_path_1);
    printk("task_path_2 = %s\n", task_path_2);

	return -1;
}

module_init(lkm_init);

MODULE_LICENSE("GPL");

结果展示:

[root@localhost task_path]# dmesg -c
[415299.952165] current path = task_path
[415299.952172] root path = /
[415299.952176] task_path_1 = /usr/bin/kmod
[415299.952179] task_path_2 = /usr/bin/kmod

三、内核源码实现

// linux-3.10/fs/proc/base.c

/* NOTE:
 *	Implementing inode permission operations in /proc is almost
 *	certainly an error.  Permission checks need to happen during
 *	each system call not at open time.  The reason is that most of
 *	what we wish to check for permissions in /proc varies at runtime.
 *
 *	The classic example of a problem is opening file descriptors
 *	in /proc for a task before it execs a suid executable.
 */

struct pid_entry {
	char *name;
	int len;
	umode_t mode;
	const struct inode_operations *iop;
	const struct file_operations *fop;
	union proc_op op;
};

static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
{
	struct task_struct *task;
	struct mm_struct *mm;
	struct file *exe_file;

	task = get_proc_task(dentry->d_inode);
	if (!task)
		return -ENOENT;
	mm = get_task_mm(task);
	put_task_struct(task);
	if (!mm)
		return -ENOENT;
	exe_file = get_mm_exe_file(mm);
	mmput(mm);
	if (exe_file) {
		*exe_path = exe_file->f_path;
		path_get(&exe_file->f_path);
		fput(exe_file);
		return 0;
	} else
		return -ENOENT;
}

/*
 * Tasks
 */
static const struct pid_entry tid_base_stuff[] = {
	DIR("fd",        S_IRUSR|S_IXUSR, proc_fd_inode_operations, proc_fd_operations),
	......
	REG("comm",      S_IRUGO|S_IWUSR, proc_pid_set_comm_operations),
	......
	LNK("cwd",       proc_cwd_link),
	LNK("root",      proc_root_link),
	LNK("exe",       proc_exe_link),

// linux-3.10/fs/proc/base.c

static int do_proc_readlink(struct path *path, char __user *buffer, int buflen)
{
	//由于这里申请的是一个页大小,便没有使用 kmalloc接口,调用伙伴系统接口分配一个物理页,返回内核虚拟地址
	char *tmp = (char*)__get_free_page(GFP_TEMPORARY);
	char *pathname;
	int len;

	if (!tmp)
		return -ENOMEM;

	//获取进程所在文件的路径
	pathname = d_path(path, tmp, PAGE_SIZE);
	len = PTR_ERR(pathname);
	if (IS_ERR(pathname))
		goto out;
	len = tmp + PAGE_SIZE - 1 - pathname;

	if (len > buflen)
		len = buflen;
		
	//把进程所在文件的路径拷贝到用户空间:char __user *buffer
	//用户空间调用 readlink /proc/pid/
	if (copy_to_user(buffer, pathname, len))
		len = -EFAULT;
 out:
	free_page((unsigned long)tmp);
	return len;
}

static int proc_pid_readlink(struct dentry * dentry, char __user * buffer, int buflen)
{
	int error = -EACCES;
	struct inode *inode = dentry->d_inode;
	struct path path;

	/* Are we allowed to snoop on the tasks file descriptors? */
	if (!proc_fd_access_allowed(inode))
		goto out;

	error = PROC_I(inode)->op.proc_get_link(dentry, &path);
	if (error)
		goto out;

	error = do_proc_readlink(&path, buffer, buflen);
	path_put(&path);
out:
	return error;
}

const struct inode_operations proc_pid_link_inode_operations = {
	.readlink	= proc_pid_readlink,
	.follow_link	= proc_pid_follow_link,
	.setattr	= proc_setattr,
};


// linux-3.10/fs/proc/internal.h
union proc_op {
	int (*proc_get_link)(struct dentry *, struct path *);
	int (*proc_read)(struct task_struct *task, char *page);
	int (*proc_show)(struct seq_file *m,
		struct pid_namespace *ns, struct pid *pid,
		struct task_struct *task);
};

struct proc_inode {
	struct pid *pid;
	int fd;
	union proc_op op;
	struct proc_dir_entry *pde;
	struct ctl_table_header *sysctl;
	struct ctl_table *sysctl_entry;
	struct proc_ns ns;
	struct inode vfs_inode;
};

/*
 * General functions
 */
static inline struct proc_inode *PROC_I(const struct inode *inode)
{
	return container_of(inode, struct proc_inode, vfs_inode);
}


其中:

proc_pid_readlink()
	-->do_proc_readlink(){
			char __user *buffer;
			copy_to_user(buffer, pathname, len);
		}

比如:当用户调用 readlink /proc/pid/exe,将该文件内容拷贝到用户空间:char __user *buffer中。