1. Creation of processes
There are four types of functions that create sub-processes under Linux: System (), fork (), exec* (), Popen ()
1.1. System functions
Prototype:
#include <stdlib.h>
int system (const char *string);
The system function executes the command specified by the string by calling the shell program/bin/sh–c, which is internally by calling Execve ("/bin/sh",..) function to implement the. After the child process is created through the system, the original and child processes run each other, with fewer associations. If the system call succeeds, 0 is returned.
Example:
#include <stdio.h>
#include <stdlib.h>
int main ()
{
System ("Ls-l"); System ("clear"), indicating clear screen
return 0;
}
In addition, the parameters after the system function can also be an executable program, for example: System ("/HOME/WANGXIAO/1"), if you want to execute the system after the process, do not block the current process, you can use & will/home/ WANGXIAO/1 to run in the background.
1.2. Fork function
Prototype:
#include <unistd.h>
pid_t fork (void);
A function that is very important when you fork a function in Linux, which creates a new process from an existing process. The new process is a child process, and the original process is the parent process. It differs from other functions in that it performs a return of two values at a time. Where the return value of the parent process is the process number of the child process, and the child process returns a value of 0. Returns-1 if an error occurs. Therefore, you can determine whether a parent or child process is the result of a return value.
The fork function creates a child process: a child process that is derived from the fork function is a replica of the parent process that inherits the address space of the process from the parent process, including the process context, process stack, memory information, open file descriptor, signal control setting, process priority, process group number, current working directory, root directory, resource limit, control terminal, and the child process is unique only its process number, resource usage and timers. After the child process is created by this method, both the original process and the child process are returned from the function fork, and each continues to run down, but the fork return value of the original process is different from the fork return value of the child process, and in the original process, the fork returns the PID of the child process, and in the child process, fork returns 0. If fork returns a negative value, it indicates that the child process was created failed. (Vfork function)
Example:
#include <stdlib.h>
#include <unistd.h>
int main ()
{
printf ("Parent process id:%d\n", getpid ());
pid_t IRet = fork ();
if (IRet < 0) {//Error
printf ("Create child process fail!\n");
}else if (IRet = = 0) {//= child process
printf ("Child process id:%d ppid:%d\n", Getpid (), Getppid ());
}else{//represents the parent process
printf ("Parent process Success,child id:%d\n", IRet);
}
return 0;
}
Some people may have questions about how the IF and ELSE statements are executed, in contradiction to our previous if...else structure. This is equivalent to two copies of the main function code, and one of the operations is the IF (IRet = = 0), and the other is the case of the Else (parent). So you can output 2 words. Question: How do I create a sibling process and a grandchild process?
1.3. exec function Family
Exec* consists of a set of functions
int execl (const char *path, const char *arg, ...)
The working process of the EXEC function family is completely different from fork, where fork is copying a copy of the original process, while the EXEC function overwrites the existing process space with the program specified by the first parameter of exec (that is, after executing the EXEC family function, all code behind it is not executing).
Path is the full path name that includes the execution file name
ARG is the command-line argument for the executable, multiple uses, and the last parameter must be NULL for the split note.
For example, there is an adder program that accepts two numbers from the command line and outputs its sum.
The code is as follows:
Add.c
#inclue <stdio.h>
#include <string.h>
int main (int argc, char * argv[])
{
Int a = atoi (argv[1]);
Int B = atoi (argv[2]);
Print F ("%d +%d =%d", A, B, a + B);
Return 0;
}
Compile the connection to get Add.exe.
Gcc–o Add.exe ADD.C
The Add.exe program is then called in Main.exe to calculate the and of 3 and 4.
The source program for MAIN.C is,
Main.c
#include <stdio.h>
#include <string.h>
Int Main ()
{
Execl ("./add.exe", "Add.exe", "3", "4", NULL):
Return 0;
}
Compile connected,
Gcc–o Main.exe MAIN.C
Then run:/main.exe.
The Add.exe program is started by EXECL during the run of Main.exe.
2.4popen function
The Popen function is similar to the system function, and differs from the system in that it works with pipelines. The prototypes are:
#include <stdio.h>
FILE *popen (const char *command, const char *type);
int Pclose (FILE *stream);
command is the full path and execution parameters of the executable file;
The type optional parameter is "R" or "W", and if "w", then Popen returns the file stream as the standard input stream of the new process, that is, stdin, if "R", then popen the returned file stream as the standard output stream of the new process.
If type is "R" (that is, the command command executes the output as the result of the current process). The output of the called program can be used by the calling program, using the file* file stream pointer returned by the Popen function to read the output of the called program through the commonly used Stdio library functions (such as fread), if Tpye is "w", (that is, the output of the current process is the result of the command's input). The calling program can send data to the callee using Fwrite, and the callee can read the data on its own standard input.
Pclose wait for the new process to end, not to kill the new process.
Example:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main ()
{
FILE *READ_FP;
Char Buffer[bufsiz + 1];
int chars_read;
memset (buffer, ' n ', sizeof (buffer));
READ_FP = Popen ("Ps-ax", "R");
if (read_fp! = NULL) {
Chars_read = fread (buffer, sizeof (char), Bufsiz, READ_FP);
while (Chars_read > 0) {
Buffer[chars_read-1] = ' + ';
printf ("reading:-\n%s\n", buffer);
Chars_read = fread (buffer, sizeof (char), Bufsiz, READ_FP);
}
Pclose (READ_FP);
Exit (exit_success);
}
Exit (Exit_failure);
}
Process Control and termination 1.1. Control of the process
When you start a child process with the fork function, the child process has its own life and will run independently.
If the parent process exits before the child process, the child process becomes an orphan process , which is automatically taken over by the PID 1 process (that is, init). After the orphan process exits, its cleanup work is automatically processed by the ancestor process init. However, before the INIT process cleans up the child process, it consumes the resources of the system, so try to avoid it.
Example1: Write an orphan process:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
Main ()
{
pid_t pid = fork ();
if (PID = = 0)
{
while (1);
}
Else
{
Exit (10);
}
}
With Ps–ef you can see that the child process is running at this time and the parent process is process number 1th.
If the child process exits first, the system does not automatically clean up the child process's environment, and the parent process must call the wait or Waitpid function to complete the cleanup, and if the parent process does not clean up, the child process that has exited will become a zombie process (defunct) , if there are too many zombie (zombie) processes in the system, it will affect the performance of the system, so the zombie process must be processed.
Function Prototypes:
#include <sys/types.h>
#include <sys/wait.h>
pid_t Wait (int *status);
pid_t waitpid (pid_t pid, int *status, int options);
Both wait and Waitpid will pause the parent process, wait for a child process that has exited, and perform cleanup work;
The wait function randomly waits for a child process that has exited and returns the PID of the subprocess;
Waitpid the child process that waits for the specified PID, or 1 for all child processes.
The status parameter is the outgoing parameter, which holds the exit status of the child process, and typically uses the following two macros to obtain the status information:
Wifexited (Stat_val) If the child process ends normally, it takes a value other than 0.
Wexitstatus (stat_val) if wifexited nonzero, it returns the exit code of the child process
The options are used to change the behavior of waitpid, the most common of which is Wnohang, which indicates that the execution of the caller will not be suspended, regardless of whether the child process exits immediately.
Example1: Write a Zombie process:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
Main ()
{
pid_t pid = fork ();
if (PID = = 0)
{
Exit (10);
}
Else
{
Sleep (10);
}
}
Quickly view the zombie process that found Z by using Ps–aux.
Example2: Avoid Zombie processes: (Wait () function)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
Main ()
{
pid_t pid = fork ();
if (PID = = 0)
{
Exit (10);
}
Else
{
Wait (NULL); Null means waiting for all processes
Sleep (10); Sleep is usually followed by wait, or the zombie process will appear.
}
}
EXAMPLE3: Using signal processing to avoid zombie processes: (Wait () function)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void signchildpsexit (int isignno)
{
int Iexitcode;
pid_t pid = Wait (&iexitcode); Wait for the child process to exit, no this sentence will appear zombie process
printf ("signno:%d Child%d exit\n", isignno,pid);
if (wifexited (Iexitcode))
{
printf ("Child exited with code%d\n", Wexitstatus (Iexitcode));
}
Sleep (10);
}
int main ()
{
Signal (SIGCHLD, signchildpsexit);
printf ("Parent process id:%d\n", getpid ());
pid_t IRet = fork ();
if (IRet = = 0)
Exit (3);
}
Example4:waitpid implementation
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void signchildpsexit (int isignno)
{
int Iexitcode;
pid_t pid = waitpid ( -1,null,0); Represents the wait for any process and blocks. If you change to Waitpid ( -1,null,wnohang), it is similar to no write waitpid effect, at which time the parent process is not blocked
printf ("signno:%d Child%d exit\n", isignno,pid);
if (wifexited (Iexitcode))
{
printf ("Child exited with code%d\n", Wexitstatus (Iexitcode));
}
Sleep (10);
}
int main ()
{
Signal (SIGCHLD, signchildpsexit);
printf ("Parent process id:%d\n", getpid ());
pid_t IRet = fork ();
if (IRet = = 0)
Exit (3);
}
1.2. Termination of the process
There are 5 ways to terminate a process:
L The natural return of the main function;
L Call the Exit function
L CALL the _exit function
L CALL the Abort function
L received a signal that could cause the process to terminate CTRL + C SIGINT ctrl+\ sigquit
The first 3 methods are normal termination, and the latter 2 are abnormal termination. However, either way, the process terminates with the same closed file, freeing up resources such as memory usage. Just the latter two termination causes the program to have some code that does not perform properly such as object destruction, execution of atexit functions, and so on.
The exit and _EXIT functions are used to terminate the process. When the program executes to exit and _exit, the process unconditionally stops all remaining operations, clears the various data structures including the PCB, and terminates the operation of the program. But they are different, the difference between exit and _exit:
The most important difference between the Exit function and the _exit function is that the Exit function checks the opening of the file before exiting, and writes the contents of the file buffer back to the file, which is the "clean I/O buffer" in the diagram.
Because of the standard library of Linux, there is an operation called "Buffered I/O", which is characterized by a buffer in memory for each open file. Each time a file is read, a number of records will be read sequentially, so that the next time you read the file can be read directly from the memory buffer, as well, each time you write a file, it is only written in memory buffer, and so on to meet certain conditions (such as a certain number or encountered a certain character), The contents of the buffer are then written to the file once. This technique greatly increases the speed of file read and write, but it also brings trouble to programming. For example, there are some data that have been written to the file, in fact, because they do not meet the specific conditions, they are only in the buffer, then use the _exit function to directly shut down the process, the data in the buffer is lost. Therefore, we recommend that you use the Exit function if you want to ensure the integrity of your data.
The exit and _exit functions are prototyped:
Header files for #include <stdlib.h>//exit
Header files for #include <unistd.h>//_exit
void exit (int status);
void _exit (int status);
Status is an integer parameter that can be used to pass the state at the end of the process. Generally speaking, 0 means the normal end; other values indicate an error and the process is not properly terminated.
Examples of example1:exit are as follows:
#include <stdio.h>
#include <stdlib.h>
int main ()
{
printf ("Using exit...\n");
printf ("The content in buffer");
Exit (0);
}
You can see that the exit function is called, and the records in the buffer are output normally.
Examples of example2:_exit are as follows:
#include <stdio.h>
#include <unistd.h>
int main ()
{
printf ("Using _exit...\n");
printf ("The content in buffer");
_exit (0);
}
It can be found that the final output does not have the content in buffer, which indicates that the _EXIT function cannot output a record in the buffer.
Linux Process Control (ii)