【推荐阅读】
proc文件系统是一种虚拟的文件系统,其信息不能从块设备读取。只有在读取文件内容时,才动态生成相应的信息。
/proc的内容
内存管理
系统进程的特征数据
文件系统
设备驱动程序
系统总线
电源管理
终端
系统控制参数
特定于进程的数据
cmdline:用于启动进程的命令行(用0作为分隔符,而不是空格)
environ表示为该程序设置的所有环境变量
maps以文本形式,列出了进程使用的所有库的内存映射。
status包含了有关进程状态的一般信息(不仅提供了有关UID/GID及进程其他信息,还包括内存分配,进程能力,各个信号掩码的状态)
stat和statm以一连串数字的形式,提供了进程及其内存消耗的更多状态信息。
fd:各个文件描述符,可以通过(ls -l),查看其文件的位置
cwd指向进程当前工作目录
exe指向包含了应用程序代码的二进制文件。
root指向进程的根目录。(不见得是全局的根目录)
一般性系统信息
与特定的内核子系统无关的一般性信息,一般存放在/proc下的文件中。
kcore是一个动态的内核文件,包含了运行中的内核的所有数据,即主内存的全部内容。与用户应用程序发生致命错误时内存转存所产生的普通内核文件相比,该文件没什么不同之处。可以将调试器用于该二进制文件,来查看运行中系统的当前状态。
网络信息
/proc/net子目录提供了内核的各种网络选项的有关数据
系统控制参数
/proc/sys/
数据结构
proc数据项的表示
proc文件系统中的每个数据项都由proc_dir_entry的一个实例描述,该结构定义了如下:
struct proc_dir_entry {
unsigned int low_ino;//inode编号
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;//指定了目录中子目录和符号链接的数目
gid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
/*
* NULL ->proc_fops means "PDE is going away RSN" or
* "PDE is just created". In either case, e.g. ->read_proc won't be
* called because it's too late or too early, respectively.
*
* If you're allocating ->proc_fops dynamically, save a pointer
* somewhere.
*/
const struct file_operations *proc_fops;
get_info_t *get_info;//指向相关子系统返回所需数据的例程
struct module *owner;
struct proc_dir_entry *next, *parent, *subdir;//parent:指向父目录的指针,subdir:指向一个目录中的第一个子数据项,next:将目录下的所有常见数据项都集成到一个单链表中
void *data;//作为read_proc,write_proc的参数传递
read_proc_t *read_proc;//指向的函数支持从内核读取数据
write_proc_t *write_proc;//指向的函数支持向内核写入数据
atomic_t count; /* use count */
int pde_users; /* number of callers into module in progress */
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
struct completion *pde_unload_completion;
shadow_proc_t *shadow_proc;
};
typedef int (read_proc_t)(char *page, char **start, off_t off,
int count, int *eof, void *data);
typedef int (write_proc_t)(struct file *file, const char __user *buffer,
unsigned long count, void *data);
proc inode
内核提供了一个数据结构,称之为proc_inode,支持以面向inode的方式来查看proc文件系统的数据项。
<linux/proc_fs.h>
union proc_op {
int (*proc_get_link)(struct inode *, struct dentry **, struct vfsmount **);//获得特定于进程的信息
int (*proc_read)(struct task_struct *task, char *page);//在虚拟文件系统中建立链接
};
struct proc_inode {
struct pid *pid;
int fd;
union proc_op op;
struct proc_dir_entry *pde;
struct inode vfs_inode;
};
初始化
<fs/proc/root.c>
void __init proc_root_init(void)
{
int err = proc_init_inodecache();//为proc_inode对象创建一个slab缓存
if (err)
return;
err = register_filesystem(&proc_fs_type);//注册文件系统
if (err)
return;
proc_mnt = kern_mount_data(&proc_fs_type, &init_pid_ns);//挂载文件系统
err = PTR_ERR(proc_mnt);
if (IS_ERR(proc_mnt)) {
unregister_filesystem(&proc_fs_type);
return;
}
proc_misc_init();//创建proc主目录中的各种文件项
proc_net_init();
#ifdef CONFIG_SYSVIPC
proc_mkdir("sysvipc", NULL);
#endif
proc_root_fs = proc_mkdir("fs", NULL);
proc_root_driver = proc_mkdir("driver", NULL);
proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */
#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)
/* just give it a mountpoint */
proc_mkdir("openprom", NULL);
#endif
proc_tty_init();
#ifdef CONFIG_PROC_DEVICETREE
proc_device_tree_init();
#endif
proc_bus = proc_mkdir("bus", NULL);
proc_sys_init();
}
装载proc文件系统
将特定于proc文件系统的超级块数据填充到一个vfsmount结构的实例中,使得新的文件系统能够集成到VFS树中。
<fs/proc/inode.c>
int proc_fill_super(struct super_block *s)
{
struct inode * root_inode;
s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
s->s_magic = PROC_SUPER_MAGIC;
s->s_op = &proc_sops;
s->s_time_gran = 1;
de_get(&proc_root);
root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);//为根目录创建一个inode
if (!root_inode)
goto out_no_root;
root_inode->i_uid = 0;
root_inode->i_gid = 0;
s->s_root = d_alloc_root(root_inode);//
if (!s->s_root)
goto out_no_root;
return 0;
out_no_root:
printk("proc_read_super: get root inode failed\n");
iput(root_inode);
de_put(&proc_root);
return -ENOMEM;
}
proc文件系统中,根inode与其他inode的不同之处在于,它不仅包含“普通”的文件和目录,还管理着特定于进程的PID目录。
管理/proc数据项
数据项的创建和注册
数据项分两个步骤添加到proc文件系统。首先,创建proc_dir_entry的一个新实例,填充描述该数据项的所有需要的信息。然后,将该实例注册到proc的数据结构。
create_proc_entry->proc_register
proc_register会根据项的类型,选择适当的inode与file的operations.
查找proc数据项
按以前的讨论,该函数将调用inode_operations的lookup函数指针。根据文件名的各个路径分量,来确定文件名所对应的inode
inode->look_up
读取和写入信息
内核使用保存在proc_file_operations中的操作来读写常规proc数据项的内容:
static const struct file_operations proc_file_operations = {
.llseek = proc_file_lseek,
.read = proc_file_read,
.write = proc_file_write,
};
proc_file_read的实现
从proc文件读取数据的操作分为3个步骤
分配一个内核内存页面,产生的数据将填充到页面中
调用一个特定于文件的函数,向内核内存页面填入数据
数据从内核空间复制到用户空间。
<fs/proc/generic.c>
proc_file_read(struct file *file, char __user *buf, size_t nbytes,
loff_t *ppos)
{
...
if (dp->get_info) {
/* 处理旧的网络例程 */
n = dp->get_info(page, &start, *ppos, count);
if (n < count)
eof = 1;
} else if (dp->read_proc) {
n = dp->read_proc(page, &start, *ppos,
count, &eof, dp->data);
} else
break;
...
}
内核在 proc_dir_entry 结构中提供了两个函数指针get_info 和 read_proc。这两个函数用于读取数据,而内核必须选择一个匹配的来使用。
proc_file_write的实现
static ssize_t
proc_file_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct proc_dir_entry * dp;
dp = PDE(inode);
if (!dp->write_proc)
return -EIO;
/* FIXME: does this routine need ppos? probably... */
return dp->write_proc(file, buffer, count, dp->data);
}
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】前100进群领取,额外赠送一份 内核资料包(含视频教程、电子书、实战项目及代码)
进程相关的信息
输出与系统进程相关的详细信息,是proc文件系统最初设计的主要任务之一。
proc_pid_lookup负责打开/proc/中特定于PID的文件。(创建一个inode作为第一个对象,用于后续的特定于PID的操作)。
<fs/proc/base.c>
struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd)
{
struct dentry *result = ERR_PTR(-ENOENT);
struct task_struct *task;
unsigned tgid;
struct pid_namespace *ns;
result = proc_base_lookup(dir, dentry);//处理self目录的情况(创建一个inode表示self,并初始化inode operations结构中的链接相关操作)
if (!IS_ERR(result) || PTR_ERR(result) != -ENOENT)
goto out;
tgid = name_to_int(dentry);//将PID字符串转换成整数
if (tgid == ~0U)
goto out;
ns = dentry->d_sb->s_fs_info;
rcu_read_lock();
task = find_task_by_pid_ns(tgid, ns);//找到task_struct结构
if (task)
get_task_struct(task);
rcu_read_unlock();
if (!task)
goto out;
result = proc_pid_instantiate(dir, dentry, task, NULL);//首先创建一个proc inode,然后初始化inode_operations为proc_tgid_base_inode_operations
put_task_struct(task);
out:
return result;
}
处理文件
在特定于PID的目录/proc/pid中处理一个文件时,这是使用该目录的inode操作完成的。
<fs/proc/base.c>
static const struct inode_operations proc_tgid_base_inode_operations = {
.lookup = proc_tgid_base_lookup,
.getattr = pid_getattr,
.setattr = proc_setattr,
};
proc_tgid_base_lookup的任务是根据给定的名称,返回一个inode实例
系统控制机制
操纵内核行为的传统方法是sysctl系统调用。
Linux借助于 proc 文件系统。内核重排了所有的sysctl,建立起一个层次结构,并导出到 /proc/sys 目录下。可以使用简单的用户空间工具来读取或操纵这些参数。要修改内核的运行时行为, cat 和 echo 就足够。
使用sysctl
<sysctl.h>
struct ctl_table
{
int ctl_name; /* 二进制ID */
const char *procname;/* /proc/sys下各目录项的文本ID,或NULL */
void *data;
int maxlen;
mode_t mode;
struct ctl_table *child;
struct ctl_table *parent;
proc_handler *proc_handler;
ctl_handler *strategy;
struct proc_dir_entry *de;
void *extra1;
void *extra2;
};
内核提供了ctl_table_header数据结构,使得能够将几个sysctl表维护在一个链表中
<sysctl.h>
struct ctl_table_header
{
ctl_table *ctl_table;
struct list_head ctl_entry;
...
};
对所有的sysctl都定义了静态的sysctl表,无论系统配置如何。根结点对应的表是 root_table ,用作所有静态定义的数据的根:
kernel/sysctl.c
static ctl_table root_table[];
static struct ctl_table_header root_table_header =
{ root_table, LIST_HEAD_INIT(root_table_header.ctl_entry) };
除了静态定义的sysctl之外,内核还提供了一个接口,用于动态注册和注销新的系统控制功能。register_sysctl_table 用于注册sysctl表,而其对应的 unregister_sysctl_table用于删除sysctl表,后者通常发生在模块卸载时。
register_sysctl_table 函数需要一个参数,一个指向 ctl_table 数组的指针,其中定义了新的sysctl层次结构。该函数由几个步骤组成。首先,创建一个新的 ctl_table_header 实例,并与目标sysctl表关联起来。然后,将 ctl_table_header 添加到现存sysctl层次结构的链表中。