linux c programming: process control (1)

A process, including code, data, and resources allocated to the process. The fork() function creates a process that is almost identical to the original process through a system call,

That is, two processes can do exactly the same thing, but if the initial parameters or the variables passed in are different, the two processes can also do different things.

One of the wonderful things about the fork call is that it is only called once, but can return twice. It may have three different return values:
    1) in the parent process, fork returns the process ID of the newly created child process;
    2) in the child process During the process, fork returns 0;
    3) If an error occurs, fork returns a negative value;

After the fork function is executed, if the new process is created successfully, there will be two processes, one is the child process and the other is the parent process. In the child process, the fork function returns 0, in the parent process,

fork returns the process ID of the newly created child process. We can use the value returned by fork to determine whether the current process is a child process or a parent process. The structure of this kind of parent-child process is very similar to that of a linked list. The process forms a linked list. The fpid (p means point) of the parent process points to the process id of the child process. Because the child process has no child process, its fpid is 0.

Take a look at the sample code:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

int global=6;

char buf[]="a write to stdout\n";

 

intmain()

{

    int var;

    pid_t pid;

    pid_t pid_parent;

    var = 0;

    if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1)

        printf("error");

 

    if((pid=fork()) == 0){

        global++;

        var ++;

    }

    else{

        sleep(2);

    }

    printf("pid=%ld,glob=%d,var=%d\n",(long)getpid(),global,var);

    exit(0);

    return 0;

}

The results of the operation are as follows: From the results, we can see that the values ​​of glob and var are different in the parent process and the child process. Operations on glob and var modifications in the child process do not affect the values ​​in the parent process. This is because although the child process obtains a copy of the parent process's data space, heap and stack, it does not obtain the storage space part. That is to say, the parent process and the child process do not share these storage space parts.

Next, continue to look at another upgraded version of the fork function, the vfork function. The difference between the vfork function and the fork function is as follows:

1

fork(): The child process copies the data segment and code segment of the parent process

vfork(): child process shares data segment with parent process

2

fork():  The execution order of the parent and child processes is uncertain

vfork():  Ensure that the child process runs first, and the parent process may be called after calling exit or exec . A deadlock can result if the child process depends on further actions from the parent process before calling exit and exec .

 

The program is modified as follows:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

 

int global=6;

char buf[]="a write to stdout\n";

 

intmain()

{

    int var;

    pid_t pid;

    pid_t pid_parent;

    var = 0;

    if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1)

        printf("error");

 

    if((pid=vfork()) == 0){

        global++;

        var ++;

        _exit(0);

    }

 

    printf("pid=%ld,ppid=%ld,glob=%d,var=%d\n",(long)getpid(),(long)getppid(),global,var);

    exit(0);

    return 0;

}

Running results: 2 points can be found from the results:

1  The operation of adding 1 to the variable by the child process changes the value of the variable in the parent process. Because the child process runs in the address space of the parent process.

There is only one print, not two prints, because the _exit() operation is executed when the child process exits, and the marked I/O stream is closed in exit , then the relevant storage area representing the standard output FILE object will be cleared to 0 . Because the child process borrows the address space of the parent process. So when the parent process resumes and calls printf , no output will be produced. printf returns -1.

wait and waitpid functions:

In the parent-child process described earlier, when the process exits, it sends a SIGCHLD signal to the parent process. By default, the SIGCHLD signal is always ignored. At this time, the process state remains in memory until the parent process uses the wait function to collect state information. , the information will be cleared. Using wait to wait for a child process to terminate is called a recycling process. When the parent process forgets to use the wait() function to wait for the terminated child process, the child process will enter a kind of parentless process. At this time, the child process is a zombie process. Zombie processes do not occupy memory or CPU, and their existence can be ignored on the surface. But in fact, the Linux operating system limits the maximum number of processes that can exist at the same time. If the program cannot clean up the zombie process in the system in time. This will eventually lead to too many processes, and errors will occur when new processes are needed again. So we need to call wait or waitpid in the parent process after the child process exits.

 

Once the process calls wait, it blocks itself immediately, and wait automatically analyzes whether a sub-process of the current process has exited. If it finds such a sub-process that has become a zombie, wait will collect the information of the sub-process. And destroy it completely and return; if no such child process is found, wait will block here until one appears.

The parameter status is used to save some status when the collected process exits, it is a pointer to int type. But if we don't care how the child process dies, and just want to kill the zombie process, (in fact, in most cases, we will think so), we can set this parameter to NULL, Like this:

pid = wait(NULL);

If successful, wait will return the process ID of the collected child 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.

code show as below:

