This is the last of a process-related system call in this column, with 2 examples demonstrating what you've learned in the past. One is the mini Shell, which is a common bash, but it is greatly simplified; the second is a daemon program that allows readers to glimpse the clues of server programming.
1.13 Shell
Linux is not too unfamiliar to the reader should have a certain understanding of the shell, that is, this program automatically after we log on, print out a $ symbol, and then wait for us to enter the command. The most common shell application under Linux is bash, and most Linux distributions are installed by default. Here we also write a shell program, this shell is far less complex than bash, but also to meet our general use, below, we started.
First, give the shell a name, which might be called a mini shell.
Linux system commands are divided into internal commands and external commands, internal commands are implemented by the shell program, such as CD, ECHO, etc., Linux internal commands are limited, and most of them are seldom used. and each Linux external command is a separate application, we are very familiar with the LS, CP, and so the vast majority of commands are external commands, these commands are in the form of executable files, most of them in the directory/bin and/sbin. In this way, the difficulty of our programming can be greatly reduced, we only need to implement a very limited internal command, for the other input, all as an application to execute.
For the sake of simplicity, the Mini shell implements only 2 internal commands:
1, CD used to switch directories, and we are familiar with the command CD similar, in addition to not so many additional features.
2. Quit to quit mini Shell.
The following is a list of the programs:
/* MSHELL.C */#include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <string.h& Gt #include <errno.h> #include <stdio.h> void Do_cd (char *argv[]); void Execute_new (char *argv[]), main () {char *cmd= (void *) malloc (256*sizeof (char)), Char *cmd_arg[10];int cmdlen,i,j, tag;do{/* Initialize cmd */for (i=0;i<255;i++) cmd[i]= ';p rintf ("-=mini shell=-*|"); Fgets (Cmd,256,stdin); Cmdlen=strlen (cmd), cmdlen--;cmd[cmdlen]= ' + ';/* Break the command line into an array of pointers Cmd_arg */for (i=0;i<10;i++) cmd_arg[i]=null;i=0; j=0; Tag=0;while (I<cmdlen && j<10) {if (cmd[i]== ') {cmd[i]= '}; tag=0;} Else{if (tag==0) cmd_arg[j++]=cmd+i;tag=1;} i++;} /* If there are more than 10 parameters, print the error and ignore the current input */if (j>=10 && i<cmdlen) {printf ("TOO many arguments\n"); continue;} /* Command quit: Exit mini Shell */if (strcmp (cmd_arg[0], "quit") ==0) break;/* command CD */if (strcmp (cmd_arg[0], "CD") ==0) {DO_CD (cmd_ arg); continue;} /* External command or Application */execute_new (CMD_ARG);} while (1);} /* Implement CD functionality */void DO_CD (char *argv[]) {if (argv[1]!=null) {if (cHdir (argv[1]) <0) switch (errno) {case enoent:printf (' DIRECTORY not found\n '); Break;case enotdir:printf ("Not A DIRECTORY name\n "); Break;case eacces:printf (" You don't have the right to access\n "); break;default:printf (" SOME ERROR happened in chdir\n ");}} /* Execute external command or application */void execute_new (char *argv[]) {pid_t pid;pid=fork (), if (pid<0) {printf ("SOME ERROR happened in fork\n "); exit (2);} else if (pid==0) {if (EXECVP (ARGV[0],ARGV) <0) switch (errno) {case enoent:printf ("COMMAND OR FILENAME not found\n"); Break;case eacces:printf ("You don't have the right to access\n"); default:printf ("SOME ERROR happened in exec\n");} Exit (3);} else Wait (NULL);}
This program is a little bit long, let's give it a detailed explanation:
Function Main:
14 rows: Defines the string cmd that is used to receive the command line entered by the user.
15 rows: Defines the pointer array cmd_arg, which form and function as we are familiar with Char *argv[].
From the above 2 definitions, we can see that the mini Shell has 2 restrictions on command input: First, the command line entered by the user must be within 255 characters (minus the end of the string), and second, the command line should have no more than 10 parameters (including the command itself).
18 Line: Enter a do-while loop, this loop is the main part of the program, the basic idea is "wait for input command-processing the input command-wait for the input command".
22 Line: Prints the input prompt information. In the Mini Shell, you can arbitrarily set your favorite command input prompt information, this program used "-=mini shell=-*| "Isn't it a bit like a CS master?" If you don't like it, you can replace it with any character.
23 Rows: Receives user input.
25-27 rows: fgets accepts input, it accepts the line break ("\ n") at the end of the input string, which we do not need, so we have to remove it. This program simply uses the string end flag ' \ s ' to overwrite the last character of the string cmd for this purpose.
30 Rows: Initializes an array of pointers cmd_arg.
32-42 Line: The input is analyzed, and the space between the parameters in cmd is filled with ' \ \ ', and the starting address of each parameter is assigned to the CMD_ARG array respectively. In this way, the cmd is decomposed into cmd_arg, but the decomposed command parameters still use the cmd memory space, so it is not appropriate to assign a value to cmd before the command execution ends.
45 rows: If the end of the input string has not been parsed (I<cmdlen), and the parsed parameter has reached or exceeded 10 (j>=10), it is assumed that the input command line exceeds the limit of 10 parameters, prints the error and receives the command again.
51-52 lines: Internal command quit: string cmd_arg[0] is the command itself, if the command is quit, then exit the loop, it is tantamount to exiting the program.
55-58 Line: Internal command CD: Call function Do_cd () to complete the action of the CD command.
61 Rows: For other external commands and applications, the calling function Execute_new () executes.
function DO_CD:
68 Rows: Consider only the parameter argv[1 immediately following the command, and no longer consider other parameters. If this parameter is present, use it as the directory to be converted.
69 Rows: Call the system call ChDir switch the current directory, see Appendix 1.
Line 70-82: Handle errors that may occur with chdir.
function Execute_new:
92 Line: Call the system call Fork to produce a new subprocess.
93 rows: If a negative value is returned, the fork call has an error.
96 rows: If 0 is returned, the current process is a child process.
97 rows: Call EXECVP to execute the new application and detect if the call is faulted (returns a negative value). The reason for using EXECVP here is that it automatically searches the default directories for the location of the target application, rather than having to do it ourselves.
98-107 rows: Error process processing that may occur for EXECVP.
108 Rows: If an error occurs in the execution of the EXECVP, the child process terminates here. On the surface, this exit is the next line of error judgment next to 97 rows, not part of the IF statement, and it seems that the exit will be executed regardless of whether the call EXECVP succeeds or not. In fact, if the EXECVP call succeeds, the process will be populated with the new program code, so it is not possible to perform this line at all. Conversely, if this line is executed, the previous EXECVP call must have an error. This effect is exactly the same as exit being included in the IF statement.
109 Line: If fork returns another value, the current process is the parent process.
110 Line: Call the system to call wait. Wait here has two functions:
- Causes the parent process to pause at this point, waiting for the child process to finish executing. In this way, you can wait until all the information for the child process is finished before printing the command prompt, waiting for the input of the next command, thus avoiding a mix of command prompt and application output.
- Collects the zombie process left after the child process exits. There may be readers who have been questioning this question-"We are programmed to generate a subprocess that is collected by our own parent process, but who collects this parent process that we manually execute?" "Now everyone should understand that all the processes we execute from the command line are finally collected by the shell."
As for the mini Shell's compilation and operation, this is no longer the case, interested readers can do their own experiments, or to improve the program, so that it is closer to even than the bash we are using.
1.14 Daemon Process 1.14.1 Understanding daemon Process
This is another interesting concept, daemon in English is "elf" meaning, as we often see in the Disney animation, some can fly, some will not, often around the protagonist of the cartoon, involved saga to mention some advice, occasionally unlucky to hit the pillar, Sometimes think of some small tricks to save the protagonist from the enemy, because of this, daemon is sometimes translated as "Guardian God." Therefore, the daemon process in the country also has two kinds of translation, some people translated "wizard process", some people translated "guardian process", these two kinds of titles appear very high frequency.
Similar to the real daemon, the daemon process is accustomed to hiding itself in the eyes of people, silently contributing to the system, and sometimes referred to as a "back-office service process." The daemon process has a long lifespan, and in general, they exit when they are executed until the entire system shuts down. Almost all of the server programs, including our well-known Apache and WU-FTP, are implemented in the form of daemon processes. Many of the common commands under Linux are inetd and ftpd, with the letter D at the end referring to daemon.
Why must you use the daemon process? The interface that each system in Linux communicates with the user is called the Terminal (terminal), each process that starts from this terminal is attached to this terminal, this terminal is called the control terminal of these processes (controlling terminal), when the control terminal is closed, The corresponding process will be automatically closed. In this regard, the reader can use the X-window in the Xterm test, (each xterm is an open terminal,) we can start the application by typing a command, such as:
$netscape
Then we close the Xterm window, and the Netscape window that just started will suddenly evaporate along with it. However, the daemon process can overcome this limitation, even if the corresponding terminal is closed, it can exist in the system for a long time, if we want to let a process live, not because of user or terminal or other changes to be affected, we must turn this process into a daemon process.
1.14.2 programming rules for daemon processes
If you want to turn your process into a daemon process, we must follow these steps strictly:
- Calling fork produces a child process while the parent process exits. All of our follow-up work is done in the child process. In doing so we can:
- If we are executing the program from the command line, this can cause the program to complete the illusion that the shell will go back to wait for the next command;
- The new process that has just been generated by fork must not be the head of a process group, which provides the precondition for the implementation of the 2nd step.
There is also an interesting phenomenon: because the parent process exits before the child process, it causes the child process to have no parent process and becomes an orphan process (orphan). Whenever the system discovers an orphan process, it is automatically adopted by the 1th process, so that the original child process becomes a subprocess of process 1th.
- Call the SETSID system call. This is the most important step in the whole process. Setsid's introduction is shown in Appendix 2, which is the role of creating a new session and being the leader of the session (session leader). If the calling process is the leader of a process group, the call will fail, but this is guaranteed in the 1th step. Calling Setsid has 3 functions:
- Let the process get rid of the control of the original session;
- Let the process get rid of the control of the original process group;
- Let the process out of control of the original control terminal;
In short, let the calling process be completely independent and out of control of all other processes.
- Switch the current working directory to the root directory. If we are executing this process on a temporarily loaded file system, such as:/mnt/floppy/, the current working directory of the process will be/mnt/floppy/. The filesystem cannot be unloaded during the entire process (umount), which can be inconvenient for us regardless of whether or not we are using this file system. The workaround is to use the CHDIR system call to change the current working directory to the root directory, and no one should want to remove the root directory. For the use of ChDir, see Appendix 1.
Of course, in this step, if there is a special need, we can also change the current working directory to other paths, such as/tmp.
- Set the file permission mask to 0. This requires calling the system call Umask, see Appendix 3. Each process inherits a file permission mask from the parent process, and when a new file is created, the mask is used to set the default access permissions for the file, masking some permissions, such as Write permissions for the general user. When another process calls the daemon program we write with exec, because we don't know what the file permission mask of that process is, it can cause some trouble when we create a new file. So, we should reset the file permission mask, we can set it to whatever value we want, but in general, everyone sets it to 0 so that it does not block any action by the user.
If your application doesn't involve creating a new file or setting up access to a file, you can simply kick the file permission mask off and skip this step.
- Close any files that you do not need. As with the file permission mask, our new process inherits some files that have already been opened from the parent process. These open files may never be read or written by our daemon process, but they consume system resources as well, and may cause the file system in which they reside to not be removed. It should be noted that the file descriptor is 0, 1 and 2 of three files (the concept of file descriptors will be introduced in the next chapter), that is, we often say that the input, output and error of the three files also need to be closed. It is very likely that many readers will be surprised by this, don't we need to input the output? But the fact is, after the 2nd step above, our daemon process has lost contact with the owning control terminal, the characters we enter from the terminal are not possible to reach the daemon process, and the characters that the daemon process outputs with conventional methods (such as printf) cannot be displayed on our terminal. So these three files have lost their existing value and should also be closed.
Below, we have seen the birth of a daemon process:
1.14.31 Daemon Programs
/* DAEMON.C */#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #define Maxfile 65535main () {pid_t pid;int i; Pid=fork (); if (pid<0) {printf ("Error in fork\n"); exit (1);} else if (pid>0)/* Parent process exits */exit (0); /* Call Setsid */ Setsid ();/* Toggle the current directory * /ChDir ("/");/* Set file permission mask */umask (0);/* Close all unwanted files that may be open */for (i=0;i< maxfile;i++) Close (i);/* so far, the process has become a complete daemon process where you can add anything you want to daemon do, such as: */for (;;) Sleep (10);}
The tasks of compiling and running are given to the reader to complete. The daemon process does not have the same eye-catching results as other processes, and basically it does its job without any fanfare. You can't see anything, but you can use the "PS-AJX" command to observe the status of your daemon process and some parameters.
1.15 Appendix 1.15.1 System Call ChDir
#include <unistd.h>int chdir (const char *path);
The role of chdir is to change the current working directory. The current working directory of the process is typically the directory at the time of application startup, and once the process has started running, the current working directory remains the same unless ChDir is called. ChDir has only 1 string arguments, which is the path to be transferred. For example:
ChDir ("/");
The current path of the process becomes the root directory.
1.15.2 system Call Setsid
#include <unistd.h> pid_t setsid (void);
A session begins when the user logs on and terminates when the user exits, during which all processes running by the user belong to this session, unless the process calls the SETSID system call.
The system calls Setsid without any parameters, and after the call, the calling process will set up a new session and be the leader of the session.
1.15.3 system Call Umask
#include <sys/types.h> #include <sys/stat.h>mode_t umask (mode_t mask);
The system call Umask can set a file permission mask that the user can use to block certain permissions to prevent the error from causing excessive privileges to certain users.
Resources
- Linux Mans pages
- Advanced programming in the UNIX environment by W. Richard Stevens, 1993
- IBM system calls with me learn (4)
[Linux] System call understanding (4)