glib主事件循环

10 篇文章 5 订阅

研究qemu事件派发机制,没想到牵扯到了glib,没办法分析一下吧

qemu里面的glib主事件循环的api大概有如下几个

    ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
    g_source_set_can_recurse(&ctx->source, true);
    g_source_destroy(&ctx->source);
    g_source_set_name(src, "aio-context");
    g_source_attach(src, NULL);
    g_source_unref(src);

来看下glib主事件是如何处理的吧

GSource *
g_source_new (GSourceFuncs *source_funcs,
	      guint         struct_size)
{
  GSource *source;

  g_return_val_if_fail (source_funcs != NULL, NULL);
  g_return_val_if_fail (struct_size >= sizeof (GSource), NULL);
  
  source = (GSource*) g_malloc0 (struct_size);
  source->priv = g_slice_new0 (GSourcePrivate);
  source->source_funcs = source_funcs;
  source->ref_count = 1;
  
  source->priority = G_PRIORITY_DEFAULT;

  source->flags = G_HOOK_FLAG_ACTIVE;

  source->priv->ready_time = -1;

  /* NULL/0 initialization for all other fields */

  TRACE (GLIB_SOURCE_NEW (source, source_funcs->prepare, source_funcs->check,
                          source_funcs->dispatch, source_funcs->finalize,
                          struct_size));

  return source;
}

首先g_source_new创建事件源。 这个函数主要作用是创建一个GSource 结构返回给用户,另外分配的大小为struct_size,为用户指定大小,所以用户其实可以自定义一个数据结构,里面的第一个成员为GSource就可以存放一些用户自定义的数据, g_source_new的第二个参数struct_size正式这种用意。另外source_funcs则是在主事件循环过程中要回调的func,用于从用户端获取一些比较的信息,这体现了控制反转(ioc)的思想。
我们来看下GSource的数据结构

typedef struct _GSource                 GSource;
struct _GSource
{
  /*< private >*/
  gpointer callback_data;    
  GSourceCallbackFuncs *callback_funcs;

  const GSourceFuncs *source_funcs;  //事件源回调函数
  guint ref_count;   // 引用计数

  GMainContext *context;  //主事件上下文

  gint priority;   //优先级
  guint flags;   //状态
  guint source_id;   //事件源id

  GSList *poll_fds;
  
  GSource *prev;
  GSource *next;

  char    *name;  //名字

  GSourcePrivate *priv;   // 私有数据
};

typedef struct _GSourcePrivate          GSourcePrivate;
struct _GSourcePrivate
{
  GSList *child_sources;   //子事件源
  GSource *parent_source;  //父事件源

  gint64 ready_time;   //就绪时间

  /* This is currently only used on UNIX, but we always declare it (and
   * let it remain empty on Windows) to avoid #ifdef all over the place.
   */
  GSList *fds;
};

注意初始化状态为G_HOOK_FLAG_ACTIVE

g_source_set_can_recurse

void
g_source_set_can_recurse (GSource  *source,
			  gboolean  can_recurse)
{
  GMainContext *context;
  
  g_return_if_fail (source != NULL);

  context = source->context;

  if (context)
    LOCK_CONTEXT (context);
  
  if (can_recurse)
    source->flags |= G_SOURCE_CAN_RECURSE;
  else
    source->flags &= ~G_SOURCE_CAN_RECURSE;

  if (context)
    UNLOCK_CONTEXT (context);
}

g_source_set_can_recurse 函数设置是否允许递归,主要修改flags标识位。

void
g_source_set_name (GSource    *source,
                   const char *name)
{
  GMainContext *context;

  g_return_if_fail (source != NULL);

  context = source->context;

  if (context)
    LOCK_CONTEXT (context);

  TRACE (GLIB_SOURCE_SET_NAME (source, name));

  /* setting back to NULL is allowed, just because it's
   * weird if get_name can return NULL but you can't
   * set that.
   */

  g_free (source->name);
  source->name = g_strdup (name);

  if (context)
    UNLOCK_CONTEXT (context);
}

g_source_set_name 设置GSource的名字。

g_source_attach(src, NULL);

guint
g_source_attach (GSource      *source,
		 GMainContext *context)
{
  guint result = 0;

  g_return_val_if_fail (source->context == NULL, 0);
  g_return_val_if_fail (!SOURCE_DESTROYED (source), 0);
  
  if (!context)
    context = g_main_context_default ();

  LOCK_CONTEXT (context);

  result = g_source_attach_unlocked (source, context, TRUE);

  TRACE (GLIB_MAIN_SOURCE_ATTACH (g_source_get_name (source), source, context,
                                  result));

  UNLOCK_CONTEXT (context);

  return result;
}

g_source_attach函数主要把事件源绑定到事件循环上下文,也就是一个线程上下文。 函数首先判断用户传入的上下文是否为空,如果为空使用g_main_context_default获取缺省的事件上下文作为事件源要绑定的上下文,然后调用g_source_attach_unlocked函数进行绑定。 我们先来看g_main_context_default的实现

GMainContext *
g_main_context_default (void)
{
  /* Slow, but safe */
  
  G_LOCK (main_loop);

  if (!default_main_context)
    {
      default_main_context = g_main_context_new ();

      TRACE (GLIB_MAIN_CONTEXT_DEFAULT (default_main_context));

#ifdef G_MAIN_POLL_DEBUG
      if (_g_main_poll_debug)
	g_print ("default context=%p\n", default_main_context);
#endif
    }

  G_UNLOCK (main_loop);

  return default_main_context;
}

如果default_main_context存在,说明缺省上下文已经创建过了,直接返回就可以了,否则使用g_main_context_new函数来创建

GMainContext *
g_main_context_new (void)
{
  static gsize initialised;
  
   *context;

  if (g_once_init_enter (&initialised))
    {
#ifdef G_MAIN_POLL_DEBUG
      if (getenv ("G_MAIN_POLL_DEBUG") != NULL)
        _g_main_poll_debug = TRUE;
#endif

      g_once_init_leave (&initialised, TRUE);
    }

  context = g_new0 (GMainContext, 1);

  TRACE (GLIB_MAIN_CONTEXT_NEW (context));

  g_mutex_init (&context->mutex);
  g_cond_init (&context->cond);

  context->sources = g_hash_table_new (NULL, NULL);
  context->owner = NULL;
  context->waiters = NULL;

  context->ref_count = 1;

  context->next_id = 1;
  
  context->source_lists = NULL;
  
  context->poll_func = g_poll;
  
  context->cached_poll_array = NULL;
  context->cached_poll_array_size = 0;
  
  context->pending_dispatches = g_ptr_array_new ();
  
  context->time_is_fresh = FALSE;
  
  context->wakeup = g_wakeup_new ();
  g_wakeup_get_pollfd (context->wakeup, &context->wake_up_rec);
  g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec);

  G_LOCK (main_context_list);
  main_context_list = g_slist_append (main_context_list, context);

#ifdef G_MAIN_POLL_DEBUG
  if (_g_main_poll_debug)
    g_print ("created context=%p\n", context);
#endif

  G_UNLOCK (main_context_list);

  return context;
}

函数分配了GMainContext数据结构,并初始化了一些成员变量
GMainContext的数据结构如下


struct _GMainContext
{
  /* The following lock is used for both the list of sources
   * and the list of poll records
   */
  GMutex mutex;  //互斥锁
  GCond cond;  //条件变量
  GThread *owner;      //所属线程
  guint owner_count;   // 属主计数
  GSList *waiters;   // 等待者连标

  volatile gint ref_count;   //引用计数

  GHashTable *sources;              /* guint -> GSource */  //事件源table

  GPtrArray *pending_dispatches;  // 待派发事件
  gint timeout;			/* Timeout for current iteration */  // 超时时间

  guint next_id;      //下一个id
  GList *source_lists;   //事件源链表
  gint in_check_or_prepare;      

  GPollRec *poll_records;          //poll记录
  guint n_poll_records;     /记录个数
  GPollFD *cached_poll_array;   //取消poll数组
  guint cached_poll_array_size; //取消poll个数

  GWakeup *wakeup;   

  GPollFD wake_up_rec;

/* Flag indicating whether the set of fd's changed during a poll */
  gboolean poll_changed;

  GPollFunc poll_func;   //轮询函数

  gint64   time;
  gboolean time_is_fresh;  // 时间刷新
};

创建GMainContext的时候使用g_wakeup_new 函数创建了成员变量wakeup,g_wakeup_new定义如下

GWakeup *
g_wakeup_new (void)
{
  GError *error = NULL;
  GWakeup *wakeup;

  wakeup = g_slice_new (GWakeup);

  /* try eventfd first, if we think we can */
#if defined (HAVE_EVENTFD)
#ifndef TEST_EVENTFD_FALLBACK
  wakeup->fds[0] = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK);
#else
  wakeup->fds[0] = -1;
#endif

  if (wakeup->fds[0] != -1)
    {
      wakeup->fds[1] = -1;
      return wakeup;
    }

  /* for any failure, try a pipe instead */
#endif

  if (!g_unix_open_pipe (wakeup->fds, FD_CLOEXEC, &error))
    g_error ("Creating pipes for GWakeup: %s", error->message);

  if (!g_unix_set_fd_nonblocking (wakeup->fds[0], TRUE, &error) ||
      !g_unix_set_fd_nonblocking (wakeup->fds[1], TRUE, &error))
    g_error ("Set pipes non-blocking for GWakeup: %s", error->message);

  return wakeup;
}

函数打开了一个管道,读端和写端都设置成非阻塞的,文件描述符保存在GWakeupd的fds数组中。

void
g_wakeup_get_pollfd (GWakeup *wakeup,
                     GPollFD *poll_fd)
{
  poll_fd->fd = wakeup->fds[0];
  poll_fd->events = G_IO_IN;
}

g_wakeup_get_pollfd函数把wake_up_rec的fd设置为管道的读端,event设置为G_IO_IN,看来wake_up_rec是用来进行唤醒的,这基本上是linux系统用于唤醒的一种方式,一端poll在读端进入休眠,另一段去写写端,就可以进行唤醒。 是不是这样我们向下分析。
g_main_context_add_poll_unlocked函数如下

/* HOLDS: main_loop_lock */
static void 
g_main_context_add_poll_unlocked (GMainContext *context,
				  gint          priority,
				  GPollFD      *fd)
{
  GPollRec *prevrec, *nextrec;
  GPollRec *newrec = g_slice_new (GPollRec);

  /* This file descriptor may be checked before we ever poll */
  fd->revents = 0;
  newrec->fd = fd;
  newrec->priority = priority;

  prevrec = NULL;
  nextrec = context->poll_records;
  while (nextrec)
    {
      if (nextrec->fd->fd > fd->fd)
        break;
      prevrec = nextrec;
      nextrec = nextrec->next;
    }

  if (prevrec)
    prevrec->next = newrec;
  else
    context->poll_records = newrec;

  newrec->prev = prevrec;
  newrec->next = nextrec;

  if (nextrec)
    nextrec->prev = newrec;

  context->n_poll_records++;

  context->poll_changed = TRUE;

  /* Now wake up the main loop if it is waiting in the poll() */
  g_wakeup_signal (context->wakeup);
}

注意这里第三个参数fd为我们刚才设置好的持有管道读端的wake_up_rec成员变量。 这里主要是把wake_up_rec持有的管道读端作为一个poll_record放入poll_records链表。poll_records是按照文件描述符的大小进行排序的。

这是poll_records发生变化,调用g_wakeup_signal进行唤醒操作(唤醒谁?)

void
g_wakeup_signal (GWakeup *wakeup)
{
  int res;

  if (wakeup->fds[1] == -1)
    {
      guint64 one = 1;

      /* eventfd() case. It requires a 64-bit counter increment value to be
       * written. */
      do
        res = write (wakeup->fds[0], &one, sizeof one);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
    }
  else
    {
      guint8 one = 1;

      /* Non-eventfd() case. Only a single byte needs to be written, and it can
       * have an arbitrary value. */
      do
        res = write (wakeup->fds[1], &one, sizeof one);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
    }
}

果然像管道写端写入1,唤醒读端,啥玩意? 这里显然还没有人监听读端的变化,因为我们在初始化GMainContext还没有进入的主事件循环。 继续往下看main_context_list 将新建的这个context链入main_context_list链表

默认的主事件循环上下文创建完成之后就是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->owner && context->owner != G_THREAD_SELF)
    g_wakeup_signal (context->wakeup);

  return source->source_id;
}

函数按照如下几个步骤执行
1 分配一个id,首先id不能是0 ,其次这个id不能为现有的id, 现有事件源都放在context->sources这个hash table里面。 id为key, GSource为value。
2 调用g_hash_table_insert 把要添加的这个事件源放入上下文的事件源链表中。
3 source_add_to_context 添加事件源要事件上下文,这里和上面添加到hashtable不同的是要将这个GSource 添加到GMainContext的source_lists 链表中,这个链表是按照事件优先级组织的事件源链表。 每个优先级对应一个链表,如果事件源有父事件源则把该事件源放在父事件源的前面,否则放在相同优先级链表的尾部。
4 把 事件源的poll_fds和priv->fds全都添加到GMainContext的poll_records中,g_main_context_add_poll_unlocked函数我们前边已经分析过了。
5 把事件源的子事件源也都添加进这个GMainContext.
6 最后如果需要写管道唤醒的话,写管道进行唤醒。

到这里万事具备了,我们来看下主事件循环怎么绑定线程并run起来

引入另一个概念Looper

struct _GMainLoop
{
  GMainContext *context;
  gboolean is_running; /* (atomic) */
  volatile gint ref_count;
};
/**
 * g_main_loop_new:
 * @context: (nullable): a #GMainContext  (if %NULL, the default 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;
}

g_main_loop_new 用于常见Looper,这让我想起了android的looper,哈哈,其实是类似的。

这里looper也有了,context也有了,接下来就是run了

void 
g_main_loop_run (GMainLoop *loop)
{
  GThread *self = G_THREAD_SELF;

  g_return_if_fail (loop != NULL);
  g_return_if_fail (g_atomic_int_get (&loop->ref_count) > 0);

  if (!g_main_context_acquire (loop->context))
    {
      gboolean got_ownership = FALSE;
      
      /* Another thread owns this context */
      LOCK_CONTEXT (loop->context);

      g_atomic_int_inc (&loop->ref_count);
      g_atomic_int_set (&loop->is_running, TRUE);

      while (g_atomic_int_get (&loop->is_running) && !got_ownership)
        got_ownership = g_main_context_wait_internal (loop->context,
                                                      &loop->context->cond,
                                                      &loop->context->mutex);
      
      if (!g_atomic_int_get (&loop->is_running))
	{
	  UNLOCK_CONTEXT (loop->context);
	  if (got_ownership)
	    g_main_context_release (loop->context);
	  g_main_loop_unref (loop);
	  return;
	}

      g_assert (got_ownership);
    }
  else
    LOCK_CONTEXT (loop->context);

  if (loop->context->in_check_or_prepare)
    {
      g_warning ("g_main_loop_run(): called recursively from within a source's "
		 "check() or prepare() member, iteration not possible.");
      return;
    }

  g_atomic_int_inc (&loop->ref_count);
  g_atomic_int_set (&loop->is_running, TRUE);
  while (g_atomic_int_get (&loop->is_running))
    g_main_context_iterate (loop->context, TRUE, TRUE, self);

  UNLOCK_CONTEXT (loop->context);
  
  g_main_context_release (loop->context);
  
  g_main_loop_unref (loop);
}

1 g_main_context_acquire 绑定 context的loop
2 绑定loop的context
3 如果loop已经停止运行,则从当前looper释放Context,这里loop->is_running是控制主事件循环运行的开关
4 循环执行g_main_context_iterate 知道looper停止
5 停止运行后释放context,释放looper

关于绑定Context和looper有两种情况,一个是当前Context已经绑定到了其他looper, 当前looper线程就会在context的waiter.cond条件变量下休眠,这个逻辑在g_main_context_wait_internal函数中, 当looper退出释放context的时候则会执行g_main_context_release函数唤醒等待的Looper. 关于绑定这个逻辑就不做深入分析了,重点来看下g_main_context_iterate函数

/* HOLDS context lock */
static gboolean
g_main_context_iterate (GMainContext *context,
			gboolean      block,
			gboolean      dispatch,
			GThread      *self)
{
  gint max_priority;
  gint timeout;
  gboolean some_ready;
  gint nfds, allocated_nfds;
  GPollFD *fds = NULL;

  UNLOCK_CONTEXT (context);

  if (!g_main_context_acquire (context))
    {
      gboolean got_ownership;

      LOCK_CONTEXT (context);

      if (!block)
	return FALSE;

      got_ownership = g_main_context_wait_internal (context,
                                                    &context->cond,
                                                    &context->mutex);

      if (!got_ownership)
	return FALSE;
    }
  else
    LOCK_CONTEXT (context);
  
  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;
  
  UNLOCK_CONTEXT (context);

  g_main_context_prepare (context, &max_priority); 
  
  while ((nfds = g_main_context_query (context, max_priority, &timeout, fds, 
				       allocated_nfds)) > allocated_nfds)
    {
      LOCK_CONTEXT (context);
      g_free (fds);
      context->cached_poll_array_size = allocated_nfds = nfds;
      context->cached_poll_array = fds = g_new (GPollFD, nfds);
      UNLOCK_CONTEXT (context);
    }

  if (!block)
    timeout = 0;
  
  g_main_context_poll (context, timeout, max_priority, fds, nfds);
  
  some_ready = g_main_context_check (context, max_priority, fds, nfds);
  
  if (dispatch)
    g_main_context_dispatch (context);
  
  g_main_context_release (context);

  LOCK_CONTEXT (context);

  return some_ready;
}

1 调用g_main_context_acquire获取context,判断如果获取不到的情况下如果block就等,否则换回,我怎么感觉这个判断有点多此一举
2 g_main_context_prepare
3 g_main_context_query
4 g_main_context_poll
5 g_main_context_check
6 g_main_context_dispatch
7 g_main_context_release

gboolean
g_main_context_prepare (GMainContext *context,
			gint         *priority)
{
  guint i;
  gint n_ready = 0;
  gint current_priority = G_MAXINT;
  GSource *source;
  GSourceIter iter;

  if (context == NULL)
    context = g_main_context_default ();
  
  LOCK_CONTEXT (context);

  context->time_is_fresh = FALSE;

  if (context->in_check_or_prepare)
    {
      g_warning ("g_main_context_prepare() called recursively from within a source's check() or "
		 "prepare() member.");
      UNLOCK_CONTEXT (context);
      return FALSE;
    }

  TRACE (GLIB_MAIN_CONTEXT_BEFORE_PREPARE (context));

#if 0
  /* If recursing, finish up current dispatch, before starting over */
  if (context->pending_dispatches)
    {
      if (dispatch)
	g_main_dispatch (context, &current_time);
      
      UNLOCK_CONTEXT (context);
      return TRUE;
    }
#endif

  /* If recursing, clear list of pending dispatches */

  for (i = 0; i < context->pending_dispatches->len; i++)
    {
      if (context->pending_dispatches->pdata[i])
        g_source_unref_internal ((GSource *)context->pending_dispatches->pdata[i], context, TRUE);
    }
  g_ptr_array_set_size (context->pending_dispatches, 0);
  
  /* Prepare all sources */

  context->timeout = -1;
  
  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)
            {
              context->in_check_or_prepare++;
              UNLOCK_CONTEXT (context);

              result = (* prepare) (source, &source_timeout);
              TRACE (GLIB_MAIN_AFTER_PREPARE (source, prepare, source_timeout));

              LOCK_CONTEXT (context);
              context->in_check_or_prepare--;
            }
          else
            {
              source_timeout = -1;
              result = FALSE;
            }

          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)
                {
                  source_timeout = 0;
                  result = TRUE;
                }
              else
                {
                  gint64 timeout;

                  /* rounding down will lead to spinning, so always round up */
                  timeout = (source->priv->ready_time - context->time + 999) / 1000;

                  if (source_timeout < 0 || timeout < source_timeout)
                    source_timeout = MIN (timeout, G_MAXINT);
                }
            }

	  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;
	  context->timeout = 0;
	}
      
      if (source_timeout >= 0)
	{
	  if (context->timeout < 0)
	    context->timeout = source_timeout;
	  else
	    context->timeout = MIN (context->timeout, source_timeout);
	}
    }
  g_source_iter_clear (&iter);

  TRACE (GLIB_MAIN_CONTEXT_AFTER_PREPARE (context, current_priority, n_ready));

  UNLOCK_CONTEXT (context);
  
  if (priority)
    *priority = current_priority;
  
  return (n_ready > 0);
}

g_main_context_prepare函数还是比较麻烦的,主要做了一下三件事
1 看有没有准备就绪的事件源,如果有返回true,没有返回false
2 找到最高优先级的准备就绪的事件源,把该优先级设置给传出参数priority
3 设置GMainContext.timeout 值,表示最近一次要到超时的事件源是何时(为什么这么做? 因为事件源可能存在一些定时任务,当到达该任务时间的时候我们要做一些处理,否则我们可以进行休眠,类似于wait(timeout).

首先判断是否有事件就绪有两个标准
1 事件源的prepare函数返回真
2 有事件达到了超时时间
source->priv->ready_time

如何计算最近超时时间
1 source->priv->ready_time
2 事件源的prepare函数的传出参数source_timeout
取最小的timeout作为事件源的超时值

gint
g_main_context_query (GMainContext *context,
		      gint          max_priority,
		      gint         *timeout,
		      GPollFD      *fds,
		      gint          n_fds)
{
  gint n_poll;
  GPollRec *pollrec, *lastpollrec;
  gushort events;
  
  LOCK_CONTEXT (context);

  TRACE (GLIB_MAIN_CONTEXT_BEFORE_QUERY (context, max_priority));

  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);

      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;
  
  if (timeout)
    {
      *timeout = context->timeout;
      if (*timeout != 0)
        context->time_is_fresh = FALSE;
    }

  TRACE (GLIB_MAIN_CONTEXT_AFTER_QUERY (context, context->timeout,
                                        fds, n_poll));

  UNLOCK_CONTEXT (context);

  return n_poll;
}

g_main_context_query的函数把注册在GMainContext的文件描述符中事件优先级高于刚才确定的最小优先级的事件源全部导出到传出参数fds

static void
g_main_context_poll (GMainContext *context,
		     gint          timeout,
		     gint          priority,
		     GPollFD      *fds,
		     gint          n_fds)
{
#ifdef  G_MAIN_POLL_DEBUG
  GTimer *poll_timer;
  GPollRec *pollrec;
  gint i;
#endif

  GPollFunc poll_func;

  if (n_fds || timeout != 0)
    {
      int ret, errsv;

#ifdef	G_MAIN_POLL_DEBUG
      poll_timer = NULL;
      if (_g_main_poll_debug)
	{
	  g_print ("polling context=%p n=%d timeout=%d\n",
		   context, n_fds, timeout);
	  poll_timer = g_timer_new ();
	}
#endif

      LOCK_CONTEXT (context);

      poll_func = context->poll_func;

      UNLOCK_CONTEXT (context);
      ret = (*poll_func) (fds, n_fds, timeout);
      errsv = errno;
      if (ret < 0 && errsv != EINTR)
	{
#ifndef G_OS_WIN32
	  g_warning ("poll(2) failed due to: %s.",
		     g_strerror (errsv));
#else
	  /* If g_poll () returns -1, it has already called g_warning() */
#endif
	}
      
#ifdef	G_MAIN_POLL_DEBUG
      if (_g_main_poll_debug)
	{
	  LOCK_CONTEXT (context);

	  g_print ("g_main_poll(%d) timeout: %d - elapsed %12.10f seconds",
		   n_fds,
		   timeout,
		   g_timer_elapsed (poll_timer, NULL));
	  g_timer_destroy (poll_timer);
	  pollrec = context->poll_records;

	  while (pollrec != NULL)
	    {
	      i = 0;
	      while (i < n_fds)
		{
		  if (fds[i].fd == pollrec->fd->fd &&
		      pollrec->fd->events &&
		      fds[i].revents)
		    {
		      g_print (" [" G_POLLFD_FORMAT " :", fds[i].fd);
		      if (fds[i].revents & G_IO_IN)
			g_print ("i");
		      if (fds[i].revents & G_IO_OUT)
			g_print ("o");
		      if (fds[i].revents & G_IO_PRI)
			g_print ("p");
		      if (fds[i].revents & G_IO_ERR)
			g_print ("e");
		      if (fds[i].revents & G_IO_HUP)
			g_print ("h");
		      if (fds[i].revents & G_IO_NVAL)
			g_print ("n");
		      g_print ("]");
		    }
		  i++;
		}
	      pollrec = pollrec->next;
	    }
	  g_print ("\n");

	  UNLOCK_CONTEXT (context);
	}
#endif
    } /* if (n_fds || timeout != 0) */
}

调用GMainContext的poll_func方法,这里以g_poll为例

