- Memahami file system pada sistem operasi
- Dapat membuat program FUSE sederhana
- Memahami langkah pembuatan sistem operasi
- i. Capaian
- ii. Daftar Isi
- 1. File System
- 2. File System in Userspace
- 3. Sistem Operasi
- 4. Pembuatan Sistem Operasi Sederhana
- 4.1. Instalasi Tools
- 4.2. Persiapan Disk Image
- 4.3. Bootloader Sederhana
- 4.4. Menjalankan Bootloader Sederhana
- 4.5. Membuat Kernel Assembly
- 4.6. Membuat Kernel C
- 4.7. Menggabungkan Kernel
- 4.8. Mengubah Bootloader
- 4.9. Menggabungkan Bootloader dan Kernel
- 4.10. Menjalankan Sistem Operasi
- 4.11. Otomatisasi Proses Kompilasi
File system adalah struktur logika yang digunakan untuk mengendalikan akses data seperti bagaimana dia disimpan maupun diambil. File system sendiri memiliki banyak jenis dengan penggunaan algoritma yang tentu berbeda. Setiap Sistem Operasi (OS) memiliki support file system yang berbeda-beda. File system digunakan untuk mengorganisir dan menyimpan file pada storage device.
File system menyediakan cara untuk memisah-misahkan data pada drive menjadi bentuk tunggal yaitu file. File system juga menyediakan cara untuk menyimpan data pada file, contohnya filename, permission, dan atribut lainnya. Pada File System, disediakan juga sebuah index yang berisi daftar file yang terletak pada suatu lokasi penyimpanan, sehingga Sistem Operasi dapat melihat ada apa saja pada lokasi penyimpanan tersebut.
1. File System Disk
File system disk adalah file system yang didesain untuk menyimpan data pada sebuah media penyimpan data. Contohnya: FAT (FAT 12, FAT 16, FAT 320), NTFS, HFS, HFS+, ext2, ext3, ext4, ISO 9660, ODS-5 dan UDF.
- FAT32 dan NTFS adalah File System di Windows.
- Ext2, Ext3, Ext4 adalah File Sytem di Linux.
- APFS, HFS dan HFS+ adalah File System dari MacOS
2. File System Flash
File system flash adalah file system yang didesain untuk menyimpan data pada media flash memory. Hal ini menjadi lazim ketika jumlah perangkat mobile semakin banyak dan kapasitas memory flash yang semakin besar. Contohnya pada linux flash filesystems yaitu JFFS, JFFS2, YAFFS, UBIFS, LogFS, F2FS.
3. File System Database
Konsep baru untuk manajemen file adalah konsep file system berbasis database. Sebagai perbaikan bagi Manajemen terstruktur hirarkis, file diidentifikasi oleh karakteristiknya, seperti tipe file, topik, pembuat, atau metadata yang sama.
4. File System Transaksional
Beberapa program terkadang membutuhkan perubahan pada beberapa file. Jika pada proses perubahan tadi mengalami kegagalan, maka file akan kembali seperti semula (tidak ada perubahan). Contohnya adalah saat menginstall sebuah software, dimana menjalankan proses writing beberapa file, jika terjadi error selama proses writing, dan software tersebut dibiarkan menjadi setengah terinstall, maka software tersebut akan rusak atau tidak stabil.
Pada File System Transaksional, tidak akan membiarkan hal tersebut terjadi. File System ini menjamin bahwa jika ada suatu proses yang error, maka proses tersebut akan dibatalkan, dan file-file yang telah terbentuk selama proses tadi akan di roll back seperti semula. Contoh dari File System ini pada UNIX adalah Valor File System, Amino, LFS dan TFFS,
5. File System Jaringan
File system jaringan adalah file system yang bertindak sebagai klien untuk protokol akses file jarak jauh, memberikan akses ke file pada sebuah server. Contoh dari file system jaringan ini adalah klien protokol NFS, AFS, SMB, dan klien FTP dan WebDAV.
6. File System Journaling
File system yang mencatat setiap perubahan yang terjadi pada storage device ke dalam jurnal (biasanya berupa log sirkular dalam area tertentu) sebelum melakukan perubahan ke file system. File sistem seperti ini memiliki kemungkinan yang lebih kecil mengalami kerusakan saat terjadi power failure atau system crash.
Virtual file system (VFS) adalah suatu lapisan perangkat lunak dalam kernel yang menyediakan interface file system untuk program user space. Virtual file system berfungsi agar berbagai jenis file system dapat diakses oleh aplikasi komputer dengan cara yang seragam. VFS menyediakan antarmuka antara system call dengan sistem yang sesungguhnya.
Dentry atau Directory Entry merupakan sebuah struktur data yang memiliki tugas sebagai penerjemah nama berkas ke inode-nya. Contoh informasi yang disimpan dalam dentry adalah name, pointer to inode, pointer to parent dentry, use count, dan lainnya. Adapula command dalam VFS dentry adalah D_compare, D_delete, D_release.
Setiap file system yang di-mount akan direpresentasikan oleh sebuah VFS Superblock. Superblock digunakan untuk menyimpan informasi mengenai partisi tersebut. Superblock menyimpan informasi sebagai berikut:
-
Device: Merupakan device identifier, contohnya /dev/hda1 adalah harddisk pertama yang terdapat pada sistem memiliki device identifier 0×300.
-
Inode Pointer: Merupakan suatu pointer yang menunjuk ke inode pertama pada sistem berkas.
-
Blocksize: Menunjukkan ukuran suatu block dari sistem berkas, contohnya 1024 bytes.
-
Superblock Operation: Merupakan suatu pointer ke sekumpulan superblock routine (fungsi) dari file system, contohnya read, write, dan sebagainya.
-
File System Type: Menunjukkan tipe dari file system, contoh: EXT2, FAT, NTFS.
-
File System Specific: Merupakan suatu pointer ke informasi yang dibutuhkan oleh _file system.
Inode adalah abstraksi VFS untuk berkas. Setiap berkas, directory, dan data lainnya pada VFS direpresentasikan oleh satu dan hanya satu VFS inode. VFS inode hanya terdapat di memori kernel dan disimpan di inode chace selama masih dibutuhkan oleh sistem. Informasi yang disimpan oleh VFS Inode diantaranya:
-
Device: Menunjukan device identifier dari suatu device yang menyimpan berkas ataupun directory.
-
Inode Number: Merupakan nomor inode yang unik dalam file system.
-
Mode: Menggambarkan apa yang direpresentasikan oleh VFS inode.
-
User ID: Merupakan identifier bagi pemilik berkas.
-
Time: Menunjukkan kapan pembuatan, modifikasi, dan penulisan suatu berkas.
-
Blocksize: Menunjukkan ukuran dari block yang digunakan oleh berkas.
-
Inode Operations: Merupakan pointer ke suatu routine yang melakukan berbagai operasi pada inode.
-
Count: Menunjukkan berapa kali suatu sistem telah menggunakan suatu inode.
-
Lock: Digunakan untuk mengunci VFS inode.
-
File System Specific Information: Menunjukkan informasi khusus yang dibutuhkan oleh suatu inode.
Berikut adalah hubungan antara dentry, superblock, dan inode pada Virtual File System.
FUSE (Filesystem in Userspace) adalah sebuah interface dimana kita dapat membuat file system sendiri pada userspace pada linux.
Keuntungan menggunakan FUSE ialah kita dapat menggunakan library apapun yang tersedia untuk membuat file system sendiri tanpa perlu mengenali secara mendalam apa yang file system sebenarnya lakukan di kernel space. Hal ini dilakukan karena modul FUSE yang dapat menjembatani antara kode file system yang berada pada userspace dengan file system yang berada pada kernel space. Beberapa manfaat yang lain dari FUSE adalah sebagai berikut:
- Dapat dimuat dan dipasang oleh pengguna biasa. Untuk akses jaringan, untuk mendapatkan file arsip, untuk removable media, dll.
- Jika driver sistem FUSE mengalami crash, tidak akan mempengaruhi kernel.
- FUSE dapat dideploy dengan cepat, baik karena tidak perlu intervensi administrator untuk menginstalnya dan karena dapat dengan mudah diakses oleh OS yang didukung.
- Tidak ada masalah lisensi terkait dengan hubungan statis dengan kernel.
Salah satu contoh yang menarik dari FUSE adalah GDFS (Google Drive File System), dimana GDFS ini memungkinkan kita untuk me-mount Google Drive kita ke sistem linux dan menggunakannya seperti file linux biasa.
Untuk mengimplementasikan FUSE ini, kita harus membuat sebuah program yang terhubung dengan library libfuse
. Tujuan dari program yang dibuat ini adalah menspesifikkan bagaimana file system merespon read/write/stat dari sebuah request dan untuk me-(mount) file system asli (kernel space) ke file system yang baru (userspace). Jadi di saat user berurusan dengan read/write/stat request di file system (userspace), kernel akan meneruskan input output request tersebut ke program FUSE dan program tersebut akan merespon kembali ke user.
Untuk lebih jelasnya mari kita coba membuat program FUSE.
Pertama-tama kita harus memstikan bahwa FUSE sudah ter-install di perangkat anda
$ sudo apt update
$ sudo apt install libfuse*
-
fuse_main()
(lib/helper.c) = sebagai fungsi main (userspace), program user memanggil fungsi fuse_main() kemudian fungsi fuse_mount() dipanggil. -
fuse_mount()
(lib/mount.c) = menciptakan UNIX domain socket, kemudian di fork dan menciptakan child process yang menjalankan fusermount -
fusermount()
(util/fusermount.c) = untuk mengecek apakah modul FUSE sudah di load. Kemudian membuka /dev/fuse dan mengirim file handle melalu UNIX domain socket kembali ke fungsi fuse_mount() -
fuse_new()
(lib/fuse.c) = menciptakan struktur data yang berisi ruang yang digukanan untuk menyimpan data file system -
fuse_loop()
(lib/fuse.c) = membaca file system calls dari /dev/fuse
Ini adalah beberapa fungsi yang disediakan oleh FUSE:
int (*getattr) (const char *, struct stat *);
//Get file attributes.
int (*readlink) (const char *, char *, size_t);
//Read the target of a symbolic link
int (*mknod) (const char *, mode_t, dev_t);
//Create a file node.
int (*mkdir) (const char *, mode_t);
//Create a directory.
int (*unlink) (const char *);
//Remove a file
int (*rmdir) (const char *);
//Remove a directory
int (*rename) (const char *, const char *);
//Rename a file
int (*chmod) (const char *, mode_t);
//Change the permission bits of a file
int (*chown) (const char *, uid_t, gid_t);
//Change the owner and group of a file
int (*truncate) (const char *, off_t);
//Change the size of a file
int (*open) (const char *, struct fuse_file_info *);
//File open operation.
int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
//Read directory
int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
//Read data from an open file
int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);
//Write data to an open file
Fuse memiliki struct
yang dinamakan fuse_operations
yang didefinisikan seperti dibawah ini:
static struct fuse_operations xmp_oper = {
.getattr = xmp_getattr,
.access = xmp_access,
.readlink = xmp_readlink,
.readdir = xmp_readdir,
.mknod = xmp_mknod,
.mkdir = xmp_mkdir,
.symlink = xmp_symlink,
.unlink = xmp_unlink,
.rmdir = xmp_rmdir,
.rename = xmp_rename,
.link = xmp_link,
.chmod = xmp_chmod,
.chown = xmp_chown,
.truncate = xmp_truncate,
.utimens = xmp_utimens,
.open = xmp_open,
.read = xmp_read,
.write = xmp_write,
.statfs = xmp_statfs,
.create = xmp_create,
.release = xmp_release,
.fsync = xmp_fsync,
.setxattr = xmp_setxattr,
.getxattr = xmp_getxattr,
.listxattr = xmp_listxattr,
.removexattr = xmp_removexattr,
};
Semua atribut pada struct
tersebut adalah pointer yang menuju ke fungsi. Setiap fungsi tersebut disebut FUSE saat suatu kejadian yang spesifik terjadi di file system. Sebagai contoh saat user menulis di sebuah file, sebuah fungsi yang ditunjuk oleh atribut "write" di struct
akan terpanggil.
Selain itu, atribut pada struct
tersebut tertulis seperti fungsi yang biasa digunakan di linux. Contohnya ialah saat kita membuat directory di FUSE maka fungsi mkdir akan dipanggil.
Untuk mengimplementasikan FUSE, kita harus menggunakan struct
ini dan harus mendefinisikan fungsi yang ada di dalam struct
tersebut. Setelahnya, kita mengisi struct
tersebut dengan pointer dari fungsi yang ingin diimplementasikan.
Kebanyakan fungsi-fungsi yang tersedia adalah opsional, kita tidak perlu mengimplementasikan semuanya. Beberapa fungsi memang harus diimplementasikan dalam file system. Fungsi-fungsi tersebut antara lain:
-
Fungsi
getattr
yang dipanggil saat sistem mencoba untuk mendapatkan atribut dari sebuah file. -
Fungsi
readdir
yang dipanggil saat user mencoba untuk menampilkan file dan direktori yang berada pada suatu direktori yang spesifik. -
Fungsi
read
yang dipanggil saat sistem mencoba untuk membaca potongan demi potongan data dari suatu file.
Untuk melihat fungsi-fungsi yang tersedia pada FUSE yang lain, buka link berikut: https://libfuse.github.io/doxygen/structfuse__operations.html
Contoh program FUSE sederhana yang hanya menggunakan 3 fungsi tersebut.
#define FUSE_USE_VERSION 28
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
static int xmp_getattr(const char *path, struct stat *stbuf)
{
int res;
res = lstat(path, stbuf);
if (res == -1) return -errno;
return 0;
}
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
DIR *dp;
struct dirent *de;
(void) offset;
(void) fi;
dp = opendir(path);
if (dp == NULL) return -errno;
while ((de = readdir(dp)) != NULL) {
struct stat st;
memset(&st, 0, sizeof(st));
st.st_ino = de->d_ino;
st.st_mode = de->d_type << 12;
if(filler(buf, de->d_name, &st, 0)) break;
}
closedir(dp);
return 0;
}
static int xmp_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
int fd;
int res;
(void) fi;
fd = open(path, O_RDONLY);
if (fd == -1) return -errno;
res = pread(fd, buf, size, offset);
if (res == -1) res = -errno;
close(fd);
return res;
}
static struct fuse_operations xmp_oper = {
.getattr = xmp_getattr,
.readdir = xmp_readdir,
.read = xmp_read,
};
int main(int argc, char *argv[])
{
umask(0);
return fuse_main(argc, argv, &xmp_oper, NULL);
}
Setelah itu kode dapat di-compile dengan cara
gcc -Wall `pkg-config fuse --cflags` [fuse.c] -o [output] `pkg-config fuse --libs`
Lalu buat sebuah direktori sebagai tujuan pembuatan FUSE dan menjalankan FUSE pada direktori tersebut.
$ mkdir [direktori tujuan]
$ ./[output] [direktori tujuan]
Setelah program dijalankan, masuklah kedalam direktori tujuan tersebut. Isi dari direktori tersebut adalah list folder yang sama seperti yang ada di dalam root
atau /
.
Unmount command digunakan untuk "unmount" sebuah filesystem yang telah ter-mount, lalu juga menginformasikan ke sistem untuk menyelesaikan semua operasi read dan write yang masih tertunda agar bisa di-detach (dilepaskan) dengan aman.
Untuk melakukan unmount FUSE, jalankan command di bawah ini:
sudo umount [direktori tujuan]
atau
fusermount -u [direktori tujuan]
- Debugging Fuse
Salah satu cara debugging yang bisa dilakukan saat memprogram fuse adalah dengan menggunakan printf
dan menjalankan program dengan cara ./[output] -f [direktori tujuan]
. Dimana -f
disini berarti menjaga program agar tetap berjalan di foreground sehingga bisa menggunakan printf
.
- Modifying Parameters value
Sesuai dengan penjelasan di awal di mana FUSE dapat memodifikasi file system di userspace tanpa perlu mengubah kode yang ada pada kernel, di sini kita coba memodifikasi kode FUSE tadi agar folder yang di mount hanya berisi /home/[user]/Documents
sebagai root folder.
Ubah kode FUSE tadi seperti yang ada dibawah ini:
#define FUSE_USE_VERSION 28
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
static const char *dirpath = "/home/[user]/Documents";
static int xmp_getattr(const char *path, struct stat *stbuf)
{
int res;
char fpath[1000];
sprintf(fpath,"%s%s",dirpath,path);
res = lstat(fpath, stbuf);
if (res == -1) return -errno;
return 0;
}
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
char fpath[1000];
if(strcmp(path,"/") == 0)
{
path=dirpath;
sprintf(fpath,"%s",path);
} else sprintf(fpath, "%s%s",dirpath,path);
int res = 0;
DIR *dp;
struct dirent *de;
(void) offset;
(void) fi;
dp = opendir(fpath);
if (dp == NULL) return -errno;
while ((de = readdir(dp)) != NULL) {
struct stat st;
memset(&st, 0, sizeof(st));
st.st_ino = de->d_ino;
st.st_mode = de->d_type << 12;
res = (filler(buf, de->d_name, &st, 0));
if(res!=0) break;
}
closedir(dp);
return 0;
}
static int xmp_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
char fpath[1000];
if(strcmp(path,"/") == 0)
{
path=dirpath;
sprintf(fpath,"%s",path);
}
else sprintf(fpath, "%s%s",dirpath,path);
int res = 0;
int fd = 0 ;
(void) fi;
fd = open(fpath, O_RDONLY);
if (fd == -1) return -errno;
res = pread(fd, buf, size, offset);
if (res == -1) res = -errno;
close(fd);
return res;
}
static struct fuse_operations xmp_oper = {
.getattr = xmp_getattr,
.readdir = xmp_readdir,
.read = xmp_read,
};
int main(int argc, char *argv[])
{
umask(0);
return fuse_main(argc, argv, &xmp_oper, NULL);
}
Hal ini terjadi karena parameter const char *path
merupakan pointer variable yang menunjuk ke value path yang digunakan user, sehingga ketika kita mengubah value dari path, maka posisi user akan berubah juga.
- PERINGATAN
Berbeda dengan Docker container, file yang berada di dalam folder mount fuse merupakan file yang sama pada filesystem host, jika kita mengubah/menghapus/menambahkan file/folder pada fuse hal itu juga terjadi pada host.
Sistem operasi adalah satu kesatuan perangkat yang melakukan kontrol pada perangkat komputer dan sumber daya yang dimilikinya. Komponen dari sistem operasi dapat diilustrasikan pada diagram berikut.
- Hardware: Merupakan perangkat keras yang digunakan oleh sistem operasi untuk menjalankan program.
- Kernel: Merupakan inti dari sistem operasi yang mengatur sumber daya dan menjalankan program.
- Shell: Merupakan antarmuka yang digunakan oleh pengguna untuk berinteraksi dengan sistem operasi.
- Application: Merupakan program yang dijalankan oleh sistem operasi melalui shell yang terhubung dengan kernel.
Kernel adalah bagian dari sistem operasi yang tidak terlihat secara langsung, baik dalam perangkat keras maupun perangkat lunak komputer. Kernel menangani event yang datang dari perangkat keras (interrupts) dan dari perangkat lunak (system calls), serta mengatur akses ke sumber daya komputer.
Contoh manajemen event dari perangkat keras (interrupt handlers) adalah penekanan tombol pada keyboard yang kemudian akan diterjemahkan ke dalam simbol karakter bersesuaian pada buffer input. Kemudian program akan dapat mengambil karakter tersebut dari buffer input.
Sedangkan system calls akan diakses oleh program, seperti membuka file, menjalankan program lain, dan sebagainya. Setiap system call handler akan melakukan pengecekan kevalidan argumen yang diberikan dan kemudian melanjutkan perintah yang diberikan.
Program pada umumnya tidak akan langsung memanggil system calls secara langsung, melainkan menggunakan standard library untuk pemanggilannya. Standard library tersebut yang nantinya akan menerjemahkan fungsi yang telah diberikan menjadi bentuk system calls yang sesuai dengan apa yang dibutuhkan oleh kernel. Contohnya adalah fungsi fopen()
pada bahasa C yang akan melakukan pemanggilan system calls untuk membuka file.
Kernel bertugas untuk mengabstraksi perintah berkaitan seperti file, proses, socket, direktori, memory, dan lainnya.
Shell merupakan program yang biasanya terintegrasi dengan distribusi dari sistem operasi. Shell berguna sebagai antarmuka antara pengguna dengan sistem operasi. Bentuk dari shell dapat berbeda dari satu sistem operasi ke sistem operasi lainnya. Shell dapat berupa command line interface (CLI) atau graphical user interface (GUI).
Konsep utama dari shell adalah sebagai berikut.
- User dapat membuka program untuk dijalankan dan dapat juga memberikan argumen pada program tersebut.
- Mengizinkan user untuk melakukan operasi pada penyimpanan lokal, seperti melakukan copy, move, delete, dan lainnya.
Untuk melakukan tugas tersebut, shell akan memanggil beberapa system calls yang akan dilanjutkan pada kernel.
Proses booting sistem operasi adalah proses dimana sistem operasi dijalankan dari keadaan mati hingga siap digunakan oleh pengguna. Secara sederhana, berikut adalah proses booting sistem operasi.
BIOS terdiri dari beberapa intruksi yang ditulis dalam bahasa pemrograman yang sangat low level. BIOS adalah program pertama yang diinstall pada komputer, bahkan sebelum sistem operasi. Ketika komputer dinyalakan, CPU akan memerintahkan BIOS untuk melakukan pengecekan pada perangkat keras dasar komputer apakah telah siap untuk digunakan atau tidak. Proses ini dinamakan Power of Self Test (POST). Beberapa komponen yang dicek pada POST adalah sebagai berikut.
- Perangkat keras, seperti prosesor, penyimpanan, dan juga memory.
- Perangkat input/output, seperti keyboard, mouse, dan monitor.
- Register dari CPU.
- Interrupt Controller
Setelah POST selesai dijalankan, akan dilanjutkan dengan proses booting sistem operasi. Proses booting dari storage dengan memuat sektor pertama ke memory disebut dengan Master Boot Record (MBR).
MBR adalah sektor pertama dari sistem penyimpanan (disk/storage). Tempat fisik dari MBR terdapat pada cylinder 0, head 0, dan sector 1. MBR menyimpan informasi tabel partisi dan juga bootloader. Bootloader adalah program yang bertugas untuk memuat sistem operasi ke dalam memory. Ukuran dari MBR adalah 512 bytes. Pada 466 bytes pertama berisi bootloader, 64 bytes berikutnya berisi tabel partisi yang terdiri dari 4 partisi masing-masing 16 bytes. Kemudian pada 2 bytes terakhir, MBR berisi magic number (0xAA55
). Magic number ini digunakan untuk menandakan bahwa MBR tersebut valid.
Tugas dari MBR adalah mencari bootloader kedua pada tabel partisi. Setelah menemukan bootloader kedua, MBR akan memuat bootloader kedua tersebut ke dalam memory dan akan dieksekusi.
Bootloader kedua yang dimuat oleh MBR adalah kernel loader. Kernel loader bertugas untuk memuat kernel dari sistem operasi ke dalam memory. Kernel loader akan memuat kernel ke dalam memory dan akan menjalankan kernel tersebut.
Pada linux, kernel loader yang digunakan adalah GRUB (Grand Unified Bootloader). GRUB adalah bootloader yang bersifat open source dan dapat digunakan pada berbagai sistem operasi. GRUB memiliki beberapa fitur yang berguna, seperti multiboot, multiboot2, dan chainloading.
Hal tersebut dilakukan karena pada kernel loader modern seperti GRUB, tidak cukup jika dimasukkan kedalam 466 bytes di MBR. Sehingga MBR perlu mencari bootloader kedua yang akan memuat kernel ke dalam memory.
Berikut adalah ilustrasi dari proses booting sistem operasi linux.
Interrupts adalah mekanisme yang digunakan oleh CPU untuk memberitahu bahwa ada event yang terjadi. Interrupts digunakan untuk menghentikan eksekusi program yang sedang berjalan dan menjalankan interrupt handler yang sesuai dengan event yang terjadi.
Interrupt handler atau Interrupt Service Routine (ISR) dapat dianalogikan dengan event listener pada event-driven programming yang umumnya digunakan pada bahasa pemrograman high-level seperti javascript. ISR adalah event handler untuk interrupts yang terjadi. Setiap interrupt memiliki kode terkait yang menandakan event yang terjadi. Kode ini disebut dengan interrupt vector. Instruksi untuk interrupt pada x86 adalah int n
dimana n
adalah vector dari interrupt. Vector interrupt dapat dituliskan dalam notasi heksadesimal (0x00 - 0xFF) atau notasi vector (00h - FFh).
Penjelasan lebih lanjut mengenai interrupts dapat dilihat di sini.
Impelementasi interrupts yang akan digunakan pada praktikum adalah service int 21h
yang digunakan untuk melakukan interrupt pada kernel DOS. Dengan melakukan pemanggilan int 21h
menggunakan parameter tertentu (seperti AH dan AL), kita dapat melakukan berbagai akses input output pada sistem operasi DOS.
Untuk membuat sistem operasi sederhana, kita memerlukan beberapa tools yang akan digunakan. Berikut adalah tools yang diperlukan.
-
Bochs: Bochs adalah emulator x86 yang digunakan untuk menjalankan sistem operasi yang dibuat. Bochs dapat dijalankan pada berbagai sistem operasi, seperti Windows, MacOS, dan Linux.
Bochs dapat diunduh pada web resmi bochs atau pada rilis github berikut. Sesuaikan dengan sistem operasi yang kalian gunakan. Sangat disarankan mengunduh bochs versi terbaru melalui website, bukan menggunakan apt.
-
NASM: NASM adalah assembler yang digunakan untuk mengubah kode assembly menjadi kode biner. NASM dapat diunduh pada web resmi NASM atau dapat menggunakan perintah berikut.
sudo apt install nasm
-
BCC: BCC adalah compiler C yang digunakan untuk melakukan kompilasi kode C menjadi 16 bit (GCC tidak dapat digunakan karena GCC menghasilkan kode 32 bit). Dokumentasi BCC dapat diakses di sini dan dapat diunduh menggunakan perintah berikut.
sudo apt install bcc
-
ld86: ld86 adalah linker yang digunakan untuk menggabungkan object kode dari kode assembly dan kode C menjadi sebuah executable.
sudo apt install bin86
-
make: Make adalah build automation tool yang digunakan untuk mempermudah proses kompilasi kode. Make dapat diunduh menggunakan perintah berikut.
sudo apt install make
Sistem operasi akan disimpan pada sebuah disk image. Disk image adalah file yang berisi data yang akan digunakan oleh sistem operasi. Disk image akan digunakan oleh emulator untuk menyimpan sistem operasi yang akan dijalankan. Kali ini, akan digunakan disk image dengan ukuran 1.44 MB (ukuran floppy disk). Disk image dapat dibuat menggunakan perintah berikut.
dd if=/dev/zero of=floppy.img bs=512 count=2880
if=/dev/zero
(input file = /dev/zero)
: Membaca dari/dev/zero
yang berisi byte 0.of=floppy.img
(output file = floppy.img)
: Menulis kefloppy.img
.bs=512
(block size = 512)
: Mengatur ukuran blok sebesar 512 byte.count=2880
: Mengatur jumlah blok sebanyak 2880 blok (ukuran floppy disk).
Sebelum masuk ke pembuatan bootloader yang memanggil kernel, kita akan membuat bootloader sederhana yang hanya menampilkan layar hitam. Berikut adalah kode bootloader sederhana.
; simple-bootloader.asm
bits 16
times 510 - ($-$$) db 0x00
dw 0xAA55
-
bits 16
: Menandakan bahwa kode ini adalah kode 16 bit. -
times 510 - ($-$$) db 0x00
: Mengisi bagian yang kosong pada MBR dengan 0x00510 bytes dikurangi dengan ukuran kode
($-$$)
diisi dengan 0x00 -
dw 0xAA55
: Menandakan bahwa MBR yang dibuat adalah valid.2 bytes terakhir diisi dengan 0xAA55
Setelah itu, kode di atas dapat dicompile menggunakan perintah berikut.
nasm -f bin simple-bootloader.asm -o simple-bootloader.bin
-f bin
(format binary)
: Mengubah kode assembly menjadi kode biner.
Selanjutnya, kode binary yang dihasilkan akan digabungkan dengan disk image yang telah dibuat sebelumnya menggunakan perintah berikut.
dd if=simple-bootloader.bin of=floppy.img bs=512 count=1 conv=notrunc
count=1
: Menulis sebanyak 1 blok (sesuai ukuran MBR 512 bytes).conv=notrunc
(convert = notruncate)
: Menulis kefloppy.img
tanpa menghapus data yang sudah ada.
Setelah membuat bootloader sederhana, kita akan menjalankan bootloader tersebut menggunakan emulator Bochs. Dapat digunakan konfigurasi pada playground bochsrc.txt
. Kemudian dapat dijalankan menggunakan perintah berikut.
bochs -f bochsrc.txt
atau jika sedang berada pada direktori playground, dapat menggunakan perintah berikut.
bochs
Setelah dijalankan akan muncul tampilan seperti berikut.
Setelah membuat bootloader, kita akan membuat kernel yang nanti akan dipanggil oleh bootloader. Berikut adalah kode kernel.
; kernel.asm
global _putInMemory
; void putInMemory(int segment, int address, char character)
_putInMemory:
push bp
mov bp,sp
push ds
mov ax,[bp+4]
mov si,[bp+6]
mov cl,[bp+8]
mov ds,ax
mov [si],cl
pop ds
pop bp
ret
-
global _putInMemory
: Mendeklarasikan fungsi_putInMemory
sebagai fungsi global yang nantinya akan bisa dipanggil melalui kode C. -
_putInMemory
: Implementasi fungsi_putInMemory
.Inti dari fungsi ini adalah untuk memasukkan karakter ke dalam memory. Fungsi ini akan menerima 3 argumen, yaitu segment, address, dan character. Segment dan address digunakan untuk menentukan alamat memory yang akan diisi dengan karakter yang diberikan.
Fungsi ini dibuat karena alamat memory yang akan diisi dapat melebihi 16 bit. Sedangkan register pada x86 hanya memiliki 16 bit. Sehingga kita memerlukan segment dan address untuk menentukan alamat memory yang akan diisi.
Setelah itu, kode di atas dapat dicompile menggunakan perintah berikut.
nasm -f as86 kernel.asm -o kernel-asm.o
-f as86
(format as86)
: Mengubah kode assembly menjadi kode objek.
Setelah membuat kernel assembly, kita akan membuat kernel C yang akan memanggil fungsi _putInMemory
yang telah dibuat sebelumnya. Berikut adalah kode kernel C.
// kernel.c
void main() {
char* str = "Halo";
int i = 0;
for (i = 0; i < 4; i++) {
char warna = 0x5;
putInMemory(0xB000, 0x8000 + i * 2, str[i]);
putInMemory(0xB000, 0x8001 + i * 2, warna);
}
while (1);
}
-
void main()
: Fungsi utama dari kernel. -
0xB8000
adalah alamat memory untuk video memory pada VGA. Alamat ini digunakan untuk menampilkan karakter pada kiri atas layar pada posisi(0, 0)
. -
Karena
0xB8000
tidak dapat ditampung dalam 16 bit (2 bytes), maka kita membagi alamat tersebut menjadi 2 bagian, yaitu segment dan address. Segment adalah0xB000
dan address adalah0x8000
. Operasi yang akan dilakukan fungsi_putInMemory
pada assembly adalah menggabungkan kedua alamat tersebut menjadi alamat memory yang valid. -
Offset untuk karakter adalah
0x8000 + i * 2
dan offset untuk warna adalah0x8001 + i * 2
. Offset ini digunakan untuk menampilkan karakter dan warna pada layar. -
Pada umumnya, offset karakter pada
(x,y)
adalah0x8000 + (y * 80 + x) * 2
. -
Sedangkan, offset warna pada
(x,y)
adalah0x8001 + (y * 80 + x) * 2
. -
while (1)
: Infinite loop agar program tidak berhenti setelah menampilkan karakter.
Setelah itu, kode di atas dapat dicompile menggunakan perintah berikut.
bcc -ansi -c kernel.c -o kernel.o
-ansi
: Menggunakan standar ANSI C.-c
: Menghasilkan object file.-o kernel.o
: Menyimpan hasil kompilasi ke dalam filekernel.o
.
Setelah membuat kernel assembly dan kernel C, kita akan menggabungkan kedua object file tersebut menjadi sebuah executable. Berikut adalah perintah yang digunakan.
ld86 -o kernel.bin -d kernel.o kernel-asm.o
-
-o kernel
: Menyimpan hasil linking ke dalam filekernel
. -
-d
: Menghapus header dari output file yang mengharuskan urutan deklarasi fungsi harus urut. -
kernel.o kernel-asm.o
: Object file yang akan digabungkan.
Setelah membuat kernel, kita akan mengubah bootloader yang telah dibuat sebelumnya agar dapat memanggil kernel yang telah dibuat. Berikut adalah kode bootloader yang telah diubah.
; bootloader.asm
bits 16
KERNEL_SEGMENT equ 0x1000 ; kernel will be loaded at 0x1000:0x0000
KERNEL_SECTORS equ 15 ; kernel will be loaded in 15 sectors maximum
KERNEL_START equ 1 ; kernel will be loaded in sector 1
; bootloader code
bootloader:
; load kernel to memory
mov ax, KERNEL_SEGMENT ; load address of kernel
mov es, ax ; buffer address are in ES:BX
mov bx, 0x0000 ; set buffer address to KERNEL_SEGMENT:0x0000
mov ah, 0x02 ; read disk sectors
mov al, KERNEL_SECTORS ; number of sectors to read
mov ch, 0x00 ; cylinder number
mov cl, KERNEL_START + 1 ; sector number
mov dh, 0x00 ; head number
mov dl, 0x00 ; read from drive A
int 0x13 ; call BIOS interrupts
; set up segment registers
mov ax, KERNEL_SEGMENT
mov ds, ax
mov es, ax
mov ss, ax
; set up stack pointer
mov ax, 0xFFF0
mov sp, ax
mov bp, ax
; jump to kernel
jmp KERNEL_SEGMENT:0x0000
; padding to make bootloader 512 bytes
times 510-($-$$) db 0
dw 0xAA55
Kode assembly di atas akan memuat kernel ke dalam memory dan akan menjalankan kernel tersebut. Kernel akan dijalankan pada alamat memory 0x1000:0x0000
. Setelah itu, kode di atas dapat dicompile menggunakan perintah berikut.
nasm -f bin bootloader.asm -o bootloader.bin
Setelah membuat bootloader dan kernel, kita akan menggabungkan kedua binary tersebut menjadi sebuah disk image. Berikut adalah perintah yang digunakan.
dd if=/dev/zero of=floppy.img bs=512 count=2880
dd if=bootloader.bin of=floppy.img bs=512 count=1 conv=notrunc
dd if=kernel.bin of=floppy.img bs=512 seek=1 count=15 conv=notrunc
seek=1
: Menulis pada blok ke-1 (setelah MBR).
Setelah membuat disk image yang berisi bootloader dan kernel, kita akan menjalankan sistem operasi sederhana yang telah dibuat menggunakan emulator Bochs. Dapat digunakan konfigurasi pada playground bochsrc.txt
. Kemudian dapat dijalankan menggunakan perintah berikut.
bochs -f bochsrc.txt
atau jika sedang berada pada direktori playground, dapat menggunakan perintah berikut.
bochs
Setelah dijalankan akan muncul tampilan seperti berikut.
Dapat dilihat bahwa terdapat tulisan Halo
pada layar. Tulisan tersebut berasal dari kode kernel yang telah dibuat sebelumnya.
Untuk mempermudah proses kompilasi, kita dapat menggunakan build automation tool seperti make
. Berikut adalah contoh Makefile
yang dapat digunakan.
prepare:
dd if=/dev/zero of=floppy.img bs=512 count=2880
bootloader:
nasm -f bin bootloader.asm -o bootloader.bin
dd if=bootloader.bin of=floppy.img bs=512 count=1 conv=notrunc
kernel:
nasm -f as86 kernel.asm -o kernel-asm.o
bcc -ansi -c kernel.c -o kernel.o
ld86 -o kernel.bin -d kernel.o kernel-asm.o
dd if=kernel.bin of=floppy.img bs=512 seek=1 conv=notrunc
build: prepare bootloader kernel
Sehingga untuk melakukan proses kompilasi, kita hanya perlu menjalankan perintah berikut.
make build
- Clemong adalah orang yang suka membalikkan
faktakata sehingga ia ingin memiliki file system bernama ClemOS yang membalikkan nama-nama file yang ada dalam direktori /home/[user]/Documents. Namun file system tersebut hanya membalikkan nama file saja (tidak termasuk extension/format file) jika berada di dalam folder Clem_[nama folder]. Untuk memudahkannya, anggap bahwa semua string setelah titik terakhir adalah extension. Perhatikan contoh agar lebih jelas:
/
│
└───Clem_SisopEZ
│ │ Cheatsheet.sisop.zip
| | Daftar_Gebetan.xlsx
│ │ index.html
│ └───modul 4 ez
│ | bocoransoal.pdf
|
└───Probstat
│ Praktikum.r.zip
│ kata-penyemangat.txt
Menjadi:
/
│
└───Clem_SisopEZ
│ │ posis.teehstaehC.zip
| | natebeG_ratfaD.xlsx
│ │ xedni.html
│ └───ze 4 ludom
│ | laosnarocob.pdf
|
└───Probstat
│ Praktikum.r.zip
│ kata-penyemangat.txt
Catatan:
- Root pada ClemOS adalah folder /home/[user]/Documents, sehingga yang ditampillkan adalah semua file dan folder di folder Documents.
- Nama file yang dibalik hanya jika terdapat di dalam folder Clem_[nama folder], berapapun kedalamannya.
- Cobalah buat folder Clem_[nama folder] untuk menguji.
- Buat sebuah file system yang mengarah ke /home/[user]/Downloads. File system ini memiliki fitur pencatatan, dimana ketika folder pada file system tersebut diakses, pengaksesan tersebut akan ditulis pada file log.log dengan format
"%.3s %.3s%3d %.2d:%.2d:%.2d %d: %s\n", hari, bulan, tanggal, jam, menit, detik, tahun, folder
. Misalnya:Mon Apr 11 20:12:47 2022: /sample
.
Catatan:
- Gunakan dan modifikasi fungsi
ctime
agar sesuai dengan format. - Bisa digabung dengan latihan soal nomor 1 (mengakses folder Documents/Downloads saja).
- Folder yang dicatat pada log adalah path dari file system.
- https://www.cs.hmc.edu/~geoff/classes/hmc.cs135.201109/homework/fuse/fuse_doc.html
- http://www.maastaar.net/fuse/linux/filesystem/c/2016/05/21/writing-a-simple-filesystem-using-fuse/
- https://github.com/asayler/CU-CS3753-PA5
- http://amazingharry.blogspot.co.id/2015/01/filesystem-in-userspace-fuse-in-linux_5.html
- https://docs.oracle.com/database/121/ADLOB/adlob_fs.htm#ADLOB45989
- http://www.fieldses.org/~bfields/kernel/vfs.txt
- https://developer.ibm.com/technologies/linux/tutorials/l-virtual-filesystem-switch/
- https://wiki.osdev.org/Introduction
- https://medium.com/@ibshafique/the-linux-boot-process-180fb07af452
- https://en.wikipedia.org/wiki/BIOS_interrupt_call
- https://en.wikipedia.org/wiki/DOS_API
- https://wiki.osdev.org/Text_mode