- - The linux mobile development >http://www.limodev.cn/blog 致力于基于linux的嵌入式系统的学习和研究,包括内核、驱动、GUI、MMI、软件设计和优化等。欢迎交换友情链接。代码请到Projects里下载。 Sat, 25 Dec 2010 12:31:53 +0000 en hourly 1 http://wordpress.org/?v=abc - 《系统程序员成长计划》@DouBan: >http://www.limodev.cn/blog/archives/1524 http://www.limodev.cn/blog/archives/1524#comments Wed, 07 Apr 2010 12:34:39 +0000 李先静 - - http://www.limodev.cn/blog/?p=1524 - - - -

《系统程序员成长计划》@DouBan: http://book.douban.com/subject/4722708/ 欢迎评论。

]]> http://www.limodev.cn/blog/archives/1524/feed 6 - 系统程序员成长计划(china-pub首发) >http://www.limodev.cn/blog/archives/1497 http://www.limodev.cn/blog/archives/1497#comments Thu, 25 Mar 2010 05:47:25 +0000 李先静 - - http://www.limodev.cn/blog/?p=1497 - - - -

《系统程序员成长计划》(china-pub首发) http://www.china-pub.com/196523

]]> http://www.limodev.cn/blog/archives/1497/feed 6 - Compile gdbserver for Android(gdb-7.1) >http://www.limodev.cn/blog/archives/1590 http://www.limodev.cn/blog/archives/1590#comments Sat, 25 Dec 2010 12:31:53 +0000 李先静 - - http://www.limodev.cn/blog/?p=1590 - - - - Compile gdbserver for Android(gdb-7.1)

前几天发现Prebuild的gdbserver在我们的平台上无法运行,只好自己去编译。按照网上的方法编译不过去,虽然折腾半天之后编译成功了,但是运行时只能看到一个线程。后来研究了一下代码,发现里面确实是有问题的:

td_ta_thr_iter是用来获取进程中线程的方法,在Android的实现在bionic/libthread_db/libthread_db.c里,它的实现很简单,遍历/proc/%d/task/目录即可。

td_err_e
td_ta_thr_iter(td_thragent_t const * agent, td_thr_iter_f * func, void * cookie,
              td_thr_state_e state, int32_t prio, sigset_t * sigmask, uint32_t user_flags)
{
    td_err_e err = TD_OK;
    char path[32];
    DIR * dir;
    struct dirent * entry;
    td_thrhandle_t handle;
 
    snprintf(path, sizeof(path), "/proc/%d/task/", agent->pid);
    dir = opendir(path);
    if (!dir) {
       return TD_NOEVENT;
    }
    handle.pid = agent->pid;
    while ((entry = readdir(dir)) != NULL) {
       if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
           continue;
       }
       handle.tid = atoi(entry->d_name);
       err = func(&handle, cookie);
       if (err) {
           break;
       }
    }
 
    closedir(dir);
 
    return err;
}

这里的agent->pid是在td_ta_new里初始化的,它从proc_handle->pid而来。

td_err_e
td_ta_new(struct ps_prochandle const * proc_handle, td_thragent_t ** agent_out)
{
    td_thragent_t * agent;
 
    agent = (td_thragent_t *)malloc(sizeof(td_thragent_t));
    if (!agent) {
       return TD_MALLOC;
    }
 
    agent->pid = proc_handle->pid;
    *agent_out = agent;
 
    return TD_OK;
}

问题是在调用tdb.td_ta_new_p时,proc_handle->pid根本就没有初始化,所以找不到进程中的线程。为了解决这个问题,我只强制把agent->pid设置为被调试的进程ID。

 /* Attempt to open a connection to the thread library.  */
 err = tdb.td_ta_new_p (&tdb.proc_handle, &tdb.thread_agent);
 if (err != TD_OK)
    {
     if (debug_threads)
    fprintf (stderr, "td_ta_new(): %s/n", thread_db_err_str (err));
     return 0;
    }
 
 //Li XianJing td_ta_new use tdb.proc_handle.pid to initialize tdb.thread_agent->pid,
 //but tdb.proc_handle.pid is unknown, so I assign signal_pid to tdb.thread_agent->pid directly.
 tdb.thread_agent->pid = signal_pid;

另外就是链接libthread_db.so的问题,无论用dlopen的方式或者还是编译链接都不行。我只好静态链接libthread_db.a,最后还要加几个空函数才能编译过去。

一切OK了。

]]> http://www.limodev.cn/blog/archives/1590/feed 0 - Android中的FrameBuffer >http://www.limodev.cn/blog/archives/1586 http://www.limodev.cn/blog/archives/1586#comments Mon, 20 Dec 2010 14:05:26 +0000 李先静 - - http://www.limodev.cn/blog/?p=1586 - - post去显示。 dequeueBuffer获取一个空闲的Buffer,用来在后台绘制。 这两个函数由eglSwapBuffers调过来,调到 egl_window_surface_v2_t::swapBuffers: nativeWindow->queueBuffer(nativeWindow, buffer); nativeWindow->dequeueBuffer(nativeWindow, &buffer); 4.msm7k/liboverlay是Overlay的实现,与其它平台不同的是,高通平台上的Overlay并不是提供一个framebuffer设备,而通过fb0的ioctl来实现的,ioctl分为两类操作: OverlayControlChannel用于设置参数,比如设置Overlay的位置,宽度和高度: bool OverlayControlChannel::setPosition(int x, int y, uint32_t w, [...] ]]> - - FrameBuffer 在Android中并不像在其它GUI那样直观,抽象的层次比较多,加上GUI的更新是通过OpenGLES来做的。所以让人很难搞清GUI更新的整个流程,最近要准备一个讲稿,所以花了一些去研究,这里做点笔记供大家参考,源代码是基于高通平台的,这些代码在网上都可以下载。

FrameBuffer相关的组件
fb
1.SurfaceFlinger是一个服务,主要是负责合成各窗口的Surface,然后通过OpenGLES显示到FrameBuffer上。SurfaceFlinger本身比较重要而且比较复杂,以后专门写一篇吧。

2.DisplayHardware是对显示设备的抽象,包括FrameBuffer和Overlay。它加载FrameBuffer和Overlay插件,并初始化OpenGLES:

    mNativeWindow = new FramebufferNativeWindow();
    framebuffer_device_t const * fbDev = mNativeWindow->getDevice();
    if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {
       overlay_control_open(module, &mOverlayEngine);
    }
    surface = eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL);
    eglMakeCurrent(display, surface, surface, context);

3.FramebufferNativeWindow 是framebuffer 的抽象,它负责加载libgralloc,并打开framebuffer设备。FramebufferNativeWindow并不直接使用 framebuffer,而是自己创建了两个Buffer:

queueBuffer负责显示一个Buffer到屏幕上,它调用fb->post去显示。
dequeueBuffer获取一个空闲的Buffer,用来在后台绘制。

这两个函数由eglSwapBuffers调过来,调到

egl_window_surface_v2_t::swapBuffers:
    nativeWindow->queueBuffer(nativeWindow, buffer);
    nativeWindow->dequeueBuffer(nativeWindow, &buffer);

4.msm7k/liboverlay是Overlay的实现,与其它平台不同的是,高通平台上的Overlay并不是提供一个framebuffer设备,而通过fb0的ioctl来实现的,ioctl分为两类操作:

OverlayControlChannel用于设置参数,比如设置Overlay的位置,宽度和高度:

bool OverlayControlChannel::setPosition(int x, int y, uint32_t w, uint32_t h) {
    ov.dst_rect.x = x;
    ov.dst_rect.y = y;
    ov.dst_rect.w = w;
    ov.dst_rect.h = h;
 
    ioctl(mFD, MSMFB_OVERLAY_SET, &ov);
}

OverlayDataChannel用于显示Overlay,其中最重要的函数就是queueBuffer:
bool OverlayDataChannel::queueBuffer(uint32_t offset) {

mOvData.data.offset = offset;   
 
ioctl(mFD, MSMFB_OVERLAY_PLAY, odPtr))
}

5.msm7k/libgralloc 是显示缓存的抽象,包括framebuffer和普通Surface的Buffer。framebuffer只是/dev/graphic/fb0的包装,Surface的Buffer则是对/dev/pmem、ashmem和GPU内存(msm_hw3dm)的包装,它的目标主要是方便硬件加速,因为 DMA传输使用物理地址,要求内存在物理地址上连续。

6.msm7k/libcopybit这是2D加速库,主要负责Surface的拉伸、旋转和合成等操作。它有两种实现方式:
copybit.cpp: 基于fb0的ioctl(MSMFB_BLIT)的实现。
copybit_c2d.cpp: 基于kgsl的实现,只是对libC2D2.so的包装,libC2D2.so应该是不开源的。

7.pmem
misc/pmem.c: 对物理内存的管理,算法和用户空间的接口。
board-msm7x27.c定义了物理内存的缺省大小:

#define MSM_PMEM_MDP_SIZE   0x1B76000
#define MSM_PMEM_ADSP_SIZE  0xB71000
#define MSM_PMEM_AUDIO_SIZE 0x5B000
#define MSM_FB_SIZE     0x177000
#define MSM_GPU_PHYS_SIZE   SZ_2M
#define PMEM_KERNEL_EBI1_SIZE   0x1C000

msm_msm7x2x_allocate_memory_regions分配几大块内存用于给pmem做二次分配。

8.KGSL
Kernel Graphics System Layer (KGSL),3D图形加速驱动程序,源代码drivers/gpu/msm目录下,它是对GPU的包装,给OpenGLES 2.0提供抽象的接口。

9.msm_hw3dm
这个我在内核中没有找到相关代码。

10.msm_fb
msm_fb.c: framebuffer, overlay和blit的用户接口。
mdp_dma.c: 对具体显示设备的包装,提供两种framebuffer更新的方式:

  • mdp_refresh_screen: 定时更新。
  • mdp_dma_pan_update: 通过pan display主动更新。

mdp_dma_lcdc.c:针对LCD实现的显示设备,mdp_lcdc_update用更新framebuffer。

]]> http://www.limodev.cn/blog/archives/1586/feed 0 - Android GUI更新过程 >http://www.limodev.cn/blog/archives/1582 http://www.limodev.cn/blog/archives/1582#comments Sun, 19 Dec 2010 12:44:29 +0000 李先静 - - http://www.limodev.cn/blog/?p=1582 - - - - Android GUI更新过程

相关组件
android_gui
1.ViewRoot
在private void draw(boolean fullRedrawNeeded)中,会调用lockCanvas,从而获取一个Canvas对象,然后调用递归调用子窗口(View)的draw函数去绘制自己,最后调用unlockCanvasAndPost让Surface把自己更新到屏幕上。

canvas = surface.lockCanvas(dirty);
mView.draw(canvas);
surface.unlockCanvasAndPost(canvas);

2.android/view/Surface
它主要对一些native函数的包装。

    private native Canvas lockCanvasNative(Rect dirty);
    public native   void unlockCanvasAndPost(Canvas canvas);

