Procfs与sysctlprocfs和sysctl都输出内核内部信息,但procfs主要是输出只读数据,而大多数sysctl信息是可写入的,但只要超级用户能写入。与一个简单的内核变量或数据结构相关联的一些文件,可以用sysctl输出。其他涉及更为复杂的数据结构而需要特殊格式时,就以procfs输出,如缓存和统计数据。Procfs大多数网络功能会在引导时或在模块加载时其初始化期间在/proc中注册一个或多个文件。当用户读取该文件时,会引起内核间接运行一组内核函数,返回某种输出内容。网络代码所注册的文件位于/proc/net。/proc中的目录可以使用proc_mkdir创建。/proc/net中的文件可以使用定义在include/linux/proc_fs.h中的proc_net_fops_create和proc_net_remove予以注册和注销。 还有两个通用的API函数create_proc_entry和remove_proc_entry。proc_net_fops_create负责调用proc_net_create创建文档,然后初始化器文件操作处理函数。 如ARP协议在/proc/net中注册其arp文件: static const struct file_operations arp_seq_fops = { .owner = THIS_MODULE, .open = arp_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release_net, }; static int __net_init arp_net_init(struct net *net) { if (!proc_net_fops_create(net, "arp", S_IRUGO, &arp_seq_fops)) return -ENOMEM; return 0; } static void __net_exit arp_net_exit(struct net *net) { proc_net_remove(net, "arp"); } proc_net_fops_create参数中权限必须指定为只读。当用户读取该文件时,使用file_operations数据结构,返回数据给用户。open所初始化的例程如前例中的qrp_seq_open会做另一次重要的初始化:注册一个函数指针数组,包括procfs用于变量要传回给用户数据的所有例程:一个例程启动dump复制数据,另一个索引到下一条记录,在dump该条记录,继续后移。这些例程的内部保存必要的环境信息,就是已经dump了多少信息。这些信息是dump点已经到正确位置继续dump所必需的。 static const struct seq_operations arp_seq_ops = { .start = arp_seq_start, .next = neigh_seq_next, .stop = neigh_seq_stop, .show = arp_seq_show, }; static int arp_seq_open(struct inode *inode, struct file *file) { return seq_open_net(inode, file, &arp_seq_ops, sizeof(struct neigh_seq_state)); } Sysctl:目录/proc/sys在/proc/sys下看到的一个文件,实际都是一个内核变量。对于每个变量,内核可以将其放在/proc/sys的位置(与相同内核组件或功能相关联的变量通常都位于同一个目录中,如/proc/sys/net/ipv4目录中都是与ipv4相关的文件)、命名(多数时候文件名都是和相关联的内核变量相同的名字)、访问权限(如一个文件可以由任何人读,但只能由超级用户修改)。输出到/proc/sys中的变量内容可借助相关联的文件进行读写或直接用sysctl系统调用。有些目录和文件在引导期间静态定义,而其他则是在允许期间添加的。当一个内核模块实现一项新功能或一个协议被加载或卸载时;当一个新的网络设备被注册或注销时。都有可能创建目录或文件。 /proc/sys中的文件和目录都是以ctl_table结构定义的。ctl_table结构的注册和注销是通过调用kernel/sysctl.c中定义的register_sysctl_table和unregister_sysctl_table: struct ctl_table_header *register_sysctl_table(struct ctl_table *table); void unregister_sysctl_table(struct ctl_table_header * header); struct ctl_table { const char *procname; /* Text ID for /proc/sys, or zero */ void *data; int maxlen; mode_t mode; struct ctl_table *child; struct ctl_table *parent; /* Automatically set */ proc_handler *proc_handler; /* Callback for text formatting */ void *extra1; void *extra2; }; ctl_table的关键成员: procname:在/proc/sys中所使用的文件名 maxlen:输出的内核变量的大小 mode:创建的/proc/sys猪相关文件或目录的访问权限 child:用于建立目录和文件直接的父子关系 proc_handler:当在/proc/sys中读取或写入一个文件时,完成读取或写入操作的函数。所有和文件相关联的ctl_instance都必须有proc_handler初始化,内内核会给目录分派一个默认值 extra1,extra2:可选参数,通常用于定义变量的最小值和最大值 根据与文件相关联的变量类型的不同,proc_handler所指的函数也不相同。 proc_dostring:读/写一个字符串 proc_dointvec:读写一个包含一个或多个整数的数组 proc_dointvec_minmax:同上,但是要确定输入数据在min/max范围内,不在该范围内的值会被拒绝 proc_dointvec_jiffies:读写一个整数数组,但此内核变量以jiffies为单位表示,在返回用户前会转化为秒数,写入前转化为jiffies proc_dointvec_ms_jiffies:同上,只是这里是转化为毫秒数 proc_doulongvec_minmax:类似proc_dointvec_minmax,但其值为长整数。 proc_doulongvec_ms_jiffies_minmax:读取一个长整数数组。此内核变量以jiffies为单位,而用户空间已毫秒为单位。此值也必须指定min和max区间 ctl_table注册文件的结构实例: static struct ctl_table ctl_forward_entry[] = { { .procname = "ip_forward", //文件名 .data = &ipv4_devconf.data[ IPV4_DEVCONF_FORWARDING - 1], //输出参数 .maxlen = sizeof(int), //参数大小 .mode = 0644, //文件权限 .proc_handler = devinet_sysctl_forward, //指定函数 .extra1 = &ipv4_devconf, .extra2 = &init_net, }, { }, }; 在这里还看不出这个文件在/proc/sys下的那个目录中。 这里是/proc/sys目录下建的默认目录: static struct ctl_table root_table[] = { { .procname = "kernel", .mode = 0555, .child = kern_table, }, { .procname = "vm", .mode = 0555, .child = vm_table, }, { .procname = "fs", .mode = 0555, .child = fs_table, }, { .procname = "debug", .mode = 0555, .child = debug_table, }, { .procname = "dev", .mode = 0555, .child = dev_table, }, { } }; 目录不需要proc_handler函数指针但却有一个child字段。child是一个指针,指向另一个ctl_table结构,这个地址是ctl_table结构列表的头元素地址 在/proc/sys中注册文件 函数register_sysctl_table的输入并不包括输入参数ctl_table应该被添加到/proc/sys文件目录的那个子目录中。原因在于所有的插入都是针对/proc/sys目录进行的,所以若想将一个文件注册到/proc/sys的子目录中,就必须建立一棵树由多个child字段链接成ctl_table实体以提供完整路径,然后将代表刚健的树根的ctl_table传递给register_sysctl_table函数。 drivers/scsi/scsi_sysctl.c显示了文件logging_level的定义已经放置到/proc/sys/dev/scsi/目录下的方法: static ctl_table scsi_table[] = { //logging_level文件 .procname = "logging_level", .data = &scsi_logging_level, .maxlen = sizeof(scsi_logging_level), .mode = 0644, .proc_handler = proc_dointvec }, { } }; static ctl_table scsi_dir_table[] = { //scsi目录 { .procname = "scsi", .mode = 0555, .child = scsi_table }, { } }; static ctl_table scsi_root_table[] = { //dev目录 { .procname = "dev", .mode = 0555, .child = scsi_dir_table }, { } }; static struct ctl_table_header *scsi_table_header; int __init scsi_init_sysctl(void) //注册 { scsi_table_header = register_sysctl_table(scsi_root_table); if (!scsi_table_header) return -ENOMEM; return 0; } void scsi_exit_sysctl(void) //注销 { unregister_sysctl_table(scsi_table_header); } 若需要添加多个文件到同一个目录下,可以定义一个模板,每次有新文件要添加到同一个目录下就重用。使用模板的好处是ctl_table结构只需要初始化一个便可贯穿整个目录。 转载请保留固定链接: https://linuxeye.com/Linux/931.html |