Linux下的proc目录有什么用途,我们查看内核版本号、加载的模块、CPU和内存使用等信息,甚至修改路由转发都是通过/proc目录进行的,本文在揭示/proc本质的基础上,对比了/proc编程的新老接口,以及一个应用实例。
以下均在ubuntu 12.04 LTS上实验
目的 | 命令 |
---|---|
查看内核符号表,用kprobes时会用到 | cat /proc/kallsyms |
查看内核版本号 | cat /proc/version |
查看加载的模块 | cat /proc/modules |
查看可用设备的列表 | cat /proc/devices |
查看CPU 的信息 (型号,家族,缓存) | cat /proc/cpuinfo |
查看物理内存、交换空间等的信息 | cat /proc/meminfo |
查看已加载的文件系统的列表 | cat /proc/mounts |
查看被支持的文件系统 | cat /proc/filesystems |
查看系统启动时内核命令行参数(grub.cfg, menu.lst) | cat /proc/cmdline |
查看socket状态 | cat /proc/net/sockstat |
查看arp表 | cat /proc/net/arp |
开启路由转发 | echo 1 >/proc/sys/net/ipv4/ip_forward |
看一下/proc/filesystems和/proc/mounts的内容
cat /proc/filesystems
nodev sysfs
nodev rootfs
nodev ramfs
nodev bdev
nodev proc
.....
ext3
ext2
ext4
cat /proc/mounts
rootfs / rootfs rw 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
...
种种迹象表明proc是一个文件系统,YES(下一节)。此外,mounts文件中的nosuid, nodev, noexec是什么意思呢?下面深挖一下mount命令及选项
举例
mount -t sysfs -o nodev,noexec,nosuid none /sys
mount -t proc -o nodev,noexec,nosuid none /proc
选项
-t<文件系统类型> 指定设备的文件系统类型。sysfs
-o<选项> 指定加载文件系统时的选项。有些选项也可在/etc/fstab中使用。这些选项包括:
....
nodev 不读文件系统上的字符或块设备。
noexec 无法执行二进制文件。
nosuid 关闭set-user-identifier(设置用户ID)与set-group-identifer(设置组ID)设置位。
nouser 使一位用户无法执行加载操作,默认设置。
....
none是设备名,/sys是挂载点
/proc
目录是一个文件系统,准确的说是一个伪文件系统(pseudo-filesystem
),该目录中的所有文件都不会消耗磁盘空间. 它提供了一个用户空间和内核空间通信的接口,可以用类似cat /proc/meminfo
读取内核信息,也可以使用类似echo 1 >>/proc/sys/net/ipv4/ip_forward
向内核写消息
最初procfs是为了提供有关系统中进程(proc
ess)的信息,后来内核中的很多模块也开始使用它来报告信息,或启用动态运行时配置。
demoproc.c
#include module.h>
#include <linux kernel.h>
#include proc_fs.h>
#include <linux string.h>
#include vmalloc.h>
#include <asm uaccess.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("proc demo");
MODULE_AUTHOR("geeksword");
#define MAX_BUFF_SIZE 2048
static char *sbuff;
static char *entry_name = "demoproc";
struct proc_dir_entry *proc_entry;
static ssize_t demoproc_write(struct file *filp, const char __user * buff,
size_t len, loff_t * offset)
{
printk(KERN_INFO "[+demoproc] call demoproc_write()\n");
printk(KERN_INFO "[+demoproc] write:%s\n", buff);
len = len < MAX_BUFF_SIZE ? len : MAX_BUFF_SIZE;
if (copy_from_user(sbuff, buff, len)) {
printk(KERN_INFO "[+demoproc]: copy_from_user() error!\n");
return -EFAULT;
}
return len;
}
static ssize_t demoproc_read(struct file *filp, char __user * buff,
size_t len, loff_t * offset)
{
printk(KERN_INFO "[+demoproc] call demoproc_read()\n");
len = strlen(sbuff);
if (*offset >= len) {
return 0;
}
len -= *offset;
if (copy_to_user(buff, sbuff + *offset, len)) {
return -EFAULT;
}
*offset += len;
return len;
}
static const struct file_operations demoproc_fops = {
.owner = THIS_MODULE,
.write = demoproc_write,
.read = demoproc_read,
};
int init_demoproc_module(void)
{
int ret = 0;
sbuff = (char *)vmalloc(MAX_BUFF_SIZE);
if (!sbuff) {
ret = -ENOMEM;
} else {
memset(sbuff, 0, MAX_BUFF_SIZE);
proc_entry =
proc_create(entry_name, 0644, NULL, &demoproc;_fops);
printk(KERN_INFO "[+demoproc] Module loaded.\n");
}
return ret;
}
void cleanup_demoproc_module(void)
{
proc_remove(proc_entry);
vfree(sbuff);
printk(KERN_INFO "[+demoproc] Module removed.\n");
}
module_init(init_demoproc_module);
module_exit(cleanup_demoproc_module);
关于基本的模块编程移步
demoproc
,权限0644
和file_operationsdemoproc_fops
proc新老接口的对比
根据lxr.free-electrons.com的查询结果: create_proc_entry()从linux3.10就没有了,ubuntu12.04 LTS的内核版本是3.13
内核版本 | <= v3.9 | >= v3.10 |
---|---|---|
创建proc | create_proc_entry() | proc_create() |
移除proc | remove_proc_entry() | proc_remove() |
关联文件操作 | proc_dir_entry | file_operations |
在编写demoproc_read()的时候遇到一个有意思的问题
struct file_operations
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
....
}
根据read函数指针编写demoproc_read, 一直不明白loff_t 指针(其实是long long )的作用 。
在函数simple_read_from_buffer()中loff_t*ppos标识buffer当前位置。 我们通过实验来探索一下,将demoproc_read函数改成如下
static ssize_t demoproc_read(struct file *filp, char __user * buff,
size_t len, loff_t * offset)
{
printk(KERN_INFO "[+demoproc] call demoproc_read()\n");
len = strlen(sbuff);
if (copy_to_user(buff, sbuff, len)) {
return -EFAULT;
}
return len;
}
编译安装后, 如果在终端输入
echo "hello proc">/proc/demoproc
cat /proc/demoproc
结果将是终端无穷无尽的输出hello proc
!
原因是demoproc_read()函数被反复调用,直到它返回0,也就是读取字节数为0(全部读完)。
如果将return len
改成return 0
,执行cat /proc/demoproc时将无任何输出。
所以我就使用这个参数loff_t*offset标识读取的数据在全局缓冲区sbuff的读取位置,
仔细分析demoproc.c,你会判断出每次demoproc_read都会执行两次,第一次读取所有的数据,第二次读取0个字符,然后结束,通过dmesg|tail
证实你的推断。
这和我们编写应用程序时,函数传地址参数是一样的,只不过这里的调用函数未知。