3.android_view_Surface.cpp
这是native函数的实现。
Surface_lockCanvas它先锁住Surface,从而获取Surface相关的信息,如果格式、宽度、高度和像素缓冲区。然后创建一个bitmap,这个bitmap和共享一个像素缓冲区,这样View就能直接把自己绘制到Surface上了。

status_t err = surface->lock(&info, &dirtyRegion);
bitmap.setPixels(info.bits);
nativeCanvas->setBitmapDevice(bitmap);

Surface_unlockCanvasAndPost它断开SkCanvas与Surface之间的关系,然后调用Surface的unlockAndPost。

nativeCanvas->setBitmapDevice(SkBitmap());
status_t err = surface->unlockAndPost();

4.surfaceflinger_client/Surface.cpp
Surface::lock调用dequeueBuffer获取一个可用的Buffer,dequeueBuffer会调用SharedBufferClient::dequeue等到backbuffer可用,然后锁住这个Buffer并填充相关信息。

status_t err = dequeueBuffer(&backBuffer);
err = lockBuffer(backBuffer.get());

Surface::unlockAndPost先unlock Buffer,然后调queueBuffer显示Buffer。

status_t err = mLockedBuffer->unlock();
err = queueBuffer(mLockedBuffer.get());

Surface::queueBuffer是比较有意思的,它设置更新的区域,然后通知服务端(SurfaceFlinger)。

mSharedBufferClient->setDirtyRegion(bufIdx, mDirtyRegion);
err = mSharedBufferClient->queue(bufIdx);
client->signalServer();

通过binder发送请求给服务:

    virtual void signal() const
    {
       Parcel data, reply;
       data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
       remote()->transact(BnSurfaceComposer::SIGNAL, data, &reply, IBinder::FLAG_ONEWAY);
    }

5.SurfaceFlinger

BnSurfaceComposer::onTransact
       case SIGNAL: {
           CHECK_INTERFACE(ISurfaceComposer, data, reply);
           signal();
       } break;

这是SurfaceFlinger中处理SIGNAL的代码,它调用SurfaceFlinger::signal,再调用 SurfaceFlinger::signalEvent,最后调用MessageQueue::invalidate唤醒是 SurfaceFlinger的主循环。

在主循环中waitForEvent返回,再handleRepaint去合成个Layer/Surface。

bool SurfaceFlinger::threadLoop()
{
    waitForEvent();
       handleRepaint();
       postFramebuffer();
}

SurfaceFlinger::handleRepaint会调用composeSurfaces把需要绘制各个Surface合并起来,绘制到FrameBuffer的BackBuffer上。

    for (size_t i=0 ; i<count ; ++i) {
       const sp<LayerBase>& layer = layers[i];
       const Region& visibleRegion(layer->visibleRegionScreen);
       if (!visibleRegion.isEmpty())  {
           /* broncho cy add */
#ifdef USEOVERLAY
           mToppest[1] = mToppest[0];
           mToppest[0] = layer->getIdentity();
#endif
           /* broncho end */
           const Region clip(dirty.intersect(visibleRegion));
           if (!clip.isEmpty()) {
               layer->draw(clip);
           }
       }
    }

SurfaceFlinger::postFramebuffer最后把FrameBuffer的BackBuffer显示到屏幕上:

hw.flip(mInvalidRegion); -->eglSwapBuffers(dpy, surface);

FrameBuffer具体的显示过程,下次再写吧。

]]> http://www.limodev.cn/blog/archives/1582/feed 0 - 在Fedora 12下编译和调试RT-Thread >http://www.limodev.cn/blog/archives/1576 http://www.limodev.cn/blog/archives/1576#comments Sat, 18 Dec 2010 09:24:14 +0000 李先静 - - http://www.limodev.cn/blog/?p=1576 - - - - 在Fedora 12下编译和调试RT-Thraed

1.安装编译工具

yum install scons
yum install python

安装工具arm-2010q1-202-arm-none-linux-gnueabi.bin,我是安装在/home/lixianjing/CodeSourcery目录下的,后面会引用这个路径。

2.下载RT-Thread

[lixianjing@vm os]$ wget http://rt-thread.googlecode.com/files/rt-thread-0.4.0%20beta1.zip
[lixianjing@vm os]$ unzip rt-thread-0.4.0/ beta1.zip
[lixianjing@vm os]$ cd rt-thread-0.4.0/ beta1/bsp/mini2440/

3.修改rtconfig.h

//#define RT_USING_NEWLIB
//#define RT_USING_PTHREADS

使用NEWLIB和PTHREADS,在我这里有些问题,编译时会说头文件有冲突。加上-nostdinc选项之后又说有些头文件找不到了。估计newlib还不太成熟,所以我暂时没有用NEWLIB。

4.修改rtconfig.py

    #EXEC_PATH  = 'E:/Program Files/CodeSourcery/Sourcery G++ Lite/bin'
    EXEC_PATH = '/home/lixianjing/CodeSourcery/Sourcery_G++_Lite/bin'
 
    #PREFIX = 'arm-none-eabi-'
    PREFIX = 'arm-none-linux-gnueabi-'
 
    LFLAGS += ' -nostdlib'
    CFLAGS += ' -nostdinc -nostdlib'

如果不加nostd,就会出现头文件冲突的情况,通常编译内核都是要加这个选项的,但是不清楚为什么在Windows下没有问题。

5.修改SConstruct

# build program
env.Program(TARGET, objs, LIBS=['libgcc.a'], LIBPATH='/home/lixianjing/CodeSourcery/Sourcery_G++_Lite/lib/gcc/arm-none-linux-gnueabi/4.4.1/armv4t/')

因为arm没有除法指令,所以除法是用函数实现的,这些函数在libgcc.a里。因为前面加了nostdlib选项,所以这里要链接一下libgcc.a。

6.加上一个raise函数
raise.c:

void raise(void)
{
    return;
}

这个函数可能是libgcc.a里某处引了它,应该相当于abort之类的功能吧,这里实现一个空函数即可。

7.修改SConscript

src_bsp = ['application.c', 'startup.c', 'board.c', 'raise.c']

这里只是把raise.c加入编译。

8.编译RT-Thread

[lixianjing@vm mini2440]$ scons

如果出现下列信息,那就是编译成功了:

arm-none-linux-gnueabi-objcopy -O binary rtthread-mini2440.axf rtthread.bin
arm-none-linux-gnueabi-size rtthread-mini2440.axf
  text       data        bss        dec        hex    filename
363064       1516      14740     379320      5c9b8    rtthread-mini2440.axf
scons: done building targets.

注意:以上工作都是在bsp/mini2440/目录下完成的。

9.编译qemu
直接用yum安装qemu是不行的,因为里面没有mini2440的配置,要下载qemu for mini2440的源代码才行:

[lixianjing@vm os]$ git clone git://repo.or.cz/qemu/mini2440.git
[lixianjing@vm os]$ cd mini2440

10.修改hw/mini2440.c

static void mini2440_reset(void *opaque)
{
    struct mini2440_board_s *s = (struct mini2440_board_s *) opaque;
    uint32_t image_size;
 
    if (s->kernel) {
       image_size = load_image(s->kernel, qemu_get_ram_ptr(0));
       if (image_size > 0) {
           if (image_size & (512 -1))  
               image_size = (image_size + 512) & ~(512-1);            s->cpu->env->regs[15] = S3C_RAM_BASE ;
           mini2440_printf("loaded kernel %s at %p/n", s->kernel, s->cpu->env->regs[15]);
       }
 
       return;
    }
...
}

开始我直接编译了qemu,运行时总出现无效地址问题。后来发现是qemu是按u-boot加载的RT-Thread,RT-Thread加载的地址与u-boot是不同的,所以根本没有执行到RT-Thread的代码。

11.编译qemu

[lixianjing@vm mini2440]$ ./configure --target-list=arm-softmmu --disable-linux-user;make
[lixianjing@vm mini2440]$ su
[root@vm mini2440]# make install

12.调试运行RT-Thread
开两个终端,都进入bsp/mini2440目录。
终端1:

[lixianjing@vm mini2440]$ qemu-system-arm -S -s -M mini2440 -kernel rtthread.bin -serial stdio
S3C: CLK=240 HCLK=240 PCLK=240 UCLK=57
QEMU: ee24c08_init
DM9000: INIT QEMU MAC : 52:54:00:12:34:56
QEMU mini2440_reset: loaded kernel rtthread.bin at 0x30000000

加 -S -s 选项表示qemu等待调试器连接。

终端2:

[lixianjing@vm mini2440]$ arm-none-linux-gnueabi-gdb rtthread-mini2440.axf
(gdb) target remote :1234
Remote debugging using :1234
_start () at /home/lixianjing/lab/os/rt-thread-0.4.0 beta1/libcpu/arm/s3c24x0/start_gcc.S:91
91        b        reset
(gdb) b rt_init_thread_entry
Breakpoint 1 at 0x30000350: file application.c, line 64.
(gdb) c
Continuing.
 
Breakpoint 1, rt_init_thread_entry (parameter=0x0) at application.c:64
64            dfs_init();
(gdb)

1234是qemu等待调试器的缺省端口号。

然后就可以用gdb研究RT-Thread了。

这里说明一下,我的工作目录是/home/lixianjing/lab/os,针对mini2440定制的qemu在mini2440下,rt-thread源代码在rt-thread-0.4.0 beta1下。

]]> http://www.limodev.cn/blog/archives/1576/feed 0 - 一个显示Android ppp浏量的小程序 >http://www.limodev.cn/blog/archives/1573 http://www.limodev.cn/blog/archives/1573#comments Fri, 17 Dec 2010 06:38:32 +0000 李先静 - - http://www.limodev.cn/blog/?p=1573 - - - - 一个显示Android ppp浏量的小程序:
static void show_ppp_status(const char* ifname)
{
    struct ifpppstatsreq req;
    memset (&req, 0, sizeof (req));
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
 
    req.stats_ptr = (caddr_t) &req.stats;
    strlcpy(req.ifr__name, ifname, sizeof(req.ifr__name));
 
    if (ioctl(sock_fd, SIOCGPPPSTATS, &req) < 0)
    {
        perror("ioctl");
    }
    else
    {
        printf("%s/n", ifname);
        printf("----------------------------------------/n");
        printf("bytes_in:   %8d Bytes/n", req.stats.p.ppp_ibytes);
        printf("bytes_out:  %8d Bytes/n", req.stats.p.ppp_obytes);
        printf("packets_in: %8d /n", req.stats.p.ppp_ipackets);
        printf("packets_out:%8d /n", req.stats.p.ppp_opackets);
        printf("----------------------------------------/n");
    }
 
    close(sock_fd);
 
    return ;
}

