哈工大操作系统实验---lab8:proc文件的实现

文章目录


实验目的

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

实验内容

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

# 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文件。


实验过程

  • 第一步: 增加新文件类型,在include/sys/stat.h文件中定义了几种文件类型和相应的测试宏:
//已有的宏定义
#define S_IFMT 00170000 //文件类型(都是8进制表示)
#define S_IFREG 0100000	//普通文件
#define S_IFCHAR 0020000 //字符设备文件
#define S_ISREG(m)  (((m) & S_IFMT) == S_IFREG) //测试m是否是普通文件
#define S_ISCHAR(m) (((m) & S_IFMT) == S_IFCHAR) //测试m是否是字符设备文件
//proc文件的宏定义/宏函数
#define S_IFPROC 0030000
#define S_ISPROC(m) (((m) & S_IFMT) ==  S_IFPROC) //测试m是否是proc文件

哈工大操作系统实验---lab8:proc文件的实现

  • 第二步: 让mknod()支持新的文件类型,文件/proc/psinfo以及/proc/hdinfo索引节点需要通过mknod()系统调用建立,所以要让它支持新的文件类型,直接修改fs/namei.c文件中的sys_mknod()函数的代码,在其中增加关于proc文件系统的判断:
if(S_ISBLK(mode) || S_ISCHAR(mode) || S_ISPROC(mode))
	inode->izone[0] = dev;

哈工大操作系统实验---lab8:proc文件的实现

  • 第三步: 进程proc文件初始化,可以在系统启动时就建立那些proc文件系统(根目录/proc)下的proc文件(其下的子文件/proc/psinfo/proc/hdinfo),通常Linux操作系统也都是在系统启动以后就直接创建那些proc文件的,显然要修改系统启动时的调用的main(),因为是要创建文件,所以当然应该在文件系统已经挂载以后才可以操作,在main()的最后会从内核态切换到用户态,并调用init(),而init()要做的第一件事就是挂载根文件系统:
void init(void)
{
	setup((void *) &drive_info);
}

显然在执行setup((void *) &drive_info)的时候,也就是根文件系统挂载以后就可以创建proc文件了,首先建立/proc目录,然后再建立该目录下的各个proc文件节点,建立目录用mkdir(),建立文件用mknod()
现在可以调用mkdir()来创建proc目录,调用mknod()来创建proc目录下的各个proc文件节点了。
内核初始化的全部工作是在main()中完成,而/init/main()在最后从内核态切换到用户态,并调用init()init()做的第一件事情就是挂载根文件系统:setup((void *) &drive_info);

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

  • 建立/proc目录
  • 建立/proc目录下的各个结点

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

_syscall2(int,mkdir,const char*,name,mode_t,mode)
_syscall3(int,mknod,const char *,filename,mode_t,mode,dev_t,dev)

哈工大操作系统实验---lab8:proc文件的实现
哈工大操作系统实验---lab8:proc文件的实现
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对应psinfo1对应hdinfo,2对应inodeinfo
上述步骤完成以后,就可以使用make all编译内核,然后./run运行内核,使用ll /proc可以看到:

哈工大操作系统实验---lab8:proc文件的实现

inode->i_mode就是通过mknod()设置的mode。信息中的XXX和你设置的S_IFPROC有关。通过此值可以了解mknod()工作是否正常。这些信息说明内核在对psinfo进行读操作时不能正确处理,向cat返回了EINVAL错误。因为还没有实现处理函数,所以这是很正常的。

注意:博主在此没截使用cat命令的图片,但是就是这个么道理,嘻嘻。

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

  • 第四步: 让proc文件(psinfo、hdinfo、inodeinfo)可读
    open()没有问题,那么需要修改的就是sys_read()了
    首先在fs/read_write.c中添加extern,表示proc_read函数是从外部调用的。

在.c文件中要引入另一个文件,而且是另一个.c文件的全局变量或者函数,就需要用extern来说明一下。

哈工大操作系统实验---lab8:proc文件的实现
然后仿照其他if语句,添加proc文件的proc_read()调用。
哈工大操作系统实验---lab8:proc文件的实现

  • 第五步: 实现上述的proc_read()函数,用于读取proc文件内容,实现在fs/proc.c文件下。
    哈工大操作系统实验---lab8:proc文件的实现
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <stdarg.h>
#include <stddef.h>

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;
}

