proc文件系统和sysfs文件系统类似,是虚拟文件系统,存在于内存中,用于内核和用户程序交互等,查看内核信息。
基于/proc文件系统如上所述的特殊性,其内的文件也常被称作虚拟文件,并具有一些独特的特点。例如,其中有些文件虽然使用查看命令查看时会返回大量信息,但文件本身的大小却会显示为0字节。此外,这些特殊文件中大多数文件的时间及日期属性通常为当前系统时间和日期,这跟它们随时会被刷新(存储于RAM中)有关。
为了查看及使用上的方便,这些文件通常会按照相关性进行分类存储于不同的目录甚至子目录中,如/proc/scsi目录中存储的就是当前系统上所有SCSI设备的相关信息,/proc/N中存储的则是系统当前正在运行的进程的相关信息,其中N为正在运行的进程(可以想象得到,在某进程结束后其相关目录则会消失)。
大多数虚拟文件可以使用文件查看命令如cat、more或者less进行查看,有些文件信息表述的内容可以一目了然,但也有文件的信息却不怎么具有可读性。其中用户程序ps、top、free等的内容就来自proc文件系统中的信息。
一、内核信息
- /proc/cmdline:在启动时传递至内核的相关参数信息,这些信息通常由lilo或grub等启动管理工具进行传递
- /proc/cpuinfo:处理器的相关信息
- /proc/crypto:系统上已安装的内核使用的密码算法及每个算法的详细信息列表
- /proc/devices:系统已经加载的所有块设备和字符设备的信息
- /proc/diskstats:每块磁盘设备的磁盘I/O统计信息列表
- /proc/dma:每个正在使用且注册的ISA DMA通道的信息列表
- /proc/filesystems:当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型
- /proc/iomem:每个物理设备上的记忆体(RAM或者ROM)在系统内存中的映射信息
- /proc/meminfo:系统中关于当前内存的利用状况等的信息,常由free命令使用
- /proc/mounts:此文件的内容为系统当前挂载的所有文件系统
- /proc/modules:当前装入内核的所有模块名称列表,可以由lsmod命令使用,也可以直接查看
- /proc/partitions:块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目
- /proc/slabinfo:对象相关slap的信息
- /proc/uptime:系统上次启动以来的运行时间,如下所示,其第一个数字表示系统运行时间,第二个数字表示系统空闲时间,单位是秒;
- /proc/version:当前系统运行的内核版本号
二、用户进程信息
/proc目录中包含许多以数字命名的子目录,这些数字表示系统当前正在运行进程的进程号,里面包含对应进程相关的多个信息文件。
- cmdline — 启动当前进程的完整命令,但僵尸进程目录中的此文件不包含任何信息
- cwd — 指向当前进程运行目录的一个符号链接
- exe — 指向启动当前进程的可执行文件(完整路径)的符号链接,通过/proc/N/exe可以启动当前进程的一个拷贝
- fd — 这是个目录,包含当前进程打开的每一个文件的文件描述符(file descriptor),这些文件描述符是指向实际文件的一个符号链接
- limits — 当前进程所使用的每一个受限资源的软限制、硬限制和管理单元;此文件仅可由实际启动当前进程的UID用户读取;(2.6.24以后的内核版本支持此功能);
- maps — 当前进程关联到的每个可执行文件和库文件在内存中的映射区域及其访问权限所组成的列表;
- mem — 当前进程所占用的内存空间,由open、read和lseek等系统调用使用,不能被用户读取;
- root — 指向当前进程运行根目录的符号链接;
- stat — 当前进程的状态信息,包含一系统格式化后的数据列,可读性差,通常由ps命令使用;
- statm — 当前进程占用内存的状态信息,通常以“页面”(page)表示;
- status — 与stat所提供信息类似,但可读性较好
- task — 目录文件,包含由当前进程所运行的每一个线程的相关信息,每个线程的相关信息文件均保存在一个由线程号(tid)命名的目录中,这类似于其内容类似于每个进程目录中的内容;
三、proc编程
与sysfs虚拟文件系统相比,proc的使用没有sysfs那么组织严谨,更加随意,因此在内核模块中用proc文件系统与用户空间进行信息交互还是很方便的。
在内核模块中使用proc,需要包含头文件
其中常用的函数包括:
- 创建一个文件:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent);
name: 文件名称
mode:文件属性,常用S_IWUSR | S_IRUGO
parent:父目录,即创建的文件在那个目录下,若为NULL,则在/proc目录下创建文件
2. 创建一个只读的文件:
struct proc_dir_entry* create_proc_read_entry(const char* name, mode_t mode, struct proc_dir_entry* parent, read_proc_t* read_proc, void* data);
read_proc是读操作回调函数,data是多个文件共享一个操作函数时区分文件的私有数据
3. 创建一个目录:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
参数意义同上,只是这个函数在proc文件系统中创建的是一个目录
4. 创建一个符号链接:
struct proc_dir_entry* proc_symlink(const chat *name, struct proc_dir_entry *parent, const char *dest)
在parent目录下创建符号链接,效果如同:ln –s dest name
5. 创建一个设备节点
struct proc_dir_entry* proc_mknod(const char *name, mode_t mode, struct proc_dir_entry *parent, kdev_t rdev)
在parent目录下创建一个设备节点,其中rdev可以由MKDEV宏生成,mode参数必须包含S_IFBLK或S_IFCHR表示创建的是块设备还是字符设备。
效果如同:mknod --mode=mode name rdev
6. 移除proc文件系统中的入口
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
上面创建的所有对象,都可以通过这个函数移除
为了对proc文件系统中的文件进行读写操作,需要设置相应的回调函数,在返回的对应proc文件的struct proc_dir_entry结构中,设置其中的:
entry->read_proc和entry->write_proc参数。
其中entry->read_proc的函数原型为:
int read_func(char* page, char** start, off_t off, int count, int* eof, void* data); 从内核读取数据
内核返回的数据需要写入page,其中page为内核地址,off和count分别是写入在page中的偏移地址和可以写入的最大字节,则两个参数主要是用于more和less命令,一般若内容较少可以忽略这两个参数。若这两个参数被使用,则需要设置eof为1来表示已到达文件尾部。
data参数用于同一个read_func函数为多个proc文件服务时,进行区分所操作的proc文件,并可以存放私有数据。
这个函数的返回值是复制到page内存中的字节数。
entry->write_proc的函数原型为:
int write_func(struct file* file, const char* buffer, unsigned long count, void* data); 写入数据到内核
参数count表示可以从buffer中访问的最大字节数,由于buffer是用户空间内存,需要使用copy_from_user复制buffer中的数据到内核空间。file参数一般被忽略,而data同上也是用于区分多个文件时的操作对象。
参数data的使用实例一般为:
struct proc_dir_entry* entry;
struct my_file_data *file_data; //文件数据
file_data = kmalloc(sizeof(struct my_file_data), GFP_KERNEL); //分配空间
entry->data = file_data; //这里是重点,需要将其赋值给对应proc文件的proc_dir_entry结构,在read和write是才能区分
int foo_read_func(char *page, char **start, off_t off, int count, int *eof, void *data)
{
int len;
if(data == file_data) {
/* 操作对应的文件*/
}
else {
/* 操作其他文件*/
}
return len;
}
需要注意的是,若entry->data的参数是动态分配的,在调用remove_proc_entry时,需要先将对应的data空间释放。