System level programming

This class... Talks a little too much. It may be a little miscellaneous to sum up. 1, Complement the tail of process management. 2, Then process synchronization is introduced. 3, The pipeline communication between some processes is mentioned. I'll review and summarize one by one.

Process management CLOSEOUT

When the parent process creates a child process through fork(), because the child process completely copies the program of the parent process, when we need to execute some instructions in the child process, the parent process must be set first. Therefore, in order to solve this problem, a function of exec family is introduced. Simply review it. I have to make up my blog at night after a full class. It's harmful!!

exec family

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[]);

The exec family has six functions with similar functions. Their ultimate purpose is to execute commands. There are two sub categories,

  • execl class: the function will pass in parameters in the form of enumeration. Since the length of the parameter list is uncertain, sentinel NULL should be used to indicate the end of enumeration;
  • execv class: the function will pass the parameters in the form of parameter vector table, char * argv [] to pass the parameters used during file execution, and the last parameter in the array is NULL;

Remind me that some parameters are path and some parameters are file. There is a slight difference between the two. When the parameter is path, the path name of the command is passed in; When the parameter is file, the name of the executable file passed in.

As for how to use several functions, you can see how to use and what role they play by looking at the following cases.

Case 4

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    pid_t tempPid;
    tempPid=fork();
    if(tempPid == -1){   
        perror("fork error");
        exit(1);
    } else if(tempPid > 0) {   
        printf("parent process:pid=%d\n", getpid());
    } else {   
        printf("child process:pid=%d\n", getpid());
        //execl("/bin/ls","-a","-l","./",NULL);	//①
        //execlp("ls","-a","-l","./",NULL);	//②
        char *arg[]={"-a","-l","./", NULL};	//③
        execvp("ls", arg);
        perror("error exec\n");
        printf("child process:pid=%d\n", getpid());
    } //of if  
    return 0;
} //of main

The above is the approximate format. You can use the ls command to view the files in the folder, view the files by path, and so on. In addition to ls, you can also use other commands, as long as bash can execute them in the exec family.

exit function

In fact, anyone who has learned programming should know something about exit,

void exit(int status);

The parameter status is the status. When it is 0, it exits normally and other values are abnormal. However, exit is an integrated function, that is, it is encapsulated. There is a core function inside the exit package_ exit(), when a function starts with an underscore, the function will be closer to the bottom layer. So exit () and_ What is the difference between exit () and the purpose of encapsulation?

  • _ exit: the system will unconditionally stop the operation, terminate the process, and clear the memory space used by the process and various data structures in the kernel;
  • Exit: Yes_ Exit is wrapped and called_ Before exit(), check the opening of the file and write the contents of the buffer back to the file. Relatively speaking, exit ratio_ Exit is more secure

Therefore, in a word, packaging is to better and safer judge whether the exit is feasible and safe. Therefore, it is summarized as follows:

Security: exit >_ exit

Closer to the bottom layer:_ exit > exit

Difference: exit requires judgment and processing_ Exit unconditional exit.

The difference between orphan process and zombie process

The last article has summarized my understanding of orphan process and zombie process, so I won't write it and directly copy the teacher's, as follows:

Orphan process: the parent process is responsible for recycling the child process. If the parent process exits before the child process exits, the child process will become an orphan process. At this time, the init process will complete the recycling of the child process instead of the parent process;

Zombie process: after calling the exit function, the process will not disappear immediately, but leave a data structure called zombie process. It gives up almost all the memory occupied by the process before exiting. It has neither executable code nor scheduling. It just keeps a position in the process list to record the exit status of the process and other information for the parent process to recycle. If the parent process does not recycle the code of the child process, the child process will always be in zombie state.
 

Conclusion: the orphan process is waiting for init recovery. If the program ends and exits but is not recovered, it is a zombie process. My understanding is that when the orphan process ends but is not recovered by init, it is also a zombie process. As for the harm of the zombie process, I also talked about it in the previous article.

Process synchronization

Process synchronization (Baidu's definition): in a multiprogramming environment, processes are executed concurrently, and there are different mutual constraints between different processes.

Key points: there are mutual constraints in different processes. In order to prevent the parent process from preempting the CPU and turning the child process into an orphan process, we used sleep blocking for a period of time. Of course, we need to use a suspend function to standardize the synchronization of processes.

wait function

pid_t wait(int *status);

Function: suspend the process, and the process enters the blocking state until the sub process becomes a zombie state. If the exit information of the sub process is captured, it will turn to the running state, and then recover the sub process resources and return; If there is no child process that becomes a zombie state, the wait function will keep the process blocked. If the current process has multiple child processes, as long as a child process that becomes a zombie state is captured, the wait function will return to the execution state.

Parameter: the parameter status is a pointer of type int *, which is used to save the state information when the child process exits. Normally, this parameter is set to NULL, which means that you don't care how to terminate the process.

Status:

  • Success: returns the process id of the child process;
  • Failure: Return - 1, errno is set to ECHILD.

Let's take a look at the case to have a deeper understanding of the wait.

Case 1

Content: if the child process p1 is the prerequisite process of its parent process p, synchronize the process based on the wait function.

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
	pid_t tempPid, tempW;
	tempPid = fork();
	if(tempPid == -1){
		perror("fork error");
		exit(1);
	}else if(tempPid == 0){//child
		sleep(3);
		printf("Child process, pid = %d, ppid = %d\n", getpid(), getppid());
	}else{//parent 
		tempW = wait(NULL);
		printf("Catched a child process, tempW = %d, ppid = %d\n", tempW, getppid());
	}//of if
	printf(">>>>>>>>finish<<<<<<<<\n");
	return 0;
}//of main