int proc_read(int dev, char * buf, int count, unsigned long * pos){
    struct task_struct ** p;
    int output_count=0;
    char * proc_buf=NULL;
    int file_size=0;
    int offset=*pos;

    struct super_block * sb; 
    struct buffer_head * bh;
    int total_blocks, total_inodes;
    int used_blocks=0, free_blocks=0;
    int i,j,k;
    char * db=NULL;

	//硬盘总共有多少块(空闲 + 非空闲),有多少inode索引节点等信息都放在super块中。
    sb=get_super(current->root->i_dev);
    total_blocks = sb->s_nzones;
    total_inodes=sb->s_ninodes;
	s_imap_blocks = sb->s_imap_blocks;
	s_zmap_blocks = sb->s_zmap_blocks;

	//psinfo: 对应的就是输出系统此时的全部进程的状态信息
    if(dev==0)
    { 
        proc_buf=(char *)malloc(sizeof(char *)*1024);
        file_size=sprintf(proc_buf,"pid\tstate\tfather\tcounter\tstart_time\n");

		//这里借鉴了,进程切换函数schedule()的代码,也就是遍历系统全部的进程。
        for(p = &LAST_TASK ; p >= &FIRST_TASK ; --p)
            if(*p)
                file_size+=sprintf(proc_buf+file_size,"%d\t%d\t%d\t%d\t%d\n",(*p)->pid,(*p)->state,(*p)->father,(*p)->counter,(*p)->start_time);

        *(proc_buf+file_size)='\0';
    }

	//hdinfo: 打印出硬盘的一些信息,
	//s_imap_blocks、ns_zmap_blocks、
	//total_blocks、free_blocks、used_blocks、total_inodes
    if(dev==1) 
    {
        for(i=0;i<sb->s_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++;
                        else
                            free_blocks++;
                }
            }
        }

        proc_buf=(char*)malloc(sizeof(char*)*512);
        file_size=sprintf(proc_buf,"s_imap_blocks:%d\ns_zmap_blocks:%d\n",s_imap_blocks,s_zmap_blocks);
        file_size+=sprintf(proc_buf+file_size,"total_blocks:%d\nfree_blcoks:%d\nused_blocks:%d\ntotal_indoes:%d\n",total_blocks,free_blocks,used_blocks,total_inodes);
    }

	//将proc_buf缓冲区的内容放入文件
    while(count>0)
        if(offset>file_size)
            break;
        put_fs_byte(*(proc_buf+offset),buf++);
        offset++;
        output_count++;
        count--;
    }

	//重置文件的pos位置,也就是指向文件末尾的指针
    (*pos)+=output_count; 

    free(proc_buf);
    return output_count;
}

由于添加了一个文件proc.c,所以需要改下fs/Makefile
哈工大操作系统实验---lab8:proc文件的实现
哈工大操作系统实验---lab8:proc文件的实现

上述的代码,是用的这位同学的,在此表示感谢!
(但是我搞不清为什么是proc_dev,添加的文件不是proc.c吗,如果有人可以解答,可以评论下。)

然后make all编译内核,./run运行内核,输出cat命令,即可查看psinfo(当前系统进程状态信息)和hdinfo(硬盘信息)的信息。
哈工大操作系统实验---lab8:proc文件的实现


实验问题

  • 如果要求你在psinfo之外再实现另一个结点,具体内容自选,那么你会实现一个给出什么信息的结点?为什么?

我会实现meminfo、cpuinfo这些节点,分别对应的信息是系统内存信息和cpu的信息,原因是我只知道这两个名词了

  • 一次read()未必能读出所有的数据,需要继续read(),直到把数据读空为止。而数次read()之间,进程的状态可能会发生变化。你认为后几次read()传给用户的数据,应该是变化后的,还是变化前的? 如果是变化后的,那么用户得到的数据衔接部分是否会有混乱?如何防止混乱? 如果是变化前的,那么该在什么样的情况下更新psinfo的内容?

我认为后几次read()传递给用户的数据应该是变化前的,因为读完一部分数据之后,之前读取的进程状态信息可能已经发生了改变了,那么读给proc_buf缓冲区的内容还是读之前的,所以可能会导致读到的数据出现混乱(数据不正确?),要使得数据读的正确、不混乱,就要在读的时候让想要变化的进程先等待,等读完了数据放到了文件中以后,再唤醒要更新的进程。

注:我的答案很可能不准确,请别被我误导了。


HIT-OS-LAB参考资料:
1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著
2.《Linux内核完全注释》
3.两个哈工大同学的实验源码
4.Linux-0.11源代码
(上述资料,如果有需要的话,请主动联系我))

该实验的参考资料
网课
官方文档
参考实验报告
返回顶部

上一篇:dumpe2fs命令详解


下一篇:CodeForces-1061B Views Matter