0%

HIT-OSLab8

HIT-OSLab8

实验目的:

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

实验内容:

  • 在 Linux-0.11 上实现 procfs(proc文件系统) 内的 psinfo 节点,当读取此节点的内容的时候,可得到系统当前所有进程的状态信息
  • 在 Linux-0.11 上实现 procfs(proc文件系统) 内的 hdinfo 节点,当读取此节点的内容的时候,可以打印出硬盘的一些信息
  • 参考格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
# 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 是一个 Linux 内核模块,它提供了一个用于访问进程信息的文件系统

  • 通过 procfs,你可以查看与进程有关的信息,如进程 ID、进程名、进程的命令行参数等
  • 它使得用户可以方便地对进程进行管理和监控

procfs 的实现依赖于内核的 proc 文件系统,它将进程信息存储在 /proc 目录下

  • 这个目录提供了一组用于访问进程信息的文件和目录
  • 通过这些文件,你可以查看进程的详细信息,如 /proc/[pid]/cmdline 文件可以查看进程的命令行参数

在开始实验前,我们需要先了解 linux 中的两个关键结构 file 和 inode

在 Linux 中,结构体 file 用于描述一个内存中的文件(打开的文件)

1
2
3
4
5
6
7
struct file {
unsigned short f_mode; /* 文件权限 */
unsigned short f_flags; /* 文件标志 */
unsigned short f_count; /* 文件计数器 */
struct m_inode * f_inode; /* 对应inode结构体 */
off_t f_pos; /* 表示文件当前位置 */
};

每种类型的文件都有一个唯一的标识符,即索引节点 inode,结构体 inode 用于描述一个在磁盘中的文件或者一个有特定功能的虚拟文件

索引节点有两种类型:

  • metadata inode 是用于存储文件元数据的 inode
    • 元数据(metadata):用来描述一个文件的特征,包含了该文件或目录的元数据,如权限、所有者、组、大小、创建时间等
  • data inode 是用于存储文件数据的 inode
    • 数据(data):泛指普通文件中的实际数据

在 linux-0.11 中似乎没有刻意去区别这两种类型的 inode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct d_inode {
unsigned short i_mode;
unsigned short i_uid;
unsigned long i_size;
unsigned long i_time;
unsigned char i_gid;
unsigned char i_nlinks;
unsigned short i_zone[9];
};

struct m_inode {
unsigned short i_mode; /* 文件权限 */
unsigned short i_uid; /* 文件所有者 */
unsigned long i_size; /* 文件大小 */
unsigned long i_mtime; /* 最后修改的时间 */
unsigned char i_gid; /* 文件组 */
unsigned char i_nlinks; /* 文件链接数 */
unsigned short i_zone[9]; /* 文件磁盘区域 */
/* these are in memory also */
struct task_struct * i_wait; /* 对应任务 */
unsigned long i_atime; /* 访问时间 */
unsigned long i_ctime; /* 修改时间 */
unsigned short i_dev; /* 设备号 */
unsigned short i_num; /* 文件编号 */
unsigned short i_count; /* 计数器 */
unsigned char i_lock; /* 锁标记 */
unsigned char i_dirt; /* 脏标记 */
unsigned char i_pipe; /* 管道标记 */
unsigned char i_mount; /* 挂载标记 */
unsigned char i_seek; /* 在文件读取时使用的缓冲区大小 */
unsigned char i_update; /* 在更新文件时使用的缓冲区大小 */
};

首先 proc 文件系统中包含一种特殊类型的文件,需要在 include/sys/stat.h 中进行定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define S_IFMT  00170000
#define S_IFREG 0100000 /* 普通文件 */
#define S_IFBLK 0060000 /* 块设备 */
#define S_IFDIR 0040000 /* 目录 */
#define S_IFCHR 0020000 /* 字符设备 */
#define S_IFIFO 0010000 /* 管道 */

