哈工大操作系统实验——实现proc文件系统

参考文章:

操作系统实验08-proc文件系统的实现

在 Linux 0.11 上实现 procfs(proc 文件系统)内的 psinfo 结点。当读取此结点的内容时,可得到系统当前所有进程的状态信息。例如,用 cat 命令显示 /proc/psinfo/proc/hdinfo的内容,可得到:

$ cat /proc/psinfo
pid    state    father    counter    start_time
0    1    -1    0    0
1    1    0    28    1
4    1    1    1    73
3    1    1    27    63
6    0    4    12    817
$ cat /proc/hdinfo
total_blocks:    62000;
free_blocks:    39037;
used_blocks:    22963;
...

procfs 及其结点要在内核启动时自动创建。

相关功能实现在 fs/proc.c 文件内。

必备知识

要点1 procfs简介

正式的 Linux 内核实现了 procfs,它是一个**虚拟文件系统**,通常被 mount(挂载) 到 /proc 目录上,通过虚拟文件和虚拟目录的方式提供访问系统参数的机会,所以有人称它为 “了解系统信息的一个窗口”。

这些虚拟的文件和目录**并没有真实地存在在磁盘**上,而是内核中各种数据的一种直观表示。虽然是虚拟的,但它们都可以通过标准的系统调用(open()read() 等)访问。

其实,Linux 的很多系统命令就是通过读取 /proc 实现的。例如 uname -a 的部分信息就来自 /proc/version,而 uptime 的部分信息来自 /proc/uptime/proc/loadavg

要点2 基本思路

Linux 是通过文件系统接口实现 procfs,并在启动时自动将其 mount 到 /proc 目录上。

此目录下的所有内容都是随着系统的运行自动建立、删除和更新的,而且它们完全存在于内存中,不占用任何外存空间。

Linux 0.11 还没有实现虚拟文件系统,也就是,还没有提供增加新文件系统支持的接口。所以本实验只能在现有文件系统的基础上,通过打补丁的方式模拟一个 procfs

Linux 0.11 使用的是 Minix 的文件系统,这是一个典型的基于 inode 的文件系统,《注释》一书对它有详细描述。它的每个文件都要对应至少一个 inode,而 inode 中记录着文件的各种属性,包括文件类型。文件类型有普通文件、目录、字符设备文件和块设备文件等。在内核中,每种类型的文件都有不同的处理函数与之对应。我们可以增加一种新的文件类型——proc 文件,并在相应的处理函数内实现 procfs 要实现的功能

步骤

要点1 新增proc文件类型

include/sys/stat.h 新增:

#define S_IFPROC 0050000#define S_ISPROC(m)  (((m) & S_IFMT) == S_IFPROC)

要点2 修改mknod()函数和init()函数

psinfo 结点要通过 mknod() 系统调用建立,所以要让它支持新的文件类型。

直接修改 fs/namei.c 文件中的 sys_mknod() 函数中的一行代码,如下:

if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISPROC(mode))inode->i_zone[0] = dev;
// 文件系统初始化

内核初始化的全部工作是在 main() 中完成,而 main() 在最后从内核态切换到用户态,并调用 init()

init() 做的第一件事情就是挂载根文件系统:

void init(void) { //    ……    setup((void *) &drive_info); //    ……
}

procfs 的初始化工作**应该在根文件系统挂载之后开始**。它包括两个步骤:

  • (1)建立 /proc 目录;建立 /proc 目录下的各个结点。本实验只建立 /proc/psinfo

  • (2)建立目录和结点分别需要调用 mkdir()mknod() 系统调用。因为初始化时已经在用户态,所以不能直接调用 sys_mkdir()sys_mknod()。必须在初始化代码所在文件中实现这两个系统调用的用户态接口。

    #ifndef __LIBRARY__
    #define __LIBRARY__
    #endif_syscall2(int,mkdir,const char*,name,mode_t,mode);
    _syscall3(int,mknod,const char*,filename,mode_t,mode,dev_t,dev);
    

    mkdir() 时 mode 参数的值可以是 “0755”(对应 rwxr-xr-x),表示只允许 root 用户改写此目录,其它人只能进入和读取此目录。

    procfs 是一个只读文件系统,所以用 mknod() 建立 psinfo 结点时,必须通过 mode 参数将其设为只读。建议使用 S_IFPROC|0444 做为 mode 值,表示这是一个 proc 文件,权限为 0444(r–r–r–),对所有用户只读。

    mknod() 的第三个参数 dev 用来说明结点所代表的设备编号。对于 procfs 来说,此编号可以完全自定义。proc 文件的处理函数将通过这个编号决定对应文件包含的信息是什么。例如,可以把 0 对应 psinfo,1 对应 meminfo,2 对应 cpuinfo。

