The GIOChannel data type provides a convenient mechanism for working with file descriptors, pipes, and sockets. It offers a unified approach to handling IO events across different types of file descriptors.
Let's examine the structure definition first:
struct _GIOChannel
{
/*< private >*/
gint ref_count;
GIOFuncs *funcs;
gchar *encoding;
GIConv read_cd;
GIConv write_cd;
gchar *line_term; /* String which indicates the end of a line of text */
guint line_term_len; /* So we can have null in the line term */
gsize buf_size;
GString *read_buf; /* Raw data from the channel */
GString *encoded_read_buf; /* Channel data converted to UTF-8 */
GString *write_buf; /* Data ready to be written to the file */
gchar partial_write_buf[6]; /* UTF-8 partial characters, null terminated */
/* Group the flags together, immediately after partial_write_buf, to save memory */
guint use_buffer : 1; /* The encoding uses the buffers */
guint do_encode : 1; /* The encoding uses the GIConv coverters */
guint close_on_unref : 1; /* Close the channel on final unref */
guint is_readable : 1; /* Cached GIOFlag */
guint is_writeable : 1; /* ditto */
guint is_seekable : 1; /* ditto */
gpointer reserved1;
gpointer reserved2;
};
Let's look at an example from glib/tests/io-channel-basic.c to understand its usage:
static void
launch_child_process (int child_count)
{
......
if (pipe (child_to_parent) == -1 || pipe (parent_to_child) == -1)
{
perror ("pipe");
exit (1);
}
......
my_input_channel = g_io_channel_unix_new (parent_to_child[0]);
id = g_new (guint, 1);
*id = g_io_add_watch_full (
my_input_channel,
G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
receive_message,
id, g_free);
......
}
Now let's explain the functions used in the example:
/**
* g_io_channel_unix_new:
* @fd: a file descriptor.
*
* Creates a new #GIOChannel given a file descriptor. On UNIX systems
* this works for plain files, pipes, and sockets.
*
* The returned #GIOChannel has a reference count of 1.
*
* The default encoding for #GIOChannel is UTF-8. If your application
* is reading output from a command using via pipe, you may need to set
* the encoding to the encoding of the current locale (see
* g_get_charset()) with the g_io_channel_set_encoding() function.
* By default, the fd passed will not be closed when the final reference
* to the #GIOChannel data structure is dropped.
*
* If you want to read raw binary data without interpretation, then
* call the g_io_channel_set_encoding() function with %NULL for the
* encoding argument.
*
* This function is available in GLib on Windows, too, but you should
* avoid using it on Windows. The domain of file descriptors and
* sockets overlap. There is no way for GLib to know which one you mean
* in case the argument you pass to this function happens to be both a
* valid file descriptor and socket. If that happens a warning is
* issued, and GLib assumes that it is the file descriptor you mean.
*
* Returns: a new #GIOChannel.
**/
GIOChannel *
g_io_channel_unix_new (gint fd)
/**
* g_io_add_watch_full: (rename-to g_io_add_watch)
* @channel: a #GIOChannel
* @priority: the priority of the #GIOChannel source
* @condition: the condition to watch for
* @func: the function to call when the condition is satisfied
* @user_data: user data to pass to @func
* @notify: the function to call when the source is removed
*
* Adds the #GIOChannel into the default main loop context
* with the given priority.
*
* This internally creates a main loop source using g_io_create_watch()
* and attaches it to the main loop context with g_source_attach().
* You can do these steps manually if you need greater control.
*
* Returns: the event source id
*/
guint
g_io_add_watch_full (GIOChannel *channel,
gint priority,
GIOCondition condition,
GIOFunc func,
gpointer user_data,
GDestroyNotify notify)
Previously, in our Gobject tutorial, we covered how GMainLoop works. Now, let's examine from the user's perspective how an event is added to GMainLoop and its complete lifecycle. From a code perspective, this flow also represents the analysis of how a callback function goes from setup to execution.
First, let's see what g_io_channel_unix_new does when creating a new GIOChannel structure:
GIOChannel *
g_io_channel_unix_new (gint fd)
{
struct stat buffer;
GIOUnixChannel *unix_channel = g_new (GIOUnixChannel, 1);
GIOChannel *channel = (GIOChannel *)unix_channel;
g_io_channel_init (channel);
channel->funcs = &unix_channel_funcs;
unix_channel->fd = fd;
/* I'm not sure if fstat on a non-file (e.g., socket) works
* it should be safe to say if it fails, the fd isn't seekable.
*/
/* Newer UNIX versions support S_ISSOCK(), fstat() will probably
* succeed in most cases.
*/
if (fstat (unix_channel->fd, &buffer) == 0)
channel->is_seekable = S_ISREG (buffer.st_mode) || S_ISCHR (buffer.st_mode)
|| S_ISBLK (buffer.st_mode);
else /* Assume not seekable */
channel->is_seekable = FALSE;
g_io_unix_get_flags (channel); /* Sets is_readable, is_writeable */
return channel;
}
static GIOFuncs unix_channel_funcs = {
g_io_unix_read,
g_io_unix_write,
g_io_unix_seek,
g_io_unix_close,
g_io_unix_create_watch,
g_io_unix_free,
g_io_unix_set_flags,
g_io_unix_get_flags,
};
Next, let's analyze the flow of g_io_add_watch_full:
guint
g_io_add_watch_full (GIOChannel *channel,
gint priority,
GIOCondition condition,
GIOFunc func,
gpointer user_data,
GDestroyNotify notify)
{
GSource *source;
guint id;
......
source = g_io_create_watch (channel, condition);
if (priority != G_PRIORITY_DEFAULT)
g_source_set_priority (source, priority);
g_source_set_callback (source, (GSourceFunc)func, user_data, notify);
id = g_source_attach (source, NULL);
g_source_unref (source);
return id;
}
In GLib, the structure representing events is GSource. Therefore, to monitor IO events, we first need to allocate a GSource structure for the IO event. Then we set the priority and callback function for the event source, and finally associate the event source with GMainContext. This code confirms the relationship between event sources and GMainContext that we mantioned earlier.
Let's look at the event source creation process:
/**
* g_io_create_watch:
* @channel: a #GIOChannel to watch
* @condition: conditions to watch for
*
* Creates a #GSource that's dispatched when @condition is met for the
* given @channel. For example, if condition is %G_IO_IN, the source will
* be dispatched when there's data available for reading.
*
* The callback function invoked by the #GSource should be added with
* g_source_set_callback(), but it has type #GIOFunc (not #GSourceFunc).
*
* g_io_add_watch() is a simpler interface to this same functionality, for
* the case where you want to add the source to the default main loop context
* at the default priority.
*
* On Windows, polling a #GSource created to watch a channel for a socket
* puts the socket in non-blocking mode. This is a side-effect of the
* implementation and unavoidable.
*
* Returns: a new #GSource
*/
GSource *
g_io_create_watch (GIOChannel *channel,
GIOCondition condition)
{
g_return_val_if_fail (channel != NULL, NULL);
return channel->funcs->io_create_watch (channel, condition);
}
Here, io_create_watch is g_io_unix_create_watch:
static GSource *
g_io_unix_create_watch (GIOChannel *channel,
GIOCondition condition)
{
GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;
GSource *source;
GIOUnixWatch *watch;
source = g_source_new (&g_io_watch_funcs, sizeof (GIOUnixWatch));
g_source_set_static_name (source, "GIOChannel (Unix)");
watch = (GIOUnixWatch *)source;
watch->channel = channel;
g_io_channel_ref (channel);
watch->condition = condition;
watch->pollfd.fd = unix_channel->fd;
watch->pollfd.events = condition;
g_source_add_poll (source, &watch->pollfd);
return source;
}
For g_io_unix_create_watch, let's examine the relationship between GIOUnixChannel, GIOUnixWatch, and GSource:
struct _GIOUnixChannel
{
GIOChannel channel;
gint fd;
};
struct _GIOUnixWatch
{
GSource source;
GPollFD pollfd;
GIOChannel *channel;
GIOCondition condition;
};
In the g_io_unix_create_watch function body, we see the setup of GIOUnixWatch members pollfd, channel, and condition, but not source. This is because the address of GIOUnixWatch is the same as its member source. The function g_source_new handles memory allocation and initialization of the GSource structure. Notably, g_source_new doesn't set the GMainContext for GSource at this point. The g_source_add_poll function adds GIOUnixWatch's GPollFD to the GSList in GSource.
Returning to g_io_add_watch_full, let's analyze the g_source_set_callback function:
static GSourceCallbackFuncs g_source_callback_funcs = {
g_source_callback_ref,
g_source_callback_unref,
g_source_callback_get,
};
/**
* g_source_set_callback:
* @source: the source
* @func: a callback function
* @data: the data to pass to callback function
* @notify: (nullable): a function to call when @data is no longer in use, or %NULL.
*
* Sets the callback function for a source. The callback for a source is
* called from the source's dispatch function.
*
* The exact type of @func depends on the type of source; ie. you
* should not count on @func being called with @data as its first
* parameter. Cast @func with G_SOURCE_FUNC() to avoid warnings about
* incompatible function types.
*
* See [memory management of sources][mainloop-memory-management] for details
* on how to handle memory management of @data.
*
* Typically, you won't use this function. Instead use functions specific
* to the type of source you are using, such as g_idle_add() or g_timeout_add().
*
* It is safe to call this function multiple times on a source which has already
* been attached to a context. The changes will take effect for the next time
* the source is dispatched after this call returns.
*
* Note that g_source_destroy() for a currently attached source has the effect
* of also unsetting the callback.
**/
void
g_source_set_callback (GSource *source,
GSourceFunc func,
gpointer data,
GDestroyNotify notify)
{
GSourceCallback *new_callback;
......
new_callback = g_new (GSourceCallback, 1);
new_callback->ref_count = 1;
new_callback->func = func;
new_callback->data = data;
new_callback->notify = notify;
g_source_set_callback_indirect (source, new_callback, &g_source_callback_funcs);
}
/**
* g_source_set_callback_indirect:
* @source: the source
* @callback_data: pointer to callback data "object"
* @callback_funcs: functions for reference counting @callback_data
* and getting the callback and data
*
* Sets the callback function storing the data as a refcounted callback
* "object". This is used internally. Note that calling
* g_source_set_callback_indirect() assumes
* an initial reference count on @callback_data, and thus
* @callback_funcs->unref will eventually be called once more
* than @callback_funcs->ref.
*
* It is safe to call this function multiple times on a source which has already
* been attached to a context. The changes will take effect for the next time
* the source is dispatched after this call returns.
**/
void
g_source_set_callback_indirect (GSource *source,
gpointer callback_data,
GSourceCallbackFuncs *callback_funcs)
{
GMainContext *context;
gpointer old_cb_data;
GSourceCallbackFuncs *old_cb_funcs;
......
context = source->context;
if (context)
LOCK_CONTEXT (context);
if (callback_funcs != &g_source_callback_funcs)
{
TRACE (GLIB_SOURCE_SET_CALLBACK_INDIRECT (source, callback_data,
callback_funcs->ref,
callback_funcs->unref,
callback_funcs->get));
}
old_cb_data = source->callback_data;
old_cb_funcs = source->callback_funcs;
source->callback_data = callback_data;
source->callback_funcs = callback_funcs;
......
}
Finally, let's analyze the g_source_attach function:
/**
* g_source_attach:
* @source: a #GSource
* @context: (nullable): a #GMainContext (if %NULL, the global-default
* main context will be used)
*
* Adds a #GSource to a @context so that it will be executed within
* that context. Remove it by calling g_source_destroy().
*
* This function is safe to call from any thread, regardless of which thread
* the @context is running in.
*
* Returns: the ID (greater than 0) for the source within the
* #GMainContext.
**/
guint
g_source_attach (GSource *source,
GMainContext *context)
{
guint result = 0;
......
if (!context)
context = g_main_context_default ();
LOCK_CONTEXT (context);
result = g_source_attach_unlocked (source, context, TRUE);
......
UNLOCK_CONTEXT (context);
return result;
}
When our GSource hasn't been assigned a GMainContext yet, we use the global default GMainContext. The main work of g_source_attach is done by g_source_attach_unlocked. Let's continue analyzing g_source_attach_unlocked:
static guint
g_source_attach_unlocked (GSource *source,
GMainContext *context,
gboolean do_wakeup)
{
GSList *tmp_list;
guint id;
/* The counter may have wrapped, so we must ensure that we do not
* reuse the source id of an existing source.
*/
do
id = context->next_id++;
while (id == 0 || g_hash_table_contains (context->sources, GUINT_TO_POINTER (id)));
source->context = context;
source->source_id = id;
g_source_ref (source);
g_hash_table_insert (context->sources, GUINT_TO_POINTER (id), source);
source_add_to_context (source, context);
if (!SOURCE_BLOCKED (source))
{
tmp_list = source->poll_fds;
while (tmp_list)
{
g_main_context_add_poll_unlocked (context, source->priority, tmp_list->data);
tmp_list = tmp_list->next;
}
for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
g_main_context_add_poll_unlocked (context, source->priority, tmp_list->data);
}
tmp_list = source->priv->child_sources;
while (tmp_list)
{
g_source_attach_unlocked (tmp_list->data, context, FALSE);
tmp_list = tmp_list->next;
}
/* If another thread has acquired the context, wake it up since it
* might be in poll() right now.
*/
if (do_wakeup &&
(context->flags & G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING ||
(context->owner && context->owner != G_THREAD_SELF)))
{
g_wakeup_signal (context->wakeup);
}
......
return source->source_id;
}
g_hash_table_insert places the GSource with its member source_id as the key into the hash table sources in GMainContext. source_add_to_context adds the GSource to the GList member of GMainContext. g_main_context_add_poll_unlocked adds the fd associated with the GSource to the poll_records in GMainContext.
The above process completes the creation of the GSource for IO events, setting the callback function, and binding the GSource to GMainContext.
The next question is how the callback function of the newly created GSource is called. To answer this, we need to analyze g_main_loop_new and g_main_loop_run:
/**
* g_main_loop_new:
* @context: (nullable): a #GMainContext (if %NULL, the global-default
* main context will be used).
* @is_running: set to %TRUE to indicate that the loop is running. This
* is not very important since calling g_main_loop_run() will set this to
* %TRUE anyway.
*
* Creates a new #GMainLoop structure.
*
* Returns: a new #GMainLoop.
**/
GMainLoop *
g_main_loop_new (GMainContext *context,
gboolean is_running)
{
GMainLoop *loop;
if (!context)
context = g_main_context_default();
g_main_context_ref (context);
loop = g_new0 (GMainLoop, 1);
loop->context = context;
loop->is_running = is_running != FALSE;
loop->ref_count = 1;
TRACE (GLIB_MAIN_LOOP_NEW (loop, context));
return loop;
}
From our example glib/tests/io-channel-basic.c, we can see that when calling g_main_loop_new, the GMainContext in our newly created GMainLoop is the global default GMainContext, which is the same GMainContext we used to bind the newly created GSource in the above analysis. At this point, our newly created GSource is associated with GMainLoop.
Next, let's analyze the g_main_loop_run function:
/**
* g_main_loop_run:
* @loop: a #GMainLoop
*
* Runs a main loop until g_main_loop_quit() is called on the loop.
* If this is called for the thread of the loop's #GMainContext,
* it will process events from the loop, otherwise it will
* simply wait.
**/
void
g_main_loop_run (GMainLoop *loop)
{
GThread *self = G_THREAD_SELF;
......
g_atomic_int_set (&loop->is_running, TRUE);
while (g_atomic_int_get (&loop->is_running))
g_main_context_iterate_unlocked (loop->context, TRUE, TRUE, self);
......
}
Previously, in our Gobject tutorial, we analyzed the flow of g_main_context_iterate_unlocked to explain the complete steps of a GMainLoop. Now, we need to break down its workflow:
static gboolean
g_main_context_iterate_unlocked (GMainContext *context,
gboolean block,
gboolean dispatch,
GThread *self)
{
......
if (!context->cached_poll_array)
{
context->cached_poll_array_size = context->n_poll_records;
context->cached_poll_array = g_new (GPollFD, context->n_poll_records);
}
allocated_nfds = context->cached_poll_array_size;
fds = context->cached_poll_array;
g_main_context_prepare_unlocked (context, &max_priority);
while ((nfds = g_main_context_query_unlocked (
context, max_priority, &timeout, fds,
allocated_nfds)) > allocated_nfds)
{
g_free (fds);
context->cached_poll_array_size = allocated_nfds = nfds;
context->cached_poll_array = fds = g_new (GPollFD, nfds);
}
if (!block)
timeout = 0;
g_main_context_poll_unlocked (context, timeout, max_priority, fds, nfds);
some_ready = g_main_context_check_unlocked (context, max_priority, fds, nfds);
if (dispatch)
g_main_context_dispatch_unlocked (context);
g_main_context_release_unlocked (context);
g_trace_mark (begin_time_nsec, G_TRACE_CURRENT_TIME - begin_time_nsec,
"GLib", "g_main_context_iterate",
"Context %p, %s ⇒ %s", context, block ? "blocking" : "non-blocking", some_ready ? "dispatched" : "nothing");
return some_ready;
}
According to the GMainContext state diagram from our Gobject tutorial, let's first look at the g_main_context_prepare_unlocked function:
static gboolean
g_main_context_prepare_unlocked (GMainContext *context,
gint *priority)
{
......
gint current_priority = G_MAXINT; //G_MAXINT = 2147483647
......
g_source_iter_init (&iter, context, TRUE);
while (g_source_iter_next (&iter, &source))
{
gint source_timeout = -1;
if (SOURCE_DESTROYED (source) || SOURCE_BLOCKED (source))
continue;
if ((n_ready > 0) && (source->priority > current_priority))
break;
if (!(source->flags & G_SOURCE_READY))
{
gboolean result;
gboolean (* prepare) (GSource *source,
gint *timeout);
prepare = source->source_funcs->prepare;
if (prepare)
{
......
result = (* prepare) (source, &source_timeout);
......
if (result)
{
GSource *ready_source = source;
while (ready_source)
{
ready_source->flags |= G_SOURCE_READY;
ready_source = ready_source->priv->parent_source;
}
}
}
if (source->flags & G_SOURCE_READY)
{
n_ready++;
current_priority = source->priority;
......
}
......
if (priority)
*priority = current_priority;
return (n_ready > 0);
}
g_main_context_prepare_unlocked uses a while loop to check if GSources associated with GMainContext are in the G_SOURCE_READY state. As analyzed above, the connection between GMainContext and GSource is established through the g_source_attach_unlocked function calling source_add_to_context.
So what is the prepare function? In the analysis above, the function g_io_unix_create_watch calls g_source_new with g_io_watch_funcs as a parameter to create a new GSource for the GIOChannel.
GSourceFuncs g_io_watch_funcs = {
g_io_unix_prepare,
g_io_unix_check,
g_io_unix_dispatch,
g_io_unix_finalize,
NULL, NULL
};
Therefore, our prepare function here is g_io_unix_prepare. Let's look at what g_io_unix_prepare does:
static gboolean
g_io_unix_prepare (GSource *source,
gint *timeout)
{
GIOUnixWatch *watch = (GIOUnixWatch *)source;
GIOCondition buffer_condition = g_io_channel_get_buffer_condition (watch->channel);
/* Only return TRUE here if _all_ bits in watch->condition will be set
*/
return ((watch->condition & buffer_condition) == watch->condition);
}
The g_io_unix_prepare function is used to determine if the conditions set for the GSource are already satisfied.
Returning to g_main_context_iterate_unlocked, let's analyze g_main_context_query_unlocked:
static gint
g_main_context_query_unlocked (GMainContext *context,
gint max_priority,
gint *timeout,
GPollFD *fds,
gint n_fds)
{
gint n_poll;
GPollRec *pollrec, *lastpollrec;
gushort events;
......
/* fds is filled sequentially from poll_records. Since poll_records
* are incrementally sorted by file descriptor identifier, fds will
* also be incrementally sorted.
*/
n_poll = 0;
lastpollrec = NULL;
for (pollrec = context->poll_records; pollrec; pollrec = pollrec->next)
{
if (pollrec->priority > max_priority)
continue;
/* In direct contradiction to the Unix98 spec, IRIX runs into
* difficulty if you pass in POLLERR, POLLHUP or POLLNVAL
* flags in the events field of the pollfd while it should
* just ignoring them. So we mask them out here.
*/
events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL);
/* This optimization --using the same GPollFD to poll for more
* than one poll record-- relies on the poll records being
* incrementally sorted.
*/
if (lastpollrec && pollrec->fd->fd == lastpollrec->fd->fd)
{
if (n_poll - 1 < n_fds)
fds[n_poll - 1].events |= events;
}
else
{
if (n_poll < n_fds)
{
fds[n_poll].fd = pollrec->fd->fd;
fds[n_poll].events = events;
fds[n_poll].revents = 0;
}
n_poll++;
}
lastpollrec = pollrec;
}
context->poll_changed = FALSE;
......
return n_poll;
}
This funcsion places the fd and events of members in poll_records with priority values less than max_priority into the return parameter fds, and returns the number of members in fds. The max_priority parameter we pass to g_main_context_query_unlocked is returned by g_main_context_prepare_unlocked, representing the highest priority value among GSources in GMainContext's GList that have the G_SOURCE_READY flag set. poll_records refers to file descriptors waiting for conditions to be met. For our io-channel-basic.c example, this is parent_to_child[0].
Next, let's analyze g_main_context_poll_unlocked in g_main_context_iterate_unlocked:
static void
g_main_context_poll_unlocked (GMainContext *context,
int timeout,
int priority,
GPollFD *fds,
int n_fds)
{
......
poll_func = context->poll_func;
......
ret = (*poll_func) (fds, n_fds, timeout);
......
}
As we can see, g_main_context_poll_unlocked's main task is to execute the poll_func function of GMainContext. From our Gobject tutorial, since our example io-channel-basic.c doesn't call g_main_context_set_poll_func for poll_func, the poll_func here is g_poll. This can also be verified through the creation process of the default global GMainContext.
GMainContext *
g_main_context_default (void)
{
......
context = g_main_context_new ();
......
}
GMainContext *
g_main_context_new (void)
{
return g_main_context_new_with_flags (G_MAIN_CONTEXT_FLAGS_NONE);
}
GMainContext *
g_main_context_new_with_flags (GMainContextFlags flags)
{
......
context->poll_func = g_poll;
......
}
Now let's look at g_poll:
gint
g_poll (GPollFD *fds,
guint nfds,
gint timeout)
{
return poll ((struct pollfd *)fds, nfds, timeout);
}
What's the relationship between GPollFD and pollfd?
struct _GPollFD
{
#if defined (G_OS_WIN32) && GLIB_SIZEOF_VOID_P == 8
#ifndef __GTK_DOC_IGNORE__
gint64 fd;
#endif
#else
gint fd;
#endif
gushort events;
gushort revents;
};
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
On Linux systems, for the poll function, we won't go into detail here.
Next, let's analyze g_main_context_check_unlocked in g_main_context_iterate_unlocked:
static gboolean
g_main_context_check_unlocked (GMainContext *context,
gint max_priority,
GPollFD *fds,
gint n_fds)
{
......
for (i = 0; i < n_fds; i++)
{
if (fds[i].fd == context->wake_up_rec.fd)
{
if (fds[i].revents)
{
TRACE (GLIB_MAIN_CONTEXT_WAKEUP_ACKNOWLEDGE (context));
g_wakeup_acknowledge (context->wakeup);
}
break;
}
}
......
/* The linear iteration below relies on the assumption that both
* poll records and the fds array are incrementally sorted by file
* descriptor identifier.
*/
pollrec = context->poll_records;
i = 0;
while (pollrec && i < n_fds)
{
......
g_source_iter_init (&iter, context, TRUE);
while (g_source_iter_next (&iter, &source))
{
......
if (!(source->flags & G_SOURCE_READY))
{
gboolean result;
gboolean (* check) (GSource *source);
check = source->source_funcs->check;
if (check)
{
......
result = (* check) (source);
......
}
else
result = FALSE;
if (result == FALSE)
{
GSList *tmp_list;
/* If not already explicitly flagged ready by ->check()
* (or if we have no check) then we can still be ready if
* any of our fds poll as ready.
*/
for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
{
GPollFD *pollfd = tmp_list->data;
if (pollfd->revents)
{
result = TRUE;
break;
}
}
}
if (result == FALSE && source->priv->ready_time != -1)
{
if (!context->time_is_fresh)
{
context->time = g_get_monotonic_time ();
context->time_is_fresh = TRUE;
}
if (source->priv->ready_time <= context->time)
result = TRUE;
}
if (result)
{
GSource *ready_source = source;
while (ready_source)
{
ready_source->flags |= G_SOURCE_READY;
ready_source = ready_source->priv->parent_source;
}
}
}
if (source->flags & G_SOURCE_READY)
{
g_source_ref (source);
g_ptr_array_add (context->pending_dispatches, source);
n_ready++;
......
}
}
g_source_iter_clear (&iter);
.......
return n_ready > 0;
}
Since we're using the global default GMainContext, for the first for loop, let'ss see how GMainContext's wake_up_rec member and wakeup are set:
GMainContext *
g_main_context_new_with_flags (GMainContextFlags flags)
{
......
context->wakeup = g_wakeup_new ();
g_wakeup_get_pollfd (context->wakeup, &context->wake_up_rec);
......
}
/**
* g_wakeup_new:
*
* Creates a new #GWakeup.
*
* You should use g_wakeup_free() to free it when you are done.
*
* Returns: a new #GWakeup
*
* Since: 2.30
**/
GWakeup *
g_wakeup_new (void)
{
......
GWakeup *wakeup;
wakeup = g_slice_new (GWakeup);
......
wakeup->fds[0] = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK);
......
if (wakeup->fds[0] != -1)
{
wakeup->fds[1] = -1;
return wakeup;
}
......
}
/**
* g_wakeup_get_pollfd:
* @wakeup: a #GWakeup
* @poll_fd: a #GPollFD
*
* Prepares a @poll_fd such that polling on it will succeed when
* g_wakeup_signal() has been called on @wakeup.
*
* @poll_fd is valid until @wakeup is freed.
*
* Since: 2.30
**/
void
g_wakeup_get_pollfd (GWakeup *wakeup,
GPollFD *poll_fd)
{
poll_fd->fd = wakeup->fds[0];
poll_fd->events = G_IO_IN;
}
In the g_main_context_check_unlocked function, the first for loop's purpose is to check when the file descriptor corresponding to the default global GMainContext's wake_up_rec member (i.e., the return value of eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK)) becomes readable, and g_wakeup_acknowledge needs to be called for confirmation. But strictly speaking, it's not necessarily the wake_up_rec's file descriptor that becomes readable. The reason is our check condition is fds[i].revents, and the revents member of struct pollfd may contain POLLERR, POLLHUP, or POLLNVAL, as detailed in man poll.
Next, it checks the status of all GSources associated with GMainContext using three methods:
- Call the check function of GSource's source_funcs member. For our io-channel-basic.c example, this is the g_io_unix_check function.
static gboolean
g_io_unix_check (GSource *source)
{
GIOUnixWatch *watch = (GIOUnixWatch *)source;
GIOCondition buffer_condition = g_io_channel_get_buffer_condition (watch->channel);
GIOCondition poll_condition = watch->pollfd.revents;
return ((poll_condition | buffer_condition) & watch->condition);
}
- Check the revents member value of the GPollFD corresponding to the fd we need to monitor.
- Time.
When it's confirmed that all monitored fds are in a ready state, the g_ptr_array_add function is called to add the relevant GSource to GMainContext's pending_dispatches.
Finally, according to the GMainContext state diagram from our Gobject tutorial, we arrive at dispatch. Specifically, we need to analyze the g_main_context_dispatch_unlocked function:
static void
g_main_context_dispatch_unlocked (GMainContext *context)
{
......
g_main_dispatch (context);
......
}
static void
g_main_dispatch (GMainContext *context)
{
GMainDispatch *current = get_dispatch ();
guint i;
for (i = 0; i < context->pending_dispatches->len; i++)
{
GSource *source = context->pending_dispatches->pdata[i];
......
source->flags &= ~G_SOURCE_READY;
if (!SOURCE_DESTROYED (source))
{
......
gboolean (*dispatch) (GSource *,
GSourceFunc,
gpointer);
GSource *prev_source;
gint64 begin_time_nsec G_GNUC_UNUSED;
dispatch = source->source_funcs->dispatch;
cb_funcs = source->callback_funcs;
cb_data = source->callback_data;
......
if (cb_funcs)
cb_funcs->get (cb_data, source, &callback, &user_data);
......
prev_source = current->source;
current->source = source;
.......
need_destroy = !(* dispatch) (source, callback, user_data);
......
current->source = prev_source;
}
......
}
......
}
The main function of g_main_dispatch is to run the user-set callback function for each GSource in GMainContext's pending_dispatches using Gsource->source_funcs->dispatch. For GIOChannel, the dispatch function is g_io_unix_dispatch.
static gboolean
g_io_unix_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
GIOFunc func = (GIOFunc)callback;
GIOUnixWatch *watch = (GIOUnixWatch *)source;
GIOCondition buffer_condition = g_io_channel_get_buffer_condition (watch->channel);
......
return (*func) (watch->channel,
(watch->pollfd.revents | buffer_condition) & watch->condition,
user_data);
}
For GIOChannel, the Gsource->callback_funcs->get function is g_source_callback_get.
static void
g_source_callback_get (gpointer cb_data,
GSource *source,
GSourceFunc *func,
gpointer *data)
{
GSourceCallback *callback = cb_data;
*func = callback->func;
*data = callback->data;
}
For our io-channel-basic.c example, the callback is receive_message and user_data is id.
At this point, the user-set callback function is executed.