preface

We know that Linux has three special processes: orphan processes, zombie processes, and daemons.

  • An orphan process is a process that continues to run after its parent has completed execution or been terminated. These orphan processes are adopted by the init process (process number 1), which collects state for them.

  • Zombie process A child process that exits before its parent has called wait() or waitpid(). This child process is the zombie process. Any child process (with the exception of init) does not disappear immediately after exit(), but leaves behind a data structure called a Zombie, waiting to be processed by the parent process.

  • Daemon THE parent of a daemon is init, and because its real parent forks out the child before the child exits, it is an orphaned process inherited by init. Daemons are non-interactive programs with no control terminal, so any output, whether to stdout, the standard output device, or stderr, the standard error device, requires special handling.

From the process description above, in Linux zombie process is the parent process has not had time to wait(i.e., tell the kernel, this is my son, I will collect his corpse. The child process completes by telling the kernel that it will not collect the dead body for it. Going back to the orphan process issue, you can see that both orphaned processes, zombie processes, and daemons end up receiving the init process, so there is no such thing as an orphan process. A daemon is also an orphan process, in which the child is adopted by the init process after the parent exits and takes care of output devices. This adoptive process is called orphan, and the following article describes how to create a daemon-like child process by implementing a child process orphan and controlling its output device.

Process orphan implementation

  • The demo code uses the goGF scaffolding, which I have personally applied to the actual development, and many commonly used tools are well encapsulated.

Official documentation: goframe.org/ source address: github.com/gogf/gf

Example 1

  • The demo processEntrust an orphan toHow does the GO language control its output device
  • throughgo run t1.goRun the code, first usinggprocWill start a main process, through the main processforkThe current process starts the child process. The main process does not wait for the child process to exit, and the code below shows that I did not call itwaitThe child process. Child process passesgproc.IsChildDiscovers that it is a child process and enters the child process code block, in which the main control output device can be separated from the terminal, called hereos.PipeCreates a file pipeline that creates two pairs of reads and writes, and willwWrite pipe assignment toos.Stdout,w1Assign a value toos.Stderr, thus taking over the results of the two standard outputs. The next operation is readrandr1For the purposes of this demonstration, a new pipe file of type STdout is assigned toos.Stdout, let the terminal receiveprintThe results;

t1.go

package main

import (
	"fmt"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gproc"
	"io"
	"os"
	"syscall"
	"time"
)



func main (a) {
	if gproc.IsChild() {
		// Create a file pipeline for stdout
		r, w, _ := os.Pipe()
		// Create a file pipeline for stderr
		r1, w1, _ := os.Pipe()
		defer func(a) {
			_ = w.Close()
			_ = w1.Close()
		}()
		os.Stdout = w
		os.Stderr = w1

		glog.Printf("%d: Hi, I am child, waiting 30 seconds to die", gproc.Pid())
		time.Sleep(time.Second)
		glog.Printf("%d: 1", gproc.Pid())
		time.Sleep(time.Second)
		glog.Printf("%d: 2", gproc.Pid())
		time.Sleep(time.Second)
		glog.Printf("%d: 3", gproc.Pid())
		time.Sleep(time.Second * 30)

		glog.Printf("end")

		_ = w.Close()
		_ = w1.Close()

		var (
			outBuf = make([]byte.1)
			errBuf = make([]byte.1)
			output string
		)
		
		// Read stdout information
		for {
			_, err := r.Read(outBuf)
			if err == io.EOF {
				break
			}
			iferr ! =nil {
				panic(err)
			}

			if string(outBuf) ! ="" {
				output += string(outBuf)
			}
		}
		// Read stderr's message
		for {
			_, err := r1.Read(errBuf)
			if err == io.EOF {
				break
			}
			iferr ! =nil {
				panic(err)
			}

			if string(errBuf) ! ="" {
				output += string(errBuf)
			}
		}
		
		// Reassign to create a stdout pipe so that data can be printed to the terminal via stdout
		os.Stdout = os.NewFile(uintptr(syscall.Stdout), "/dev/stdout")
		fmt.Println(output)


	} else {
		m := gproc.NewManager()
		p := m.NewProcess(os.Args[0], os.Args, os.Environ())
		p.Start()

		//p.Wait()
		glog.Printf("Parent PID: %d", gproc.Pid())
	}
}

Copy the code
  • You can see that the pid is 806
# go run t1.go
2021-01-10 21:51:24.545 Parent PID: 806
Copy the code
  • The parent starts and exits, and init 1 takes over the child.
# ps l|grep t1
0     0   813     1  20   0 743752  7288 -      Sl   pts/1      0:00 /tmp/go-build311777737/b001/exe/t1
0     0   820   389  20   0 112748  2300 -      S+   pts/3      0:00 grep --color=auto t1
Copy the code
  • After 30 seconds, you see output on the terminal
# 2021-01-10 21:51:24.548 813: Hi, I am child, waiting 30 seconds to die2021-01-10 21:51:25.548 813:1 2021-01-10 21:51:26.549 813:2 2021-01-10 21:51:27.549 813:3 2021-01-10 21:51:57.549 endCopy the code

Example 2 (subreaper)

  • There’s another thing I want to introduce heresubreaper, a system call from the linux3.4 kernel,subreaperThe name means reaper of a subprocess, which means throughPR_SET_CHILD_SUBREAPERThis system call sets a process to the ancestor processinitProcesses can also adopt orphan processes (arg2 should be greater than 1, as shown in the code below). The way a child is adopted is by its nearest ancestor.

Reference: man7.org/linux/man-p…

  • The followingancestor_process.go, set yourself up as the ancestor process, and startsub_process_1.goThe first child process, and 60 seconds wait and call upwaitFunctions;sub_process_1Continue to launchsub_process_2butsub_process_1Don’t callwaitThe function exits and letssub_process_2He went into an orphan state. In the usual case where the ancestor process is not setsub_process_2Will beinitAdoption, but we have an ancestry process in place, sosub_process_2Should have been the nearestancestor_processAdoption.

ancestor_process.go

package main

import (
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gproc"
	"golang.org/x/sys/unix"
	"os"
	"time"
)

//
func SetSubreaper(i int) error {
	return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0.0.0)}func main (a) {
	// If the parameter is greater than 1, it can be set as the ancestor process
	if err := SetSubreaper(1) ; err != nil {
		panic(err)
	}
	glog.Printf("subreaper started....")
	m := gproc.NewManager()
	// Call the first child process
	p := m.NewProcess("/usr/local/go/bin/go"And []string{"run"."sub_process_1.go"}, os.Environ())

	_ , err := p.Start()
	iferr ! =nil {
		panic(err)
	}
	glog.Printf("ancestor: %d", gproc.Pid())
	time.Sleep(60 * time.Second)
	iferr := p.Wait(); err ! =nil {
		panic(err)
	}

}