也就是说,打开linux-0.11/init/main.c

加入:

#ifndef __LIBRARY__
#define __LIBRARY__
#endif_syscall2(int,mkdir,const char*,name,mode_t,mode);
_syscall3(int,mknod,const char*,filename,mode_t,mode,dev_t,dev);

在init函数中,添加:

mkdir("/proc",0755);
mknod("/proc/psinfo",S_IFPROC|0400,0);
//其余文件以此类推...

编译运行即可看到:

这些信息至少说明,psinfo 被正确 open() 了。所以我们不需要对 sys_open() 动任何手脚,唯一要打补丁的,是 sys_read()

要点3 修改read(),让proc可读

首先分析 sys_read(在文件 fs/read_write.c 中)

要在这里一群 if 的排比中,加上 S_IFPROC() 的分支,进入对 proc 文件的处理函数。需要传给处理函数的参数包括:

  • inode->i_zone[0],这就是 mknod() 时指定的 dev ——设备编号
  • buf,指向用户空间,就是 read() 的第二个参数,用来接收数据
  • count,就是 read() 的第三个参数,说明 buf 指向的缓冲区大小
  • &file->f_posf_pos 是上一次读文件结束时“文件位置指针”的指向。这里必须传指针,因为处理函数需要根据传给 buf 的数据量修改 f_pos 的值。

依照指导书,在read_write.c添加如下语句:

extern int proc_handler(unsigned short dev,char* buf,int count,off_t* f_pos);int sys_read(...){// ...if(S_ISPROC(inode->i_mode)){return proc_handler(inode->i_zone[0],buf,count,&file->f_pos);}// ...
}

要点4 编写pro文件的处理函数

proc 文件的处理函数的功能是根据设备编号,把不同的内容写入到用户空间的 buf。写入的数据要从 f_pos 指向的位置开始,每次最多写 count 个字节,并根据实际写入的字节数调整 f_pos 的值,最后返回实际写入的字节数。当设备编号表明要读的是 psinfo 的内容时,就要按照 psinfo 的形式组织数据。

实现此函数可能要用到如下几个函数:

  • malloc() 函数
  • free() 函数

包含 linux/kernel.h 头文件后,就可以使用 malloc()free() 函数。它们是可以被核心态代码调用的,唯一的限制是一次申请的内存大小不能超过一个页面。

进程的信息就来源于内核全局结构数组 struct task_struct * task[NR_TASKS] 中,具体读取细节可参照 sched.c 中的函数 schedule()

可以借鉴一下代码:

for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)if (*p)(*p)->counter = ((*p)->counter >> 1)+...;

cat 是 Linux 下的一个常用命令,功能是将文件的内容打印到标准输出。

它核心实现大体如下:

#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{char buf[513] = {'\0'};int nread;int fd = open(argv[1], O_RDONLY, 0);while(nread = read(fd, buf, 512)){buf[nread] = '\0';puts(buf);}return 0;
}

在cat的代码中,open函数返回了psinfo的文件描述符,read函数读到该文件描述符,就会识别出我们要读写的文件是PROC类型的,因此就会跳转到我们的proc_handler去执行,再进一步跳转到psinfo_handler执行。根据cat的代码和指导书的提示,不难得出,我们的目标就是把进程的信息按照格式给弄进buf里面,就可以了。

而这也正体现了proc作为“虚拟文件”的特点。对它进行读写,它的信息并非存放在磁盘中,而是全部由放在内存中的逻辑和数据【由task_struct提供】来完成。

在fs文件夹下创建文件proc_dev.c,编写proc文件的处理函数。代码如下:

#include <linux/fs.h>
#include <unistd.h>
#include <asm/segment.h>
#include <stdarg.h>
#include <linux/sched.h>
#include <sys/types.h>
#include <linux/kernel.h>#define set_bit(nr,addr) ({\
register int res ; \
__asm__ __volatile__("btsl %2,%3\n\tsetb %%al": \
"=a" (res):"0" (0),"r" (nr),"m" (*(addr))); \
res;})struct task_struct** p=&FIRST_TASK;
char s[100];
int flag;extern int psinfo_handler(off_t* f_pos,char* buf);
extern int hdinfo_handler(off_t* f_pos,char* buf);int proc_handler(unsigned short dev,char* buf,int count,off_t* f_pos){//根据设备编号,把不同的内容写入到用户空间的 bufswitch(dev){case 0:return psinfo_handler(f_pos,buf);case 1:return hdinfo_handler(f_pos,buf);default:break;}   return -1;
}//在内核态和用户态间传递数据
int put_into_buf(char* buf,char* s){int cnt=0;while(s[cnt]!='\0'){put_fs_byte(s[cnt++],buf++);}return cnt;
}int sprintf(char* buf,const char* fmt,...){va_list args;int i;va_start(args,fmt);i=vsprintf(buf,fmt,args);va_end(args);return i;
}int psinfo_handler(off_t* f_pos,char* buf){int i;//初始化字符串for(i=0;i<100;i++)  s[i]=0;//如果是第一次read,需要在屏幕上打印列表头,并且重置p指针为进程队列头if((*f_pos)==0){sprintf(s,"pid\tstate\tfather\tcounter\tstart_time\n");p=&FIRST_TASK;}//到达文件末尾if((*p)==NULL){return 0;}//每次仅输出一行if((*f_pos)!=0){sprintf(s,"%ld\t%ld\t%ld\t%ld\t%ld\n",(*p)->pid,(*p)->state,(*p)->father,(*p)->counter,(*p)->start_time);p++;}int cnt=put_into_buf(buf,s);*f_pos+=cnt;return cnt;
}//可参考fs/super.c mount_root()
int hdinfo_handler(off_t* f_pos,char* buf){//防止循环多次打印if(flag==1){flag=0;return -1;}struct super_block* sb;sb=get_super(0x301);/*磁盘设备号 3*256+1*/int free=0;int i=sb->s_nzones;while (-- i >= 0)if (!set_bit(i&8191,sb->s_zmap[i>>13]->b_data))free++;sprintf(s,"total_blocks:\t%d\nfree_blocks:\t%d\nused_blocks:\t%d\n",sb->s_nzones,free,sb->s_nzones-free);int cnt=put_into_buf(buf,s);flag=1;return cnt;
}

运行结果:

这部分踩过的坑:

1.LAST_TASK 的定义

对于LAST_TASK,我本来的理解是,当前所有进程的最后一个。

本来我设的是跟schedule一样,另p=LAST_TASK,从末尾开始打印。我那时其余代码跟上面一样,就只是把上面的FIRST改成LAST,结果输出为空,调试发现LAST_TASK==NULL。

然后打开sched.h,看到LAST_TASK的定义:

#define LAST_TASK task[NR_TASKS-1]

原来它就是单纯简单粗暴地指“最后一个”进程23333

我们目前当前的进程数量远远小于进程的最大数量,因此最大数量编号的那个进程自然也就是空的了。

2.char s[100]={0};

用这个的时候编译报错:undefined reference to ’memset‘

说明这个简略写法其实本质是用的memset,而要用memset的话需要包含头文件string.h。经测试得包含了string.h后确实就好使了。

//s_imap_blocks、ns_zmap_blocks、//total_blocks、free_blocks、used_blocks、total_inodesfor(i=0;is_zmap_blocks;i++){bh=sb->s_zmap[i];db=(char*)bh->b_data;for(j=0;j<1024;j++){for(k=1;k<=8;k++){if((used_blocks+free_blocks)>=total_blocks)break;if( *(db+j) & k)used_blocks++;elsefree_blocks++;}

3.我发现一件事

我第一次把init/main.c写错了,写成:

mkdir("/proc",0755);
mknod("/proc/psinfo",S_IFPROC|0400,0);
mknod("/proc/hdinfo",S_IFPROC|0400,0);
mknod("/proc/inodeinfo",S_IFPROC|0400,0);

设别号忘了改了。然后进行了一次编译,运行。

之后我发现错了,就改成了

mkdir("/proc",0755);
mknod("/proc/psinfo",S_IFPROC|0400,0);
mknod("/proc/hdinfo",S_IFPROC|0400,1);
mknod("/proc/inodeinfo",S_IFPROC|0400,2);

再次编译运行,结果上面的那个错还是没改回来

直到我手动把proc文件夹删了,再重新读一次磁盘加载proc文件夹,才回归正常。

感想

本次实验耗时:下午一点到晚上九点半()

本实验通过对proc虚拟文件的编写流程,实际上让我们体会到了“一切皆文件”的思想。

什么东西都可以是文件,只不过它们有不同的文件类型和不同的read/write处理函数。

对于终端设备和磁盘,其read/write函数本质上是在用out指令跟它的缓冲区交互,只不过磁盘比终端设备抽象层次更深,包含了文件系统的层层封装。

对于虚拟文件,其read/write函数本质上就是与内存交互,通过一段逻辑【处理函数】将内存存储的当前操作系统信息实时显示出来,而不需要存储。

