5

I'm learning about the relationship between processes, process groups (and sessions) in Linux.

I compiled the following program...

#include <iostream>
#include <ctime>
#include <unistd.h>

int main( int argc, char* argv[] )
{
  char buf[128];
  time_t now;
  struct tm* tm_now;
  while ( true )
  {
    time( &now );
    tm_now = localtime( &now );
    strftime( buf, sizeof(buf), "%a, %d %b %Y %T %z", tm_now );
    std::cout << buf << std::endl;
    sleep(5);
  }
  return 0;
}

... to a.out and ran it as a background process like so...

a.out &

This website says the following...

Every process is member of a unique process group, identified by its process group ID. (When the process is created, it becomes a member of the process group of its parent.) By convention, the process group ID of a process group equals the process ID of the first member of the process group, called the process group leader.

Per my reading, the first sentence conflicts with the in-parentheses content: is a process a member of a unique process group, or is it a member of the process group of its parent?

I tried to investigate with ps...

ps xao pid,ppid,pgid,sid,command | grep "PGID\|a.out"
  PID  PPID  PGID   SID COMMAND
24714 23890 24714 23890 ./a.out

This tells me my a.out process is pid 24714, spawned from parent pid 23890 and part of program group 24714. To begin with, I don't understand why this pgid matches the pid.

Next, I tried to investigate the parent process...

ps xao pid,ppid,pgid,sid,command | grep "PGID\|23890"
  PID  PPID  PGID   SID COMMAND
23890 11892 23890 23890 bash
24714 23890 24714 23890 ./a.out

It makes sense to me that the parent process of my a.out is bash. At first I thought "bash's pid matches its pgid - that must be because it's the process group leader. Maybe that makes sense because bash is kind of the "first thing" that got run, from which I ran my process." But that reasoning doesn't make sense because a.out's pgid also matches its own pid.

Why doesn't a.out's pgid equal bash's pgid? That's what I would have expected, from my understanding of the quote.

Can someone clarify the relationship between pids and pgids?

4

There is no conflict; a process will by default be in a unique process group which is the process group of its parent:

$ cat pg.c
#include <stdio.h>
#include <unistd.h>
int main(void)
{
    fork();
    printf("pid=%d pgid=%d\n", getpid(), getpgrp());
}
$ make pg
cc     pg.c   -o pg
$ ./pg 
pid=12495 pgid=12495
pid=12496 pgid=12495
$ 

The fork splits our process into parent (12495) and child (12496), and the child belongs to the unique process group of the parent (12495). bash departs from this because it issues additional system calls:

$ echo $$
12366
$

And then in another terminal we run:

$ strace -f -o blah -p 12366

And then back in the first terminal:

$ ./pg
pid=12676 pgid=12676
pid=12677 pgid=12676
$

And then we control+c the strace, and inspect the system calls:

$ egrep 'exec|pgid' blah
12366 setpgid(12676, 12676)             = 0
12676 setpgid(12676, 12676 <unfinished ...>
12676 <... setpgid resumed> )           = 0
12676 execve("./pg", ["./pg"], [/* 23 vars */]) = 0
12676 write(1, "pid=12676 pgid=12676\n", 21 <unfinished ...>
12677 write(1, "pid=12677 pgid=12676\n", 21 <unfinished ...>

bash has used the setpgid call to set the process group, thus placing our pg process into process group unrelated to that of the shell. (setsid(2) would be another way to tweak the process group, if you're hunting for system calls.)

  • Do I have this straight? The strace tells us bash (pid 12366) leads to creation (I guess forking) of child process (pid 12676). Then bash calls setpgid to set the pgid of the child process (12676) to 12676. But what's with the second call to setpgid(12676, 12676) that seems to be performed by process 12676? Then lastly, bash execves the program to kick off the process. – StoneThrow May 4 '17 at 22:39
  • And I guess the explicit call to setpgid would be the reason why pg's pgid doesn't match bash's pgid. As opposed to the parent/child processes from the pg executable that demonstrate the "expected" behavior because there is no explicit call to setpgid() (?) – StoneThrow May 4 '17 at 22:42
  • 1
    There's a fork (clone, actually) not shown where bash splits into 12366 and 12676. The call complications aside, it's mostly to show that bash does not follow the expected behavior because it makes an extra system call. – thrig May 4 '17 at 22:48
  • bash puts the child into a new process group to implement job control. Each job has to be a separate process group from the shell so it can be moved from foreground to background. – Barmar May 5 '17 at 1:45
5

Bash puts your program in its own process group as part of its job control handling. For example, from the bash manpage:

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 SIG‐ INT. These processes are said to be in the foreground. Background processes are those whose process group ID differs from the terminal's; such processes are immune to keyboard-generated signals. Only foreground processes are allowed to read from or, if the user so specifies with stty tostop, write to the terminal. Background processes which attempt to read from (write to when stty tostop is in effect) the terminal are sent a SIGTTIN (SIGTTOU) signal by the kernel's terminal driver, which, unless caught, suspends the process.

Also the part about set -m:

Monitor mode. Job control is enabled. This option is on by default for interactive shells on systems that support it (see JOB CONTROL above). All processes run in a separate process group. When a background job completes, the shell prints a line containing its exit status.

So, if you want the behavior you're expecting, you can:

  1. Use fork inside your code, to create a process without bash doing it.
  2. Use set +m to turn off monitor mode in bash. This will break things like fg, though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.