Russian Process Roulette

A couple of friends and I just discussed the idea of Russian Process Roulette: Everybody starts a little program which kills processes at random. The winner is the one whose computer remains usable the longest. Is this crazy? Yes. Immature? Sure. Fun? Definitely. Adviced against to actually play unless the consequences are fully understood? That too.

Playing the game is as simple as starting a command like the following:

while sleep 600; do kill -9 $(tr -cd "[:digit:]" < /dev/urandom | head -c5); done

This one-liner will wake up every 10 minutes, generate a random 5-digit number and then kill the process with the corresponding process ID. It will run forever, until it is either stopped, kills itself or has successfully crashed the user’s session.

Of course, there are some problems with this code. The time at which the next process will be killed is predictable, making it easier to prepare for the eventuality of a dying process. Worse, the range of random process ids (0 to 99999) is about three times as large as the actual range of process ids on a typical linux system (0 to 32768). This lowers the chance of hitting a valid process ID quite a bit. So let’s put our scripting-fu to some misguided use and “optimize” the code.

The first step is to write a function which gives uniformly distributed random numbers below a threshold.

random_number_below () {
  local upperbound=$1
  local candidate=$(( $1 + 1 ))
  local maxlen=$(printf $upperbound | wc -c)
  while [ $candidate -gt $upperbound ]
  do
      candidate=$(tr -cd '[:digit:]' < /dev/urandom | head -c "$maxlen" )
  done
  echo $candidate
}

Our random number generator is very very wasteful in terms of processor cycles. Even more, if the output of /dev/urandom is truly random, the function were not even guaranteed to terminate in any specified time frame. But given the nature of the application we have in mind, both caveats are completely acceptable here. Candidate numbers are generated by reading random characters from the systems urandom device, throwing away every character that isn’t a digit. After this, the candidate number is checked to make sure we haven’t generated a number greater than the specified upper bound. If that should be the case, we run the whole procedure again. Otherwise, the random number is returned. While wasteful, this function produces an unbiased uniform distribution on the interval [0, upperbound].

The second problem, regarding the range of possible process identifiers, is easier to fix. A little bit of searching reveals that the largest possible process identifier can be read from the file /proc/sys/kernel/pid_max. With this functionality in our hands, we can now write a “better” Russian Process Roulette script.

while sleep $(random_number_below 999)
do
    local maxpid=$(cat /proc/sys/kernel/pid_max)
    kill -9 $(random_number_below "$maxpid")
done

We should (probably not) run this as root, thereby making sure no process is safe from our “process gun”.

While the result of our efforts isn’t necessarily useful, it is still a nice exercises in shell scripting. Our game remains destructive and immature, but the execution is slightly more sophisticated. Hope you like it.