2007 開工了!
新的 2007 年,「Jollen's Blog」也會把重點放在「Linux kernel」與「Linux device driver」;期待您的意見與指教。
|
« December 2006 | (回到Blog入口) | February 2007 » January 2007 歸檔January 1, 20072007 開工了!新的 2007 年,「Jollen's Blog」也會把重點放在「Linux kernel」與「Linux device driver」;期待您的意見與指教。
January 2, 2007Process Creation, #2:Running a "User Process"Process Creation 在討論「Process Creation」議題時,首先要了解的就是「Linux 的三種 Process」。同樣是「執行中的程式(process)」,但是依其「特性(design perspective)」區分的話,可以歸納為以下三種:
其中「user process」就是我們此系列日記所要介紹的對象。User process 是很單純的一種 process,簡單來說,以下二種「執行程式的方式」就是 user process:
那麼 user process 是怎麼執行的呢?嗯,先前我們提過的日記「Process Creation, #1:由 shell 執行外部程式《基本觀念與範例》」便介紹了這樣的觀念。 Process Creation:Running a Program 接著,我們就以一張圖來展示「在 shell 模式執行外部程式(running a program)」的流程。
這樣就很清楚了:
至於「idle process」與「kernel threads」則不適用此圖! *1 即外部程式:Linux 的執行檔為 ELF 的格式,所以稱其為「ELF image」
January 4, 2007一個防止程式被玩耍的小技倆今天在分享自己實作上的經驗時,聊到「如何防止程式被人家玩了!」。我們所要實現的想法很簡單,就是設法防止「執行檔」被 Linux 下的「標準程式工具」給把玩了!例如:
我分享了一個簡單的方法,這個方法在以往「Linux Systems Programming」的課或多或少也曾向同學介紹過;不過,大家要知道的是,這只是一個有趣的小東西,或者說是一個「小手段」,任何高手級的 Linux 玩家,大概只要不到一小時就能輕易 反擊這個做法。 實測 就拿 "tar" 指令來看,以下是正常的操作: # objdump -d /bin/tar /bin/tar: file format elf32-i386 Disassembly of section .init: 08049734 <.init>: 8049734: 55 push %ebp 8049735: 89 e5 mov %esp,%ebp 8049737: 83 ec 08 sub $0x8,%esp 804973a: e8 b1 07 00 00 call 0x8049ef0 804973f: e8 0c 08 00 00 call 0x8049f50 8049744: e8 93 b7 01 00 call 0x8064edc 8049749: c9 leave 804974a: c3 ret Disassembly of section .plt: ... 把 "tar" 做「處理」後,這些標準的工具全都失效了。先用 Jollen 提供的小工具處理 /bin/tar: # ./truncate_it /bin/tar > ./tar.trunc # chmod a+x tar.trunc 再執行 objdump 試試: # objdump -d tar.trunc objdump: tar.trunc: File truncated 連 nm、gdb 等,也都失效了: # nm tar.trunc nm: tar.trunc: File truncated [root@mail tmp]# gdb tar.trunc GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"..."/tmp/tar.trunc": not in executable format: File truncated (gdb) r Starting program: No executable file specified. Use the "file" or "exec-file" command. 但是程式是可以正常使用的,不會有什麼問題: # ./tar.trunc --help|more GNU `tar' saves many files together into a single tape or disk archive, and can restore individual files from the archive. Usage: ./tar.trunc [OPTION]... [FILE]... Examples: ./tar.trunc -cf archive.tar foo bar # Create archive.tar from files foo a nd bar. ./tar.trunc -tvf archive.tar # List all files in archive.tar verbo sely. ./tar.trunc -xf archive.tar # Extract all files from archive.tar. If a long option shows an argument as mandatory, then it is mandatory for the equivalent short option also. Similarly for optional arguments. ... 基本上,「執行時期都正常」,但是「程式的操作工具都失效」是這個小技巧能達成的效果。反過來想,既然 run-time 是正常的,那麼也就難逃 run-time 除錯工具的荼毒了。 下載程式 做法與原理是相當簡單的,只要知道「如何活用 ELF 規格」,箇中奧妙就不難懂了。大家可以下載 Jollen 寫的小程式「Truncate It」,這是一個執行檔,下篇日記我會介紹「Truncate It」的原理並提供 source code。使用方法如下:
記得 chmod 成可執行: # chmod a+x tar.trunc 然後可以在「目前的目錄下」得到 tar.trunc 執行檔。 truncate_it 可以針對 ELF 的執行檔做一些小動作,並產生一個新的執行檔(結果輸出到 stdout)。被 truncate_it 處理過的檔案「大部份」都能正常執行,並且無法使用 GNU binutils 或是 gdb 對他做作何動作。 January 5, 2007應用在 Embedded Linux 場合的 Busybox 有了 "CONFIG_DESKTOP"Busybox 於 2006-12-14 釋出 1.3.0(stable)版,並於 2006-12-27 釋出 1.3.1(stable)更新。不過,吸引我的並不是這個平凡的版本更新資訊,而是看到以下這段文字: This release has CONFIG_DESKTOP option which enables features needed for busybox usage on desktop machine. 'CONFIG_DESKTOP' 的出現,讓原本是應用在 Embedded Linux 場合的 Busybox,也開始支援 Desktop Linux 的應用了。於是,我心裡浮現出以下的感想... Busybox 原先的設計理念是基於「簡化的系統工具」與「簡單化的指令集」,並朝 footprint 的目標而發展;當初由於 target device 硬體環境的貧乏(如 RAM 只有 4Mbytes),因此這種做法不但合情合理,並且也很「直覺」(make sense)。 現在,target device 硬體端技術的進步,讓 Embedded Linux 與 Desktop Linux 之間的界線越來越模糊。「根本上的定義」,Embedded Linux 是最小化的 Linux 系統,於是我們要學會 root filesystem 的建置,並儘最大力氣把它做到最小(down size);如今,最小化,或是對尺寸的敏感性,在一些 Embedded Linux 的應用場合上,都不再是重點,甚致可以用「精簡大小不 make sense」來回應。 當 Embedded Linux 隱約等於 Desktop Linux 時,根本上的 Embedded Linux 技能(也就是建置最小化的 root filesystem)仍然是必備的基本能力,但是許多「Auto Build」的環境才是實用的做法;在這樣的前提下,所謂的 Embedded Linux 可能需要再重新定義。如果由「技術上的定義」來解釋,BSP 與週邊界面可能才能叫做「基本能力」,例如:如何 porting 某 SOC NAND flash controller 的 Linux 驅動程式,至於 root filesystem 的話,「就交給滑鼠左鍵吧」。 Linux 的 Virtual Memory Areas(VMA):基本概念介紹由 user process 角度來說明的話,VMA 是 user process 裡一段 virtual address space 區塊;virtual address space 是連續的記憶體空間,當然 VMA 也會是連續的空間。VMA 對 Linux 的主要好處是,可以記憶體的使用更有效率,並且更容易管理 user process address space。 從另一個觀念來看,VMA 可以讓 Linux kernel 以 process 的角度來管理 virtual address space。Process 的 VMA 對映,可以由 /proc/<pid>/maps 檔案查詢;例如 pid 1(init)的 VMA mapping 為: $ cat /proc/1/maps 08048000-0804e000 r-xp 00000000 08:01 12118 /sbin/init 0804e000-08050000 rw-p 00005000 08:01 12118 /sbin/init 08050000-08054000 rwxp 00000000 00:00 0 40000000-40016000 r-xp 00000000 08:01 52297 /lib/ld-2.2.4.so 40016000-40017000 rw-p 00015000 08:01 52297 /lib/ld-2.2.4.so 40024000-40025000 rw-p 00000000 00:00 0 40025000-40157000 r-xp 00000000 08:01 58241 /lib/i686/libc-2.2.4.so 40157000-4015c000 rw-p 00131000 08:01 58241 /lib/i686/libc-2.2.4.so 4015c000-40160000 rw-p 00000000 00:00 0 bfffe000-c0000000 rwxp fffff000 00:00 0 列表中的欄位格式如下: start-end perm offset major:minor inode image Linux 以 struct vm_area_struct 資料結構來紀錄每一「區塊」的 VMA 資訊(include/linux/mm.h):
struct vm_area_struct 裡有 3 個欄位,用來來維護 VMA 資料結構:
VMA 的實作主要是為了能更有效率地管理記憶體,並且是基於 paging 系統之上所發展出的;VMA 是比原始 paging 理論更高階的記憶體管理方法。 January 8, 2007Process Creation, #3:sys_fork《基本觀念》fork() 是「Process Creation」議題的重要里程碑:提到 system call 代表著我們的研究要正式進入 kernel space 的層面了。 值得附帶一提的是,sys_fork 是一個 machine-dependent 的 system call,以 i386 為例,其實作位於 linux/arch/i386/kernel/process.c。*1 Operating System 端的觀念 sys_fork 是非常重要的 system call,從作業系統的角度來解釋的話,這就是「建立 process」的主要 system call。當外部程式(即 ELF image)被使用者鍵入指令後,shell 便會呼叫 fork() 系統呼叫,並透過作業系統的 fork system call 來產生新的 process,以執行此外部程式。這種類型的 fork 也稱做 spawn。 可參考先前 Jollen 所分享的「Process Creation」前二則日記,以便了解 spawn、fork、exec 等等觀念:
強烈建議您先閱讀以上二則日記,並了解 user-space 端的 process 觀念後,再繼續往下學習,才能更容易「感受」fork system call 的觀念。 Linux 端的觀念 Linux 並沒有 spawn system call,根據「Process Creation」日記的解說,Linux 下的 fork + exec 等於 spawn。而其中最核心的觀念則是在於 fork 的實作,也就是「如何產生新的 process」。 Linux 以 sys_fork 或 sys_clone 來產生新的 process,而這二個 system call 最後都會呼叫到 do_fork() 函數,do_fork() 是 Linux 主要的 fork-routine。繼續探討 do_fork() 時,便會接觸到以下二個重要議題:
關於更深入的 fork(),我們就先在此打住,下篇日記《核心實作》再做介紹。以下是 sys_fork() 與 sys_clone() 的程式碼(Linux 2.6.17.7::arch/i386/kernel/process.c):
do_fork() 的函數原型: /* * Ok, this is the main fork-routine. * * It copies the process, and if successful kick-starts * it and waits for it to finish using the VM if required. */ long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr); clone_flags 是 do_fork() 觀念的核心,也是很有趣的議題。
Qt Centre Programming Contest 2007:與一些自己的小想法Qt Centre(The Ultimate Qt Community)釋出一則消息:「Qt Centre Programming Contest 2007」。嘿,Qt Centre 也和幾個 partners(這裡面當然有 Trolltech)辦起 Qt4 的程式設計比賽了。 社群手法 先前曾提到的「Embedded Linux 2006 十大回顧!」都是很「具體」的事件,不過若大環境來看,絕對可以加上一條「開放源碼的社群經營手法在 2006 年展現氣勢。」大家可以思考 Linux mobile 的爆炸性成長過程,與其策略手法,甚致是 IBM 的幾個 case study,便能了解「社群手法」的重要性。 因為形成了這樣的 ecosystem,因此更能促進 open source 運動的發展,這絕對是好事一樁,。 回歸正題 2007 年的開放源碼世界,除了「Linux mobile」會持續躍進外,「community」的經營手法當然也會是本年度的重點戲。 每次看到一些消息,腦筋都不免會跳脫常軌,出現一些奇怪的聯想。還是回到主題來吧。Qt4 的比賽當然不會是一個「解題(problem solving)」的比賽,看一下他的「Guidelines on how to win the contest」,特別強調的是: 1. idea。關鍵你的 idea 要「大」。 我覺得這幾個 keywords 是很不錯的,可以比較一般化的把這幾個想法應用在 Embedded Linux 專案計畫中。例如:code quality 方面,「self-explainable variable names」是基本功;又如,在「dependencies」方面,因為是 Embedded Linux 開發,限制程式人員所能使用的 library 與 application 則是合理的。 January 9, 2007「Truncate It」小技倆的原始碼與原理先前提到的「一個防止程式被玩耍的小技倆」中,Jollen 提供了一個稱為「Truncate It」的小工具,我把他的原始碼放在此處「http://tw.jollen.org/elf-programming/truncate_it.tar.bz2」,有興趣的朋友可下載回家把玩。 用法請參考前一則日記的介紹,另外,「Truncate It」只是一個「呈現概念的原型」,並未良好的 coding,除了結果是輸出到 stdout 外,現階段也只能處理 IA32 的 ELF image。 Truncate It 的原理 Truncate It 的原理相當簡單,我只是把「Section Header Table」等資訊由 ELF image 中移除,因此「標準工具」便無法處理 ELF image;這是由於 GNU binutils 的工具都是以 linking view 來解讀 ELF image 之故,若要正常反組譯「truncated ELF image」,就要以 execution view 的角度來解讀執行檔。 此外,Section Header Table 在 execution view 時是 optional 的,可參考 Jollen 先前的日記「ELF(Executable and Linking Format)格式教學文件, #1: ELF 簡介」。 以下以一個操作流程來說明 Truncate It 的原理: 1. 如下圖(objdump)。 2. 把前半段的內容切出來,存成 hello.trunc。 # dd if=hello of=hello.trunc bs=1308 count=1 0x51c 等於十進位 1308。其中,.bss section 的長度為 4,但是 0x51c 並不需要再加上 4,原因請參考 Jollen 的「BSS Section Concepts: .bss section 的基本觀念介紹專欄」。 此外,.bss section 在一般情況下都會是「strip 後的最後一個 section」。.bss section 不佔實體檔案空間,會有 .bss section 的存在,主要的原因是「因為當初 C 語言標準所種下的因。」 January 10, 2007.bss section:C 語言所種下的因由於當初 C 語言標準提到「未初始化的全域變數(un-initialized global variables)其初始值為零(zero)」,所以得到的結果便是「程式執行時,必須將未初始化的全域變數都初始化成零」。 Linux 針對這種狀況的解決方式是「配置 zeroed pages 給 .bss section」,因此 un-initialized global variables 的值(value)便會為零。 註:Un-initialized global variables 會被編譯器放到 .bss section,可參考 Jollen 的「BSS Section 觀念教學」專欄。 此外,global variable 被初始化為零時,也會被 GCC 放到 .bss section 裡。所以,以下的寫法: int foo; 會等於: int foo = 0; 以上二種寫法都會讓 foo 被放到 .bss section。由此可知,以下二種狀況,變數都會被放在 .bss section: 1. 當 global variable 未被初始化時; -fno-zero-initialized-in-bss 如果(但是確實有這種應用場合)我們不想讓 variable 被放到 .bss section 呢?做法有二。第一種方式是「傳統做法」,程式設計師只要將 global variable 初始化為「非零」值即可,舉以下程式為例: #include <stdio.h> 將此程式編譯: $ gcc -O2 -o bss bss.c 利用 objdump 來觀察後,會發現 foo 變數被 GCC 放到 .data section 裡了。這種做法是基於 coding 時的做法。 第二種做法是 GCC 3.4.x 後所支援的「-fno-zero-initialized-in-bss」最佳化選項。將程式修改如下: #include <stdio.h> 請特別留意,global variable 仍要做初始化,所以「foo」一定要做「assignment」為零值的動作。將程式編譯: $ gcc -fno-zero-initialized-in-bss -O2 -o bss bss.c GCC 會把 foo 放到 .data section。同樣可利用 objdump 來觀察。 良好的 Embedded Linux 程式寫作習慣 了解以上的觀念後,我要來說明一個重要的觀念。由於某些特定應用,或是 target 需要將變數放在 .data section 裡,因此若是 Embedded Linux 的應用,建議應對全域變數做初始化,例如: int x; 應將這種傳統 C 的寫作習慣調整為: int x = 0; 良好的習慣養成,未來將會得到許多好處。 這種寫法並非傳統 C 語言所講的「多此一舉」,應該以 Linux systems software 的角度來思考:這種寫法便能搭配 GCC 的「-fno-zero-initialized-in-bss」或是「-fzero-initialized-in-bss(預設)」選項,來決定 global variable 要放到 .bss section 或是 .data section。 January 11, 2007Process Creation, #4:sys_fork《核心實作》接續前一記日記的觀念:「Linux 以 sys_fork 或 sys_clone 來產生新的 process,而這二個 system call 最後都會呼叫到 do_fork() 函數,do_fork() 是 Linux 主要的 fork-routine。」 將此觀念以實作角度來說明的話,do_fork() 必須要做的工作便是「copy 原來的 process 成為另一個新的 process」。Linux 的 sys_fork() 內部實作就是以「copy process」的方式來實作。也就是說,當 user program 呼叫 fork() wrapper function 後,sys_fork() 便會「copy」原來的 process,以得到一個新的 process。 由此觀念的推導,我們便能了解到,sys_fork() 的內部實作關鍵便是: 1. 如何 copy process。 2. 要 copy process 的「哪個部份」? 這二個關鍵,都是相當值得玩味的題目,同時,透過探討「copy process」的核心實作,我們也可以強化「process address space」的觀念。以下先將 sys_fork() 的內部流程先大略 trace 一遍後,再討論「copy process」的主題;而「copy what」則會在「clone()」的專欄裡再做介紹。 首先,sys_fork() 與 sys_clone() 都呼叫到 do_fork routine。以下是 do_fork() 的原始碼:
我把重要的地方用紅色字體標示出來,一開始,我們必須先了解此部份的實作: 1. 宣告一個 process descriptor。 2. 要求一個 PID 給新的 process 使用。 3. 呼叫 copy_process(),以複制出新的 process。 由此可知,Linux kernel 的 copy_process() API 是重要的「process creation」API。 接著,把 copy_process() 的原始碼 trace 出來:
copy_process() 程式碼有點多,這裡只先列出其函數原型。 看到 copy_process() 的第一個參數 clone_flags,這個參數一開始是由 sys_fork() 或是 sys_clone() 所傳遞進來的,並且 copy_process() 會根據 clone_flags 來決定「copy what」。 那麼我怎麼知道 clone_flags 有哪些值?這個部份定義在 <linux/sched.h> 標頭檔裡,以下是 clone_flags 的 bitwise 值定義: /* * cloning flags: */ #define CSIGNAL 0x000000ff /* signal mask to be sent at exit */ #define CLONE_VM 0x00000100 /* set if VM shared between processes */ #define CLONE_FS 0x00000200 /* set if fs info shared between processes */ #define CLONE_FILES 0x00000400 /* set if open files shared between processes */ #define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */ #define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */ #define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */ #define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */ #define CLONE_THREAD 0x00010000 /* Same thread group? */ #define CLONE_NEWNS 0x00020000 /* New namespace group? */ #define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */ #define CLONE_SETTLS 0x00080000 /* create a new TLS for the child */ #define CLONE_PARENT_SETTID 0x00100000 /* set the TID in the parent */ #define CLONE_CHILD_CLEARTID 0x00200000 /* clear the TID in the child */ #define CLONE_DETACHED 0x00400000 /* Unused, ignored */ #define CLONE_UNTRACED 0x00800000 /* set if the tracing process can't force CLONE_PTRACE on this clone */ #define CLONE_CHILD_SETTID 0x01000000 /* set the TID in the child */ #define CLONE_STOPPED 0x02000000 /* Start in stopped state */ /* * List of flags we want to share for kernel threads, * if only because they are not used by them anyway. */ #define CLONE_KERNEL (CLONE_FS | CLONE_FILES | CLONE_SIGHAND) 以 sys_fork() 的實作來看:
呼叫 fork() wrapper function 時,並無法讓 user 自行定義 clone flags;因此,「在學會 clone() 函數的用法前」,其實可以先暫時跳過 clone flags 這個部份。 到這裡是 sys_fork() 內部實作的 trace,雖然我們了解到 clone flags 的作用,但是由於 sys_fork() 並不指定此參數,所以先不討論 clone flags。不過,我們的 sys_fork() trace 功課還沒完成,下一篇日記將會是「Process Creation, #5:copy process」。 以上 kernel trace,皆使用 Linux 2.6.17.7 原始程式碼。
January 13, 2007Nano-X 程式設計, #3:顯示圖片(image.c)程式範例 image.c 是以 hello.c 為基礎,加上顯示圖片的功能。透過 image.c 我們可以學到以下的 Nano-X 程式設計方法: ˙ 如何使用嵌入式圖片 由檔案讀取圖片檔並顯示顯示圖片是一般常見的做法,這裡我們所要實作的範例是希望可以將圖片直接嵌入程式裡,而不是由外部檔案讀取。 如何使用嵌入式圖片 要將圖片嵌入於程式裡,首先必須將圖片轉換成數值資料形式的 C 程式。Nano-X 提供一個檔名為 convbmp 的工具來將 BMP 格式的圖片轉換成 C 程式。 convbmp工具的原始程式位於 src/mwin/bmp/convbmp.c,這是提供給 Microwindows API 使用者的工具,因此我們在設定 Nano-X 編譯選項時,除了勾選 Nano-X API 外,還要勾選 Microwindows API 選項才能產生 convbmp 執行檔。編譯後可以在 src/bin/ 目錄下找到 convbmp,我們手動將此工具安裝到 /usr/bin/目錄下,以方便我們使用:
先將取得的圖檔轉換成 BMP 的格式,再利用 convbmp 轉換成 C 程式。例如,我想轉換圖檔 jollen.bmp,那麼將圖檔轉換成 C 程式的指令就是:
圖(jollen.bmp) 轉換後便會得到 jollen.c。接著我們再修改 hello.c 將圖片顯示於視窗上。 如何將圖片顯示於視窗上 因為圖片資料屬於外部變數,因此先在程式裡宣告外部圖片變數:
image_jollen 是一個陣列,存放圖檔的 pixel 資料,此陣列由 convbmp 轉換後產生,詳見 jollen.c 程式。接著,在處理 GR_EVENT_TYPE_EXPOSURE 事件的地方呼叫 GrDrawImageBits() 函數畫出圖片即可:
編譯時別忘了與 jollen.c 程式做連結,這個部份可以寫一個簡單的 Makefile rule來完成: mage: image.o jollen.o $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ 以下是 image.c 的完整程式,粗體字是新加入的程式碼。
注釋
Nano-X 程式設計, #4:設定 Window Manager(wm.c)wm.c 以 image.c 為範本,wm.c 執行後在畫面上只會看到圖片圖(沒有視窗標題列與邊框),並且可使用滑鼠來拖曳圖片。本範例主要在展示以下的 Nano-X API 程式設計方法: ˙ 如何設定視窗屬性(window manager)。 要寫出「托曳圖片」的功能,首先必須把視窗的標題列與邊框去除,否則只能在標題列上托曳整個「視窗」。做法非常簡單,只要在建立視窗時設定 window manager 即可: wid = GrNewWindowEx(GR_WM_PROPS_APPWINDOW |
GR_WM_PROPS_NODECORATE |
GR_WM_PROPS_NOAUTOMOVE,
NULL,
GR_ROOT_WINDOW_ID,
0, 0,
image_jollen.width, /* 圖片寬度 */
image_jollen.height /* 圖片高度 */,
0xFFFFFF);
粗體字的地方是新加入的 window manager 屬性,為了達到我們的要求,我們指定了3個屬性值:
由於我們並未指定 GR_WM_PROPS_CAPTION,因此不會有視窗標題列。 在處理使用者互動上,我們需要自行處理3個滑鼠事件:
因此選擇事件的程式碼應修改成: GrSelectEvents(wid, GR_EVENT_MASK_MOUSE_POSITION |
GR_EVENT_MASK_BUTTON_UP |
GR_EVENT_MASK_BUTTON_DOWN);
接下來,要特別注意的地方是「顯示圖片的時機」。我們在這裡設計成在視窗顯示後、進入 event loop 前處理。首先是顯示圖片的程式寫法: GrMapWindow(wid); GrDrawImageBits(wid, gc, 0, 0, &image_jollen); 接著,修改 event loop,並加上處理以上 3 個事件(粗體字部份)的程式碼:
對於滑鼠按鍵的處理方式為:當滑鼠被按下時,便記錄滑鼠的新座標,然後將視窗移到最上層;若此時使用者移動滑鼠,則將整個「視窗」移到新座標位置。如此一來,使用者就會看到 「整個圖片被托曳」的效果。 處理滑鼠按鍵的程式寫法如下:
先判斷目前所產生的事件是否為 GR_EVENT_TYPE_BUTTON_DOWN;如果是,才記錄新座標,並將 button_down 設為 1。若不是
GR_EVENT_TYPE_BUTTON_DOWN 事件,表示滑鼠按鍵已放開,此時將 button_down 設為 0。
先判斷 button_down 是否為 false,若 button_down等於0,表示滑鼠按鍵是放開的,因此不做任何動作。反正,若 button_down 為true,代表滑鼠按鍵仍「持續」按住,此時才能呼叫 GrMoveWindow() 移動視窗。 以下是 wm.c 的完整程式,粗體字是新加入或修改過的程式碼。
注釋
January 14, 2007Process Creation, #5:copy_process()Before We Start Trace Linux kernel 時,有幾個相當重要的原則要掌握住:
以本日記為例,我們想要了解 Linux kernel 如何產生新的 process,而其中的關鍵便是 copy_process() 函數。但是,目前我們在做的是 sys_fork() 的 trace,而 sys_fork() 並不指定任何的 clone_flags 參數值,因此,在 trace copy_process() 的過程中,我們可以先行略過與 clone_flags 有關的特定處理。 copy_process() 以下是 copy_process() 的完整實作,我把現階段可略過的部份標示為灰體字。
有許多沒有標成灰色字體的程式片斷,其實也應該先行省略不看,像是:資料結構的操作、spinlock、錯誤處理、變數初始化等等。 copy_process() 的關鍵在哪裡? Trace 到這裡後,我會先就程式碼本身的實作,節錄「深入 sys_fork() 底層」相關的實作片斷。以下供您參考: 1. 新的 process description: struct task_struct *p = NULL; 2. "dup" current 成為 p: p = dup_task_struct(current); if (!p) goto fork_out; 3. copy_process() 會判斷目前的 process 數是否過多: if (nr_threads >= max_threads) goto bad_fork_cleanup_count; max_threads 是在 fork_init() 階段算出來的,可參考「Jollen 的 Linux 核心分享包,#3: fork_init()《講義6》」。 4. 開始 "copy" current 給新的 process: if ((retval = copy_semundo(clone_flags, p))) goto bad_fork_cleanup_audit; if ((retval = copy_files(clone_flags, p))) goto bad_fork_cleanup_semundo; if ((retval = copy_fs(clone_flags, p))) goto bad_fork_cleanup_files; if ((retval = copy_sighand(clone_flags, p))) goto bad_fork_cleanup_fs; if ((retval = copy_signal(clone_flags, p))) goto bad_fork_cleanup_sighand; if ((retval = copy_mm(clone_flags, p))) goto bad_fork_cleanup_signal; if ((retval = copy_keys(clone_flags, p))) goto bad_fork_cleanup_mm; if ((retval = copy_namespace(clone_flags, p))) goto bad_fork_cleanup_keys; retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespace; 作業還沒完 解析出精華的目的,當然是為了做「更深入」且「有效率」的研究。我用我的學習方法,呈現「Process Creation」系列專欄的推導過程;希望我的做法對您是真正有幫助的。 在繼續進行前,必須了解幾個基礎知識。Keyword 如下:
「Process Creation」系列專欄到此告一段落,不過「作業」仍會完成,並不是就此結束。我會起另外一個專欄,來把剩下的 hacking 功課完成。
January 15, 2007Linux 的 Virtual Memory Areas(VMA):Process 與 VMA 整體觀念要了解 VMA(Virtual Memory Area)的「整體觀念」,最好的方式就是圖解說明。下圖說明了 process 與 VMA 的整體觀念。
圖:Process 與 VMA 整體觀念 Memory Descriptor Linux 的「Process Descriptor」資料結構為 struct task_struct(include/linux/sched.h)。Process descriptor 裡的 mm field 紀錄了 process 的 VMA 資訊:
struct mm_struct 即是 Linux 提供的「Memory Descriptor」資料結構,以下是 struct mm_struct 的原型宣告:
Memory descriptor 故名思義,是用來描述 process 記憶體資訊的資料結構。由 struct mm_struct 裡可以看到一個稱為 mmap 的 field,mmap 的 data type 為 struct vm_area_struct,這個資料結構即是我們在「Linux 的 Virtual Memory Areas(VMA):基本概念介紹」所介紹的 VMA 資料結構。 VMA 與 ELF Image 的對映關係 在「Linux 的 Virtual Memory Areas(VMA):基本概念介紹」曾經介紹過,Process 的 VMA 對映,可以由 /proc/<pid>/maps 檔案查詢;例如 pid 1(init)的 VMA mapping 為: $ cat /proc/1/maps 08048000-0804e000 r-xp 00000000 08:01 12118 /sbin/init 0804e000-08050000 rw-p 00005000 08:01 12118 /sbin/init 08050000-08054000 rwxp 00000000 00:00 0 40000000-40016000 r-xp 00000000 08:01 52297 /lib/ld-2.2.4.so 40016000-40017000 rw-p 00015000 08:01 52297 /lib/ld-2.2.4.so 40024000-40025000 rw-p 00000000 00:00 0 40025000-40157000 r-xp 00000000 08:01 58241 /lib/i686/libc-2.2.4.so 40157000-4015c000 rw-p 00131000 08:01 58241 /lib/i686/libc-2.2.4.so 4015c000-40160000 rw-p 00000000 00:00 0 bfffe000-c0000000 rwxp fffff000 00:00 0 列表結果便能用來說明 VMA 與 ELF image 之間的關係。搭配上圖來說明列表結果的 VMA 對映關係,如下:
另外,要留意的是,在文中所指的 code section 與 data section 不見得就是 ELF 的 .text section 與 .data section;我們以 code section 來表示所有可執行的節區,以 data section 來表示包含資料的節區。 在整個 VMA 的討論過程中,我們只針對 code section 與 data section 做討論(如圖),至於 .bss section 的話,原則上另案來討論其核心實作會比較實際一些。
January 16, 2007Shared Memory 的 Race Condition今天在討論基本的 shared memory 機制時聊到,shared memory 有同步性的問題(synchronization),主要的原因是 Linux 並未對 shared memory 做同步的控制。以下單純由 user-space programming 的角度來探討此觀念。 首先,試作以下 3 個小程式:
以上程式可由 [http://tw.jollen.org/ipc-programming/shm_race.tar.bz2] 下載。以下是操作方法:
先執行 shm_allocate 配置 shared memory,並任意填入一個初始字串,程式會印出此 shared memory 的 Segment ID。接著:
再執行 shm_read,命令列參數加上 shared memory 的 Segment ID。shm_read 會持續不斷的讀取 shared memory 的內容。然後,在另一個 terminal 執行 shared memory 的寫入程式。 我們透過 shm_write 寫入 [00000000,11111111,22222222,...,99999999] 字串到 shared memory,並觀察輸出。由於 shm_write 寫入 shared memory 的資料是「相同數字的字串」,因此,若觀察到以下的結果,表示 shm_read 與 shm_write 間存在 race condition 問題:
因此,使用 shared memory 做為 IPC 機制時,必須實作同步的演算法。由演算法層面來討論,我們區分以下 3 個層面的探討:
可此可知,當「只有有人寫入 shared memory,便要考慮 race conditon 問題」。若由演算法層面來思考此問題,以下是相對較為單純簡單的做法:
有機會的話,再跟大家分享朋友寫的 code。 January 17, 2007Embedded Linux 測試:Bootstrap root filesystem(x86)階段《程式執行測試》本文是單純實作時的測試方法說明,「Bootstrap root filesystem」的做法、步驟、概念與隱含的觀念請自行參考相關文件。 前情提要:由 Busybox 開始 這個階段的實作「提要」如下。 我們到 Busybox 官方網站下載原始碼:http://busybox.net/downloads/busybox-1.3.1.tar.bz2。如果您是初次「把玩」open source package,必須了解一個重要的概念:對任何的 open source 套件而言,以下二份文件是必讀的:
大多數的套件都包含以上二個檔案,並且許多重要的資訊都寫在這二份文件檔裡。將 Busybox 套件解開後,先檢查以下的 utility 是否有勾選:
並且將安裝路徑(install prefix)設定到事先建立的 root filesystem 空目錄。接著進行編譯,在編譯過程中可能會產生一些錯誤,請先把產生錯誤的選項取消,試著將 Busybox 編譯出來後並安裝至 root filesystem 目錄下。 我要怎麼測試此階段 root filesystem 的正確性? 假設 root filesystem 放在 /tmp/busybox_project/rootfs 目錄下,那麼直接用 chroot 來做第一次測試是不錯的方式。指令如下:
若出現以下訊息,表示此 root filesystem 是無法運作的:
以下是正確無誤的畫面: # chroot /tmp/busybox_project/rootfs/ /bin/sh BusyBox v1.3.1 (2007-01-17 14:42:55 CST) Built-in shell (ash) Enter 'help' for a list of built-in commands. # ls bin lib linuxrc sbin usr 進到 shell 模式後,就可以跑 root filesystem 裡的應用程式了。這種測試方式,主要是對 root filesystem 裡的應用程式進行執行測試,主要的目的可能有:
常見原因 此階段產生錯誤的原因可能有:
這種測試方式,可能無法真正有效測試到 init process 階段的問題,因此變更 root 根目錄後的第一個執行程式應該指定為 shell。 如果您想練習 chroot 測試,必須下載 Busybox 並自行建構基本的 root filesystem。我也提供了此階段的成果檔案 [http://tw.jollen.org/root-filesystem/busybox_project_001.tar.bz2],以方便您練習 chroot 用法。 |