udevd worker timeout

Error information

Waiting for UDev uevents to be processed    [BUSY]
udevd[155]: worker [173] timeout, kill it
udevd[155]: seq 1431 '/devices/pci0000:00/0000:00:1c.1/0000:12:00.0' killed
udevd[155]: worker [173] terminated by signal 9 (Killed)

PCI device

12:00.0 Network controller: Broadcom Corporation BCM4313 802.11b/g/n Wireless LAN Controller (rev 01)

How to fix?
Edit /etc/rc.conf

MODULES=(fuse brcmsmac)  # add brcmsmac

Over!

KVM 网络配置:VDE 方式

KVM 不知道从什么时候不能直接通过参数 -net nic 访问网络了,今天研究了一下可以通过 VDE 方式访问网络,配置相对之前有点复杂了。

======== Host OS 配置 ========
VDE 服务配置
配置文件 /etc/conf.d/vde 需要修改的行

VDE_CONFIG="vde-config"
 
VDE_CONNECTION="vde-connection"

复制配置文件模板 /etc/vde/vde-config.sample, /etc/vde/vde-connection.sample

sudo cp /etc/vde/vde-config.sample /etc/vde/vde-config
sudo cp /etc/vde/vde-connection.sample /etc/vde/vde-connection

配置文件 /etc/vde/vde-config 需要修改的行

VDE_SOCK_GROUP="kvm"
 
VDE_TAP="tap0"

启动 VDE 服务

sudo /etc/rc.d/vde start

配置网络接口 tap0 地址

sudo ifconfig tap0 192.168.100.1 netmask 255.255.255.0 up

配置 NAT

sudo iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o eth0 -j MASQUERADE
sudo iptables-save > /tmp/iptables
sudo cp /tmp/iptables /etc/iptables/iptables.rules
sudo touch /etc/iptables/ip6tables.rules

配置文件/etc/conf.d/iptables 中需要修改的行

IPTABLES_FORWARD=1

启动 NAT 服务

sudo /etc/rc.d/iptables start

启动 KVM

kvm -net nic -net vde,sock=/var/run/vde/sample.sock -hda ...

======== Guest OS 配置 ========
网络连接信息

Address: 192.168.100.2
Netmask: 255.255.255.0
Gateway: 192.168.100.1
DNS: 8.8.8.8

Over!

GstXOverlay 使用 Firefox plugin 中的窗口输出视频

最近编写的 Firefox plugin 需要使用 GstXOverlay 接口在插件中创建的窗口里输出视频图像,使用 PluginContainer 方式运行总是出现如下错

###!!! ABORT: X_GetWindowAttributes: BadWindow (invalid Window parameter);

而关闭了 IPC 直接在 Firefox 主进程地址空间中执行 Plugin 则是正常的,进一步调试发现是由于为 GstXOverlay 接口操作的对象设置的窗口时机不对导致的。

起初是在输出窗口 “realize” 的时候将其设置为输出窗口,这在独立进程的测试程序或关闭 IPC 的情况下都可以正常工作。代码如下

...
 
static void drawing_area_realize_handler(GtkWidget *widget,
            gpointer user_data)
{
    HevPluginPrivate *priv = (HevPluginPrivate*)user_data;
    XID xid = 0;
 
    g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
    xid = gdk_x11_drawable_get_xid(GDK_DRAWABLE(gtk_widget_get_window(widget)));
    gst_x_overlay_set_window_handle(GST_X_OVERLAY(priv->xvimagesink), xid);
    gst_element_set_state(priv->pipeline, GST_STATE_PLAYING);
}
 
NPError NPP_SetWindow(NPP instance, NPWindow *window)
{
    HevPluginPrivate *priv = (HevPluginPrivate*)instance->pdata;
    GtkWidget *plug = NULL;
    GtkWidget *drawing_area = NULL;
 
    g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
    g_return_val_if_fail(instance, NPERR_INVALID_INSTANCE_ERROR);
 
    /* Check re-enter */
    if(priv->window == window)
      return NPERR_NO_ERROR;
 
    priv->window = window;
 
    plug = gtk_plug_new((GdkNativeWindow)window->window);
    gtk_widget_show(plug);
 
    drawing_area = gtk_drawing_area_new();
    g_signal_connect(G_OBJECT(drawing_area), "realize",
                G_CALLBACK(drawing_area_realize_handler), priv);
    gtk_container_add(GTK_CONTAINER(plug),
                drawing_area);
    gtk_widget_show(drawing_area);
 
    return NPERR_NO_ERROR;
}
 
...

测试总结后发现主要原因是 realize 的窗口并不一定是 mapped,而 GstXOverlay 所需要的视频回放窗口必需是 mapped 的,在不使用 IPC 和独立进程模式的情况下,realized 和 mapped 时间间隔非常的短,所以不影响,而在使用多进程的通过 IPC 交互的模式下,map 过程会被滞后,从而导致问题。正确的设置回放窗口的时机是 map-event 信号触发的时候。 示例代码如下

...
 
static gboolean drawing_area_map_event_handler(GtkWidget *widget,
            GdkEvent *event, gpointer user_data)
{
    HevPluginPrivate *priv = (HevPluginPrivate*)user_data;
    XID xid = 0;
 
    g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
    xid = gdk_x11_drawable_get_xid(GDK_DRAWABLE(gtk_widget_get_window(widget)));
    gst_x_overlay_set_window_handle(GST_X_OVERLAY(priv->xvimagesink), xid);
    gst_element_set_state(priv->pipeline, GST_STATE_PLAYING);
 
    return FALSE;
}
 
NPError NPP_SetWindow(NPP instance, NPWindow *window)
{
    HevPluginPrivate *priv = (HevPluginPrivate*)instance->pdata;
    GtkWidget *plug = NULL;
    GtkWidget *drawing_area = NULL;
 
    g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
    g_return_val_if_fail(instance, NPERR_INVALID_INSTANCE_ERROR);
 
    /* Check re-enter */
    if(priv->window == window)
      return NPERR_NO_ERROR;
 
    priv->window = window;
 
    plug = gtk_plug_new((GdkNativeWindow)window->window);
    gtk_widget_show(plug);
 
    drawing_area = gtk_drawing_area_new();
    g_signal_connect(G_OBJECT(drawing_area), "map-event",
                G_CALLBACK(drawing_area_map_event_handler), priv);
    gtk_container_add(GTK_CONTAINER(plug),
                drawing_area);
    gtk_widget_show(drawing_area);
 
    return NPERR_NO_ERROR;
}
 
...

Over!

Linux 降低 Swap 使用率

当前,对于一台 RAM 超过 2GB 的且安装着 Linux 操作系统的桌面计算机来说,在正常使用 RAM 基本不超过 1GB 的情况下,更少的使用 Swap 或许是一个更好的选择。

通过修改内核的 vm.swappiness 属性可以控制 Swap 的使用率,该属性可取值的范围是 0 – 100。取值越小表示对 Swap 的使用率越低,反之越高。在 4GB 的 RAM 情况下,建议设置为 5 吧。

可以通过下面的方式临时修改 vm.swappiness

echo 100 > /proc/sys/vm/swappiness
# 或
sysctl vm.swappiness=100

也可以在 /etc/sysctl.conf 中增加一行来永久设置

vm.swappiness = 100

Over!

Python for MIPS 修正记录

最近发现 Python2 和 Python3 在 import 共享库形式的模块时经常出现 “invalid mode parameter” 错误,追踪后发现是由于 MIPS 平台中 RTLD_GLOBAL 等宏和 x86 平台中定义值是不同的,而 Python 源代码中是默认为 x86 平台生成的,所以在编译前需要重新生成。

重新生成方法
Python2

cd Lib/plat-linux2/
./regen

Python3

cd Lib/plat-linux2/
./regen
cd Lib/plat-linux3/
./regen

Over!

GObject 引用计数的错误使用记录

在使用 GTK+ 编写浏览器插件 UI 的过程中发现部分资源在适合的时期一直不会被释放,追踪后发现是由于引用计数使用不当造成的,在此记录并提醒自己。

正常情况下,一个 GObject 被创建之后引用计数为 1,如果其它对象对其引用则增加一个引用计数,反次减小一个,当引用计数为 0 时对象会被销毁,资源被释放。

下面说一下我遇到的问题,

GtkWidget *tree_view = NULL;
GtkListStore *list_store = NULL;
 
list_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_STRING,
                          G_TYPE_BOOLEAN);
tree_view = gtk_tree_view_new();
gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view),
                          GTK_TREE_MODEL(list_sotre));
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree_view),
                          -1, "Name", gtk_cell_renderer_text_new(),
                          "text", COL_NAME, NULL);
/* 至此,即是我之前使用的代码,造成了内存泄漏, list_store 没有被释放,
   因为 gtk_tree_view_set_model 方法会增加一个引用计数。 */
g_object_unref(G_OBJECT(list_store));
/* 需要在使用后反引用一下。 */

你会发现在上面的代码中 gtk_cell_renderer_text_new 方法创建的 GtkCellRenderer 对象在被 gtk_tree_view_insert_column_with_attributes 方法引用后为什么不需要进行一次反引用呢?我们来看这两种类型的继承关系,

  GObject
   +----GtkListStore
  GObject
   +----GInitiallyUnowned
         +----GtkObject
               +----GtkCellRenderer
                     +----GtkCellRendererText
                     +----GtkCellRendererPixbuf
                     +----GtkCellRendererProgress
                     +----GtkCellRendererSpinner
                     +----GtkCellRendererToggle

从继承关系上可以看到 GtkObject 是继承是 GInitiallyUnowned 的,所有从 GInitiallyUnowned 继承的类型在创建后都是 floating 引用的,这种类型的引用其引用计数不为 1,当它被其它对象引用时,这个对象会先使用 g_object_is_floating 方法检查是否存在 floating 引用,存在则调用 g_object_ref_sink 增加引用计数,并去除 floating 引用标记,这时这个对象的引用计数为 1,也就意味着在后面不再需要显式的反引用了。

Over!

GTK+ Widget的内部结构与工作流程

最近写了些GTK+的Widget,稍微了解了一下其内部结构,先记录下来,用以备忘。

GtkWidget的基本结构是这样的:

typedef struct {
  GtkStyle *GSEAL (style);
  GtkRequisition GSEAL (requisition);
  GtkAllocation GSEAL (allocation);
  GdkWindow *GSEAL (window);
  GtkWidget *GSEAL (parent);
} GtkWidget;

其中最重要的是它的window属性,每个GtkWidget都必须有一个window。Widget是围绕着window转的,只有有了window,Widget的存在才有意义。

要注意这里的window是一个GdkWindow,而不是GtkWindow。GdkWindow是对X的window的封装,大致上是屏幕上的一块矩形区域,可以在上面画画,可以接收事件。

一个Widget从创建、显示到销毁,大致要经过这么几个过程:
1、创建(new)
这是调用gtk_xxx_new时所触发的。它干的活很简单,用gobject的对象系统创建一个相应widget的实例。
当创建实例时,gobject会自动调用指定的初始化(init)函数(在get_type时指定),init函数负责把widget的各字段都初始化(把标题文字什么的设为NULL之类的)。

注意此时window并没有被创建,其实只是有了个widget的架子而已。

创建之后就可以对widget进行各种属性的设置了。

2、实例化(realize)
实例化的过程,就是将window创建出来的过程。这其中包括几个阶段:

  • 询问大小请求(size_request):
    GTK在实例化一个widget之前,会询问这个widget希望的大小是多大。widget可以根据自己的情况(例如属性什么的),计算出自己所需要的大小,也可以返回一个默认值,反正就是widget自己定啦:)。
  • 分配大小(size_allocate):
    GTK获得大小请求后就会给widget分配一个大小。要注意的是分配的大小不一定和请求的大小相同。一般来说,在分配大小时widget需要做几件事。
    将分配的大小记录在自己的allocation中。
    如果自己的window已经创建了,那么要改变自己所拥有的window的大小,使之符合所分配的大小。
    如果widget是一个容器(container),那么对其所有的子widget也要相应地计算它们的大小并重新给它们分配大小。
    分配大小可能发生在实例化之前,也可能在实例化后因为所属容器的大小、位置发生变化而被重新分配,因此widget的window可能已经被创建,也可能是NULL,需要进行判断。
  • 实例化
    这才是真正的实例化阶段。实例化所需要做的事只有一个:用gdk_window_new创建window。创建好window后需要用GTK_WIDGET_SET_FLAGS来给widget设置GTK_REALIZED标志。设置之后用GTK_REALIZED宏检查widget是否已经被实例化时会返回TRUE,表示该widget已经被实例化了。
    可以用gtk_widget_realize手动实例化一个widget

3、映射(map)
所谓映射,就是将已经创建好的window映射(显示)到屏幕上。需要做的事是用gdk_window_show将window给显示出来。和实例化时类似,需要用GTK_WIDGET_SET_FLAGS设置GTK_MAPPED标志,表示已经映射好了。

要注意的是map时需要判断widget是否已经实例化(用GTK_REALIZED),如果没有,应该首先实例化widget,这样才能显示window。
同样可以用gtk_widget_map手动映射一个widget。

用gtk_widget_show来显示一个widget的本质,就是将widget实例化,并将其映射。当然每一步都要判断是否已经做过,重复实例化和映射会造成资源泄漏(window被多次创建)和其他问题。

以上就是一个widget从创建到显示的过程。当然其中还有其父widget的流程。一个widget当且仅当其父widget被实例化后才能实例化,映射亦然(放心,这个流程是GTK+自动判断的)

接下来就是销毁一个widget时要做的事了。

4、反映射(unmap)
当隐藏一个widget时,其实就是取消这个widget的映射。具体做法是用gtk_window_hide来隐藏window,并用GTK_WIDGET_UNSET_FLAGS来取消(GTK_MAPPED)。

5、反实例化(unrealize)
销毁一个widget之前会自动要求将其反实例化。反实例化就是将window给销毁(记得把window指针设回NULL),并取消(GTK_REALIZED)标志。
有时可能会需要用gtk_widget_unrealize来手动反实例化一个widget。

6、销毁(destroy)
和new对应,把剩下的资源释放,最后用gobject的相应函数释放整个widget

下面是取自GtkEntry中的典型代码:

创建:

GtkWidget*
gtk_entry_new (void)
{
  /* 返回类型为GTK_TYPE_ENTRY的对象(Gobject的工作) */
  return g_object_new (GTK_TYPE_ENTRY, NULL);
}
 
/* 初始化函数,在g_object_new时自动调用 */
static void
gtk_entry_init (GtkEntry *entry)
{
  GtkEntryPrivate *priv = GTK_ENTRY_GET_PRIVATE (entry);
  /* 设置widget标识 */
  GTK_WIDGET_SET_FLAGS (entry, GTK_CAN_FOCUS);
  /* 初始化各字段 */
  entry->text_size = MIN_SIZE;
  entry->text = g_malloc (entry->text_size);
  entry->text[0] = '\0';
  /* …… */
  /* 设置拖放 */
  gtk_drag_dest_set (GTK_WIDGET (entry),
                     GTK_DEST_DEFAULT_HIGHLIGHT,
                     NULL, 0,
                     GDK_ACTION_COPY | GDK_ACTION_MOVE);
  gtk_drag_dest_add_text_targets (GTK_WIDGET (entry));
  /* 输入法context */
  entry->im_context = gtk_im_multicontext_new ();
  /* 信号 */
  g_signal_connect (entry->im_context, "commit",
            G_CALLBACK (gtk_entry_commit_cb), entry);
  /* …… */
}

大小分配:

static void
gtk_entry_size_allocate (GtkWidget     *widget,
             GtkAllocation *allocation)
{
  GtkEntry *entry = GTK_ENTRY (widget);
  /* 保存到allocation中 */
  widget->allocation = *allocation;
  /* 判断是否实例化 */
  if (GTK_WIDGET_REALIZED (widget))
    {
      /* 计算窗口大小…… */
      /* 改变窗口大小 */
      gdk_window_move_resize (widget->window, x, y, width, height);
      /* …… */
    }
}

大小请求:

