Ir para o conteúdo
ou

Software livre Brasil

 Voltar a Blog
Tela cheia

Life after exec()

9 de Janeiro de 2012, 0:00 , por Software Livre Brasil - | Ninguém está seguindo este artigo ainda.

From the “not necessarily big news, but still useful” department.

The situation: for Very Good Reasons™1, you want to replace your current process by calling exec, but you still want to have the chance to do something after the process you exec()ed finishes.

This is a simple technique I just came up with: just before replacing the current process by calling exec(), you fork() a process in the background that will wait for the current process id to disappear from the process list, and then does whatever you want to do.

A simple proof-of-concept I wrote is composed of two bash programs: wrapper and real.

real is really simple: it just waits a few seconds and then prints its process id to the console:

#!/bin/bash
  
sleep 5
echo $BASHPID

wrapper is the program that handles the situation we want to exercise: it replaces itself with real, but still has the chance to do something after real finishes. In this case, wrapper notifies the user that real finished.

#!/bin/bash

echo $BASHPID
real_program_pid=$BASHPID
(
  while ps -p "$real_program_pid" >/dev/null; do
    sleep 0.1s
  done
  notify-send 'real program finished'
) &
exec ./real

One nice property that wrapper explores is that when exec() starts real, it really replaces wrapper, and therefore has the same process id (in this case accessible by bash in the $BASHPID variable). Because of this, the background process that wrapper starts just before the exec() call already knows which process it has to watch for.

The actual code for waiting is not optimal, though. I cannot use waitpid() (the wait builtin in bash), since real is not a child process of wrapper. I went with a brute force approach here, and I am pretty sure there is a cheaper way to wait for a random PID without a busy loop (but that wasn’t the point here).

1 update: I am aware of the classic fork()/exec() pattern. My Very Good Reasons™ include the fact that I can’t control the flow: I am writing a plugin for a program that calls its plugins in sequence, and after that, calls exec(), but my plugin is interested in doing some work after exec() finishes.


Tags deste artigo: english exec fork unix bash

44 comentários

  • 8370f1272881f06e450938a80b7f28b8?only path=false&size=50&d=404Thadeu Cascardo(usuário não autenticado)
    9 de Janeiro de 2012, 13:40

    Make real the child

    Usually, you use exec in the child, so you can wait in the parent. You can do it, then, in a much simpler way. Wrapper would be like:

    ./real &
    wait $!


  • Cd6f6d866c49294e2fc74940822fe528?only path=false&size=50&d=404martin f. krafft(usuário não autenticado)
    9 de Janeiro de 2012, 13:57

    Why?

    I really don't understand the Very Good Reasons™ that force you to do this. AS the previous commenter said: just run a child.


  • 03bae673708dc477eb5081271eb8e255?only path=false&size=50&d=404Jon(usuário não autenticado)
    9 de Janeiro de 2012, 14:37

    Please expand on the rationale

    Echoing the above comments. I guess (but you don't expand on this) that you *have* to have the same PID, hence your song-and-dance. But this is a strange inversion of the classic fork/exec and wait for child PID situation that Thadeu mentions.

    Why won't fork/exec/wait work for you? If you can expand on this then we can see the point... otherwise it looks like you may not be aware of fork/exec/wait!


  • 50e8566ae648791d494e7316ed51bbbe?only path=false&size=50&d=404Sam Hocevar(usuário não autenticado)
    10 de Janeiro de 2012, 10:59

    Race condition

    Regardless of your valid reasons, this is very bad advice because it introduces a race condition.

    Your problem can be solved using the fork()/exec() pattern. Call fork() in the plugin handler. In the parent, wait() for the child, call the cleanup code, exit(). In the child, return to the main program; it's the child that will exec().