全部代码到这里下载

]]> http://www.limodev.cn/blog/archives/1573/feed 0 - Android中的网络时间同步 >http://www.limodev.cn/blog/archives/1569 http://www.limodev.cn/blog/archives/1569#comments Sun, 12 Dec 2010 08:31:50 +0000 李先静 - - http://www.limodev.cn/blog/?p=1569 - - - - 在 Android的系统设置中,有自动同步网络时间的选项。因为Broncho A1移植到froyo版本之后,我们发现时间同步选项无效了。所以我花了一点时间去研究 Android的网络时间同步的流程。研究的结果让我感到惊讶,Android的网络时间同步居然与SNTP协议无关,甚至与TCP/IP协议也毫无关系。

从设置的应用程序中可以了解到,自动同步网络时间的选项只是修改了Settings.System.AUTO_TIME这个设置:

private void setAutoState(boolean isEnabled, boolean autotimeStatus) {
       if (isEnabled == false) {
           mAutoPref.setChecked(autotimeStatus);
           mAutoPref.setEnabled(isEnabled);
       }
       else {
           Settings.System.putInt(getContentResolver(),
              Settings.System.AUTO_TIME, autotimeStatus ? 1 : 0);
       }
       mTimePref.setEnabled(!autotimeStatus);
       mDatePref.setEnabled(!autotimeStatus);
       mTimeZone.setEnabled(!autotimeStatus);
    }

谁会用这个设置呢?然后从代码中查找Settings.System.AUTO_TIME,主要有下面两处:

telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java

GSM和CDMA的实现应该是类似的,这里只是看看GSM:

1. reference-ril/reference-ril.c处理主动上报消息。

    if (strStartsWith(s, "%CTZV:")) {
        /* TI specific -- NITZ time */
        char *response;
 
        line = p = strdup(s);
        at_tok_start(&p);
 
        err = at_tok_nextstr(&p, &response);
 
        free(line);
        if (err != 0) {
            LOGE("invalid NITZ line %s/n", s);
        } else {
            RIL_onUnsolicitedResponse (
                RIL_UNSOL_NITZ_TIME_RECEIVED,
                response, strlen(response));
        }
}
</pr>
这里是处理模组主动上报的消息,如果是时间和时区消息,则调用RIL_onUnsolicitedResponse。
 
2.	RIL_onUnsolicitedResponse会把消息发送给RIL的客户端。
<pre lang="c">
ret = sendResponse(p, client_id);

时间和时区信息的格式在RIL_UNSOL_NITZ_TIME_RECEIVED消息的定义处有说明:

“data” is const char * pointing to NITZ time string in the form “yy/mm/dd,hh:mm:ss(+/-)tz,dt”

3. RIL客户端处理RIL_UNSOL_NITZ_TIME_RECEIVED消息(telephony/java/com/android/internal/telephony/RIL.java: processUnsolicited)

            case RIL_UNSOL_NITZ_TIME_RECEIVED:
                if (RILJ_LOGD) unsljLogRet(response, ret);
 
                // has bonus long containing milliseconds since boot that the NITZ
                // time was received
                long nitzReceiveTime = p.readLong();
 
                Object[] result = new Object[2];
 
                result[0] = ret;
                result[1] = Long.valueOf(nitzReceiveTime);
 
                if (mNITZTimeRegistrant != null) {
 
                    mNITZTimeRegistrant
                        .notifyRegistrant(new AsyncResult (null, result, null));
                } else {
                    // in case NITZ time registrant isnt registered yet
                    mLastNITZTimeInfo = result;
                }

是GsmServiceStateTracker向RIL注册的,所以事件会由GsmServiceStateTracker来处理。

4. GsmServiceStateTracker 处理EVENT_NITZ_TIME事件:

            case EVENT_NITZ_TIME:
                ar = (AsyncResult) msg.obj;
                String nitzString = (String)((Object[])ar.result)[0];
                long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
                setTimeFromNITZString(nitzString, nitzReceiveTime);
                break;

这里nitzString是时间字符串,由setTimeFromNITZString负责解析。

private void setTimeFromNITZString (String nitz, long nitzReceiveTime) {
            String[] nitzSubs = nitz.split("[/:,+-]");
 
            int year = 2000 + Integer.parseInt(nitzSubs[0]);
            c.set(Calendar.YEAR, year);
 
            // month is 0 based!
            int month = Integer.parseInt(nitzSubs[1]) - 1;
            c.set(Calendar.MONTH, month);
 
            int date = Integer.parseInt(nitzSubs[2]);
            c.set(Calendar.DATE, date);
 
            int hour = Integer.parseInt(nitzSubs[3]);
            c.set(Calendar.HOUR, hour);
 
            int minute = Integer.parseInt(nitzSubs[4]);
            c.set(Calendar.MINUTE, minute);

如果在系统设置中,用户选择了自动同步网络时间,才会去设置系统时间。

           if (getAutoTime()) {
               setAndBroadcastNetworkSetTimeZone(zone.getID());
           }
           if (getAutoTime()) {
setAndBroadcastNetworkSetTime(c.getTimeInMillis());
           }

关于NITZ在WIKI上有说明:
NITZ, or Network Identity and Time Zone[1], is a mechanism for provisioning local time and date, as well as network provider identity information to mobile devices via a wireless network[2]. NITZ has been part of the official GSM standard since phase 2+ release 96[3]. NITZ is often used to automatically update the system clock of mobile phones.

由于NITZ的实现是可选的,如果运营商不支持它,Android手机就无法使用此功能了。此时用最好用SNTP来代替,否则用户会感到迷惑。但Android目前好像并没有这样做,我只找到两处地方调用SntpClient,但它们都没有去设置系统时间。

]]> http://www.limodev.cn/blog/archives/1569/feed 0 - Android中的WatchDog >http://www.limodev.cn/blog/archives/1566 http://www.limodev.cn/blog/archives/1566#comments Sat, 11 Dec 2010 14:02:15 +0000 李先静 - - http://www.limodev.cn/blog/?p=1566 - - - - 现在的CPU基本上都带有WatchDog功能,这种硬件的WatchDog可以在系统死掉(死锁或者程序跑飞)后重启系统,让系统回到可以工作的状态。WatchDog不能防止系统死掉,但是它能够起死回生,从而提高系统的可用性。

硬件级的WatchDog也有它的局限性,它只能在系统范围内生效,不能针对单个进程,某个进程死掉了,WatchDog未必知道。对于像Linux这类久经考验的操作系统来说,整个系统死掉概率非常低,所以此时硬件级的WatchDog意义反而不大。

Android 平台实现了一个软件的WatchDog来监护SystemServer。SystemServer无疑是Android平台中最重要的进程了,里面运行了整个平台中绝大多数的服务。在这个进程中运行着近50个线程,任何一个线程死掉都可能导致整个系统死掉。SystemServer退出反而问题不大,因为 init进程会重新启动它,但是它死锁就麻烦了,因为整个系统就没法动了。

在 SystemServer里运行的服务中,最重要的几个服务应该数ActivityManager、WindowManager和 PowerManager。软件的WatchDog主要就是确保这几个服务发生死锁之后,退出SystemServer进程,让init进程重启它,让系统回到可用状态。

每个被监护的Service必须实现Watchdog.Monitor接口,这个接口只要实现一个函数monitor,这个函数实现非常简单,就拿ActivityManager来说吧:

public void monitor() {
       synchronized (this) { }
    }

它去锁一下对象,什么也不做,然后就返回。如果对象没有死锁,这个过程就会很顺利。如果对象死锁了,这个函数就会挂在这里。

当然实现Watchdog.Monitor接口还不够,还要把它注册到WatchDog服务中,在初始化时加这样一行代码就行了:

Watchdog.getInstance().addMonitor(this);

最后我们看看WatchDog服务的实现。WatchDog服务包括两个方面:

1.定期调用被监护对象的monitor函数,这是在主线程中完成的。如果被监护对象死锁,则会阻塞在这里。

                   final int size = mMonitors.size();
                   for (int i = 0 ; i < size ; i++) {
                       mCurrentMonitor = mMonitors.get(i);
                       mCurrentMonitor.monitor();
                   }

2.检测是否发生死锁,这是在Watchdog线程中运行的。如果发生死锁而且没有被调试,则退出SystemServer,init进程就会重启SystemServer进程。

           // Only kill the process if the debugger is not attached.
           if (!Debug.isDebuggerConnected()) {
               Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name);
               Process.killProcess(Process.myPid());
               System.exit(10);
           } else {
               Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
           }

最近我们碰到一个问题,SystemServer不断重启:原来wifi模块死锁了,此时重启SystemServer,但是重启之后马上又死锁了,结果成了一个死循环。在这种情况下,我想还是在重启系统会好点。

]]> http://www.limodev.cn/blog/archives/1566/feed 0 - FTK将作为RT-Thread子项目发展 >http://www.limodev.cn/blog/archives/1564 http://www.limodev.cn/blog/archives/1564#comments Thu, 09 Dec 2010 02:19:00 +0000 李先静 - - http://www.limodev.cn/blog/?p=1564 - - - - RT-Thread(http://www.rt-thread.org)是bernard.xiong(熊谱翔)兄领导的一个开源项目。RT- Thread非常优秀,早在FTK开始之前,我花了一些时间研究国内与RTOS和GUI相关的一些开源项目,RT-Thread优秀的设计和漂亮的代码风格给我留下了很深的印象,bernard.xiong(熊谱翔)兄自然也成为我最佩服的高手之一。

bernard兄不但在kernel方面的造诣远高于我,在GUI方面的功力也令人佩服的,加上他的领导能力,FTK作为RT-Thread子项目发展的前途会更加光明。FTK将作为RT-Thread子项目发展,我考虑很久了,因为担心RT-Thread团队会不会接受,所以最近才和bernard兄商量,呵,没想他豪爽的接受了。

FTK将作为RT-Thread子项目发展后,FTK的开发人员可以参与到RT-Thread的开发,RT-Thread的开发人员也会参与FTK的开发,大家可以认识更多的朋友,学习更多嵌入式方面的知识,这种联合定会成为双赢的局面。以后,我会作为普通开发人员参与FTK的开发和维护(由于健康原因,参与可能会少些),具体的项目规划由bernard兄负责。

感谢大家长期以来对FTK的关爱和支持!特别感谢woodysu, malajisi, huangyuxi, yut616, songbohr, MinPengli, huangzhihua, riwen.huang, jiaojinxing1987, ngwsx2008和其他FTK的参与者,呵,希望大家继续支持FTK,支持RT-Thread。

]]> http://www.limodev.cn/blog/archives/1564/feed 5 - 带着儿子爬山 >http://www.limodev.cn/blog/archives/1558 http://www.limodev.cn/blog/archives/1558#comments Mon, 15 Nov 2010 03:29:02 +0000 李先静 - - http://www.limodev.cn/blog/?p=1558 - - - -

]]> http://www.limodev.cn/blog/archives/1558/feed 6 - FTK内存使用及优化 >http://www.limodev.cn/blog/archives/1556 http://www.limodev.cn/blog/archives/1556#comments Sat, 23 Oct 2010 04:44:22 +0000 李先静 - - http://www.limodev.cn/blog/?p=1556 - - - - 1.字体

