HIT oslab之实验9 proc文件系统的实现

一、实验内容

在Linux-0.11上实现procfs(proc文件系统):/proc,该目录下有2个文件:
(1)psinfo(进程信息)
当读取此文件的内容时,可得到系统当前所有进程的状态信息。
在这里插入图片描述
(2)hdinfo(硬盘信息)
当读取此文件的内容时,可得到系统硬盘的使用情况。
在这里插入图片描述

说明:
①procfs要在内核启动时自动创建。
②相关功能实现在 fs/proc.c 文件内。

二、实现procfs

1.增加新文件类型(在include/sys/stat.h文件中)

(0)原因
HIT oslab之设备管理 (显示器 + 键盘)讲过,unix/linux哲学认为“一切皆文件”。每个文件都要对应至少一个 inode,而 inode 中记录着文件的各种属性,包括文件类型。文件类型有普通文件、目录、字符设备文件和块设备文件等。在内核中,每种类型的文件都有不同的处理函数与之对应。

inode:index node 索引节点。

(1)方法
①定义一个类型宏 S_IFPROC,其值应在 0010000 到 0100000 之间,但后四位八进制数必须是 0(这是 S_IFMT 的限制,分析测试宏可知原因),而且不能和已有的任意一个 S_IFXXX 相同;

注意:C 语言中以 “0” 直接接数字的常数是八进制数。

②定义一个测试宏 S_ISPROC(m),形式仿照其它的 S_ISXXX(m)
在这里插入图片描述

2.把proc文件当作设备文件就可以根据设备号来处理不同的proc文件

(1)逻辑
如果把HIT oslab之设备管理 (显示器 + 键盘)搞明白了,先根据文件类型做一次分支,如果是设备文件接下来就是根据设备号来做一次分支了。

(2)在fs/namei.c中对sys_mknod()按如下进行修改:
在这里插入图片描述

sys_mknod函数的核心功能:创建inode。
这样就能根据路径/proc/psinfo找到psinfo的inode了。

3.procfs的初始化

(0)前言
上文要实现根据路径/proc/psinfo找到psinfo的inode了。首先得有proc目录啊!

而要有proc目录,首先得有根目录啊!而挂载根目录是1号进程在执行init函数时实现的。(注意:此时已经是用户态了!!!)
所以,procfs 的初始化工作应该在根文件系统挂载之后开始。

(1)建立 /proc 目录;建立 /proc 目录下的各个文件(以psinfo为例子)。

由于此时是用户态,不能直接调用sys_mkdir()建立目录和sys_mknod()建立inode。
所以,又是通过系统调用的方式来实现!

1)在init/main.c中做如下修改
在这里插入图片描述

说明:
①保证下图#define存在,若不存在则自行添加
在这里插入图片描述
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。

mkdir时会创建proc目录的inode
S_IFPROC | 0444真妙啊! 怪不得S_IFPROC的最后4个八进制数是0!这样做一个mode既告知了文件属性,又告知了文件权限。妙不可言啊!

(2)验证
在这里插入图片描述
在这里插入图片描述

说明:
①03和设置的S_IFPROC(0030000)吻合,0444也和设置的文件权限吻合。
②之所以是EINVAL,是因为还没有实现针对psinfo的处理函数。但验证了psinfo可以被正确open()。

4.让proc文件可读

(0)逻辑
cat /proc/psinfo的流程如下:
fd = open("proc/psinfo");
②read(fd),并把psinfo文件的内容打印到屏幕上。

read函数请求系统调用,让sys_read系统调用函数完成读文件的操作。
在sys_read函数中,就有如下内容:
在这里插入图片描述
和上文验证结果吻合。

(1)增加对proc文件的处理函数proc_read(在fs/read_write.csys_read函数中)
在这里插入图片描述

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

三、实现proc 文件的处理函数proc_read

1.在linux-0.11/fs目录下新建proc_dev.c

2.proc_dev.c的源码及注释

#include <linux/kernel.h> //用到了malloc(), free()
#include <stdarg.h> //用到了va_start()等和vsprintf()
#include <linux/sched.h> //遍历进程是仿照schedule函数,其在sched.c中
#include <asm/segment.h> //使用了put_fs_byte

#define MAX_NUM 1024 //malloc申请的内存空间不超过1页;

/* 仿照printf函数实现sprintf:将格式化数据写入缓冲区 
 *成功返回写入buf的字符个数,失败返回-1
 * */