gint
g_poll (GPollFD *fds,
	guint    nfds,
	gint     timeout)
{
  struct timeval tv;
  fd_set rset, wset, xset;
  GPollFD *f;
  int ready;
  int maxfd = 0;

  FD_ZERO (&rset);
  FD_ZERO (&wset);
  FD_ZERO (&xset);

  for (f = fds; f < &fds[nfds]; ++f)
    if (f->fd >= 0)
      {
	if (f->events & G_IO_IN)
	  FD_SET (f->fd, &rset);
	if (f->events & G_IO_OUT)
	  FD_SET (f->fd, &wset);
	if (f->events & G_IO_PRI)
	  FD_SET (f->fd, &xset);
	if (f->fd > maxfd && (f->events & (G_IO_IN|G_IO_OUT|G_IO_PRI)))
	  maxfd = f->fd;
      }

  tv.tv_sec = timeout / 1000;
  tv.tv_usec = (timeout % 1000) * 1000;

  ready = select (maxfd + 1, &rset, &wset, &xset,
		  timeout == -1 ? NULL : &tv);
  if (ready > 0)
    for (f = fds; f < &fds[nfds]; ++f)
      {
	f->revents = 0;
	if (f->fd >= 0)
	  {
	    if (FD_ISSET (f->fd, &rset))
	      f->revents |= G_IO_IN;
	    if (FD_ISSET (f->fd, &wset))
	      f->revents |= G_IO_OUT;
	    if (FD_ISSET (f->fd, &xset))
	      f->revents |= G_IO_PRI;
	  }
      }

  return ready;
}

g_poll主要做的事情就是用select去观察的可读写IO_PRI等事件,要观察的事件主要通过f->events的反应,最后当事件到达则设置f->revents表示事件到达。最后返回到达事件的个数,最后注意select的timeout参数,正是我们计算到的最小timeout。

这和android的thread looper非常像。

不用说了g_main_context_check 函数就是检查这些事件到达的文件描述符以及超时事件。

gboolean
g_main_context_check (GMainContext *context,
		      gint          max_priority,
		      GPollFD      *fds,
		      gint          n_fds)
{
  GSource *source;
  GSourceIter iter;
  GPollRec *pollrec;
  gint n_ready = 0;
  gint i;
   
  LOCK_CONTEXT (context);

  if (context->in_check_or_prepare)
    {
      g_warning ("g_main_context_check() called recursively from within a source's check() or "
		 "prepare() member.");
      UNLOCK_CONTEXT (context);
      return FALSE;
    }

  TRACE (GLIB_MAIN_CONTEXT_BEFORE_CHECK (context, max_priority, fds, 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;
        }
    }

  /* If the set of poll file descriptors changed, bail out
   * and let the main loop rerun
   */
  if (context->poll_changed)
    {
      TRACE (GLIB_MAIN_CONTEXT_AFTER_CHECK (context, 0));

      UNLOCK_CONTEXT (context);
      return FALSE;
    }

  pollrec = context->poll_records;
  i = 0;
  while (pollrec && i < n_fds)
    {
      while (pollrec && pollrec->fd->fd == fds[i].fd)
        {
          if (pollrec->priority <= max_priority)
            {
              pollrec->fd->revents =
                fds[i].revents & (pollrec->fd->events | G_IO_ERR | G_IO_HUP | G_IO_NVAL);
            }
          pollrec = pollrec->next;
        }

      i++;
    }

  g_source_iter_init (&iter, context, TRUE);
  while (g_source_iter_next (&iter, &source))
    {
      if (SOURCE_DESTROYED (source) || SOURCE_BLOCKED (source))
	continue;
      if ((n_ready > 0) && (source->priority > max_priority))
	break;

      if (!(source->flags & G_SOURCE_READY))
	{
          gboolean result;
          gboolean (* check) (GSource *source);

          check = source->source_funcs->check;

          if (check)
            {
              /* If the check function is set, call it. */
              context->in_check_or_prepare++;
              UNLOCK_CONTEXT (context);

              result = (* check) (source);

              TRACE (GLIB_MAIN_AFTER_CHECK (source, check, result));

              LOCK_CONTEXT (context);
              context->in_check_or_prepare--;
            }
          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++;

          /* never dispatch sources with less priority than the first
           * one we choose to dispatch
           */
          max_priority = source->priority;
	}
    }
  g_source_iter_clear (&iter);

  TRACE (GLIB_MAIN_CONTEXT_AFTER_CHECK (context, n_ready));

  UNLOCK_CONTEXT (context);

  return n_ready > 0;
}

