c++webserver/Chapter II Multiprocess Development

Posted by may27 on Sun, 20 Feb 2022 00:06:25 +0100

1. PCB on Linux

2. Process State Transition

1.Tristate

2.Pentastate

3.ps command.

//Static View Process
ps aux(View all current processes) | ajx

4.top command

//View process dynamically

5.kill command

//Kill Process
kill -9/SIGKILL	pid		Force Kill Process
killall -name Process name	  Kill by process name  

6. Process relations

ppid	Parent Process
pid		Current Process
pgid	Process Group Number

7. Process Functions

1. Create a process

pid_t fork(void);
/*
	Create a process
	Return value: fork returns two times, one for the parent process and one for the child process (distinguishing parent from child processes)
		  The parent process returns the created child process ID (greater than 0), -1 indicates failure, and errno is set. (full processes | out of memory)
		  Subprocess returns 0
	Starting with the function execution, copy creates a subprocess, where the two processes execute across each other!
	Because it's copy, the code is the same, the values of the variables are the same, but the variables are right! Physical address is different!
	The current process pid can be viewed with getpid();
*/

#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t pid = fork();
    if (pid > 0)
    {
        printf("Currently the parent process,pid = %d,Of its children pid = %d\n", getpid(), pid);
    }
    else if (pid == 0)
    {
        printf("Currently a child process,pid = %d,Its parent process is:%d\n", getpid(), getppid());
    }
    else if (pid < 0)
    {
        printf("Failed to create process");
    }
    return 0;
}

!!! The real process is read-time sharing and write-time copying, that is, the parent and child processes share the same memory space, and the user area only copies when writing is involved.

The core area must be a duplicate;

Different parts:

  1. fork() returns different values

  2. Data:

  • pid of current process
  • The pid, or ppid, of the parent of the current process
  • Signal Set

Common ground:

If no write operation is performed

  • File Descriptor Table
  • User Area Data

2. Process Exit

void exit(int status); //c library
/*
	parameter
	1.	The state at exit (after the child process exits, the state is sent to the parent process to help recycle resources, etc.);
*/
void _exit(int status); //linux system
// !! IU buffers will not refresh when exiting, if there is no\n. may not be displayed when printing!!

Difference:

8.GDB Debugging Multiprocess

Default debugging is just the parent process, the child process will always run

commandEffect
set-follow-fork-mode[parent | child]Set Tracked Processes
show-follow-fork-modeShow processes currently executing
set-detach-on-fork[on | off]Default on, whether to run out of parent process, off hangs (8.0 may have a bug)
info inferiorsView current program process information
inferior idSwitch processes, debug (requires child processes to hang)
detach inferiors idGet a process of the program out of GDB debugging and run on its own

9.exec family of functions

  • The purpose of the exec family of functions is to find an executable based on the specified file name and use it instead of the calling process, in other words, to execute an executable within the calling process.
  • Functions of the EXEC function family do not return after successful execution because the entity of the calling process, including code snippets, data snippets, stacks, and so on, has been replaced by new content, leaving only some superficial information such as the process ID as is still the same. Only if the call fails, they will return to -1, executed from the call point of the original program and then down.
  • User area content of the original process is replaced by that of the new file.
  • Typically, a parent process creates a child process and executes a file or command with the exec function
!int execl(const char *path,const char *arg,.../* (char *) NULL*/) ;	//Common, c Library
/*
	Parameters:
		1.	Executable file path, or shell command (command file)
		2.	List of parameters required to execute an executable (can be multiple)
			The first parameter is generally useless, just write the name of the executor. Second start writing parameter, ending with NULL
	Return value: Success returns nothing (because the code has also been replaced), Failure returns -1, proceed with the following code;
*/
!int execlp(const char *file,const char *arg, ... /* (char *)NULL*/);	//Common, c Library
/*
	- Will go to the environment variable to find the specified executable, find the execution, cannot find the execution is unsuccessful (ps command can not write/bin)
	- Exclude current path
	parameter
		1.	Execution File Name  
	Like the others
*/
int execle(const char*path,const char*arg,./*,(char*)null,char*const envp[]*/);
//l(list) 		 Parameter address list, ending with a null pointer
int execv(const char*path,char*const argv[]);int execvp(Const char*file,char*const argv[]);
//v (vector) 	 Address of a pointer array with address of each parameter (in an array of strings)
int execvpe(const char*file,char*const argv[],char*const envp[]);
//p(path) 	 Search executable by directory specified by PATH environment variable
!int execve(const char*filenename,char*const argv[],char*const envp[]);//linux kernel function
//e (environment) 	 Address of pointer array with address of environment variable string

