After reading this tutorial, you will be able to
- understand the essential components of a shell
- learn basic C programming, including spawn a new child process to execute a command
1. Loop
void loop() {
int status;
char* line;
char** args;
do {
printf(">");
line = readline();
args = parseline(line);
status = execute(args);
free(line);
free(args);
} while (status);
}
int main(int argc, char** argv) {
loop();
return EXIT_SUCCESS;
}
2. Read
char *readline() {
char *line = NULL;
size_t linecap = 0;
while (getline(&line, &linecap, stdin) == -1) {
if (feof(stdin)) {
exit(EXIT_SUCCESS);
} else {
perror("readline");
exit(EXIT_FAILURE);
}
}
return line;
}
3. Parse
#define TOKEN_BUF_SIZE 64
#define TOK_DELIM " \t\r\n\a"
void validateTokens(char *const *tokens) {
if (tokens == NULL) {
fprintf(stderr, "allocation error\n");
exit(EXIT_FAILURE);
}
}
char **parseline(char *line) {
int buf_size = TOKEN_BUF_SIZE;
char** tokens = malloc(buf_size * sizeof(char *));
validateTokens(tokens);
char *token;
int position = 0;
token = strtok(line, TOK_DELIM);
while (token != NULL) {
tokens[position++] = token;
token = strtok(NULL, TOK_DELIM);
if (position >= buf_size) {
buf_size += TOKEN_BUF_SIZE;
tokens = realloc(tokens, buf_size * sizeof(char *));
validateTokens(tokens);
}
}
tokens[position] = NULL;
return tokens;
}
4. Execute
To start a process on Unix, there are 2 ways: being the Init
process duplicate a process by using fork()
.
Init Process
When a Unix computer boots, its kernel is loaded. Once it is loaded and initialized, the kernel starts only one process, which is called Init
. This process runs for the entire length of time that the computer is on, and it manages loading up the rest of the processes that you need for your computer to be useful.
fork()fork()
is a system call. When this function is called, the operating system makes a duplicate of the process and starts them both running. The original process is called the “parent”, and the new one is called the “child”. fork()
returns 0
to the child process, and it returns to the parent the process ID number (PID) of its child.
int execute(char** args) {
pid_t pid = fork();
int status;
if (pid == 0) {
if (execvp(args[0], args) == -1) {
perror("xsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
perror("xsh 2");
} else {
do {
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return EXIT_FAILURE;
}