linux /proc虚拟文件系统

1. /proc目录
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。

用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。下面列出的这些文件或子文件夹,并不是都是在你的系统中存在,这取决于你的内核配置和装载的模块。另外,在/proc下还有三个很重要的目录:net,scsi和sys。 Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则scsi 目录不存在。

除了以上介绍的这些,还有的是一些以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的 PID号为目录名,它们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link。

2. 子文件或子文件夹
/proc/buddyinfo 每个内存区中的每个order有多少块可用,和内存碎片问题有关

/proc/cmdline 启动时传递给kernel的参数信息

/proc/cpuinfo cpu的信息

/proc/crypto 内核使用的所有已安装的加密密码及细节

/proc/devices 已经加载的设备并分类


/proc/dma 已注册使用的ISA DMA频道列表

/proc/execdomains Linux内核当前支持的execution domains

/proc/fb 帧缓冲设备列表,包括数量和控制它的驱动

/proc/filesystems 内核当前支持的文件系统类型

/proc/interrupts x86架构中的每个IRQ中断数

/proc/iomem 每个物理设备当前在系统内存中的映射

/proc/ioports 一个设备的输入输出所使用的注册端口范围

/proc/kcore 代表系统的物理内存,存储为核心文件格式,里边显示的是字节数,等于RAM大小加上4kb

/proc/kmsg 记录内核生成的信息,可以通过/sbin/klogd或/bin/dmesg来处理

/proc/loadavg 根据过去一段时间内CPU和IO的状态得出的负载状态,与uptime命令有关

/proc/locks 内核锁住的文件列表

/proc/mdstat 多硬盘,RAID配置信息(md=multiple disks)

/proc/meminfo RAM使用的相关信息

/proc/misc 其他的主要设备(设备号为10)上注册的驱动

/proc/modules 所有加载到内核的模块列表

/proc/mounts 系统中使用的所有挂载

/proc/mtrr 系统使用的Memory Type Range Registers (MTRRs)

/proc/partitions 分区中的块分配信息

/proc/pci 系统中的PCI设备列表

/proc/slabinfo 系统中所有活动的 slab 缓存信息

/proc/stat 所有的CPU活动信息

/proc/sysrq-trigger 使用echo命令来写这个文件的时候,远程root用户可以执行大多数的系统请求关键命令,就好像在本地终端执行一样。要写入这个文件,需要把/proc/sys/kernel/sysrq不能设置为0。这个文件对root也是不可读的

/proc/uptime 系统已经运行了多久

/proc/swaps 交换空间的使用情况

/proc/version Linux内核版本和gcc版本

/proc/bus 系统总线(Bus)信息,例如pci/usb等

/proc/driver 驱动信息

/proc/fs 文件系统信息

/proc/ide ide设备信息

/proc/irq 中断请求设备信息

/proc/net 网卡设备信息

/proc/scsi scsi设备信息

/proc/tty tty设备信息

/proc/net/dev 显示网络适配器及统计信息

/proc/vmstat 虚拟内存统计信息

/proc/vmcore 内核panic时的内存映像

/proc/diskstats 取得磁盘信息

/proc/schedstat kernel调度器的统计信息

/proc/zoneinfo 显示内存空间的统计信息,对分析虚拟内存行为很有用

以下是/proc目录中进程N的信息

/proc/N pid为N的进程信息

/proc/N/cmdline 进程启动命令

/proc/N/cwd 链接到进程当前工作目录

/proc/N/environ 进程环境变量列表

/proc/N/exe 链接到进程的执行命令文件

/proc/N/fd 包含进程相关的所有的文件描述符

/proc/N/maps 与进程相关的内存映射信息

/proc/N/mem 指代进程持有的内存,不可读

/proc/N/root 链接到进程的根目录

/proc/N/stat 进程的状态

/proc/N/statm 进程使用的内存的状态

/proc/N/status 进程状态信息,比stat/statm更具可读性

/proc/self 链接到当前正在运行的进程