#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) /* 是否为普通文件 */
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) /* 是否为目录 */
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* 是否为字符设备 */
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* 是否为块设备 */
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* 是否为管道 */

#define S_IFPROC 0030000 /* proc文件 */
#define S_ISPROC(m) (((m) & S_IFMT) == S_IFPROC) /* 是否为proc文件 */

接下来我们需要在 sys_mknod sys_read 中添加有关 proc 文件的判断,使其可以支持 proc 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int sys_mknod(const char * filename, int mode, int dev)
{
const char * basename;
int namelen;
struct m_inode * dir, * inode;
struct buffer_head * bh; /* 用于描述一块磁盘块缓冲区 */
struct dir_entry * de; /* 用于描述一个目录项 */

if (!suser())
return -EPERM;
if (!(dir = dir_namei(filename,&namelen,&basename))) /* 获取上层目录的inode */
return -ENOENT;
if (!namelen) {
iput(dir); /* 将一个inode从当前目录中移除 */
return -ENOENT;
}
if (!permission(dir,MAY_WRITE)) { /* 检查目录的权限 */
iput(dir);
return -EPERM;
}
bh = find_entry(&dir,basename,namelen,&de); /* 在目录中查找一个文件 */
if (bh) { /* 文件名重复 */
brelse(bh);
iput(dir);
return -EEXIST;
}
inode = new_inode(dir->i_dev); /* 分配一个新的index结构体 */
if (!inode) {
iput(dir);
return -ENOSPC;
}
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;
inode->i_dirt = 1;
bh = add_entry(dir,basename,namelen,&de); /* 添加一个新目录项 */
if (!bh) {
iput(dir);
inode->i_nlinks=0;
iput(inode);
return -ENOSPC;
}
de->inode = inode->i_num;
bh->b_dirt = 1;
iput(dir);
iput(inode);
brelse(bh);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int sys_read(unsigned int fd,char * buf,int count)
{
struct file * file;
struct m_inode * inode;

if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
return -EINVAL;
if (!count)
return 0;
verify_area(buf,count);
inode = file->f_inode;
if (inode->i_pipe) /* 处理管道 */
return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;
if (S_ISPROC(inode->i_mode)) /* 处理proc文件 */
return proc_read(inode->i_zone[0],&file->f_pos,buf,count);
if (S_ISCHR(inode->i_mode)) /* 处理字符设备 */
return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
if (S_ISBLK(inode->i_mode)) /* 处理块设备 */
return block_read(inode->i_zone[0],&file->f_pos,buf,count);
if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) { /* 处理目录和普通文件 */
if (count+file->f_pos > inode->i_size)
count = inode->i_size - file->f_pos;
if (count<=0)
return 0;
return file_read(inode,file,buf,count);
}
printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);
return -EINVAL;
}

然后我们需要在根文件系统加载后创建 /proc 目录以及其中的 psinfo hdinfo 文件:

1
2
3
4
5
6
7
8
9
setup((void *) &drive_info); /* 挂载根文件系统 */
(void) open("/dev/tty0",O_RDWR,0); /* 创建stdin */
(void) dup(0); /* 创建stdout */
(void) dup(0); /* 创建stderr */
mkdir("/proc",0755);
mknod("/proc/psinfo",S_IFPROC|0444,0);
mknod("/proc/hdinfo",S_IFPROC|0444,1);
printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
NR_BUFFERS*BLOCK_SIZE);

最后的实验操作就是实现 proc_read 函数(新建 fs/proc.c 文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#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; })

char proc_buf[4096] = {'\0'};

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 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) /* 遍历task[NR_TASKS] */
if (*p != NULL){
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;
}

int get_hdinfo() {
int read = 0;
int i, used;
struct super_block *sb;
sb = get_super(0x301); /* 磁盘设备号:3*256+1 */
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);
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;
}

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

最后修改 fs/makefile

1
2
3
4
5
6
7
8
9
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

......

proc.o: proc.c ../include/string.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/linux/kernel.h

效果如下: