2010年12月17日 星期五

Linux 下程序的記憶體映射

Standard
Linux 下的執行檔為 ELF 格式,其啟動原理與各作業系統上的執行檔一樣,不外乎是載入檔案到記憶體上,並讀取需要的 Shared library link 清單,最後找到於 Filesystem 上對應的 symbol link 和 library,載入外部函式和執行程式。

這邊以 VIM 為例,我們透過 Linux 下的 ldd 指令可以得知執行檔需要的外部 Libraries:
$ ldd /usr/bin/vim
 linux-gate.so.1 =>  (0xb7837000)
 libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb77f2000)
 libncurses.so.5 => /lib/libncurses.so.5 (0xb77b8000)
 libselinux.so.1 => /lib/libselinux.so.1 (0xb779c000)
 libacl.so.1 => /lib/libacl.so.1 (0xb7795000)
 libgpm.so.2 => /usr/lib/libgpm.so.2 (0xb778f000)
 libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7649000)
 libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7645000)
 /lib/ld-linux.so.2 (0xb7838000)
 libattr.so.1 => /lib/libattr.so.1 (0xb763f000)

由以上結果可以看到 VIM 所需的 library,以及在 filesystem 上找到的對應 library ,然後還可以得到當 library 被載入到記憶體上時的起始位置。其中比較特別的是 linux-gate.so.1,實體並不存在於 filesystem 中,這代表 kernel system call 的記憶體映射和其位址。

Kernel 其實有 Interface 提供我們取得更多 Process 的記憶體映射資料,藉由讀取 /proc/[Process ID]/maps 這檔案,我們除了可以得到 Process 連結的 library,還可以得到映射的範圍和使用權限等訊息,。

下面這例子是觀察正在執行的 VIM(PID=17605):
$ cat /proc/17605/maps 
08048000-081c0000 r-xp 00000000 08:05 7029211    /usr/bin/vim.basic
081c0000-081ce000 rw-p 00178000 08:05 7029211    /usr/bin/vim.basic
081ce000-081d8000 rw-p 00000000 00:00 0 
089df000-08b30000 rw-p 00000000 00:00 0          [heap]
b739a000-b73a4000 r-xp 00000000 08:05 4202534    /lib/i686/cmov/libnss_files-2.11.2.so
b73a4000-b73a5000 r--p 00009000 08:05 4202534    /lib/i686/cmov/libnss_files-2.11.2.so
b73a5000-b73a6000 rw-p 0000a000 08:05 4202534    /lib/i686/cmov/libnss_files-2.11.2.so
b73a6000-b73b9000 r-xp 00000000 08:05 4202536    /lib/i686/cmov/libnsl-2.11.2.so
b73b9000-b73ba000 r--p 00012000 08:05 4202536    /lib/i686/cmov/libnsl-2.11.2.so
b73ba000-b73bb000 rw-p 00013000 08:05 4202536    /lib/i686/cmov/libnsl-2.11.2.so
b73bb000-b73bd000 rw-p 00000000 00:00 0 
b73da000-b73f7000 r--p 00000000 08:05 7074661    /usr/share/vim/vim73/lang/zh_TW.UTF-8/LC_MESSAGES/vim.mo
b73f7000-b756d000 r--p 00000000 08:05 7045410    /usr/lib/locale/locale-archive
b756d000-b756e000 rw-p 00000000 00:00 0 
b756e000-b7572000 r-xp 00000000 08:05 4194576    /lib/libattr.so.1.1.0
b7572000-b7573000 rw-p 00003000 08:05 4194576    /lib/libattr.so.1.1.0
b7573000-b7574000 rw-p 00000000 00:00 0 
b7574000-b7576000 r-xp 00000000 08:05 4202526    /lib/i686/cmov/libdl-2.11.2.so
b7576000-b7577000 r--p 00001000 08:05 4202526    /lib/i686/cmov/libdl-2.11.2.so
b7577000-b7578000 rw-p 00002000 08:05 4202526    /lib/i686/cmov/libdl-2.11.2.so
b7578000-b76b8000 r-xp 00000000 08:05 4202537    /lib/i686/cmov/libc-2.11.2.so
b76b8000-b76ba000 r--p 0013f000 08:05 4202537    /lib/i686/cmov/libc-2.11.2.so
b76ba000-b76bb000 rw-p 00141000 08:05 4202537    /lib/i686/cmov/libc-2.11.2.so
b76bb000-b76be000 rw-p 00000000 00:00 0 
b76be000-b76c3000 r-xp 00000000 08:05 11665845   /usr/lib/libgpm.so.2.0.0
b76c3000-b76c4000 rw-p 00004000 08:05 11665845   /usr/lib/libgpm.so.2.0.0
b76c4000-b76ca000 r-xp 00000000 08:05 4194402    /lib/libacl.so.1.1.0
b76ca000-b76cb000 rw-p 00006000 08:05 4194402    /lib/libacl.so.1.1.0
b76cb000-b76e4000 r-xp 00000000 08:05 4194310    /lib/libselinux.so.1
b76e4000-b76e5000 r--p 00018000 08:05 4194310    /lib/libselinux.so.1
b76e5000-b76e6000 rw-p 00019000 08:05 4194310    /lib/libselinux.so.1
b76e6000-b76e7000 rw-p 00000000 00:00 0 
b76e7000-b771e000 r-xp 00000000 08:05 4194642    /lib/libncurses.so.5.7
b771e000-b7721000 rw-p 00036000 08:05 4194642    /lib/libncurses.so.5.7
b7721000-b7745000 r-xp 00000000 08:05 4202516    /lib/i686/cmov/libm-2.11.2.so
b7745000-b7746000 r--p 00023000 08:05 4202516    /lib/i686/cmov/libm-2.11.2.so
b7746000-b7747000 rw-p 00024000 08:05 4202516    /lib/i686/cmov/libm-2.11.2.so
b774a000-b7752000 r-xp 00000000 08:05 4202519    /lib/i686/cmov/libnss_nis-2.11.2.so
b7752000-b7753000 r--p 00008000 08:05 4202519    /lib/i686/cmov/libnss_nis-2.11.2.so
b7753000-b7754000 rw-p 00009000 08:05 4202519    /lib/i686/cmov/libnss_nis-2.11.2.so
b7754000-b775a000 r-xp 00000000 08:05 4202515    /lib/i686/cmov/libnss_compat-2.11.2.so
b775a000-b775b000 r--p 00006000 08:05 4202515    /lib/i686/cmov/libnss_compat-2.11.2.so
b775b000-b775c000 rw-p 00007000 08:05 4202515    /lib/i686/cmov/libnss_compat-2.11.2.so
b775c000-b7763000 r--s 00000000 08:05 1799068    /usr/lib/gconv/gconv-modules.cache
b7763000-b7764000 r--p 00175000 08:05 7045410    /usr/lib/locale/locale-archive
b7764000-b7766000 rw-p 00000000 00:00 0 
b7766000-b7767000 r-xp 00000000 00:00 0          [vdso]
b7767000-b7782000 r-xp 00000000 08:05 4194720    /lib/ld-2.11.2.so
b7782000-b7783000 r--p 0001a000 08:05 4194720    /lib/ld-2.11.2.so
b7783000-b7784000 rw-p 0001b000 08:05 4194720    /lib/ld-2.11.2.so
bff9b000-bffb0000 rw-p 00000000 00:00 0          [stack]

這些看似複雜且嚇人的訊息,每個欄位已清楚描述著此 process 的記憶體映射和對應的實體檔案,由於數量太多,我們暫且抽出 VIM 本身的映射訊息做說明:
08048000-081c0000 r-xp 00000000 08:05 7029211    /usr/bin/vim.basic
081c0000-081ce000 rw-p 00178000 08:05 7029211    /usr/bin/vim.basic

『08048000-081c0000』和『081c0000-081ce000』代表程式在記憶體上的起始點和終點,『r-xp』和『rw-p』是存取權限,『00000000』和『00178000』是映射記憶體位址在實體檔案中的偏移量,『08:05』分別代表的是 major 和 minor device number,而『7029211』是 device node number,最後一欄則為記憶體所對應的實體檔案。

此外,在範例中我們看到了幾乎每個程式和 library 都映射了兩個以上的區塊,這意味著第一段是 code segment,而第二,第三段映射是 data segment (data + bss + heap)。

後記

更多的驗證可以閱讀[The True Story of Hello World] 一文,作者透過簡單的 Hello World 程式驗證 segment 和 memory mapping 的觀念,值得參考。

2010年12月12日 星期日

寫支 C 程式用 XRandr Extension 設定你的螢幕解析度

Standard
在 X11 下取得螢幕顯示大小,最快的方式就是得到當前的 Screen ,然後用 DisplayWidth() 和 DisplayHeight() 得到寬高,如果是在高階的圖形介面架構下開發應用程式,如:GTK+,也有相關的 API 可以取得 display 的大小。然而,要是我們想取得更多關於螢幕和顯示晶片所支援的模式,便要引入 XRandr Extension 來達成。

若不了解 XRandr 是什麼,可以開啟終端機(Terminal)程式,接著輸入 xrandr 指令,就會看到顯示器的相關訊息,除了可以得到有幾種螢幕可以用,還分別可以看到支援的模式(解析度和掃描頻率),以下是在筆者 X200t (有外接一台支援 1920x1080 的 24" 顯示器)上執行的結果:
$ xrandr
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 8192 x 8192
VGA1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 531mm x 299mm
   1920x1080      60.0*+   60.0     60.0  
   1440x900       59.9  
   1280x800       59.8  
   1152x864       75.0  
   1024x768       70.1     60.0  
   800x600        60.3     56.2  
   640x480        66.7     60.0  
   720x400        70.1  
LVDS1 connected (normal left inverted right x axis y axis)
   1280x800       60.0 +
   1024x768       60.0  
   800x600        60.3     56.2  
   640x480        59.9  
HDMI1 disconnected (normal left inverted right x axis y axis)
DP1 disconnected (normal left inverted right x axis y axis)
HDMI2 disconnected (normal left inverted right x axis y axis)
DP2 disconnected (normal left inverted right x axis y axis)
DP3 disconnected (normal left inverted right x axis y axis)
註:Xrandr 除了得到關於當前顯示器的設定之外,也可以設定現在的模式,更多功能可以參考『xrandr --help』的說明。

當然,XRandr Extension 還支援旋轉畫面、多螢幕顯示等控制,不過不在本文討論範疇。這裡,我們要用 C 語言先來實作一支程式,用 XRandr API 得到目前解析度以及列出顯示器支援的所有模式。

listmode.c 完整程式碼:
#include <stdio.h>
#include <X11/X.h>
#include <X11/extensions/Xrandr.h>

void main()
{
    XRRScreenSize *sizes;
    XRRScreenConfiguration *sc;
    int nsize;
    short rate;
    Rotation current_rotation;
    SizeID size_id;

    int i;

    Display *dpy = XOpenDisplay(NULL);
    int screen = DefaultScreen(dpy);
    Window root = RootWindow(dpy, screen);

    /* Get XRandr Screen Configuration */ 
    sc = XRRGetScreenInfo(dpy, root);

    /* Get all Modes of Screen */
    sizes = XRRConfigSizes(sc, &nsize);
    printf("nsize: %d\n", nsize);

    /* Get Current Rate */
    rate = XRRConfigCurrentRate(sc);
    printf("rate: %d\n", rate);

    /* Get  current mode ID and rotation config */
    size_id = XRRConfigCurrentConfiguration(sc, ¤t_rotation);
    printf("size id: %d\n", size_id);

    for (i = 0; i < nsize; i++) {
        if (size_id == i)
            printf("%d %dx%d *\n", i, sizes[size_id].width, sizes[size_id].height);
        else
            printf("%d %dx%d\n", i, sizes[i].width, sizes[i].height);
    }
}

編譯:
gcc -o listmode listmode.c -lX11 -lXrandr

執行結果(在筆者電腦上 1920x1080 60Hz 為當前的解析度模式):
$ ./listmode
nsize: 8
rate: 60
size id: 0
0 1920x1080 *
1 1440x900
2 1280x800
3 1152x864
4 1024x768
5 800x600
6 640x480
7 720x400

若要更進一步更改和設定模式,可在範例程式碼最後加上 RRSetScreenConfigAndRate():
XRRSetScreenConfigAndRate(dpy, sc, root, (SizeID)0,
    current_rotation, rate, CurrentTime);
註:其中 SizeID 型態指的是模式的編號,同列表中最前面的編號,可擇一使用。

2010年12月8日 星期三

Mandice Superted - Non-GoogleTV Web Browser for TV Device

Standard
由於一些合作廠商們不願意使用 GoogleTV 或 Android 做 TV Devices,最近實作了 Connected TV 專用的網頁瀏覽器(Web Browser) - 『Superted』。他們選擇不走 Google 之路有很多因素,像是 Google 雖有遠大願景,但當前還不夠成熟,所以目前為止 Android 在 Connected TV 上,還未有立即性的應用程式擴充價值。其次,中國大陸的 PPStream Content 和山寨市場之大,讓華人市場不接受只有 Youtube 的 GoogleTV。再來,就是跨平台開發成本與硬體驅動的議題。這也是為什麼我們會被要求寫一個『比較適合』在 TV Device 上使用的網頁瀏覽器,當然,廠商們的聖旨也說要有大又明顯的 UI,又或者是與 GoogleTV 雷同。

延續過去的 Webkit 網頁瀏覽器實驗品,加上了 3D User Interface 的支援,便完成了 Superted 的原型:



後記

影片中是使用 Acer AspireOne(Intel Atom N270) 第一代 netbook 做功能演示,也已經在 VIA platform 上順利測試過。