FTK 内置的字体文件大小是4519756字节。如果系统支持mmap或者使用norflash,这不会存在太大问题,否则就要全部读到内存中了,显然这是不明智的。幸好,我们通常并不需要显示全部字符,而只需要几百甚至几十个字符就行了,这时可以使用tools/fontextract重新生成字体文件,新的字体文件会非常小。

2.输入法数据

输入法数据也是很大的,如果系统支持mmap或者使用norflash,这不会存在太大问题。否则就有点麻烦了,解决方法有:
* 精简输入数据
* 重写查找算法
* 使用商业输入法
* 不需要输入法可以直接去掉。

3.share_canvas

canvas是绘制widget的画板,整个系统只需要一个就行了,与窗口的个数无关。它所占的内存是4 * display_width*display_height。这个无法减小。

4.主题

主题包含一些图标,这些图标不大,全部解码后在内存中占400K左右,由于是按需解码的,所以很少会全部解码进来。

5.FTK的库

约300K

6.其它

其它动态分配的内存,主要是各种widget使用的,通常不会超过200K。

做些精简之后,FTK使用的内存应该能减少到2M左右。

]]> http://www.limodev.cn/blog/archives/1556/feed 0 - FTK-0.5发布 >http://www.limodev.cn/blog/archives/1548 http://www.limodev.cn/blog/archives/1548#comments Wed, 22 Sep 2010 04:20:24 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1548 - - - - FTK-0.5 Release Notes:
1.Bug fix and optimization.
2.Widgets improvements:
  o entry supports tips.
  o label supports alignment.
  o listview supports marquee.
  o progressbar support text information.
3.VNC supported.
4.Screen rotate.
5.Gettext supported.
6.Opengles supported.
7.Unicode linebreak.
8.Gui autotest tool.
9.IPC supported(fbus).
10.Config service(fconf).
11.Filebrowser and filechooser.
12.New applications(shell/filebrowser).
13.More hardware supported.
  o st7781.
  o zoran TVBOX.
  o qi-hardware ben.
  o mini4020
14.PSP supported.
15.Documents

特别感谢tao yu,yapo su, lendy, farter, 焦进星, 李伟杰和其他参与FTK开发和讨论的朋友们。欢迎大家加入邮件列表讨论。

有兴趣的朋友请到FTK下载源代码。

附:
A gui library for embedded system. FTK equals funny tool kit, write it just for fun.

ftk

General features:

  • Lightweght.
  • Beautiful look and feel.
  • High code quality: design pattern, good code style.
  • Stable: code review, unit test, stress test, valgrind checked.
  • Full gui features: window, dialog, panel, button, menu…
  • Good portability: Unix, Windows, RTOS…

Modern features:

  • Theme.
  • Window animation.
  • Alpha channel to implement transparent/translucent window.
  • XML UI description language.
  • Script binding.
  • InputMethod with Handwrite
  • Screen rotation
  • Guesture recognition(TODO).

FTK-0.5截图:
OpenGLES:
alpha3d.png
alpha.png
Button:
button.png
Cairo:
cairo1.png
cairo2.png
Calculator:
calc.png
CheckButton
checkbutton.png
ComboBox:
combobox.png
Cursor:
cursor.png
Desktop:
desktop.png
Dialog:
dialog.png
Entry:
entry.png
Animation:
expand.png
File Chooser:
filechooser.png
OpenGLES:
fog.png
FullScreen:
fullscreen.png
IconView:
iconview.png
ImageButton:
imagebutton.png
Input Method:
ime.png
Label:
label.png
ListView:
listview.png
Menu:
menu.png
MessageBox:
msgbox.png
Popup Menu:
popmenu.png
Programs:
programs.png
ProgressBar:
progressbar.png
Text View:
text_view.png
Transparent:
translucent.png
transparent.png
WaitBox
waitbox.png

]]> http://www.limodev.cn/blog/archives/1548/feed 8 - 嵌入式GUI FTK设计与实现-显示设备(FtkDisplay) >http://www.limodev.cn/blog/archives/1544 http://www.limodev.cn/blog/archives/1544#comments Tue, 13 Jul 2010 14:55:17 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1544 - - - - 对于GUI来说,输入设备(如键盘和鼠标)和显示设备 (如LCD)都是与硬件相关的。在前面关于《事件源(FtkSource)》的一节中,我们看到了FtkSource很好的抽象了输入设备,让FTK独立于具体的输入设备,从而提高了FTK的可移植性。同样为了提高FTK的可移植性,我们需要对显示设备进行抽象。这里我们引入接口FtkDisplay来抽象显示设备:

ftk_display

FtkDisplay的主要接口函数有:

* ftk_display_width: 获取显示设备的宽度。
* ftk_display_height: 获取显示设备的高度。
* ftk_display_update: 在指定位置显示位图数据。
* ftk_display_snap: 从显示设备中指定位置截图。

1.ftk_display_x11
这是针对X Window实现的显示设备,它将一个X窗口当作显示设备,让所有FTK窗口都显示在同一个X窗口上。这样做的目的是在PC上模拟运行FTK的应用程序,从而提高用FTK开发软件的效率。

创建 X窗口:

    win = XCreateSimpleWindow(display, RootWindow(display, screen),
       0, 0, width, height, 3, BlackPixel(display, screen), WhitePixel(display, screen));

创建XImage:

       priv->bits = FTK_ZALLOC(width * height * priv->pixelsize);
       priv->ximage = XCreateImage(display, priv->visual, priv->depth, ZPixmap,
           0, (char*)priv->bits, width, height,
           32, width * priv->pixelsize);

获取宽度:

static int ftk_display_x11_width(FtkDisplay* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, 0);
 
    return priv->width;
}

获取高度:

static int ftk_display_x11_height(FtkDisplay* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, 0);
 
    return priv->height;
}

显示位图:先把位图拷贝到XImage上,然后调用 XPutImage显示XImage。

static Ret ftk_display_x11_update(FtkDisplay* thiz, FtkBitmap* bitmap, FtkRect* rect, int xoffset, int yoffset)
{
    Ret ret = RET_FAIL;
    DECL_PRIV(thiz, priv);
 
    if(bitmap != NULL)
    {
       int display_width  = priv->width;
       int display_height = priv->height;
       ret = priv->copy_to_data(bitmap, rect,
           priv->bits, xoffset, yoffset, display_width, display_height);
       XPutImage(priv->display, priv->win, priv->gc, priv->ximage,
           xoffset, yoffset, xoffset, yoffset, rect->width, rect->height);
    }
    else
    {
       XPutImage(priv->display, priv->win, priv->gc, priv->ximage, 0, 0, 0, 0, priv->width, priv->height);
       ret = RET_OK;
    }
 
    XFlush(priv->display);
    XSync(priv->display, 0);
 
    return ret;
}

截取位图:从XImage拷贝数据到位图中。

static Ret ftk_display_x11_snap(FtkDisplay* thiz, FtkRect* r, FtkBitmap* bitmap)
{
    FtkRect rect = {0};
    DECL_PRIV(thiz, priv);
    int w = ftk_display_width(thiz);
    int h = ftk_display_height(thiz);
    int bw = ftk_bitmap_width(bitmap);
    int bh = ftk_bitmap_height(bitmap);
 
    rect.x = r->x;
    rect.y = r->y;
    rect.width = FTK_MIN(bw, r->width);
    rect.height = FTK_MIN(bh, r->height);
 
    return priv->copy_from_data(bitmap, priv->bits, w, h, &rect);
}

在运行configure时,用–with- backend=linux-x11:widh x height来指定宽度和高度。

2.ftk_display_mem

这是针对类似于framebuffer显示设备实现的基类,用于简化像Linux FrameBuffer,Sigma和ucosii(Windows模拟)的显示设备的实现。

创建函数:宽度、高度、格式和显存地址在创建时传入:

FtkDisplay* ftk_display_mem_create(FtkPixelFormat format,
    int width, int height, void* bits, FtkDestroy on_destroy, void* ctx)
{
    FtkDisplay* thiz = NULL;
 
    thiz = (FtkDisplay*)FTK_ZALLOC(sizeof(FtkDisplay) + sizeof(PrivInfo));
    if(thiz != NULL)
    {
       DECL_PRIV(thiz, priv);
       thiz->update   = ftk_display_mem_update;
       thiz->width    = ftk_display_mem_width;
       thiz->height   = ftk_display_mem_height;
       thiz->snap     = ftk_display_mem_snap;
       thiz->destroy  = ftk_display_mem_destroy;
 
       priv->bits = bits;
       priv->width = width;
       priv->height = height;
       priv->format = format;
       priv->on_destroy = on_destroy;
       priv->on_destroy_ctx = ctx;
 
       switch(format)
       {
           case FTK_PIXEL_RGB565:
           {
               priv->copy_to_data   = ftk_bitmap_copy_to_data_rgb565;
               priv->copy_from_data = ftk_bitmap_copy_from_data_rgb565;
               break;
           }
           case FTK_PIXEL_BGR24:
           {
               priv->copy_to_data   = ftk_bitmap_copy_to_data_bgr24;
               priv->copy_from_data = ftk_bitmap_copy_from_data_bgr24;
               break;
           }
           case FTK_PIXEL_BGRA32:
           {
               priv->copy_to_data   = ftk_bitmap_copy_to_data_bgra32;
               priv->copy_from_data = ftk_bitmap_copy_from_data_bgra32;
               break;
           }
           case FTK_PIXEL_RGBA32:
           {
               priv->copy_to_data   = ftk_bitmap_copy_to_data_rgba32;
               priv->copy_from_data = ftk_bitmap_copy_from_data_rgba32;
               break;
           }
           default:
           {
               assert(!"not supported framebuffer format.");
               break;
           }
       }
    }
 
    return thiz;
}

这里根据格式选择适当的拷贝函数,用于把位图拷贝到显存,和从显存拷贝数据到位图。

获取宽度:

static int ftk_display_mem_width(FtkDisplay* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, 0);
 
    return priv->width;
}

获取高度:

static int ftk_display_mem_height(FtkDisplay* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, 0);
 
    return priv->height;
}

显示位图:这里只简单的调用copy_to_data。

static Ret ftk_display_mem_update(FtkDisplay* thiz, FtkBitmap* bitmap, FtkRect* rect, int xoffset, int yoffset)
{
    DECL_PRIV(thiz, priv);
    int display_width  = priv->width;
    int display_height = priv->height;
    return_val_if_fail(priv != NULL, RET_FAIL);
 
    ftk_logd("%s: ox=%d oy=%d (%d %d %d %d)/n", __func__, xoffset, yoffset,
       rect->x, rect->y, rect->width, rect->height);
    return priv->copy_to_data(bitmap, rect,
       priv->bits, xoffset, yoffset, display_width, display_height);
}

截取位图:这里只是简单的调用 copy_from_data。

