Proc 概念

  • 在Linux中的 /proc 是一个虚拟文件系统,该目录不包含真正的文件,而是包含着运行时系统的各种信息(系统内存、安装的设备、硬件配置、进程等),是内核的控制和信息中心,被很多系统实时程序调用(lsmod / cat /proc/modules),可以改变其中的某些文件来达到改变内核运行状态的目的 (sysctl

  • 该文件系统大致包含了

    • 与进程相关的目录
    • 通用的系统信息
    • 网络信息
    • 系统控制信息
  • 该文件系统中,大部分文件 [夹]的大小都为0,除了 kcore / mtrr / self 文件外,这里面的每一个文件都被绑定到一个内核函数上,当文件被读取的时候即时的产生文件内容

  • /proc 中以数字命名的文件夹,表示系统当前正在运行的进程的PID,里面包含着与该进程相关的一些文件

    • cmdline : 命令行参数。启动当前进程的完整指令,僵尸进程该文件下无内容
    • cwd : 到当前工作目录的一个链接
    • environ : 环境变量的值
    • exe : 当前进程的可执行文件链接,可以使用 /proc/pid/exe 启用当前进程的一个拷贝
    • fd : 文件描述
    • 太多了,每一个的介绍移步 此处

seq_file

seq_file

  • 由于 profs 的默认操作函数只能适用一页的缓存,所以在处理大文件 / 输出多个结构体时时很不方便,所以有一些大佬们就开发出来了伟大的 seq_file (序列文件)

  • seq_file 的头文件定义才如下位置,这里按照新下载的内核加压缩文件为例

  • 其结构体定义如下

    struct seq_file {
      char *buf;
      size_t size;
      size_t from;
      size_t count;
      size_t pad_until;
      loff_t index;
      loff_t read_pos;
      u64 version;
      struct mutex lock;
      const struct seq_operations *op;
      int poll_event;
      const struct file *file;
      void *private;
    };
    • buf 表示 seq_file 接口使用的缓存页指针
    • size : 表示缓存页大小
    • from : 从 seq_file 中向用户态缓存区拷贝时,相对于 buf 的偏移地址
    • count : buf 中可以拷贝到用户态的字符数
    • index : seq_operationsstart / next 的下标值, seq_operations 定义下文给出
    • read_pos : 当前已经拷贝到用户态的文件的数据大小
    • ……其实都是黑匣子,关键还是要自己定义 seq_operations

seq_operations

  • 该结构体也在上一个头文件中,定义如下:

    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);
    };
  • 根据 Linux.die.net给出的定义,当用户访问 /proc 时,seq_file API就会开始产生一个序列,该序列主要由 start() / stop() / next() 组成

  • 一个序列开始于用户对 start() 函数的调用,若返回值非 NULL,则会首先调用 next() 函数的,next() 函数是一个从数据头迭代到数据尾的迭代器,当 next() 函数被调用,同时也会紧接着调用 show() 函数,show() 函数将会数据值写入用户读取的缓冲区中,读取完毕则返回 NULL,此时会再次唤醒 next() 函数,当 next() 返回 NULL ,也就可以说是读取完毕,那么调用 stop()

  • 综上分析, seq_operations 实现了读取数据序列,并且将其写入 seq_file 自建的缓存中,所以,只需要写好迭代等函数的接口定义就好了,其他的前辈们都做好了黑箱,下面记录一下对每一个函数接口的理解

  • start

    void * (*start) (struct seq_file *m, loff_t *pos);
    • 该方法在 第一次读取 /proc 文件 / stop() 函数被调用之后,会被自动调用

    • struct seq_file *m 中,m 的类型是一个 seq_file 的指针,一般指向当前 seq_file 结构体

    • loff_t *pos 中的 loff_t 就是一个 long long ,于此表示一个位置,某论坛大佬说,不同的版本,不一定代表字节起始地址还是一个元素地址, lwn.net 定义如下

      Modules implementing a virtual file with seq_file must implement a simple iterator object that allows stepping through the data of interest. Iterators must be able to move to a specific position - like the file they implement - but the interpretation of that position is up to the iterator itself. A seq_file implementation that is formatting firewall rules, for example, could interpret position N as the Nth rule in the chain. Positioning can thus be done in whatever way makes the most sense for the generator of the data, which need not be aware of how a position translates to an offset in the virtual file. The one obvious exception is that a position of zero should indicate the beginning of the file.

      The /proc/sequence iterator just uses the count of the next number it will output as its position.

      start() takes a position as an argument and returns an iterator which will start reading at that position.

    • 返回值规定是一个迭代器,该迭代器的整个数据结构是保存当前位置的单个 loff_t 数值

  • show

    int (*show) (struct seq_file *m, void *v);
    • 该方法将格式化当前迭代器所指向的元素以便于输出,返回 0 值表示正常终止

    • 该方法就是将 v 指向的元素中的数据,借助 seq_file 提供一的一些工具,输出到 seq_file 的内部缓存中

    • 下面的几个函数一般用于直接输出字符 / 字符串

      int seq_putc(struct seq_file *m, char c);
      int seq_puts(struct seq_file *m, const char *s);
      int seq_escape(struct seq_file *m, const char *s, const char *esc);

      前两个用于直接输出单个字符 / 字符串,最后一个是任何一个在 esc 中的 s 中的字符将以八进制的形式输出

    • 下面的函数一般用于打印文件名

      int seq_path(struct seq_file *m, struct vfsmount *mnt, 
               struct dentry *dentry, char *esc);

      mntdentry 中是感兴趣的文件部分, esc 是在输出中应该转义的一些字符。原文是这样的

  • next

    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    • v 是之前调用 start / next 返回的元素指针,上一个已经完成输出的元素的地址
    • pos 是需要迭代器移动到的元素的索引值
    • next 返回值非 NULL 是,迭代到下一个输出位置,若返回NULL,那么就调用 stop
  • stop

    void (*stop) (struct seq_file *m, void *v);
    • m 一般指向当前 seq_file 结构体, v 一般是指向上一个 next / stop 的元素指针
    • 一般直接返回就行,除非需要在退出时做某些操作才进行处理

