Linux system call to me (3) - Zombie process

xiaoxiao2021-03-06  34

Technical Article: Linux System Call and I learn (3) - Zombie Process Passengers In the previous article, we have learned the concept of parent processes and sub-process and have mastered the usage of the system calling exit, but maybe Few people realize that after a process calls EXIT, the process is not immediately disappeared, but leaving a data structure called zombie process (Zombie). Author: This article taken from the town of Ray: IBM DW China the previous article, 2002 September 24, we have to understand the concept of parent and child processes, and has mastered the system call exit usage, but probably few people realize After a process calls EXIT, the process does not disappear immediately, but leaving a data structure called zombie process (Zombie). In the five states of the Linux process, the zombie process is a very special. It has given up almost all memory space, no executable code, can not be scheduled, only in the process list, record the process Information such as exit status is for other processes, in addition to this, the zombie process no longer occupies any memory space. From this point of view, though the zombie process has a cool name, but its influence is far from the real zombie brothers, the real zombies can always feel horrible, and the zombie process has left some The information that will hang is unpredictable. Perhaps the readers are more curious about this new concept, let us see what the zombie process in Linux is like. When a process has exited, its parent process has not called the system calling WAIT (later), it will always maintain a zombie, using this feature, let's write a simple small program:

/ * zombie.c * /

#include

#include

Main ()

{

PID_T PID;

PID = fork ();

IF (PID <0) / * If an error * /

Printf ("Error Occurred! N");

ELSE IF (pid == 0) / * If it is a child process * /

exit (0);

ELSE / * If it is a parent process * /

Sleep (60); / * Sleep 60 seconds, during this time, what the father process can't do * /

Wait (NULL); / * Collect zombie process * /

}

The role of the SLEEP is to let the process sleep specify the number of seconds. In these 60 seconds, the child process has exited, and the parent process is busy sleeping, it is impossible to collect it, so we can keep the sub-process 60 seconds status. Compile this procedure:

$ cc zombie.c -o zombie

The background runs program so that we can perform the next command:

$ ./zombie &

[1] 1577

Column processes within the system:

$ PS -AX

...

1177 PTS / 0 s 0:00 -bash

1577 PTS / 0 s 0:00 ./zombie

1578 PTS / 0 Z 0:00 [Zombie

]

1579 PTS / 0 r 0:00 ps -ax

See the middle "z"? That is the symbol of the zombie process, which means that the process 1578 is now a zombie process. We have already learned the system calling exit, and its role is to make the process exit, but it is only limited to turning a normal process into a zombie process, and it cannot be completely destroyed. The zombie process has little effect on other processes, does not take up the CPU time, and the consumed memory is almost ignored, but it stays there, but people feel very uncomfortable. And the number of processes in the Linux system is limited. In some special cases, if there are too many zombie processes, it will affect the generation of new processes. So how do we destroy these zombie processes? Let's take a look at the zombie process. We know that Linux and UNIX have always cut the relationship between the shears, the concept of zombie process is also inherited from UNIX, and UNIX's pioneers design this thing is not because It's boring to boring for other programmers. The zombie process saves many information on programmers and system administrators, first of all, how did this process die? Is it normal to quit, or there is a mistake, or is it forced by other processes? Second, how much is the total system CPU time and total user CPU time occupied by this process? The number of page errors and the number of received signals. These information are stored in the zombie process. Imvolution if there is no zombie process, the process is exited, all related information is immediately invisible, and at this time, the programmer or system administrator needs to be used, just do it. So how do we collect this information and end these zombie processes? It is necessary to rely on WaitPid calls and Wait calls you want to talk. The roles of both are information left by the zombie process, while making this process completely disappeared. Below, these two calls are described in detail. Wait

Introduction WAIT's function prototype is:

#include

/ * Provide the definition of type PID_T * /

#include

PID_T WAIT (INT * STATUS)

Once the process calls Wait, immediately block yourself, automatically analyze if WAIT is automatically analyzed that the current process has exited, if it finds such a sub-process that has become zombie, Wait will collect this sub-process information, And returned it completely; if you didn't find such a child process, Wait would have been blocked here until there is an emergence. The parameter status is used to save some status when the collected process exits, which is a pointer to the INT type. But if we don't care about this sub-process, I just want to destroy this zombie process, and in fact, we will think so, we can set this parameter null, Just like this:

PID = Wait (NULL);

If successful, Wait will return the process ID of the collected sub-process, if the calling process has no child process, the call will fail, at this time, Wait returns -1, and Errno is set to echild. In the actual combat, let us use an example to actually apply WAIT calls, and the program is used in the program to call fork. If you are not familiar with it or have been forgotten, please refer to the previous article process management related system call (1) .

/ * Wait1.c * /

#include

#include

#include

#include

Main ()

{

PID_T PC, PR;

PC = fork ();

IF (PC <0) / * If an error * /

Printf ("Error Ocurred! N"); ELSE IF (PC == 0) {/ * If it is a child process * /

Printf ("This Is Child Process with PI% DN", getPid ());

Sleep (10); / * Sleep 10 seconds * /

}

Else {/ * If it is a parent process * /

Pr = Wait (null); / * Wait here * /

Printf ("i catched a child process with pid of% dn"), PR);

}

exit (0);

}

Compile and run:

$ cc wait1.c -o wait1

$ ./wait1

This is child process with pid of 1508

I catched a child process with pid of 1508

It can be clearly noted that there is 10 seconds before the results of the second line of results, this is the time we set to make the child process sleep, only the child has woke up from sleep, it can only quit normal, Can be captured by the parent process. In fact, we have long time for the time to sleep, the parent process will wait, if you are interested, you can try to modify this value and see what the results will appear. Parameter status If the value of the parameter status is not null, Wait takes out the status when the child process exits and stores it, which is an integer value (int), pointing out that the child process is normal and is endless (one The process can also be ended by other processes, we will introduce in later articles), and return values ​​at the end of the normal end, or which signal ends. Since this information is stored in a different binary bit of an integer, it is very troublesome with a conventional method, and people have designed a special macro (Macro) to complete this work. Let's learn the most Commonly used: ● Wifexited (status) This macro is used to indicate whether the child process is normal. If so, it will return a non-zero value. (Please note that although the name is the same, the parameter status is not only the only parameter of WAIT - pointing to the integer's pointer status, but the integer pointing to the pointer, not to make it.) ● WexitStatus (status) When WifexITED When returning a non-zero value, we can use this macro to extract the return value of the child process. If the child process calls EXIT (5) exits, WexitStatus will return 5; if the child process calls EXIT (7), WexitStatus ) Will return 7. Please note that if the process is not normal exit, it means that WifexITED returns 0, this value is meaningless. The following examples come to actually work in combat:

/ * wait2.c * /

#include

#include

#include

Main ()

{

Int status;

PID_T PC, PR;

PC = fork ();

IF (PC <0) / * If an error * /

Printf ("Error Ocurred! N");

ELSE IF (PC == 0) {/ * child process * /

Printf ("This Is Child Process with PI% D.N", getPID ());

EXIT (3); / * child process returns 3 * /

}

Else {/ * Parent Process * / Pr = Wait (& Status);

IF (Wifexited) {/ * If WifexITED returns a non-zero value * /

Printf ("The Child Process% D EXIT NORMALLY.N", PR);

Printf ("THE RETURN CODE IS% D.N", WexitStatus (STATUS));

} else / * If WifexITED returns zero * /

Printf ("The Child Process% D EXIT ABNORMALLY.N", PR);

}

}

Compile and run:

$ cc wait2.c -o wait2

$ ./wait2

This Is Child Process with PID of 1538.

The Child Process 1538 exit normally.

The return code is 3.

The parent process accurately captures the return value of the child process, and print it out. Of course, the macro that handles the process exits the status, but most of them are rarely used in the usual programming, and they are not a waste of space here. Interested readers can refer to Linux Man Pages. Go to understand their usage. Process Synchronization Sometimes, the result of the operation of the parent process requires the next operation, or the function of the child process is a prerequisite for the parent process (such as: sub-process setup file, while the parent process writes data At this time, the parent process must stop in a certain position, waiting for the sub-process to run, and if the parent process does not wait, it can be seen, it can be imagined, it will appear very chaotic. This situation is called synchronization between processes, more accurately, this is a special case of process synchronization. Process synchronization is to coordinate more than 2 processes so that it will be performed in order to arrange the order. Solving process synchronization issues have a more common way, we will introduce later, but for this case, you can use the WAIT system call to simply resolve. Please see the following programs:

#include

#include

Main ()

{

PID_T PC, PR;

Int status;

PC = fork ();

IF (PC <0)

Printf ("ERROR OCCURED ON forking.n");

ELSE IF (PC == 0) {

/ * Sub-process work * /

exit (0);

} else {

/ * Work of the parent * /

PR = Wait (& status);

/ * Use the result of the child process * /

}

}

This program is just an example, but it does not really take it, but it explains some problems. First, when the Fork calls successfully, the father and son process do everything, but when the work of the Father's process, I need to use the child. When the result of the process, it stops calling WAIT, waiting until the sub-process runs, then the utilization of the sub-process will continue to execute, so that we solve our proposed process synchronization problem.

Waitpid

Introduction The prototype in the WAITPID system call in the Linux function library is:

#include

/ * Provide the definition of type PID_T * /

#include

PID_T WAITPID (PID_T PID, INT * STATUS, INT OPTIONS)

In essence, the system calls WaitPID and WAITs are identical, but WaitPID has two parameters PID and Options that can be controlled by users, thus providing another more flexible way for us. Let's take a detailed introduction to these two parameters: ● PID can be seen from the name PID and type PID_T of the parameters, and a process ID is required. However, when PID takes different values, there is a different meaning here. When PID> 0, only the process ID is equal to the child process of the PID, no matter how many child processes have ended exit, as long as the specified child process has not ended, WaitPid will wait. When PID = -1, wait for any sub-process to exit without any restrictions, at this time, WaitPID and WAIT are exactly the same. When PID = 0, wait for any sub-process in the same process group, if the child process has joined other process groups, WaitPID will not pay anything. When PID <-1, wait for any sub-process in a specified process group, the ID of this process group is equal to the absolute value of the PID. ● Options Options provides some additional options to control Waitpid, currently only two options for WNOHANG and Wuntraced in Linux, which is two constants that can be used with "|" operators, such as: Ret = WaitPid (-1, NULL, WNOHANG | Wuntraced;

If we don't want to use them, you can also set Options to 0, such as:

Ret = WaitPID (-1, null, 0);

If you use the WNOHANG parameter to call WaitPID, even if there is no child process to exit, it will return immediately, not like Wait to wait forever. The Wuntraced parameters, due to some knowledge of tracking debugging, adding very little, here, there is not much pen ink, interested readers can check the relevant materials themselves. Seeing this, a smart reader may have seen the clues - Wait is not a packaging Waitpid? Yes, look

/include/unistd.h file 349-352 line will find the following block:

Static Inline PID_T WAIT (INT * WAIT_STAT)

{

Return WaitPID (-1, Wait_stat, 0);

}

Return value and error WaitPID's return value is slightly complex than Wait, a total of 3 cases: ● When returned, waitPid returns the process ID of the collected sub-process; ● If you set the option WAITPID discovery There is no exit child process to collect, then return 0; ● If the call is in the call, return -1, then Errno will be set to indicate an error; when the child process indicated by the PID does not exist, or This process exists, but not a child process that calls the process, WaitPid will return, then Errno is set to echild;

/ * WaitPid.c * /

#include

#include

#include

Main ()

{

PID_T PC, PR;

PC = fork ();

IF (PC <0) / * If Fork error * /

Printf ("ERROR OCCURED ON forking.n");

Else IF (PC == 0) {/ * If it is a child process * /

Sleep (10); / * Sleep 10 seconds * / exit (0);

}

/ * If it is a parent process * /

Do {

Pr = Waitpid (PC, NULL, WNOHANG); / * Using the WNOHANG parameter, WaitPid will not wait here * /

IF (Pr == 0) {/ * If there is no collection of sub-process * /

Printf ("No Child Exitedn");

Sleep (1);

}

} while (pr == 0); / * does not collect the sub-process, go back to continue try * /

IF (Pr == PC)

Printf ("Successful Get Child% DN", PR);

Else

Printf ("Some Error OccureDN);

}

Compile and run:

$ cc waitpid.c -o waitpid

$ ./waitpid

No child exited

No child exited

No child exited

No child exited

No child exited

No child exited

No child exited

No child exited

No child exited

No child exited

Successful Get Child 1526

After 10 failure attempts, the parent process finally collected the sub-process of exiting. Because this is just an example program, it is inconvenient to write too complicated, so we let the parent process and sub-processes sleep for 10 seconds and 1 second, represent them for 10 seconds and 1 second. The parent-child process has work to do, and the parent process uses the job's short-term, if you look out, if you quit it. There may be a lot of readers to start reading from this series of articles, and there is a big doubt here: since all new processes are generated by Fork, and the child process generated by the Fork is almost exactly the same. Do you mean that all the processes in the system should be exactly the same? Moreover, as our common sense, when we execute a program, the content of the new process should be the content of the program. Is we understand? Obviously, to solve these doubts, we must mention the EXEC system call we need to introduce. Introduction is that the exec system call, in fact, in Linux, does not exist in the form of exec (), EXEC refers to a set of functions, a total of 6, respectively:

#include

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 Execve (const char * path, char * const argv [], char * const envp [];

Only Execve is a true system call, and others are in this basis. Based on the package function. The effect of the EXEC function family is to find the executable file according to the specified file name, and use it to replace the contents of the calling process, in other words, it is to execute an executable file inside the calling process. The executable here can be either a binary file or a script file that can be performed under any Linux. Unlike the general situation, the execution of the Exec function family will not return after success, because the entity of the process, including code segments, data segments, and stacks have been replaced by new content, leaving only some surfaces such as process IDs. Information is still maintained, quite a "golden shell" in "36 counts". It seems that the old body has been injected into the new soul. Only if the call failed, they will only return a -1, and execute it from the original program call point. Now we should understand how Linux perform a new program, every process thinks you can't make any contribution to the system and support, he can play a last minute, call any EXEC, let yourself with new The appearance is reborn; or, more common cases, if a process wants to perform another program, it can make a new process, then call any EXEC, which seems to have a new process by executing the application. same. In fact, the second situation is applied so universal, so that Linux is specifically optimized, we already know, the Fork will copy all the contents of the calling process to the newly generated sub-process, these copies The action consumes time, and if we will call EXEC immediately after Fork, these hard-copying things will be erased immediately, which looks very difficult, so people have designed a "write time copy ( Copy-on-write "" technology, make the content of the parent process after the end, but to copy when it is real practical, so if the next statement is exec, it will not be white and useless. It improves efficiency. Slightly deep into the six functions seems to be very complicated, but in fact, both the role or usage is very similar, only very different. Before learning them, let's take a look at the main function we have learned. The following MAIN function may be in a number of things: int Main (int Argc, char * argv [], char * envp [])

It may be different from most textbooks, but in fact, this is the true form of the MAIN function. The parameter Argc pointed out that the number of command line parameters when running the program, array Argv stores all command line parameters, and array ENVP stores all environment variables. The environment variable refers to a set of values. After logging in, many applications need to rely on it to determine some details of the system, our most common environment variable is PATH, indicating where to search for applications, Such as / bin; Home is also a relatively common environment variable, which points out our personal directory in the system. Environment variables typically exist in the form of "xxx = xxx" in strings, XXX represents the variable name, XXX represents the value of the variable. It is worth mentioning that all Argv arrays and ENVP arrays are pointers that point to strings, which represent an end of array with a NULL element. We can watch what you have passed to Argc, Argv and Envp, by this program:

/ * main.c * /

INT Main (int Argc, char * argv [], char * envp [])

{

Printf ("N ### Argc ### N% DN", Argc);

Printf ("n ### argv ### n");

While (* argv)

Printf ("% SN", * (Argv ));

Printf ("N ### Envp ### n");

While (* ENVP)

Printf ("% SN", * (ENVP ));

Return 0;

}

Compile it:

$ cc main.c -o main

When running, we deliberately add a few command line parameters without any effect:

$ ./main -xx 000

### argc ###

3

### Argv ###

./main

-xx

000

### ENVP ###

PWD = / home / lei

Remotehost = dt.laser.com

Hostname = localhost.localdomain

Qtdir = / usr / lib / qt-2.3.1

Lessopen = | /usr/bin/lesspipe.sh% s

Kdedir = / usr

User = LEI

LS_COLORS =

MachType = i386-redhat-linux-gnu

Mail = / var / spool / mail / lei

INPUTRC = / etc / inputrc

LANG = en_us

Logname = lei

SHLVL = 1

Shell = / bin / bash

HostType = i386

Ostype = Linux-GNU

Histsize = 1000

Term = ANSI

HOME = / home / lei

Path = / usr / local / bin: / usr / usr / bin: / home / lei / bin

_ =. / main

We see that the program will "./main" as the first command line argument, we have three command line parameters. This may be different from what everyone usually habits, and be careful not to make mistakes. Now take a look at the Exec function family, first focus on Execve:

Int Execve (const char * path, char * const argv [], char * const envp [];

Compare the complete form of the main function, see if there is a problem? Yes, the ARGV and ENVP in these two functions are a complete relationship with a complete correspondence. EXECVE 1st parameter PATH is the full path to be executed, and the second parameter argv is transmitted to the command line parameters of the executed application, and the third parameter ENVP is the environment variable that is transmitted to the executed application. Try to see that these 6 functions can also find that the top 3 functions are starting with Execl, and the last three are beginning with Execv, and their difference is that the function started by the Execv is "char * argv []" In this way, the command line parameters are transmitted, and the function started with the Execl uses us more easy to habits, and the parameters are listed one, and then the end is ended in one null. The role of NULL here is the same as NULL in the argv array. In all 6 functions, only Execle and Execve use char * envp [] to deliver environment variables, the other 4 functions have no such parameters, which does not mean that they do not pass environment variables, these four functions will put the default Environment variables do not do any modifications to the executed application. Execle and Execve use the specified environment variable to replace those defaults. There are also 2 functions execlp and execvp, which look, they are very different from EXECL and EXECV, and the facts are true, except for the four functions other than Execlp and Execvp, their first A parameter PATH must be a complete path, such as "/ bin / ls"; and the first parameter file of Execlp and Execvp can be simply just a file name, such as "LS", these two functions can automatically go to the environment Variable Path's directory to find it. The introduction of practical knowledge is almost, then let's take a look at the actual application: / * EXEC.C * /

#include

Main ()

{

Char * envp [] = {"Path = / TMP",

"User = lei",

"Status = tempeig",

Null};

Char * argv_execv [] = {"Echo", "Excuted by Execv", NULL};

Char * argv_execvp [] = {"echo", "executed by execvp", null};

Char * argv_execve [] = {"env", null};

IF (fork () == 0)

IF (Execl ("/ Bin / Echo", "Echo", "Executed by Execl", NULL <0)

Perror ("Err on Execl");

IF (fork () == 0)

IF (Execlp ("Echo", "Echo", "EXECUTED BY EXECLP", NULL <0)

Perror ("Err on Execlp");

IF (fork () == 0)

IF (Execle ("/ USR / BIN / ENV", "ENV", NULL, ENVP <0)

"" Err on Execle ");

IF (fork () == 0)

IF (Execv ("/ bin / echo", argv_execv) <0)

Perror ("Err on Execv");

IF (fork () == 0) IF (Execvp ("echo", argv_execvp) <0)

PERROR ("Err on Execvp");

IF (fork () == 0)

IF (Execve ("/ USR / BIN / ENV", Argv_execve, ENVP) <0)

Perror ("Err on Execve");

}

Two Linux common system commands, ECHO, and ENV are called in the program. Echo will print out the back of the command line parameters, and the ENV is used to list all environment variables. Since the order in which each sub-process execution cannot be controlled, there is a possibility that a comparison of more confusing outputs - the results of each sub-process printing together, rather than strictly follow the order listed in the procedure. Compile and run:

$ CC EXEC.c -o EXEC

$ ./exec

Executed by EXECL

PATH = / TMP

User = LEI

Status = Testing

Executed by EXECLP

Excuted by EXECV

Executed by EXECVP

PATH = / TMP

User = LEI

Status = Testing

Sure enough, the result of the Execle output ran to the Execlp. In the usual programming, if you use the Exec function family, you must remember to add the wrong determination statement. Because of other system calls, Exec is easily injured, the location of the executed file, the permissions, etc. can result in the failure of the call. The most common error is: Can't find the file or path, at this time, Errno is set to Enont; array argv and envp forget to end with NULL, at this time, errno is set to efault; there is no runtime to perform files, at this time Errno Set to EACCES.

A life of the process

Let me use some image metaphor to make a small summary of a short life of the process: With a Fork, a new process is falling, but it is just a clone of the old process. Then, with the EXEC, the new process is relocated, and starting from home and starting career serving the people. People have been born and sick. The process is the same. It can be nature and death, that is, the last "}" running to the main function, which is easy to leave us; it can also be committed, suicide has 2 ways, one is to call the EXIT function One is to use Return in the main function, no matter which way, it can leave the suicide note, put it in the return value; it can also be murdered, and other processes end his life. After the process is dead, it will leave a zombie, Wait and Waitpid act as a corpse, push zombies into creating, so that it will eventually be invisible. This is the complete life of the process.

summary

This article focuses on the system calling WAIT, WAITPID, and EXEC function, the introduction of system calls related to process management is here, in the next article, the next article, is also the last article related to the system-related system calls, We will turn around you through two cool practical examples. "From IBM DW China"

转载请注明原文地址:https://www.9cbs.com/read-64432.html

New Post(0)