【转载】proc 虚拟文件系统

  • 内核代码中分别找出一处 proc 和 seq_file 的完整使用过程,记录下来
  • 在用户空间进行相应“读”、“写”

介绍

Proc 虚拟文件系统

操作 proc 文件:

  • /proc 文件系统是一个虚拟文件系统(没有任何一部分与磁盘相关,只存在内存中,不占用外存空间),包含了一些目录和虚拟文件
  • 通过它可以在 Linux 内核空间和用户空间之间进行通信:可以向用户呈现内核中的一些信息(用 cat、more 等命令查看 /proc 文件中的信息),也可以用作一种从用户空间向内核发送信息的手段
  • LKM(Linux kernel module) 是用来展示 /proc 文件系统的一种简单方法,因为它可以动态地向 Linux 内核添加或删除代码
$ cd /proc
$ ls

蓝色部分是目录,其它是文件。

注:

  1. /proc 文件系统可以用于获取运行中的进程的信息。在 /proc 中有一些编号的子目录。每一个编号的目录对应一个进程 id(PID)。这样,每个运行中的进程 /proc 中都有一个用它的 PID 命名的目录。这些子目录中包含可以提供有关进程的状态和环境的重要细节信息的文件,它们是读取进程信息的接口。
  2. 还有一些文件的含义,如:

bash apm # 高级电源管理信息 bus # 总线配置信息(USB的配置也记录在此) cmdline # 内核命令行 Cpuinfo # 关于Cpu信息 Devices # 可以用到的设备(块设备/字符设备) Dma # 使用的DMA通道 Filesystems # 支持的文件系统 Interrupts # 中断的使用 Ioports # I/O端口的使用 Kcore # 内核核心印象 Kmsg # 内核消息 Ksyms # 内核符号表 Loadavg # 负载均衡 Locks # 内核锁 Meminfo # 内存信息 Misc # 杂项 Modules # 加载模块列表(可以想成是驱动程序) Mounts # 加载的文件系统 Partitions # 系统识别的分区表 PCI # 在PCI总线上,每台设备的详细情况(可以使用lspci来查看) Rtc # 实时时钟 Slabinfo Slab # 池信息 stat # 全面统计状态表 Swaps # 对换空间的利用情况 Version # 内核版本 Uptime # 系统正常运行时间

  1. 在 /proc 下还有三个很重要的目录:netscsisys 。sys 目录是可写的,通过它可访问和修改内核参数,而 net 和 scsi 则依赖于内核配置。例如,如果系统不支持 scsi,则 scsi 目录不存在。

练习

  • 内核代码中分别找出一处 proc 和 seq_file 的完整使用过程,记录下来

proc

proc_create 函数开始,剖析其中的实现

static inline struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops)
{
    return proc_create_data(name, mode, parent, proc_fops, NULL);
}

函数返回一个 proc_dir_entry 。可以看到 proc_create 中直接调用了 proc_create_data,而该函数主要完成 2 个功能:

  1. 调用 __proc_create 完成具体 proc_dir_entry 的创建。
  2. 调用 proc_redister 把 entry 注册进系统。
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data)
{
    struct proc_dir_entry *pde;
    if ((mode & S_IFMT) == 0)
        mode |= S_IFREG;

    if (!S_ISEG(mode))
    {
        WARN_ON(1); // use proc_mkdir()
        return NULL;
    }

    if ((mode & S_IALLUGO) == 0)
        mode |= S_IRUGO;
    pde = __proc_create(&parent, name, mode, 1);
    if (!pde)
        goto out;
    pde->proc_fops = proc_fops;
    pde->data = data;
    if (proc_register(parent, pde) < 0)
        goto out_free;
    return pde;
    out_free:
    kfree(pde);
    out:
    return NULL;
}

先看 proc_dir_entry 的创建,这里通过 __proc_create 函数,其实该函数内部很简单,就是为 entry 分配了空间,并对相关字段进行设置,主要包括 name、namelen、mod 和 nlink 等。创建好后,就设置操作函数 proc_fops 和 data 。然后就调用 proc_register 进行注册

