「オペレーティング システム」 by Li Zhijun | 実験 9 - proc ファイル システムの実現

目次

1. 実験の目的

2. 実験内容

3. 実験の準備

1.procfs の概要

2. 基本的な考え方

4. 実験プロセス

1. 新しいファイルタイプを追加する

2. mknod() に新しいファイルタイプをサポートさせます

(1) mknodシステムコールを修正する

(2) procfsの初期化

3. proc ファイルを読み取り可能にする

(1) proc_read関数の外部呼び出しを追加

(2) procファイルのproc_read()呼び出しを追加

4. procファイルの処理機能を実現する

(1) proc.cを書く

(2) メイクファイルを修正する

5. コンパイルして実行する 


1. 実験の目的

1. 仮想ファイルシステムの実現原理をマスターします。

2. ファイル、ディレクトリ、およびファイル システムの概念を練習します。

2. 実験内容

procfs(procファイルシステム) 内のpsinfo ジャンクションは        Linux 0.11 に実装されています(実装することhdinfo もできますinodeinfo)。このノードの内容を読み取ると、システム内のすべての現在のプロセスのステータス情報を取得できます。

たとえば、  cat コマンドを使用して/proc/psinfo と の内容を表示するには/proc/hdinfo 、次のように取得できます。

$ 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 関連する関数はファイルに実装されます 。

3. 実験の準備

1.procfs の概要

       公式の Linux カーネルが実装されています procfs。これは仮想ファイル システム (プロセス ファイル システム) であり、通常はディレクトリにマウント (マウント) されます /proc 。仮想ファイルと仮想ディレクトリを通じてシステム パラメータにアクセスする機会を提供するため、「」と呼ぶ人もいます。システム情報への窓」。

       これらの仮想ファイルとディレクトリは実際にはディスク上に存在するのではなく、カーネル内のさまざまなデータを視覚的に表現したものです。仮想ではありますが、これらはすべて標準のシステム コール (  open()read() ... ) を通じてアクセスできます。

たとえば、/proc/meminfo メモリ使用量情報が含まれている場合、cat コマンドを使用してその内容を表示できます。

       実際、Linux のシステムコマンドの多くは /proc 読み込みによって実装されています。たとえば uname -a 、情報の一部は からのものであり /proc/version、 uptime 情報の一部は と からの /proc/uptime もの です/proc/loadavg 。

procfsProcfs の詳細については、http: //en.wikipedia.org/wiki/Procfsご覧ください。

2. 基本的な考え方

       Linux はファイル システム インターフェイスを通じて実装され procfs、 /proc 起動時に自動的にディレクトリにマウントされます。

       このディレクトリ内のすべてのコンテンツは、システムの動作に応じて自動的に作成、削除、更新され、外部ストレージ領域を占有することなく完全にメモリ内に存在します。

       Linux 0.11 はまだ仮想ファイルシステムを実装していません。つまり、新しいファイルシステムのサポートを追加するためのインターフェイスがまだ提供されていません。したがって、この実験では既存のファイル システムをベースにパッチを当ててシミュレーションすることしかできません procfs 。

       Linux 0.11 は、典型的な Linux ベースの inode ファイル システムである Minix ファイル システムを使用します。これについては、「Notes」ブックで詳しく説明されています。各ファイルは少なくとも 1 つの i ノードに対応する必要があり、ファイル タイプを含むファイルのさまざまな属性が i ノードに記録されます。ファイル タイプには、通常のファイル、ディレクトリ、キャラクタ デバイス ファイル、およびブロック デバイス ファイルが含まれます。カーネルでは、ファイルの種類ごとに、それに対応した異なる処理機能を持っています。新しいファイルタイプ「 proc ファイル」を追加し、対応する処理関数で実現したい機能を実現しますprocfs。 

4. 実験プロセス

1. 新しいファイルタイプを追加する

【ヒント】

include/sys/stat.h いくつかのファイル タイプと対応するテスト マクロがファイル内で定義されています。 

#define S_IFMT  00170000

// 普通文件
#define S_IFREG  0100000

// 块设备
#define S_IFBLK  0060000

// 目录
#define S_IFDIR  0040000

// 字符设备
#define S_IFCHR  0020000
#define S_IFIFO  0010000
//……

// 测试 m 是否是普通文件
#define S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)

// 测试 m 是否是目录
#define S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)

// 测试 m 是否是字符设备
#define S_ISCHR(m)      (((m) & S_IFMT) == S_IFCHR)

// 测试 m 是否是块设备
#define S_ISBLK(m)      (((m) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(m)     (((m) & S_IFMT) == S_IFIFO)

新しいファイル タイプを追加するには、次の 2 つの手順があります。

  • 値が 0010000 ~ 0100000 である必要がある型マクロを定義します  が、8 進数の最後の 4 桁は 0 でなければなりません (これが制限です。理由はテスト  マクロを分析することでわかります)。また、それと同じにすることはできません 。既存のもののいずれか。S_IFPROCS_IFMTS_IFXXX 
  •  別の形式で テスト マクロを定義します S_ISPROC(m)S_ISXXX(m) 

なお、C言語では、数値に「0」が直結した定数は8進数となります。

追加を開始します:

#define S_IFPROC 0030000
#define S_ISPROC(m)     (((m) & S_IFMT) ==  S_IFPROC)

2.  mknod() に新しいファイルタイプをサポートさせます

(1) mknodシステム

psinfo また、hdinfoノードは システム コールを通じて作成されるため、新しい ファイル タイプmknod() をサポートするようにします。proc

fs/namei.c 次のように、 file 内の関数のコード行を変更し 、  if 判定にファイル システムに関する判定を追加します。 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);

(2) procfsの初期化

【ヒント】

カーネル初期化のすべての作業は main() で実行され、 main() 最後にカーネル モードからユーザー モードに切り替えて を呼び出します init() 。

init() 最初に行うことは、ルート ファイルシステムをマウントすることです。

void init(void)
{
//    ……
    setup((void *) &drive_info);
//    ……
}

procfs 初期化作業は、ルート ファイル システムがマウントされた後に開始する必要があります。これには 2 つの手順が含まれます。

  • ディレクトリを作成し、その下に各ノードを作成します /proc  /proc 
  • ディレクトリとノードの作成には、それぞれ呼び出し  と  システム コールが必要です。初期化時にすでにユーザーモードになっているため、 直接 呼び出すことはできませんこれら 2 つのシステム コールのユーザー モード インターフェイスは、初期化コードが配置されているファイル、つまり API に実装する必要があります。mkdir()mknod()sys_mkdir() sys_mknod() 
#ifndef __LIBRARY__
#define __LIBRARY__
#endif

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

ディレクトリとノードの対応するパラメータを作成します。

ディレクトリを作成するとき、modeパラメータの値は「0755」( rwxr-xr-xに対応 )にすることができます。これは、root ユーザーのみがこのディレクトリを書き換えることができ、他のユーザーはこのディレクトリに入って読み取ることしかできないことを意味します。 。 mkdir()  /proc 

procfs は読み取り専用のファイルシステムであるため、  ノードのmknod() 作成に使用する場合は、mode パラメーターを通じて読み取り専用に設定する必要があります。 これをモード値として使用することをお勧めします。これは、これが 0444 (r--r--r--) の権限を持つ proc ファイルであり、すべてのユーザーに対して読み取り専用であることを示しますpsinfo。 S_IFPROC|0444

③ 3 mknod() 番目のパラメータ dev は、ノードが表すデバイス番号を記述するために使用されます。procfs の場合、この番号は完全にカスタマイズ可能です。proc ファイルの処理関数は、この番号を使用して、対応するファイルにどのような情報が含まれているかを判断します。たとえば、psinfo には 0、hdinfo には 1、inodeinfo には 2 を入力できます。

追加を開始します:

① APIを以下にinit/main.c 追加しますmkdir()mknod()

② のinit/main.c 関数 init で初期化します procfs

mkdir("/proc",0755);
mknod("/proc/psinfo",S_IFPROC|0444,0);
mknod("/proc/hdinfo",S_IFPROC|0444,1);
mknod("/proc/inodeinfo",S_IFPROC|0444,2);

【テスト初期化効果】 

この作業が問題なく完了した場合、0.11 カーネルをコンパイルして実行すると、次のように ll /proc 表示されます。

cat を使用してこのファイルを読み取ってみることもできます。

上記は  設定されたモードで、前の 030 は inode->i_mode 設定  内容に関係します。この値を使用して、  正常に動作しているかどうかを確認します。上記の情報は、カーネルが 読み取り操作を正しく処理できず、EINVAL エラーを cat に返していることを示しています。ハンドラー関数がまだ実装されていないため、これは正常です。mknod()S_IFPROCmknod()psinfo、hdinfo、inodeinfo 

上記の情報は、少なくともpsinfo、hdinfo、inodeinfo それが正しいことを 示していますopen() 。したがって、何もする必要はありません sys_open() 。パッチを適用する必要があるのは だけです sys_read() 。

3. proc ファイルを読み取り可能にする

【ヒント】

最初の解析 sys_read(ファイル内 fs/read_write.c ):

int sys_read(unsigned int fd,char * buf,int count)
{
    struct file * file;
    struct m_inode * inode;
//    ……
    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);
    }

    printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);    //这条信息很面善吧?
    return -EINVAL;
}

明らかに、 ifs here のグループの比較で S_IFPROC() proc ファイルの処理関数に分岐を追加するには、処理関数に渡す必要があるパラメーターは次のとおりです。

  • inode->i_zone[0]、これは mknod() その時点で指定されたもの dev 、つまりデバイス番号です。
  • bufはユーザー空間を指し、 read() の 2 番目のパラメータであり、データを受信するために使用されます。
  • count、これは の read() 3 番目のパラメータであり、 buf 指すバッファのサイズを示します。
  • &file->f_pos,f_pos 最後に読み込んだファイルの末尾にある「ファイル位置ポインタ」の位置です。buf 処理関数は渡されるデータ量に応じて値を変更する 必要があるため、ここでポインタを渡す必要があります f_pos 。

(1) proc_read関数の外部呼び出しを追加

extern proc_readを追加して、  proc_read関数が外部から呼び出されることを示します。fs/read_write.c 

/* 新增proc_read函数外部调用 */
extern int proc_read(int dev,char* buf,int count,unsigned long *pos);

(2) procファイルのproc_read()呼び出しを追加

sys_read関数内では、他の if ステートメントに続けてS_IFPROC()ブランチを追加し、proc_read() 呼び出し処理を proc ファイルに追加します。

/* 新增proc_read调用 */
if (S_ISPROC(inode->i_mode))
    return proc_read(inode->i_zone[0],&file->f_pos,buf,count);

4. procファイルの処理機能を実現する

【ヒント】

proc ファイルの処理関数の機能は、デバイス番号に応じて異なる内容をユーザー空間に書き込むことですbuf書き込まれるデータは 、指定された位置から開始され、一度に最大バイトまで f_pos 書き込み、 実際に書き込まれたバイト数に応じて値を調整し、最後に実際に書き込まれたバイト数を返す必要があります。デバイス番号が psinfo の内容を読み取ることを示す場合、データは psinfo の形式で編成される必要があります。countf_pos

この機能を実装するには、次の関数を使用できます。

  • malloc()
  • 無料()

ヘッダー ファイルをインクルードする と、 および 関数linux/kernel.h を使用できるようになります  。これらはコア状態コードによって呼び出すことができます。唯一の制限は、一度に適用されるメモリ サイズが 1 ページを超えてはいけないことです。malloc()free()

(1) proc.cを書く

新しいファイルをディレクトリに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;
}

(2) メイクファイルを修正する

コード ツリーには、さまざまなモジュールのコンパイルを担当する多くの Makefile があります。上記は ディレクトリproc.cに追加されるので、ディレクトリ内の Makefile をそれに応じて変更する必要があります。fsfs

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

5. コンパイルして実行する 

(現在のシステム プロセス ステータス情報) および (ハードディスク情報) 情報をcat proc/xxx表示して、Linux 0.11 を再コンパイルして実行します。psinfohdinfo

おすすめ

転載: blog.csdn.net/Amentos/article/details/131469815