Forking A New Process Using GLib

These days we tend to think of concurrency in terms of spawning threads. Need to perform a long running calculation? Spawn a thread. However, there are other ways; we can fork and create a new process. Unfortunately for us, fork and threads don’t play nice together. How so, you ask? When you fork a new process, only the current thread is copied into the new process. If any other thread held a lock on a mutex, that mutex will never be unlocked in the new process. This includes mutexes held by system calls such as malloc.

In light of this, you may be wondering why I’m wasting your time with this. No, this isn’t just a PSA, there is something sane you can do with fork in a multi-threaded world: you can call exec and friends. And it just so happens that GLib can help us with this. GLib provides us with Process Spawning facilities that integrate with GIOChannel and GMainLoop.

The first thing you may notice is that there isn’t actually a GLib equivalent to fork or exec. These two calls are combined into the g_*_spawn_* family of functions. The reason for this is because GLib itself spawns threads to perform work. By default, *all* GLib applications potentially have threads running and as such it is never safe to call fork without immediately calling exec.

Forking A New Process

First, let’s do some setup:

gchar * child_argv[] = {"[PROGRAM_TO_RUN]", "[ARGUMENTS]", NULL}

This is the command that will be executed (Your argv). Since this array is terminated by NULL, GLib is able to determine its length and we do not need an argc.

GPid pid; gint stdout; GError * error = NULL;

We’ll need these as well. Now, it’s time to start our process:

gboolean result = g_spawn_async_with_pipes (NULL, child_argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &pid, NULL, &stdout, NULL, &error);

Yeah… That one’s a doosey. Let’s go over all those fields.

The first argument is the child’s working directory. If this is NULL, then the child inherits the parent’s working directory.

The second argument is the child’s argument vector. This is the command that will be executed.

The third argument is the child’s environment. Like the argv, this must be NULL-terminated. If NULL the child inherits the parent’s environment.

The fourth argument is the child’s spawn flags.

The fifth argument is a pointer to a GSpawnChildSetupFunc function, to be called just before exec. If null, then the process will fork and exec without additional setup.

The sixth argument is the gpointer to be passed to the GSpawnChildSetupFunc.

The seventh argument is a location to return the PID of the new process.

The eighth, ninth, and tenth arguments are return locations for the file descriptors of STDIN, STDOUT, and STDERR respectively.

The last argument is a return location for a GError if something goes wrong. This function returns FALSE if something goes wrong.

What Now

So you’ve got your fancy new process, what do you do with it?

Well, first let’s create some GIOChannels using our file descriptors:

GIOChannel * outch = g_io_channel_unix_new(stdout);

Next, we add callbacks:

GSource * stdout_source = g_io_create_watch( outch, G_IO_IN); g_source_set_callback(stdout_source, stdout_callback, outch, NULL); g_source_attach(stdout_source, main_context); GSource * stdout_abort = g_io_create_watch( outch, G_IO_ERR | G_IO_HUP | G_IO_NVAL); g_source_set_callback(stdout_abort, abort_callback, NULL, NULL); g_source_attach(stdout_abort, main_context);

Here, I’ve created two sources: one that will be called when there’s data to be read, and one to be called when something goes wrong. The first call to g_io_create_watch creates a GSource that watches for a certain condition. The second call to g_source_set_callback tells the watch what function to call when the condition is met. This function should have the following signature:

static gboolean callback(gpointer data)

The final call to g_source_attach attaches a source to a GMainContext. If NULL is passed to the second argument, then the default context is used.

…and that’s all there is to it! Your callbacks can operate on file descriptors using the g_io_channel_* family of functions, and when the abort callback is called, it can exit gracefully.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: