Gobject tutorial 十二

参考:

GLib.IOChanneld

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];
待续。。。。。。

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值