这个函数有点长,主要做下面几件事
1 如果一个事件源有感兴趣的事情发生,那么调用check函数看是否真的是感兴趣的事件,check成功表示该事件源已经准备就绪
2 检查事件源是否超时,超时代表该事件源已经就绪
3 把上述1,2 中准备就绪的事件源添加到GMainContext的pending_dispatches列表中准备派发

g_main_context_dispatch就是要进行派发了

void
g_main_context_dispatch (GMainContext *context)
{
  LOCK_CONTEXT (context);

  TRACE (GLIB_MAIN_CONTEXT_BEFORE_DISPATCH (context));

  if (context->pending_dispatches->len > 0)
    {
      g_main_dispatch (context);
    }

  TRACE (GLIB_MAIN_CONTEXT_AFTER_DISPATCH (context));

  UNLOCK_CONTEXT (context);
}
/* HOLDS: context's lock */
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];

      context->pending_dispatches->pdata[i] = NULL;
      g_assert (source);

      source->flags &= ~G_SOURCE_READY;

      if (!SOURCE_DESTROYED (source))
	{
	  gboolean was_in_call;
	  gpointer user_data = NULL;
	  GSourceFunc callback = NULL;
	  GSourceCallbackFuncs *cb_funcs;
	  gpointer cb_data;
	  gboolean need_destroy;

	  gboolean (*dispatch) (GSource *,
				GSourceFunc,
				gpointer);
          GSource *prev_source;

	  dispatch = source->source_funcs->dispatch;
	  cb_funcs = source->callback_funcs;
	  cb_data = source->callback_data;

	  if (cb_funcs)
	    cb_funcs->ref (cb_data);
	  
	  if ((source->flags & G_SOURCE_CAN_RECURSE) == 0)
	    block_source (source);
	  
	  was_in_call = source->flags & G_HOOK_FLAG_IN_CALL;
	  source->flags |= G_HOOK_FLAG_IN_CALL;

	  if (cb_funcs)
	    cb_funcs->get (cb_data, source, &callback, &user_data);

	  UNLOCK_CONTEXT (context);

          /* These operations are safe because 'current' is thread-local
           * and not modified from anywhere but this function.
           */
          prev_source = current->source;
          current->source = source;
          current->depth++;

          TRACE (GLIB_MAIN_BEFORE_DISPATCH (g_source_get_name (source), source,
                                            dispatch, callback, user_data));
          need_destroy = !(* dispatch) (source, callback, user_data);
          TRACE (GLIB_MAIN_AFTER_DISPATCH (g_source_get_name (source), source,
                                           dispatch, need_destroy));

          current->source = prev_source;
          current->depth--;

	  if (cb_funcs)
	    cb_funcs->unref (cb_data);

 	  LOCK_CONTEXT (context);
	  
	  if (!was_in_call)
	    source->flags &= ~G_HOOK_FLAG_IN_CALL;

	  if (SOURCE_BLOCKED (source) && !SOURCE_DESTROYED (source))
	    unblock_source (source);
	  
	  /* Note: this depends on the fact that we can't switch
	   * sources from one main context to another
	   */
	  if (need_destroy && !SOURCE_DESTROYED (source))
	    {
	      g_assert (source->context == context);
	      g_source_destroy_internal (source, context, TRUE);
	    }
	}
      
      g_source_unref_internal (source, context, TRUE);
    }

  g_ptr_array_set_size (context->pending_dispatches, 0);
}

其实这个函数最主要的就是调用GSource的dispatch函数,然后如果函数返回真为表示要销毁GSource 。

最后总结下, glib主事件循环所实现的功能
1 观察文件描述符事件

g_source_add_poll (GSource *source,
		   GPollFD *fd)

2 定时任务,主要设置超时时间

void
g_source_set_ready_time (GSource *source,
                         gint64   ready_time)

实现的机制就是使用select,唤醒的条件
1 事件源发生变化需要唤醒的文件描述符GMainContext的管道读端
向GMainContext添加事件源或者向GMainContext中的GSource添加新的关心事件后向管道写端写入数据,唤醒select
2 用户关心的事件到达
3 超时

这些机制完全和android thread looper一样,只不过glibc的更复杂功能更多

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值