Suspending the parent process can prevent the child process from becoming an orphan process, because the suspension of the parent process will not end until the child process ends.

If the parameter of the wait function is not empty, the exit status of the child process can be obtained, and the exit status is stored in the lower eight bits of the parameter status. Linux defines a set of macro functions to judge the exit status of a process, of which the two most basic are as follows:

#include <sys/wait.h>
int WIFEXITED(int status);//Judge whether the child process exits normally. If yes, return a non-zero value; otherwise, return 0
int WEXITSTATUS(int status);//If it is used in combination with wifexied, if wifexied returns a non-zero value, the macro is used to extract the return value of the child process.

Look at a case directly and deepen your understanding of it.

Case 2

Content: use wait to synchronize processes, and use macros to get the return value of child processes.

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    int tempStatus;
    pid_t tempPid, tempW;
    tempPid = fork();
    if(tempPid == -1){
        perror("fork error");
        exit(1);
    } else if(tempPid == 0){//child
        sleep(3);
        printf("Child process: pid=%d\n",getpid());
        exit(5);
 	}  else{//parent
        tempW = wait(&tempStatus);
        if(WIFEXITED(tempStatus)){
            printf("Child process pid=%d exit normally.\n", tempW );
            printf("Return Code:%d\n",WEXITSTATUS(tempStatus));
        } else {
            printf("Child process pid=%d exit abnormally.\n", tempW);
        }//of if
    }//of if
    return 0;
}//of main

Although wait is available, its disadvantages are too obvious. If there are multiple child processes, as long as one of the child processes ends, the parent process will unlock the suspended state, and the subsequent child processes will become orphans. To solve this problem, we refer to the upgraded wait function waitpid().

waitpid function

#include <sys/wait.h> 
pid_t waitpid(pid_t pid, int *status, int options);

Function: you can wait for the specified child process, or get the status of the child process without blocking the parent process.

Parameter Description:

pid: it is generally the pid of the process, and it may also be other values. Further description is as follows:
– pid > 0: wait for the subprocess (numbered pid) to exit. If it exits, the function returns; If not, wait all the time;
– pid = 0: wait for all child processes of the same process group to exit. If a child process joins other process groups, waitpid no longer cares about its state;
– pid = -1: the waitpid function degenerates into a wait function, blocking, waiting and recycling a child process;
– pid < - 1: wait for any child process in the specified process group. The id of the process group is equal to the absolute value of pid.
Options: provides control options, which can be one constant or two connected constants. The options are as follows:
– WNOHANG: if the child process is not terminated, waitpid will not block the parent process and will return immediately;
– WUNTRACED: if the execution of the child process is suspended, waitpid will return immediately;
– 0: do not use option.
Return value Description:
Success: returns the captured child process id;
0: options = WNOHANG, waitpid: no child processes that have exited can be recycled;
- 1: error, errno is set.
 

Case 3

Content: the parent process refers to the child process in the waiting process group. If the process does not exit, the parent process will be blocked all the time.

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
	pid_t tempPid, tempP, tempW;
	tempPid= fork();							//Create the first child process
	if (tempPid == -1){							
		perror("fork1 error");
		exit(1);
	} else if (tempPid == 0){						//Child process sleep
		sleep(5);
		printf("First child process:pid=%d\n", getpid());
	} else {						//The parent process continues to create the process
		int i;
		tempP = tempPid;
		for (i = 0; i < 3; i++){					//Three child processes are created by the parent process
			if ((tempPid = fork()) == 0){
				break;
			}//of if
		}//of for i
		if (tempPid == -1){						//error
			perror("fork error");
			exit(2);
		} else if (tempPid == 0){					//Subprocess
			printf("Child process:pid=%d\n", getpid());
			exit(0);
		} else {					//Parent process
			tempW = waitpid(tempP, NULL, 0);			//Wait for the first child process to execute
			if (tempW == tempP){
				printf("Catch a child Process: tempW=%d\n", tempW);
			}else{
				printf("waitpid error\n");
			}//of if
		}//of if
	}//of if
	return 0;
}//of main

Case 4

