Archive | GIOChannel RSS for this section

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.

Bringing The Portability With GModule

As I’ve been writing DMP Photo Booth, I’ve been taking great pains to improve portability. I’ve got a fancy module-based architecture designed to segregate the non-portable sections of the project from the Core. The only problem? A bunch of ugly POSIX calls: dlopen(), dlsym(), and dlclose(). Kind of defeats the purpose of using modules for portability if I don’t load said modules in a portable way, doesn’t it? I thought so too…

Enter GModule

GModule is part of the GLib family of libraries. GModule provides a portable way to handle working with shared libraries. It works on any POSIX compliant platform, as well as Windows, and HP-UX via its shl_load() mechanism. You can read more about GModule in the GLib Reference Manual. While I’m sure there is some edge case that GLib doesn’t cover, this is far more portable than I’d initially envisioned DMP Photo Booth being. (Yay, HP-UX support!)

Another consideration in all of this is the adding of dependencies. However, since I’m already using GTK3 for my GUI, I already have a GLib dependency, so there is no added burden to using GModule.

The How

GModule is actually quite similar to using POSIX dlfcn.h functions. Some semantics are different, but GModule has functions that are roughly equivalent to the POSIX functions.

GModule * g_module_open(const gchar *file_name, GModuleFlags flags);

G_module_open() is the replacement for dlopen() in POSIX. The GModule pointer that it returns is the replacement for the void pointer returned by dlopen(). GModuleFlags is an integer flag that can be boolean or’d in. Your options for this are G_MODULE_BIND_LAZY and G_MODULE_BIND_LOCAL which are equivalent to RTLD_LAZY and RTLD_LOCAL.

gboolean g_module_symbol(GModule *module, const gchar *symbol_name, gpointer *symbol);

This is your replacement for dlsym(), and functions mostly in the same way. module is the gmodule pointer to extract symbols from, symbol_name is the symbol to get, and symbol is the function pointer to populate. This function returns true if successful, and false if not. This function is commonly called like this:

if (!g_module_symbol(dmp_pb_camera_module, "dmp_cm_capture", (gpointer *) &dmp_cm_capture)) { /* error handling here */ }

This idiom can be found throughout the GLib documentation. Did you see the craziness that is argument number 3? Dmp_cm_capture is a function pointer, as you may remember, but GObject tends to make things a little tricky, and will throw thousands of warnings if you don’t cast your function pointer to a gpointer *. The definition of gpointer is:

typedef void* gpointer;

That means that a gpointer * is actually void **. Therefore, you are expected to pass in the address of your pointer using an ampersand, hence (gpointer *) &dmp_cm_capture.

gboolean g_module_close(GModule *module);

As a nice change of pace from the last function, this one is quite straight forward. You pass in your GModule pointer, and it closes it, just like dlclose(), then returns true (or false, if an error occurs). Nothing particularly noteworthy here.

gboolean g_module_supported();

While this function does not line up with a POSIX function, I felt it was important to mention this one. This function returns true if you are on a platform that GModule supports. If you’re on Linux/UNIX/OSX, or Windows, or HP-UX, this should return true. If you’re hacking your Atari-2600, it will most likely return false. Stick this in front of calls to GModule functions and save yourself a headache.

Moving Forward

My design philosophy for DMP Photo Booth is that the Core should compile on any (typical) platform with no changes. Using GLib, this seems to be within reach. With GModules, GTK3 User Interfaces, I’ve kept the faith so far. Looking through the GLib documentation, it has GThreads, so when I inevitably get mired in threaded programming, I should be golden. GLib also has support for Pipes and file IO via GIOChannel, but the documentation claims only partial support in Windows.

Here’s hoping all goes well!

%d bloggers like this: