http://www.limodev.cn/blog/archives/category/gtk
《系统程序员成长计划》@DouBan: http://book.douban.com/subject/4722708/ 欢迎评论。
]]> http://www.limodev.cn/blog/archives/1524/feed 6 -《系统程序员成长计划》(china-pub首发) http://www.china-pub.com/196523
]]> http://www.limodev.cn/blog/archives/1497/feed 6 -前几天发现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 -FrameBuffer相关的组件
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 -相关组件
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 -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 -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 -从设置的应用程序中可以了解到,自动同步网络时间的选项只是修改了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 -硬件级的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 -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 -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 -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.
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:
Button:
Cairo:
Calculator:
CheckButton
ComboBox:
Cursor:
Desktop:
Dialog:
Entry:
Animation:
File Chooser:
OpenGLES:
FullScreen:
IconView:
ImageButton:
Input Method:
Label:
ListView:
Menu:
MessageBox:
Popup Menu:
Programs:
ProgressBar:
Text View:
Transparent:
WaitBox
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 -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 -以前写过一篇文章介绍如何在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
完整源代码和脚本请到这里下载:
svn checkout http://ftke.googlecode.com/svn/trunk/android-ftk-emu android-ftk-emu
无论是示例代码还是书中的BUG,请大家Report到http://code.google.com/p/spexamples/issues/list。
如果以后有机会出新版本,将有样书赠送给第一个发现BUG的朋友。
谢谢大家支持。
]]> http://www.limodev.cn/blog/archives/1533/feed 4 -过了几个月,我当了小组长,手下带了几位同事。有刚毕业的,有工作两年的,也有工作经验和我差不多的。有次我让一位同事写一个小程序,过了好几天他还没有写完。和他讨论了一会儿后,才知道他写的程序动不动就死掉,查了几天没有查出原因来。由于我以前做过两年全职的调试工作,看了一下他的代码,马上发现问题所在:指针声明之后,没有指向任何有效的内存,就直接使用了!
后来和他们交流多了,才发现有的同事工作几年之后,连一些编程的基本技能都没有掌握,代码写得乱七糟八的。这些代码集成到项目中后,产品越来越不稳定,整个项目慢慢失去控制。那个项目后来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 -要求:实现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下。
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.
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截图:
按钮:
单选/多选框:
下拉框:
对话框:
列表框:
进度条:
多行编辑器:
忙等待:
图标视图:
输入法:
单行编辑器:
全屏效果:
桌面:
计算器:
最近脸色有些苍白。
老婆还不太像全职师奶。
* 批处理程序是一步一步的执行,直到完成任务为止,完成任务后程序立即退出。
* 图形用户界面应用程序则是事件驱动的,它等待事件发生,然后处理事件,如此循环,直到用户要求退出为止。
两种执行模型如下图所示:
通常我们把等待事件/处理事件的循环称为主循环(MainLoop),主循环是GUI应用程序必要组件之一,FTK当然也离开不主循环 (MainLoop)。大多数嵌入式GUI都采用了Windows类似的主循环:
while(GetMessage(&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); }
我不太喜欢这种主循环,主要原因有两点:
* 它看似简洁,但内部实现并不简洁。它从当前线程的队列里获取消息,然后处理消息。这些消息从来哪里的?当然是由其它线程传递给当前线程的,这就意味着GUI需要多线程的支持。而FTK不想引入多线程来折磨自己,至少在GUI部分是不想的。
* 这是面向过程的处理方式。消息是一个对象,按照面向对象的思想来说,对象的数据和行为应该绑定在一起。而这里的消息是纯粹的数据,所有消息都由目标窗口的消息处理函数来处理的。FTK希望每类消息都有自己的处理函数,而不是全部由窗口来处理。
FTK采用了GTK类似的主循环:
ftk_run();
它看起来更简洁,内部实现也不需要多线程的支持。这里采用了POSA(面向模式的软件架构)中的Reactor模式,它的主要好处有:
* 用单线程处理多个事件源。
* 增加新事件源时不会影响事件分发的框架。
整个主循环由下列组件构成:
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; }
这里我们从下至上的来介绍一下各个组件的功能:
1.最下层是平台相关的适配层。它包括两个部分:
* 操作系统适配层:它主要是封装平台相关的函数,为上层提供统一的接口,比如mmap和pipe等。
* Backend: 主要是对输入/显示设备的抽象。像mouse/keyboard之类的输入设备和framebuffer的显示设备等。
2. 再上层是一些interface,这些interface为上层提供统一的接口,隔离了具体的实现细节,从提高了系统的可移植性和灵活性。比如图片解码器和字体引擎都可以根据实际情况进行配置。
3.再上层是窗口、窗口管理器和主循环等基础设施。
* 顶层窗口包括应用程序窗口,菜单条,状态栏和对话框几种,它们是放置各种控件的容器。
* 窗口管理器负责控制各个窗口的位置/大小和事件的分发。
* 主循环负责监听来自各个事件源的事件,如输入设备事件,定时器和idle等,然后调用相应的分发函数去处理这些事件。
4.再上层是控件的实现,如按钮、文本、进度条和列表框等等。这些大部分控件都是可以裁减的,开发者可以根据实际需求去掉一些不必要的组件。
5. 再上层是XUL,包括XML界面描述和脚本绑定。目前实现了lua的绑定,以后会增加其它脚本的绑定。
6.最上层是基于FTK的应用环境和平台。目前计划的有:
* 一个基本的桌面环境,包括桌面、Shell、文件管理器、音乐播放器和其它一些工具。
* PhoneME(J2ME)
* Webkit
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
#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更多推荐
所有评论(0)