Process / process classification / interprocess communication -- signal

Posted by nac20 on Sat, 05 Mar 2022 09:34:12 +0100

process

Create process

Create a process and directly call the fork function:

#include <unistd.h>

	pid_t fork(void);

Negative value: failed to create child process.
0: return to the newly created child process.
Positive: returns to the father or caller. This value contains the process ID of the newly created child process.

#include <unistd.h>
#include <stdio.h>
int main()
{
    pid_t fpid;//fpid represents the value returned by the fork function
    int count=0;
    fpid=fork();
    if (fpid < 0)
        printf("error in fork!");
    else if (fpid == 0) {
        printf("i am the child process, my process id is %d\n",getpid());
        printf("I'm children\n");
        count +=2;
    }
    else {
        printf("i am the parent process, my process id is %d\n",getpid());
        printf("I'm parent.\n");
        count++;
    }
    printf("The statistical result is: %d\n",count);
    return 0;
}

After calling the fork function, a child process will be created, and both the parent and child processes will execute from the fork. The fork function has two return values. For the parent process, the pid of the child process will be returned. At this time, the pid will be greater than 0, and for the child process, the pid will be equal to 0.

Process is the basic unit of kernel scheduling resources. What is the relationship between the resources managed by parent-child processes?

The traditional linux operating system treats all processes in a unified way: the child process copies all the resources owned by the parent process. This method makes the creation process very slow, because the child process needs to copy all the address spaces of the parent process. How does the modern operating system deal with it?
There are three main ways:

  • Copy on write (if not changed, it will not be copied)
  • Lightweight processes allow parent-child processes to share many data structures of each process in the kernel, such as address space, open file table and signal processing.
  • The process created by vfork system call can share the memory address space of its parent process. In order to prevent the parent process from rewriting the data required by the child process and blocking the execution of the parent process until the child process exits

Destruction process

exit - terminates the executing process

#include <stdlib.h>

   void exit(int status);
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    pid_t fpid;//fpid represents the value returned by the fork function
    int count=0;
    int status = 0;

    fpid=fork();
    if (fpid < 0)
        printf("error in fork!\n");
    else if (fpid == 0) {
        printf("i am the child process, my process id is %d\n",getpid());
        printf("I'm children\n");
        count +=2;
        //exit(-10);  // Unsigned integer, printing 246
        exit(2);
    }
    else {
        printf("i am the parent process, my process id is %d\n",getpid());
        printf("I'm parent.\n");
        count++;
    }
    printf("The statistical result is: %d\n",count);
    //The parent process captures the state of the child process
    wait(&status);
    printf("parent: status: %d\n", WEXITSTATUS(status)); //2
    return 0;
}

wait supplement

man wait

Multi process high concurrency design


Examples

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name); 

int main(int argc,char **argv)
{
    start_worker_processes(4);
    //Manage child processes
    wait(NULL);
}

void start_worker_processes(int n)
{
    int i=0;
    for(i = n - 1; i >= 0; i--)
    {
       spawn_process(worker_process_cycle,(void *)(intptr_t) i, "worker process");
    }
}

pid_t spawn_process(spawn_proc_pt proc, void *data, char *name)
{

    pid_t pid;
    pid = fork();

    switch(pid)
    {
    case -1:
        fprintf(stderr,"fork() failed while spawning \"%s\"\n",name);
        return -1;
    case 0:
          proc(data);
          return 0;
    default:
          break;
    }   
    printf("start %s %ld\n",name,(long int)pid);
    return pid;
}

//Core bound to cpu
static void worker_process_init(int worker)
{
    cpu_set_t cpu_affinity;
    //worker = 2;
	//Multi core high concurrency processing 4core 0 - 0 core 1 - 1 2 - 2 3 - 3  
    CPU_ZERO(&cpu_affinity);
    CPU_SET(worker % CPU_SETSIZE,&cpu_affinity);// 0 1 2 3 
    //sched_setaffinity
    if(sched_setaffinity(0,sizeof(cpu_set_t),&cpu_affinity) == -1)
    {
       fprintf(stderr,"sched_setaffinity() failed\n");
    }
}

void worker_process_cycle(void *data)

{
     int worker = (intptr_t) data;
    //initialization
     worker_process_init(worker);

    //work
    for(;;)
    {
      sleep(10);
      printf("pid %ld ,doing ...\n",(long int)getpid());
    }
}


View the commands executed by the process on the cpu core: PS - ELO, ruser, PID, LWP, PSR, args

Orphan zombie daemon

Orphan process:

If a parent process exits and one or more of its child processes are still running, those child processes will become orphans. Orphan processes will be adopted by the init process, and the init process will complete the status collection for them.
Think about how to imitate an orphan process? The answer is: kill the parent process!

Zombie process:

A process uses fork to create a child process. If the child process exits and the parent process does not call wait or waitpid to obtain the status information of the child process, the process descriptor of the child process is still saved in the system. This process is called a dead end process.

  1. How did the zombie process come about
       when a process calls the exit command to end its life, in fact, it is not really destroyed, but leaves a data structure called Zombie (the system calls exit, which is used to exit the process, but it is only limited to turning a normal process into a Zombie process, and it cannot be completely destroyed).
       in the state of Linux process, zombie process is a very special kind. It has abandoned almost all memory space, has no executable code, and cannot be scheduled. It only retains a position in the process list to record the exit status and other information of the process for other processes to collect. In addition, zombie process no longer occupies any memory space. It needs its parent process to collect the corpse for it. If its parent process does not install SIGCHLD signal processing function, calls wait or waitpid() to wait for the child process to end, and does not explicitly ignore the signal, it will always remain in the zombie state. If the parent process ends at this time, the init process will automatically take over the child process and collect the corpse for it, and it can still be cleared. However, if the parent process is a cycle and will not end, the child process will always maintain the zombie state, which is why there are sometimes many zombie processes in the system.
    How to view zombie processes:
    Using the command ps, you can see that the process marked as is a zombie process.
    How to clean up zombie processes:
      rewrite the parent process and collect the body of the child process after it dies. The specific method is to take over the SIGCHLD signal. After the child process dies, it will send SIGCHLD signal to the parent process. After the parent process receives this signal, it will execute waitpid() function to collect the body for the child process. This is based on the principle that even if the parent process does not call wait, the kernel will send SIGCHLD message to it. Although the default processing is ignored, if you want to respond to this message, you can set a processing function.
      
      kill the parent process. After the death of the parent process, the zombie process becomes an "orphan process" and is passed to process init 1. Init will always be responsible for cleaning up the zombie process. All the zombie processes it produces disappear.

Daemon

Processes that are not associated with any terminal are usually running when the system starts. They run as root or other special users (apache and postfix) and can handle some system level tasks. The daemon is separated from the terminal to prevent the information during the execution of the process from being displayed on any terminal, and the process will not be interrupted by the terminal information generated by any terminal (such as closing the terminal). So how do you become a daemon? The steps are as follows:

1. Call fork() to create a new process, which will be the future daemon
2. exit is invoked in the parent process to ensure that the child process is not the leader of the process.
3. Call setsid() to create a new session area
4. Change the current directory to the root directory (if the current directory is used as the directory of the daemon, the current directory cannot be unloaded as the working directory of the daemon)
5. Redirect standard input, standard output and standard error to / dev/null
Let's look at this Code:

#include <fcntl.h>
#include <unistd.h>

int daemon(int nochdir, int noclose)
{
    int fd;

    switch (fork()) {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        _exit(0);
    }

    if (setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)chdir("/");

    if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
        (void)dup2(fd, STDIN_FILENO);
        (void)dup2(fd, STDOUT_FILENO);
        (void)dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)close (fd);
    }
    return (0);
}

Interprocess communication

signal

What is a signal? Signal is a method that provides a program with a way to deal with asynchronous events. It is realized by software interrupt. You cannot customize signals. All signals are predefined by the system.

Who generates the signal?

1) The shell terminal generates the corresponding signal according to the current error (segment error, illegal instruction, etc.) Ctrl+c

For example:
For socket communication or pipeline communication, if the reader has been closed and performs write operation (or sends data), the process performing write operation will receive SIGPIPE signal (indicating pipeline rupture)
The default behavior of this signal is to terminate the process.
2) At the shell terminal, use the kill or kill command to generate a signal

Example:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>


void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
}

int main(void) 
{

	signal(SIGINT, myhandle);
	while (1) {
        sleep(1);
        printf("waiting signal\n");
	}
	return 0;
}

Enter Ctrl + C and SIGINT will capture this signal

 ./a.out   &
 kill  -HUP  13733     /* Send SIGHUP to the process with PID 13733 */        

3) in the program code, call the kill system call to generate the signal.

Common signal table

