使用proc 文件系统来访问 Linux 内核的内容.docx
《使用proc 文件系统来访问 Linux 内核的内容.docx》由会员分享,可在线阅读,更多相关《使用proc 文件系统来访问 Linux 内核的内容.docx(18页珍藏版)》请在冰点文库上搜索。
使用proc文件系统来访问Linux内核的内容
使用/proc文件系统来访问Linux内核的内容
这个虚拟文件系统在内核空间和用户空间之间打开了一个通信窗口
M.TimJones (mtj@),资深首席软件工程师,Emulex
简介:
/proc文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在Linux®内核空间和用户空间之间进行通信。
在/proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。
本文对/proc虚拟文件系统进行了介绍,并展示了它的用法。
发布日期:
2006年4月24日
级别:
初级
访问情况:
43874次浏览
评论:
2 (查看 | 添加评论 -登录)
平均分(108个评分)
为本文评分
最初开发/proc文件系统是为了提供有关系统中进程的信息。
但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。
/proc文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。
虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。
实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。
尽管像本文这样短小的一篇文章无法详细介绍/proc的所有用法,但是它依然对这两种用法进行了展示,从而可以让我们体会一下/proc是多么强大。
清单1是对/proc中部分元素进行一次交互查询的结果。
它显示的是/proc文件系统的根目录中的内容。
注意,在左边是一系列数字编号的文件。
每个实际上都是一个目录,表示系统中的一个进程。
由于在GNU/Linux中创建的第一个进程是 init进程,因此它的 process-id 为 1。
然后对这个目录执行一个 ls 命令,这会显示很多文件。
每个文件都提供了有关这个特殊进程的详细信息。
例如,要查看 init 的command-line项的内容,只需对 cmdline 文件执行 cat 命令。
/proc中另外一些有趣的文件有:
cpuinfo,它标识了处理器的类型和速度;pci,显示在PCI总线上找到的设备;modules,标识了当前加载到内核中的模块。
清单1.对/proc的交互过程
[root@plato]#ls/proc
1204023472874474fbmdstatsys
1042061235629309filesystemsmeminfosysrq-trigger
113207323752933acpifsmiscsysvipc
13752124092934buddyinfoidemodulestty
1395218924452935businterruptsmountsuptime
1706220125142938cmdlineiomemmtrrversion
179221125152947cpuinfoioportsnetvmstat
180222326073cryptoirqpartitions
181227826083004deviceskallsymspci
182229126093008diskstatskcoreself
223012633056dmakmsgslabinfo
201523112805394driverloadavgstat
2019233728214execdomainslocksswaps
[root@plato1]#ls/proc/1
auxvcwdexeloginuidmemoom_adjrootstatmtask
cmdlineenvironfdmapsmountsoom_scorestatstatuswchan
[root@plato]#cat/proc/1/cmdline
init[5]
[root@plato]#
清单2展示了对/proc中的一个虚拟文件进行读写的过程。
这个例子首先检查内核的TCP/IP栈中的IP转发的目前设置,然后再启用这种功能。
清单2.对/proc进行读写(配置内核)
[root@plato]#cat/proc/sys/net/ipv4/ip_forward
0
[root@plato]#echo"1">/proc/sys/net/ipv4/ip_forward
[root@plato]#cat/proc/sys/net/ipv4/ip_forward
1
[root@plato]#
另外,我们还可以使用 sysctl 来配置这些内核条目。
有关这个问题的更多信息,请参阅 参考资料 一节的内容。
顺便说一下,/proc文件系统并不是GNU/Linux系统中的惟一一个虚拟文件系统。
在这种系统上,sysfs 是一个与/proc类似的文件系统,但是它的组织更好(从/proc中学习了很多教训)。
不过/proc已经确立了自己的地位,因此即使sysfs与/proc相比有一些优点,/proc也依然会存在。
还有一个 debugfs 文件系统,不过(顾名思义)它提供的更多是调试接口。
debugfs的一个优点是它将一个值导出给用户空间非常简单(实际上这不过是一个调用而已)。
内核模块简介
可加载内核模块(LKM)是用来展示/proc文件系统的一种简单方法,这是因为这是一种用来动态地向Linux内核添加或删除代码的新方法。
LKM也是Linux内核中为设备驱动程序和文件系统使用的一种流行机制。
如果您曾经重新编译过Linux内核,就可能会发现在内核的配置过程中,有很多设备驱动程序和其他内核元素都被编译成了模块。
如果一个驱动程序被直接编译到了内核中,那么即使这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。
但是如果这个驱动程序被编译成一个模块,就只有在需要内存并将其加载到内核时才会真正占用内存空间。
有趣的是,对于LKM来说,我们不会注意到有什么性能方面的差异,因此这对于创建一个适应于自己环境的内核来说是一种功能强大的手段,这样可以根据可用硬件和连接的设备来加载对应的模块。
下面是一个简单的LKM,可以帮助您理解它与在Linux内核中看到的标准(非动态可加载的)代码之间的区别。
清单3给出了一个最简单的LKM。
(可以从本文的 下载 一节中下载这个代码)。
清单3包括了必须的模块头(它定义了模块的API、类型和宏)。
然后使用 MODULE_LICENSE 定义了这个模块使用的许可证。
此处,我们定义的是 GPL,从而防止会污染到内核。
清单3然后又定义了这个模块的 init 和 cleanup 函数。
my_module_init 函数是在加载这个模块时被调用的,它用来进行一些初始化方面的工作。
my_module_cleanup 函数是在卸载这个模块时被调用的,它用来释放内存并清除这个模块的踪迹。
注意此处 printk 的用法:
这是内核的 printf 函数。
KERN_INFO 符号是一个字符串,可以用来对进入内核回环缓冲区的信息进行过滤(非常类似于syslog)。
最后,清单3使用 module_init 和 module_exit 宏声明了入口函数和出口函数。
这样我们就可以按照自己的意愿来对这个模块的 init和 cleanup 函数进行命名了,不过我们最终要告诉内核维护函数就是这些函数。
清单3.一个简单的但可以正常工作的LKM(simple-lkm.c)
#include
/*DefinesthelicenseforthisLKM*/
MODULE_LICENSE("GPL");
/*Initfunctioncalledonmoduleentry*/
intmy_module_init(void)
{
printk(KERN_INFO"my_module_initcalled.Moduleisnowloaded.\n");
return0;
}
/*Cleanupfunctioncalledonmoduleexit*/
voidmy_module_cleanup(void)
{
printk(KERN_INFO"my_module_cleanupcalled.Moduleisnowunloaded.\n");
return;
}
/*Declareentryandexitfunctions*/
module_init(my_module_init);
module_exit(my_module_cleanup);
清单3尽管非常简单,但它却是一个真正的LKM。
现在让我们对其进行编译并在一个2.6版本的内核上进行测试。
2.6版本的内核为内核模块的编译引入了一种新方法,我发现这种方法比原来的方法简单了很多。
对于文件 simple-lkm.c,我们可以创建一个makefile,其惟一内容如下:
obj-m+=simple-lkm.o
要编译LKM,请使用 make 命令,如清单4所示。
清单4.编译LKM
[root@plato]#make-C/usr/src/linux-`uname-r`SUBDIRS=$PWDmodules
make:
Enteringdirectory`/usr/src/linux-2.6.11'
CC[M]/root/projects/misc/module2.6/simple/simple-lkm.o
Buildingmodules,stage2.
MODPOST
CC/root/projects/misc/module2.6/simple/simple-lkm.mod.o
LD[M]/root/projects/misc/module2.6/simple/simple-lkm.ko
make:
Leavingdirectory`/usr/src/linux-2.6.11'
[root@plato]#
结果会生成一个 simple-lkm.ko 文件。
这个新的命名约定可以帮助将这些内核对象(LKM)与标准对象区分开来。
现在可以加载或卸载这个模块了,然后可以查看它的输出。
要加载这个模块,请使用 insmod 命令;反之,要卸载这个模块,请使用 rmmod 命令。
lsmod可以显示当前加载的LKM(参见清单5)。
清单5.插入、检查和删除LKM
[root@plato]#insmodsimple-lkm.ko
[root@plato]#lsmod
ModuleSizeUsedby
simple_lkm15360
autofs4262440
video139560
button52640
battery76840
ac37160
yenta_socket189523
rsrc_nonstatic94721yenta_socket
uhci_hcd321440
i2c_piix478240
dm_mod564683
[root@plato]#rmmodsimple-lkm
[root@plato]#
注意,内核的输出进到了内核回环缓冲区中,而不是打印到 stdout 上,这是因为 stdout 是进程特有的环境。
要查看内核回环缓冲区中的消息,可以使用 dmesg 工具(或者通过/proc本身使用 cat/proc/kmsg 命令)。
清单6给出了 dmesg 显示的最后几条消息。
清单6.查看来自LKM的内核输出
[root@plato]#dmesg|tail-5
cs:
IOportprobe0xa00-0xaff:
clean.
eth0:
Linkisdown
eth0:
Linkisup,runningat100Mbithalf-duplex
my_module_initcalled.Moduleisnowloaded.
my_module_cleanupcalled.Moduleisnowunloaded.
[root@plato]#
可以在内核输出中看到这个模块的消息。
现在让我们暂时离开这个简单的例子,来看几个可以用来开发有用LKM的内核API。
回页首
集成到/proc文件系统中
内核程序员可以使用的标准API,LKM程序员也可以使用。
LKM甚至可以导出内核使用的新变量和函数。
有关API的完整介绍已经超出了本文的范围,因此我们在这里只是简单地介绍后面在展示一个更有用的LKM时所使用的几个元素。
创建并删除/proc项
要在/proc文件系统中创建一个虚拟文件,请使用 create_proc_entry 函数。
这个函数可以接收一个文件名、一组权限和这个文件在/proc文件系统中出现的位置。
create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。
然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。
create_proc_entry 的原型和 proc_dir_entry 结构中的一部分如清单7所示。
清单7.用来管理/proc文件系统项的元素
structproc_dir_entry*create_proc_entry(constchar*name,mode_tmode,
structproc_dir_entry*parent);
structproc_dir_entry{
constchar*name;//virtualfilename
mode_tmode;//modepermissions
uid_tuid;//File'suserid
gid_tgid;//File'sgroupid
structinode_operations*proc_iops;//Inodeoperationsfunctions
structfile_operations*proc_fops;//Fileoperationsfunctions
structproc_dir_entry*parent;//Parentdirectory
...
read_proc_t*read_proc;///procreadfunction
write_proc_t*write_proc;///procwritefunction
void*data;//Pointertoprivatedata
atomic_tcount;//usecount
...
};
voidremove_proc_entry(constchar*name,structproc_dir_entry*parent);
稍后我们就可以看到如何使用 read_proc 和 write_proc 命令来插入对这个虚拟文件进行读写的函数。
要从/proc中删除一个文件,可以使用 remove_proc_entry 函数。
要使用这个函数,我们需要提供文件名字符串,以及这个文件在/proc文件系统中的位置(parent)。
这个函数原型如清单7所示。
parent参数可以为NULL(表示/proc根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。
表1列出了可以使用的其他一些父 proc_dir_entry,以及它们在这个文件系统中的位置。
表1.proc_dir_entry快捷变量
proc_dir_entry
在文件系统中的位置
proc_root_fs
/proc
proc_net
/proc/net
proc_bus
/proc/bus
proc_root_driver
/proc/driver
回调函数
我们可以使用 write_proc 函数向/proc中写入一项。
这个函数的原型如下:
intmod_write(structfile*filp,constchar__user*buff,
unsignedlonglen,void*data);
filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)。
buff 参数是传递给您的字符串数据。
缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。
len 参数定义了在 buff 中有多少数据要被写入。
data 参数是一个指向私有数据的指针(参见清单7)。
在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。
Linux提供了一组API来在用户空间和内核空间之间移动数据。
对于 write_proc 的情况来说,我们使用了 copy_from_user 函数来维护用户空间的数据。
读回调函数
我们可以使用 read_proc 函数从一个/proc项中读取数据(从内核空间到用户空间)。
这个函数的原型如下:
intmod_read(char*page,char**start,off_toff,
intcount,int*eof,void*data);
page 参数是这些数据写入到的位置,其中 count 定义了可以写入的最大字符数。
在返回多页数据(通常一页是4KB)时,我们需要使用 start 和 off 参数。
当所有数据全部写入之后,就需要设置 eof(文件结束参数)。
与 write 类似,data 表示的也是私有数据。
此处提供的 page 缓冲区在内核空间中。
因此,我们可以直接写入,而不用调用 copy_to_user。
其他有用的函数
我们还可以使用 proc_mkdir、symlinks 以及 proc_symlink 在/proc文件系统中创建目录。
对于只需要一个 read 函数的简单/proc项来说,可以使用 create_proc_read_entry,这会创建一个/proc项,并在一个调用中对 read_proc 函数进行初始化。
这些函数的原型如清单8所示。
清单8.其他有用的/proc函数
/*Createadirectoryintheprocfilesystem*/
structproc_dir_entry*proc_mkdir(constchar*name,
structproc_dir_entry*parent);
/*Createasymlinkintheprocfilesystem*/
structproc_dir_entry*proc_symlink(constchar*name,
structproc_dir_entry*parent,
constchar*dest);
/*Createaproc_dir_entrywitharead_proc_tinonecall*/
structproc_dir_entry*create_proc_read_entry(constchar*name,
mode_tmode,
structproc_dir_entry*base,
read_proc_t*read_proc,
void*data);
/*Copybuffertouser-spacefromkernel-space*/
unsignedlongcopy_to_user(void__user*to,
constvoid*from,
unsignedlongn);
/*Copybuffertokernel-spacefromuser-space*/
unsignedlongcopy_from_user(void*to,
constvoid__user*from,
unsignedlongn);
/*Allocatea'virtually'contiguousblockofmemory*/
void*vmalloc(unsignedlongsize);
/*Freeavmalloc'dblockofmemory*/
voidvfree(void*addr);
/*Exportasymboltothekernel(makeitvisibletothekernel)*/
EXPORT_SYMBOL(symbol);
/*Exportallsymbolsinafiletothekernel(declarebeforemodule.h)*/
EXPORT_SYMTAB
回页首
通过/proc文件系统实现财富分发
下面是一个可以支持读写的LKM。
这个简单的程序提供了一个财富甜点分发。
在加载这个模块之后,用户就可以使用 echo 命令向其中导入文本财富,然后再使用 cat 命令逐一读出。
清单9给出了基本的模块函数和变量。
init 函数(init_fortune_module)负责使用 vmalloc 来为这个点心罐分配空间,然后使用memset 将其全部清零。
使用所分配并已经清空的 cookie_pot 内存,我们在/proc中创建了一个 proc_dir_entry 项,并将其称为fortune。
当 proc_entry 成功创建之后,对自己的本地变量和 proc_entry 结构进行了初始化。
我们加载了/proc read 和 write 函数(如清单9和清单10所示),并确定这个模块的所有者。
cleanup 函数简单地从/proc文件系统中删除这一项,然后释放cookie_pot 所占据的内存。
cookie_pot 是一个固定大小(4KB)的页,它使用两个索引进行管理。
第一个是 cookie_index,标识了要将下一个cookie写到哪里去。
变量 next_fortune 标识了下一个cookie应该从哪里读取以便进行输出。
在所有的fortune项都读取之后,我们简单地回到了next_fortune。
清单9.模块的init/cleanup和变量
#include
#include
#inc