"Creating Your Own Command Interpreter: Main Concepts Guide: Simple Shell - Part 1"
Deeper into UNIX: Exploring the Inner Workings of UNIX with a Custom Interpreter
How does a shell work
When you tell your computer what to do, it can be like talking to a wall. But with a shell, you have a personal assistant that understands your commands and talks to the operating system for you.
A shell is a program that acts as an interface between the user and the operating system. It provides a command-line interface (CLI) for the user to interact with the operating system and execute various commands and programs.
When a user enters a command in the shell, the shell interprets the command and executes it. The shell performs the following steps to execute a command:
Parsing: The shell splits the command into words and identifies the command and its arguments.
Path resolution: If the command is not a built-in command, the shell searches for the command in the directories listed in the PATH environment variable.
Forking: The shell creates a child process to execute the command.
I/O redirection: The shell sets up the input and output streams for the child process based on any redirection specified by the command.
*For example, if the user enters the command
sort < input.txt > output.txt
in the shell, the shell will create a child process to execute thesort
command. The shell will set up the input stream for the child process to read from theinput.txt
file, and the output stream for the child process to write to theoutput.txt
file.
Executing: The child process loads the executable file for the command and executes it with the provided arguments.
Returning: The child process returns the result of the command to the shell, which displays it to the user.
The shell also provides various features such as command history, command completion, and shell scripts that allow users to automate tasks and improve their productivity.
What is a pid and a ppid ?
parent-child relationships
In a computer system, a process is a computer program that is currently running on the computer's operating system.. Each process is identified by a unique process ID (PID), which is a number assigned by the operating system.
The PID is used to track and manage the process by the operating system. For example, the operating system uses the PID to allocate system resources, such as memory and CPU time, to the process.
In addition to the PID, each process also has a parent process ID (PPID), which is the PID of the process that created it. The PPID is used to track the process hierarchy or tree, where each process has one parent process and may have multiple child processes.
For example, When you open a terminal window, a Terminal process is created.
The Terminal process creates a Shell process, which runs in the terminal window.
The Shell process has a PID (Y) and its PPID is the PID of the Terminal process (X).
When you run a command in the Shell, a new process is created to execute the command.
The new process has a new PID (W), and its PPID is the PID of the Shell process (Y).
The PPID is useful for managing and controlling processes, such as terminating a process and all its child processes or monitoring the resource usage of a process and its descendants in the process tree.
in summary, When you run a command in the shell, the shell process creates a new process to execute the command. This new process also has a unique PID, and its PPID is set to the PID of the shell process that created it.
How to manipulate the environment of the current process ?
first what Environment variables?
Environment variables are a common way to define and manage a program or process. These are named values that are used by the operating system and applications to configure various aspects of the system and runtime environment.
For instance, an environment variable may contain information about the user who is currently logged in, the type of processor being used, or the location of certain files or directories.
By manipulating environment variables, you can customize the behaviour of your program or process to better suit your needs and the needs of your system, for example setting the language: such as "LANG" or "LC_MESSAGES" to determine which language to use for the user interface.
To manipulate the environment of the current process, you can use commands or programming interfaces provided by the operating system or programming language.
Here are some common ways to manipulate environment variables:
Set an environment variable: You can set an environment variable for the current process by using the "export" command in a Unix-like shell or the "
set
" command in cmd. For example, suppose you want to set an environment variable named "DATABASE_URL" to the URL of your database server. You can set this variable by running the following command in your terminal:export DATABASE_URL=postgres://user:password@localhost:5432/mydatabase
This sets the "DATABASE_URL" environment variable to the specified value, which can then be used by any program or script that reads this variable.
Modify an environment variable: You can modify an existing environment variable by assigning a new value to it. For example,
MY_VAR=new_value
changes the value of the "MY_VAR" environment variable to "new_value".Remove an environment variable: You can remove an environment variable by using the "unset" command in a Unix-like shell or the "set" command. For example,
"
unset MY_VAR
" removes the "MY_VAR" environment variable.
Access environment variables in code: You can access environment variables in your code by calling the appropriate function or library provided by your programming language or operating system. For example, In C, you can access environment variables through the global variable "environ" declared in the "unistd.h" header file. The "environ" variable is an array of strings that contains the names and values of all environment variables.
Here's an example program that reads the value of an environment variable named "MY_VAR" and prints it to the console:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char* my_var_value = getenv("MY_VAR");
if (my_var_value != NULL) {
printf("The value of MY_VAR is %s\n", my_var_value);
} else {
printf("MY_VAR is not set\n");
}
return 0;
}
Note that the "getenv" function returns a pointer to a string that contains the value of the environment variable, or NULL if the variable is not set. The returned string is a pointer to a static buffer that may be overwritten by subsequent calls to "getenv". So, it's a good practice to copy the value to a local buffer if you need to modify it or use it later in your code.
It's important to note that changes made to the environment variables of the current process only affect that process and its child processes. The changes are not permanent and will be lost when the process terminates.
what is a system call ?
First get to know Mr. Kernel, The kernel is the central component of an operating system that manages the system's resources and provides services to applications. It is responsible for controlling the hardware of the computer and providing a stable and secure environment for applications to run in. The kernel is a piece of software that interacts directly with the hardware of the computer, including the CPU, memory, and input/output devices. It provides a layer of abstraction between the hardware and applications, allowing them to access the hardware in a controlled and secure manner.
Now let us explore : What is a system call ?
A system call is a programming interface provided by the operating system that allows applications to request services from the kernel.
When an application needs to perform a privileged operation such as reading or writing to a file, creating a new process, or allocating memory, it makes a system call to request the kernel to perform the operation on its behalf.
The system call interface provides a standard set of functions that applications can use to interact with the kernel. The specific set of system calls available depends on the operating system and its version.
Examples of common system calls include:
open(): opens a file for reading or writing
read(): reads data from a file or device
write(): writes data to a file or device
fork(): creates a new process
exec(): replaces the current process with a new one
mmap(): maps a region of memory to a file or device
System calls are usually made through a wrapper function provided by the programming language or operating system. For example, in C, the system calls are made through the "syscall" function, while in Python, the "os" module provides a set of functions that wrap the system calls.
System calls are an important part of the operating system's interface with applications, providing a way for them to access the underlying hardware and resources of the system in a controlled and secure manner.
What is the difference between a function and a system call ?
The main difference between a function and a system call is that a function is executed in the same execution context as the calling code, while a system call is executed in a different execution context (i.e., kernel mode) and requires a context switch to transfer control from the user application to the kernel.
Functions are used for modularizing code and making it easier to read, write, and maintain. System calls are used to provide access to system resources that are not directly accessible by user applications.
While both functions and system calls provide a way to modularize code and perform specific tasks, they operate at different levels of abstraction and serve different purposes in a program.
here are examples of a function and a system call in C to illustrate the difference:
Example of a function in C
Now let's look at an example of a system call:
In this example, the program uses the open()
and read()
system calls to read the contents of a file named "test.txt". The open()
system call takes as arguments the name of the file and a set of flags that indicate how the file should be opened. It returns a file descriptor “fd” that is used by the read()
system call to read data from the file.
A file descriptor is a unique identifier ‘small non-negative integers’ that is used by operating systems to represent a file or other input/output resource, such as a socket or pipe.
Note that the open()
and read()
system calls are executed in kernel mode, which allows them to access system resources that are not directly accessible by user applications. Also note that these system calls require error handling code, which is not required in the case of a simple function call.
In conclusion, understanding how a shell works and the underlying concepts of processes, environment variables, system calls, and file descriptors is crucial for any programmer or system administrator. In the next part of this series, we will delve deeper into the topic of processes and explore how to create them. We will also cover the three prototypes of the main function, how to use the PATH environment variable to find programs, how to execute programs using the execve system call, and much more. Stay tuned for Part 2 of this series, where we will continue to explore the fascinating world of shells and processes.
Go to part 2 : PART 2 Simple Shell
hi
how to contact with you where is your linked in or any...?
Despite being a total beginner regarding C shell and I started reading out of curiosity, I found myself get a good grasp of what you'r explaining which was shocking for me; I've been always found technical articles boring and hard to understand, but yours is the total opposite. Keep up the good work.