Liping Zou bio photo

Liping Zou

An Android Developer

Email Twitter Instagram Github

第七次实验的内容是在Linux 0.11上实现procfs(proc文件系统)内的psinfo结点。当读取此结点的内容时,可得到系统当前所有进程的状态信息。例如,用cat命令显示/proc/psinfo的内容,可得到:

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

procfs及其结点要在内核启动时自动创建。相关功能实现在fs/proc.c文件内。

这次的实验总体来说难度也不是那么大,要修改的文件在指导书中都详细说明了,要自己实现的只有proc.c。

首先在include/sys/stat.h文件中,添加如下几行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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_ISUID  0004000
#define S_ISGID  0002000
#define S_ISVTX  0001000
#define S_IFPROC  0070000   /**/

#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_ISPROC(m)     (((m) & S_IFMT) == S_IFPROC)      /**/
  1. 定义一个类型宏S_IFPROC,其值应在0010000到0100000之间,但后四位八进制数必须是0(这是S_IFMT的限制,分析测试宏可知原因),而且不能和已有的任意一个S_IFXXX相同;我使用的是0070000.

  2. 定义一个测试宏S_ISPROC(m),形式仿照其它的S_ISXXX(m)

psinfo结点要通过mknod()系统调用建立,所以要让它支持新的文件类型。直接修改fs/namei.c文件中的sys_mknod()函数中的一行代码,大约在namei.c中的sys_mknod()方法中的444行左右的位置上,如下:

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)))
		return -ENOENT;
	if (!namelen) {
		iput(dir);
		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);
	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;
}

在main.c中,需要调用mkdir()和mknod()系统调用,所以添加上如下的代码即可:

1
2
3
4
5
6
#define __LIBRARY__
#include <unistd.h>
#include <time.h>

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

然后在init()方法中调用mkdir()和mknod()即可:(至于参数的含义可以参考指导书的详细说明)

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
void init(void)
{
	int pid,i;

	setup((void *) &drive_info);
	mkdir("/proc",0755);
	mknod("/proc/psinfo",S_IFPROC|0444,0);
	(void) open("/dev/tty0",O_RDWR,0);
	(void) dup(0);
	(void) dup(0);
	printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
		NR_BUFFERS*BLOCK_SIZE);
	printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
	if (!(pid=fork())) {
		close(0);
		if (open("/etc/rc",O_RDONLY,0))
			_exit(1);
		execve("/bin/sh",argv_rc,envp_rc);
		_exit(2);
	}
	if (pid>0)
		while (pid != wait(&i))
			/* nothing */;
	while (1) {
		if ((pid=fork())<0) {
			printf("Fork failed in init\r\n");
			continue;
		}
		if (!pid) {
			close(0);close(1);close(2);
			setsid();
			(void) open("/dev/tty0",O_RDWR,0);
			(void) dup(0);
			(void) dup(0);
			_exit(execve("/bin/sh",argv,envp));
		}
		while (1)
			if (pid == wait(&i))
				break;
		printf("\n\rchild %d died with code %04x\n\r",pid,i);
		sync();
	}
	_exit(0);	/* NOTE! _exit, not exit() */
}

下一步,让proc变得可读,要做的是在fs/read_write.c中为我们的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
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_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);
	}
	if(S_ISPROC(inode->i_mode)) {
   
	return ProcDeal(inode->i_zone[0],buf,count,&file->f_pos);
	}

	printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);
	return -EINVAL;
}

最后就是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
#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <stdarg.h>


char k_buffer[2048];
int flag = 0;  /*判断表头信息是否被打印*/

int ProcDeal(int devNum,char *buffer,int count,off_t *pos)
{
	//printk ("count----------->%d\n",count);
      
	char *k_buffer_pointer = &k_buffer[0];
	struct task_struct *p;
	int j,i;
	
        if(flag == 0)
 	{
		int offset = sprintf(k_buffer_pointer,"%s\t%s\t%s\t%s\t%s\n","pid","state","father","counter","start_time");
		k_buffer_pointer += offset;
		flag = 1;
	}
        for (i = 0; i<=NR_TASKS ; i++)
        {
		p = task[i];
                if(p==NULL) break;
		if(p!=NULL)
		{
			int offset = sprintf(k_buffer_pointer,"%d\t%d\t%d\t%d\t%ld\n",p->pid,p->state,p->father,p->counter,p->start_time);
			k_buffer_pointer += offset;
			//iCount += offset;
		}
        }
	//printk("iCount--------->%d",iCount);
	for(j=0 ; j<count ; j++)
	{
		//if (j==iCount) {printk("j-------->%d\n",j); break;}
		if(k_buffer[j+(*pos)]=='\0')
			break;
		put_fs_byte(k_buffer[j+(*pos)],&buffer[j+(*pos)]);
	}
        *pos = (*pos)+j;
	if(j==0)
		flag = 0;
       return j;
}

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

最后的最后,上个报告~

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

答:我会给出CPU的当前信息。因为CPU的信息也是一个重要的计算机参数,用户得到这个参数,就可以知道比如当前进程总数、CPU寄存器的状态等等信息,方便用户了解系统的信息。而linux0.11中没有方便查看这个参数的命令,所以加上这个参数会比较有价值。

  1. 一次read()未必能读出所有的数据,需要继续read(),直到把数据读空为止。而数次read()之间,进程的状态可能会发生变化。你认为后几次read()传给用户的数据,应该是变化后的,还是变化前的?

    1) 如果是变化后的,那么用户得到的数据衔接部分是否会有混乱?如何防止混乱?

    2) 如果是变化前的,那么该在什么样的情况下更新psinfo的内容?

答:我的做法几次传给用户的数据是变化后的,因为如果每次读不完,那么下次会重新调用文件处理函数,向内核态的缓冲区重新写一遍进程的当前信息,靠文件指针约束某一次从那里开始给用户put,这样的话当进程信息有变化时,是把新的进程信息继续put给用户的。

可能会有混乱。因为进程信息的许多数据类型都不是一个字节(比如int一般是4个字节,long更多),而我是一个字节一个字节的向用户态put,那么当系统的进程数比较多的时候,可能会遇到恰好在某个多字节数据的中间终止本次put操作,下次接着从这里继续put,这样的话就会导致用户态中的文件内容的某个数据的前面的某些字节是上次读出来的,而后面的字节是新读出来的,这样就会就会有混乱了。

解决的办法是:设置一个数组保存循环时每次的向内核缓冲区里面写进去的字节数(我程序里面的offset,其实是一个进程的完整信息,也即task[]数组中的一项的信息),这样put给用户时,每次按着对应的字节数put出去,也即每次都是put给用户完整的一个进程的信息(但要加一个判断,判断每次剩余的空间是不是足够装下完整的一个进程的信息),再相应的移动文件指针即可避免混乱。