1.13 Shell
Readers who are not familiar with Linux should have a certain understanding of shell, that is, this program is automatically executed after we log on, print out a $ symbol, and then wait for us to enter the command. The most common shell application in Linux is Bash, which is installed by default in most Linux distributions. Next we will also write a shell program by hand. This shell is far less complex than bash, but it can also meet our general usage. Next, we will start.
First, give the shell a name called mini shell.
Commands in Linux are divided into two types: Internal commands and external commands. Internal commands are implemented by Shell programs, such as CD and echo. Linux has a limited number of internal commands, and most of them are rarely used. Every Linux external command is a separate application. The vast majority of commands such as LS and CP, which we are very familiar with, are external commands that can exist in the form of executable files, most of them are stored in the directory/bin and/sbin. In this way, the difficulty of programming can be greatly reduced. We only need to implement limited internal commands and execute all other inputs as applications.
For simplicity and clarity, mini shell only implements two internal commands:
1. CD is used to switch directories. It is similar to the command CD we are familiar with, except that there are not so many additional functions.
2. Quit is used to exit the mini shell.
The following is a program list:
1: /* mshell.c */
2: #include <sys/types.h>
1: #include <unistd.h>
3: #include <sys/wait.h>
4: #include <string.h>
5: #include <errno.h>
6: #include <stdio.h>
7:
9: void do_cd(char *argv[]);
10: void execute_new(char *argv[]);
11:
12:main()
13:{
14:char *cmd=(void *)malloc(256*sizeof(char));
15:char *cmd_arg[10];
16:int cmdlen,i,j,tag;
17:
18:do{
19:/* initialize cmd */
20:for(i=0;i<255;i++) cmd[i]='\0';
21:
22:printf("-=Mini Shell=-*| ");
23:fgets(cmd,256,stdin);
24:
25:cmdlen=strlen(cmd);
26:cmdlen--;
27:cmd[cmdlen]='\0';
28:
29:/* break down the command line into the pointer array pai_arg */
30:for(i=0;i<10;i++) cmd_arg[i]=NULL;
31:i=0; j=0; tag=0;
32:while(i<cmdlen && j<10){
33:if(cmd[i]==' '){
34:cmd[i]='\0';
35:tag=0;
36:}else{
37:if(tag==0)
38:cmd_arg[j++]=cmd+i;
39:tag=1;
40:}
41:i++;
42:}
43:
44:/* if there are more than 10 parameters, print the error and ignore the current input */
45:if(j>=10 && i<cmdlen){
46:printf("TOO MANY ARGUMENTS\n");
47:continue;
48:}
49:
50:/* command quit: Exit mini shell */
51:if(strcmp(cmd_arg[0],"quit")==0)
52:break;
53:
54:/* command CD */
55:if(strcmp(cmd_arg[0],"cd")==0){
56:do_cd(cmd_arg);
57:continue;
58:}
59:
60:/* External commands or Applications */
61:execute_new(cmd_arg);
62:}while(1);
63:}
64:
65:/* implement the CD function */
66:void do_cd(char *argv[])
67:{
68:if(argv[1]!=NULL){
69:if(chdir(argv[1])<0)
70:switch(errno){
71:case ENOENT:
72:printf("DIRECTORY NOT FOUND\n");
73:break;
74:case ENOTDIR:
75:printf("NOT A DIRECTORY NAME\n");
76:break;
77:case EACCES:
78:printf("YOU DO NOT HAVE RIGHT TO ACCESS\n");
79:break;
80:default:
81:printf("SOME ERROR HAPPENED IN CHDIR\n");
82:}
83:}
84:
85:}
86:
87:/* execute External commands or Applications */
88:void execute_new(char *argv[])
89:{
90:pid_t pid;
91:
92:pid=fork();
93:if(pid<0){
94:printf("SOME ERROR HAPPENED IN FORK\n");
95:exit(2);
96:}else if(pid==0){
97:if(execvp(argv[0],argv)<0)
98:switch(errno){
99:case ENOENT:
100:printf("COMMAND OR FILENAME NOT FOUND\n");
101:break;
102:case EACCES:
103:printf("YOU DO NOT HAVE RIGHT TO ACCESS\n");
104:break;
105: default:
106: printf("SOME ERROR HAPPENED IN EXEC\n");
107:}
108:exit(3);
109:}else
110:wait(NULL);
111:}
This program is a little long. Let's explain it in detail:
Function main:
Line 14: defines the CMD string, which is used to receive user input command lines.
Row 15: defines the pointer array, which has the same form and function as the familiar char * argv.
From the two definitions above, we can see that mini shell has two restrictions on command input: first, the command line entered by the user must be within 255 characters (excluding the string ending sign '\ 0'). Secondly, the number of command line parameters cannot exceed 10 (including the command itself ).
Line 18: enter a do-while loop, which is the main part of the program. The basic idea is "waiting for the input command-processing the input command-waiting for the input command ".
Row 22: print the input prompt. In mini shell, you can set your favorite command input prompt information at will. This program uses "-= mini shell =-* |", isn't it a bit like a CS master? If you do not like it, you can replace it with any character.
Row 23: receives user input.
Line 25-27: fgets accepts the line break ("\ n") at the end of the input string when accepting the input. This is not required, so we need to remove it. In this program, the string end flag '\ 0' is used to overwrite the last character of the string cmd.
Row 30: Initialize the pointer array pai_arg.
Row 32-42: analyzes the input, fills the space between parameters in cmd with '\ 0', and adds the start address of each parameter to the pai_arg array. In this way, CMD is decomposed into cmd_arg, but the command parameters after decomposition still use the memory space of CMD. Therefore, it is not appropriate to assign a value to cmd before the command execution ends.
Row 45: If I <symbol Len) is not analyzed at the end of the input string, and the analyzed parameters have reached or exceed 10 (j> = 10 ), if the input command line exceeds the limit of 10 parameters, print the error and re-receive the command.
Line 5-52: Internal Command quit: String pai_arg [0] is the command itself. If the command is quit, exit the loop, which means to exit the program.
Line 5-58: Internal Command CD: Call the function do_cd () to complete the action of the CD command.
Row 61: Execute the execute_new () function for other external commands and applications.
Function do_cd:
Row 68: only the argv [1] parameter followed by the command is considered, and other parameters are not considered. If this parameter exists, use it as the directory to be converted.
Row 69: Call the system to call chdir to switch the current directory. See Appendix 1.
Lines 70-82: handle possible chdir errors.
Function execute_new:
Row 92: Call the system to call fork to generate a new sub-process.
Row 93: if a negative value is returned, a fork call error occurs.
Row 96: If 0 is returned, the current process is a sub-process.
Row 97: execvp is called to run the new application, and an error is detected (negative value is returned ). The reason that execvp is used here is that it can automatically find the location of the target application in each default directory, without the need for our own programming.
Line 8-: handle possible errors of execvp.
Row 108: if an error occurs during execvp execution, the sub-process is terminated here. On the surface, this exit is the next statement followed by 97 rows of incorrect judgment, rather than part of the IF statement. It seems that exit will be executed no matter whether execvp is successfully called or not. But in fact, if the execvp call is successful, the process will be filled with new program code, and thus it is impossible to execute this line. If this line is executed, the previous execvp call must have encountered an error. This effect is exactly the same as that in the IF statement where exit is included.
Row 109: If fork returns other values, the current process is a parent process.
Row 110: Calls wait by calling the system. Wait has two functions:
- Pause the parent process here and wait until the sub-process is completed. In this way, you can print the command prompt after all the information of the sub-process is output, and wait for the input of the next command to avoid the mixing of the command prompt and application output.
- Collect zombie processes left after the sub-process exits. Some readers may always have doubts about this issue-"The child processes we program to generate are collected by our own parent process, but who collects the parent process we manually execute? "Now we should understand that all the processes we execute from the command line are finally collected by shell.
The compilation and running of mini shell will not be described here. Interested readers can perform their own experiments or improve the program, bring it closer to or even greater than the bash we are using.
1.14 daemon process
1.14.1 understand the daemon process
This is another interesting concept. In English, daemon refers to "Genie", just like what we often see in Disney animations. Some of them will fly, and some will not, I often turn around the cartoon hero and give some advice in the dark. Sometimes I am unlucky to hit the pillar. Sometimes I want to come up with some small tricks to save the hero from the enemy. For this reason, daemon is sometimes translated as "patron saint ". Therefore, the daemon process has two translation methods in China. Some translate "Genie process" and some translate "daemon process", which are frequently used.
Similar to the real daemon process, the daemon process is also used to hiding itself out of sight and silently contributing to the system. Sometimes they are called "background service processes ". Daemon processes have a long life cycle. Generally, they exit from execution until the entire system is shut down. Almost all server programs, including well-known Apache and Wu-FTP, are implemented in the form of a daemon process. Many common commands in Linux, such as inetd and FTPD, use the letter D at the end of daemon.
Why must the daemon process be used? In Linux, the interface for communication between each system and users is called terminal. Every process starting from this terminal is attached to this terminal, this terminal is called the control terminal of these processes. When the control terminal is closed, the corresponding process is automatically closed. In this case, you can use the xterm in X-window to experiment with it. (each xterm is an open terminal.) You can run the following command to start the application:
$ Netscape
Then we close the xterm window, and the newly started Netscape window will also evaporate. However, the daemon process can break through this restriction. Even if the corresponding terminal is closed, it can survive for a long time in the system. If we want a process to survive for a hundred years, without being affected by user, terminal, or other changes, you must turn this process into a daemon process.
1.14.2 daemon process programming rules
To change your process to a daemon process, follow these steps:
- Call fork to generate a child process, and the parent process exits. All subsequent work is completed in the sub-process. In this way, we can:
- If we execute the program from the command line, this may lead to the illusion that the program has been executed, shell will go back and wait for the next command;
- The new process generated through fork will not be the leader of a process group, which provides a prerequisite for the implementation of step 1.
In this case, a very interesting phenomenon occurs: because the parent process has exited before the child process, it will cause the child process to have no parent process and become an orphan process (orphan ). Every time the system finds an orphan process, it will be automatically adopted by process 1, so that the original child process will become the child process of process 1.
- Call the setsid system call. This is the most important step in the process. For details about setsid, see Appendix 2. It is used to create a new session and act as the session leader ). If the calling process is the leader of a process group, the call will fail, but this is guaranteed in step 1. Calling setsid has three functions:
- Let the process get rid of the control of the original session;
- Remove the process from the control of the original process group;
- Remove the process from the control of the original control terminal;
In short, the calling process is completely independent from the control of all other processes.
- Switch the current working directory to the root directory. If we execute this process on a temporary file system, such as/mnt/floppy/, the current working directory of the process will be/mnt/floppy /. The file system cannot be detached (umount) during the entire process, and whether or not we are using this file system, this will cause us a lot of inconvenience. The solution is to use the chdir system call to change the current working directory to the root directory. No one wants to unload the root directory. For more information about how to use chdir, see Appendix 1.
Of course, in this step, if there are special needs, we can also change the current working directory to another path, such as/tmp.
- Set the File Permission mask to 0. This requires that the system call umask be called. See Appendix 3. Each process inherits a File Permission mask from the parent process. When a new file is created, this mask is used to set the default access permissions for the file and block some permissions, for example, the write permission of a general user. When another process uses exec to call our daemon program, because we do not know what the File Permission mask of the process is, it will cause some trouble when we create a new file. Therefore, we should re-set the File Permission mask. We can set it to any value we want, but generally we set it to 0, it does not block any user operations.
If your application does not involve creating new files or setting file access permissions, you can also kick off the File Permission mask and skip this step.
- Close all unnecessary files. Like the File Permission mask, our new process will inherit some opened files from the parent process. These opened files may never be read or written by our daemon process, but they consume the same system resources and may cause the file system to be unable to be detached. It should be noted that the file descriptor is 0, 1, and 2 (the concept of the file descriptor will be introduced in the next chapter ), that is to say, the input, output, and error files also need to be closed. Many readers may wonder this. Do we not need input or output? But the fact is that after Step 1 above, our daemon process has lost contact with the control terminal, and the characters we input from the terminal cannot reach the daemon process, the daemon process output characters using conventional methods (such as printf) cannot be displayed on our terminal. Therefore, these three files have lost value and should be closed.
Next, let's see the birth of a daemon process:
1.14.3 a daemon program
/* Daemon. C */# include <unistd. h> # include <sys/types. h> # include <sys/STAT. h ># define maxfile 65535 main () {pid_t PID; int I; pid = fork (); If (PID <0) {printf ("error in fork \ n "); exit (1);} else if (pid> 0)/* parent process exited */exit (0);/* Call setsid */setsid (); /* switch the current directory */chdir ("/");/* set the File Permission mask */umask (0 ); /* close all unnecessary files that may be opened */for (I = 0; I <maxfile; I ++) Close (I);/* until now, the process has become a complete daemon process. You can add anything you want daemon to do here, such as: */For (;) sleep (10 );} |
-
-
Compile and run the task on your own. Unlike other processes, the daemon process has very eye-catching running results. Basically, it just does its own thing with no sound. You cannot 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 function of chdir is to change the current working directory. The current working directory of the process is generally the directory when the application starts. Once the process starts running, the current working directory will remain unchanged unless chdir is called. Chdir has only one string parameter, which is the path to be transposed. For example:
The current path of the process is changed to the root directory.
1.15.2 System Call setsid
#include <unistd.h> pid_t setsid(void); |
A session starts when the user logs on and ends when the user exits. During this period, all processes run by the user belong to this session, unless the process calls the setsid system call.
The system calls setsid without any parameters. After the call, the calling process establishes a new session and takes the lead 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 can call umask to set a File Permission mask. Users can use it to shield some permissions, so as to prevent misoperation from causing excessive permissions to some users.
References
- Linux man pages
- Advanced Programming in the Unix environment by W. Richard Steven S, 1993