//extern int vsprintf(char * buf, const char * fmt, va_list args);
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) {
	/*由于buf是用户段的地址空间,所以内核段还得申请一段地址空间
	 *之后再把内核段数据传输到用户段的地址空间
	 * */
	char *proc_buf = NULL;
	int printf_num = 0; //接收sprintf的返回值,表征输出的字符个数;
	int tmp_pn; //可能sprintf会返回-1 
	unsigned long offset = *pos; //博客中解释
	int output_num = 0; //根据output_num来更新pos;
	struct task_struct **p; //为了遍历系统所有进程

	switch (dev) {
		case 0:
			/** 处理psinfo文件 **/
			proc_buf = (char *)malloc(sizeof(char) * MAX_NUM);
			printf_num = sprintf(proc_buf, "pid\tstate\tfather\tcounter\tstart_time\n");
			/* 遍历系统的所有进程(参照schedule函数的处理)*/
			for(p = &FIRST_TASK; p <= &LAST_TASK; p++) {
				if(*p) {
					tmp_pn = sprintf(proc_buf + printf_num,  					"%3d\t%5d\t%6d\t%7d\t%10d\n", 
					(*p)->pid, (*p)->state, (*p)->father, (*p)->counter, (*p)->start_time);
					if(tmp_pn < 0) {
						//proc_buf空间不够,需要增大MAX_NUM
						return -1;
					}
					printf_num += tmp_pn;
				}	
			}
			*(proc_buf + printf_num) = '\0';
			break;
		default:
			break;
	}
	/* 将内核段数据proc_buf传输到用户段的地址空间buf*/
	while(count > 0) {
		if(offset > printf_num)
			break;
		put_fs_byte(*(proc_buf + offset), buf++);
		count--;
		if(*(proc_buf + offset) == '\0')
			break;
		offset++;
		output_num++;
	}

	(*pos) += output_num;

	free(proc_buf);
	return output_num;
}

解释:
①pos的定义在linux/fs.h中,如下所示:
在这里插入图片描述
off_t类型用于指示文件的偏移量, 实际是unsigned long。

疑惑:
感觉pos在proc_read函数中没发挥作用啊。
每次执行cat /proc/psinifo,都会重新向proc_buf处写内容,然后把内容传输到buf中。但是,从运行结果来看,每次都是从偏移0个字符开始传输(pos = 0,这可能是一种default)。而且确实也应该从proc_buf的0处开始传输。

3.修改fs/Makefile

在这里插入图片描述

四、验证

1.cat /proc/psinfo的结果
在这里插入图片描述

五、补充

1.创建/proc/hdinfo/proc/inodeinfo(在init/main.c的init函数中)

在这里插入图片描述
在这里插入图片描述

2.在上述proc_read函数中,增加处理hdinfo(即dev = 1)的内容

(1)选取如下内容作为要打印的hard disk information
①i节点位图所占块数:s_imap_blocks
②逻辑块位图所占块数:s_zmap_blocks
③i节点的总数:s_ninodes;
④逻辑块的总数:s_nzones;
⑤已用的逻辑块数:used_blocks
⑥空闲的逻辑块数:free_blocks

通过②计算出⑤~⑥;

(2)①~④在超级块(super_block)中定义
struct super_block * sb = get_super(current->root->i_dev);

解释:

  • 1个文件系统对应1个超级块。
  • 当前进程(其结构体,如下所示)的root字段指根目录i节点结构。
    在这里插入图片描述
  • 根目录i节点(其结构体,如下所示)的i_dev字段指明了根目录所在设备的编号。
    在这里插入图片描述
  • 综上所述:get_super(current->root->i_dev)找到了文件系统对应的超级块。而文件系统又是对磁盘(准确说是外部存储设备)的抽象,从而可以打印出hard disk information。

(3)proc_dev.c的源码及注释

#include <linux/kernel.h> //用到了malloc(), free()
#include <stdarg.h> //用到了va_start()等和vsprintf()
#include <linux/sched.h> //遍历进程是仿照schedule函数,其在sched.c中
#include <asm/segment.h> //使用了put_fs_byte

#define MAX_NUM 1024 //malloc申请的内存空间不超过1页;
#define MAX_B_DATA 1024 //一个位图占1024B

/* 仿照printf函数实现sprintf:将格式化数据写入缓冲区 
 *成功返回写入buf的字符个数,失败返回-1
 * */
//extern int vsprintf(char * buf, const char * fmt, va_list args);
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) {
	/*由于buf是用户段的地址空间,所以内核段还得申请一段地址空间
	 *之后再把内核段数据传输到用户段的地址空间
	 * */
	char *proc_buf = NULL;
	int printf_num = 0; //接收sprintf的返回值,表征输出的字符个数;
	int tmp_pn; //可能sprintf会返回-1 
	unsigned long offset = *pos; //博客中解释
	int output_num = 0; //根据output_num来更新pos;
	struct task_struct **p; //为了遍历系统所有进程
	
	/* 与硬盘信息相关的变量*/
	struct super_block *sb;
	unsigned short sib;        // sib指s_imap_blocks
	unsigned short szb;        // szb指s_zmap_blocks
	unsigned short ninodes;   // 指s_ninodes;
	unsigned short nzones;    // 指s_nzones;
	int used_blocks = 0;
	int free_blocks = 0;
	struct buffer_head *bh;   // 逻辑块位图缓冲块指针;
	int i;  // i号逻辑块位图缓冲块
	char bd;   // 逻辑块位图, 只用1位来指示盘块的使用情况,而char是8bit;
	int j; // 1024个bd;
	int k; // bd的每1bit;
	int flag = 0;  // 当used_blocks + free_blocks == nzones,就不应该再统计了。

	switch (dev) {
		case 0:
			/** 处理psinfo文件 **/
			proc_buf = (char *)malloc(sizeof(char) * MAX_NUM);
			printf_num = sprintf(proc_buf, "pid\tstate\tfather\tcounter\tstart_time\n");
			/* 遍历系统的所有进程(参照schedule函数的处理)*/
			for(p = &FIRST_TASK; p <= &LAST_TASK; p++) {
				if(*p) {
					tmp_pn = sprintf(proc_buf + printf_num,  					"%3d\t%5d\t%6d\t%7d\t%10d\n", 
					(*p)->pid, (*p)->state, (*p)->father, (*p)->counter, (*p)->start_time);
					if(tmp_pn < 0) {
						// proc_buf空间不够,需要增大MAX_NUM
						return -1;
					}
					printf_num += tmp_pn;
				}
					
			}
			*(proc_buf + printf_num) = '\0';
			break;
		case 1:
			/** 处理hdinfo文件 */	
				
			// 获得超级块
			sb = get_super(current->root->i_dev);
			/* 获取1~4信息 */
			sib = sb->s_imap_blocks;
			szb = sb->s_zmap_blocks;
			ninodes = sb->s_ninodes;
			nzones = sb->s_nzones;

			/* 计算5~6 */
			for(i = 0; i < szb; i++) {
				if(flag)
					break; 
				bh = sb->s_zmap[i];  // s_zmap指逻辑块位图缓冲块指针数组
				for(j = 0; j < MAX_B_DATA; j++) {
					if(flag)
						break;
					bd = *(bh->b_data + j);
					for(k = 0; k < 8; k++) {
							if(used_blocks + free_blocks == nzones) {
								flag = 1;
								break;
							}
							if(bd & (1 << k))
								used_blocks++;
							else
								free_blocks++;
					}
				}
			}

			/* 写入proc_buf */
			proc_buf = (char *)malloc(sizeof(char) * MAX_NUM);
			printf_num = sprintf(proc_buf, 
			"s_imap_blocks: %d\ns_zmap_blocks: %d\ns_ninodes: %d\ns_nzones: %d\nused_blocks: %d\nfree_blocks: %d\n", 
			sib, szb, ninodes, nzones, used_blocks, free_blocks);
			*(proc_buf + printf_num) = '\0';
			break;
		default:
			break;
	}
	
	/* 将内核段数据proc_buf传输到用户段的地址空间buf*/
	while(count > 0) {
		if(offset > printf_num)
			break;
		put_fs_byte(*(proc_buf + offset), buf++);
		count--;
		if(*(proc_buf + offset) == '\0')
			break;
		offset++;
		output_num++;
	}
	(*pos) += output_num;

	free(proc_buf);
	return output_num;
}

打印出hdinfo内容的核心部分:
在这里插入图片描述

buffer_head数据结构:
在这里插入图片描述

(4)结果
①used_blocks + free_blocks = 65536 != 62000
在这里插入图片描述

解释:

  • 65536是理论值
    在这里插入图片描述
  • 62000是实际值,理由如下:
    虽然逻辑块位图有8个缓冲块,但是实际硬盘可能小于64MB,猜测多统计了些free_blocks

②used_blocks + free_blocks = 62000

if(used_blocks + free_blocks == nzones) {
	flag = 1;
	break;
}

在这里插入图片描述

为啥多统计了些used_blocks?
在这里插入图片描述
原来超出实际值后,都是用1表示的。所以算成了used_blocks。

3.参考资料

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值