10. Orphan Progress (No Father)

The parent process runs, but the child process is still running (not finished).

Whenever an orphan process occurs, the kernel sets the parent process of the orphan process to init(pid = 1), and the init process loops wait() its exited child process. When the orphan process's life cycle ends, the init process handles all of its aftermath. So there's no harm

11. Zombie Processes (Body and Soul)

  • After each process finishes, the user area data in its own address space is released, and the PCB in the kernel area cannot release itself, requiring the parent process to release it.
  • When the process terminates, the parent process has not been recycled, and the child process residual resource (PCB) is stored in the kernel and becomes a Zombie process.
  • A zombie process cannot be killed by kill-9.
  • This causes a problem. If the parent process does not call wait() or waitpid(), the information that is retained will not be released and the process number will always be occupied, but the process number that the system can use is limited. If a large number of zombie processes are generated, the system will not be able to generate new processes because there are no available process numbers. This is a danger to the zombie process and should be avoided.

12. Process recycling

  • The parent process calls wait() or waitpid() to get the process's exit status while being thoroughly aware of the process (mainly the zombie cleanup process)
  • The wait() function is the same as the waitpid() function, except that the wait() function can block, the waitpid() can set no blocking, and the waitpid() can also specify which subprocess to wait for to end.
  • Note: A wait or waitpid call can only clean up one subprocess, and a loop should be used to clean up multiple subprocesses.
pid_t wait(int *status);
/*
	Parameters:
	1.	The status information at the time the process exits is passed in an address of type int, and the parameter is an outgoing parameter.
	2.	Return value: Successfully returned child process id, fail-1 (all child processes end, function call fails);
	The process calling the wait() function is blocked and awakened until a child process has ended or a signal is received that cannot be ignored
	Return -1 if there are no subprocesses or if they have all ended;
*/
pid_t waitpid(pid_t pid, int *status, int options);
/*
	Retracts the specified pid process,
	parameter
	1.	>0 pid of child process 	 				= 0 Recycle all subprocesses of the current process group
		-1 Recycle all subprocesses (equivalent to wait ()<-1 The absolute value of the group id of a process group, reclaim subprocesses of an id process group
	2.	The status information at the time the process exits is passed in an address of type int, and the parameter is an outgoing parameter.
	3.	Set blocking (0) or non-blocking (WNOHANG)
	Return value: >0. Return the process id | =0 	 option = WNOHANG, indicates that there are subprocesses | <0 errors or that there are no subprocesses
*/
waitpid(-1,&status,0) = wait(&status);

Status status status type

WIFEXITED(status)		//Non-0, process exits normally (determine if program exits normally)
WEXITSTATUS(status)	//If the upper macro is true, get the status of the process exit (exit parameter)
    
WIFSIGNALED(status)	//Non-0, process terminates abnormally (determine why it exited abnormally)
WTERMSIG(status)		//If the macro above is true, get the signal number that terminates the process
    
WIFSTOPPED(status)		//Non-0, process is paused
WSTOPSIG(status)		//If the upper macro is true, get the number of the signal that causes the process to pause
    
WIFCONTINUED (status)	//Non-0, process has resumed running after pausing

13. Interprocess Communication (IPC)

Purpose:

  • Data transfer: A process needs to send its data to another process
  • Notification Event: A process needs to send a message to another or a group of processes to notify them that something has happened (such as notifying the parent process when the process terminates).
  • Resource sharing: Share the same resource among multiple processes. To do this, the kernel is required to provide mutually exclusive and synchronization mechanisms.
  • Process control: Some processes want complete control over the execution of another process (such as a Debug process), in which case the control process wants to be able to intercept all traps and exceptions of the other process and be able to know its state changes in a timely manner.

mode

14. Anonymous Pipeline (Ring Queue)

Output of the previous command as input to the next command

Characteristic:

  • Pipelines are actually buffers maintained in kernel memory, which have limited storage capacity and may not be the same size for different operating systems.
  • Pipelines have file characteristics: read and write operations, anonymous pipes have no file entities, and named pipes have file entities but do not store data. Pipelines can be operated on as operation files.
  • A pipeline is a byte stream. When using a pipeline, there is no concept of message or message boundary (equivalent to no protocol). A process reading data from the pipeline can read any size of data block, regardless of the size of the data block that the writing process writes to the pipeline.
  • The data passed through the pipeline is sequential, and the bytes read from the pipeline are in exactly the same order that they are written to the pipeline.
  • The data in the pipeline is transmitted in a one-way direction, one end for writing, one end for reading, and the pipeline is half-duplex (only one side can be sent to the other side at the same time)
  • Reading data from a pipeline is a one-time operation. Once read away, the data is discarded from the pipeline. Free up space to write more data. lseek () cannot be used to access data randomly in a pipeline.
  • Anonymous pipes can only be used between processes with a common ancestor (parent and child or two sibling processes, related). Because there are identical file descriptors, all can communicate through both ends of the file descriptor and the pipeline.

