【哈工大_操作系统实验】Lab9 proc文件系统的实现

October 28, 2024

Github代码仓库链接

本节将更新哈工大《操作系统》课程第九个 Lab 实验 proc文件系统的实现。按照实验书要求,介绍了非常详细的实验操作流程,并提供了超级无敌详细的代码注释。

实验目的:

  • 掌握虚拟文件系统的实现原理;
  • 实践文件、目录、文件系统等概念。

实验任务:

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

在这里插入图片描述

  • cat:显示文件内容、将文件内容合并输出到终端或其他文件。
  • procfs,它是一个虚拟文件系统,通常被 mount(挂载) 到 /proc 目录上,通过虚拟文件和虚拟目录的方式提供访问系统参数的机会;
  • 这些虚拟的文件和目录并没有真实地存在在磁盘上,而是内核中各种数据的一种直观表示,是完全存在于内存中的。虽然是虚拟的,但它们都可以通过标准的系统调用(open()、read() 等)访问。

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

一、增加 proc 文件

1、在 include/sys/stat.h 文件定义中新增 proc 文件的 宏定义 和 测试宏:

//proc文件的宏定义/宏函数 
#define S_IFPROC 0030000 
#define S_ISPROC(m) (((m) & S_IFMT) == S_IFPROC) //测试m是否是proc文件

2、修改 fs/namei.c 文件,让mknod() 支持新的文件类型

proc 目录下创建psinfohdinfoinodeinfo等文件,需要调用mknod 函数 -> sys_mknod 函数,则需要让它支持新的文件类型,成功创建inode

int sys_mknod(const char * filename, int mode, int dev)
{
//  ....
	inode->i_mode = mode;
    if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISPROC(mode))
        inode->i_zone[0] = dev;
    inode->i_mtime = inode->i_atime = CURRENT_TIME;

3、修改 init/main.c 文件,创建进程proc文件 procfs 的初始化工作应该在根文件系统挂载之后开始,包括两个步骤:

  1. 建立 /proc 目录,通过用户态调用mkdir() -> sys_mkdir()
  2. 建立 /proc 目录下的各个结点,通过用户态调用mknod() -> sys_mknod()

/* 新增用户态接口 mkdir 和 mknode 系统调用*/
_syscall2(int,mkdir,const char*,name,mode_t,mode)
_syscall3(int,mknod,const char *,filename,mode_t,mode,dev_t,dev)

void init(void)
{
    int pid,i;

    setup((void *) &drive_info);
    (void) open("/dev/tty0",O_RDWR,0);
    (void) dup(0);
    (void) dup(0);

    /* 创建proc目录 和 文件 */
    mkdir("/proc",0755);
    mknod("/proc/psinfo",S_IFPROC|0444,0);
    mknod("/proc/hdinfo",S_IFPROC|0444,1);
    mknod("/proc/inodeinfo",S_IFPROC|0444,2);
//  ....
  • 参数 0755(对应 rwxr-xr-x),表示只允许 root 用户改写此目录
  • 参数 S_IFPROC|0444 做为 mode 值,表示这是一个 proc 文件,权限为 0444(r--r--r--),对所有用户只读。
  • mknod() 的第三个参数 dev 用来说明结点所代表的设备编号。

此时若调用 cat /proc/psinfo 会报错,因为内核在对 psinfo 进行读操作时不能正确处理,所以还要创建 proc_read 函数,并在 sys_read() 中打补丁。

二、实现 proc 文件可读

1、创建 fs/proc.c 文件,实现根据设备编号,把不同的内容写入到用户空间的 buf,使得 cat 指令可以正确获取 proc 文件内容。

#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <linux/fs.h>
#include <stdarg.h>
#include <unistd.h>

// 内联汇编定义一个宏,用于在位图中测试特定位是否被设置
#define set_bit(bitnr,addr) ({ \
register int __res ; \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res; })

// 定义一个缓冲区,用于存储进程、硬盘或 inode 信息,大小为 4096 字节
char proc_buf[4096] ={'\0'};

// 声明一个外部函数 vsprintf,用于将格式化输出写入字符串
extern int vsprintf(char * buf, const char * fmt, va_list args);

//Linux0.11没有sprintf(),该函数是用于输出结果到字符串中的,所以就实现一个,这里是通过vsprintf()实现的。
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;
}

// 获取进程信息函数,并将其格式化写入 proc_buf
int get_psinfo()
{
    int read = 0;
    read += sprintf(proc_buf+read,"%s","pid\tstate\tfather\tcounter\tstart_time\n");
    struct task_struct **p;
    // 遍历所有进程
    for(p = &FIRST_TASK ; p <= &LAST_TASK ; ++p)
     if (*p != NULL)
     {
	     // 进程ID 进程状态 父进程ID 时间片 启动时间
         read += sprintf(proc_buf+read,"%d\t",(*p)->pid);
         read += sprintf(proc_buf+read,"%d\t",(*p)->state);
         read += sprintf(proc_buf+read,"%d\t",(*p)->father);
         read += sprintf(proc_buf+read,"%d\t",(*p)->counter);
         read += sprintf(proc_buf+read,"%d\n",(*p)->start_time);
     }
     return read;
}

