Search code examples
shellsubprocessupstart

how many child process (subprocess) generated by 'su -c command'


When using Upstart, controlling subprocesses (child process) is quite important. But what confused me is as following, which has gone beyond upstart itself:

scenario 1:

root@ubuntu-jstorm:~/Desktop# su cr -c 'sleep 20 > /tmp/a.out'

I got 3 processes by: cr@ubuntu-jstorm:~$ ps -ef | grep -v grep | grep sleep

root       8026   6544  0 11:11 pts/2    00:00:00 su cr -c sleep 20 > /tmp/a.out
cr         8027   8026  0 11:11 ?        00:00:00 bash -c sleep 20 > /tmp/a.out
cr         8028   8027  0 11:11 ?        00:00:00 sleep 20

scenario 2:

root@ubuntu-jstorm:~/Desktop# su cr -c 'sleep 20'

I got 2 processes by: cr@ubuntu-jstorm:~$ ps -ef | grep -v grep | grep sleep

root       7975   6544  0 10:03 pts/2    00:00:00 su cr -c sleep 20
cr         7976   7975  0 10:03 ?        00:00:00 sleep 20

The process of sleep 20 is the one I care, especially in Upstart, the process managed by Upstart should be this while not bash -c sleep 20 > /tmp/a.out is managed by Upstart, while not the sleep 20.

In scenario 1, upstart will not work correctly, above is the reason.

Therefore, why scenario 1 got 3 process, this doesn't make sense for me. Even though I know I can use command 'exec' to fix it, I just want to get the procedure what happened when the two command committed.


Solution

  • su -c starts the shell and passes it the command via its -c option. The shell may spawn as many processes as it likes (it depends on the given command).

    It appears the shell executes the command directly without forking in some cases e.g., if you run su -c '/bin/sleep $$' then the apparent behaviour as if:

    1. su starts a shell process (e.g., /bin/sh)
    2. the shell gets its own process id (PID) and substitute $$ with it
    3. the shell exec() /bin/sleep.

    You should see in ps output that sleep's argument is equal to its pid in this case.

    If you run su -c '/bin/sleep $$ >/tmp/sleep' then /bin/sleep argument is different from its PID (it is equal to the ancestor's PID) i.e.:

    1. su starts a shell process (e.g., /bin/sh)
    2. the shell gets its own process id (PID) and substitute $$ with it
    3. the shell double forks and exec() /bin/sleep.

    The double fork indicates that the actual sequence of events might be different e.g., su could orchestrate the forking or not forking, not the shell (I don't know). It seems the double fork is there to make sure that the command won't get a controlling terminal.