Use:

//Create anonymous pipes for process communication
int pipe (int pipefd[ 2]);
 /*
 	parameter
 	1.	int pipefd[2]Is an outgoing parameter.
 		[0]Corresponding to read, [1] corresponding to write
 	Return value: 0 for success and -1 for failure;
 	Be careful:!!! Pipeline must be created before fork()!!! Create it for the same pipeline (file descriptors are the same for content)
 		 Pipeline is blocked by default, read is blocked if there is no data. If the pipe is full, write is blocked
 */
//View Pipeline Buffer Size Command
ulimit -a  //(pipe size of one block. How many blocks) Modifiable
//View Pipeline Buffer Size Function
long fpathconf(int fd,int name);
/*
	long size = fpathconf(pipefd[0],_PC_PIPE_BUFF);	//size Just size
*/

//Set non-blocking state
fcnl(pipefd[],flag);

The parent process and the child process cannot read and write at the same time, that is, the parent process reads first, then the child process must write first. Otherwise, both processes are blocked and the programs are interlocked.

Pause between reading and writing, otherwise the same process will appear to read itself. If you only need one (read.write) function, turn off the other. close(pipefd[]);

Output to the screen can be turned to the end of the pipe: stdout->fileno->pipefd[1]; Dup2 (pipefd[1], stdout_fileno)

Read-write characteristics (special case of blocking I/O):

  1. All file descriptors pointing to the pipe end are closed ** (Pipeline end reference count is 0**), a process reads data from the read end of the pipe, reads the remaining data of the pipe back to 0 (non-1. It is not an error, exits normally). It's like reading to the end of a file;
  2. If the read-end file descriptors pointing to the pipeline are all closed ** (Pipeline Read-End Reference Count is 0**), then if data is written to the pipeline, the process receives a SIGPIPE, which usually causes the process to terminate abnormally.
  3. If the file descriptor pointing to the pipeline end is not closed ** (the pipeline end reference count is not 0**), then when there is no data in the pipeline, the read execution will block until the pipeline has data to read and return.
  4. If the file descriptor pointing to the read end of the pipeline is not closed ** (Pipeline Read End Reference Count is not 0**). When the pipeline writes data, the call to write() will block when the pipeline is full until there is space to write and put it back
Summary:
  1. Reading pipe;
    • There is data in the pipe, and read returns the actual number of bytes read.
    • There is no data in the pipeline:
      • Write is closed completely, read returns 0 (equivalent to reading to the end of the file)
      • Writer not completely closed, read blocked waiting
  2. Write Pipeline:
    • Pipeline reader is all closed and process terminates abnormally (process receives SIGPIPE signal)
    • The reading end of the pipe is not completely closed:
      • Pipe full, write blocked
      • When the pipe is not full, write writes the data and returns the number of bytes actually written

15. Named Pipes (Named Pipes, FIFO Files)

Provide files (pipeline files) that can communicate as long as the process has access to the path; Pipes are the same as anonymous pipes. FIFO

1. Unlike anonymous pipes

  • FIFO exists as a special file in the file system, but the contents of FIFO are stored in memory.
  • When a process using FIFO exits, the FIFO file will continue to be saved in the file system for later use.
  • FIFO s have names, and unrelated processes can communicate by opening named pipes.

2. Named pipe use

//Create a named pipe by command
mkfifo	Name

//Create a named pipe from a function
int mkfifo (const char *pathname,mode_t mode);
/*
	parameter
	1.	Pipeline File Path
	2.	File permissions (open is the same, bitwise and with ~umask)
	Return value: success returns 0, failure returns -1, set errno;
*/

/*
- Once you create a FIFo using mkfifo, you can open it using open, and common file I/o functions are available for fifo. For example: close, read, write, unlink, etc.
- FIFO Strictly adhere to First in First out, reading pipes and FIFO s always returns data from the beginning, and writing to them adds data to the end. They do not support file location operations such as lseek()
- If one end is not opened, the pipe becomes blocked. Until both ends open.
- If one end is disconnected while running, the other end is also disconnected
*/

