当前位置: 首页 > 科技观察

Linux内核中proc文件系统介绍

时间:2023-03-11 22:56:11 科技观察

procfs文件系统是内核中的一个特殊文件系统。它是一个虚拟的文件系统:它不是存在于实际存储设备中的文件,而是存在于内存中。procfs中的文件是用来让用户空间程序访问内核中的某些信息(比如/proc/[0-9]+/中的进程信息),或者用于调试目的(/proc/ksyms,这个文件列出给出变量或函数地址的已注册内核符号。每行给出符号的地址、符号的名称和注册该符号的模块。程序ksyms、insmod和kmod使用此文件。它还列出了正在运行的任务数、任务总数和最后分配的PID。)本文档描述了内核中procfs文件系统的使用。它首先介绍与管理文件系统相关的所有功能。在功能介绍之后,还展示了如何与用户空间进行通信,以及一些小技巧。在文档的顶部,还给出了一个完整的示例。注意/proc/sys中的文件属于sysctl文件,不属于procfs文件系统,由另一套完全不同的api管理。seq_fileprocfs对于大文件有点笨拙。为了清理procfs文件系统,让内核编程更简单,引入了seq_file机制。seq_file机制提供了大量简单的接口来实现大型内核虚拟文件。seq_file机制用于当您使用一系列结构来创建返回给用户空间的虚拟文件时。要使用seq_file机制,必须创建一个指向序列的“迭代器”对象,并且可以一个一个地指向序列中的对象,并且可以输出序列中的任意一个对象。听起来很复杂,但实际上,过程非常简单。接下来,我们将通过实际示例展示如何做到这一点。首先,您必须包含头文件。接下来,您必须创建迭代器方法:start、next、stop和show。通常首先调用start方法。该方法的函数原型为:void*start(structseq_file*sfile,loff_t*pos);sfile没有作用,通常被忽略。pos参数是一个整数,表示从哪个位置开始读取。关于位置的定义完全取决于功能实现;它不必是结果文件中的字节位置。由于seq_file机制通常使用特定的结构序列来实现,因此位置通常是指向序列中下一个结构的指针。在wingdriver中,每个device代表了sequence中的一个结构体,所以入参pos代表g_pstWingDevices数组的索引。因此wingdriver中start方法的实现是:staticvoid*wing_seq_start(structseq_file*s,loff_t*pos){if(*pos>=g_iWingDevicesNum)returnNULL;/*Nomoretoread*/returng_pstWingDevices+*pos;}如果返回值不为NULL,代表一个私有数据,可供迭代器使用。下一个函数应该将迭代器移动到下一个位置,或者如果序列中没有数据则返回NULL。该方法的函数原型为:void*next(structseq_file*sfile,void*v,loff_t*pos);这里,参数v表示上一个函数调用返回的迭代器(可能是start函数,也可能是下一个函数),参数pos是当前在文件中的位置。next函数要改变pos的指向,是stepbystep还是jump取决于iterator的工作机制。wingdriver中next函数的实现是:staticvoid*wing_seq_next(structseq_file*s,void*v,loff_t*pos){(*pos)++;if(*pos>=g_iWingDevicesNum)returnNULL;return_pstWingDevices+*pos;}当内核停止迭代器的工作时,它调用停止函数来清理场景:voidstop(structseq_file*sfile,void*v);wingdriver没有清理工作,所以stop函数是空的。voidwing_seq_stop(structseq_file*sfile,void*v){}如果seq_file代码在调用start和stop的时候没有进行sleep或者非原子操作,那么这个机制就没有意义了。您必须确保从开始函数调用到停止函数调用的调用非常短。因此,在启动函数中获取信号量或自旋锁更安全。如果seq_file的其他方法是原子的,那么整个调用链一定是原子的。在这些函数调用中,内核调用call函数向内核空间输出特征信息。该函数的函数原型为:intshow(structseq_file*sfile,void*v);这个方法应该按照指针v指定的顺序创建item的输出。不能使用printk,而是使用下面的具体函数:intseq_printf(structseq_file*sfile,constchar*fmt,...)这个函数是一个类似的实现到seq_file机制中的printf;它使用通常的格式字符串和参数来组成输出字符串。您必须将show函数中的seq_file结构传递给此函数。如果它返回一个非零值,则填充缓冲区并抛出输出。在大多数实现中,返回值被选择为被忽略。intseq_putc(structseq_file*sfile,charc);intseq_puts(structseq_file*sfile,constchar*s);这两个函数相当于用户层的putc和puts。intseq_escape(structseq_file*m,constchar*s,constchar*e??sc);此函数等同于seq_puts,除了s中同时出现在esc中的任何字符都以八进制格式打印。esc的一个常见值是“\t\n\”,它可以防止嵌入的空格弄乱输出和可能的shell脚本。该函数可用于输出并给出指定与命令项关联的文件名。它不太可能在设备驱动程序中有用;为了完整起见,我们将其包含在这里。机翼设备中的显示函数示例:staticintwing_seq_show(structseq_file*s,void*v){ST_Wing_Dev_Type*pDev=(ST_Wing_Dev_Type*)v;seq_printf(s,"\nThisDeviceis%i\n",pDev->iData);return0;}在我的示例中,我将ST_Wing_Dev_Type结构表示为迭代器。以上就是完整的iterator操作,wingdriver必须将它们打包在一起才能连接到procfs文件系统。首先要做的是使用它们组成seq_operations结构:staticstructseq_operationss_stWingSeqOps={.start=wing_seq_start,.next=wing_seq_next,.stop=wing_seq_stop,.show=wing_seq_show};有了这个结构,我们必须创建一个内核可理解的文件实现。我们不使用前面描述的read_proc方法;使用seq_file时,***连接到稍低级别的procfs。这意味着创建一个file_operations(与字符设备相同的结构),它实现了内核对文件的读取和查找操作。幸运的是,这个操作非常简单。首先创建一个用seq_file方法连接文件的open方法:staticintwing_proc_open(structinode*inode,structfile*file){returnseq_open(file,&s_stWingSeqOps);}在调用seq_open函数时,将文件与上面定义的序列操作相关联。open是我们唯一需要实现的函数接口,所以我们的file_operations结构是:staticstructfile_operationss_stWingProcFops={.owner=THIS_MODULE,.open=wing_proc_open,.read=seq_read,.llseek=seq_lseek,.release=seq_release};***我们要在procfs文件系统中创建文件:proc_create("wingdevices",0644,NULL,&s_stWingProcFops);关键结构structproc_dir_entry表示/proc目录下的一个目录或文件,它是procfs文件系统的主要结构,它的定义在/fs/internal.h:/**Thisisnotcompletelyimplementedyet.Theideaisto*createanin-memorytree(liketheactual/procfilesystem*tree)的这些proc_dir_entries,这样我们就可以动态地*将新文件添加到/proc。**“下一个”指针创建一个链接列表的一个/proc目录,*而par/subdir创建目录结构(每个*/procfile都有一个父目录,但是“subdir”对于所有*非目录条目都是NULL)。*/structproc_dir_entry{unsignedintlow_ino;umode_tmode;nlink_tnlink;kuid_tuid;kgid_tgid;loff_tsize;conststructinode_operations*proc_iops;conststructfile_operations*proc_fops;structproc_dir_entry*next,*parent,*subdir;void*data;atomic_tcount;/*usecount*/atomic_tin_use;/*numberofcallersintomoduleinprogress;*//*negative->it'sgoingawayRSN*/structcompletion*pde_unload_completion;structlist_headpde_openers;/*whodid->open,butnot->release*/spinlock_tpde_unload_lock;/*procandmp*deopscheck/u8namelen;charname[];};主要接口procfs应该包含的头文件在3.x内核中procfs主要接口有:proc_symlinkproc_mkdirproc_mkdir_dataproc_mkdir_modeproc_create_dataproc_createproc_set_sizeproc_set_userPDE_DATAproc_get_parent_dataproc_removeremove_proc_entryremove_proc_subtreeproc_mkdir说明:在/proc下创建目录函数原型:structproc_dir_entry*proc_mkdir(constchar*name,structproc_dir_entry*parent)参数:name待创建目录的父父目录名,若为NULL则表示直接在/下创建目录过程。proc_mkdir_data说明:在/proc下创建一个目录函数原型:structproc_dir_entry*proc_mkdir_data(constchar*name,umode_tmode,structproc_dir_entry*parent,void*data)参数:name待创建目录的名称mode指定目录的权限createdparentparentdirectory,如果为NULL,则表示直接在/proc下创建一个目录。dataproc_create_data描述:创建proc虚拟文件系统文件函数原型:structproc_dir_entry*proc_create_data(constchar*name,umode_tmode,structproc_dir_entry*parent,conststructfile_operations*proc_fops,void*data)参数:name要创建的文件名。mode指定创建文件的权限。parent是你要创建一个名为name的文件的文件夹,如:init_net.proc_net就是在/proc/net/下创建一个文件。proc_fops为structfile_operations数据保存指向私有数据的指针,如果不是NULL。示例://////////////////////test.c////////////////////////////////////////#include#include#include#include#include#include#include#include#includeMODULE_LICENSE("GPL");typedefstruct{intdata1;intdata2;}ST_Data_Info_Type;staticST_Data_Info_Typeg_astDataInfo[2];staticinttest_proc_show(structseq_file*m,void*v){ST_Data_Info_Type*pInfo=(ST_Data_Info_Type*)m->private!=NULL){seq_printf(m,"%d----%d\n",pInfo->data1,pInfo->data2);}return0;}staticinttest_proc_open(structinode*inode,structfile*file){returnsingle_open(file,test_proc_show,PDE_DATA(inode));}staticconststructfile_operationsdl_file_ops={.owner=THIS_MODULE,.open=test_proc_open,.read=seq_read,.llseek=seq_lseek,.release=single_release,};staticstructproc_dir_entry*s_pstRootTestDir;voidinit_mem(void){/*create/proc/test*/s_pstRootTestDir=proc_mkdir("test",NULL);if(!s_pstRootTestDir)return;g_astDataInfo[0].data1=1;g_astDataInfo[0].data2=2;proc_create_data("proc_test1",0644,s_pstRootTestDir,&dl_file_ops,&g_astDataInfo[0]);g_astDataInfo[1].data1=3;g_astDataInfo[1].data2=4;proc_create_data("proc_test2",0644,s_pstRootTestDir,&dl_file_ops,&g_astDataInfo[1]);}staticint__inittest_module_init(void){printk("[test]:moduleinit\n");init_mem();return0;}staticvoid__exittest_module_exit(void){printk("[test]:moduleexit\n");remove_proc_entry("proc_test1",s_pstRootTestDir);remove_proc_entry("proc_test2",s_pstRootTestDir);remove_proc_entry("test",NULL);}module_init(test_module_init);module_exit(test_module_exit);proc_create说明:创建proc虚拟文件系统目录:structproc_dir_entry*proc_create(constchar*name,umode_tmode,structproc_dir_entry*parent,conststructfile_operations*proc_fops)parameters:name你要创建的文件名mode指定创建文件的权限parent是你要创建的文件名为name的文件夹,如:init_net.proc_net就是要在/proc/net/下创建一个文件。proc_fopsisstructfile_operations注意:这个接口和proc_create_data的区别是不能保存私有数据指针。PDE_DATA获取proc_create_data传入的私有数据。proc_symlink说明:此函数在procfs目录中创建从名称到目标的符号链接。相当于用户空间的ln-sdestname。函数原型:structproc_dir_entry*proc_symlink(constchar*name,structproc_dir_entry*parent,constchar*dest)参数:name原始符号。父符号所在的目录。dest创建的符号链接的名称。remove_proc_entry说明:删除procfs文件系统中的文件或目录。函数原型:voidremove_proc_entry(constchar*name,structproc_dir_entry*parent)参数:name要删除的文件或目录的名称。父符号所在的目录。如果为NULL,则表示它在/proc目录中。