Apa??? Bikin kernel lagi???? :(
Looking for the English version?
Pada final praktikum, kita akan melanjutkan task-4
dari praktikum modul 4 yang sebelumnya. Kali ini, kita akan membuat sebuah filesystem sederhana yang dapat digunakan untuk menyimpan file-file yang kita buat. Filesystem yang akan kita buat ini akan menggunakan metode penyimpanan data yang sederhana, yaitu dengan menyimpan data file ke dalam blok-blok yang telah disediakan oleh filesystem. Jika kalian sudah tidak sabar ingin langsung mengerjakan task-task yang ada, bisa search TODO
pada workspace ini. Berikut adalah gambaran yang akan kalian kerjakan pada final praktikum kali ini.
- Membuat filesystem yang dapat digunakan untuk menyimpan file-file yang kita buat.
- Melengkapi kernel untuk dapat membaca dan menulis file ke dalam filesystem yang telah kita buat.
- Membuat shell sederhana yang dapat digunakan untuk mengakses filesystem yang telah kita buat.
Penjelasan pada praktikum final akan sering menggunakan angka heksadesimal. Penggunaan angka heksadesimal ditandai dengan prefix 0x
. Jika kalian belum terbiasa dengan angka heksadesimal, kalian dapat menggunakan kalkulator yang mendukung mode heksadesimal atau menggunakan konversi angka heksadesimal ke desimal.
Jika kalian sudah melewati modul 4, pasti sudah tidak asing lagi dengan struktur disk yang akan kita gunakan. Disk yang kita gunakan terdiri dari beberapa blok. Selanjutnya blok akan disebut sektor. Setiap sektor memiliki ukuran 512 bytes. Sektor pertama akan digunakan sebagai boot sector, yang berisi hasil kompilasi dari bootloader.asm
. Sektor kedua hingga sektor ke-15 akan digunakan untuk menyimpan kode teks dari kernel yang kita buat.
Dengan melihat hasil dari modul 4, berikut adalah struktur disk yang akan kita gunakan. Dapat dilihat menggunakan aplikasi seperti HxD
atau menggunakan perintah hexdump
atau xxd
.
Untuk memudahkan dalam ilustrasi selanjutnya, struktur disk akan digambarkan sebagai berikut.
Satu sektor akan digambarkan sebagai satu blok. Alamat sektor akan dinomori ulang dari 0x00
. Sehingga sektor pertama akan memiliki alamat 0x00
, sektor kedua akan memiliki alamat 0x01
, dan seterusnya. Satu baris akan berisi 16 sektor. Sehingga baris pertama akan berisi sektor dengan alamat 0x00
hingga 0x0F
, baris kedua akan berisi sektor dengan alamat 0x10
hingga 0x1F
, dan seterusnya.
Untuk mencari alamat sektor pada isi file floppy.img
, kita dapat mengonversi alamat sektor ke dalam alamat byte dengan cara seperti pada gambar di atas.
Filesystem yang akan dibuat akan menggunakan beberapa komponen, yaitu map, node, dan data. Map akan disimpan sebanyak 1 sektor pada sektor 0x100
. Node akan disimpan sebanyak 2 sektor pada sektor 0x101
dan 0x102
. Data akan disimpan sebanyak 1 sektor pada sektor 0x103
.
Berikut adalah ilustrasi dari struktur filesystem yang akan kita buat.
Map akan digunakan untuk menandai blok-blok pada disk yang telah digunakan oleh file. Setiap blok akan memiliki status 0x00
jika sektor yang bersangkutan belum digunakan, dan 0x01
jika sektor yang bersangkutan telah digunakan. Contohnya, karena pada sektor 0x00
telah digunakan oleh bootloader, maka isi dari map ke-0 adalah 0x01
. Komponen map akan digunakan ketika kita ingin menulis file ke dalam disk untuk mengetahui sektor mana saja yang dapat kita gunakan.
Berikut adalah ilustrasi dari komponen map.
Map akan berukuran 1 sektor (512 bytes). Item ke-0 hingga item ke-15 pada map akan memiliki status 0x01
karena telah digunakan oleh sistem operasi. Item ke-16 hingga item ke-255 akan memiliki status 0x00
karena belum digunakan. Mulai dari item ke-256 (0x100
) hingga item ke 511 (0x1FF
) akan ditandai sebagai sektor yang telah digunakan. Hal ini dikarenakan kita tidak memperbolehkan file untuk menulis data pada sektor yang berada di atas sektor 0x100
.
Node akan digunakan untuk menyimpan informasi dari file atau direktori yang kita buat. Setiap node akan memiliki ukuran 16 bytes. Sehingga, total akan terdapat 64 item node yang bisa disimpan. Berikut adalah ilustrasi dari komponen node.
Berikut adalah penjelasan dari setiap item pada node.
-
P: kolom pertama pada item node berguna sebagai penunjuk parent node dari node yang bersangkutan dan akan bernilai
0xFF
jika parent dari node yang bersangkutan adalah root node.Sebagai contoh, pada node index ke-1, nilai dari kolom pertama adalah
0x00
. Hal ini menunjukkan bahwa node index ke-1 adalah parent node dari node index ke-0. Sedangkan pada node index ke-0, nilai dari kolom pertama adalah0xFF
. Hal ini menunjukkan bahwa parent node dari node index ke-0 adalah root node. -
D: kolom kedua pada item node berguna sebagai penunjuk index dari komponen data yang akan digunakan untuk menyimpan data file. Jika nilai dari kolom kedua adalah
0xFF
, maka node tersebut merupakan direktori.Sebagai contoh, pada node index ke-0, nilai dari kolom kedua adalah
0x00
. Hal tersebut berarti informasi data dari file tersebut bisa diakses pada komponen data index ke-0. Sedangkan pada node index ke-1, nilai dari kolom kedua adalah0xFF
. Hal tersebut berarti node tersebut merupakan direktori. -
Node name: kolom ketiga hingga kolom terakhir pada item node berguna sebagai nama dari node tersebut. Nama dari node akan memiliki panjang maksimal 13 karakter (karakter terakhir adalah karakter null).
Komponen data akan digunakan untuk petunjuk sektor-sektor yang digunakan untuk menyimpan data file. Setiap item data akan memiliki ukuran 16 bytes. Sehingga, total akan terdapat 32 item data yang bisa disimpan. Berikut adalah ilustrasi dari komponen data.
Setiap kolom pada item data akan menunjukkan alamat sektor yang digunakan untuk menyimpan data file. Karena satu byte hanya dapat menunjukkan alamat sektor hingga 255 (0xFF
), maka kita hanya dapat menyimpan alamat sektor hingga sektor 0xFF
. Oleh karena itu, item map ke-256 hingga akhir akan ditandai sebagai sektor yang telah digunakan.
Berikut adalah ilustrasi dari ketiga komponen filesystem yang telah dijelaskan sebelumnya.
Pada task ini, kalian diminta untuk membuat syscall readSector
dan writeSector
yang akan digunakan untuk membaca dari disk ke memory dan menulis dari memory ke disk.
Berikut adalah implementasi dari readSector
dan penjelasannya.
void readSector(byte* buf, int sector) {
int ah = 0x02; // read sector service number
int al = 0x01; // number of sectors to read
int ch = div(sector, 36); // cylinder number
int cl = mod(sector, 18) + 1; // sector number
int dh = mod(div(sector, 18), 2); // head number
int dl = 0x00; // drive number
interrupt(
0x13,
ah << 8 | al,
buf,
ch << 8 | cl,
dh << 8 | dl
);
}
-
Interrupt vector yang akan digunakan adalah
0x13
untuk melakukan operasi disk I/O. -
Register
ah
akan diisi dengan0x02
yang menunjukkan operasiread
. -
Register
al
akan diisi dengan0x01
yang menunjukkan jumlah sektor yang akan dibaca. -
Register
ch
dancl
akan diisi dengan nomor cylinder dan sector yang akan dibaca.Pada floppy disk, terdapat 2 head, 18 sector per track, dan 36 track per cylinder. Sehingga, nomor cylinder akan dihitung dengan membagi nomor sektor dengan 36. Sedangkan nomor sector akan dihitung dengan mengambil sisa pembagian nomor sektor dengan 18 dan ditambahkan dengan 1.
-
Register
dh
dandl
akan diisi dengan nomor head dan drive yang akan digunakan.Pada floppy disk, terdapat 2 head. Sehingga, nomor head akan dihitung dengan membagi nomor sektor dengan 18 dan mengambil sisa pembagian dengan 2. Sedangkan nomor drive akan diisi dengan
0x00
yang menunjukkan drive pertama.
Untuk writeSector
, kalian dapat menggunakan implementasi yang sama dengan readSector
dengan mengganti nilai register ah
dengan 0x03
yang menunjukkan operasi write
.
Pada filesystem.h
, terdapat beberapa konstanta dan tipe data yang akan digunakan untuk membantu dalam implementasi filesystem. Kalian diminta untuk mengimplementasikan fungsi fsRead
yang akan digunakan untuk membaca direktori atau file dari filesystem. Fungsi fsRead
akan menerima parameter sebagai berikut.
void fsRead(struct file_metadata* metadata, enum fs_return* status);
-
metadata
adalah pointer kefile_metadata
yang akan digunakan untuk menyimpan informasi dari file atau direktori yang akan dibaca.Struktur
file_metadata
akan memiliki struktur sebagai berikut.struct file_metadata { byte parent_index; unsigned int filesize; char node_name[MAX_FILENAME]; byte buffer[FS_MAX_SECTOR * SECTOR_SIZE]; };
parent_index
adalah index dari parent node dari file atau direktori yang akan dibaca.filesize
adalah ukuran dari file yang akan dibaca.filesize
berisi 0 dalam pemanggilan fungsifsRead
.node_name
adalah nama dari file atau direktori yang akan dibaca.buffer
adalah pointer ke buffer yang akan digunakan untuk menyimpan data dari file atau direktori yang akan dibaca.buffer
berisi0x00
dalam pemanggilan fungsifsRead
.
-
status
adalah pointer kefs_return
yang akan digunakan untuk menyimpan status dari operasi yang dilakukan.
Langkah-langkah yang harus dilakukan pada fungsi fsRead
adalah sebagai berikut.
-
Membaca filesystem dari disk ke memory.
-
Iterasi setiap item node untuk mencari node yang memiliki nama yang sesuai dengan
metadata->node_name
dan parent index sesuai denganmetadata->parent_index
. -
Jika node yang dicari tidak ditemukan, maka set
status
denganFS_R_NODE_NOT_FOUND
. -
Jika node yang ditemukan adalah direktori, maka set
status
denganFS_R_TYPE_IS_DIRECTORY
. -
Jika node yang ditemukan adalah file, maka proses selanjutnya adalah sebagai berikut.
- Set
metadata->filesize
dengan 0. - Lakukan iterasi i dari 0 hingga
FS_MAX_SECTOR
- Jika data index ke-i dari node yang ditemukan adalah
0x00
, maka hentikan iterasi. - Lakukan
readSector
untuk membaca data dari sektor yang ditunjuk oleh data pada data index dengan sectors ke-i disimpan ke dalammetadata->buffer + i * SECTOR_SIZE
. - Tambahkan
SECTOR_SIZE
kemetadata->filesize
.
- Set
-
Set
status
denganFS_R_SUCCESS
.
Selanjutnya kalian diminta untuk mengimplementasikan fungsi fsWrite
yang akan digunakan untuk menulis file ke dalam filesystem. Fungsi fsWrite
akan menerima parameter yang sama dengan fsRead
sebagai berikut.
void fsWrite(struct file_metadata* metadata, enum fs_return* status);
Pada fungsi fsWrite
, metadata
yang diterima akan berisi informasi berikut.
parent_index
adalah index dari parent node dari file yang akan ditulis. Jikaparent_index
adalah0xFF
, maka file yang akan ditulis akan disimpan pada root directory.filesize
adalah ukuran dari file yang akan ditulis. Jikafilesize
adalah 0, maka file yang akan ditulis adalah direktori.node_name
adalah nama dari file yang akan ditulis.buffer
adalah pointer ke buffer yang berisi data dari file yang akan ditulis.
Langkah-langkah yang harus dilakukan pada fungsi fsWrite
adalah sebagai berikut.
-
Membaca filesystem dari disk ke memory.
-
Lakukan iterasi setiap item node untuk mencari node yang memiliki nama yang sama dengan
metadata->node_name
dan parent index yang sama denganmetadata->parent_index
. Jika node yang dicari ditemukan, maka setstatus
denganFS_R_NODE_ALREADY_EXISTS
dan keluar. -
Selanjutnya, cari node yang kosong (nama node adalah string kosong) dan simpan index-nya. Jika node yang kosong tidak ditemukan, maka set
status
denganFS_W_NO_FREE_NODE
dan keluar. -
Iterasi setiap item data untuk mencari data yang kosong (alamat sektor data ke-0 adalah
0x00
) dan simpan index-nya. Jika data yang kosong tidak ditemukan, maka setstatus
denganFS_W_NO_FREE_DATA
dan keluar. -
Iterasi setiap item map dan hitung blok yang kosong (status blok adalah
0x00
ataufalse
). Jika blok yang kosong kurang darimetadata->filesize / SECTOR_SIZE
, maka setstatus
denganFS_W_NOT_ENOUGH_SPACE
dan keluar. -
Set nama dari node yang ditemukan dengan
metadata->node_name
, parent index denganmetadata->parent_index
, dan data index dengan index data yang kosong. -
Lakukan penulisan data dengan cara sebagai berikut.
-
Buatlah variabel counter yang akan digunakan untuk menghitung jumlah sektor yang telah ditulis (akan disebut dengan j).
-
Lakukan iterasi i dari 0 hingga
SECTOR_SIZE
. -
Jika item map pada index ke-i adalah
0x00
, maka tulis index i ke dalam data item sektor ke-j dan tulis data dari buffer ke dalam sektor ke-i. -
Penulisan dapat menggunakan fungsi
writeSector
darimetadata->buffer + i * SECTOR_SIZE
. -
Tambahkan 1 ke j.
-
-
Tulis kembali filesystem yang telah diubah ke dalam disk.
-
Set
status
denganFS_W_SUCCESS
.
Setelah berhasil mengimplementasikan fungsi fsRead
dan fsWrite
, selanjutnya adalah pembuatan shell sederhana. Shell akan menggunakan read-eval-print-loop (REPL) yang akan menerima perintah dari user dan mengeksekusi perintah tersebut. Pada task ini, kalian diminta untuk mengimplementasikan fungsi printCWD
yang akan digunakan untuk menampilkan current working directory (CWD) dari shell.
Fungsi printCWD
akan menerima parameter byte cwd
yang menunjukkan node index dari current working directory. Fungsi akan menampilkan path dari root (/
) hingga node yang ditunjuk oleh cwd
. Jika cwd
adalah 0xFF
, maka path yang ditampilkan adalah /
. Setiap node yang ditampilkan akan dipisahkan oleh karakter /
.
Selanjutnya, kalian diminta untuk mengimplementasikan fungsi parseCommand
yang akan digunakan untuk memisahkan perintah yang diberikan oleh user. Fungsi parseCommand
akan menerima parameter sebagai berikut.
void parseCommand(char* buf, char* cmd, char arg[2][64]);
buf
adalah string yang berisi perintah yang diberikan oleh user.cmd
adalah string yang akan digunakan untuk menyimpan perintah yang diberikan oleh user.arg
adalah array of string yang akan digunakan untuk menyimpan argumen dari perintah yang diberikan oleh user.
Karena hanya akan ada 2 argumen yang diberikan oleh user, maka arg
akan memiliki ukuran 2. Jika argumen yang diberikan oleh user adalah 1, maka arg[1]
akan berisi string kosong. Jika argumen yang diberikan oleh user adalah 0, maka arg[0]
dan arg[1]
akan berisi string kosong.
Fungsi cd
akan digunakan untuk mengubah current working directory dari shell. Berikut adalah spesifikasi dari fungsi cd
.
-
cd <dirname>
dapat memindahkan current working directory ke direktori yang berada di bawah current working directory. -
cd ..
akan memindahkan current working directory ke parent directory dari current working directory. -
cd /
akan memindahkan current working directory ke root directory. -
cd
hanya dapat memindahkan current working directory ke direktori, tidak dapat memindahkan current working directory ke file. -
Implementasi relative path dan absolute path tidak diwajibkan.
Fungsi ls
akan digunakan untuk menampilkan isi dari direktori. Berikut adalah spesifikasi dari fungsi ls
.
-
ls
akan menampilkan isi dari current working directory. -
ls .
akan menampilkan isi dari current working directory. -
ls <dirname>
akan menampilkan isi dari direktori yang berada di bawah current working directory. -
ls
hanya dapat menampilkan isi dari direktori, tidak dapat menampilkan isi dari file. -
Implementasi relative path dan absolute path tidak diwajibkan.
Fungsi mv
akan digunakan untuk memindahkan file atau direktori. Berikut adalah spesifikasi dari fungsi mv
.
-
mv <filename> <dirname>/<outputname>
akan memindahkan file yang berada di bawah current working directory ke direktori yang berada di bawah current working directory. -
mv <filename> /<outputname>
akan memindahkan file yang berada di bawah current working directory ke direktori root directory. -
mv <filename> ../<outputname>
akan memindahkan file yang berada di bawah current working directory ke parent directory dari current working directory. -
mv
hanya dapat memindahkan file, tidak dapat memindahkan direktori. -
Implementasi relative path dan absolute path tidak diwajibkan.
Fungsi cp
akan digunakan untuk menyalin file. Berikut adalah spesifikasi dari fungsi cp
.
-
cp <filename> <dirname>/<outputname>
akan menyalin file yang berada di bawah current working directory ke direktori yang berada di bawah current working directory. -
cp <filename> /<outputname>
akan menyalin file yang berada di bawah current working directory ke direktori root directory. -
cp <filename> ../<outputname>
akan menyalin file yang berada di bawah current working directory ke parent directory dari current working directory. -
cp
hanya dapat menyalin file, tidak dapat menyalin direktori. -
Implementasi relative path dan absolute path tidak diwajibkan.
Fungsi cat
akan digunakan untuk menampilkan isi dari file. Berikut adalah spesifikasi dari fungsi cat
.
-
cat <filename>
akan menampilkan isi dari file yang berada di bawah current working directory. -
Implementasi relative path dan absolute path tidak diwajibkan.
Fungsi mkdir
akan digunakan untuk membuat direktori. Berikut adalah spesifikasi dari fungsi mkdir
.
mkdir <dirname>
akan membuat direktori yang berada di bawah current working directory.
Untuk melakukan testing, kalian dapat menjalankan make build run
pada terminal untuk melakukan kompilasi dan menjalankan OS. Setelah itu tutup OS dan jalankan make generate test=1
untuk melakukan populasi file dan direktori ke dalam filesystem (ubah nilai 1 dengan nomor test yang sesuai). Setelah itu, jalankan kembali OS dengan make run
dan coba perintah shell yang telah kalian implementasikan.
Berikut adalah struktur filesystem yang akan digunakan pada test ini.
/
├─ dir1
│ ├─ dir1-1
│ │ └─ dir1-1-1
│ └─ dir1-2
│ └─ dirname
├─ dir2
│ └─ dirname
└─ dir3
Berikut adalah struktur filesystem yang akan digunakan pada test ini.
/
├─ file-0
├─ dir-1
│ └─ dir-2
│ └─ . . .
│ └─ dir-62
└─ file-63
Berikut adalah struktur filesystem yang akan digunakan pada test ini.
/
├─ 1024
├─ 4096
├─ 8192_0
├─ 8192_1
├─ ...
└─ 8192_13
Berikut adalah struktur filesystem yang akan digunakan pada test ini.
/
├─ dir1
│ ├─ katanya
│ ├─ dir3
│ │ ├─ bikin
│ │ ├─ fp
│ │ └─ dir4
│ ├─ dir5
│ │ ├─ cuma
│ │ └─ seminggu
├─ dir2
└─ doang
-
Untuk debugging filesystem, kalian dapat mengecek menggunakan hexedit pada Linux atau HxD pada Windows. Dengan informasi sektor map
0x100
, node0x101
dan0x102
, serta data0x103
, kalian dapat mengetahui data yang tersimpan pada filesystem. Untuk mendapatkan offset byte dari sektor, kalian dapat menggunakan rumusoffset = sektor * 512
atauoffset = sektor * 0x200
. Sebagai contoh untuk mengetahui isi dari filesystem map, dapat membuka HxD dan hexedit dengan menekanCtrl + G
dan memasukkan offset byte dari sektor map (0x100 * 0x200 = 0x20000
). -
bcc
tidak memberikan error checking sebanyakgcc
. Kalian dapat menggunakangcc
untuk melakukan error checking pada saat kompilasi. -
Dikarenakan penggunaan
bcc
dengan mode ANSI C, kalian tidak dapat mendeklarasikan variabel di tengah blok kode atau scope. Variabel harus dideklarasikan di awal blok kode atau scope. -
Selalu jalankan
make
pada direktoripraktikum-final
, bukan pada subdirektori. -
Sedikit sneak peek apa yang akan kalian buat.
Bochs.for.Windows.-.Display.2024-06-10.03-51-51.mp4