3. Special points

  1. Reading pipe;
    • There is data in the pipe, and read returns the actual number of bytes read.
    • There is no data in the pipeline:
      • Write is closed completely, read returns 0 (equivalent to reading to the end of the file)
      • Writer not completely closed, read blocked waiting
  2. Write Pipeline:
    • Pipeline reader is all closed and process terminates abnormally (process receives SIGPIPE signal)
    • The reading end of the pipe is not completely closed:
      • Pipe full, write blocked
      • When the pipe is not full, write writes the data and returns the number of bytes actually written

16. Memory Mapping

1. Definition

Memory-mapped I/0 maps data from disk files to memory, allowing users to modify disk files by modifying memory.

2. Communication principle:

The mapping department can be operated on. Mapping content modifications are synchronized to files in real time when multiple processes read the same file. Has the same modifications. So communication can be achieved;

3. Use

//Map memory files into memory
void *mmap(void *addr, size_ t length, int prot, int flags,int fd,off t offset);
/*
	Parameters:
    1.	Map the first address of memory (incoming NULL, specified by the kernel).
    2.	Mapping data length, cannot be 0. Recommended file length (by page, integer multiple size of page)
    3.	Request permission to operate on a memory mapped area (PROT_EXEC (Execute)| READ | WRIED | NONE (None))
    	To operate on a map area, you must have read permissions. If you want to write on or on
    4.	[ MAP_SHARED(Synchronize with Disk Files) | MAP_ PRIVATE (Create a new file out of sync with the file) | MAP_ ANONYMOUS (Anonymous mapping, no file required, fd specifies -1, with)];
    5.	File descriptor, file size cannot be zero, open permission cannot conflict with previous (neither here nor above);
    6.	Offset. Normally, you must specify an integer multiple of 4K. 0 to indicate no offset. If it weren't an error
    Return value: Map area header address returned successfully, MAP_returned unsuccessfully FAILED;
*/

//Release memory mapping
int munmap (void *addr, size t length) ;
/*
	parameter
	1.	To free up memory size
	2.	Release memory size as length for nmap
*/

4. Use Objects

  1. Processing with relationships (parent-child processes) - Create mapping areas from parent processes when there are no child processes yet. Shared memory map area when child process is created
  2. Unrelated processes
    1. Prepare files of size not 0
    2. Process 1: Create a memory map from a disk file - get a pointer to manipulate this memory
    3. Process 2: Create a memory map from a disk file - get a pointer to manipulate this memory
    4. Communicate using memory mapped areas
  3. Memory mapped area communication, non-blocking

5. Notes

  1. Pointer++ operations are possible, but not recommended. Because freeing memory can be cumbersome;
  2. If flag and prot parameters are inconsistent, mmap fails and returns MAP_FAILED;
  3. Closing the file descriptor pointer after mmap will have no effect
  4. ptr out of bounds produces a segment error
  5. It can also be used to copy files (fast) - memory copy, too large to fit

6. Anonymous Mapping

No file entity required for memory mapping

Can only be used with parent-child processes because there are no entity files

17. Signal

1. Definition

Signal is one of the oldest ways of communication between processes in Linux. It is the notification mechanism to the process when an event occurs, sometimes referred to as software interrupt. It is a simulation of interrupt mechanism at the software level and a way of asynchronous communication. Signals can cause one running process to be interrupted by another running asynchronous process, which in turn handles an emergency.

Many signals to processes are usually from the kernel. The various events that trigger the kernel to signal the process are as follows:

  • For a foreground process, the user can signal it by entering special terminal characters. For example, typing Ctrl+C usually sends an interrupt signal to the process.
  • A hardware exception occurs, in which the hardware detects an error condition and notifies the kernel, which then sends the corresponding signal to the process. For example, execute an abnormal machine language instruction, such as divide by o, or reference an inaccessible memory region.
  • Changes in system state, such as an alarm timer expiration, can cause SIGALRM signals, CPU time for a process to execute is exceeded, or a child process of the process exits.
  • Run the kill command or call the kill function.

2. The two main purposes of using signals are:

  • Let the process know that a specific thing has happened. Forces a process to execute a signal handler in its own code.

3. Signal characteristics:

  • simple
  • Can't carry a lot of information
  • Sending priority is higher when certain conditions are met

4. Signal List

Look at the system-defined signal list: kill-l, the first 31 signals are regular, (34-64) the rest are real-time.

View signal meaning man 7 signal

