使用 /proc 文件系统来访问 Linux 内核的内容
简介: /proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux 内核空间和用户空间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。本文对 /proc 虚拟文件系统进行了介绍,并展示了它的用法。
最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。
/proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。
尽管像本文这样短小的一篇文章无法详细介绍 /proc 的所有用法,但是它依然对这两种用法进行了展示,从而可以让我们体会一下 /proc 是多么强大。
- root@ubuntu:~# cd /proc/
- root@ubuntu:/proc# ls
- 1 1629 1813 1977 26712 4 843 acpi key-users softirqs
- 10 1633 1816 2 26994 41 849 asound kmsg stat
- 1008 1667 1819 20 26995 42 857 buddyinfo kpagecount swaps
- 11 1668 1820 2030 27 43 858 bus kpageflags sys
- 1152 1697 1822 2039 27043 44 860 cgroups latency_stats sysrq-trigger
- 1182 17 1830 20791 27054 45 861 cmdline loadavg sysvipc
- 12 1706 1835 2090 27055 46 867 cpuinfo locks timer_list
- 13 1711 1836 2091 27056 5 868 crypto mdstat timer_stats
- 132 1712 1844 21 27059 571 876 devices meminfo tty
- 14 1726 1852 2160 27064 6 878 diskstats misc uptime
- 1407 1744 1854 2194 27083 667 9 dma modules version
- 1427 1779 18642 2195 28 7 938 driver mounts version_signature
- 1475 1782 1885 22 29 721 939 execdomains mpt vmallocinfo
- 1483 1783 1887 2213 3 746 940 fb mtrr vmmemctl
- 1487 1786 1893 23 30 760 941 filesystems net vmstat
- 15 1792 1896 238 31 776 942 fs pagetypeinfo zoneinfo
- 1552 1795 19 24 32 778 943 interrupts partitions
- 1557 1799 1900 240 326 782 944 iomem sched_debug
- 16 18 1906 248 328 784 945 ioports schedstat
- 1602 1806 1908 25476 35 785 946 irq scsi
- 1621 1808 1920 265 37 797 947 kallsyms self
- 1625 1811 1970 266 38 8 951 kcore slabinfo
- root@ubuntu:/proc# cd 1
- root@ubuntu:/proc/1# ls
- attr coredump_filter fd loginuid mountstats personality smaps syscall
- auxv cpuset fdinfo maps net root stack task
- cgroup cwd io mem oom_adj sched stat wchan
- clear_refs environ latency mountinfo oom_score schedstat statm
- cmdline exe limits mounts pagemap sessionid status
- root@ubuntu:/proc/1# cat cmdline
- /sbin/initroot@ubuntu:/proc/1#
- root@ubuntu:/proc/1# cat sta
- stack stat statm status
- root@ubuntu:/proc/1# cat stat
- stat statm status
- root@ubuntu:/proc/1# cat status
- Name: init
- State: S (sleeping)
- Tgid: 1
- Pid: 1
- PPid: 0
- TracerPid: 0
- Uid: 0 0 0 0
- Gid: 0 0 0 0
- FDSize: 32
- Groups:
- VmPeak: 2804 kB
- VmSize: 2792 kB
- VmLck: 0 kB
- VmHWM: 1672 kB
- VmRSS: 1672 kB
- VmData: 436 kB
- VmStk: 84 kB
- VmExe: 100 kB
- VmLib: 2068 kB
- VmPTE: 28 kB
- Threads: 1
- SigQ: 1/7949
- SigPnd: 0000000000000000
- ShdPnd: 0000000000000000
- SigBlk: 0000000000000000
- SigIgn: 0000000000001000
- SigCgt: 00000001a0012623
- CapInh: 0000000000000000
- CapPrm: ffffffffffffffff
- CapEff: fffffffffffffeff
- CapBnd: ffffffffffffffff
- Cpus_allowed: ff
- Cpus_allowed_list: 0-7
- Mems_allowed: 1
- Mems_allowed_list: 0
- voluntary_ctxt_switches: 2044
- nonvoluntary_ctxt_switches: 259
- Stack usage: 16 kB
- root@ubuntu:/proc/1#
/proc 中另外一些有趣的文件有:
cpuinfo,它标识了处理器的类型和速度;
pci,显示在 PCI 总线上找到的设备;
modules,标识了当前加载到内核中的模块。
顺便说一下,/proc 文件系统并不是 GNU/Linux 系统中的惟一一个虚拟文件系统。在这种系统上,sysfs 是一个与 /proc 类似的文件系统,但是它的组织更好(从 /proc 中学习了很多教训)。不过 /proc 已经确立了自己的地位,因此即使 sysfs 与 /proc 相比有一些优点,/proc 也依然会存在。还有一个 debugfs 文件系统,不过(顾名思义)它提供的更多是调试接口。debugfs 的一个优点是它将一个值导出给用户空间非常简单(实际上这不过是一个调用而已)。
创建并删除 /proc 项
要在 /proc 文件系统中创建一个虚拟文件,请使用 create_proc_entry 函数。这个函数可以接收一个文件名、一组权限和这个文件在 /proc 文件系统中出现的位置。create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。create_proc_entry 的原型和 proc_dir_entry 结构中的一部分如下面 所示。
- struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,
- struct proc_dir_entry *parent );
- struct proc_dir_entry {
- const char *name; // virtual file name
- mode_t mode; // mode permissions
- uid_t uid; // File's user id
- gid_t gid; // File's group id
- struct inode_operations *proc_iops; // Inode operations functions
- struct file_operations *proc_fops; // File operations functions
- struct proc_dir_entry *parent; // Parent directory
- ...
- read_proc_t *read_proc; // /proc read function
- write_proc_t *write_proc; // /proc write function
- void *data; // Pointer to private data
- atomic_t count; // use count
- ...
- };
- void remove_proc_entry( const char *name, struct proc_dir_entry *parent );
假如上面create_proc_entry 的返回值是一个 proc_dir_entry 指针,那么使用以下方法配置该文件执行读写操作时应该调用的函数
- proc_dir_entry ->read_proc = read_proc ;
- proc_dir_entry ->write_proc = write_proc ;
要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在 /proc 文件系统中的位置(parent)。
parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。下面列表列出了可以使用的其他一些父 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 中写入一项。这个函数的原型如下:
int mod_write( struct file *filp, const char __user *buff,unsigned long len, void *data );
filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff 参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。len 参数定义了在 buff 中有多少数据要被写入。data 参数是一个指向私有数据的指针。在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。
Linux 提供了一组 API 来在用户空间和内核空间之间移动数据。对于 write_proc 的情况来说,我们使用了 copy_from_user 函数来维护用户空间的数据。
读回调函数
我们可以使用 read_proc 函数从一个 /proc 项中读取数据(从内核空间到用户空间)。这个函数的原型如下:
int mod_read( char *page, char **start, off_t off,int count, 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 函数进行初始化。这些函数的原型如下所示。
- /* Create a directory in the proc filesystem */
- struct proc_dir_entry *proc_mkdir( const char *name,
- struct proc_dir_entry *parent );
- /* Create a symlink in the proc filesystem */
- struct proc_dir_entry *proc_symlink( const char *name,
- struct proc_dir_entry *parent,
- const char *dest );
- /* Create a proc_dir_entry with a read_proc_t in one call */
- struct proc_dir_entry *create_proc_read_entry( const char *name,
- mode_t mode,
- struct proc_dir_entry *base,
- read_proc_t *read_proc,
- void *data );
- /* Copy buffer to user-space from kernel-space */
- unsigned long copy_to_user( void __user *to,
- const void *from,
- unsigned long n );
- /* Copy buffer to kernel-space from user-space */
- unsigned long copy_from_user( void *to,
- const void __user *from,
- unsigned long n );
- /* Allocate a 'virtually' contiguous block of memory */
- void *vmalloc( unsigned long size );
- /* Free a vmalloc'd block of memory */
- void vfree( void *addr );
- /* Export a symbol to the kernel (make it visible to the kernel) */
- EXPORT_SYMBOL( symbol );
- /* Export all symbols in a file to the kernel (declare before module.h) */
- EXPORT_SYMTAB
说明:
name : 要创建的文件名;
mode : 文件掩码,为 0 则按照系统默认的掩码创建文件。
base : 指定该文件所在的目录,如果为 NULL,则文件被创建在 /proc 根目录下。
read_proc : 实现该文件的 read_proc 函数。也就是说,当我们读取 "name" 这个文件时(如 cat /proc/myproc_name) ,读取请求会通过这个函数发送到驱动模块,然后在函数里处理的数据会写到 myproc_name 文件中。
data : 内核忽略此参数,但会把它当作参数传递给 read_proc 这个自定义函数。
用法:
- struct proc_dir_entry *parent;
- parent = proc_mkdir ("myproc", NULL);
- create_proc_read_entry ("scullmem", 0744, parent, scull_read_procmem, NULL);
- # ll /proc/myproc/scullmem
- -rwxr--r-- 1 root root 0 2010-09-27 20:48 /proc/myproc/scullmem*
上面的 scullmem 后有 1 星号表示此文件可执行,实际上 /proc 下的文件一般都是只读的,这里只是演示权限位。
通过 /proc 文件系统实现财富分发,一个比较经典的实例
这个简单的程序提供了一个财富甜点分发。在加载这个模块之后,用户就可以使用 echo 命令向其中导入文本财富,然后再使用 cat 命令逐一读出。
init 函数(init_fortune_module)负责使用 vmalloc 来为这个点心罐分配空间,然后使用 memset 将其全部清零。使用所分配并已经清空的 cookie_pot 内存,我们在 /proc 中创建了一个 proc_dir_entry 项,并将其称为 fortune。当 proc_entry 成功创建之后,对自己的本地变量和 proc_entry 结构进行了初始化。我们加载了 /proc read 和 write 函数,并确定这个模块的所有者。cleanup 函数简单地从 /proc 文件系统中删除这一项,然后释放 cookie_pot 所占据的内存。
cookie_pot 是一个固定大小(4KB)的页,它使用两个索引进行管理。第一个是 cookie_index,标识了要将下一个 cookie 写到哪里去。变量 next_fortune 标识了下一个 cookie 应该从哪里读取以便进行输出。在所有的 fortune 项都读取之后,我们简单地回到了 next_fortune。
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/proc_fs.h>
- #include <linux/string.h>
- #include <linux/vmalloc.h>
- #include <asm/uaccess.h>
- MODULE_LICENSE("GPL");
- MODULE_DESCRIPTION("Fortune Cookie Kernel Module");
- MODULE_AUTHOR("M. Tim Jones");
- #define MAX_COOKIE_LENGTH PAGE_SIZE
- static struct proc_dir_entry *proc_entry;
- static char *cookie_pot; // Space for fortune strings
- static int cookie_index; // Index to write next fortune
- static int next_fortune; // Index to read next fortune
- int init_fortune_module( void )
- {
- int ret = 0;
- cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );
- if (!cookie_pot) {
- ret = -ENOMEM;
- } else {
- memset( cookie_pot, 0, MAX_COOKIE_LENGTH );
- proc_entry = create_proc_entry( "fortune", 0644, NULL );
- if (proc_entry == NULL) {
- ret = -ENOMEM;
- vfree(cookie_pot);
- printk(KERN_INFO "fortune: Couldn't create proc entry\n");
- } else {
- cookie_index = 0;
- next_fortune = 0;
- proc_entry->read_proc = fortune_read;
- proc_entry->write_proc = fortune_write;
- proc_entry->owner = THIS_MODULE;
- printk(KERN_INFO "fortune: Module loaded.\n");
- }
- }
- return ret;
- }
- void cleanup_fortune_module( void )
- {
- remove_proc_entry("fortune", &proc_root);
- vfree(cookie_pot);
- printk(KERN_INFO "fortune: Module unloaded.\n");
- }
- module_init( init_fortune_module );
- module_exit( cleanup_fortune_module );
- ssize_t fortune_write( struct file *filp, const char __user *buff,
- unsigned long len, void *data )
- {
- int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;
- if (len > space_available) {
- printk(KERN_INFO "fortune: cookie pot is full!\n");
- return -ENOSPC;
- }
- if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {
- return -EFAULT;
- }
- cookie_index += len;
- cookie_pot[cookie_index-1] = 0;
- return len;
- }
- int fortune_read( char *page, char **start, off_t off,
- int count, int *eof, void *data )
- {
- int len;
- if (off > 0) {
- *eof = 1;
- return 0;
- }
- /* Wrap-around */
- if (next_fortune >= cookie_index) next_fortune = 0;
- len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);
- next_fortune += len;
- return len;
- }
- [root@plato]# insmod fortune.ko
- [root@plato]# echo "Success is an individual proposition.
- Thomas Watson" > /proc/fortune
- [root@plato]# echo "If a man does his best, what else is there?
- Gen. Patton" > /proc/fortune
- [root@plato]# echo "Cats: All your base are belong to us.
- Zero Wing" > /proc/fortune
- [root@plato]# cat /proc/fortune
- Success is an individual proposition. Thomas Watson
- [root@plato]# cat /proc/fortune
- If a man does his best, what else is there? General Patton
- [root@plato]#