static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp)
{
    struct proc_dir_entry *tmp;
    int ret;

    ret = proc_alloc_inum(&dp->low_ino);
    if (ret)
        return ret; // 如果是目录
    if (S_ISDIR(dp->mode))
    {   // 如果是链接
        dp->proc_fops = &proc_dir_operations;
        dp->proc_iops = &proc_dir_inode_operations;
        dir->nlink++;
    }
    else if (S_ISLNK(dp->mode))
    {
        // 若是文件
        dp->proc_iops = &proc_link_inode_operations;
    }
    else if (S_ISREG(dp->mode))
    {
        BUG_ON(dp->proc_fops == NULL);
        dp->proc_iops = &proc_file_inode_operations;
    }
    else
    {
       WARN_ON(1);
       return -EINVAL;
    }

    spin_lock(&proc_subdir_lock);

    for (tmp = dir->subdir; tmp; tmp = -> next)
        if (strcmp(tmp->name, dp->name) == 0)
        {
            WARN(1, "proc_dir_entry '%s/%s' already registered\n", dir->name, dp->name);
            break;
        }

   // 子 dir 链接成链表,且子 dir 中含有父 dir 的指针
    dp->next = dir->subdir;
    dp->parent = dir;
    dir->subdir = dp;
    spin_unlock(&proc_subdir_lock);

    return 0;
}

函数首先分配一个inode number,然后根据entry的类型对其进行操作函数赋值,主要分为目录、链接、文件。这里我们只关注文件,文件的操作函数一般由用户自己定义,即上面我们设置的ops,这里仅仅是设置inode操作函数表,设置成了全局的proc_file_inode_operations,然后插入到父目录的子文件链表中,注意是头插法。基本结构如下,其中每个子节点都有指向父节点的指针。

seq_file

UNIX的世界里,文件是最普通的概念,所以用文件来作为内核和用户空间传递数据的接口也是再普通不过的事情,并且这样的接口对于shell也是相当友好的,方便管理员通过shell直接管理系统。由于伪文件系统proc文件系统在处理大数据结构(大于一页的数据)方面有比较大的局限性,使得在那种情况下进行编程特别别扭,很容易导致bug,所以序列文件接口被发明出来,它提供了更加友好的接口,以方便程序员。

struct seq_file
{
    char *buf;  // seq_file 接口使用的缓存页指针
    size_t size;    // seq_file 接口使用的缓存页大小
    size_t from;    /* 从 seq_file 中向用户态缓冲区拷贝时相对于 buf 的偏移地址 */
    size_t count;   // buf 中可以拷贝到用户态的字符数目
    loff_t index;   // start、next 的处理的下标 pos 数值
    loff_t read_pos;    // 当前已拷贝到用户态的数据量大小
    u64 version;
    struct mutex lock;  // 针对此 seq_file 操作的互斥锁,所有 seq_* 的访问都会上锁
    const struct seq_operations *op;    // 操作实际底层数据的函数
    void *private;
};

在这个结构体中,几乎所有的成员都是由seq_file内部实现来处理的,程序员不用去关心,除非你要去研究seq_file的内部原理。对于这个结构体,程序员唯一要做的就是实现其中的const struct seq_operations *op。为使用 seq_file接口对于不同数据结构体进行访问,你必须创建一组简单的对象迭代操作函数。

seq_file内部机制使用这些接口函数访问底层的实际数据结构体,并不断沿数据序列向前,同时逐个输出序列里的数据到seq_file自建的缓存(大小为一页)中。也就是说seq_file内部机制帮你实现了对序列数据的读取和放入缓存的机制,你只需要实现底层的迭代函数接口就好了,因为这些是和你要访问的底层数据相关的,而seq_file属于上层抽象。这可能看起来有点复杂,大家看了上面的图就好理解了:

struct seq_operations
{
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};
void * (*start) (struct seq_file *m, loff_t *pos);

start 方法会首先被调用,它的作用是在设置访问的起始点。
m:指向的是本seq_file的结构体,在正常情况下无需处理。
pos:是一个整型位置值,指示开始读取的位置。对于这个位置的意义完全取决于底层实现,不一定是字节为单位的位置,可能是一个元素的序列号。
返回值如果非NULL,则是一个指向迭代器实现的私有数据结构体指针。如果访问出错则返回NULL。

