Implementation of Experiment 9 proc file system
Purpose
- Master the realization principle of virtual file system;
- Practice the concepts of files, directories, filesystems, etc.
Experimental content
Implementation of psinfo nodes within procfs (proc file system) on Linux 0.11. When reading the content of this node, you can get the status information of all current processes in the system. For example, using the cat command to /proc/psinfo
display the content, you can get:
$ 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
and its nodes are automatically created when the kernel starts.
Related functions are implemented in fs/proc.c
the file .
Introduction to procfs
The official Linux kernel is implemented procfs
. It is a virtual file system, which is usually mounted (mounted) to /proc
the directory , and provides opportunities to access system parameters through virtual files and virtual directories, so some people call it "understanding system information. a window".
These virtual files and directories do not actually exist on the disk, but a visual representation of various data in the kernel. Although virtual, they are all accessible through standard system calls ( open()
, , read()
etc.).
For example, /proc/meminfo
contains memory usage information, you can use the cat command to display its contents:
Linux is implemented through the file system interface procfs
and automatically mounts it to /proc
the directory .
All the contents in this directory are automatically created, deleted and updated along with the operation of the system, and they exist completely in the memory without occupying any external storage space.
Linux 0.11 does not yet implement virtual filesystems, that is, does not yet provide an interface for adding support for new filesystems. Therefore, this experiment can only simulate one by patching on the basis of the existing file system procfs
.
Linux 0.11 uses the Minix file system, which is a typical file system inode
based , which is described in detail in the book "Notes". Each of its files must correspond to at least one inode, and various attributes of the file are recorded in the inode, including the file type. File types include ordinary files, directories, character device files, and block device files. In the kernel, each type of file has a different processing function corresponding to it. We can add a new file type - proc file, and realize the functions to be realized by procfs in the corresponding processing function.
Add new file types
File location:include/sys/stat.h
#define S_IFPROC 0030000
#define S_ISPROC(m) (((m) & S_IFMT) == S_IFPROC)
Let mknod()
support new file types
The psinfo node is created through mknod()
system calls, so it needs to support new file types.
if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISPROC(mode))
inode->i_zone[0] = dev;
procfs
initialization work
All the work of kernel initialization is done main()
in , and main()
at the end switch from kernel mode to user mode and call init()
.
init()
The first thing to do is mount the root filesystem:
void init(void)
{
// ……
setup((void *) &drive_info);
// ……
}
procfs
The initialization work should start after the root file system is mounted. It consists of two steps:
-
(1) Create
/proc
a directory ; create/proc
each node under the directory. This experiment was built only/proc/psinfo
. -
(2) To create a directory and a node, you need to call
mkdir()
andmknod()
system call respectively. Because it is already in the user mode when it is initialized, it cannot besys_mkdir()
calledsys_mknod()
. The user mode interface of these two system calls must be implemented in the file where the initialization code is located, that is, the API: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);
File Directory:init/main.c
mkdir()
The value of the mode parameter can be "0755" (corresponding rwxr-xr-x
), which means that only the root user is allowed to rewrite this directory, and other people can only enter and read this directory.
procfs is a read-only file system, so when mknod()
creating a psinfo node with , it must be set as read-only through the mode parameter. It is recommended to use S_IFPROC|0444
as the mode value, indicating that this is a proc file with permission 0444 (r–r–r–), read-only for all users.
mknod()
The third parameter dev is used to describe the device number represented by the node. For procfs, this number is fully customizable. The processing function of the proc file will use this number to determine what information the corresponding file contains. For example, you can put 0 for psinfo, 1 for meminfo, and 2 for cpuinfo.
If the work is completed without any problem, after compiling and running the 0.11 kernel, you ll /proc
can see:
inode->i_mode
It is the mode mknod()
set . The XXX in the information S_IFPROC
is related to . Use this value to know whether mknod()
it is working properly. These messages indicate that the hdinfo
kernel failed to process correctly when reading from , and returned an EINVAL error to cat. This is normal since the handler function has not been implemented yet.
This information at least shows hdinfo
that it open()
is . So we don't need to sys_open()
touch anything, the only thing that needs to be patched is sys_read()
.
make proc
the file readable
File location:fs/read_write.c
Add extern, indicating that the proc_read function is called from the outside
/*新增proc_read函数外部调用*/
extern int proc_read(int dev,char* buf,int count,unsigned long *pos);
Then in the sys_read function, follow other if statements, add the branch of S_IFPROC(), and add the proc_read() call of the proc file:
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);
The parameters that need to be passed to the handler function include:
inode->i_zone[0]
, which is what was specifiedmknod()
whendev
- the device numberbuf
, pointing to user space, is the second parameterread()
of , used to receive datacount
, which is the third parameterread()
of , indicatingbuf
the size of the buffer pointed to by&file->f_pos
,f_pos
is the point of the "file position pointer" at the end of the last read file. A pointer must be passed here, because the processing function needs to modify the value of according to the amount of data passedbuf
tof_pos
.
implement proc_read
function
The function of the processing function of the proc file is to write different contents to buf in the user space according to the device number. The data to be written should start from the position f_pos
pointed to , write at most count bytes each time, and adjust f_pos
the value according to the actual number of bytes written, and finally return the actual number of bytes written. When the device number indicates that the content of psinfo is to be read, the data should be organized in the form of psinfo.
The following functions may be used to implement this function:
- malloc() function
- free() function
linux/kernel.h
Once the header file is included , malloc()
the and free()
functions are available. They can be called by core state code, and the only limitation is that the memory size requested at a time cannot exceed one page.
File location:fs/proc.c
The code comes from a big guy's blog --- reference code
#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;
}
Also modify fs/Makefile
the file:
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
Compile and run
Recompile the kernel again make all
, then run the kernel, and check the information of psinfo
(current system process status information) and hdinfo
(hard disk information):
God rewards work
bye