Copy the code

sub_process_1.go

package main

import (
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gproc"
	"os"
)


func main (a) {
	glog.Printf("sub process 1....")
	m := gproc.NewManager()

	p := m.NewProcess("/usr/local/go/bin/go"And []string{"run"."sub_process_2.go"}, os.Environ())

	_ , err := p.Start()
	iferr ! =nil {
		panic(err)
	}
	glog.Printf("sub process 1: %d", gproc.Pid())

}
Copy the code

sub_process_2.go

package main

import (
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gproc"
	"os"
	"time"
)

func main(a) {
	glog.Printf("sub process 2.... started")
	glog.Printf("sub process 2: %d", gproc.Pid())
	glog.Printf("sub process 2: ppid %d", os.Getppid())
	time.Sleep(30 * time.Second)
	glog.Printf("sub process 2.... finished")}Copy the code
  • The output, this is weirdancestorThe PID turns out to be andsub_porecess_2The ppID of is inconsistent, actually because we are usinggo runStart, so another process is forked to execute.
# go run ancestor_process.goThe 2021-01-10 23:27:44. 109 subreaper started... 2021-01-10 23:27:44.114 sub Process 1.... 2021-01-10 23:27:44.625 Sub Process 1: 1608 2021-01-10 23:27:45.120 sub Process 2.... 2021-01-10 23:27:45.120 sub Process 2: 1648 2021-01-10 23:27:45.120 sub Process 2: 1648 2021-01-10 23:27:45. Ppid 1613 2021-01-10 23:28:15.120 sub Process 2.... finishedCopy the code
  • You can see who’s doing it by looking at the previous methodsub_process_2.goPid 1571 isancestorAnd its child process is 1613, which is 1613go runThis process, and the child of 1613 is 1648,

    The final relationship is1571(acestor) -> 1613(go run) -> 1648(sub_process_2);
# ps l |grep sub_process_2
0     0  1613  1571  20   0 836060 30040 -      Sl+  pts/1      0:00 /usr/local/go/bin/go run sub_process_2.go
0     0  1648  1613  20   0 745092  9420 -      Sl+  pts/1      0:00 /tmp/go-build141590160/b001/exe/sub_process_2
0     0  1662  1512  20   0 112748  2360 -      S+   pts/2      0:00 grep --color=auto sub_process_2
Copy the code

Write in the last

Here we are. Just give us a “like” before we leave. Since contacting the GO language, I have made up a lot of knowledge at the system level and network knowledge, and now I have to turn over the knowledge points that have been brought before. There will be an update on Containerd, and Docker’s love-hate relationship with Google(K8S) is now on the table.