[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: is kill 0 to be used to terminate the current script
From: |
Greg Wooledge |
Subject: |
Re: is kill 0 to be used to terminate the current script |
Date: |
Tue, 21 Mar 2023 09:28:45 -0400 |
On Tue, Mar 21, 2023 at 01:27:08PM +0100, alex xmb ratchev wrote:
> On Tue, Mar 21, 2023, 12:58 Greg Wooledge <greg@wooledge.org> wrote:
>
> DESCRIPTION
> > 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.
> that one , to send signal also to $( jobs -p ) ?
You're basically asking how process groups work. See below.
> never seen , must be .c manual
I pasted from "man 2 kill" on Linux. I thought it was pretty obvious
that it was a man page, from the formatting, and that it was in section 2,
from the words "system call".
OK, next, let's quote something from "man bash" regarding process groups:
To facilitate the implementation of the user interface to job control,
the operating system maintains the notion of a current terminal process
group ID. Members of this process group (processes whose process group
ID is equal to the current terminal process group ID) receive keyboard-
generated signals such as SIGINT. These processes are said to be in
the foreground.
After a bit more digging, I found "man 7 credentials" on Debian, which
also talks about process groups:
Process group ID and session ID
Each process has a session ID and a process group ID, both represented
using the type pid_t. A process can obtain its session ID using get‐
sid(2), and its process group ID using getpgrp(2).
A child created by fork(2) inherits its parent's session ID and process
group ID. A process's session ID and process group ID are preserved
across an execve(2).
Sessions and process groups are abstractions devised to support shell
job control. A process group (sometimes called a "job") is a collec‐
tion of processes that share the same process group ID; the shell cre‐
ates a new process group for the process(es) used to execute single
command or pipeline (e.g., the two processes created to execute the
command "ls | wc" are placed in the same process group). A process's
group membership can be set using setpgid(2). The process whose
process ID is the same as its process group ID is the process group
leader for that group.
So. We have a few concepts to explore here. The first is this thing
called a "process group ID". Each process has one, but you don't normally
see it in "ps" output, because it's not one of the default fields in either
the "-f" or the "u" output format. So we need to construct a special
command to see them.
unicorn:~$ ps -f
UID PID PPID C STIME TTY TIME CMD
greg 1010 999 0 Jan24 pts/2 00:00:00 bash
greg 339327 1010 0 08:58 pts/2 00:00:00 ps -f
unicorn:~$ ps -o uid,pid,ppid,pgid,tty,time,cmd
UID PID PPID PGID TT TIME CMD
1000 1010 999 1010 pts/2 00:00:00 bash
1000 339375 1010 339375 pts/2 00:00:00 ps -o uid,pid,ppid,pgid,tty,time
Now that we know how to see the PGID, we can verify what credentials(7)
is saying about pipelines.
unicorn:~$ PS1='pts/2:\w\$ '
pts/2:~$ sleep 100 | sleep 200 | grep foo
unicorn:~$ PS1='pts/3:\w\$ '
pts/3:~$ ps -t pts/2 -o pid,ppid,pgid,cmd
PID PPID PGID CMD
1010 999 1010 bash
339818 1010 339818 sleep 100
339819 1010 339818 sleep 200
339820 1010 339818 grep foo
Using two terminal windows, I created a pipeline in one, and ran ps in
the other, so that I could see the PIDs and PGIDs of the processes in
the pipeline. As promised, all the processes in the pipeline share
the same PGID (339818).
Therefore, if we wanted to kill the entire pipeline programatically (i.e.
not by simply pressing Ctrl-C in pts/2), we could send a signal to the
entire process group. Remember this part from kill(2):
If pid is less than -1, then sig is sent to every process in the
process group whose ID is -pid.
So, from pts/3 we could use this command:
kill -TERM -339818
That would send a SIGTERM to each of the three processes in the process
group (a.k.a. the pipeline).
This leads to the next obvious question: how does a script get the process
group ID of a pipeline?
pts/2:~$ sleep 100 | sleep 200 | grep foo &
[1] 340241
pts/2:~$ echo "$!"
340241
pts/3:~$ ps -t pts/2 -o pid,ppid,pgid,cmd
PID PPID PGID CMD
1010 999 1010 bash
340239 1010 340239 sleep 100
340240 1010 340239 sleep 200
340241 1010 340239 grep foo
That $! value isn't the process group ID. It's the PID of the last command
in the pipeline. To get the process group ID, we need to do more work.
pts/3:~$ ps -p 340241 -o pgid=
340239
So, that's one way we can get it. Maybe there's something cleaner?
No idea.
Now, you asked:
> that one , to send signal also to $( jobs -p ) ?
pts/2:~$ sleep 500 | sleep 600 &
[1] 340484
pts/2:~$ sleep 700 | sleep 800 | sleep 900 &
[2] 340490
pts/2:~$ jobs -p
340483
340488
It appears that "jobs -p" gives the process group IDs. Interesting. I
didn't know that (it's not in "help jobs" either). But that's really
handy -- we can skip the PGID lookup.
We can also take advantage of the fact that (jobs -p) in a subshell has
special voodoo that lets it report the parent's jobs.
pts/2:~$ mapfile -t jobs < <(jobs -p)
pts/2:~$ declare -p jobs
declare -a jobs=([0]="340483" [1]="340488")
And then with a little shell magic:
pts/2:~$ echo kill -TERM "${jobs[@]/#/-}"
kill -TERM -340483 -340488
pts/2:~$ kill -TERM "${jobs[@]/#/-}"
pts/2:~$
[1]- Terminated sleep 500 | sleep 600
[2]+ Terminated sleep 700 | sleep 800 | sleep 900