시스템 소프트웨어 1차 과제
<Log Structured File System Profiling>
제출자 p1 : LKM 코드 작성 및 보고서 작성
p2 : 커널 코드 작성 및 보고서 작성
제출일 2021.11.3
개발 환경
- 사용 언어 : C언어
- 가상 머신 : Oracle VM VirtualBox 6.1.28
- 운영 체제 : Ubuntu 16.04 LTS (64bit)
- 커널 버전 : Linux-4.4
- I/O 벤치마크 툴 : iozone
- 하드웨어 스펙 : Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz 2.71GHz
Freeday: 사용안함
목차
-배경지식
-작성한 코드에 대한 설명
-실행 방법에 대한 간략한 설명 및 실행 결과 캡쳐
-결과 그래프 및 그에 대한 설명
-과제 수행 시 어려웠던 부분과 해결 방법
1 배경지식
1.1 Virtual File System (VFS)
VFS는 실제 파일 시스템과 사용자 프로세스 사이에 존재하는 추상화 계층에 위치하며, 여러 개의 파일 시스템을 사용하기 위해 API를 제공한다. 사용자는 마치 하나의 파일 시스템이 있는 것처럼 프로그램이 가능하며, object-oriented 방식을 통해서 해당 file이 속한 file system의 read, write가 호출될 수 있도록 함수 포인터를 사용한다. 이를 위해 VFS는 Superblock, Inode, File, Dentry 등의 4가지 구조체를 가지고 있다. 먼저 Superblock의 경우, 마운트 시킨 파일 시스템의 정보를 저장하고 있으며, 내부에는 파일 시스템 타입인 s_type, superblock 함수 포인터의 모음인, s_op, inode 리스트를 의미하는 s_inodes 등의 정보를 가지고 있다. 다음으로 inode는 파일 관리에 필요한 소유자 그룹, 접근 모드, 파일 형태, inode 번호 등의 정보를 저장하고 있으며, 하나의 파일에 대해 하나의 inode를 생성하는 것이 가능하며, 고유한 inode를 통해 파일을 식별하는 것이 가능하다. Dentry는 디렉토리의 inode와 file path를 가지고 있는 객체로서 디스크에 저장된 것이 아닌 메모리에 저장되어 있는 In-memory data structure이다. 그렇기 때문에 directory에 효율적으로 접근하기 위해 모든 directory 정보를 dentry에 caching하여 사용한다. 마지막으로, file은 프로세스가 open한 파일을 표현하는데 사용하는 구조체이며 dentry와 마찬가지로 In-memory data structure이며, dentry와 다른 점은 파일시스템마다 존재하는 구조체는 아니라 하나만 존재한다는 점이다.
1.2 Log-structured File System (LFS)
기존의 파일 시스템인 Fast File System(FFS)와 달리, 데이터를 Write하는 경우 디스크에 연속적으로 Write를 하는 파일 시스템이다. 기존의 Read의 경우에는 memory caching 덕분에 성능이 많이 향상이 되었음에도, Write의 경우에는 일반 data의 경우에는 asynchronous하게 이루어 지지만 metadata update는 synchronous하게 이루어진다. 이 과정에서 어쩔 수 없이 여러 번 small write를 통해 이루어지기 때문에 성능이 향상될 수가 없었다. 또한 파일 시스템 동작 중에 crash가 발생하는 경우에, 전체 disk를 모두 다 scan해야 하며 이는 굉장히 시간이 오래 걸린다. 이러한 점에서 나온 아이디어가 여러 small write를 하지 말고 하나로 묶어서 한 번에 하면 어떨까 하는 것이고 이렇게 하게 된다면 여러 번의 small write 대신 한 번에 모아서 write함으로써 seek time을 줄일 수 있어서 성능향상이 가능해지고, 또한 Data recovery의 경우에도 마지막에 위치한 정보만 확인하게 되기 때문에 더 좋아지게 된다. 데이터를 순차적으로 쓰기 때문에 파일을 읽기 위해 필요한 inode의 위치를 쉽게 알 수 없게 되었고, 그렇기 때문에 inode map을 제일 마지막 위치에 도입해 inode를 쉽게 찾을 수 있도록 하였다. 또한 성능을 위한 Free space를 가능한 크게 유지하기 위해 디스크를 segment 단위로 나누어 쓰게 되며, 쓰기 전에 segment에 위치한 live data는 다른 segment로 복사가 되고(segment cleaning), 나머지 data는 flush된다. 이런 장점이 있었음에도 불구하고 디스크에서는 사용되지 않다가 현재에는 Flash Memory의 특성과도 잘 맞아서 Flash Memory의 파일 시스템으로 사용이 된다.
1.3 Nilfs2
Nilfs는 Linux에서 제공하는 LFS이다. 위에서 언급한 LFS의 특성에 맞게 data recovery가 비교적 쉽다. Flash Memory의 특성과도 잘 맞기 때문에 현재는 SSD를 위한 파일 시스템으로도 이용된다고 한다. 가장 큰 장점으로 스냅샷을 이야기할 수 있는데, 이를 통해 원하는 시점으로 돌아가는 것도 가능하다고 한다. 마찬가지 이유로 data recovery가 쉽다고 이야기할 수도 있다. 이것이 가능한 이유로 한 번 쓰여진 데이터의 경우, garbage collection하지 않는다면 계속 유지되기 때문이다.
1.4 Loadable Kernel Module (LKM)
동적으로 kernel의 모듈을 넣었다가 뺐다 할 수 있는 기능이다. Monolithic-kernel에서 Micro-kernel의 장점을 취하고 있다고 할 수 있으며, 장점으로는 특정 기능을 필요한 경우 넣고, 필요 없는 경우 뺄 수 있기 때문에 커널 기능의 확장이 용이하며, 커널 주소 공간에서 수행되므로 성능 저하가 적다. 단점으로는 커널 영역에서 동작을 하기 때문에 잘 못된 LKM이 전체 시스템의 동작에 영향을 줄 수도 있다는 것이다.
1) Proc File System
유저 영역에서 커널 영역의 자료구조에 접근할 때 사용되는 메모리에만 존재하는 가상 파일 시스템으로 실제로 block device에 연결된 것이 아니라 메모리 상에 mapping되어 있기 때문에 물리적인 device를 필요로 하지 않는다. 기존에 사용하던 printk의 경우 제한된 버퍼 사이즈, 데이터의 가공이 어려웠다는 점을 proc file system의 경우 해소가 가능하며, 더 나아가 runtime에 필요한 작업들을 코드를 통해 가능하게 한다는 점에서 더 낫다고 할 수 있다.
2 작성한 코드에 대한 설명
2.1 ext4 커널코드
block/blk-core.c
// Modification Start struct Info { unsigned long long block_number; // block number struct timeval time; // time const char* fs_name; // file system name }; #define Q_SIZE 200 struct Info Circular_Q[Q_SIZE]; int head = 0; // Queue - head int tail = 0; // Queue - tail struct timeval currenttime; // to get time // Modification End … 생략 EXPORT_SYMBOL(submit_bio); // Modification Start EXPORT_SYMBOL(Circular_Q); EXPORT_SYMBOL(head); EXPORT_SYMBOL(tail); // Modification End |
위 코드는 blk-core.c에 추가된 구초체, 변수를 선언하는 부분이다. Info 구조체는 블록 넘버, 시간, 파일 시스템 종류를 저장하기 위해 작성하였다. 이후 Circular 큐를 만들기 위해서 Q_SIZE를 설정하고 Info 자료형으로 Circular_Q를 선언하였다. head는 Circular_Q의 마지막으로 저장된 인덱스를 가르키고 tail은 Circular_Q를 비워주기 위해 선언한 변수이다. currenttime은 시간을 저장하기 위한 변수이다. 이후 생략된 부분을 지난 다음 export_symbol을 통해 해당 변수를 LKM에서 사용할 수 있도록 선언하였다.
if (rw & WRITE) { // Write count_vm_events(PGPGOUT, count); // Modification Start if(bio != NULL && bio->bi_bdev->bd_super != NULL) // null-exception { if((head+1) % Q_SIZE != tail) // Q - full check { head = (head+1) % Q_SIZE; do_gettimeofday(¤ttime); // get time Circular_Q[head].block_number = bio->bi_iter.bi_sector; // block number Circular_Q[head].fs_name = bio->bi_bdev->bd_super->s_type->name; // file system name Circular_Q[head].time = currenttime; // time } } // Modification End } else { task_io_account_read(bio->bi_iter.bi_size); count_vm_events(PGPGIN, count); } |
위 코드는 blk-core.c의 submit_bio 함수의 Circular_Q 구현 부분이다. bd_super이라는 super_block 정보를 가져오지 못하면 실행되지 않도록 if문을 설정하였다. 다음으로 큐가 꽉차면 실행되지 않도록 if문을 설정하였다. 두 조건을 모두 만족하면 head는 한 칸 움직이고 currenttime에 시간을 저장한 뒤 로그에 저장할 블록넘버, 파일 시스템 이름, 시간 정보를 Circular_Q의 인덱스마다 순차적으로 Q_SIZE만큼 저장한다.
2.2 Ext4 LKM(proc) 코드
/basic.c
#include<linux/module.h> #include<linux/kernel.h> #include<linux/init.h> #include<linux/proc_fs.h> #include<asm/uaccess.h> // copy_to_user #include<linux/time.h> // struct tm #include <linux/delay.h> #include <linux/timer.h> #define PROC_DIRNAME "hw1" #define PROC_FILENAME "hw1file" #define Q_SIZE 200 static struct proc_dir_entry *proc_dir; static struct proc_dir_entry *proc_file; struct Info { unsigned long long block_number; struct timeval time; const char* fs_name; // file system name }; extern struct Info Circular_Q[Q_SIZE]; // Circular Queue extern int head; // Queue index - head extern int tail; // Queue index - tail struct tm currenttimes; struct tm passedtimes; struct timeval currenttime; struct timeval passedtime; struct tm times; char info[1000][100]; int info_index = 0; |
위 코드는 basic.c 코드의 구조체, 변수 선언 부분이다. 파일 디렉토리는 hw1, 파일 이름은 hw1file로 설정하고 Q_SIZE는 200으로 설정하였다. proc_dir, proc_file은 이후 모듈을 올리면 실행되는 Init 함수에서 디렉토리와 파일을 생성하기 위해 사용하는 포인터 변수이다. Info 구조체는 커널에서 작성한 구조체와 동일하게 작성하였다. extern을 통해 Circular_Q, head, tail 변수를 커널에서 가져와서 사용할 수 있다. currenttimes, passedtimes, currenttime, passedtime 변수들은 이후 5초마다 모듈이 실행될 수 있도록 설정하기 위해 사용할 변수들이다. info는 데이터를 저장할 버퍼이고 info_index는 버퍼가 꽉차면 멈추도록 설정하기 위해 선언하였다.
static int my_open(struct inode *inode, struct file *file) { printk(KERN_INFO "Simple Module Open!!\n"); return 0; } static ssize_t my_read(struct file *file, char *buffer, size_t length, loff_t *offset) { if(copy_to_user(buffer, info, sizeof(info))){ return -EFAULT; } return length; } static ssize_t my_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos) { int i=0; printk(KERN_INFO "Simple Module Write!!\n"); } |
위 코드는 /proc 파일이 open, read write 될 때 실행되는 함수들이다. my_open 함수는 파일을 open 할 때 Simple Module Open!!이라는 문구를 출력하고 my_read 함수는 info에 저장되어있는 데이터를 buffer를 통해 사용자에게 데이터를 전달해준다. 마지막으로 my_write는 파일에 쓰기를 할 때 실행되는 함수로 쓰기를 하면 Simple Module Write!!라는 문구가 출력된다.
static const struct file_operations myproc_fops = { .owner = THIS_MODULE, .open = my_open, .read = my_read, .write = my_write, }; static int __init simple_init(void) { printk(KERN_INFO "Simple Module Init!!\n"); proc_dir = proc_mkdir(PROC_DIRNAME, NULL); proc_file = proc_create(PROC_FILENAME, 0600, proc_dir, &myproc_fops); // name, mode(permission), parent, file_operations int i; do_gettimeofday(¤ttime); do_gettimeofday(&passedtime); time_to_tm(passedtime.tv_sec, 0, &passedtimes); while(1) { do_gettimeofday(¤ttime); // get time time_to_tm(currenttime.tv_sec, 0, ¤ttimes); if(currenttimes.tm_sec - passedtimes.tm_sec>=5) //per 5 seconds Q Save { if(info_index<1000) { for(i=tail+1;i<Q_SIZE;i++) { time_to_tm(Circular_Q[i].time.tv_sec,0,×); sprintf(info[info_index++], "File System: %s, Block Number: %lld, Time: %d:%d:%d:%ld\n", Circular_Q[i].fs_name, Circular_Q[i].block_number, times.tm_hour, times.tm_min, times.tm_sec, Circular_Q[i].time.tv_usec); } for(i=0; i<=tail;++i){ time_to_tm(Circular_Q[i].time.tv_sec,0,×); sprintf(info[info_index++], "File System: %s, Block Number: %lld, Time: %d:%d:%d:%ld\n", Circular_Q[i].fs_name, Circular_Q[i].block_number, times.tm_hour, times.tm_min, times.tm_sec, Circular_Q[i].time.tv_usec); } tail=head; } passedtimes = currenttimes; } } return 0; } static void __exit simple_exit(void) { printk(KERN_INFO "Simple Module Exit!!\n"); return ; } module_init(simple_init); module_exit(simple_exit); MODULE_AUTHOR(); MODULE_DESCRIPTION(""); MODULE_LICENSE("GPL"); MODULE_VERSION("NEW"); |
위 코드는 LKM 모듈을 올리고 내릴 때 실행되는 init, exit 함수 부분으로 init 함수에 로그 정보를 저장하는 모든 기능을 구현하였다. proc_dir과 proc_file에 필요한 값인 디렉토리와 파일 그리고 사용할 함수를 myproc_fops에 저장하여 인자로 넘긴다. currenttime과 passedtime을 비교하여 5초마다 Circular_Q에 있는 정보를 info에 저장하고 tail 값을 초기화해서 큐를 사용할 수 있게 해준다. 그리고 info_index를 통해 1000번 반복할 경우 실행되지 않도록 설정하였다. info에 데이터를 저장한 후 tail을 head 값으로 초기화하여 큐가 다시 동작하도록 설정하였다.
obj-m +=basic.o KDIR = /home/lemon/Desktop/linux all: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.o *.ko *.mod.* *.symvers *.order |
위 코드는 basic.c를 컴파일 하기 위한 Makefile이다. KDIR에서 사용하는 커널의 디렉토리 경로를 입력하고 all에는 make가 명령어를 입력했을 때 실행되는 명령어를 입력, clean에는 마지막으로 clean할 때 삭제할 파일들을 입력하면 된다.
2.3 Nilfs2 커널코드
/linux/fs/nilfs/segbuf.c
bio->bi_end_io = nilfs_end_bio_write; bio->bi_private = segbuf; // Modification Start bio->bi_bdev->bd_super = segbuf->sb_super; // Modification End submit_bio(mode, bio); segbuf->sb_nbio++; |
위 코드는 segbuf.c 코드의 일부이다. nilfs2 파일 시스템을 사용하기 위해서 /fs/nilfs/segbuf.c에서 nilfs2가 사용하는 super_block 정보를 bio 구조체를 통해 넘겨줘야 blk-core.c에서 nilfs2 super_block을 인식할 수 있다. 따라서 위와 같이 submit 함수로 bio를 넘겨주기 전에 nilfs2의 super_block 정보를 bio->bi_bdev->bd_super에 저장한 후 넘겨주었다.
if (rw & WRITE) { // Write count_vm_events(PGPGOUT, count); // Modification Start if(bio != NULL && bio->bi_bdev->bd_super != NULL && strcmp(bio->bi_bdev->bd_super->s_type->name,"nilfs2") == 0) // null-exception { if((head+1) % Q_SIZE != tail) // Q - full check { head = (head+1) % Q_SIZE; do_gettimeofday(¤ttime); // get time Circular_Q[head].block_number = bio->bi_iter.bi_sector; // block number Circular_Q[head].fs_name = bio->bi_bdev->bd_super->s_type->name; // file system name Circular_Q[head].time = currenttime; // time } } // Modification End } |
위 코드는 blk-core.c 코드의 일부이다. nilfs2가 write를 할 때 블록 정보와, 시간 값, 파일 시스템이름을 저장하는데 ext4 파일 시스템의 데이터도 같이 저장되기 때문에 nilfs2 파일 시스템의 데이터만 저장하기 위해 if문에 bio->bi_bdev->bd_super->s_type->name와 “nilfs2”를 비교하는 구문을 추가하였다.
2.4 Nilfs2 LKM(proc) 코드
/basic.c
struct tm time; static ssize_t my_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos) { int i=0; for(i=tail+1; i<Q_SIZE;++i){ time_to_tm(Circular_Q[i].time.tv_sec, 0, ×); sprintf(info[i], "File System: %s, Block Number: %lld, Time: %d:%d:%d:%ld\n", Circular_Q[i].fs_name, Circular_Q[i].block_number, times.tm_hour, times.tm_min, times.tm_sec, Circular_Q[i].time.tv_usec); } for(i=0; i<=tail;++i){ time_to_tm(Circular_Q[i].time.tv_sec, 0, ×); sprintf(info[i], "File System: %s, Block Number: %lld, Time: %d:%d:%d:%ld\n", Circular_Q[i].fs_name, Circular_Q[i].block_number, times.tm_hour, times.tm_min, times.tm_sec, Circular_Q[i].time.tv_usec); } tail=head; return 0; } static int __init simple_init(void) { printk(KERN_INFO "Simple Module Init!!\n"); proc_dir = proc_mkdir(PROC_DIRNAME, NULL); proc_file = proc_create(PROC_FILENAME, 0600, proc_dir, &myproc_fops); // name, mode(permission), parent, file_operations return 0; } |
Nilfs2용 LKM 코드에서는 init이 아닌 my_write 함수에 Circular_Q 큐 데이터를 info에 저장하는 코드를 구현하였다. 모듈이 시작할 때 한번 동작하는 init 함수와 달리 my_write 함수는 파일에 대한 쓰기를 진행할 때 마다 실행되는 함수이다. init에 있던 코드는 my_write 함수에 구현했으므로 ext4용 LKM의 init 함수에 있던 코드는 대부분 삭제하고 my_write 함수에 구현하였다.
3 실행 방법에 대한 간략한 설명 및 실행 결과 캡쳐
3.1 ext4
1) ext4용 커널 코드 컴파일
blk-core.c 코드 수정 후 컴파일
2) LKM 코드 컴파일
basic.c를 컴파일해서 basic.ko파일을 생성
3) LKM 로드
생성한 basic.ko를 insmod로 로드
lsmod basic.ko로 로딩 확인
모듈 로딩 후 /proc 디렉토리에 hw1 디렉토리 생성 및 hw1file 생성 확인
4) iozone 실행
iozone -i 0 -f (임의의 파일경로)
iozone으로 write 실행
5) echo 명령어로 커널 큐의 데이터를 LKM의 info에 write
echo “ext4” >> /proc/hw1/hw1file
6) cat 명령어로 info 데이터를 read하여 로그파일로 저장
cat /proc/hw1/hw1file >> ./log.txt
7) ext4 실행결과
3.2 Nilfs2
1) nilfs2용 커널 코드 컴파일
blk-core.c, segbuf.c 코드 수정 후 컴파일
2) LKM 코드 컴파일
nilfs2용 basic.c를 컴파일해서 basic.ko파일을 생성
3) LKM 로드
생성한 basic.ko를 insmod로 로드
lsmod basic.ko로 로딩 확인
모듈 로딩 후 /proc 디렉토리에 hw1 디렉토리 생성 및 hw1file 생성 확인
4) nilfs 마운트
losetup /dev/loop0 ./diskfile // I/O Device 등록
mkdir nilfs // 마운트할 파일 생성
mount -t nilfs2 /deb/loop0 nilfs // 마운트
5) iozone 실행
iozone -a -i 0 -i 2 -f nilfs/(임의의 파일)
ext4의 iozone 옵션과 똑같이 하면 6개 정도의 데이터만 출력되고 그 뒤로 null 값이 출력되므로 -a옵션과 random read/write를 제공하는 -i 2 옵션을 추가하여 그러한 상황을 피할 수 있다.
6) echo 명령어로 커널 큐의 데이터를 LKM의 info에 write
echo “ext4” >> /proc/hw1/hw1file
7) cat 명령어로 info 데이터를 read하여 로그파일로 저장
cat /proc/hw1/hw1file >> ./log.txt
8) nilfs2 실행결과
4 결과 그래프
4.1 Ext4
위 그래프는 엑셀 csv 파일을 파이썬을 통해 그래프로 구현하였다. 세로축은 블록 넘버를 의미하고 가로축은 시간을 의미한다. Ext4의 경우, 새로운 데이터를 write한다면 같은 실린더 내에서 data를 쓴 후 inode의 위치에 데이터를 다시 쓴다. 그러므로 접근하는 data block에 어느 정도 변동이 생길 수 밖에 없고, 그래프를 통해 ext4의 동작 방식을 살펴보면 대체적으로 비슷한 블록 넘버에 쓰기를 진행하지만 가끔씩 멀리 떨어진 블록 넘버에 쓰기를 진행하는 경우가 있다.
4.2 Nilfs2
Ext4와는 다르게 거의 모든 쓰기 동작이 블록 넘버에 순차적으로 접근한다. Nilfs2 LFS이므로 0번부터 순차적으로 쓰기를 진행하는 것을 확인할 수 있다. 그렇다고 아예 순차적인 것은 아니며 중간 중간에 0번과 2000000번 대에 접근하는 것을 볼 수 있는데, fixed position에 대해서는 superblock과 checkpoint region이 위치하는데 0번에 대해서는 current inode map의 주소 값을 저장하는 checkpoint region인 것이 분명하다. 마찬가지로 생각한다면 2000000번 대의 block number에 대해서도 유사한 기능을 수행한다는 것을 유추할 수 있다.
5 과제 수행 시 어려웠던 부분과 해결 방법
1) Nilfs2의 결과
처음 생각에는 ext4에 대해서 LKM을 구현하고 완성된 실험 결과를 이끌어 낸다면 그 후의 nilfs2에 대해서는 간단히 mount하고, segbuf.c를 수정해서 성능 측정을 하면 될 줄 알았다. 그렇지만, 기존의 코드를 그대로 사용하니 대부분의 log에는 ext4에 대한 기록만이 있었고, strcmp를 통해 걸러낸 후에는 nilfs2가 몇 개 없었다. 수정했던 내용은 커널 코드 부분과 LKM 부분이므로 그래서 생각나는 대로 이것저것 바꿔보고 여러 방식을 시도해보았다. 그나마 다행인 점은 저번 학기에서 OS 수업 때 걸렸던 커널 컴파일은 기본 1~2시간 정도 걸렸는데, 이번에는 커널 컴파일은 그렇게 시간이 걸리지 않아서 많은 것을 시도해보고 바꿔 보는 것이 가능했던 것 같다. 그러던 와중에 ext4의 경우에는 기본적으로 printk 등의 로그를 사용하면 기록이 남는다는 것을 알게 되었고, nilfs2의 경우에는 기본적인 그러한 로그를 쓰는 과정들을 우리가 구현한 것이 아니기 때문에 block을 할당하는 횟수가 적어서 결과가 적게 나오는 것은 아닐까라는 생각을 하게 되었고, iozone을 통해서 좀 더 다양하게 돌려주면 결과가 나올 것 같다는 생각을 하게 되었다. 그 중에서 간단히 -a옵션(모드 자동화 – 파일 크기를 자동으로 증가해서 테스트)과 -i옵션(0(write/rewrite), 2(random-read/write)을 주게 되었다. 여러가지 옵션들을 실험한 끝에 iozone -a -i 0 -i 2 -f (경로)와 같은 옵션을 통해 실행하면 원하는 데이터를 얻을 수 있었다.
2) nilfs proc 코드 작성
위에서 마찬가지 이유로 nilfs2에 대한 log를 출력하다가 Ext4에서는 proc 코드를 작성할 때 init 함수에 주기적으로 큐를 저장하는 코드를 구현하여 /proc/hw1/hw1file에 데이터가 제대로 저장되었지만 nilfs에서는 Null 값이 저장되어서 코드를 수정하느라 힘들었다(나중에 가서야 데이터가 적었다는 생각을 했지만 그 때는 그런 줄 알았다). 시행착오 끝에 init이 아닌 my_write에 큐를 저장하는 코드를 구현한 결과 데이터가 hw1/hw1file에 제대로 저장된다는 것을 깨달아서 해결할 수 있었다.
3) 전반적인 실습
지난 번 OS 때 했던 실습과는 다르게 printk와 dmesg를 이용하는 것 대신 proc file system을 이용한 것과 새로운 file system을 mount하기, iozone3을 사용해서 테스트하는 작업들은 지난번의 kernel코드만 다루었던 OS 실습에 비해 확실히 더 새롭고 낯설어서 조금 더 어려웠던 것 같다. 또한 하다가 보니 log file들이 용량이 너무 커져서 설정한 디스크 용량을 가득 채웠고, 그로 인해 속도저하가 생겨서 실습에 어려움을 겪었다. Syslog나 kern.log에 용량이 엄청 많아져서 해당 file에 0을 채우는 방법도 시도해보았는데 kern.log file은 얼마 지나지 않아서 다시 용량이 커져서 결국 새로 시도했었다.
6 F2FS에 대해 동일한 실험 수행.
F2FS(Flash Friendly File System)도 Nilfs2와 마찬가지로 LFS(Log Structured File System)이다. LFS는 연속적으로 Write를 하기 때문에 Flash Memory의 특성과도 잘 맞는 File System이라 할 수 있다. 최근에 부각되는 Flash Memory(SSD)를 이용하기 위해서 많은 LFS들이 개발되었으며, 그 예시로 BTRFS(B-Tree File System)과 Nilfs2가 있다. 하지만 Flash Memory의 특성들을 모두 고려한 것은 아니고 성능과 수명 측면에서 좋지 않았기 때문에 새로운 File System인 F2FS가 제시되었다고 한다. F2FS의 특징으로는
1. Segment, section, and zone
- Flash에 친화적인 Layout을 사용함
2. NAT(Node Address Table)
- 비용(시간 및 공간) 측면에서 효율적인 index 구조를 제시함
- Main Area에 저장된 모든 node block들의 위치를 저장하는 Table
3. Multi-head Logging
- Multi-head를 사용하여 병렬처리를 하고 Hot/Cold를 분류함
4. Cleaning
- Background와 Foreground로 구별되어 존재한다.
등이 있다.
정리하자면 기존에 쓰던 nilfs2보다는 성능을 많이 향상 시킨 LFS라고 볼 수 있으며 EXT4와도 비교했을 때 성능이 더 낫거나 비슷한 정도임을 알 수 있다.
Ext4의 경우, 새로운 데이터를 write한다면 같은 실린더 내에서 data를 쓴 후 inode의 위치에 데이터를 다시 쓴다. 그러므로 접근하는 data block에 어느 정도 변동이 생길 수 밖에 없다.
LFS인 F2FS의 경우, LFS이므로 순차적으로 Block Number에 접근함을 알 수 있으며, 이는 F2FS에 대해서 실험을 수행하게 될 경우 LFS가 아닌 EXT4보다는 LFS인 Nilfs2와 유사한 결과(순차적으로 block number에 접근하는 것을 의미함)를 도출할 것이다.
'잡동사니' 카테고리의 다른 글
시스템 해킹 기초 (0) | 2021.12.15 |
---|---|
pthread를 이용한 client-side socket programming (0) | 2021.12.06 |
네트워크 해킹 기초 (0) | 2021.11.05 |
CPU 프로세스 우선순위 변경에 따른 CPU 실제사용시간 비교 (0) | 2021.11.03 |
[리눅스] 파일시스템 관련 명령어 모음 (0) | 2021.10.11 |
댓글