static Ret ftk_display_mem_snap(FtkDisplay* thiz, FtkRect* r, FtkBitmap* bitmap)
{
    FtkRect rect = {0};
    DECL_PRIV(thiz, priv);
    int w = ftk_display_width(thiz);
    int h = ftk_display_height(thiz);
    int bw = ftk_bitmap_width(bitmap);
    int bh = ftk_bitmap_height(bitmap);
    return_val_if_fail(priv != NULL, RET_FAIL);
 
    rect.x = r->x;
    rect.y = r->y;
    rect.width = FTK_MIN(bw, r->width);
    rect.height = FTK_MIN(bh, r->height);
 
    return priv->copy_from_data(bitmap, priv->bits, w, h, &rect);
}

3.ftk_display_fb

这针对Linux FrameBuffer实现的显示设备,Linux FrameBuffer设备文件通常是/dev/fb0或/dev/graphic/fb0。

打开FrameBuffer并获取相关参数:

static int fb_open(struct FbInfo *fb, const char* fbfilename)
{
    fb->fd = open(fbfilename, O_RDWR);
    if (fb->fd < 0)
    {
       return -1;
    }
 
    if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0)
       goto fail;
    if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0)
       goto fail;
 
#ifdef FTK_FB_NOMMAP
    //uclinux doesn't support MAP_SHARED or MAP_PRIVATE with PROT_WRITE, so no mmap at all is simpler
    fb->bits = fb->fi.smem_start;
#else
    fb->bits = mmap(0, fb_size(fb), PROT_READ | PROT_WRITE, MAP_SHARED, fb->fd, 0);
#endif
}

然后调用ftk_display_mem去创建显示设备:

   thiz = ftk_display_mem_create(format, fb->vi.xres, fb->vi.yres,
           fb->bits, fb_close, fb);

对于sigma和ucosii(Windows模拟)实现的显示设备也很简单,这里就不再多说了。

4.ftk_display_rotate

这并不是针对某种物理显示设备的实现。而是利用装饰模式,不改变显示设备对象的接口,让显示设备具有屏幕旋转的功能。它可以对不同的显示设备进行装饰,所以只需要实现一次即可。

创建:传入一个FtkDisplay,返回新的一个 FtkDisplay,这是装饰模式的典型特征。

FtkDisplay* ftk_display_rotate_create(FtkDisplay* display, FtkRotate rotate)
{
    FtkDisplay* thiz = NULL;
    return_val_if_fail(display != NULL, NULL);
 
    thiz = FTK_ZALLOC(sizeof(FtkDisplay) + sizeof(PrivInfo));
 
    if(thiz != NULL)
    {
       DECL_PRIV(thiz, priv);
 
       priv->display = display;
       thiz->width   = ftk_display_rotate_width;
       thiz->height  = ftk_display_rotate_height;
       thiz->destroy = ftk_display_rotate_destroy;
 
       ftk_display_set_rotate(thiz, rotate);
    }
 
    return thiz;
}

获取宽度:根据旋转角度决定返回高度还是宽度。

static int ftk_display_rotate_width(FtkDisplay* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, 0);
 
    return (priv->rotate == FTK_ROTATE_0 || priv->rotate == FTK_ROTATE_180) ?
       ftk_display_width(priv->display) : ftk_display_height(priv->display);
}

获取高度:根据旋转角度决定返回高度还是宽度。

static int ftk_display_rotate_height(FtkDisplay* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, 0);
 
    return (priv->rotate != FTK_ROTATE_0 && priv->rotate != FTK_ROTATE_180) ?
       ftk_display_width(priv->display) : ftk_display_height(priv->display);
}

设置旋转的角度:根据角度选择适当的变换函数,这里用到了策略模式。

static const FtkDisplayUpdate updates[FTK_ROTATE_NR] =
{
    ftk_display_rotate_update_0,
    ftk_display_rotate_update_90,
    ftk_display_rotate_update_180,
    ftk_display_rotate_update_270
};
 
static const FtkDisplaySnap snaps[FTK_ROTATE_NR] =
{
    ftk_display_rotate_snap_0,
    ftk_display_rotate_snap_90,
    ftk_display_rotate_snap_180,
    ftk_display_rotate_snap_270
};
 
Ret ftk_display_set_rotate(FtkDisplay* thiz, FtkRotate rotate)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL && priv->display != NULL, RET_FAIL);
    return_val_if_fail(thiz->destroy == ftk_display_rotate_destroy, RET_FAIL);
 
    if(priv->rotate == rotate && priv->bitmap != NULL)
    {
       return RET_OK;
    }
 
    priv->rotate = rotate;
    if(priv->bitmap != NULL)
    {
       ftk_bitmap_unref(priv->bitmap);
       priv->bitmap = NULL;
    }
 
    if(priv->rotate != FTK_ROTATE_0)
    {
       FtkColor bg = {0};
       memset(&bg, 0xff, sizeof(bg));
       priv->bitmap = ftk_bitmap_create(ftk_display_width(priv->display),        ftk_display_height(priv->display), bg);
    }
 
    thiz->snap = snaps[rotate];
    thiz->update = updates[rotate];
 
    return RET_OK;
}

(具体算法请参考源代码)。

另外FtkDisplay还利用观察者模式实现了更新通知功能,目前主要用在更新sprite(光标)上,当屏幕有更新时,通知sprite重新截图。以后可以还用于VNC的实现,当屏幕有更新时通知客户端更新。

* ftk_display_reg_update_listener 注册监听者。
* ftk_display_unreg_update_listener 注销监听者。
* ftk_display_update_and_notify 更新屏幕并通知监听者。

其它一些FtkDisplay的实现这就不多说了,有兴趣的朋友可以阅读源代码。

]]> http://www.limodev.cn/blog/archives/1544/feed 4 - 嵌入式GUI FTK设计与实现-事件源(FtkSource) >http://www.limodev.cn/blog/archives/1540 http://www.limodev.cn/blog/archives/1540#comments Fri, 09 Jul 2010 03:07:40 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1540 - - - - 在《主循环》一节中, 我们介绍了MainLoop 处理各个事件源的方法,它在事件源上等待事件发生,然后调用事件源的处理函数去处理事件。事件源(FtkSource)是对事件来源的一种抽象,事件的来源可能是一个输入设备(如键盘和触摸屏),可能是一个定时器,也可能是一个网络套接字或管道。总之,只要实现FtkSource要求的接口,就可以让 MainLoop来处理了。

FtkSource 要求实现下列接口函数:

* ftk_source_get_fd 用来获取文件描述符,这个文件描述符不一定是真正的文件描述符,只要是能MainLoop挂在上面等待的句柄(Handle)即可。

* ftk_source_check 用来检查事件源要求等待的时间。-1表示不关心等待时间。0表示要马上就有事件发生,正数表示在指定的时间内将有事件发生。

* ftk_source_dispatch 用来处理事件,每个事件源都有自己的处理函数,这样可以简化程序的实现。

目前FTK内部使用的事件源主要有:

1.ftk_source_input(.c/.h)

在Linux下,输入设备文件都在/dev /input/下,并用一致的事件结构(input_event)将事件上报给应用程序,ftk_source_input是针对这些设备文件实现的事件源。

由于是从设备文件读取输入事件,那主循环可以挂在这些设备文件上等待事件发生。所以ftk_source_get_fd只要返回文件描述符,而 ftk_source_check返回-1即可。

static int ftk_source_input_get_fd(FtkSource* thiz)
{
    DECL_PRIV(thiz, priv);
 
    return priv->fd;
}
 
static int ftk_source_input_check(FtkSource* thiz)
{
    return -1;
}

ftk_source_dispatch中读取事件(input_event),将它转换成FTK的事件结构,然后通过将事件分发(调用 ftk_wnd_manager_queue_event)出去。

键值的映射是放在表 s_key_mapp中的:

static unsigned short s_key_map[0x100] =
{
    [KEY_1]           =  FTK_KEY_1,
    [KEY_2]           =  FTK_KEY_2,
    [KEY_3]           =  FTK_KEY_3,
    [KEY_4]           =  FTK_KEY_4,
    [KEY_5]           =  FTK_KEY_5,
    [KEY_6]           =  FTK_KEY_6,
    [KEY_7]           =  FTK_KEY_7,
   ...
};

如果有特殊键值或其它需求,修改这个结构即可。

2.ftk_source_dfb(.c/.h)

FTK可以用DirectFB作为 backend,ftk_source_dfb是针对DirectFB输入事件实现的事件源。DirectFB可以通从EventBuffer直接读取,也可以从EventBuffer获取一个管道的文件描述符,然后从这个管道读取事件。为了方便,我们使用后者来实现ftk_source_dfb,主循环可以挂在这个文件描述符上等待事件。所以ftk_source_get_fd只要返回文件描述符,而 ftk_source_check返回-1即可。

static int  ftk_source_dfb_get_fd(FtkSource* thiz)
{
    DECL_PRIV(thiz, priv);
 
    return priv->fd;
}
 
static int  ftk_source_dfb_check(FtkSource* thiz)
{
    return -1;
}

ftk_source_dispatch中读取事件(DFBEvent),将它转换成FTK的事件结构,然后通过将事件分发(调用 ftk_wnd_manager_queue_event)出去。

键值的映射是放在表 s_key_mapp中的:

static const int s_key_map[] =
{
    [DIKI_A-DIKI_UNKNOWN]              =  FTK_KEY_a,
    [DIKI_B-DIKI_UNKNOWN]              =  FTK_KEY_b,
    [DIKI_C-DIKI_UNKNOWN]              =  FTK_KEY_c,
    [DIKI_D-DIKI_UNKNOWN]              =  FTK_KEY_d,
    [DIKI_E-DIKI_UNKNOWN]              =  FTK_KEY_e,
    [DIKI_F-DIKI_UNKNOWN]              =  FTK_KEY_f,
    [DIKI_G-DIKI_UNKNOWN]              =  FTK_KEY_g,
    [DIKI_H-DIKI_UNKNOWN]              =  FTK_KEY_h,
   ...
};

如果有特殊键值或其它需求,修改这个结构即可。

3.ftk_source_tslib(.c/.h)

对于电阻式的触摸屏,虽然通常在Linux下也是通过(/dev/input)下的设备文件上报事件的,但是需要对输入事件进行去抖、滤波和校正之后才能使用,tslib是专门做这些工作的,所以这时我们用tslib读取事件是更明智的选择。

tslib 提供了一个函数用于获取文件描述符,主循环可以挂在这个文件描述符上等待事件。所以ftk_source_get_fd只要返回文件描述符,而 ftk_source_check返回-1即可。

static int ftk_source_tslib_get_fd(FtkSource* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL && priv->ts != NULL, -1);
 
    return ts_fd(priv->ts);
}
 
static int ftk_source_tslib_check(FtkSource* thiz)
{
    return -1;
}

ftk_source_dispatch中读取事件(ts_sample),将它转换成FTK的事件结构,然后通过将事件分发(调用 ftk_wnd_manager_queue_event)出去。

4.ftk_source_primary(.c/.h)

ftk_source_primary的地位比较特别,相当于其它GUI中的事件队列。