连结上述函数与结构体

  • seq_operations 集合

    static struct seq_operations scull_seq_ops = {
     .start = scull_seq_start,
     .next = scull_seq_next,
     .stop = scull_seq_stop,
     .show = scull_seq_show
    };

    该静态结构体将我们自己写好的与功能相关的 iterator 操作集合包装起来形成一个集合供 seq_file 与其他函数 / 结构体调用

  • open 函数

    static int scull_proc_open(struct inode *inode, struct file *file)
    {
        return seq_open(file, &scull_seq_ops);
    }

    该方法链接文件到 seq_file

  • file_operations 结构体

    static struct file_operations scull_proc_ops = {
     .owner = THIS_MODULE,
     .open = scull_proc_open,
     .read = seq_read,
     .llseek = seq_lseek,
     .release = seq_release 
    }; 

    此时,该结构体中的 .open 理应为 自己编写的 my_proc_open 方法,其余的都是原来就预装好的,直接使用即可

  • 创建 /proc 中的实际文件

    entry = create_proc_entry("pcbshower", 0644, NULL);
    if (entry)
        entry->proc_fops = &scull_proc_ops;

    此处官方给出的建议是不使用 create_proc_read_entry ,而是调用更加贴近底层的 create_proc_entry ,参数分别表示 文件名,位置,父级目录

内核模块显示进程控制块信息

  • 代码存在了Github,可以通过curl 获取,有BUG的话发PR呀,谢谢~GitHub

效果图

/**
 * @file pcbshower.c
 * @author FancyKing ([email protected])
 * @brief 利用进程控制块显示进程信息
 * @version 0.1
 * @date 2019-05-25 - 2019-05-26
 *
 * @copyright Copyright (c) 2019
 *
 */

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#include <linux/seq_file.h>
#include <linux/slab.h>

#define FILENAME "pcbshower"
struct task_struct *shower;

int task_nums = 0, num_of_running = 0, num_of_interruptible = 0,
    num_of_uninterruptible = 0, num_of_zombie = 0, num_of_stopped = 0,
    num_of_traced = 0, num_of_dead = 0, num_of_unknown = 0, tmp_exit_state = 0,
    tmp_state = 0;

static void *scull_seq_start(struct seq_file *m, loff_t *pos) {
  if (*pos == 0) {
    shower = &init_task;
    return &shower;
  } else {
    if (shower == &init_task) {
      return NULL;
    } else {
      return (void *)pos;
    }
  }
}

