浅析/proc/sys/目录下sysctl文件显示和read读操作全过程
《浅析linux内核__sysctl_head_next()函数内部实现》
1.安装/proc/sys目录对应的节点和文件操作方法集
proc_root_init
==>proc_sys_init();
int proc_sys_init(void)
{
proc_sys_root = proc_mkdir("sys", NULL); // 创建/proc/sys/目录,将de挂到/proc->subdir下面,以便proc_root_lookup使用,这里同时添加了 dp->low_ino唯一id号.
proc_sys_root->proc_iops = &proc_sys_inode_operations;// 替换为自己的iops,这样"sys"目录项dentry对应的inode的i_fop就等于proc_sys_inode_operations
proc_sys_root->proc_fops = &proc_sys_file_operations;
proc_sys_root->nlink = 0;
return 0;
}
static const struct inode_operations proc_sys_inode_operations = {
.lookup = proc_sys_lookup, // 用来检查sys/目录下名为dentry->d_name.name的文件或目录是否存在,sys/目录下所有有文件都是来自 sysctl_head_next链表上的header->table元素,如果存在,调用proc_sys_make_inode()为其创建节点.
.permission = proc_sys_permission,
.setattr = proc_sys_setattr,
};
2.执行cat /proc/sys/kernel/printk命令
proc_sys_inode_operations.proc_sys_lookup()将执行,
首先创建kernel目录项和inode节点,
然后创建printk目录项和inode节点[luther.gliethttp].
static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd)
{
struct ctl_table_header *head;
struct inode *inode;
struct dentry *err;
struct ctl_table *table;
err = ERR_PTR(-ENOENT);
// 比如查找/proc/sys/kernel目录,那么返回的table就是后面root_table[]中的
// static struct ctl_table root_table[] = {
// {
// .ctl_name = CTL_KERN,
// .procname = "kernel",
// .mode = 0555,
// .child = kern_table,
// },
// {
// .ctl_name = CTL_VM,
// .procname = "vm",
// .mode = 0555,
// .child = vm_table,
// },
// {
// .ctl_name = CTL_FS,
// .procname = "fs",
// .mode = 0555,
// .child = fs_table,
// },
// {
// .ctl_name = CTL_DEBUG,
// .procname = "debug",
// .mode = 0555,
// .child = debug_table,
// },
// {
// .ctl_name = CTL_DEV,
// .procname = "dev",
// .mode = 0555,
// .child = dev_table,
// },
// /*
// * NOTE: do not add new entries to this table unless you have read
// * Documentation/sysctl/ctl_unnumbered.txt
// */
// { .ctl_name = 0 }
// };
//{
// .ctl_name = CTL_KERN,
// .procname = "kernel",
// .mode = 0555,
// .child = kern_table,
//},
//kern_table[] = {
// ......
// #if defined CONFIG_PRINTK
// {
// .ctl_name = KERN_PRINTK,
// .procname = "printk",
// .data = &console_loglevel,
// .maxlen = 4*sizeof(int),
// .mode = 0644,
// .proc_handler = &proc_dointvec, // 操作.data = &console_loglevel,数据的方法[luther.gliethttp]
// },
// .......
//}
// 返回kernel所在table的child对应的printk对应table.[luther.gliethttp]
table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head); // 源码见后
if (!table)
goto out;
// 好了,现在这个就是printk所在的table了
err = ERR_PTR(-ENOMEM);
// 为该printk文件建立inode,源码见后
inode = proc_sys_make_inode(dir, table);
if (!inode)
goto out;
err = NULL;
dentry->d_op = &proc_sys_dentry_operations;
d_add(dentry, inode);
out:
sysctl_head_finish(head);
return err;
}
比如查找/proc/sys/kernel,那么这里parent就指向sys
static struct ctl_table *do_proc_sys_lookup(struct dentry *parent,
struct qstr *name,
struct ctl_table_header **ptr)
{
struct ctl_table_header *head;
struct ctl_table *table = NULL;
// 遍历root_table_header挂接的所有header
for (head = sysctl_head_next(NULL); head;
head = sysctl_head_next(head)) {
// 第一次见到的就是root_table,它的代码见后面[luther.gliethttp]
table = proc_sys_lookup_entry(parent, name, head->ctl_table); // 传入的是head->ctl_table顶层table
if (table)
break;
}
*ptr = head;
return table;
}
static struct ctl_table *proc_sys_lookup_entry(struct dentry *dparent,
struct qstr *name,
struct ctl_table *table)
{
// 传入的是head->ctl_table顶层table,所以这里的table为header_list所代表header的第一个顶层table
table = proc_sys_lookup_table(dparent, table);
if (table)
table = proc_sys_lookup_table_one(table, name); // 检查该table是否含有名为name的一个项.
return table;
}
static struct ctl_table *proc_sys_lookup_table(struct dentry *dentry,
struct ctl_table *table)
{
struct dentry *ancestor;
struct proc_inode *ei;
int depth, i;
// 首先取出dentry所代表的proc_inode对应的fd,该fd对应该dentry在目录体系中
// 自己所处目录深度[luther.gliethttp],
// /proc/sys/ 目录深度fd为0
// /proc/sys/kernel 目录深度fd为1
// /proc/sys/kernel/printk 目录深度fd为2
ei = PROC_I(dentry->d_inode);
depth = ei->fd;
if (depth == 0) // 因为这里查询的是"kernel"目录,所以dentry为"sys",所以它的depth等于0
return table;
// 如果这里查询的是/proc/sys/kernel/printk
// 那么dentry就指向了"kernel",所以这里depth就等于1
// 传入的是head->ctl_table顶层table,所以这里的table为header_list所代表header的第一个顶层table
// 所以下面将从1层开始,从顶层head_table依次遍历child,这样来检出所需目录或文件[luther.gliethttp]
for (i = 1; table && (i <= depth); i++) { // 保证i从1到depth之间,同时一旦当前table找不到ancestor->d_name文件,那么for将退出
ancestor = proc_sys_ancestor(dentry, i); // 从第1层开始,依次检查相应table中是否真实存在ancestor->d_name文件或目录[luther.gliethttp]
table = proc_sys_lookup_table_one(table, &ancestor->d_name); // 检查该table是否包含名为ancestor->d_name的文件或目录
if (table)
table = table->child; // 如果table包含名为ancestor->d_name的文件或目录,那么child就是管理i+1层目录深度的table
// 就这样一层层的搜索到depth目录深度对应的table,这是一个很有耐心很讲究章法的分层过程[luther.gliethttp]
}
return table;
}
// 从dentry所在目录深度出发,向回追溯到目录深度为depth的父dentry
static struct dentry *proc_sys_ancestor(struct dentry *dentry, int depth)
{
for (;;) {
struct proc_inode *ei;
ei = PROC_I(dentry->d_inode);
if (ei->fd == depth)
break; /* found */ // 该目录深度fd等于depth,ok我们找到了指定深度的目录项[luther.gliethttp]
dentry = dentry->d_parent;
}
return dentry;
}
static struct inode *proc_sys_make_inode(struct inode *dir, struct ctl_table *table)
{
struct inode *inode;
struct proc_inode *dir_ei, *ei;
int depth;
inode = new_inode(dir->i_sb);
if (!inode)
goto out;
/* A directory is always one deeper than it's parent */
dir_ei = PROC_I(dir);
depth = dir_ei->fd + 1; // 新创建的目录永远比当前它的parent的深度多1,所以这里加1,/proc/sys的深度为0
ei = PROC_I(inode);
ei->fd = depth; // 记住自己的深度,这样在我下面创建的目录,将以我的该fd为基数,作上面的加1运算[luther.gliethttp].
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
inode->i_op = &proc_sys_inode_operations;
inode->i_fop = &proc_sys_file_operations;
inode->i_flags |= S_PRIVATE; /* tell selinux to ignore this inode */
proc_sys_refresh_inode(inode, table);
// 如果该table有proc_handler处理函数,那么他是一个文件,否则该table是一个目录,所以该inode也就是一个相应目录[luther.gliethttp]
out:
return inode;
}
static void proc_sys_refresh_inode(struct inode *inode, struct ctl_table *table)
{
/* Refresh the cached information bits in the inode */
if (table) {
inode->i_uid = 0;
inode->i_gid = 0;
inode->i_mode = table->mode;
if (table->proc_handler) {
inode->i_mode |= S_IFREG; // 如果该管理该inode的table含有proc_handler,那么说明该inode是一个文件[luther.gliethttp].
inode->i_nlink = 1;
} else {
inode->i_mode |= S_IFDIR;
inode->i_nlink = 0; /* It is too hard to figure out */
}
}
}
因为在/proc/sys/目录下的所有文件,不论是目录文件还是普通用文件,他们都有唯一的文件方法集f_ops,即:proc_sys_file_operations
static const struct file_operations proc_sys_file_operations = {
.read = proc_sys_read,
.write = proc_sys_write,
.readdir = proc_sys_readdir,
};
来看看sys_read()==>proc_sys_read()是怎么区分目录文件和普通文件的.
比如我们执行如下shell命令:
luther@gliethttp:~$ cat /proc/sys/kernel/printk
4 4 1 7
static ssize_t proc_sys_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct dentry *dentry = filp->f_dentry;
struct ctl_table_header *head;
struct ctl_table *table;
ssize_t error;
size_t res;
//kern_table[] = {
// ......
// #if defined CONFIG_PRINTK
// {
// .ctl_name = KERN_PRINTK,
// .procname = "printk",
// .data = &console_loglevel,
// .maxlen = 4*sizeof(int),
// .mode = 0644,
// .proc_handler = &proc_dointvec, // 操作.data = &console_loglevel,数据的方法[luther.gliethttp]
// },
// .......
//}
// 返回kernel所在table的child对应的printk对应table.[luther.gliethttp]
table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head);
/* Has the sysctl entry disappeared on us? */
error = -ENOENT;
if (!table)
goto out;
/* Has the sysctl entry been replaced by a directory? */
error = -EISDIR;
if (!table->proc_handler) // 该table没有proc_handler处理函数,说明它是一个目录,不能对一个目录执行read操作,只能执行proc_sys_readdir
goto out;
/*
* At this point we know that the sysctl was not unregistered
* and won't be until we finish.
*/
error = -EPERM;
if (sysctl_perm(table, MAY_READ))
goto out;
/* careful: calling conventions are nasty here */
res = count;
error = table->proc_handler(table, 0/* 0表示读操作,1表示写操作*/, filp, buf, &res, ppos); // 执行proc_dointvec
if (!error)
error = res;
out:
sysctl_head_finish(head);
return error;
}
|