ftk_source_primary 使用管道来实现队列先进先出(FIFO)的特性,这样可以避免引入互斥机制来保护队列,管道有自己的文件描述符,主循环可以挂在这个文件描述符上等待事件。所以ftk_source_get_fd只要返回文件描述符,而 ftk_source_check返回-1即可。

static int ftk_source_primary_get_fd(FtkSource* thiz)
{
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv != NULL, -1);
 
    return ftk_pipe_get_read_handle(priv->pipe);
}
 
static int ftk_source_primary_check(FtkSource* thiz)
{
    return -1;
}

ftk_source_primary_dispatch 函数中对FTK_EVT_ADD_SOURCE/FTK_EVT_REMOVE_SOURCE两个事件做了特殊处理,用于增加和移除事件源,对于其余事件只是调用窗口管理器的事件分发函数(ftk_wnd_manager_default_dispatch_event)去处理事件。

ftk_source_primary提供了 ftk_source_queue_event用于向管道中写入事件,向管道中写入事件当于其它GUI向事件队列中增加事件。

5.ftk_source_timer

ftk_source_timer主要用于定时执行一个动作,比如闪动光标和更新时间。它与前面的事件源不同的是,它没有相应的文件描述符,主循环无法通过挂在文件描述符上来等待事件的发生。所以 ftk_source_get_fd始终返回-1:

static int ftk_source_timer_get_fd(FtkSource* thiz)
{
    return -1;
}

ftk_source_check返回下一次事件发生的时间间隔,告诉MainLoop 必须在这个时刻唤醒,并执行处理函数:

static int ftk_source_timer_check(FtkSource* thiz)
{
    DECL_PRIV(thiz, priv);
    int t = priv->next_time - ftk_get_relative_time();
 
    t = t < 0 ? 0 : t;
 
    return t;
}

ftk_source_timer_dispatch 的实现很简单,它计算下一次timer的时间,然后调用用户设置的回调函数。

tatic Ret ftk_source_timer_dispatch(FtkSource* thiz)
{
    Ret ret = RET_FAIL;
    DECL_PRIV(thiz, priv);
    return_val_if_fail(priv->action != NULL, RET_REMOVE);
 
    if(thiz->disable > 0)
    {
       ftk_source_timer_calc_timer(priv);
       return RET_OK;
    }
 
    ret = priv->action(priv->user_data);
    ftk_source_timer_calc_timer(priv);
 
    return ret;
}

5.ftk_source_idle(.c/.h)

idle的主要用途有:

* 在空闲时执行低优先级任务。有的任务优先级比较低,但费耗时间相对较长,比如屏幕刷新等操作。如果为了避免它阻碍当前操作太久,此时我们把它放到idle里去做。

* 将同步操作异步化。有的操作你可能不希望它在当前的处理函数中同步执行,这时也可以用idle来异步化 ,让它在后面的dispatch中执行。

* 串行化对GUI的访问。在FTK中,出于效率的考虑, GUI对象是没有加锁保护的,也就是只有GUI线程能访问这些对象。如果其它线程要访问GUI对象,此时就需要用idle来串行化了。idle是GUI线程(主线程)中执行的,所以它能访问GUI对象。

idle的实现有点timeout为0的定时器,把它独立出来主要为了概念上更清楚一点:

static int ftk_source_idle_get_fd(FtkSource* thiz)
{
    return -1;
}
 
static int ftk_source_idle_check(FtkSource* thiz)
{
    return 0;
}

ftk_source_idle_dispatch 只是简单的调用用户设置的回调函数。

static Ret ftk_source_idle_dispatch(FtkSource* thiz)
{
    DECL_PRIV(thiz, priv);
 
    return_val_if_fail(priv->action != NULL, RET_REMOVE);
 
    if(thiz->disable > 0)
    {
       return RET_OK;
    }
 
    return priv->action(priv->user_data);
}

FTK中还有其它一些事件源,比如针对X11模拟运行的事件源,它们的实现都是类似的,这里就不再多说了。

]]> http://www.limodev.cn/blog/archives/1540/feed 1 - 用Android模拟器运行FTK >http://www.limodev.cn/blog/archives/1537 http://www.limodev.cn/blog/archives/1537#comments Mon, 05 Jul 2010 02:24:38 +0000 李先静 - - - - - - http://www.limodev.cn/blog/?p=1537 - - - - 用Android模拟器运行FTK

以前写过一篇文章介绍如何在Androidr模拟器上运行FTK(http://www.limodev.cn/blog/archives/1400),那种方法有点别扭,而且要下载Android 的源代码才能编译。最近几个开发板都坏了,所以上周末研究了一下Android模拟器,自己生成文件系统,只是利用Android的模拟器来运行FTK。

Android的模拟器是基于qemu的,个人感觉qemu不好用,除了用Openmoko做的模拟器外,我还没成功用qemu来模拟运行过arm linux。Android在qemu做了不少人性化的工作,用起来比较方便。

0.请参考网上的方法安装Android SDK。

1.生成自己的init程序。

创建几个基本目录。

int init_dirs(void)
{
    umask(0);
    mkdir("/dev", 0755);
    mkdir("/system", 0777);
    mkdir("/dev/block", 0755);
 
    return 0;
}

创建MTD设备的结点。

int init_dev_nodes(void)
{
    int ret = 0;
    ret = mknod("/dev/block/mtdblock0", 0755|S_IFBLK, makedev(31, 0));
    printf("mknod /dev/block/mtdblock0 ret = %d errno=%d/n", ret, errno);
 
    return 0;
}

加载system.img 分区。

int mount_filesystem(void)
{
    int ret = 0;
    ret = mount("/dev/block/mtdblock0", "/system", "yaffs2", 0, NULL);
    printf("mount /dev/block/mtdblock0 ret = %d errno=%d/n", ret, errno);
 
    return 0;
}

运行FTK的桌面。

int startup_desktop(const char* name)
{
    struct stat st = {0};
    int ret = stat(name, &st);
 
    if(ret != 0)
    {
       return -1;
    }
 
    printf("starting the second init: %s./n", name);
 
    if(fork() == 0)
    {
       ret = execl(name, name);
    }
 
    return ret;
}
 
int switch_root(void)
{
    int ret = chroot("/system");
    ret = startup_desktop("/opt/bin/desktop");
 
    return ret;
}

2.生成ramdisk.img

rm -rf ramdisk
mkdir ramdisk
 
cd init && make; cd - >/dev/null
 
echo "Generating ramdisk..."
 
cd ramdisk && find . | cpio -c -o > ../initrd; cd - >/dev/null
gzip initrd && mv initrd.gz ramdisk.img

3.生成system.img

if [ ! -d rootfs ]
then
    tar xf rootfs.tar.gz
fi

(在这里解压编译好的FTK(ftk-dist.tar.gz)到rootfsrts)

./mkyaffs2image rootfs system.img

4.拷贝生成的ramdisk.img system.img到SDK目录里。

echo "Copying files..."
for f in ramdisk.img system.img
do
    echo "  $f --> $ADNROID_SDK_IMAGES_DIR"
    mv -f $f $ADNROID_SDK_IMAGES_DIR
done

android-ftk
完整源代码和脚本请到这里下载:
svn checkout http://ftke.googlecode.com/svn/trunk/android-ftk-emu android-ftk-emu

]]> http://www.limodev.cn/blog/archives/1537/feed 2 - 《系统程序员成长计划》示例代码 >http://www.limodev.cn/blog/archives/1533 http://www.limodev.cn/blog/archives/1533#comments Wed, 14 Apr 2010 14:05:00 +0000 李先静 - - http://www.limodev.cn/blog/?p=1533 - - - - 在googlecode上建立了一个项目:http://code.google.com/p/spexamples/ 用来维护《系统程序员成长计划》示例代码。

无论是示例代码还是书中的BUG,请大家Report到http://code.google.com/p/spexamples/issues/list

如果以后有机会出新版本,将有样书赠送给第一个发现BUG的朋友。

谢谢大家支持。

]]> http://www.limodev.cn/blog/archives/1533/feed 4 - 《系统程序员成长计划》成长过程 >http://www.limodev.cn/blog/archives/1531 http://www.limodev.cn/blog/archives/1531#comments Mon, 12 Apr 2010 12:38:28 +0000 李先静 - - http://www.limodev.cn/blog/?p=1531 - - - - 《系统程序员成长计划》的起源应该追溯到六年前了。04年我进入恒基伟业深圳研发部,参与商务通隐形手机开发。在此之前我是做服务器软件开发的,对嵌入式软件开发非常好奇,所以想方设法进入这个行业。恒基伟业在嵌入式方面的实力也是相当高的,很多在消费类设备方面的开发高手都有在恒基伟业工作过的经验。原以为做嵌入式开发的程序员都是非常牛的人,动不动都是能写上上万行汇编语言。真正进入这个行业之后,才知道牛人毕竟是少数,大部分人都和我一样是普通的程序员。

过了几个月,我当了小组长,手下带了几位同事。有刚毕业的,有工作两年的,也有工作经验和我差不多的。有次我让一位同事写一个小程序,过了好几天他还没有写完。和他讨论了一会儿后,才知道他写的程序动不动就死掉,查了几天没有查出原因来。由于我以前做过两年全职的调试工作,看了一下他的代码,马上发现问题所在:指针声明之后,没有指向任何有效的内存,就直接使用了!

后来和他们交流多了,才发现有的同事工作几年之后,连一些编程的基本技能都没有掌握,代码写得乱七糟八的。这些代码集成到项目中后,产品越来越不稳定,整个项目慢慢失去控制。那个项目后来BUG总数超过了10000个,当时大家都知道在做一件不可能完成的任务,士气变得非常低落。经历那一段非常郁闷的时期之后,我更深刻的体会到高素质的程序员才是软件开发成功的关键。于是我开始对手下的同事进行培训,把一些我认为重要的知识和方法教给他们。虽然没有收到预期的效果,不过积累了一些素材。

05年我跟着现在的上司进入鼎智通信,我还是小组长,带了几个新手。我根据前面的经验制定了一个培训计划,我的上司看了之后觉得不错,就在软件部推广。下面是当时我发在博客上的培训计划:

学习的方式:

对于基础性知识,指定几本教材,大家轮流学习,学习完成后给其他讲解。对于较难的知识,由我或者请其他有经验的人讲解。集中学习时间定在周三晚上。

第一阶段:

目标:熟悉常见的算法和调试方法,培养良好的编程风格,从而提高编程 能力。
时间:8周
内容:

1. 编程规范
2. 双向链表
3. 调试方法与调式工具的使用
4. 动态数组
5. 快速排序/归并排 序/堆排序/二分查找
6. 状态机
7. 下推自动机
8. 嵌入式软件开发
9. Unicode基本知识

第二阶段:

目标: 学习如何阅读和改进别人的代 码。
时间: 4周
内容:
1. 代码阅读方法
2. 代码重构

第三阶段:

目标:自动测试
时间:2周
内容:
1. 测试理论
2. 常用的自动测试框架

第四阶段:

目标:软件设计和文档编写
时间:12周
内容:

1. 面向对象的设计与分析
2. 契约式编程
3. 设计模式
4. 软件架构编档
5. 了解常见的软件过程: XP/RUP等

第五阶段:

目标:学习一种脚本语言,能自动化的工作尽量让计算机去做,从而提高 工作效率。

时间:4周
内容:cygwin + bash 或者 python 或者 perl 或者vbscript。

第六阶段:

目标:综合应用所学的知识,完成一个模块的设计、编程、测试。
时间:4周
内容:待定

这个培训坚持了半年,收到一些效果,但是不够理想。主要原因是这个培训计划追求大而全,加上大家工作很忙,还没有消化就进入了下一个阶段。现在来看,这个计划完全可以作为一个程序员的三年学习计划了。

05年下半年,Broncho团队成立。我改为手把手的培训方式,通过代码评审等方式,发现新手犯的错误,然后纠正他,并把一些典型的错误统一讲解。慢慢的积累了典型错误和问题的素材,后来我开始思考:能不能搞一系列的题目,让新手把他们该犯的错误犯一遍,让这些典型的错误在他们正式工作之前就被纠正了呢?

这些题目要有足够的挑战,又不能让人望而生畏。要解决这个难题不容易,记得当时《C++沉思录》给了我一些启示:用一个好的程序的演化过程,逐步深入的学习各种基本的技术和方法。于是有了第一章双向链表演化的雏形,第二章写得又快又好的秘诀,则是我多年来一直在思考的。

07年下半年正式在Broncho团队起用这套培训课程,记得Broncho团队的吴松是第一位参加这个培训的同事。经过一年多时间,前前后后有十多位同事的参与,这套培训课程逐渐完善起来,到现在Broncho团队仍然在使用。

08年十月份开始把这些培训资料整理成文字,并在我的博客上发布。这是一项艰苦的工作,虽然这些内容在脑子里都过了几十遍了,但写出来的时候,要讲清楚还是不容易的。一年多时间,经过读者的建议,又做了些完善。

09年七月底完稿,并确定由人民邮电出版社旗下的图灵出版社出版。我一直强调图书的质量,并多次跟出版社要求,我不要稿费甚至自己拿钱出来补贴工作人员都可以,但是一定要保证图书的质量。出版的过程中出现了一些波折,到今年三月本书才终于与读者见面。

另外我解释一下,书上为什么没有推荐序之类的东西。在出版之前,一些热心的朋友主动说帮我写或者请名人写推荐序,我都一一婉拒了。我的理由很简单,那些“名人”都没有仔细读过我的书,帮忙写推荐只是纯粹的吹捧,都是虚假的,对读者不负责的。我希望更多的读者读本书,但是决不能用这种手段来欺骗读者。只有真正的读者才有资格对书做出评论,如果有机会出第二版,我会请读者写推荐序的。

]]> http://www.limodev.cn/blog/archives/1531/feed 5 - FTK移植指南(初稿) >http://www.limodev.cn/blog/archives/1529 http://www.limodev.cn/blog/archives/1529#comments Wed, 07 Apr 2010 13:13:16 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1529 - - - - 1.内存分配
要求:实现malloc/realloc/free三个函数,函数原型和标准C一致。或者实现FtkAllocator接口。
参考:ftk_allocator_default.c

2.文件操作
要求:实现FtkMmap接口。libpng和libjpeg还要求实现标准C的FILE(也可以用自己的解码器)。
参考:os/linux/ftk_mmap_linux.c

3.显示设备
要求:实现FtkDisplay接口,对于基本framebuffer的显示方式,可以利用ftk_display_mem来实现。
参考:backend/fb/ftk_display_fb.c

4.输入设备
要求:输入事件如果在主线程中读取,则要实现FtkSource接口。输入事件如果在单独线程中读取,可以用ftk_wnd_manager_queue_event来分发。
参考:backend/fb/ftk_source_input.c
注意:按键需要映射成FTK的键值。

5.管道
要求:实现FtkPipe接口。
参考:ftk_pipe_socket.c

6.图形解码
如果有自定义的图像格式或优化过的解码器,请实现ftk_image_decoder接口。

7.主函数
主函数可以是main或者ftk_main,由宏FTK_HAS_MAIN决定。

8.主循环
要求:实现FtkMainLoop
参考:ftk_main_loop_select.c

对于RTOS,FTK通常只是需要ftk_source_primary一个管道,所有输入事件都通过它来串行化,所以实现MainLoop时挂在这个管道上即可。

9.辅助函数和宏
usleep
ftk_strncpy
ftk_snprintf
ftk_vsnprintf
ftk_getcwd
ftk_sscanf
ftk_get_relative_time

FTK_ROOT_DIR
FTK_FONT
DATA_DIR
LOCAL_DATA_DIR
FTK_DATA_ROOT
TESTDATA_DIR

参考:os/windows/ftk_win32.h

10.编译系统
对于兼容gcc的编译器,尽量使用automake来管理。对于不能兼容gcc的编译器,请把相关工程文件放到src/compiler下。

]]> http://www.limodev.cn/blog/archives/1529/feed 9 - FTK-0.4 发布 >http://www.limodev.cn/blog/archives/1517 http://www.limodev.cn/blog/archives/1517#comments Mon, 05 Apr 2010 07:11:54 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1517 - - - - 经过大家一个月的努力,在FTK诞生6个月之际,FTK-0.4如期发布,其主要修改有:

1. 修改了一些BUG。
2. 完整的支持LUA绑定。
3. 完善交叉编译脚本。
4. 增加FTK配置文件。
5. 优化显示速度。
6. 增加缩放动画效果。
7. UCOSII支持(Windows模拟环境)。

特别感谢minpengli,Riwen Huang,zhangalion,tao yu,yapo su, phil song, ZhiHua Huang, hua song, Richard Sharpe和其他参与FTK开发和讨论的朋友们。欢迎大家加入邮件列表讨论。

有兴趣的朋友请到FTK下载源代码。

附:
A gui library for embedded system. FTK equals funny tool kit, write it just for fun.

ftk

General features:

  • Lightweght.
  • Beautiful look and feel.
  • High code quality: design pattern, good code style.
  • Stable: code review, unit test, stress test, valgrind checked.
  • Full gui features: window, dialog, panel, button, menu…
  • Good portability: Unix, Windows, RTOS…

Modern features:

  • Theme.
  • Window animation.
  • Alpha channel to implement transparent/translucent window.
  • XML UI description language.
  • Script binding.
  • InputMethod with Handwrite(working)
  • Screen rotation(TODO).
  • Guesture recognition(TODO).

FTK-0.4截图:
按钮:

单选/多选框:

下拉框:

对话框:

列表框:

进度条:

多行编辑器:

忙等待:

图标视图:

输入法:

单行编辑器:

全屏效果:

桌面:


计算器:

]]> http://www.limodev.cn/blog/archives/1517/feed 7 - 爬塘朗山 >http://www.limodev.cn/blog/archives/1509 http://www.limodev.cn/blog/archives/1509#comments Sun, 04 Apr 2010 13:16:28 +0000 李先静 - - http://www.limodev.cn/blog/?p=1509 - - - - 塘朗山比南山好玩,南山完全是为爬山而爬山,塘朗山还可以看下风景。tls

最近脸色有些苍白。
lxj

老婆还不太像全职师奶。
huan

]]> http://www.limodev.cn/blog/archives/1509/feed 7 - 嵌入式GUI FTK设计与实现-主循环 >http://www.limodev.cn/blog/archives/1500 http://www.limodev.cn/blog/archives/1500#comments Sun, 28 Mar 2010 04:19:11 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1500 - - - - 带图形用户界面(GUI)的应用程序和传统的批处理程序是不同的:

* 批处理程序是一步一步的执行,直到完成任务为止,完成任务后程序立即退出。
* 图形用户界面应用程序则是事件驱动的,它等待事件发生,然后处理事件,如此循环,直到用户要求退出为止。

两种执行模型如下图所示:

cmp

通常我们把等待事件/处理事件的循环称为主循环(MainLoop),主循环是GUI应用程序必要组件之一,FTK当然也离开不主循环 (MainLoop)。大多数嵌入式GUI都采用了Windows类似的主循环:

    while(GetMessage(&msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

我不太喜欢这种主循环,主要原因有两点:

* 它看似简洁,但内部实现并不简洁。它从当前线程的队列里获取消息,然后处理消息。这些消息从来哪里的?当然是由其它线程传递给当前线程的,这就意味着GUI需要多线程的支持。而FTK不想引入多线程来折磨自己,至少在GUI部分是不想的。

* 这是面向过程的处理方式。消息是一个对象,按照面向对象的思想来说,对象的数据和行为应该绑定在一起。而这里的消息是纯粹的数据,所有消息都由目标窗口的消息处理函数来处理的。FTK希望每类消息都有自己的处理函数,而不是全部由窗口来处理。

FTK采用了GTK类似的主循环:

    ftk_run();

它看起来更简洁,内部实现也不需要多线程的支持。这里采用了POSA(面向模式的软件架构)中的Reactor模式,它的主要好处有:

* 用单线程处理多个事件源。
* 增加新事件源时不会影响事件分发的框架。

整个主循环由下列组件构成:
mainloop
FtkSource 是一个接口,是所有事件源的抽象,它要求事件源实现下列函数:

* ftk_source_get_fd 用来获取文件描述符,当然这个文件描述符不一定是真正的文件描述符,只要是MainLoop能挂在上面等待的句柄(Handle)即可。
* ftk_source_check 用来检查事件源要求等待的时间。-1表示不关心等待时间。0表示要马就有事件发生,正数表示在指定的时间内将有事件发生。
* ftk_source_dispatch 用来处理事件,每个事件源都有自己的处理函数,而不是全部耦合到窗口的处理函数中。

FtkSourcesManager负责管理所有事件源。主要提供下列函数:

* ftk_sources_manager_add 增加一个事件源。
* ftk_sources_manager_remove 删除一个事件源。
* ftk_sources_manager_get_count 获取事件源总数。
* ftk_sources_manager_get 获取指定索引的事件源。

FtkMainLoop负责循环的等待事件发生,然后调用事件源的处理函数去处理。主要提供下列函数:

* ftk_main_loop_run 启动主循环
* ftk_main_loop_quit 退出主循环
* ftk_main_loop_add_source 增加一个事件源
* ftk_main_loop_remove_source 删除一个事件源

FtkMainLoop提供了add_source和remove_source两个函数对 FtkSourcesManager相应函数进行包装,这里包装不是简单的调用FtkSourcesManager的函数,而是发送一个事件:

Ret ftk_main_loop_add_source(FtkMainLoop* thiz, FtkSource* source)
{
    FtkEvent event = {0};
    return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);
 
    event.type = FTK_EVT_ADD_SOURCE;
    event.u.extra = source;
 
    return ftk_source_queue_event(ftk_primary_source(), &event);
}
 
Ret ftk_main_loop_remove_source(FtkMainLoop* thiz, FtkSource* source)
{
    FtkEvent event = {0};
    return_val_if_fail(thiz != NULL && source != NULL, RET_FAIL);
 
    event.type = FTK_EVT_REMOVE_SOURCE;
    event.u.extra = source;
 
    return ftk_source_queue_event(ftk_primary_source(), &event);
}

这个事件由Primary Source进行处理:

static Ret ftk_source_primary_dispatch(FtkSource* thiz)
{
    ...
    switch(event.type)
    {
        case FTK_EVT_ADD_SOURCE:
        {
            ftk_sources_manager_add(ftk_default_sources_manager(), event.u.extra);
            break;
        }
        case FTK_EVT_REMOVE_SOURCE:
        {
            ftk_sources_manager_remove(ftk_default_sources_manager(), event.u.extra);
            break;
        }
    }
    ...
}

为什么要多此一举呢?原因这样的:FTK是单线程的,GUI线程只负责用户界面的管理,由后台工作的线程负责长时间的操作。但是后台工作的线程经常需要更新用户界面,比如下载网络数据的线程要更新下载进度界面。FTK需要提供一种机制,让后台线程来更新用户界面但又不需要引入互斥机制。这可以通过idle来串行化对GUI的操作,后台线程要更新GUI时,就增加一个idle source,后台线程不能直接调用ftk_sources_manager_add,那样需要互斥机制,而且也不能唤醒主循环去处理这个idle。所以它通过Primary Source的管道发送一个事件,这个事件会唤醒睡眠中的主循环,然后调用Primary Source分发函数去处理事件。

现在我们来看ftk_main_loop_run的实现,ftk_main_loop_run的实现是平台相关的,对于支持select的平台,用 select是最好的方法。下面是基于select的实现:

1.找出最小等待时间和文件描述符

        for(i = 0; i < ftk_sources_manager_get_count(thiz->sources_manager); i++)
        {
            source = ftk_sources_manager_get(thiz->sources_manager, i);
            if((fd = ftk_source_get_fd(source)) >= 0)
            {
                FD_SET(fd, &thiz->fdset);
                if(mfd < fd) mfd = fd;
                n++;
            }
 
            source_wait_time = ftk_source_check(source);
            if(source_wait_time >= 0 && source_wait_time < wait_time)
            {
                wait_time = source_wait_time;
            }
        }

这里遍历所有source,找出一个最小的等待时间和要等待的文件描述符。

2. 等待事件发生

        tv.tv_sec = wait_time/1000;
        tv.tv_usec = (wait_time%1000) * 1000;
        ret = select(mfd + 1, &thiz->fdset, NULL, NULL, &tv);

3.检查事件源并调用相应的事件处理函数

            if( (ret > 0) && (fd = ftk_source_get_fd(source)) >= 0 && FD_ISSET(fd, &thiz->fdset))
            {
                if(ftk_source_dispatch(source) != RET_OK)
                {
                    /*as current is removed, the next will be move to current, so dont call i++*/
                    ftk_sources_manager_remove(thiz->sources_manager, source);
                    ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source);
                }
                else
                {
                    i++;
                }
                continue;
            }
            //这里处理timer和idle。
            if((source_wait_time = ftk_source_check(source)) == 0)
            {
                if(ftk_source_dispatch(source) != RET_OK)
                {
                    /*as current is removed, the next will be move to current, so dont call i++*/
                    ftk_sources_manager_remove(thiz->sources_manager, source);
                    //ftk_logd("%s:%d remove %p/n", __func__, __LINE__, source);
                }
                else
                {
                    i++;
                }
                continue;
            }

如果事件源处理函数的返回值不是RET_OK的事件,我们认为出错了或者是事件要求自己被移除,那就把它移除掉。

GUI是事件驱动的,创建一个窗口后,函数马上就返回了,窗口中的控件对用户事件处理是在以后的事件循环中进行的。这对于大多数情况是正常的,但有时我们需要用户确认一些问题,根据确认的结果做相应的处理。比如,用户删除一个文件,我们要确认他是否真的想删除后才能去删除。也就是在创建对话框后,函数不是马上返回,而且等用户确认,关闭对话框后才返回。

为了做到这一点,我们要在一个事件处理函数中,创建另外一个主循环来分发事件。模态对话框就是这样实现的:

int ftk_dialog_run(FtkWidget* thiz)
{
    DECL_PRIV1(thiz, priv);
    return_val_if_fail(thiz != NULL, RET_FAIL);
    return_val_if_fail(ftk_widget_type(thiz) == FTK_DIALOG, RET_FAIL);
 
    ftk_widget_show_all(thiz, 1);
    priv->main_loop = ftk_main_loop_create(ftk_default_sources_manager());
    ftk_main_loop_run(priv->main_loop);
    ftk_main_loop_destroy(priv->main_loop);
    priv->main_loop = NULL;
 
    return ftk_widget_id(ftk_window_get_focus(thiz));
}

对话模型提供了一个用于退出该主循环的函数:

Ret ftk_dialog_quit(FtkWidget* thiz)
{
    DECL_PRIV1(thiz, priv);
    return_val_if_fail(ftk_dialog_is_modal(thiz), RET_FAIL);
 
    ftk_main_loop_quit(priv->main_loop);
 
    return RET_OK;
}
]]> http://www.limodev.cn/blog/archives/1500/feed 5 - 嵌入式GUI FTK设计与实现-分层视图 >http://www.limodev.cn/blog/archives/1494 http://www.limodev.cn/blog/archives/1494#comments Mon, 22 Mar 2010 13:14:04 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1494 - - - - 为了从整体上把握FTK的架构,我们先看看FTK的分层视图:

ftk_layer_view

这里我们从下至上的来介绍一下各个组件的功能:

1.最下层是平台相关的适配层。它包括两个部分:

* 操作系统适配层:它主要是封装平台相关的函数,为上层提供统一的接口,比如mmap和pipe等。
* Backend: 主要是对输入/显示设备的抽象。像mouse/keyboard之类的输入设备和framebuffer的显示设备等。

2. 再上层是一些interface,这些interface为上层提供统一的接口,隔离了具体的实现细节,从提高了系统的可移植性和灵活性。比如图片解码器和字体引擎都可以根据实际情况进行配置。

3.再上层是窗口、窗口管理器和主循环等基础设施。

* 顶层窗口包括应用程序窗口,菜单条,状态栏和对话框几种,它们是放置各种控件的容器。
* 窗口管理器负责控制各个窗口的位置/大小和事件的分发。
* 主循环负责监听来自各个事件源的事件,如输入设备事件,定时器和idle等,然后调用相应的分发函数去处理这些事件。

4.再上层是控件的实现,如按钮、文本、进度条和列表框等等。这些大部分控件都是可以裁减的,开发者可以根据实际需求去掉一些不必要的组件。

5. 再上层是XUL,包括XML界面描述和脚本绑定。目前实现了lua的绑定,以后会增加其它脚本的绑定。

6.最上层是基于FTK的应用环境和平台。目前计划的有:

* 一个基本的桌面环境,包括桌面、Shell、文件管理器、音乐播放器和其它一些工具。
* PhoneME(J2ME)
* Webkit

]]> http://www.limodev.cn/blog/archives/1494/feed 1 - FTK在ben上运行起来了 >http://www.limodev.cn/blog/archives/1492 http://www.limodev.cn/blog/archives/1492#comments Sat, 20 Mar 2010 06:58:44 +0000 李先静 - - http://www.limodev.cn/blog/?p=1492 - - - - Qi Hardware是由一帮出色的hacker组成的团队,和他们老大Wolfgang Spraul 有过一面之缘,对他们也是很佩服的。呵,帮他们打个广告吧。另外,FTK已经在ben上运行起来了。

Hi,
Qi Hardware is proud to announce the availability of the first product
that tries to adhere to copyleft hardware standards:

Ben NanoNote

It’s a 99 USD Linux clamshell device, others have called it

“The $99 open hardware thingie” (CrunchGear)
“Dedicated Vi device” (The Register)
“tiny, hackable” (Engadget)
“totalmente libre” (viva linux!)
“adorabile 3 pollici Linux” (Netbook News)

(source: http://sharism.cc/voices/)

Get more detailed product information at http://sharism.cc/specs/
The Ben is available and in stock at an online store nearby you:

Europe (99 EUR): http://www.tuxbrain.com/
Belgium (99 EUR): http://hackable-devices.com/products/product/nanonote/
Netherlands (125 EUR): http://openmobile.nl
Germany (119 EUR): http://www.pulster.de
India (6500 INR): http://www.idasystems.net
USA/Canada/rest of the world (shipping from Hong Kong, 99 USD): http://nanonote.cc

Thanks for interest in our project, more background information about
what we do at http://en.qi-hardware.com
viva linux!
Wolfgang

]]> http://www.limodev.cn/blog/archives/1492/feed 0 - 关于变量的初始化 >http://www.limodev.cn/blog/archives/1490 http://www.limodev.cn/blog/archives/1490#comments Thu, 18 Mar 2010 23:26:10 +0000 李先静 - - - - http://www.limodev.cn/blog/?p=1490 - - - - 做了两年的调试工作后,我对没有初始化的变量引起BUG总是有种恐慌,所以习惯给任何变量都赋上一个初始值。今天一个新同事问到一个问题,int a[100] = {0};是初始化第一个元素还是所有元素?我说是所有的。我们看看下面这个例子:
#include <stdio.h>
#include <stdlib.h>
 
typedef struct _Book
{
    int isbn;
    char name[32];
    char author[32];
}Book;
 
int main(int argc, char* argv[])
{
    int a[100] = {0};
    Book b = {0};
 
    return 0;
}

反汇编出来:

    int a[100] = {0};
 80483a3:    8d 95 68 fe ff ff        lea    -0x198(%ebp),%edx
 80483a9:    b8 90 01 00 00           mov    $0x190,%eax
 80483ae:    89 44 24 08              mov    %eax,0x8(%esp)
 80483b2:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)
 80483b9:    00
 80483ba:    89 14 24                 mov    %edx,(%esp)
 80483bd:    e8 06 ff ff ff           call   80482c8 <memset@plt>
    Book b = {0};
 80483c2:    8d 95 18 fe ff ff        lea    -0x1e8(%ebp),%edx
 80483c8:    b8 44 00 00 00           mov    $0x44,%eax
 80483cd:    89 44 24 08              mov    %eax,0x8(%esp)
 80483d1:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)
 80483d8:    00
 80483d9:    89 14 24                 mov    %edx,(%esp)
 80483dc:    e8 e7 fe ff ff           call   80482c8 <memset@plt>

可以看到:
0×190 = sizeof(int)*100
0×44 = sizeof(Book)

即对整个变量做了清零动作。

呵,我不知道gcc之外的编译器是不是这样做的,反正建议大家在修改FTK时遵循一下这个原则。

]]> http://www.limodev.cn/blog/archives/1490/feed 6
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