设置好了访问起始点,seq_file内部机制可能会使用show方法获取start返回值指向的结构体中的数据到内部缓存,并适时送往用户空间。

int (*show) (struct seq_file *m, void *v);

所以show方法就是负责将v指向元素中的数据输出到seq_file的内部缓存,但是其中必须借助seq_file提供的一些类似printf的接口函数:

int seq_printf(struct seq_file *sfile, const char *fmt, ...);
//专为 seq_file 实现的类似 printf 的函数;用于将数据常用的格式串和附加值参数.
/*你必须将给 show 函数的 set_file 结构指针传递给它。如果seq_printf 返回-1,意味着缓存区已满,部分输出被丢弃。但是大部分时候都忽略了其返回值。*/

int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);
//类似 putc 和 puts 函数的功能,sfile参数和返回值与 seq_printf相同。
int seq_escape(struct seq_file *m, const char *s, const char *esc);
//这个函数类似 seq_puts ,但是它会将 s 中所有在 esc 中出现的字符以八进制格式输出到缓存。
//esc 的常用值是"\t\n\\", 它使内嵌的空格不会搞乱输出或迷惑 shell 脚本.

int seq_write(struct seq_file *seq, const void *data, size_t len)
//直接将data指向的数据写入seq_file缓存,数据长度为len。用于非字符串数据。

int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);
//这个函数能够用来输出给定目录项关联的文件名,驱动极少使用。

在show函数返回之后,seq_file机制可能需要移动到下一个数据元素,那就必须使用next方法

void * (*next) (struct seq_file *m, void *v, loff_t *pos);

在next实现中应当递增pos指向的值,但是具体递增的数量和迭代器的实现有关,不一定是1。而next的返回值如果非NULL,则是下一个需要输出到缓存的元素指针,否则表明已经输出结束,将会调用stop方法做清理。

void (*stop) (struct seqa_file *m, void *v);

在stop实现中,参数m指向本seq_file的结构体,在正常情况下无需处理。而v是指向上一个next或start返回的元素指针。在需要做退出处理的时候才需要实现具体的功能。但是许多情况下可以直接返回。

在next和start的实现中可能需要对一个序列的函数进行遍历,而在内核中,对于一个序列数据结构体的实现一般是使用双向链表或者哈希链表,所有seq_file同时提供了一些对于内核双向链表和哈希链表的封装接口函数,方便程序员实现对于通过链表链接的结构体序列的操作。这些函数名一般是seq_list_或者seq_hlist_,这些函数的实现都在fs/seq_file.c中,有兴趣的朋友可以看看。我在后面的实验中依然使用内核通用的双向链表API。

在用户空间进行“读”、“写”

使用 cat 命令,读进程 1 的状态信息,比如 PID、PPID 和 status 等。

使用 echo ,修改 hostname。

参考帖子

Linux | proc虚拟文件系统

原文地址:https://www.cnblogs.com/narisu/p/8761850.html

时间: 2024-10-08 15:55:16

【转载】proc 虚拟文件系统的相关文章

使用 /proc 文件系统来访问 linux操作系统 内核的内容 &amp;&amp; 虚拟文件系统vfs及proc详解

http://blog.163.com/he_junwei/blog/static/19793764620152743325659/ http://www.01yun.com/other/20130422/366044.html 使用 /proc 文件系统来访问 Linux 内核的内容 这个虚拟文件系统在内核空间和用户空间之间打开了一个通信窗口 简介: /proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux? 内核空间和用户空间之间进行通信.在 /proc 文件系统中,

linux文件系统体系结构 和 虚拟文件系统(VFS)

图 1. Linux 文件系统组件的体系结构 用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系统调用(打开.读取.写和关闭)提供用户接口.系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点. VFS 是底层文件系统的主要接口.这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大.有两个针对文件系统对象的缓存(inode 和 dentry).它们缓存最近使用过的文件系统对象. 每个文件系统实

Linux虚拟文件系统(VFS)学习

