PPID=1 runs as a background process, rather than being under the direct control of an interactive user Process Groups and Tty Management 进程组 TTY
kill(2) - Linux manual page https://man7.org/linux/man-pages/man2/kill.2.html
NAME top
kill - send signal to a process
SYNOPSIS top
#include <signal.h> int kill(pid_t pid, int sig); Feature Test Macro Requirements for glibc (see feature_test_macros(7)): kill(): _POSIX_C_SOURCE
DESCRIPTION top
The kill() system call can be used to send any signal to any process group or process. If pid is positive, then signal sig is sent to the process with the ID specified by pid. If pid equals 0, then sig is sent to every process in the process group of the calling process. If pid equals -1, then sig is sent to every process for which the calling process has permission to send signals, except for process 1 (init), but see below. If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid. If sig is 0, then no signal is sent, but existence and permission checks are still performed; this can be used to check for the existence of a process ID or process group ID that the caller is permitted to signal. For a process to have permission to send a signal, it must either be privileged (under Linux: have the CAP_KILL capability in the user namespace of the target process), or the real or effective user ID of the sending process must equal the real or saved set- user-ID of the target process. In the case of SIGCONT, it suffices when the sending and receiving processes belong to the same session. (Historically, the rules were different; see NOTES.)
RETURN VALUE top
On success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set to indicate the error.
ERRORS top
EINVAL An invalid signal was specified. EPERM The calling process does not have permission to send the signal to any of the target processes. ESRCH The target process or process group does not exist. Note that an existing process might be a zombie, a process that has terminated execution, but has not yet been wait(2)ed for.
CONFORMING TO top
POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD.
如何避免 Go 命令行执行产生“孤儿”进程? https://mp.weixin.qq.com/s/MJKlUBF9_vpGt2z9r7WjsA
如何避免 Go 命令行执行产生“孤儿”进程?
四 总结
当我们使用 Go 程序执行其他程序的时候,如果其他程序也开启了其他进程,那么在 kill 的时候可能会把这些进程变成孤儿进程,一直执行并滞留在内存中。当然,如果我们程序非法退出,或者被 kill 调用,也会导致我们执行的进程变成孤儿进程,那么为了解决这个问题,从两个思路来解决:
-
给要执行的程序创建新的进程组,并调用 syscall.Kill,传递负值 pid 来关闭这个进程组中所有的进程(比较完美的解决方法)。
-
如果要调用的程序也是我们自己编写的,那么可以使用 PR_SET_PDEATHSIG 来感知父进程退出,那么这种方式需要调用 Linxu 的 prctrl,可以使用 CGO 的方式,也可以使用 syscall.RawSyscall 的方式。
但不管使用哪种方式,都只是提供了一种思路,在我们编写服务端服务程序的时候,需要特殊关注,防止孤儿进程消耗服务器资源。
https://zh.wikipedia.org/wiki/行程群組
UNIX Signals and Process Groups https://sites.cs.ucsb.edu/~almeroth/classes/W99.276/assignment1/signals.html
Process group - Wikipedia https://en.wikipedia.org/wiki/Process_group
在兼容于POSIX标准的操作系统中,进程组(英语:Process group,又译过程组群)是指一个或多个进程的集合。进程组被使用于控制信号的分配。对于一个进程组发出的的信号,会被个别递送到这个组群下的每个进程成员中。
进程组本身,也可以被集合成一个组群来管理,称为会话组(sessions)。归属于某个特定会话组下的进程组,不能移动到别的会话组下;在某个进程组下的特定进程,在创造出新的进程时,这个进程也只能属于这个父进程所归属的相同会话组。
应用
shell程序借由提交信号(Signal)给进程组(Process groups)的方式来控制任务(job)。tty设备使用所谓的前景进程组(foreground process group),借由键盘送给这种组群的信号来产生中断:
- SIGINT (INTerrupt, Control+C)
- SIGTSTP (Terminal SToP, Control+Z)
- SIGQUIT (QUIT, Control+\)
而背景进程组(Background process group)在尝试读取或者写至终端(Terminal)时,进程将收到SIGTTIN或SIGTTOU信号。接下来shell会切开该指令丢进进程组,然后控制在前景的进程组来处理终端有关的指令。
Process Groups and Tty Management
One of the areas least-understood by most UNIX programmers is process-group management, a topic that is inseparable from signal-handling.
To understand why process-groups exist, think back to the world before windowing systems.
Your average developer wants to run several programs simultaneously -- usually at least an editor and a compilation, although often a debugger as well. Obviously you cannot have two processes reading from the same tty at the same time -- they'll each get some of the characters you type, a useless situation. Likewise output should be managed so that your editor's output doesn't get the output of a background compile intermixed, destroying the screen.
This has been a problem with many operating systems. One solution, used by Tenex and TOPS-20, was to use process stacks. You could interrupt a process to run another process, and when the new process was finished the old would restart.
While this was useful it didn't allow you to switch back and forth between processes (like a debugger and editor) without exiting one of them. Clearly there must be a better way.
The Berkeley Approach
The Berkeley UNIX folks came up with a different idea, called process groups. Whenever the shell starts a new command each process in the command (there can be more than one, eg "ls | more") is placed in its own process group, which is identified by a number. The tty has a concept of "foreground process group", the group of processes which is allowed to do input and output to the tty. The shell sets the foreground process group when starting a new set of processes; by convention the new process group number is the same as the process ID of one of the members of the group. A set of processes has a tty device to which it belongs, called its "controlling tty". This tty device is what is returned when /dev/tty is opened.
Because you want to be able to interrupt the foreground processes, the tty watches for particular keypresses (^Z is the most common one) and sends an interrupt signal to the foreground process group when it sees one. All processes in the process group see the signal, and all stop -- returning control to the shell.
At this point the shell can place any of the active process groups back in the foreground and restart the processes, or start a new process group.
To handle the case where a background process tries to read or write from the tty, the tty driver will send a SIGTTIN
or SIGTTOU
signal to any background process which attempts to perform such an operation. Under normal circumstances, therefore, only the foreground process(es) can use the tty.
The set of commands to handle process groups is small and straightforward. Under BSD, the commands are:
int setpgrp(int
process_id, int
group_number);
- Move a process into a process group. If you are creating a new process group the group_number should be the same as process_id. If process_id is zero, the current process is moved.
int getpgrp(int
process_id);
- Find the process group of the indicated process. If process_id is zero, the current process is inspected.
int killpgrp(int
signal_number, int
group_number);
- Send a signal to all members of the indicated process group.
int ioctl(int
tty, TIOCSETPGRP, int
foreground_group);
- Change the foreground process group of a tty.
int ioctl(int
tty, TIOCGETPGRP, int *
foreground_group);
- Find the foreground process group of a tty.
int ioctl(int
tty, TIOCNOTTY, 0);
- Disassociate this process from its controlling tty. The next tty device that is opened will become the new controlling tty.
The POSIX Approach
The BSD process-group API is rarely used today, although most of the concepts survive. The POSIX specification has provided new interfaces for handling process groups, and even overloaded some existing ones. It also limits several of the calls in ways which BSD did not.
The POSIX process-group API is:
int setpgid(int
process_id, int
process_group);
- Move a process into a new process group. Process_id is the process to move, process_group is the new process group.
int getpgid(int
process_id);
- Find the process group of a process. Process_id is the process to inspect.
int getpgrp(void);
- Find the process group of the current process. This is identical to
getpgrp(getpid())
. int tcsetpgrp(int
tty, int
foreground_group);
- Change the foreground process group of a tty. Tty is the file descriptor of the tty to change, foreground_group is the new foreground process group.
int tcgetpgrp(int
tty, int *
foreground_group);
- Find the foreground process group of a tty. Tty is the file descriptor of the tty to inspect, foreground_group is returned filled with the foreground process group of the tty.
int kill(int -
process_group, int
signal_number);
- Send a signal to a process group. Note that process_group must be passed as a negative value, otherwise the signal goes to the indicated process.
Differences between POSIX and BSD Process Group Management
The setpgrp()
function is called setpgid()
under POSIX and is essentially identical. You must be careful under POSIX not to use the setpgrp()
function -- usually it exists, but performs the operation of setsid()
.
The getpgrp()
function was renamed getpgid()
, and getpgid()
can only inspect the current process' process group.
The killpgrp()
function doesn't exist at all. Instead, a negative value passed to the kill()
function is taken to mean the process group. Thus you'd perform killpgrp(
process_group)
by calling kill(-
process_group)
.
The ioctl()
commands for querying and changing the foreground process group are replaced with first-class functions:
int tcsetpgrp(int
tty, int
process_group);
int tcgetpgrp(int
tty, int *
process_group);
While the original BSD ioctl()
functions would allow any tty to take on any process group (or even nonexistant process groups) as its foreground tty, POSIX allows only process groups which have the tty as their controlling tty. This limitation disallows some ambiguous (and potentially security-undermining) cases present in BSD.
The TIOCNOTTY ioctl used in BSD is replaced with the setsid()
function, which is essentially identical to:
if (getpgrp() != getpid()) { ioctl(tty, TIOCNOTTY, 0); setpgrp(getpid(), getpid()); }
It releases the current tty and puts the calling process into its own process group. Notice that nothing is done if the calling process is already in its own process group -- this is another new limitation, and eliminates some ambiguous cases that existed in BSD (along with some of BSD's flexibility).
https://en.wikipedia.org/wiki/Daemon_(computing)
【后台进程,非互动】
d 结尾
syslogd 系统日志记录
sshd 响应ssh连接请求
In multitasking computer operating systems, a daemon (/ˈdiːmən/ or /ˈdeɪmən/)[1] is a computer program that runs as a background process, rather than being under the direct control of an interactive user. Traditionally, the process names of a daemon end with the letter d, for clarification that the process is, in fact, a daemon, and for differentiation between a daemon and a normal computer program. For example, syslogd is the daemon that implements the system logging facility, and sshd is a daemon that serves incoming SSH connections.
In a Unix environment, the parent process of a daemon is often, but not always, the init process. A daemon is usually either created by a process forking a child process and then immediately exiting, thus causing init to adopt the child process, or by the init process directly launching the daemon. In addition, a daemon launched by forking and exiting typically must perform other operations, such as dissociating the process from any controlling terminal (tty). Such procedures are often implemented in various convenience routines such as daemon(3) in Unix.
【系统启动时,启动】
如对网络请求、硬件活动的响应
Systems often start daemons at boot time which will respond to network requests, hardware activity, or other programs by performing some task. Daemons such as cron may also perform defined tasks at scheduled times
https://zh.wikipedia.org/wiki/守护进程
在一个多任务的电脑操作系统中,守护进程(英语:daemon,/ˈdiːmən/或/ˈdeɪmən/)是一种在后台执行的电脑程序。此类程序会被以进程的形式初始化。守护进程程序的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。
通常,守护进程没有任何存在的父进程(即PPID=1),且在UNIX系统进程层级中直接位于init之下。守护进程程序通常通过如下方法使自己成为守护进程:对一个子进程运行fork,然后使其父进程立即终止,使得这个子进程能在init下运行。这种方法通常被称为“脱壳”。
系统通常在启动时一同引导守护进程。守护进程为对网络请求,硬件活动等进行响应,或其他通过某些任务对其他应用程序的请求进行回应提供支持。守护进程也能够对硬件进行配置(如在某些Linux系统上的devfsd),运行计划任务(例如cron),以及运行其他任务。
在DOS环境中,此类应用程序被称为驻留程序(TSR)。在Windows系统中,由称为Windows服务的应用程序来履行守护进程的职责。
在原本的Mac OS系统中,此类应用程序被称为“extensions”。而作为Unix-like的Mac OS X有守护进程。(在Mac OS X中也有“服务”,但他们与Windows中类似的程序在概念上完全不相同。)
https://zh.wikipedia.org/wiki/父进程
在计算机领域,父进程(英语:Parent Process)指已创建一个或多个子进程的进程。
UNIX
在UNIX里,除了进程0(即PID=0的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。
操作系统内核以进程标识符(Process Identifier,即PID)来识别进程。进程0是系统引导时创建的一个特殊进程,在其调用fork创建出一个子进程(即PID=1的进程1,又称init)后,进程0就转为交换进程(有时也被称为空闲进程),而进程1(init进程)就是系统里其他所有进程的祖先。
僵尸进程与孤儿进程
当一个子进程结束运行(一般是调用exit、运行时发生致命错误或收到终止信号所导致)时,子进程的退出状态(返回值)会回报给操作系统,系统则以SIGCHLD信号将子进程被结束的事件告知父进程,此时子进程的进程控制块(PCB)仍驻留在内存中。一般来说,收到SIGCHLD后,父进程会使用wait系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的PCB;而如若父进程没有这么做的话,子进程的PCB就会一直驻留在内存中,也即成为僵尸进程。
孤儿进程则是指父进程结束后仍在运行的子进程。在类UNIX系统中,孤儿进程一般会被init进程所“收养”,成为init的子进程。
为避免产生僵尸进程,实际应用中一般采取的方式是:
- 将父进程中对SIGCHLD信号的处理函数设为SIG_IGN(忽略信号);
- fork两次并杀死一级子进程,令二级子进程成为孤儿进程而被init所“收养”、清理[1]。
Linux
在Linux内核中,进程和POSIX线程有着相当微小的区别,父进程的定义也与UNIX不尽相同。Linux有两种父进程,分别称为(形式)父进程与实际父进程,对于一个子进程来说,其父进程是在子进程结束时收取SIGCHLD信号的进程,而实际父进程则是在多线程环境里实际创建该子进程的进程。对于普通进程来说,父进程与实际父进程是同一个进程,但对于一个以进程形式存在的POSIX线程,父进程和实际父进程可能是不一样的[2]。
守护进程
ppid 1
https://unix.stackexchange.com/questions/240646/why-we-use-setsid-while-daemonizing-a-process
nohup 修改ppid 为1
https://pubs.opengroup.org/onlinepubs/7908799/xsh/setsid.html
https://github.com/karelzak/util-linux/blob/master/sys-utils/setsid.c
https://unix.stackexchange.com/questions/316186/how-does-nohup-work
The act of using nohup
simply changes the PPID of the spawned process to 1 (init) so that when the shell exits, it is no longer a child process of that shell and so therefor doesn't receive a HUP.
EDIT: Should think more before I post sometimes. Leaving it here to remind me of my shame :-(
https://zh.wikipedia.org/wiki/孤儿进程
在操作系统领域中,孤儿进程(Orphan Process)指的是在其父进程执行完成或被终止后仍继续运行的一类进程。
解决办法
“收养”
在类UNIX操作系统中,为避免孤儿进程退出时无法释放所占用的资源而僵死,任何孤儿进程产生时都会立即为系统进程init或systemd自动接收为子进程,这一过程也被称为“收养”(英语:re-parenting)[1]。在此需注意,虽然事实上该进程已有init作为其父进程,但由于创建该进程的进程已不存在,所以仍应称之为“孤儿进程”。
进程组
因为父进程终止或崩溃都会导致对应子进程成为孤儿进程,所以也无法预料一个子进程执行期间是否会被“遗弃”。有鉴于此,多数类UNIX系统都引入了进程组以防止产生孤儿进程:在父进程终止后,用户的Shell会将父进程所在进程组标为“孤儿进程组”,并向终止的进程下属所有子进程发出SIGHUP信号,以试图结束其运行,如此避免子进程继续以“孤儿进程”的身份运行[2]。
远程调用的情况
RPC过程中也会产生孤儿进程。例如,若客户端进程在发起请求后突然崩溃,且对应的服务器端进程仍在运行,则该服务器端进程就会成为孤儿进程。这样的孤儿进程会浪费服务器的资源,甚至有耗尽资源的潜在危险,但也有对应的解决办法[3]:
- 终止机制:强制杀死孤儿进程(最常用的手段);
- 再生机制:服务器在指定时间内查找调用的客户端,若找不到则直接杀死孤儿进程;
- 超时机制:给每个进程指定一个确定的运行时间,若超时仍未完成则强制终止之。若有需要,亦可让进程在指定时间耗尽之前申请延时。
“孤儿进程”的应用
除此之外,用户也可能会刻意使进程成为孤儿进程,以使之与用户会话脱钩,并转至后台运行。这一做法常应用于启动需要长时间运行的进程,也即守护进程[4]。另外,UNIX命令nohup也可以完成这一操作[5]。
https://linux.die.net/man/3/execvp
The exec() family of functions replaces the current process image with a new process image. The functions described in this manual page are front-ends for execve(2). (See the manual page for execve(2) for further details about the replacement of the current process image.)
The initial argument for these functions is the name of a file that is to be executed.
https://zh.wikipedia.org/wiki/进程ID
在计算机领域,进程标识符(英语:process identifier,又略称为进程ID(英语:process ID)、PID)是大多数操作系统的内核用于唯一标识进程的一个数值。这一数值可以作为许多函数调用的参数,以使调整进程优先级、杀死进程之类的进程控制行为成为可能。
类UNIX系统
在类UNIX操作系统中,新进程都衍自系统调用fork()。fork()调用会将子进程的PID返回给父进程,使其可以之指代子进程,从而在需要时以之为函数参数。例如,若以子进程PID为参数调用waitpid(),可使父进程以休眠状态等待子进程结束;若以之为参数调用kill(),便可结束对应子进程。
在各PID中,较为特别的是0号PID和1号PID。PID为0者为交换进程(英语:swapper),属于内核进程,负责分页任务;PID为1者则常为init进程,主要负责启动与关闭系统。值得一提的是,1号PID本来并非是特意为init进程预留的,而init进程之所以拥有这一PID,则是因为init即是内核创建的第一个进程。不过,现今的许多UNIX/类UNIX系统内核也有以进程形式存在的其他组成部分,而在这种情况下,1号PID则仍为init进程保有,以与之前系统保持一致[1]。
PID的分配机制则因系统而异,一般从0开始,然后顺序分配,直到达到一个最大值(亦因系统而异),而后又从300开始重新分配;在Mac OS X和HP-UX下,则是由100开始重分配。在分配PID时,若遇到已分配的PID,则直接跳过,继续递增查找下一个可分配PID。
Microsoft Windows
Microsoft Windows系列操作系统提供了一系列API,以使开发者可以获取相关PID,如用于获取当前进程PIDGetCurrentProcessId()
[2]、返回其他进程PID的GetProcessId()
[3]。在操作系统内部,进程ID与线程ID在同一个名字空间中,因此二者不会重合。
PID文件
有些长时间运行的进程(如MySQL的守护进程)会将自己的PID写入一个文件,以使其他进程可寻获之。
参见
https://en.wikipedia.org/wiki/Process_identifier
In computing, the process identifier (a.k.a. process ID or PID) is a number used by most operating system kernels—such as those of Unix, macOS and Windows—to uniquely identify an active process. This number may be used as a parameter in various function calls, allowing processes to be manipulated, such as adjusting the process's priority or killing it altogether.
Unix-like
In Unix-like operating systems, new processes are created by the fork()
system call. The PID is returned to the parent process, enabling it to refer to the child in further function calls. The parent may, for example, wait for the child to terminate with the waitpid()
function, or terminate the process with kill()
.
There are two tasks with specially distinguished process IDs: swapper or sched has process ID 0 and is responsible for paging, and is actually part of the kernel rather than a normal user-mode process. Process ID 1 is usually the init process primarily responsible for starting and shutting down the system. Originally, process ID 1 was not specifically reserved for init by any technical measures: it simply had this ID as a natural consequence of being the first process invoked by the kernel. More recent Unix systems typically have additional kernel components visible as 'processes', in which case PID 1 is actively reserved for the init process to maintain consistency with older systems.
Process IDs, in the first place, are usually allocated on a sequential basis, beginning at 0 and rising to a maximum value which varies from system to system. Once this limit is reached, allocation restarts at 300 and again increases. In macOS and HP-UX, allocation restarts at 100. However, for this and subsequent passes any PIDs still assigned to processes are skipped. Some consider this to be a potential security vulnerability in that it allows information about the system to be extracted, or messages to be covertly passed between processes. As such, implementations that are particularly concerned about security may choose a different method of PID assignment.[1] On some systems, like MPE/iX, the lowest available PID is used, sometimes in an effort to minimize the number of process information kernel pages in memory.
The current process ID is provided by a getpid()
system call, or as a variable $$
in shell. The process ID of a parent process is obtainable by a getppid()
system call.
On Linux, the maximum process ID is given by the pseudo-file /proc/sys/kernel/pid_max
.[2]
Pidfile
Some processes, for example, the moc music player and the MySQL daemon, write their PID to a documented file location, to allow other processes to look it up.
Microsoft Windows
On the Windows family of operating systems, one can get the current process's ID using the GetCurrentProcessId()
function of the Windows API,[3] and ID of other processes using GetProcessId()
.[4] Internally, process ID is called a client ID, and is allocated from the same namespace as thread IDs, so these two never overlap. The System Idle Process is given process ID 0. The System Process is given the process ID 8 on Windows 2000 and 4 on Windows XP and Windows Server 2003.[5] On the Windows NT family of operating systems, process and thread identifiers are all multiples of 4, but it is not part of the specification.[6]
See also
- User identifier (UID)
- Group identifier (GID)
- Handle (computing)
- Program Segment Prefix (PSP)