哈工大操作系统实验——实现proc文件系统相关推荐

  1. linux实验报告哈工大,哈工大操作系统实验---lab8:proc文件的实现

    文章目录 实验目的 掌握虚拟文件系统的实现原理 实践文件.目录.文件系统等概念 实验内容 在Linux0.11上实现procfs(proc文件系统)内的psinfo节点,当读取此节点的内容的时候,可得 ...

  2. 哈工大操作系统实验总结

    实验地址, https://www.lanqiao.cn/courses/115/learning/?id=374, 在现做实验, 好处是环境提前都配好了, 不足之处是敲代码有网络延迟, 环境无法保存 ...

  3. 操作系统实验——简易FAT16文件系统的实现

    操作系统实验--简易FAT16文件系统的实现 前言 实验要求 FAT16基础知识 磁盘组成部分 分区原理 思路 完整代码 前言 暑假啦!呼,所有的补课终于也都结束了,虽然绩点还是一如既往的拉跨,但是很 ...

  4. 哈工大操作系统实验一——操作系统的引导

    写在前面 哈尔滨工业大学李治军老师的<操作系统>课程实验,相关资源: 哈工大操作系统实验手册 实验资源与参考 不配环境懒人福利:实验楼 在线课程:操作系统,李治军,哈工大(网易云课堂) 参 ...

  5. 广州大学2020操作系统实验四:文件系统

    相关资料 广州大学2020操作系统实验一:进程管理与进程通信 广州大学2020操作系统实验二:银行家算法 广州大学2020操作系统实验三:内存管理 广州大学2020操作系统实验四:文件系统 广州大学2 ...

  6. 哈工大操作系统实验1-操作系统引导

    哈工大操作系统实验1-操作系统引导 实验内容: 1. 改写 bootsect.s 主要完成如下功能: bootsect.s 能在屏幕上打印一段提示信息"XXX is booting...&q ...

  7. 操作系统实验四:文件系统

    一.实验目的 1.熟悉Linux文件系统的文件和目录结构,掌握Linux文件系统的基本特征: 2.模拟实现Linux文件系统的简单I/O流操作:备份文件. 二.实验环境 Linux系统 三.实验内容 ...

  8. 哈工大操作系统实验坏境搭建

    学习目标: 在Linux搭建Linux -0.11 实验环境 学习内容: 1.将笔记后面的资源下载下来,并通过FTP传输到Linux上 FTP的使用:FTP文件传输 2.安装GCC3.4 (1)解压 ...

  9. 哈工大操作系统实验:动手修改操作系统内核,自定义开机界面

    注意:其中一部分英文注释是我的塑料英文,主要是为了和源码一起看起来更和谐一点,很容易懂 实验做完了,才发现实验后网页有老师给的参考答案,哈哈反正我也不看,得记录一下,不记录就亏了.一天多时间都花在汇编 ...

最新文章

  1. WebUploader 上传图片回显
  2. 变频器端子阻抗3k_PLC与变频器连接问题分析
  3. redis 存储数据不设置过期时间 会自动过期吗_Redis-数据淘汰策略持久化方式(RDB/AOF)Redis与Memcached区别...
  4. 你该知道的深度强化学习相关知识
  5. 蚂蚁金服OceanBase挑战TPCC | TPC-C基准测试之存储优化
  6. cilium插件测试_Cilium网络概述
  7. fcn+caffe+siftflow实验记录
  8. springboot+aop+自定义注解,打造通用的全局异常处理和参数校验切面(通用版)
  9. SCRUM 12.23
  10. 《Java程序设计精编教程(第3版)》之课后习题 - 个人作
  11. 西宾喜马拉雅语音下载工具(ximalayadown)
  12. word中使用通配符替换【持续更新系列】
  13. Windows神软Classic Shell停更!经典开始菜单永别了
  14. OpenMAX编程-音视频等组件介绍
  15. Linux服务器需要安装代理软件EPS(agent)数据库
  16. 计算机如何进行加减乘除计算—(计算机基础课十三)​​​​​​​​​​​​​​
  17. “云钉一体”战略解读:阿里打通了数字化的“罗马引水桥”
  18. 小白学习Winform 遇到的问题总结
  19. 云服务器除了阿里云外其他哪个比较好?
  20. 解决QT加载dll失败问题

热门文章

  1. 20-判断年份是否为闰年
  2. Eclipse入门操作之快速上手、Eclipse的快捷键:
  3. 知识图谱第6享:动态本体
  4. HFS网络文件服务器,实现外网访问局域网
  5. MTK的Android刷机包各个文件作用
  6. javaScript ES6-ES11新特性总结
  7. 假设检验之参数检验 ------- 配对样本 t 检验
  8. 数据预处理2: impute.SimpleImputer来填补缺失值
  9. HBase入门介绍(从基础到架构)
  10. Linux下的SVN服务器搭建