参考:
GIOChannel
GIOChannel数据类型为使用普通文件描述符、管道、套接字提供了一种便捷方式。也可以说是,提供了对IO事件的统一处理方式。
首先,我们看看其定义。
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;
};
我们以glib源码中glib/tests/io-channel-basic.c为例,看看它是怎么使用的。
static void
spawn_process (int children_nb)
{
......
if (pipe (pipe_to_sub) == -1 || pipe (pipe_from_sub) == -1)
{
perror ("pipe");
exit (1);
}
......
my_read_channel = g_io_channel_unix_new (pipe_from_sub[0]);
id = g_new (guint, 1);
*id = g_io_add_watch_full (my_read_channel,
G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
recv_message,
id, g_free);
......
}
现在对上例中使用的函数进行说明。
/**
* 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)
之前,我们在Gobject tutorial 十-CSDN博客中,介绍过GMainLoop的工作原理,现在,我们以IO事件为例,从用户的角度看看,用户添加一个事件到GMainLoop后,这个事件在GMainLoop中的整个运行流程。其实,从代码的角度来看,这个流程也是分析事件的回调函数从设置到运行的整个过程。
首先,我们来看看g_io_channel_unix_new函数为新创建的GIOChannel结构体做了哪些设置。
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,
};
接下来,我们重点分析函数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;
}
在GLib中代表可事件的结构是GSouce,因此为了对IO事件进行监控,需要首先为IO事件分配GSource结构体。接着设置掉表事件的事件源的优先级及回调函数,最后将事件源关联到GMainContext。此处的代码验证了我们之前说到的事件源与GMainContext之间的关系。
首先我们看看事件源的生成过程。
/**
* 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);
}
这里的io_create_watch就是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;
}
对于g_io_unix_create_watch,我们先看看GIOUnixChannel、GIOUnixWatch、GSoucce这三者之间的关系。
struct _GIOUnixChannel
{
GIOChannel channel;
gint fd;
};
struct _GIOUnixWatch
{
GSource source;
GPollFD pollfd;
GIOChannel *channel;
GIOCondition condition;
};
在g_io_unix_create_watch的函数体中我们看到了对GIOUnixWatch成员pollfd、channel、conditon的设置,而没有对source的设置,这是因为,GIOUnixWatch的地址与其成员source的地址一致。函数g_source_new完成GSource结构体内存分配及成员初始化。值得注意的是此时,g_source_new并未对GSouce的GMainContext进行设置。g_source_add_poll函数将GIOUnixWatch的GPollFD加入到GSource的GSList中。
让我门回到g_io_add_watch_full,接下来,我们要分析函数g_source_set_callback。
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;
......
}
最后,我们分析一下函数g_source_attach。
/**
* 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;
}
当我们的GSource,到目前为止没有设置GMainContext时,会使用全局默认的GMainContext。g_source_attach函数的主要工作是g_source_attach_unlocked函数完成。我们接着分析函数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将GSource,以其成员source_id为Key,放入GMainContext中的哈希表sources。source_add_to_context将GSource放入GMainContext的GList成员中。g_main_context_add_poll_unlocked将与GSource相关的fd放入GMainContext的poll_records中。
上述流程完成了IO事件相关的GSource的创建、设置了回调函数、绑定了Gsource与GMainContext。
接下来的问题就是,新创建的GSource的回调函数如何被调用的。要回答这个问题,我们需要分析函数g_main_loop_new和函数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;
}
我们使用的实例glib/tests/io-channel-basic.c 对函数g_main_loop_new的调用可知,我们现在创建的GMainLoop中的GMainContext是全局默认的GMainContext,与上述分析中我们绑定新创建的GSource使用的是同一个GMainContext,至此,我们新创建的GSource与GMainLoop产生了关联。
接下来,我们分析一下函数g_main_loop_run。
/**
* 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);
......
}
之前,在Gobject tutorial 十-CSDN博客 中,我们分析过函数g_main_context_iterate_unlocked的流程用以说明一个GMainLoop的完整步骤,现在,我们需要细分其工作流程。
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;
}
根据 Gobject tutorial 十-CSDN博客所述GMainContext的状态图,我们先来看看函数g_main_context_prepare_unlocked。
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使用while循环查找与GMainContext相关的GSource是否是处于G_SOURCE_READY状态,如上分析所述,GMainContext与GSource挂钩是通过函数g_source_attach_unlocked调用source_add_to_context实现的。
那么,prepare函数又是谁呢?在上述分析中我们调用的函数g_io_unix_create_watch会以参数g_io_watch_funcs调用g_source_new,来为GIOChannel创建新对应的GSource。
GSourceFuncs g_io_watch_funcs = {
g_io_unix_prepare,
g_io_unix_check,
g_io_unix_dispatch,
g_io_unix_finalize,
NULL, NULL
};
因此,此处,我们的prepare函数就是g_io_unix_prepare,现在我们来看看g_io_unix_prepare的功能。
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);
}
g_io_unix_prepare函数用于判断GSource设定的条件是否已经满足。
回到函数g_main_context_iterate_unlocked,我们接着分析函数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;
}
此函数的作用是将GMainContext的poll_records中成员的priority的数值小于max_priority的成员的fd、events放入返回参数fds中,并返回fds中的成员个数。我们调用g_main_context_query_unlocked函数时传入的参数max_priority是由函数g_main_context_prepare_unlocked返回的,max_priority表示的是GMainContext的GList中被设置了G_SOURCE_READY标志位的GSource中,proirty数值最大的那个值。poll_records指代需要等待条件满足的文件描述符。对我们的例子io-channel-base.c来说就是pipe_from_sub[0]。
接下来我们继续分析函数g_main_context_iterate_unlocked中的g_main_context_poll_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);
......
}
可见,我们的函数g_main_context_poll_unlocked主要任务是执行GMainContext的poll_func函数。由Gobject tutorial 十-CSDN博客知,由于我们的例子io-channel-basic.c中并未调用g_main_context_set_poll_func对poll_func,因此,此处的poll_func是g_poll。这一点也可以通过默认全局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;
......
}
接下来,我们来看一下g_poll。
gint
g_poll (GPollFD *fds,
guint nfds,
gint timeout)
{
return poll ((struct pollfd *)fds, nfds, timeout);
}
GPollFD与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 */
};
在linux系统下,对于poll函数,在这里我们不再赘述。
接下来,我们继续分析函数g_main_context_iterate_unlocked中的g_main_context_check_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;
}
由于我们现在使用的是全局默认GMainContext,对于第一个for循环,我们先来看看GMainContext的wake_up_rec成员和wakeup是如何设置的。
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;
}
函数g_main_context_check_unlocked中,第一个for循环的作用就是默认全局的GMainContext的成员wake_up_rec所对应的文件描述符,即eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK)的返回值可读时,需要调用g_wakeup_acknowledge进行确认。但是严格意义上来说,不一定是wake_up_rec所对应的文件描述符变成可读状态。原因是我们的判断条件是fds[i].revents,而struct pollfd的成员revents的返回值可能包含POLLERR, POLLHUP或POLLNVAL,详见 man poll。
接下来,检查所有与GMainContext相关的GSource的状态,使用的方式有三种:
1.调用GSource成员source_funcs的check函数,对于我们的例子glib/tests/io-channel-basic.c来说,就是函数g_io_unix_check。
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);
}
2.查看与我们需要检测的fd 对应的GPollFD的revents成员值。
3.时间。
当确认我们检测的所有fd中,有处于ready状态的,则调用函数g_ptr_array_add将相关GSource加入到GMainContext的pending_dispatches中。
最后,根据Gobject tutorial 十-CSDN博客的状态图,我们来到dispatch。具体来说,我们需要分析函数g_main_context_dispatch_unlocked。
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];
待续。。。。。。