3、模块详解

        
[root@plato]# ls /proc
1     2040  2347  2874  474          fb           mdstat      sys
104   2061  2356  2930  9            filesystems  meminfo     sysrq-trigger
113   2073  2375  2933  acpi         fs           misc        sysvipc
1375  21    2409  2934  buddyinfo    ide          modules     tty
1395  2189  2445  2935  bus          interrupts   mounts      uptime
1706  2201  2514  2938  cmdline      iomem        mtrr        version
179   2211  2515  2947  cpuinfo      ioports      net         vmstat
180   2223  2607  3     crypto       irq          partitions
181   2278  2608  3004  devices      kallsyms     pci
182   2291  2609  3008  diskstats    kcore        self
2     2301  263   3056  dma          kmsg         slabinfo
2015  2311  2805  394   driver       loadavg      stat
2019  2337  2821  4     execdomains  locks        swaps
[root@plato 1]# ls /proc/1
auxv     cwd      exe  loginuid  mem     oom_adj    root  statm   task
cmdline  environ  fd   maps      mounts  oom_score  stat  status  wchan
[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 然后又定义了这个模块的 initcleanup 函数。my_module_init 函数是在加载这个模块时被调用的,它用来进行一些初始化方面的工作。my_module_cleanup 函数是在卸载这个模块时被调用的,它用来释放内存并清除这个模块的踪迹。注意此处printk 的用法:这是内核的 printf 函数。KERN_INFO 符号是一个字符串,可以用来对进入内核回环缓冲区的信息进行过滤(非常类似于syslog)。

最后,清单 3 使用 module_initmodule_exit 宏声明了入口函数和出口函数。这样我们就可以按照自己的意愿来对这个模块的initcleanup 函数进行命名了,不过我们最终要告诉内核维护函数就是这些函数。


清单 3. 一个简单的但可以正常工作的 LKM(simple-lkm.c)

        
#include <linux/module.h>
/* Defines the license for this LKM */
MODULE_LICENSE("GPL");
/* Init function called on module entry */
int my_module_init( void )
{
  printk(KERN_INFO "my_module_init called.  Module is now loaded.\n");
  return 0;
}
/* Cleanup function called on module exit */
void my_module_cleanup( void )
{
  printk(KERN_INFO "my_module_cleanup called.  Module is now unloaded.\n");
  return;
}
/* Declare entry and exit functions */
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=$PWD modules
make: Entering directory `/usr/src/linux-2.6.11'
  CC [M]  /root/projects/misc/module2.6/simple/simple-lkm.o
  Building modules, stage 2.
  MODPOST
  CC      /root/projects/misc/module2.6/simple/simple-lkm.mod.o
  LD [M]  /root/projects/misc/module2.6/simple/simple-lkm.ko
make: Leaving directory `/usr/src/linux-2.6.11'
[root@plato]#


结果会生成一个 simple-lkm.ko 文件。这个新的命名约定可以帮助将这些内核对象(LKM)与标准对象区分开来。现在可以加载或卸载这个模块了,然后可以查看它的输出。要加载这个模块,请使用insmod 命令;反之,要卸载这个模块,请使用 rmmod 命令。lsmod 可以显示当前加载的 LKM(参见清单 5)。


清单 5. 插入、检查和删除 LKM

        
[root@plato]# insmod simple-lkm.ko
[root@plato]# lsmod
Module                  Size  Used by
simple_lkm              1536  0
autofs4                26244  0
video                  13956  0
button                  5264  0
battery                 7684  0
ac                      3716  0
yenta_socket           18952  3
rsrc_nonstatic          9472  1 yenta_socket
uhci_hcd               32144  0
i2c_piix4               7824  0
dm_mod                 56468  3
[root@plato]# rmmod simple-lkm
[root@plato]#


注意,内核的输出进到了内核回环缓冲区中,而不是打印到 stdout 上,这是因为stdout 是进程特有的环境。要查看内核回环缓冲区中的消息,可以使用dmesg 工具(或者通过 /proc 本身使用 cat /proc/kmsg 命令)。清单 6 给出了dmesg 显示的最后几条消息。


清单 6. 查看来自 LKM 的内核输出

        
[root@plato]# dmesg | tail -5
cs: IO port probe 0xa00-0xaff: clean.
eth0: Link is down
eth0: Link is up, running at 100Mbit half-duplex
my_module_init called.  Module is now loaded.
my_module_cleanup called.  Module is now unloaded.
[root@plato]#


可以在内核输出中看到这个模块的消息。现在让我们暂时离开这个简单的例子,来看几个可以用来开发有用 LKM 的内核 API。

内核程序员可以使用的标准 API,LKM 程序员也可以使用。LKM 甚至可以导出内核使用的新变量和函数。有关 API 的完整介绍已经超出了本文的范围,因此我们在这里只是简单地介绍后面在展示一个更有用的 LKM 时所使用的几个元素。

 

要在 /proc 文件系统中创建一个虚拟文件,请使用 create_proc_entry 函数。这个函数可以接收一个文件名、一组权限和这个文件在 /proc 文件系统中出现的位置。create_proc_entry 的返回值是一个proc_dir_entry 指针(或者为 NULL,说明在create 时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。create_proc_entry 的原型和proc_dir_entry 结构中的一部分如清单 7 所示。



        
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 );


稍后我们就可以看到如何使用 read_procwrite_proc 命令来插入对这个虚拟文件进行读写的函数。

要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在 /proc 文件系统中的位置(parent)。这个函数原型如清单 7 所示。

parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。表 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 中写入一项。这个函数的原型如下:

int mod_write( struct file *filp, const char __user *buff,
               unsigned long len, void *data );


filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff 参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。len 参数定义了在buff 中有多少数据要被写入。data 参数是一个指向私有数据的指针(参见 清单 7)。在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。

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_mkdirsymlinks 以及proc_symlink 在 /proc 文件系统中创建目录。对于只需要一个read 函数的简单 /proc 项来说,可以使用 create_proc_read_entry,这会创建一个 /proc 项,并在一个调用中对read_proc 函数进行初始化。这些函数的原型如清单 8 所示。



        
/* 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


下面是一个可以支持读写的 LKM。这个简单的程序提供了一个财富甜点分发。在加载这个模块之后,用户就可以使用 echo 命令向其中导入文本财富,然后再使用cat 命令逐一读出。

清单 9 给出了基本的模块函数和变量。init 函数(init_fortune_module)负责使用vmalloc 来为这个点心罐分配空间,然后使用 memset 将其全部清零。使用所分配并已经清空的cookie_pot 内存,我们在 /proc 中创建了一个 proc_dir_entry 项,并将其称为 fortune。当proc_entry 成功创建之后,对自己的本地变量和 proc_entry 结构进行了初始化。我们加载了 /proc readwrite 函数(如清单 9 和清单 10 所示),并确定这个模块的所有者。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 );


向这个罐中新写入一个 cookie 非常简单(如清单 10 所示)。使用这个写入 cookie 的长度,我们可以检查是否有这么多空间可用。如果没有,就返回 -ENOSPC,它会返回给用户空间。否则,就说明空间存在,我们使用 copy_from_user 将用户缓冲区中的数据直接拷贝到 cookie_pot 中。然后增大cookie_index(基于用户缓冲区的长度)并使用 NULL 来结束这个字符串。最后,返回实际写入cookie_pot 的字符的个数,它会返回到用户进程。


        
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;
}


对 fortune 进行读取也非常简单,如清单 11 所示。由于我们刚才写入数据的缓冲区(page)已经在内核空间中了,因此可以直接对其进行操作,并使用sprintf 来写入下一个 fortune。如果 next_fortune 索引大于 cookie_index(要写入的下一个位置),那么我们就将next_fortune 返回为 0,这是第一个 fortune 的索引。在将这个 fortune 写入用户缓冲区之后,在next_fortune 索引上增加刚才写入的 fortune 的长度。这样就变成了下一个可用 fortune 的索引。这个 fortune 的长度会被返回并传递给用户。


 

        
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;
}


从这个简单的例子中,我们可以看出通过 /proc 文件系统与内核进行通信实际上是件非常简单的事情。现在让我们来看一下这个 fortune 模块的用法(参见清单 12)。


        
[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]#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值