实验内容在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 文件。
实验过程//已有的宏定义
#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文件

if(S_ISBLK(mode) || S_ISCHAR(mode) || S_ISPROC(mode))
inode->izone[0] = dev;

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 的初始化工作应该在根文件系统挂载之后开始。它包括两个步骤:
建立目录和结点分别需要调用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)


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 编译内核,然后./run 运行内核,使用ll /proc 可以看到:

inode->i_mode 就是通过mknod() 设置的mode 。信息中的XXX 和你设置的S_IFPROC 有关。通过此值可以了解mknod() 工作是否正常。这些信息说明内核在对psinfo 进行读操作时不能正确处理,向cat 返回了EINVAL 错误。因为还没有实现处理函数,所以这是很正常的。
注意:博主在此没截使用cat命令的图片,但是就是这个么道理,嘻嘻。
这些信息至少说明,psinfo 被正确open() 了。所以我们不需要对sys_open() 动任何手脚,唯一要打补丁的,是sys_read() 。 在.c文件中要引入另一个文件,而且是另一个.c文件的全局变量或者函数,就需要用extern来说明一下。
 然后仿照其他if 语句,添加proc 文件的proc_read() 调用。

#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 ,

 上述的代码,是用的这位同学的,在此表示感谢! (但是我搞不清为什么是proc_dev,添加的文件不是proc.c吗,如果有人可以解答,可以评论下。)
然后make all 编译内核,./run 运行内核,输出cat 命令,即可查看psinfo (当前系统进程状态信息)和hdinfo (硬盘信息)的信息。

实验问题我会实现meminfo、cpuinfo这些节点,分别对应的信息是系统内存信息和cpu的信息,原因是我只知道这两个名词了
我认为后几次read()传递给用户的数据应该是变化前的,因为读完一部分数据之后,之前读取的进程状态信息可能已经发生了改变了,那么读给proc_buf缓冲区的内容还是读之前的,所以可能会导致读到的数据出现混乱(数据不正确?),要使得数据读的正确、不混乱,就要在读的时候让想要变化的进程先等待,等读完了数据放到了文件中以后,再唤醒要更新的进程。
注:我的答案很可能不准确,请别被我误导了。
HIT-OS-LAB参考资料: 1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著 2.《Linux内核完全注释》 3.两个哈工大同学的实验源码 4.Linux-0.11源代码 (上述资料,如果有需要的话,请主动联系我))
|