Signal nameexplain
SIGABORTThe process terminated abnormally
SIGALRMTimeout alarm
SIGFPEFloating point operation exception
SIGHUPConnection hang up
SIGILLIllegal instruction
SIGINTTerminal interrupt (Ctrl+C will generate this signal)
SIGKILL*Terminate process
SIGPIPEWrite data to a pipeline without a read process
SIGQUITTerminal exit (Ctrl + \ will generate this signal)
SIGSEGVInvalid memory segment access
SIGTERMtermination
SIGUSR1*User defined signal 1
SIGUSR2*User defined signal 2

If the above signals are not captured, the process will terminate after receiving them!

SIGCHLDSubprocess stopped or exited
SIGCONT*Let the suspended process continue
SIGSTOP*Stop execution (i.e. "pause")
SIGTSTPInterrupt pending
SIGTTINRead background attempt
SIGTTOUThe daemon attempted to write

Signal processing

① Ignore this signal
② Capture the signal and specify the signal processing function for processing
③ Execute the default actions of the system, most of which are to terminate the process

Signal acquisition

Signal acquisition refers to the execution of a specified function after receiving a certain signal.
Note: SIGKILL and SIGSTOP cannot be captured, that is, the response actions of these two signals cannot be changed.

signal()

Usage: man 2 signal

#include<signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//signum: signal
//sighandler_t: The response function can also ignore the signal, as shown in the figure below

SIG_IGN: ignore signal
SIG_DFL: system default mode
Or specify a function

Note: the return type of signal and its second parameter are all function pointer types

ps ax | grep ./a.out      //Query process number
ps -ef | grep ./a.out

sigaction()

(highly recommended in actual project)
Usage:
man 2 sigaction

The signum parameter indicates the type of signal to capture
The act parameter specifies the new signal processing method
The oldact parameter outputs the processing mode of the previous signal (if it is not NULL)

//struct sigaction 
struct sigaction {
	void (*sa_handler)(int);   /* Response function of signal */
	sigset_t   sa_mask;          /* Shielded signal set */                         
	int sa_flags;                /* When SA_ SA is included in flags_ When resethand\
	After receiving the signal and calling the specified signal processing function for execution, reset the response behavior of the signal to the default behavior SIG_DFL */

//sa_handler this parameter is the same as the parameter handler of signal(), representing the new signal processing function
//sa_mask is used to set SA to be temporarily set when processing this signal_ Mask the specified semaphore set
//sa_flags is used to set other related operations of signal processing. The following values are available. 

// SA_RESETHAND simply means that you only want the signal processing function to be called once
//SA_RESTART: if the signal interrupts a system call of the process, the system automatically starts the system call
//SA_NODEFER: generally, when the signal processing function is running, the kernel will block the given signal. But if SA is set_ Nodefer flag, then the kernel will not block the signal when the signal processing function is running

}

Supplement:
When SA_ When the mask contains A signal A, if the signal A occurs during the execution of the signal processing function,
Then block the signal a (i.e. do not respond to the signal temporarily) until the execution of the signal processing function ends.
That is, after the signal processing function is executed, it responds to signal A

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
}

int main(void) 
{
	struct sigaction act;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	//act.sa_flags = 0;
	act.sa_flags = SA_RESETHAND;//Respond to the signal only once

	sigaction(SIGINT, &act, 0);

	while (1) {
        sleep(1);
        printf("waiting signal\n");
	}

	return 0;
}

Pressing ctrl+c once will respond to the signal, and the second press will exit

Signal transmission

Signal transmission mode:

Generate signal with shortcut key at shell terminal
Use the kill, kill command.
Use the kill function and the alarm function

1) Using the kill function
Send the specified signal to the specified process

Usage: man 2 kill

be careful:

"Permission" is required to send a signal to the specified process:
The process of an ordinary user can only send signals to other processes of the user
root Users can send signals to all users' processes

kill failed

Return - 1 on failure

Failure reason:

① Insufficient permissions
② Signal does not exist
③ The specified process does not exist

example:
Create a child process. The child process outputs the string "child process work!" every second. The parent process waits for the user to input. If the user presses the character a, it sends a signal SIGUSR1 to the child process, and the output string of the child process is changed to uppercase; if the user presses the character a, it sends a signal SIGUSR2 to the child process, and the output string of the child process is changed to lowercase

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int workflag = 0;

void work_up_handle(int sig) 
{
	workflag = 1;
}

void work_down_handle(int sig) 
{
	workflag = 0;
}



