引子
当我们执行cat /proc/pid/smaps读取某个进程对应的虚拟内存区间到信息显示给我们,整个过程究竟发生了什么呢?
>>用户态open("/proc/pid/smaps")--> 内核proc_pid_smaps_operations.open()
>>用户态 read(fd) --> 内核proc_pid_smaps_operations.read()
其中open()函数最终会返回一个文件描述符fd供后续read(fd)函数使用。
内核实现
我们先看一下open()和read()进入在内核中是如何实现的,它们都定义在proc_pid_smaps_operations结构中:
const struct file_operations proc_pid_smaps_operations = {
.open = pid_smaps_open,
.read = seq_read,
.llseek = seq_lseek,
.release = proc_map_release,
};
可以看到proc_pid_smaps_operations()函数实现了内核中open、read的操作函数。
两个函数的具体实现如下:
(1)read
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
(2)open
static int pid_smaps_open(struct inode *inode, struct file *file)
此函数通过参数inode找到进程相关的结构并放到file的私有数据结构中;
然而只有进程相关的数据结构还不够, 因为后面的read陷入内核后调用的seq_read()函数是内核的一个通用架构的函数,它并未实现特定的proc文件对应的特定动作。因而,特定的proc文件还需要提供自己特有的文件操作方法供通用的seq_read()调用。
因此,pid_smaps_open()函数里面还加入了一个file_operations参数&proc_pid_smaps_op,专门为读取进程虚拟内存区(vma)信息提供了自己的实现方法。
static int pid_smaps_open(struct inode *inode, struct file *file)
{
return do_maps_open(inode, file, &proc_pid_smaps_op);
}
我们也看看proc_pid_smaps_op这个file_operations结构具体是如何实现的吧:
static const struct seq_operations proc_pid_smaps_op = {
.start = m_start, //返回进程的第一VMA
.next = m_next, //返回进程的下一个VMA
.stop = m_stop,
.show = show_pid_smap //打印某一个VMA的详细信息
};
private_data建立起桥梁
如果你是内核的设计者,在有了这些数据结构之后你会如何设计seq_read()来读取一个进程所有的vma信息呢?
我们首先来看看seq_read()函数的参数:文件对应的内核数据结构file,用户态buf用于存放读取到的信息,size和ppos分别是大小和偏移。通用的seq_read()函数要将进程的vma信息读取给用户的buf,似乎也只有靠参数struct file *file来和具体的进程产生关联了。
没错,它有一个struct seq_file指针类型的成员file->private_data,看名字我们也能够猜得到:这个private_data成员专门存放私人数据,即特定的proc文件相关的内容。对应到我们的场景就是进程相关的结构和smaps文件操作函数集proc_pid_smaps_op结构。咱们的pid_smaps_open()函数主要工作就是这件事:将进程相关的结构proc_maps_private和smaps文件操作函数结构放到struct seq_file结构指针类型的file->private_data中:
struct seq_file {
......
const struct seq_operations *op; //存放smaps文件的函数句柄proc_pid_smaps_op
......
void *private; //存放特定文件的私有数据proc_maps_private
};
即:
file->private_data->op = proc_pid_smaps_op
file->private_data->private = proc_maps_private //(应该说是一个struct proc_maps_private *的一个实例)
结构proc_pid_smaps_op前面我们已经了解过了;proc_maps_private又是什么呢?
struct proc_maps_private {
struct inode *inode;
struct task_struct *task;
struct mm_struct *mm;
#ifdef CONFIG_MMU
struct vm_area_struct *tail_vma;
#endif
#ifdef CONFIG_NUMA
struct mempolicy *task_mempolicy;
#endif
};
看到了吧,这个proc_maps_private结构存放了文件的inode,进程描述符、进程的内存描述符等等进程相关的结构。
这样在丰满了file->private_data结构后,后续的seq_read()就可以通过参数file里面的内容来按部就班做smaps的读取操作了。
总结一下:
cat /proc/pid/smaps主要有先后两个步骤:open()和read()。
open()陷入内核后会通过/proc/pid/smaps文件相关的inode来找到进程相关信息并搜集到proc_maps_private结构中,最后将proc_maps_private结构和smaps文件具体的操作函数结构放到file->private_data中,最终open()函数返回smaps文件对应的文件描述符fd;
read()函数通过入参fd找到前面open()函数准备好的file结构,进行读取操作,详细情况将在下一集讲述。