5. Signal Processing Action

  1. Default Processing Action in Signal 5

    • Term Terminates Process

    • Ign current process ignores this signal

    • Core terminates the process and generates a core file (error message for abnormal exit)

    • stop pauses the current process

    • cont Continues the currently suspended process

  2. Several states of signal: generation, pending, delivery

  3. SIGKILL and SIGSTOP signals cannot be captured, blocked, or ignored and can only perform default actions.

//Find Error Information Function

ulimit -c 1024;	//Specify the core file size, defaulting to 0. Do not generate
gcc -g ___.c -o ___;	//Get executable
./___				//Execute the executable to get the core file
gdb __;				//Enter debugging state
core-file core		//Get error message

6. Signal-dependent functions

//Send any signal sig to a process
int kill(pid_t pid,int sig);
/*
	Parameters:
	1.	pid of sending process
		>0	Send to specified process
		=0,	Send to all processes in the current process group
		=-1	Send to every process with permission to accept
		<-1	Send to reversed pgid process group (-123 to pgid = 123)
	2.	Signal name sig (in macros)
*/


//Send signal to current process
int raise (int sig) ;
/*
	Parameters:
	1.	Signal sent
	Return value: success 0, failure not 0;
*/

//!!! Actual time = Kernel time + User time + Time consumed (IO operations)!!!

//Send a SIGABRT signal to the current process, killing it
void abort (void) ;

//Set the timer, in seconds, to start counting down when the call starts, 0 is the function that sends a signal to the current process: SIGALARM. Actual Time
unsigned int alarm(unsigned int seconds);
/*
	No blocking; Executes regardless of the state of the process
	parameter
	1.	seconds: The length of the countdown. Units: seconds. If the parameter is 0, the timer is invalid;
				 Cancel a timer through alarm;
	Return value: no timer before, return 0;
		  There is a timer before, returning the remaining time of countdown;
	SIGALARM: Default termination of the current process, each process has and only has one timer (repeated calls will override the timer above);
*/

//Periodic timer, which can replace alarm function, has higher accuracy. Unit Subtle
int setitimer(int which,const struct itimerval *new_val,struct itimerval *old_value);
{
/*
	parameter
	1.	Specify clock type [ 	 ITIMER_ REAL (real time, 3 normal time.)| ITIMER_ VTRTUAL (Virtual Time, User Time) | ITIMER_ PROF (user time, kernel time)]
	2.	Structure of timer
	  	 struct itimerval 
	  	 {
	  	 	  struct it_value
	  	 	  {
	  	 	     	struct timeval it_interval; 	//Time of each stage, interval (executed every few seconds after delay)
              		struct timeval it_value;    //How long is the delay in executing the locator (start)
	  	 	  };

              struct it_interval 			//The structure of time
              {
            		time_t      tv_sec;        //Seconds
            		suseconds_t tv_usec;       //subtle
        	  };
         };
   3.	The property (outgoing parameter) of the last timer can be set to null directly.
*/
}


//Signal capture, SIGKILL SIGSTOP cannot be captured and ignored. Signal capture must be in front of signal generation!
sighandler_t signal(int signum,sighandler_t handler);
/*
	parameter
	1.	Signal to capture
	2.	What to do when a signal is captured
		- SIG_IGN:	Ignore signal (for timer)
		- SIG_DFT:	Use signal default behavior
		- Callback function: 	   Kernel calls, programmers are only responsible for writing.
	Return value: Successfully returns the address of the last registered signal processing function and NULL for the first time
		  Failed return SIG_ERR, set errno
*/
int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact) ;

7. Callback function

void (*sighandler_t)(int); //Function Pointer
//The int type parameter represents the value of the captured signal.

//Example
void myalarm(int num)
{
}

;// Seconds
suseconds_t tv_usec; // subtle
};
};
3. The property of the last timer (outgoing parameter) can be set to null directly;
*/
}

//Signal capture, SIGKILL SIGSTOP cannot be captured and ignored. Signal capture must be in front of signal generation!
sighandler_t signal(int signum,sighandler_t handler);
/*
parameter
1. Signal to capture
2. What to do when the signal is captured
- SIG_IGN: Ignore signal (for timer)
- SIG_DFT: Use signal default behavior
-Callback function: Kernel call, programmer is responsible for writing only.
Return value: Successfully returns the address of the last registered signal processing function and NULL for the first time
Failed return SIG_ERR, set errno
*/
int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact) ;

#### 7. Callback function

~~~cpp
void (*sighandler_t)(int); //Function Pointer
//The int type parameter represents the value of the captured signal.

//Example
void myalarm(int num)
{
}

Topics: C++ Back-end