// 获取硬盘信息(参考fs/super.c mount_root()函数)
int get_hdinfo()
{
    int read = 0;
    int i,used;
    struct super_block * sb;
    sb=get_super(0x301);  /*磁盘设备号 3*256+1*/
    /*Blocks信息:总块数、已用块数、空闲块数*/ 
    read += sprintf(proc_buf+read,"Total blocks:%d\n",sb->s_nzones);
    used = 0;
    i=sb->s_nzones;
    while(--i >= 0)
    {
        if(set_bit(i&8191,sb->s_zmap[i>>13]->b_data))
            used++;
    }
    read += sprintf(proc_buf+read,"Used blocks:%d\n",used);
    read += sprintf(proc_buf+read,"Free blocks:%d\n",sb->s_nzones-used);
    /*Inodes 信息:inode数量、已用/空闲inode数量*/
    read += sprintf(proc_buf+read,"Total inodes:%d\n",sb->s_ninodes);
    used = 0;
    i=sb->s_ninodes+1;
    while(--i >= 0)
    {
        if(set_bit(i&8191,sb->s_imap[i>>13]->b_data))
            used++;
    }
    read += sprintf(proc_buf+read,"Used inodes:%d\n",used);
    read += sprintf(proc_buf+read,"Free inodes:%d\n",sb->s_ninodes-used);
     return read;
}

// 获取inode信息,inode 号和第一个数据区块号
int get_inodeinfo()
{
    int read = 0;
    int i;
    struct super_block * sb;
    struct m_inode *mi;
    sb=get_super(0x301);  /*磁盘设备号 3*256+1*/
    i=sb->s_ninodes+1;
    i=0;
    while(++i < sb->s_ninodes+1)
    {
        if(set_bit(i&8191,sb->s_imap[i>>13]->b_data))
        {
            mi = iget(0x301,i);
            read += sprintf(proc_buf+read,"inr:%d;zone[0]:%d\n",mi->i_num,mi->i_zone[0]);
            iput(mi);
        }
        if(read >= 4000) 
        {
            break;
        }
    }
     return read;
}

// proc文件读取函数,根据设备号决定读取信息,并将读取的信息复制到用户空间的缓冲区
int proc_read(int dev, unsigned long * pos, char * buf, int count)
{
    
     int i;
    if(*pos % 1024 == 0)
    {
        if(dev == 0)
            get_psinfo();
        if(dev == 1)
            get_hdinfo();
        if(dev == 2)
            get_inodeinfo();
    }
     for(i=0;i<count;i++)
     {
         if(proc_buf[i+ *pos ] == '\0')  
          break; 
         put_fs_byte(proc_buf[i+ *pos],buf + i+ *pos);
     }
     *pos += i;
     return i;
}

2、修改 fs/Makefile 文件中的编译规则:

OBJS=	open.o read_write.o inode.o file_table.o buffer.o super.o \
	block_dev.o char_dev.o file_dev.o stat.o exec.o pipe.o namei.o \
	bitmap.o fcntl.o ioctl.o truncate.o proc.o
	
//......
### Dependencies:
proc.o : proc.c ../include/linux/kernel.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/asm/segment.h

3、修改 fs/read_write.c 文件,在 sys_read 中添加对 proc 文件的处理函数补丁,这样cat指令才能成功读到内容。

/*新增proc_read函数外部调用*/
extern int proc_read(int dev,char* buf,int count,unsigned long *pos);

int sys_read(unsigned int fd,char * buf,int count)
{
//  ....
    if (inode->i_pipe)
        return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;

    /*新增proc_read调用*/
    if (S_ISPROC(inode->i_mode))
        return proc_read(inode->i_zone[0],&file->f_pos,buf,count);
        
//  ....
}
  • inode->i_zone[0],这就是 mknod() 时指定的 dev :设备编号
  • &file->f_pos,f_pos 是上一次读文件结束时“文件位置指针”的指向
  • buf,指向用户空间,就是 read() 的第二个参数,用来接收数据
  • count,就是 read() 的第三个参数,说明 buf 指向的缓冲区大小

三、编译并运行

1、编译并运行

cd oslab_Lab8/linux-0.11
make all
../run

2、测试结果

在 Bochs 中进行测试:

cat /proc/psinfo
cat /proc/hdinfo
cat /proc/inodeinfo

在这里插入图片描述


Profile picture

Written by JokerDebug who works at Southeast University, Nanjing, China You can follow me on Github