实验环境:实验楼-proc文件系统的实现
实验理论:Linux-0.11操作系统实验8理论-proc文件系统的实现
实验任务:在 Linux 0.11
上实现 procfs
(proc 文件系统)内的psinfo
结点。当读取此结点的内容时,可得到系统当前所有进程的状态信息。例如,用 cat
命令显示 /proc/psinfo
的内容,可得到:
# 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
文件新增proc
文件的宏定义以及测试宏://已有的宏定义 #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文件
-
让
mknod()
支持新的文件类型文件
/proc/psinfo
以及/proc/hdinfo
索引节点需要通过mknod()
系统调用建立,所以要让它支持新的文件类型。直接修改
fs/namei.c
文件中的sys_mknod()
函数的一行代码,在其中增加关于proc
文件系统的判断: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);
-
进程
proc
文件初始化内核初始化的全部工作是在
main()
中完成,而/init/main()
在最后从内核态切换到用户态,并调用init()
。init()
做的第一件事情就是挂载根文件系统:setup((void *) &drive_info);
根文件系统挂载以后就可以创建
proc
文件了,首先建立/proc
目录,然后再建立该目录下的各个proc
文件节点。建立目录和文件结点分别需要调用
mkdir()
和mknod()
系统调用。因为初始化时已经在用户态,所以不能直接调用sys_mkdir()
和sys_mknod()
。必须在初始化代码所在文件中实现这两个系统调用的用户态接口,即API。修改
init/main.c
,新增两个系统调用用户接口并接着修改init()
函数实现对其的调用:static inline _syscall0(int,fork) static inline _syscall0(int,pause) static inline _syscall1(int,setup,void *,BIOS) static inline _syscall0(int,sync) /*新增mkdir和mknode系统调用*/ _syscall2(int,mkdir,const char*,name,mode_t,mode) _syscall3(int,mknod,const char *,filename,mode_t,mode,dev_t,dev) //....... setup((void *) &drive_info); (void) open("/dev/tty0",O_RDWR,0); (void) dup(0); (void) dup(0); mkdir("/proc",0755); mknod("/proc/psinfo",S_IFPROC|0444,0); mknod("/proc/hdinfo",S_IFPROC|0444,1); mknod("/proc/inodeinfo",S_IFPROC|0444,2);
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对应hdinfo,2对应inodeinfo。 -
至此,可以重新编译内核
make all
,然后运行内核,并使用ll /proc
可以看到:
这些信息说明内核在对psinfo
进行读操作时不能正确处理,向 cat 返回了 EINVAL 错误。因为还没有实现处理函数,所以这是很正常的。这些信息至少说明,psinfo
被正确open()
了。所以我们不需要对sys_open()
动任何手脚,唯一要打补丁的,是sys_read()
。 -
让
proc
文件可读修改
fs/read_write.c
添加extern
,表示proc_read
函数是从外部调用的。/*新增proc_read函数外部调用*/ extern int proc_read(int dev,char* buf,int count,unsigned long *pos);
然后在
sys_read
函数中仿照其他if
语句,加上S_IFPROC()
的分支,添加proc
文件的proc_read()
调用。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); if (S_ISCHR(inode->i_mode)) return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
需要传给处理函数的参数包括:
inode->i_zone[0]
,这就是mknod()
时指定的dev
——设备编号buf
,指向用户空间,就是read()
的第二个参数,用来接收数据count
,就是read()
的第三个参数,说明buf
指向的缓冲区大小&file->f_pos
,f_pos
是上一次读文件结束时“文件位置指针”的指向。这里必须传指针,因为处理函数需要根据传给buf
的数据量修改f_pos
的值。
-
实现
proc_read
函数proc
文件的处理函数的功能是根据设备编号,把不同的内容写入到用户空间的buf
。写入的数据要从f_pos
指向的位置开始,每次最多写count
个字节,并根据实际写入的字节数调整f_pos
的值,最后返回实际写入的字节数。当设备编号表明要读的是psinfo
的内容时,就要按照psinfo
的形式组织数据。在
/fs/
目录下实现proc.c
文件:#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); //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 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) { 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 信息*/ 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 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; } 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; }
同时修改
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
-
至此,所有的要求全部完成,再次重新编译内核
make all
,然后运行内核,查看psinfo
(当前系统进程状态信息)和hdinfo
(硬盘信息)的信息:
实验问题:
-
如果要求你在
psinfo
之外再实现另一个结点,具体内容自选,那么你会实现一个给出什么信息的结点?为什么?会实现meminfo、cpuinfo这些节点,分别对应的信息是系统内存信息和cpu的信息
-
一次read()未必能读出所有的数据,需要继续read(),直到把数据读空为止。而数次read()之间,进程的状态可能会发生变化。你认为后几次read()传给用户的数据,应该是变化后的,还是变化前的? 如果是变化后的,那么用户得到的数据衔接部分是否会有混乱?如何防止混乱? 如果是变化前的,那么该在什么样的情况下更新psinfo的内容?
是变化前的,在读取位置f_pos为0时才更新psinfo内容。
该inode对应的i_zone[0]依然存在。也就是说,只是从inode映射中取消映射该inode,但是实际上硬盘上的数据还在。
参考链接:
https://blog.csdn.net/wangyi_lin/article/details/7046463
https://blog.csdn.net/qq_41708792/article/details/93719759
https://blog.csdn.net/m0_38099380/article/details/89368549