void wait_function_test(){

    pid_t pr,pc;

    pc=fork();

    if (pc < 0){

        printf("error");

    }

    else if(pc == 0){

        printf("this is child process with pid of %d\n",getpid());

        sleep(10);

    }

    else{

        pr=wait(NULL);

        printf("I catched a child process with pid of %d\n",pr);

    }

    exit(0);

}

The execution result is as follows: There is a 10-second waiting time before the result of the second line is printed, which is the time we set to let the child process sleep. Only when the child process wakes up from sleep can it exit normally, which means caught by the parent process

If the value of the parameter status is not NULL, wait will take out the status of the child process when it exits and store it in it. This is an integer value (int) that indicates whether the child process exited normally or was terminated abnormally, and ended normally. The return value of the time, or information such as which signal ended. Since this information is stored in different binary bits of an integer, it is very troublesome to read it by conventional methods, so people have designed a special set of macros (macro) to do this work.

1. WIFEXITED(status) This macro is used to indicate whether the child process exits normally. If so, it will return a non-zero value (please note that although the name is the same, the parameter status here is different from the only parameter of wait-- - the pointer to the integer status, but the integer pointed to by that pointer, remember not to confuse it)

2. WEXITSTATUS(status) When WIFEXITED returns 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) to exit, WEXITSTATUS(status) will return 5; if the child process calls exit(7), WEXITSTATUS(status) will return 7. Note that this value is meaningless if the process does not exit normally, that is, if WIFEXITED returns 0.

The code is modified as follows:

void wait_function_test(){

    pid_t pr,pc;

    int status;

    pc=fork();

    if (pc < 0){

        printf("error");

    }

    else if(pc == 0){

        printf("this is child process with pid of %d\n",getpid());

        exit(3);

    }

    else{

        pr=wait(&status);

        if(WIFEXITED(status)){

            printf("the child process %d exit normally\n",pr);

            printf("the return code is %d.\n",WEXITSTATUS(status));

        }

        else{

            printf("The child process %d exit abnormally",pr);

        }

    }

    exit(0);

}

The parent process accurately captures the return value 3 of the child process and prints it out.

Let's take a look at an upgraded version of waitpid  of the wait function. In the previous function, if a process has several child processes, wait will return as long as one process terminates. If we want to wait for a specific process to terminate, for example knowing the PID of a process , then how to do it, we can call wait and compare the process ID it returns with the ID we expect . If not, save the process ID and continue calling wait , repeatedly until the desired process terminates. This is more troublesome, and using waitpid can wait for a specific process function.

waitpid has two more parameters pid and options that can be controlled by the user , thus providing us with another more flexible way of programming. Let's take a closer look at these two parameters:

pid : As can be seen from the parameter name pid and type pid_t , what is needed here is a process ID . But it has a different meaning here when pid takes different values.

         When pid>0 , only wait for the child process whose process ID is equal to pid . No matter how many other child processes have finished running and exited, as long as the specified child process has not ended, waitpid will keep waiting.

         When pid=-1 , wait for any child process to exit without any restrictions. At this time, waitpid and wait have exactly the same functions.

         When pid=0 , wait for any child process in the same process group. If the child process has joined another process group, waitpid will not pay any attention to it.

         When pid<-1 , wait for any child process in a specified process group whose ID is equal to the absolute value of pid .

options : options provides some additional options to control waitpid , currently only two options WNOHANG and WUNTRACED are supported in Linux , these are two constants, which can be used by connecting them with the "|" operator, for example:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

If we don't want to use them, we can also set options to 0 , like:

ret=waitpid(-1,NULL,0);

If waitpid is called with the WNOHANG parameter , it will return immediately even if no child process exits, instead of waiting forever like wait .

return value and error

The return value of waitpid is slightly more complicated than wait , and there are three cases:

          1. When returning normally, waitpid returns the process ID of the collected child process ;

          2. If the option WNOHANG is set and waitpid finds that there are no exited child processes to collect during the call, it returns 0 ;

          3. If there is an error in the call, return -1 , then errno will be set to the corresponding value to indicate the error;

When the child process indicated by pid does not exist, or the process exists, but is not a child process of the calling process, waitpid will return with an error, and errno is set to ECHILD ;

code show as below:

void wait_function_test(){

    pid_t pr,pc;

    pc=fork();

    if (pc < 0){

        printf("error");

    }

    else if(pc == 0){

        sleep(10);

        exit(3);

    }

    do{

        pr=waitpid(pc,NULL,WNOHANG);

        if(pr == 0){

            printf("no child existed\n");

            sleep(1);

        }

    }while(pr==0);

    if(pr==pc){

        printf("successfully get child %d\n",pr);

    }

    else{

        printf("some error occured\n");

    }

    exit(0);

}

operation result. It can be seen that even if no child process exits, after setting WNOHANG, the parent process will return immediately and will not wait forever like wait .

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325944818&siteId=291194637