static void *scull_seq_next(struct seq_file *m, void *v, loff_t *pos) {
  (*pos)++;
  shower = next_task(shower);
  return NULL;
}

static void scull_seq_stop(struct seq_file *m, void *v) {
  if (shower == &init_task) {
    seq_printf(m, "[M] total tasks          : %10d\n", task_nums);
    seq_printf(m, "[M] TASK_RUNNING         : %10d\n", num_of_running);
    seq_printf(m, "[M] TASK_INTERRUPTIBLE   : %10d\n", num_of_interruptible);
    seq_printf(m, "[M] TASK_UNINTERRUPTIBLE : %10d\n", num_of_uninterruptible);
    seq_printf(m, "[M] TASK_TRACED          : %10d\n", num_of_stopped);
    seq_printf(m, "[M] TASK_TRACED          : %10d\n", num_of_stopped);
    seq_printf(m, "[M] EXIT_ZOMBIE          : %10d\n", num_of_zombie);
    seq_printf(m, "[M] EXIT_DEAD            : %10d\n", num_of_dead);
    seq_printf(m, "[M] UNKNOWN              : %10d\n", num_of_unknown);
  }
}

int scull_seq_show(struct seq_file *m, void *v) {
  seq_printf(m, "#%-3d ", task_nums++);
  seq_printf(m, "%7d ", shower->pid);
  seq_printf(m, "%5lu ", shower->state);
  seq_printf(m, "%-20s ", shower->comm);
  seq_printf(m, "%-5d ", shower->prio);
  seq_printf(m, "%-5d ", shower->static_prio);
  seq_printf(m, "%-5d ", shower->normal_prio);
  seq_printf(m, "%-4d ", shower->rt_priority);
  seq_printf(m, "%u ", shower->policy);
  seq_printf(m, "%-11lu ", shower->nvcsw);
  seq_printf(m, "%5lu ", shower->nivcsw);
  seq_printf(m, "%13lu ", shower->utime);
  seq_printf(m, "%13lu ", shower->stime);
  // seq_printf( m,  "%lu ", shower->real_start_time.tv_sec);
  seq_puts(m, "\n");
  tmp_state = shower->state;            // put p->state to variable t_state
  tmp_exit_state = shower->exit_state;  // similar to above

  if (tmp_exit_state != 0) {
    switch (tmp_exit_state) {
      case EXIT_ZOMBIE:
        num_of_zombie++;
        break;
      case EXIT_DEAD:
        num_of_dead++;
        break;
      default:
        break;
    }
  } else {
    switch (tmp_state) {
      case TASK_RUNNING:
        num_of_running++;
        break;
      case TASK_INTERRUPTIBLE:
        num_of_interruptible++;
        break;
      case TASK_UNINTERRUPTIBLE:
        num_of_uninterruptible++;
        break;
      case TASK_STOPPED:
        num_of_stopped++;
        break;
      case TASK_TRACED:
        num_of_traced++;
        break;
      default:
        num_of_unknown++;
        break;
    }
  }
  return 0;
}

static struct seq_operations scull_seq_ops = {.start = scull_seq_start,
                                              .next = scull_seq_next,
                                              .stop = scull_seq_stop,
                                              .show = scull_seq_show};

static int scull_proc_open(struct inode *inode, struct file *file) {
  return seq_open(file, &scull_seq_ops);
}

static const struct file_operations scull_proc_ops = {.owner = THIS_MODULE,
                                                      .open = scull_proc_open,
                                                      .read = seq_read,
                                                      .llseek = seq_lseek,
                                                      .release = seq_release};

int __init shower_init(void) {
  struct proc_dir_entry *scull_seq_entry;
  printk("Now installing the module: %s \n", FILENAME);
  scull_seq_entry = proc_create(FILENAME, 0x644, NULL, &scull_proc_ops);
  if (scull_seq_entry == NULL) {
    return -ENOMEM;
  } else {
    return 0;
  }
}

void __exit shower_exit(void) {
  remove_proc_entry(FILENAME, NULL);
  printk("Now removing the module: %s \n", FILENAME);
}

module_init(shower_init);
module_exit(shower_exit);

MODULE_LICENSE("GPL");

标签: Linux, Kernel

添加新评论