虚拟文件系统(Virtual Filesystem)也可称之为虚拟文件系统转换(Virtual Filesystem Switch),是一个内核软件层,用来处理与Unix标准文件系统相关的所有系统调用.其健壮性表现在能为各种文件系统提供一个通用的接口. 通用文件系统模型 VFS所隐含的主要思想在于引入一个通用的文件系统模型(common file model),这个模型能够表示所有支持的文件系统.在通用文件模型中,每个目录被看做一个文件,可以包含若干文件和其他的子目录. 通用文件模型由下列对象类

虚拟文件系统

VFS的层次 文件系统实现与用户进程(或C库)之间. 文件系统分类 基于磁盘的文件系统(ext2/3  fat  iso9660-).虚拟文件系统(proc).网络文件系统(nfs) 通用文件模型 VFS提供一种结构模型,包含了一个强大的文件系统所应具备的所有组件.所有的文件系统实现,都必须提供与VFS定义的结构配合的例程,以弥补两种试图之间的差异. 文件描述符 一个整数,在用户层所有有关文件的操作中用于标识一个文件,在打开文件时由内核创建,特定于进程. inode l  inode是什么? 2

GlusterFS:虚拟文件系统(VFS)介绍

2012-10-27 12:05 1434人阅读 评论(0) 收藏 举报  分类: GlusterFS文件系统研究(13)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 1.概述 Linux虚拟文件系统是一个内核软件层,用来处理与UNIX标准文件系统相关的所有系统调用.其健壮性表现在能为各种文件系统提供一个通用的接口. Linux虚拟文件系统支持的文件系统可以划分为三种主要的类型: 1.磁盘文件系统 这些文件系统管理在本地磁盘分区中可用的磁盘空间或者其他可以起到磁盘作

linux的虚拟文件系统VFS

虚拟文件系统(virtual file system),别名虚拟文件系统开关,是linux中的一个软件层,向用户空间提供文件系统操作接口. VFS包含的系统调用包括open(2).stat(2).read(2).write(2).chmod(2)等等,这些系统调用在进程环境中执行.下面几个重要的数据结构是VFS(虚拟文件系统中涉及到的数据结构): 1.Directory Entry Cache(dcache) VFS实现了系统调用open(2).stat(2).chmod(2),和其他类似的文件

Linux 文件系统---虚拟文件系统VFS----超级块、inode、dentry、file

一: 什么是文件系统,详见:http://zh.wikipedia.org/zh/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F 其实一句话就是管理这块文件的机制(组织方式,数据结构之类...) Linux系统中存在很多的文件系统,例如常见的ext2,ext3,ext4,sysfs,rootfs,proc...很多很多...我们知道每个文件系统是独立的,有自己的组织方法,操作方法.那么对于用户来说,不可能所有的文件系统都了解,那么怎么做到让用户透明的去处理文件呢?例如:

20150514我读《深入理解linux内核》之虚拟文件系统笔记

20150514我读<深入理解linux内核>之虚拟文件系统笔记 2015-05-14 Lover雪儿 虚拟文件系统所隐含的思想就是把很多不同种类的文件系统的共同信息放入内核,其中有一个字段或者函数来支持Linux所支持的所有实际文件系统所提供的任何操作.对所调用的每个读.写或者其他函数,内核都能把他们替换成支持本地Linux文件系统.NTFS文件系统,或者文件所在的任何其他文件系统的实际函数. 虚拟文件系统可以称为虚拟文件系统转换,是一个内核软件层,用来处理与Unix标准文件系统相关的所有系

Linux虚拟文件系统VFS解析

参考<Linux内核设计与实现> 虚拟文件系统(VFS)是linux内核和具体I/O设备之间的封装的一层共通访问接口,通过这层接口,linux内核可以以同一的方式访问各种I/O设备. 虚拟文件系统本身是linux内核的一部分,是纯软件的东西,并不需要任何硬件的支持. 1. 虚拟文件系统的作用 虚拟文件系统(VFS)是linux内核和存储设备之间的抽象层,主要有以下好处. - 简化了应用程序的开发:应用通过统一的系统调用访问各种存储介质 - 简化了新文件系统加入内核的过程:新文件系统只要实现VF