int main(void) 
{
	pid_t pd;
	char c;


	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		char *msg;
		struct sigaction act; 
		act.sa_flags = 0;
		act.sa_handler = work_up_handle;
		sigemptyset(&act.sa_mask);		
		sigaction(SIGUSR1, &act, 0);
		
		act.sa_handler = work_down_handle;
		sigaction(SIGUSR2, &act, 0);
		
		while (1) {
			if (!workflag) {
				msg = "child process work!";
			} else {
				msg = "CHILD PROCESS WORK!";
			}
			printf("%s\n", msg);
			sleep(1);
		}
		
	} else {
		while(1) {
			c = getchar();
			if (c == 'A') {
				kill(pd, SIGUSR1);
			} else if (c == 'a') {
				kill(pd, SIGUSR2);
			}
		}
	}
	

	return 0;
}

Example: "alarm clock", create a child process. The child process sends a SIGALR to the parent process after 5 seconds. After the parent process receives the SIGALRM signal, "alarm" (simulated by printing)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int wakeflag = 0;

void wake_handle(int sig) 
{
	wakeflag = 1;
}

int main(void) 
{
	pid_t pd;
	char c;


	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		sleep(5);
		kill(getppid(), SIGALRM);
	} else {
		struct sigaction act; 
		act.sa_handler = wake_handle;
		act.sa_flags = 0;
		sigemptyset(&act.sa_mask);

		sigaction(SIGALRM,  &act, 0);

		pause(); //Suspend the process until any signal is received

		if (wakeflag) {
			printf("Alarm clock work!!!\n");
		}
	}

	return 0;
}   
Use alarm()

Function: Send a SIGALRM signal to the process itself within the specified time.
Usage: man 2 alarm
Note: the unit of time is "second"

The actual alarm time is a little longer than the specified time.  
If the parameter is 0, the set alarm clock will be cancelled.
If the alarm time has not arrived, call again alarm,The alarm clock will be timed again
 Each process can use at most one alarm clock.

Return value:

Failed: Return-1
 Success: return the remaining time of the last alarm clock (seconds)

Example:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>

int wakeflag = 0;

void wake_handle(int sig) 
{
	wakeflag = 1;
}

int main(void) 
{
	int ret;
	
	struct sigaction act;
	act.sa_flags = 0;
	act.sa_handler = wake_handle;
	sigemptyset(&act.sa_mask);
	sigaction(SIGALRM, &act, 0);
	
	printf("time =%ld\n", time((time_t*)0));

	ret = alarm(5);//Send a signal in five seconds, only once
	if (ret == -1) {
		printf("alarm error!\n");
		exit(1);
	}

	//Suspend the current process until any signal is received
	pause();

	if (wakeflag) {
		printf("wake up, time =%ld\n", time((time_t*)0));
	}

	return 0;
}
Using raise
Send a signal to the process itself.
Prototype: int  raise (int sig)

Send multiple signals

When a process is executing the operation function corresponding to a signal (the installation function of the signal), if at this time, the process receives the same signal (the signal of the same signal value) many times,

Then: if the signal is unreliable (< 32), it can only respond again.
If the signal is reliable (> 32), it can respond multiple times (without omission). However, you must wait for this response function to execute before responding to the next one.

When a process is executing the operation function corresponding to a signal (the installation function of the signal), if the process receives another signal (a signal with different signal values), then:
If the signal is included in the Sa of the signal of the current signal_ Mask, the signal will not be processed immediately. The signal processing function is not executed until the current signal processing function is executed.

Otherwise:
Immediately interrupt the current execution process (wake up immediately if you are sleeping, such as sleep) to execute the new signal response. After the new response is executed, it will continue to execute after returning to the original signal processing function.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
	int i;
	for (i=0; i<10; i++) {
		sleep(1);
}
	printf("Catch end.%d\n", sig);
}

int main(void) 
{
	struct sigaction act, act2;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	//sigaddset(&act.sa_mask, SIGUSR1);// Set shielding and will not be interrupted\
    //If it is not set, it will be interrupted by the new signal, and then continue to execute
    
       act.sa_flags = 0;

	sigaction(SIGINT, &act, 0);


	act2.sa_handler = myhandle;
	sigemptyset(&act2.sa_mask);
	act2.sa_flags = 0;
	sigaction(SIGUSR1, &act, 0);

	while (1) {

	}

	return 0;
}

Get unprocessed signal

When the signals in the process's signal mask word occur, these signals will not be responded by the process,
These signals that have occurred but have not been processed can be obtained through the sigpending function

sigpending

Usage: man sigpending
Return value:
0 if successful
- 1 if failed

Waiting blocking signal

pause

Block the process until any signal occurs

sigsuspend

Set the signal mask word with the specified parameters, and then wait for the signal to occur when blocking.
That is, only wait for signals other than the signal mask word

Topics: C++ Back-end