static void
gtk_entry_size_request (GtkWidget      *widget,
            GtkRequisition *requisition)
{
  /* 计算所需大小…… */
  /* 设置所城大小 */
  if (entry->width_chars < 0)
    requisition->width = MIN_ENTRY_WIDTH + xborder * 2 + inner_border.left + inner_border.right;
  else
    {
      /* …… */     
      requisition->width = char_pixels * entry->width_chars + xborder * 2 + inner_border.left + inner_border.right;
    }
  requisition->height = PANGO_PIXELS (entry->ascent + entry->descent) + yborder * 2 + inner_border.top + inner_border.bottom;
  /* …… */
}

实例化:

static void
gtk_entry_realize (GtkWidget *widget)
{
  /* …… */
  /* 设置标志 */
  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  /* …… */
  /* 创建window */
  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
  gdk_window_set_user_data (widget->window, entry);
  /* …… */
}

映射:

static void
gtk_entry_map (GtkWidget *widget)
{
  /* …… */
  /* 判断是否可以且需要显示 */
  if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_MAPPED (widget))
    {
      /* 调用父类的map函数,也就是GtkWidget的,这样就不用自己设置GTK_MAPPED和显示widget->window了 */
      GTK_WIDGET_CLASS (gtk_entry_parent_class)->map (widget);
      /* …… */
      /* 显示需要显示的window */
                gdk_window_show (icon_info->window);
      /* …… */
}

反映射:

static void
gtk_entry_unmap (GtkWidget *widget)
{
  /* …… */
  /* 判断是否需要隐藏 */
  if (GTK_WIDGET_MAPPED (widget))
    {
      /* …… */
      /* 隐藏需要显示的window */
                gdk_window_hide (icon_info->window);
      /* …… */
      /* 调用父类的unmap函数,也就是GtkWidget的,这样就不用自己取消GTK_MAPPED和隐藏widget->window了 */
      GTK_WIDGET_CLASS (gtk_entry_parent_class)->unmap (widget);
    }
}

反实例化:

static void
gtk_entry_unrealize (GtkWidget *widget)
{
  /* …… */
  /* 调用父类的unrealize函数来销毁widget->window和取消GTK_REALIZED标识 */
  GTK_WIDGET_CLASS (gtk_entry_parent_class)->unrealize (widget);
  /* …… */
}

销毁:

static void
gtk_entry_destroy (GtkObject *object)
{
  /* 销毁为成员分配的空间…… */
  /* 用父类的object销毁函数自动调用gobject来销毁 */
  GTK_OBJECT_CLASS (gtk_entry_parent_class)->destroy (object);
}

From: http://tigersoldier.is-programmer.com/2009/5/24/gtk-custom-widget-workflow.8478.html

Over!

使用 Firefox 多实例模式调试插件

最近需要调试在 Firefox 里调试 Mozilla NPAPI 的插件程序,但是 Firefox 还需要登录着 GMail 和 OA 等应用。为了不冲突,可以创建一个新的 Firefox Profile 并启动新的实例进行调试。

创建新的 Profile

firefox -no-remote -ProfileManager


上面创建了一个新的 Profile: PluginTest

以新实例启动 Firefox

firefox -no-remote -P PluginTest

Over!

使用长轮询优化 LOPhone

LOPhone 是一个基于 Web UI 的 ModemManager 客户端例程,可在网页中控制 Modem 拨打和接听电话。在原来的实现中是使用短轮询的方式同步服务器端和客户端的状态,因为是使用定时器触发请求发送,所以状态同步实时性差,而且对服务器端的资源浪费严重。

短轮询
短轮询是指客户端使用 AJAX 定时向服务器端发起 HTTP 请求,服务器端不管是否存在状态更新都响应客户端的请求,一次请求完成后即可关闭连接。短轮询的优点是实现简单;缺点是状态同步实时性差、浪费服务器端资源和网络流量。

长轮询
长轮询是指客户端使用 AJAX 向服务器端发起异步 HTTP 请求,该请求可包含客户端当前的状态描述,服务器端收到请求后与暂存的最新状态进行比较,如果需要更新则立即返回响应,否则阻塞等待直接状态更新并返回最新状态。

LOPhone
ModemManager with CDMA Phone Interface: http://gitcafe.com/heiher/ModemManager
LOPhone Web UI: http://gitcafe.com/heiher/lophone
LOPhone HevSCGIServer Module: http://gitcafe.com/heiher/hev-scgi-server-lophone

Over!