1. Unix Shell functions
A shell is a program that manages processes and runs programs. All the common shells have 3 main functions:
(1) running the program;
(2) Managing inputs and outputs
(3) Programmable
The shell is also a programming language with variable and process control.
2. UNIX Process Model
A program is a sequence of machine instructions stored in a file, typically a compiler that compiles source code into binary format code. Running a program means loading these machine instruction sequences into memory and then having the processor (CPU) run-by-article. In UNIX terminology, an executable program is a sequence of machine instruction machine data. A process is the memory space and settings of the program when it is run. Data and programs are stored in a disk file, and the program runs in the process.
Each process has a number that uniquely identifies it, called the process ID, which is generally referred to as PID, and also has a parent process ID (PPID). Each process is connected to a terminal, which has a running time, a priority, a niceness level, a size ...
The memory in Unix system is divided into system space and user space. The process exists in user space.
3. How to execute a program
Shell print prompt, the user enters instructions, the shell runs this command, and then the shell prints the prompt again--so repeatedly.
A Shell's main loop performs the following 4 steps:
(1) User type a.out
(2) The shell creates a new process to run this appears
(3) Shell loads the program from disk
(4) The program runs in its process to know the end
That is:
while (! end_of_input)
get command
execute command
wait for command to finish
How does one program run another? The answer is that the program calls exec family functions:
man 3 exec
#include <unistd.h>
extern char ** environ;
int execl (const char * path, const char * arg, ...);
int execlp (const char * file, const char * arg, ...);
int execle (const char * path, const char * arg,
..., char * const envp []);
int execv (const char * path, char * const argv []);
int execvp (const char * file, char * const argv []);
int execvpe (const char * file, char * const argv [],
char * const envp []);
/ *
* The exec () family of functions replaces the current process
* image with a new process image. The functions described
* in this manual page are front-ends for execve (2).
* /
4. How to create a process
A process calls fork to copy itself. The process calls fork, and when control is transferred to the fork code in the kernel, the kernel does:
(1) Allocate new memory blocks and kernel data structures
(2) Copy the original process to the new process
(3) Add a new process to the running process
(4) Return control to two processes
man 2 fork
#include <unistd.h>
pid_t fork (void);
5. How to communicate between the parent process and the child process (how the parent process waits for the child process to exit)
The process calls wait to wait for the end of the subroutine. The system call wait does two things. First, wait pauses the process that called it until the child process is introduced. Then, wait gets the value passed to exit when the child process ends. This wait performs two operations: notification and communication.
One of the purposes of wait is to notify the parent process that the child process has finished running. Its second purpose is to tell the parent process how the child process ended. A process ends in one of three ways (success, failure, or death). According to Unix conventions, a successful program calls exit (0) or returns 0 from the main function; the program encounters a problem and passes it a non-zero value when exiting the call to exit.
When the parent process calls wait, an integer variable address is passed to the function. The kernel saves the u raised state of the child process in this variable. If the child process calls exit to exit, the kernel puts the return value of exit into this integer variable; if the process is killed, the kernel stores the signal sequence number in this variable. This integer consists of 3 parts-the upper 8 bits record the exit value, the lower 7 bits record the signal serial number, and the 7th bit is used to indicate that an error has occurred and a core dump is generated.
man 2 wait
#include <sys / types.h>
#include <sys / wait.h>
pid_t wait (int * status);
pid_t waitpid (pid_t pid, int * status, int options);
int waitid (idtype_t idtype, id_t id, siginfo_t * infop, int options);
6. Process death: exit and _exit
Exit is the reverse operation of fork, and the process stops by calling exit. Fork creates a process, and exit deletes a process. This is basically the case.
Exit refreshes all streams, calls the functions registered by atexit and on_exit, and performs other exit-related operations defined by the current system. Then call _exit. The system function _exit is a kernel operation. This operation processes all the memory allocated to the process, closes all files opened by the process, and releases all the kernel data structures used to manage and maintain the process.
Those processes that have died but did not assign an exit value are called zombie processes.
The system call _exit terminates the current process and performs all necessary cleanup work:
(1) Close all file descriptors and directory descriptors
(2) Set the PID of the process to the PID of the init process
(3) If the parent process calls wait or waitpid to wait for the child process to end, then notify the parent process
(4) Send SIGCHLD signal to the parent process
Excerpt from the book, for the shell's fork (), exec () and exit () loop
/ *
* prompting shell version 02
*
* Solves the ‘one-shot’ problem of version 01
* Uses execvp (), but fork () s first so that the
* shell waits around to perform another command
* New problem: shell cathes signals. Run vi, press ^ C.
* /
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 20 / * cmdline args * /
#define ARGLEN 100 / * token length * /
int main (void)
{
char * arglist [MAXARGS + 1]; / * an array of ptrs * /
int numargs; / * index into array * /
char argbuf [ARGLEN]; / * read stuff here * /
void execute (char **);
char * makestring (char *); / * malloc etc * /
numargs = 0;
while (numargs <MAXARGS)
{
printf ("Arg [% d]?", numargs);
if (fgets (argbuf, ARGLEN, stdin) && * argbuf! = ‘\ n’)
arglist [numargs ++] = makestring (argbuf);
else
{
if (numargs> 0) / * any args * /
{
arglist [numargs] = NULL; / * colse list * /
execute (arglist); / * do it * /
numargs = 0; / * and reset * /
}
}
}
return 0;
}
void execute (char ** arglist)
/ *
* use fork and execvp and wiat to do it
* /
{
pid_t pid, exitstatus; / * of child * /
pid = fork (); / * make new process * /
switch (pid)
{
case -1:
perror ("fork falued");
exit (1);
case 0:
execvp (arglist [0], arglist); / * do it * /
perror ("execvp failed");
exit (1);
default:
while (wait (& exitstatus)! = pid)
;
printf ("child exited with status% d,% d \ n",
exitstatus >> 8, exitstatus & 0377);
break;
}
}
char * makestring (char * buf)
/ *
* trim off newline and create storage for the string
* /
{
char * cp;
buf [strlen (buf) -1] = ‘\ 0‘; / * trim newline * /
cp = malloc (strlen (buf) +1); / * get memory * /
if (cp == NULL) / * or die * /
{
fprintf (stderr, "no memory \ n");
exit (1);
}
strcpy (cp, buf); / * copy chars * /
return cp;
}