Lab 6 Challenge
BUAA 2023 Spring OS
Primary architecture based on PassBash. Code available at:
Copyright © Tony’s Studio 2023
1. Overall Design
1.1 Demand Analysis
The guiding book may differ each year, and right now, as I’m writing, is 2023. However, the basic requirements for a Shell 🐚 are generally the same.
- Friendly user interaction.
- Command &
Conquerargument parsing. - Job scheduling.
I think the first one is quite comprehensive, since it is always difficult to provide better user experiences. Here, my understanding of a friendly Shell is that it must at least support free editing, history command and, completion perhaps.
So, I implemented Pash, with primary features as follows.
- Free editing experience. Not only direction key, but also Home, End, Delete, even Ctrl.
- Enhanced user experience. Provide clear error info, support command history, and auto-completion on Tab.
- Complete redirection. Add support for
>>
. - Beautiful interface with ANSI colors.
- Clean and tidy directory structure.
1.2 Architecture
The original shell looks like this.
And Pash, is like this. Only better. With internal command and external command unified, we can parse them altogether, and enable redirection and pipe for internal commands.
Here is another figure showing all modules of Pash.
2. Implementation
All functions that are exclusive to Pash are declared in
user/include/pash.h
, and the definition of Pash is inuser/pash.c
, while auxiliary functions and other modules are inuser/lib/pashlib.c
.
2.1 Input Module
2.1.1 Raw Input
Input module is what I considered most well designed. It is powerful and flexible. For low-level input, it uses getch
function that is similar to function with the same name in Windows conio.h
. It rely on a system call.
1 | // kern/syscall_all.c |
2.1.2 Input Recognition
Well, in MOS, special keys are in form of ANSI code, just like what in Linux. So it could be a little annoying to recognize these keys. However, with finite state machine, it would be a problem no more. The state machine looks just like what is shown below, but some layers are omitted since there is only one node in those layers. Each leaf node is registered with a event handler corresponding to the input, and non-leaf node with a handler to determine the next node.
Well, in code, this state machine consists of these two types of functions. And to maintain a consistent input state, I use a input context structure. input_opt_t
will be elaborated later.
1 | // user/lib/pashlib.c |
Important: One thing to notice is that, for this struct, we have to use
__attribute__((aligned(32)))
, or unexpected error will raise. It is sure to be a problem of byte alignment, but I could not explain it yet.
2.1.3 Input Options
One of the primary features of this input module is that is support custom options. This is the part I’m most satisfied with.
1 | typedef struct _input_history_t |
Here, I’d like to show you some examples. Expand it as you wish.
Input Option Example
This is a piece of code extracted from history handling part of input. Some extra checks are removed to reduce passage length.
1 | // command starts with white space will not be saved (like Linux) |
And another one is about completion. It is registered to Tab key.
1 | static void _input_tab(const input_opt_t* opt, input_ctx_t* ctx) |
For the concrete implementation of history and completion service, please refer to my git repository. 😉
2.2 Command Resolving
2.2.1 Command Parsing
Basic command parsing remains almost the same. However, it no longer do this in a forked child process. And the execution flow changes slightly for ;
and &
support. Mainly use hasNext
and needWait
flags to tell Shell to continue parsing, or end. Here, I only showed code essentially related to ;
and &
.
1 | static int _runcmd(char* cmd) |
Here, the process of getting token in command also uses a state machine. (It is a good thing.)
2.2.2 Command Execution
After parsing, shell will then execute the command. Here, both internal and external command are first handled in _execv
of Pash itself. If it is not a internal command, Pash will use library function execv
to execute the command.
1 | // user/pash.c |
2.2.3 Patch for Redirection
Since we moved command parsing in Pash process, and internal command also support redirection, so Pash it self will be affected by commands with redirection and pipe. Therefore, the standard input and output must be reset after the execution of these commands.
1 | // user/pash.c |
2.2.4 Argument Parsing
The original MOS uses an argument parsing macro from Plan 9… It is good, but is almost unreadable. So here, I implemented getopt
function to resemble the one in Linux. It’s a little long, so just refer to my repository. Here I just give you a simple example of how it is used. (Error handling are removed to reduce passage length.)
1 | // user/tree.c |
2.3 File System
2.3.1 Present Working Directory
This is simple, just place this stuff to PCB is OK.
1 | struct Env |
Then, just implement some functions to handle this.
1 | // user/include/lib.h |
2.3.2 Absolute Path
Once we have complicated path, it comes the problem that, how to get absolute path? It is impossible (at least for not) for a file to get its parent, even worse, we have to deal with path that looks like home/../../opt/./el...
. So we have to request file system process to do this for us.
1 | // user/lib/file.c |
Actually, we can get the parent of a
struct File
usingFile::f_dir
. But I failed to do so in user library function. I didn’t have time to figure out why, perhaps you can do it. 🧐Anyway, when we only have filename, isn’t it more simpler to directly get the absolute path than open a directory, then traverse its parent?
2.3.3 Open Mode
To realize >>
, and history function, we have to implement O_APPEND
mode for opening file, and of course, O_CREAT
also. What’s more, a detail when open with O_WRONLY
, we should clear the content of the file.
2.4 Extra
2.4.1 Directory Structure
To make everything in place, I refactored file structure of MOS to resemble Linux. Isn’t it tidy?
And we have to change how we burn files into MOS disk in fs/Makefile
.
1 | FSIMGFILES := rootfs/init.b \ |
2.4.2 User Profile
In Pash, we stored user profile in file, rather than hard code. And uses a library function to get this.
1 | // lib.h |
2.4.3 Colorful Output
Here, we also use ANSI code to output colorful characters. And we wrapped it in printfc
. (I renamed previous user/lib/fprintf.c
to user/lib/printf.c
.)
1 | // user/lib/printf.c |
3. Epilogue
Well, I guess this is a general overview of Pash. However, there are a lot pitfalls that I didn’t mention. Some of them were really obscure that I could not fully comprehend. But, it is truly exciting to see Pash running on MOS. 😁
"When I left you, I was but the learner. Next time, I will be, the master."