Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 30 additions & 26 deletions 08_VirtualFileSystem/02_VirtualFileSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ As we anticipated earlier in this chapter we are going to use a linked list to k
So we assume that functions to handle list of mountpoints are present (their implementation is left as exercise), from now on we assume the following functions are present:

```c
mountpoint_t *create_mountpoint(...)
mountpoint_t *create_mountpoint(...);
mountpoint_t *add_mountpoint(mountpoint_t* mountpoint);
void remove_mountpoint(mountpoint_t* mountpoint);
```
Expand All @@ -77,16 +77,15 @@ Let's call this new structure `mountpoint_t` and start to fill in some fields:
```c
#define VFS_TYPE_LENGTH 32
#define VFS_PATH_LENGTH 64
struct {
typedef struct {

char type[VFS_TYPE_LENGTH];
char mountpoint[VFS_TYPE_LENGTH];
char device[VFS_PATH_LENGTH];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although in theory the mountpoint_t structure should point somehow to the device, we are not covering it in the book yet (and is not even mentioend) so for the purpose of the chapter is not really needed. And also in case we add it in the future, I don't think it should be a char * but probably a a new kerneld data structure, containing the device information. So for now is not really necessary to add it.

Copy link
Contributor Author

@oito8bits oito8bits Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this because there is a part where the device is initialized in a mountpoint_t:

mountpoint_t *new_mountpoint = malloc(sizeof(mountpoint_t)); // We assume a kind of malloc is present
new_mountpoint.device = device;
new_mountpoint.type = type;
new_mountpoint.mountpoint = target;
new_mountpoint.operations = NULL

Copy link
Contributor Author

@oito8bits oito8bits Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed it was a char *, since the device from vfs_mount() is also one:

int vfs_mount(char *device, char *target, char *fs_type);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahaha you are right!
I forgot about it, and I probably should have checked.

I kind of understand maybe why I did it, but I think I overlooked at that part. Probably was more to explain the idea behind the function, so It was kept as a char. Probably this part should be improved because what I think will most likely happen under the hood is that using the string device, the vfs_mount function will search for the device that has that name (or mountpoint or whatever it is) and return a pointer to it. And that should be the one stored into the new_mountpoint struct.

@oito8bits if you want you can try to fix this part (of course feele free to ask here or on discord if you have any question) , otherwise if you don't have time, or not sure how to improve it, let me know and I'll merge this PR as it is and hopefully I'll fix it myself (or @DeanoBurrito ) in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t implemented this in my kernel yet, so I can’t say for sure how this structure will look. You can go ahead and merge it, and maybe in the future I’ll add it if you guys haven’t done it already.

char mountpoint[VFS_PATH_LENGTH];

fs_operations_t operations;

} mountpoint_t;

typedef struct mountpoint_t mountpoint_t;
```

The next thing is to have a variable to store those mountpoints, since we are using a linked list it is going to be just a pointer to its root, this will be the first place where we will look whenever we want to access a folder or a file:
Expand Down Expand Up @@ -114,7 +113,7 @@ There can be others of course configuration parameters like access permission, d
int ustar_open(char *path, int flags);
int ustar_close(int ustar_fd);
void ustar_read(int ustar_fd, void *buffer, size_t count);
int ustar_close(int ustar_fd)
int ustar_close(int ustar_fd);
```

For the mount and umount operation we will need two functions:
Expand All @@ -129,10 +128,12 @@ int vfs_mount(char *device, char *target, char *fs_type);
Once called it will simply create and add a new item to the mountpoints list, and will populate the data structure with the information provided, for example inside the function to create a mountpoint (in our example `create_mountpoint`) we are going to have something like the following code:
```c
mountpoint_t *new_mountpoint = malloc(sizeof(mountpoint_t)); // We assume a kind of malloc is present
new_mountpoint.device = device;
new_mountpoint.type = type;
new_mountpoint.mountpoint = target;
new_mountpoint.operations = NULL
if(new_mountpoint == NULL)
return -1;
strcpy(new_mountpoint->device, device);
strcpy(new_mountpoint->type, type);
strcpy(new_mountpoint->mountpoint, target);
new_mountpoint->operations = NULL;
```

the last line will be populated soon, for now let's leave it to `NULL`.
Expand Down Expand Up @@ -219,13 +220,16 @@ After having implemented all the functions to load file systems and identify giv
Let's quickly recap on how we usually read a file in C:

```C
int fd;
char *buffer = (char *) calloc(11, sizeof(char));
int file_descriptor;
char *buffer = calloc(11, sizeof(char));
if (buffer == NULL) {
return -1;
}
int file_descriptor = open("/path/to/file/to_open", O_RDONLY);
int sz = read(file_descriptor, buffer, 10)
int sz = read(file_descriptor, buffer, 10);
buffer[sz] = '\0';
printf("%s", buffer);
close(file_pointer);
close(file_descriptor);
```

The code snippet above is using the C stdlib file handling libraries, what the it does is:
Expand All @@ -234,7 +238,7 @@ The code snippet above is using the C stdlib file handling libraries, what the i
* If the file is found and the fd value is not -1, than we can read it
* It now reads 10 bytes from the opened file (the `read` function will access it via the fd field), and store the output in the buffer. The read function returns the number of bytes read.
* If we want to print the string read we need to append the EndOfLine symbol after the last byte read.
* Now we can close the file_pointer (destroying the file descriptor associated with the id if it is possible, otherwise -1 will be returned).
* Now we can close the file_descriptor (destroying the file descriptor associated with the id if it is possible, otherwise -1 will be returned).

As we can see there are no instructions where we specify the file system type, or the driver to use this is all managed by the vfs layer. The above functions will avail of kernel system calls open/read/close, they usually sits somewhere above the kernel VFS layer, in our _naive_ implementation they we are not going to create new system calls, and let them to be our VFS layer, and where needed make a simpler version of them.

Expand Down Expand Up @@ -265,21 +269,21 @@ The flags value is a bitwise operator, and there are other possible values to be
The return value of the function is the file descriptor id. We have already seen how to parse a path and get the mountpoint id if it is available. But what about the file descriptor and its id? What is it? File descriptors represents a file that has been opened by the VFS, and contain information on how to access it (i.e. mountpoint_id), the filename, the various pointers to keep track of current read/write positions, eventual locks, etc. So before proceed let's outline a very simple file descriptor struct:

```c
struct {
typedef struct {
uint64_t fs_file_id;
int mountpoint_id;
char *filename;
int buf_read_pos;
int buf_write_pos;
int file_size;
char *file_buffer;
} file_descriptor_t
} file_descriptor_t;
```

We need to declare a variable that contains the opened file descriptors, as usual we are using a naive approach, and just use an array for simplicity, this means that we will have a limited number of files that can be opened:

```c
struct file_descriptors_t vfs_opened_files[MAX_OPENED_FILES]
struct file_descriptors_t vfs_opened_files[MAX_OPENED_FILES];
```

Where the `mountpoint_id` fields is the id of the mounted file system that is containing the requested file. The `fs_file_id` is the fs specific id of the fs opened by the file descriptor, `buf_read_pos` and `buf_write_pos` are the current positions of the buffer pointer for the read and write operations and `file_size` is the size of the opened file.
Expand Down Expand Up @@ -308,16 +312,16 @@ The basic idea is that once mountpoint_id has been found, the vfs will use the m
int open(const char *path, int flags){
mountpoint_t *mountpoint = get_mountpoint(pathname);

if (mountpoint != NULL`) {
if (mountpoint != NULL) {
char *rel_path = get_rel_path(mountpoint, path);
int fs_specific_id = mountpoint.operations.open(rel_path, flags);
int fs_specific_id = mountpoint->operations.open(rel_path, flags);
if (fs_specific_id != ERROR) {
/* IMPLEMENTATION LEFT AS EXERCISE */
// Get a new vfs descriptor id vfs_id
vfs_opened_files[vfs_id] = //fill the file descriptor entry at position
}
}
return vfs_id
return vfs_id;
}
```

Expand All @@ -340,7 +344,7 @@ int close(int fildes) {
mountpoint_id = vfs_opened_files[fildes].mountpoint_id;
mountpoint_t *mountpoint = get_mountpoint_by_id(mountpoint_id);
fs_file_id = vfs_opened_files[fildes].fs_file_id;
fs_close_result = mountpoint.close(fs_file_id);
fs_close_result = mountpoint->operations.close(fs_file_id);
if(fs_close_result == 0) {
vfs_opened_files[fildes].fs_file_id = -1;
return 0;
Expand Down Expand Up @@ -381,9 +385,9 @@ Text example of a file...
And we have the following code:

```c
char *buffer[5]
int sz = read(file_descriptor, buffer, 5)
sz = read(file_descriptor, buffer, 5)
char *buffer[5];
int sz = read(file_descriptor, buffer, 5);
sz = read(file_descriptor, buffer, 5);
```

The `buffer` content of the first read will be: `Text `, and the second one `examp`. This is the purpose `buf_read_pos` variable in the file descriptor, so it basically needs to be incremented of nbytes, of course only if `buf_read_pos + nbytes < file_size` .
Expand All @@ -393,7 +397,7 @@ The pseudocode for this function is going to be similar to the open/close:
ssize_t read(int fildes, void *buf, size_t nbytes) {
if (vfs_opened_files[fildes].fs_fildes_id != -1) {
int mountpoint_id = vfs_opened_files[fildes].mountpoint_id;
mountpoint_t *mountpoint = get_mountpoint_by_id(mountpoint_id)
mountpoint_t *mountpoint = get_mountpoint_by_id(mountpoint_id);
int fs_file_id = vfs_opened_files[fildes].fs_file_id;
int bytes_read = mountpoints.read(fs_file_id, buf, nbytes)
if (opened_files[fildes].buf_read_pos + nbytes < opened_files[fildes].file_size) {
Expand Down
1 change: 1 addition & 0 deletions 99_Appendices/I_Acknowledgments.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ In no particular order:
- @sudw1n ([https://github.com/sudw1n](https://github.com/sudw1n))
- @alexdev404 ([https://github.com/AlexDev404](https://github.com/AlexDev404))
- @frischerZucker ([https://github.com/frischerZucker](https://github.com/frischerZucker))
- @oito8bits ([https://github.com/oito8bits](https://github.com/oito8bits))