第 13 章 基于 proc 的 Linux 进程控制块 信息读取 山东科技大学操作系统教研组
理解 proc 伪文件系统的基本概念和功能,掌握常见操作命令。 了解 Linux 进程控制块 task_strcut ,并理解其重要成员变量的含义。 理解基于 seq_file 机制的 proc 伪文件操作机制 熟练掌握生成 proc 伪文件的 Linux 内核模块代码实现方法。 通过实验认识 Linux 的进程概念,并加深对操作系统进程概念的理解。 掌握 Linux 内核为用户提供服务的方式、方法,尤其是 proc 机制。 Proc 是 Linux 为用户提供内核和计算机运行状态的主要接口,是从事 Linux 运 维、管理工作所必须掌握的基本技能。
实验 1 :测试 proc 文件系统功能 (教材 P.168 ) 实验 2 :创建显示系统进程信息的 proc 模块 (教材 P.179 ) 拓展实验:显示 Linux 进程控制块中更多的信息 (教材 P.182 )
实验目的 理解 proc 伪文件系统的基本概念和功能,掌握常见操 作命令。 通过实验深入理解操作系统为用户提供服务的方式、 方法
实验内容 请运行以下命令并仔细观察实验结果,理解 proc 文件系统的功能、作用 $ cat /proc/cpuinfo $ cat /proc/modules $ cat /proc/meminfo $ cat /proc/iomem $ cat /proc/devices $ cat /proc/self/maps $ cat /proc/filesystems $ cat /proc/version 提示:请结合 Linux 源代码文档中的 proc.txt 或网上资料了解 proc 伪文件的机制和功能
原理 / 背景 Proc 是什么 首先, proc 是 Linux 系统根目录下名为 “proc” 的文件目录。 其次,它是一种伪文件系统。它只存在内存当中,不像普通文件 一样占用外存空间。它以文件系统的方式提供应用程序访问系统 内核数据的操作接口。 proc 伪文件系统动态地从系统内核读出所 需信息并提交给应用程序 Proc 文件系统提供哪些信息? 与进程相关的目录 通用系统信息 网络信息 系统控制信息
原理 / 背景 Proc 可以作为动态添加的 LKM 模块的输出接口。 应用程序可以通过读取 proc 中相应的模块信息来获取动态添加的 LKM 模块输出。 后续各章我们将采用 proc 作为 LKM 与应用程序的主要接口。 提醒: cat 是读取 proc 的一个命令;读者可以在编写程序,在程序中 通过函数调用直接读取 proc 信息。相关实现,可以参考 cat 程序。
实验目的 了解 Linux 进程控制块 task_strcut ,并理解其重要成员变量的 含义。 理解基于 seq_file 机制的 proc 伪文件操作机制 熟练掌握生成 proc 伪文件的 Linux 内核模块代码实现方法。 认识 Linux 的进程概念,深入理解操作系统的进程概念,
实验内容 1. 从教材提供的电子资源中找到或者按教材提示自己编写 Linux 内核模块 tasklist.c 及其对应的 Makefile 文件 2. 该模块创建 /proc/tasklist 文件,并且提取系统中所有进程 的 pid 、 state 和名称进行显示。 3. 编译、安装、删除该模块,查看该模块的安装位置、运 行情况
实验要求 1. 通过阅读、执行 tasklist.c 、对应的 Makefile 文件及其执行结果, 理解 Linux 中进程控制块的管理方式、以及进程控制块的基本 组成。 2. 通过阅读、执行 tasklist.c 、对应的 Makefile 文件及其执行结果, 深入理解 Linux 操作系统为用户提供服务的 proc 机制的工作原 理和方法,尤其是 proc 的创建方式和基于 seq_file 的操作机制。 3. 结合操作系统知识,通过实验深入理解计算机程序在内核空间 的运行方式。
原理 / 背景 Linux 进程控制块 LKM 模块操作 Proc 文件的方式
Linux 的进程控制块 PCB 使用 task_struct 结构体进行 描述 进程概念 进程控制块概念 进程控制块中保存的信息是进程在执行过程的快照, 包含右图中用户进程在用户空间中的分布信息,但 不仅仅局限于这些信息。 进程控制块位于内核空间,只能被特权级别的进程, 即操作系统进程操作,而不能被用户程序操作 换句话说,用户程序不能直接获取进程信息,必须 通过 OS 。 图 13.4 一个用户进程的虚 拟地址空间分布示意图
* task_struct 结构体定义在 头文件中 struct task_struct { /*Linux 内核 */ volatile longstate; // 进程状态 void*stack; // 进程内核栈 unsigned longflags; // 进程标记 struct mm_struct*mm; // 进程内存结构体 struct thread_struct*thread; // 所包含的线程的结构体 pid_tpid; // 进程唯一标识符 pid_t tgid; // 线程组对应的进程标识符 char comm[TASK_COMM_LEN]; // 进程名称 /* 其他省略 */ };
* Linux 中的进程状态和转换 #define TASK_RUNNING0 // 表示运行或可运行 #define TASK_INTERRUPTIBLE1 // 进程正在睡眠或者说阻塞, 但可被唤醒 #define TASK_UNINTERRUPTIBLE2 // 进程正在睡眠或者说阻塞, 而且不能被唤醒 #define __TASK_STOPPED4 // 进程停止执行 #define __TASK_TRACED8 // 进程可以被追踪,比如处于调试状态 /* in tsk->exit_state */ #define EXIT_ZOMBIE16 #define EXIT_DEAD32
LKM 模块操作 Proc 文件的方式 Proc 文件只能由 Linux 内核代码创建。 Porc 文件有一套规定的操作函数和方法。 Linux 内核 3.x.x 版本中 proc 文件的访问方式与 2.x.x 版本相比发生 了重大改变。网络上很多 proc 的编程说明都只适用于老版本内核 Proc 文件包括两个级别的操作: 文件级别的操作:创建 proc 时指定操作函数结构体 记录级别的操作:打开 proc 文件时指定操作函数结构体
Proc 文件创建函数 一个创建示例 static inline struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,conststruct file_operations *proc_fops) char modname[] = “tasklist” ; // 声明要创建的 proc 文件的名称,此处为 “/proc/tasklist” struct proc_dir_entry* my_proc_entry; my_proc_entry = proc_create(modname, 0, NULL, &my_proc); static const struct file_operations my_proc = {.owner = THIS_MODULE,.open = my_open,.read = seq_read,.llseek = seq_lseek,.release = seq_release }; // 先定义 proc 文件操作函数结构体,并实现自己定义的函数,如 my_open
* Proc 记录采用 seq_file 机制(序列文件机制)进行操作 * seq_file 机制的相关函数和定义说明包含在 头文件中 * 一个示例 首先定义记录操作函数结构体。 根据程序功能,实现所定义的函数 如 my_seq_start 等。 其次在定义的 proc 文件操作函数 my_proc 中的 open 函数中指明使用该结 构体 static struct seq_operations my_seq_fops = {.start = my_seq_start,.next = my_seq_next,.stop = my_seq_stop,.show = my_seq_show }; static int my_open(struct inode *inode, struct file *file) { return seq_open(file, &my_seq_fops); }
本实验代码为电子资源中 “/ 源代码 /ch13/vexp1” 目录下的 tasklist.c 和对应的 Makefile 文件。 编译: make 添加 LKM 模块 $ sudo insmod tasklist.ko 查看 LKM 运行结果: $ cat /proc/tasklist 删除 LKM 模块 $ sudo rmmod tasklist 请读者按着本节内容提示完成 tasklist 内核模块的添加和撤销实验, 并结合实验结果理解 tasklist 源代码以及本节内容。掌握 proc 的操作 方法以及进程控制块的读取方法。
– 请对照代码和实验结果,理解整个 tasklist 模块的运行流程
Linux 进程调度策略 Linux 进程优先级 /* * Scheduling policies */ #define SCHED_NORMAL0 #define SCHED_FIFO1 #define SCHED_RR2 #define SCHED_BATCH3 /* SCHED_ISO: reserved but not implemented yet */ #define SCHED_IDLE5 在 task_struct 结构体中有多个与进程优先级相关的成员变量,定义如下: int prio, static_prio, normal_prio; unsigned int rt_priority;
实验 3 :显示 Linux 进程控制块中更多的信息实验目 标为实现一个内核模块,该模块创建 /proc/tasklist 文 件,并且提取系统中所有进程的 pid 、 state 、名称、 优先级、运行时间、上下文切换等信息进行显示。本 实验代码为电子资源中 “/ 源代码 /ch13/vexp2” 目录下 的 tasklist.c 和对应的 Makefile 文件。
上述实验都是编写一个 LKM 模块,创建一个用于读取 进程控制快信息的 proc 文件 tasklist 。应用程序,如 cat ,访问 /proc/tasklist 从而获得系统中正在运行的进 程信息。 除了读取信息外,应用程序还需要向内核传递消息, 即写入。 从应用程序向内核写入信息,必须经过严格审查,否 则容易引起系统的崩溃。 Linux 不允许应用程序直接向系统写入数据。应用程 序必须向一个 LKM 模块传递消息,然后由 LKM 模块负 责将消息写入。
LKM 调用两个系统函数实现用户空间和内核空间之间的数据传 递 copy_from_user 完成从用户空间拷贝数据到内核空间 copy_to_user 完成从内核空间拷贝数据到用户空间。 内核中的内存空间管理更为严格,必须进行内核内存的分配和 回收: kmalloc(size_t size, int flags) // 请注意该函数与应用程序使 用的 malloc 函数的差异 kfree
* strace 命令可以用于追踪系统调用的过程和返回结果。在使用 cat 读取 proc 文件时可以使用 strace 命令查看从应用程序到内核模块的系统调用。 特别是在完成课后练习 1 的情况下,请读者执行 strace 命令,加深对操 作系统系统调用概念和流程的理解 。 * 课后练习 1,2,3