Content: continuously obtain the status of sub processes based on waitpid function.

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
	pid_t tempPid, tempW;
	tempPid = fork();
	if (tempPid == -1){
		perror("fork error");
		exit(1);
	} else if (tempPid == 0){
		sleep(3);
		printf("Child process:pid=%d\n", getpid());
		exit(0);
	} else {
		do{
			tempW = waitpid(tempPid, NULL, WNOHANG);
			if (tempW == 0){
				printf("No child exited\n");
				sleep(1);
			}//of if
		} while (tempW == 0);
		if (tempW == tempPid){
			printf("Catch a Child process:tempW=%d\n", tempW);
		}else{
			printf("waitpid error\n");
		}//of if
	}//of if
	return 0;
}//of main

 

Interprocess communication (partial)

Communication means the information interaction between processes. We transfer information from one process to another through a buffer, which we call pipeline.

This part mainly talks about pipes. Let me briefly review the remaining socket s, shared memory and so on. After the next class, write an article again!!

The Conduit

Take a picture:

Here's a reminder. Writing and reading cannot be carried out at the same time, that is, the pipeline can only read or write. If two processes operate on the pipeline at the same time, one of the two processes must stop for the other to operate.

Pipelines are further classified as:

  • Anonymous pipeline: it can only be used for inter process communication with kinship. After the process exits, the pipeline will be destroyed.
  • Named pipeline: the connection between the named pipeline and the process is weak, which is equivalent to an interface for reading and writing memory. After the process exits, the named pipeline still exists.

Anonymous Pipe

The use process of anonymous pipeline is as follows:
① Create an anonymous pipe in the process, pipe function;
② Close the pipeline port not used in the process, and use the close function;
③ In the process to be communicated, operate the read and write ports of the pipeline respectively, and the read/write function;
④ Close, close.

0x01 pipe function

#include <unistd.h>
int pipe(int pipefd[2]);

Function: create anonymous pipes

Parameters:

pipefd: pass in parameters, an array of file descriptors; Linux abstracts the pipeline into a special file

  • Success: return 0
  • Unsuccessful: - 1 returned.

Case 1

Content: pipe() is used to realize the communication between parent and child processes. The parent process is used as the reading end and the child process is used as the writing end.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
    int tempFd[2];//Define file descriptor array
    int tempRet=pipe(tempFd);//Create pipe
    if(tempRet == -1){   
        perror("pipe");
        exit(1);
    }   
    pid_t tempPid=fork();
    if(tempPid > 0){//Parent process - read
        close(tempFd[1]);//Close write end
        char tempBuf[64]={0};
        tempRet = read(tempFd[0], tempBuf, sizeof(tempBuf));//Read data
        close(tempFd[0]);
        write(STDOUT_FILENO, tempBuf, tempRet);//Write the read data to the standard output
        wait(NULL);
    } else if(tempPid == 0){//Subprocess - write
        close(tempFd[0]);//Close the reader
        char *tempStr="hello,pipe\n";
        write(tempFd[1], tempStr, strlen(tempStr)+1);//Write data
        close(tempFd[1]);
   }//of if
   return 0;
}//of main

Set tempFd [] as the pipeline for reading and writing. Close the reading end in the child process and the writing end in the parent process. Because we can't read and write at the same time in the pipeline, we should close the reading of the parent process and hang up waiting for the child process to write data. Attach a picture of the teacher for easy understanding,

0x02 dup2 function

In class, the teacher said that one of his colleagues used this redirection function to do a project and create his own company. It can be seen how great this function can be!!

#include <unistd.h>
int dup2(int oldfd, int newfd);
  • Its function is to copy the file descriptor of the parameter oldfd to newfd
  • If the function call is successful, newfd is returned
  • Otherwise, return - 1 and set errno.

Case 2

Content: use the pipeline to realize the communication between brother processes, and the brother processes realize the function of the command "ls | wc – l".

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    int tempFd[2];
    int tempRet = pipe(tempFd);
    if(tempRet == -1){
        perror("pipe err");
        exit(1);
    }//of if   
    int i;
    pid_t tempPid, tempWpid;
    for(i=0; i<2; i++){//2 sub
        if((tempPid = fork()) == 0){
            break;
        }//of if
    }//of if
	if(2 == i){//Parent process, recycling child process
        close(tempFd[0]); //Turn off reading
        close(tempFd[1]); //Turn off write
        tempWpid = wait(NULL);
        printf("wait child 1 success,pid=%d\n", tempWpid );
        tempPid = wait(NULL);
        printf("wait child 2 success,pid=%d\n", tempPid);
    } else if(0 == i){//Subprocess 1 - write
        close(tempFd[0]);
        dup2(tempFd[1], STDOUT_FILENO);//Direct to standard output
        execlp("ls", "ls", NULL);
    } else if(1 == i){//Subprocess 2 - read
        close(tempFd[1]);
        dup2(tempFd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL);
    }//of if
    return 0;
}//of main

 

This is mainly the communication between brother processes, that is, the information interaction between two child processes under a parent process, which is independent of the parent process.

reference resources: Fundamentals of Linux Programming 5.1: interprocess communication-1_ Henry Smale's blog - CSDN blog

Tags: C++ Linux GNU

Posted by lostsoul111455 on Thu, 12 May 2022 21:42:22 +0300