Everything you need to know to start coding your own shell
Process
A process is an executing program with a unique process ID (PID). The PID is an integer value used by the operating system for process management, resource allocation, and inter-process communication.
PPID, or Parent Process IDentifier, represents the PID of the process that created another process, establishing a parent-child relationship. Understanding PID and PPID is crucial for managing processes, determining hierarchies, and enabling communication and synchronization within an operating system.
You can use the system call getpid
(man 2 getpid
)
#include <stdio.h>
#include <unistd.h>
/**
* main - PID
*
* Return: Always 0.
*/
int main(void)
{
pid_t my_pid;
my_pid = getpid();
printf("%u\n", my_pid);
return (0);
}
Note in the example above, that every time you run the program, a new process is created, and its ID is different.
Each process has a parent: the process that created it. It is possible to get the PID of a parent process by using the getppid
system call (man 2 getppid
), from within the child process.
#include <stdio.h>
#include <unistd.h>
/**
* main - PID
*
* Return: Always 0.
*/
int main(void)
{
pid_t my_ppid;
my_ppid = getppid();
printf("%u\n", my_ppid);
return (0);
}
In other words, the PPID represents the PID of the process that initiated the execution of another process. It establishes a hierarchical relationship between processes, forming a parent-child relationship. When a process creates a child process, the child process inherits the PPID of its parent.
Arguements
Command parsing and execution: Learn how to parse user input to identify commands, arguments, and options. Implement the logic to execute commands by creating child processes, handling input/output redirection, and managing process execution.
The command line arguments are passed through the main
function: int main(int ac, char **av);
av
is aNULL
terminated array of stringsac
is the number of items inav
Write a program that prints all the arguments, without using ac
.
#include <stdio.h>
// The main function that serves as the entry point of the program
// It takes in two arguments:
// - ac: the argument count, representing the number of command line arguments
// - av: the argument vector, a NULL-terminated array of strings containing the command line arguments
int main(int ac, char **av) {
int i = 1; // Start from av[1] to skip av[0] (program name)
// Loop through the arguments until encountering NULL
while (av[i] != NULL) {
printf("%s\n", av[i]); // Print the current argument
i++;
}
return 0;
}
Write a program that prints "$ "
, wait for the user to enter a command, and prints it on the next line.
#include <stdio.h>
#include <stdlib.h>
int main(int ac, char **av) {
printf("$ "); // Print the prompt symbol
char command[100]; // Buffer to store the entered command
// Read the command until encountering end-of-file (EOF)
if (fgets(command, sizeof(command), stdin) != NULL) {
printf("%s", command); // Print the entered command
}
return 0;
}
Executing a program
The system call execve
allows a process to execute another program. Note that this system call does load the new program into the current process’ memory in place of the “previous” program: on success execve
does not return to continue the rest of the “previous” program.
#include <stdio.h>
#include <unistd.h>
/**
* main - execve example
*
* Return: Always 0.
*/
int main(void)
{
char *argv[] = {"/bin/ls", "-l", "/usr/", NULL};
printf("Before execve\n");
if (execve(argv[0], argv, NULL) == -1)
{
perror("Error:");
}
printf("After execve\n");
return (0);
}
Creating processes
The system call fork
creates a new child process, almost identical to the parent (the process that calls fork
). Once fork
successfully returns, two processes continue to run the same program, but with different stacks, data and heaps.
#include <stdio.h>
#include <unistd.h>
/**
* main - fork example
*
* Return: Always 0.
*/
int main(void)
{
pid_t my_pid;
pid_t pid;
printf("Before fork\n");
pid = fork();
if (pid == -1)
{
perror("Error:");
return (1);
}
printf("After fork\n");
my_pid = getpid();
printf("My pid is %u\n", my_pid);
return (0);
}
Wait
The wait
system call (man 2 wait
) suspends execution of the calling process until one of its children terminates.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/**
* main - fork & wait example
*
* Return: Always 0.
*/
int main(void)
{
pid_t child_pid;
int status;
child_pid = fork();
if (child_pid == -1)
{
perror("Error:");
return (1);
}
if (child_pid == 0)
{
printf("Wait for me, wait for me\n");
sleep(3);
}
else
{
wait(&status);
printf("Oh, it's all better now\n");
}
return (0);
}
fork + wait + execve
Write a program that executes the command ls -l /tmp
in 5 different child processes. Each child should be created by the same process (the father). Wait for a child to exit before creating a new child.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int num_children = 5;
int i;
for (i = 0; i < num_children; i++) {
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
} else if (pid == 0) {
// Child process
printf("Child %d: PID %d - Start\n", i + 1, getpid());
execl("/bin/ls", "ls", "-l", "/tmp", NULL);
perror("execl error");
exit(1);
} else {
// Parent process
int status;
wait(&status); // Wait for the child to exit
printf("Child %d: PID %d - End\n", i + 1, pid);
}
}
return 0;
}
Next
We will look at File system operations