목차
2. mknod()가 새로운 파일 형식을 지원하도록 합니다.
(2) proc 파일의 proc_read() 호출 추가
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 소개
공식 리눅스 커널이 구현되어 있다 procfs
. 보통 디렉토리에 마운트(마운트)되는 가상 파일 시스템(프로세스 파일 시스템)이다. /proc
가상 파일과 가상 디렉토리를 통해 시스템 매개변수에 접근할 수 있는 기회를 제공하기 때문에 혹자는 " 시스템 정보를 볼 수 있는 창".
이러한 가상 파일 및 디렉토리는 실제로 디스크에 존재하지 않지만 커널의 다양한 데이터를 시각적으로 표현한 것입니다. 가상이지만 모두 표준 시스템 호출( open()
, read()
... )을 통해 액세스할 수 있습니다.
예를 들어, /proc/meminfo
메모리 사용량 정보가 포함된 경우 cat 명령을 사용하여 내용을 표시할 수 있습니다.
실제로 Linux의 많은 시스템 명령은 /proc
읽기로 구현됩니다. 예를 들어 uname -a
정보의 일부는 에서 이고 /proc/version
정보 uptime
의 일부는 /proc/uptime
및 에서 입니다 /proc/loadavg
.
procfs
Procfs에 대한 자세한 내용 은 http://en.wikipedia.org/wiki/Procfs 를 방문하세요.
2. 기본 아이디어
Linux는 파일 시스템 인터페이스를 통해 구현되며 시작 시 자동으로 디렉토리 procfs
에 마운트됩니다 ./proc
이 디렉터리의 모든 내용은 시스템의 동작에 따라 자동으로 생성, 삭제, 갱신되며 외부 저장 공간을 차지하지 않고 메모리에 완전히 존재합니다.
Linux 0.11은 아직 가상 파일 시스템을 구현하지 않습니다. 즉, 새 파일 시스템에 대한 지원을 추가하기 위한 인터페이스를 아직 제공하지 않습니다. 따라서 이 실험은 기존 파일 시스템을 기반으로 패치를 적용하여 한 가지만 시뮬레이션할 수 있습니다 procfs
.
Linux 0.11은 일반적인 Linux 기반 파일 시스템인 Minix 파일 시스템을 사용하며 inode
"Notes" 책에 자세히 설명되어 있습니다. 각 파일은 적어도 하나의 inode에 해당해야 하며 파일 유형을 포함하여 파일의 다양한 속성이 inode에 기록됩니다. 파일 유형에는 일반 파일, 디렉토리, 문자 장치 파일 및 블록 장치 파일이 포함됩니다. 커널에서 각 유형의 파일에는 그에 상응하는 다른 처리 기능이 있습니다. 새로운 파일 유형인 proc
file을 추가하고 해당 처리 기능에서 실현할 기능을 실현할 수 있습니다 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)
새 파일 유형을 추가하는 두 단계가 있습니다.
- 값이 0010000에서 0100000 사이여야 하지만 8진수의 마지막 4자리가 0이어야 하는 유형 매크로를 정의합니다 (이것이 제한 사항이며 이유는 테스트 매크로를 분석하여 알 수 있음). 기존의 것.
S_IFPROC
S_IFMT
S_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
다음과 같이 파일의 함수에서 코드 라인을 수정 하고 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
초기화 작업은 루트 파일 시스템이 마운트된 후 시작해야 하며 다음 두 단계를 포함합니다.
- 디렉터리를 만들고 디렉터리 아래에 각 노드를 만듭니다 .
/proc
/proc
- 디렉토리와 노드를 생성하려면 각각 호출 과 시스템 호출이 필요합니다. 초기화할 때 이미 사용자 모드에 있기 때문에 직접 호출할 수 없습니다 . 이 두 시스템 호출의 사용자 모드 인터페이스는 초기화 코드가 있는 파일, 즉 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)
디렉토리 및 노드의 해당 매개변수를 작성하십시오.
① 디렉토리를 생성할 때 모드 매개변수의 값은 "0755"(rwxr-xr-x 에 해당 )일 수 있습니다. 즉, 루트 사용자만 이 디렉토리를 다시 쓸 수 있으며 다른 사람은 이 디렉토리에 들어가고 읽을 수 있습니다. . mkdir()
/proc
②
procfs 는 읽기 전용 파일 시스템이므로 노드 mknod()
생성에 사용할 때 mode 매개 변수를 통해 읽기 전용으로 설정해야 합니다. 모든 사용자에게 읽기 전용인 0444(r--r--r--) 권한이 있는 proc 파일임을 나타내는 모드 값으로 사용하는 것이 좋습니다 psinfo
. S_IFPROC|0444
③ mknod()
세 번째 매개 변수 dev는 노드가 나타내는 장치 번호를 설명하는 데 사용됩니다. procfs의 경우 이 숫자는 완전히 사용자 정의할 수 있습니다. proc 파일의 처리 기능은 이 번호를 사용하여 해당 파일에 포함된 정보를 결정합니다. 예를 들어 psinfo에 0, hdinfo에 1, inodeinfo에 2를 입력할 수 있습니다.
추가 시작:
① init/main.c
추가 mkdir()
및 mknod()
API:
② 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을 통해 이 파일을 읽을 수도 있습니다.
위 inode->i_mode
는 mknod()
모드 설정이고 이전 030은 S_IFPROC
우리가 설정한 것과 관련이 있습니다. mknod()
이 값을 사용하여 제대로 작동하는지 확인하십시오 . 위의 정보는 커널이 psinfo、hdinfo、inodeinfo
읽기 작업을 올바르게 처리할 수 없으며 cat에 EINVAL 오류를 반환함을 보여줍니다. 핸들러 기능이 아직 구현되지 않았기 때문에 이는 정상입니다.
위의 정보는 적어도 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()
데이터를 수신하는 데 사용되는 의 두 번째 매개변수입니다.count
의read()
세 번째 매개변수로buf
가리키는 버퍼의 크기를 나타냅니다.&file->f_pos
,f_pos
마지막으로 읽은 파일의 끝에 있는 "파일 위치 포인터"의 지점입니다.buf
처리 함수는 전달된 데이터의 양에 따라 값을 수정 해야 하므로 포인터를 여기에 전달해야 합니다f_pos
.
(1) proc_read 함수 외부 호출 추가
proc_read 함수가 외부에서 호출되었음을 나타내기 위해 extern 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 파일에 proc_read() 호출 처리를 추가합니다.
/* 新增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 형식으로 구성해야 합니다.count
f_pos
이 기능을 구현하기 위해 다음 기능을 사용할 수 있습니다.
- 맬록()
- 무료()
헤더 파일이 포함되면 및 기능을 linux/kernel.h
사용할 수 있습니다 . 핵심 상태 코드에 의해 호출될 수 있으며 한 번에 적용되는 메모리 크기가 한 페이지를 초과할 수 없다는 유일한 제한이 있습니다.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 수정
코드 트리에는 다양한 모듈의 컴파일을 담당하는 많은 Makefile이 있습니다. 위의 내용은 디렉토리 proc.c
에 추가되었으므로 디렉토리의 Makefile도 그에 맞게 수정해야 합니다.fs
fs
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을 다시 컴파일하고 실행합니다 .psinfo
hdinfo