Email me: jollen # jollen.org

more: Jollen 的 Embedded Linux 教育訓練

« October 2006 | (回到Blog入口) | December 2006 »

November 2006 歸檔

November 1, 2006

玩 FreeDOS 1.0

FreeDOS 在今年 9 月時釋出了重大的里程(big milestone)版本 "1.0"。FreeDOS 是一個 "complete", "free", "100% MS-DOS compatible" 的 operating system,今天心血來潮,把她裝起來玩了一下,其實還真有點懷念呢。


fdos2.jpg
懷念的滋味:西冒號斜線大於。

fdos1.jpg
DOOM!

Ragel:狀態機編譯器

Ragel 是一個 "State Machine Compiler",Ragel 可以把狀態機(regular expression)編譯成 C/C++/Objective-C/D 程式碼。Ragel 除了可以用來快速撰寫分析文件的程式外,用來學習 Automata 也是很好的工具。

Ragel 的官方網站:http://www.cs.queensu.ca/~thurston/ragel/

November 2, 2006

Preemptive Process Scheduling 的觀念

先前我們在介紹 sys_getppid 時,跳掉了一段 code 如下:

#if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT)
{
   ...
}
#endif

這段 code 在判斷 kernel 是否有 'CONFIG_SMP'(多處理器)與 'CONFIG_PREEMPT'(可搶先的 process 排程)。目前 kernel 2.6 在各種處理器平臺已經支援 preemptive process scheduler,當然也包含了 i386 處理器。

這個 kernel 的設定位於 arch/i386/Kconfig,我們節錄 Kconfig 內容如下:

config PREEMPT
        bool "Preemptible Kernel"
        help
          This option reduces the latency of the kernel when reacting to
          real-time or interactive events by allowing a low priority process to
          be preempted even if it is in kernel mode executing a system call.
          This allows applications to run more reliably even when the system is
          under load.
Say Y here if you are building a kernel for a desktop, embedded or real-time system. Say N if you are unsure.

這段 code 與 CONFIG_PREEMPT 的用意非常清楚:提供 preemptive process scheduling 的能力,我們對 Linux kernel preemptive process scheduling 的處理方法相當感興趣。

在進一步研究與 scheduling 有關的 system call 與 'CONFIG_PREEMPT' 前,有必要先來複習一下什麼是 "preemptvie"。

Preemption

一個重要的工作是了解「preemptive」的觀念。

當 process 進入執行狀態(running state)時,排程器(scheduler)會去檢查進入該 process 優先序(priority),若該 process 的 priority 比目前執行中的 process priority 高,Linux 便會搶先(preemptive)執行該 process,因此原本的 process 便被中斷(interrupt)。

這種 high priority process 把 low priority process 執行權了活生生搶走的機制便是 preemptive process scheduling;因此我們說,Linux 的 process 是 "preemptive"!Preemption(搶奪)的排程,目前在 Linux 2.6 的核心裡已經有不錯的支援了。

更多 Linux Preemptive Scheduling

1. Linux 的 scheduler 包含 preemptive 的實作。
2. Linux kernel 本身是 nonpreemptvie。我們在這裡指的 "preemptive" 是指 "process preemptive"。所以當 process 是在 User Mode 時才能被搶先。
3. Process preemptvie(可被搶先因此中斷):process 除了在 time-slice 用完時被中斷外(典型案例),在某些狀況下也是可以被中斷的。例如,前面提到的優先序問題便是一例。
4. 被中斷的 process 仍然是 running state(因為只是被搶先而已)。

Linux/Unix 作業系統核心是 nonpreemptive 的實作,這樣的作業系統設計較簡單,並且可免除許多核心同步(kernel synchronization)的問題。

Preemptive Scheduling

以下是節錄自作業系統教科書裡,對於可搶先班程的說明[1]:

CPU scheduling decisions may take place when a process -

1. Switches from running to waiting state.
2. Switches from running to ready state.
3. Switches from waiting to ready.
4. Terminates.

Scheduling under 1 and 4 is nonpreemptive.


這裡有一份 preemption 的名詞解釋:http://www.netrino.com/Publications/Glossary/Preemption.html

[1] A. Silberschatz, P. Galvin, and G. Gagne, Applied Operating System Concepts, First Edition, John Wiley & Sons, Inc. (2000).

KVM 驅動程式的 HOWTO

之前提到過 Kernel 2.6 的 KVM 驅動程式現身了,今天看到 KVM 驅動程式的 HOWTO 已經上線了。KVM 的官方網站是:http://kvm.sourceforge.net/

裡頭除了有 HOWTO 外,也有 mailing list 可以加入了。KVM 的驅動程式除了有 kernel-space 的 driver 外,也包含一個 user-space 的 library,以及給 QEMU 的 patch。

November 4, 2006

目前的「Linux System Calls' Forum」與「Jollen 的 Linux 核心分享包」專欄進度報告。

「Linux System Calls' Forum」與「Jollen 的 Linux 核心分享包」是二個相互配合的專欄,因此相關的日記彼此之間是具備相依性與次序的。建議的閱讀順序是「依照日記的時間(即發佈順序)」來閱讀「Linux System Calls' Forum」與「Jollen 的 Linux 核心分享包」。這是這二個系統日記的閱讀建議。

目前的狀況報告

對於 Linux system call 的討論,我們已經來到了 scheduler 的門口了,要深入了解 Linux scheduler 的設計原理與相關理論前,Jollen 還必須多做一點功課才行。到目前為止,我們所討論過的幾個 system call 如下。

跟上我們的腳步:請讀以下的文章,再看這篇日記!

「Jollen 的 Linux 核心分享包」已發佈二則日記如下。

跟上我們的腳步:請讀以下的文章,再看這篇日記!

另外,Jollen 在每一個 system call 的討論都點出幾個基本的主題,希望大家能大致了解這目前「Linux System Calls' Forum」裡出現的幾個主題後,再繼續往下看「Linux System Calls' Forum」論壇。

後續進度

這篇日記完成後,緊接著的就是「Linux System Calls' Forum, #7:(第157號系統服務)sys_sched_getscheduler」,後然緊接的是「Jollen 的 Linux 核心分享包,#3」了,請大家多多指正!

November 5, 2006

Linux System Calls' Forum, #7:(第157號系統服務)sys_sched_getscheduler

「作業系統的排程器(scheduler)提供好幾種排程演算法,並在不同的應用場合,或是不同的 process 採取適合的排程策略(policy)。」

Linux 的 process descriptor 資料結構為 struct task_struct,我們最早開始談論與 process 有關的 system call 時,就看過這個用來描述 process 的資料結構。struct task_struct 裡頭的 policy 欄位便是用來描述該 process 的排程策略。

與排程有關的系統呼叫

整理與 scheduling 有關的幾個 system call 如下(可參考「Linux 2.6 的 System Call:12 大類」),另外 Jollen 也把到目前為止已介紹過的 system call 加上紅色註記。

號碼 System Call 名稱 Manipulation

34

sys_nice 關於 struct task_struct->nice 欄位。

154

sys_sched_setparam 關於 struct task_struct->rt_priority 欄位。

155

sys_sched_getparam 關於 struct task_struct->rt_priority 欄位。

156

sys_sched_setscheduler 關於 struct task_struct->policy 欄位。(這是下一個要讀的)

157

sys_sched_getscheduler 關於 struct task_struct->policy 欄位。

158

sys_sched_yield 暫不討論。

159

sys_sched_get_priority_max 根據 'policy' 傳回 'rt_priority' 所允許的最大 priority 值。

160

sys_sched_get_priority_min 根據 'policy' 傳回 'rt_priority' 所允許的最小 priority 值。

161

sys_sched_rr_get_interval 若 policy 為 SCHED_RR(Round Robin policy),則傳回 time quantum(time slice)值。

241

sys_sched_setaffinity 這是 Linux 2.6 新增加的 system call。

242

sys_sched_getaffinity 這是 Linux 2.6 新增加的 system call。

「'sys_sched_getscheduler' 便是用來取得 process 排程策略(policy)的 system call。」

「'struct task_struct->policy' 是今天要了解的主題。」

我們想要思考一個問題「作業系統的教科書提到的排程演算法中,Round Robin 是我們知道現在的分時作業系統(eg. Linux)所採用的策略,所以我想了解 Linux 的 process descriptor 裡,是哪一個 field 在紀錄這件事情。」

struct task_struct 的 policy 欄位

這個問題還挺簡單的,因為直接看 struct task_struct 幾乎就能猜到是哪一個 field,再用谷歌找資料來印證我們的設假;另外,看「Understanding the Linux Kernel」也寫的很清楚。

struct task_struct {
	...
	unsigned long policy;
	...
};

接著再回頭來看今天的主題,sys_sched_getscheduler 的實作(Linux 2.6.11+):

/**
 * sys_sched_getscheduler - get the policy (scheduling class) of a thread
 * @pid: the pid in question.
 */
asmlinkage long sys_sched_getscheduler(pid_t pid)
{
	int retval = -EINVAL;
	task_t *p;  // jollen: 'typedef struct task_struct task_t;'

	if (pid < 0)
		goto out_nounlock;

	retval = -ESRCH;
	read_lock(&tasklist_lock);
	p = find_process_by_pid(pid); // 參照說明 1.
	if (p) {
		retval = security_task_getscheduler(p);
		if (!retval)
			retval = p->policy; // 參照說明 2.
	}
	read_unlock(&tasklist_lock);

out_nounlock:
	return retval;
}

取出重點部位來看:

  1. 取得指定 PID 的 process descriptor。常常在一些說明文件看到「若 PID 為 0,則取得 current 的...」,不過這裡的實作好像看的不太清楚,沒關係等一下再來往下看 code。
  2. 傳回 policy 欄位。

很簡單的將 sys_sched_getscheduler 看了一下,並且了解到 policy 欄位的用途。

find_process_by_pid

把 find_process_by_pid() 的實作拿出來看:

/**
 * find_process_by_pid - find a process with a matching PID value.
 * @pid: the pid in question.
 */
static inline task_t *find_process_by_pid(pid_t pid)
{
	return pid ? find_task_by_pid(pid) : current;
}

發現一件很重要的是,也是之前都沒提過的。

「若 PID 為 0,則傳回 current,否則傳回 PID 為 pid 的 process 之 process descriptor。」

別忘了,current 的 data type 也是 'struct task_struct'。

Linux 排程策略:policy 欄位的值

Linux 2.6 提供的排程策略如下:

/*
 * Scheduling policies
 */
#define SCHED_NORMAL		0
#define SCHED_FIFO			1
#define SCHED_RR			2
#define SCHED_BATCH		3

定義在 include/linux/sched.h。順帶把 Linux 2.4 提供的排程策略也節錄一下:

/*
 * Scheduling policies
 */
#define SCHED_OTHER		0
#define SCHED_FIFO			1
#define SCHED_RR			2

November 8, 2006

「Mobile 2.0 的思考」與第一隻採用 OpenMoko 的 Linux Smartphone

這隻手機是由台灣的大眾電腦所開發(真是一個好消息),原始的報導在 LinuxDevices.com 上,詳情可點閱這篇文章「Cheap, hackable Linux smartphone due soon」。

Neo1973(代號:FIC-GTA001) 是「completely open, Linux-based, GPS-equipped, quad-band GSM/GPRS phone」,沒錯,這是一隻「PHS 手機」。預計上市的時間是今年(2006)的 12 月中,並在明年的 1 月份銷售。

Neo1973 採用 Samsung S3C2410 平臺,並且是第一隻採用「OpenMoko」平臺的 Linux 智慧型手機,此外,更採用「apt-get-lke」的 software manager。不管正著看還是反著看,都很符合 Linux 玩家的習慣(與期望)。

非常期待他的問世,到時一定要去帶一隻回家「hack hack」一番。

Sean Moss-Pultz(Neo1973 的 PM,大眾電腦),在 Amsterdam(阿姆斯特丹)所舉行的「Open Source in Mobile」 conference 上發表了一篇演說,內容是有關於「OpenMoKo」這個 Linux smartphone 專用的 application framework 與即將推出的 Neo1973 Linux 智慧型手機。這篇演說的後半段提到一個 Linux 智慧型手機的 ecosystem(生態)觀念,他稱之為「Mobile 2.0」。

這場演講提出一個讓人充滿無限想像空間的「Mobile 2.0」概念,假使以使用者與互動為主的 WWW 是 Web 2.0,那麼將來以使用者與互動為主的 mobile phone 不就是「Mobile 2.0」。

如果 Linux 是自由開放的國度,使用者是 Mobile 2.0 的核心價值,那麼未來 Linux Smartphone 哪需要與微軟較勁?

最近這一年,可以陸續看到許多 Linux 智慧型手機出場在大眾市場,而當大家只看到 Linux 智慧型手機聲斯力竭地與微軟較勁時,Mobile 2.0 的觀點著實另人興奮。

突然發現到,我好像已經加入 Moss-Pultz 的 ecosystem 一員了,顯然我是這個系統的 end user。這個互動來自於「open hacking」的思維邏輯,近年來似乎被不少的國際大廠巧妙地應用著。(突然一個唸頭閃過,昨天參加的一場 roadshow 不就也是如此!)

 

(圖片來源:http://www.linuxdevices.com/news/NS2986976174.html)

Neo1973 其實是採取「dual-OS」的產品策略,因此她也能執行 Microsoft Windows Mobile。不過大家最期待的「Open Source」節錄一小段原始報導的說明如下:

Because OpenMoKo consists exclusively of open-source software, the Neo1973 will ship with a limited feature set, including a dialer (image at left), unified SyncML-enabled email/text messaging client, phonebook, (image at right), and media player, according to Moss-Pultz. However, many additional open source applications will be available through "feeds," including "certified" ones from FIC, as well as those from commercial and community sources, he adds.

還有更棒,Neo1973 採用 OpenEmbedded 來讓使用者增加、移除與更新套件,OpenEmbedded 裡頭已經包含了數以「千」計的套件,透過 bitbake 來建立 Linux distribution,讓愛好者與玩家都能透過簡單的指令來實做「Embedded Linux to Devices」。

Neo1973 的 hardware 規格也是很棒的,整理如下:

1. Samsung S3C2410 SoC

2. 128MB RAM

3. 64M flash

4. 64MB MicroSD

5. 2.8" touchscreen

6. Assisted GPS

7. quad-band GSM/GPRS module

Moss-Pultz 的 presentation 可以在這裡找到:http://www.linuxdevices.com/files/article072/sld002.html

OpenMoko 的「official site」是:http://www.openmoko.com/press/index.html。在這上面有完整的新聞稿與簡報 PDF 檔可以下載。

Revision, 2006/11/09, 01:13PM.

November 9, 2006

Jollen 的 Linux 核心分享包,#3: fork_init()《講義6》

我們知道,在 start_kernel() 的開機過程中,會呼叫許多初始化核心的常式。其中 fork_init() 是用來初始化 kernel 的"fork" 環境的。"fork()" 是用來產生 child process 的 system call,當 parent process 想要執行外部程式時,會先 fork child process,接著 child process 再利用 exec system call 自己的空間取代為外部程式。

fork_init.jpg

從講義第 5 頁開始,我們把問題做分割,找出與 Scheduling 有關的初始化流程來做研讀,因為我們想要以 Scheduling 做為深入 Linux kernel 的第一門課。

講解 fork_init()

1. Kernel 能 fork 的 process 數是依據 physical memory 的大小來決定的,我們看到 fork_init(unsigned long mempages) mempages 即是用來計算 max_threads 的參數。

2. 看到 mempages 參數,這個參數是由 start_kernel() 呼叫 fork_init() 時傳入的:

asmlinkage void __init start_kernel(void)
{
   ...
   fork_init(num_physpages);
   ...
}

3. 找一下 num_physpages,原來是 kernel 的 global symbol 啊,這個地方在 mm/memory.c

unsigned long num_physpages;
...
EXPORT_SYMBOL(num_physpages);

我們的想法是,目前還沒讀到有關 Memory Manager 與 mm 初始化的主題,所以還不如先暫時把這個地方當黑盒子,再做一次分割。因此,num_physpages 是怎麼來的就先放到「TODO」清單裡了。

4. 計算 max_threads 需要以下二個常數:

a. THREAD_SIZE:dependent on architecture

b. PAGE_SIZE:dependent on architecture

這二個常數都是平臺相依的,我把他們都標示在投影片上。這是在 processor-level 的 porting 就要處理掉的。

5. 再來,如同程式碼所述:至少要能產生 20 個 thread 系統才能開機。所以限定 max_threads 的最小值是 20。

另外,我們會發現有些與 "memory" 或 "signal" 有關的 code,目前比較好的做法是先放到「TODO」。希望我們這裡的思考邏輯與陳述方式對大家「閱讀核心」能有一點幫助。

跟上我們的腳步:請先行閱讀以下的文章,再看這篇日記!

November 12, 2006

Linux Link TEch Show 的訪談:理查史都曼談 GPLv3

我們知道前陣子、這陣子,有關 GPLv3 的議題真的是「有點火」熱。大家對 GPLv3 都有不同的看法與意見,甚致連 Linus 也都跑出來表示意見。

日前 Linux Link Tech Show 對理查史都曼(Richard Stallman)做了一場訪談,主題是談論有關 GPLv3 的修改目的。這場訪談是以聲音檔的方式提供下載,有 oggMP3 二種格式可取得。

找個時間一定要仔細聽聽這個訪談,畢竟 GPLv3 很可能會決定開放源碼解決方案的大未來。

November 13, 2006

Novell 宣佈釋出 Mono 1.2:Linux 執行 .NET 程式的解決方案

前幾天大家或許都看到 Microsoft/Novell 宣佈要作作開發 Linux 軟體的新聞了。就在 2 天前,Novell 在 Microsoft 的 TechEd Developers Conference & Expo(Barcelona Spain)宣佈推出 Mono 1.2。

Mono 是一套可以讓 Linux 與 Unix 使用者執行 Microsoft .NET 程式的軟體,在 LinuxWatch 的報導中提出 1.2 版是一個重要的里程碑,節錄原始報導如下:

Mono 1.2 enables Linux and Unix users to use Microsoft .NET code and applications. This new version, which brings performance improvements and support for Windows Forms, is seen as an important milestone toward compatibility with the .NET Framework 2.0. Other enhancements in the latest release include virtual machine upgrades, enhanced Java support, better memory consumption, stability improvements, and support for more .NET 2.0 features.

Mono 1.2 的新突破是可以讓 Microsoft 的 UI(user interface)更容易轉移到 Linux 平臺上。Mono Project 的官方網站是:http://www.mono-project.com/Main_Page,現在已經可以下載到 Mono 1.2 了。

對 Linux 應用軟體開發的朋友,可以了解一下這個重要的專案計畫。

November 14, 2006

Sun Microsystem 釋出 GPLv2 Java 實作:與我的有感而發

Sun Microsystems 在今天宣佈,將要以 GPLv2 釋出他的 Java 實作。

這二年可以看到非常多的廠商或是專案加入 open source community 的陣營,而 Sun 的 Java 實作超過 10 年的發展,過程真是非常的精采,最終也提出 GPLv2 的版本,有點小意外,但我想可能也不出大家所預期的可能結果。

Sun 的 Java 實作最終還是選擇以 GPLv2 釋出新版本,並且也不意外地引發「Is Java's move to GPL too late?」的討論。在這 10 幾年的精采發展過程中,Java 也曾回拒 open source 的想法與建議,最終仍然無法抵擋一個潮流與趨勢。

與其說 Sun 是在無計可施的局面下選擇 open source,我覺得還不如說這是 open source 這個「生態環境(ecosystem)」的成形、成熟與狀大,讓人「不可不為」。當然這是我自己的看法。

對 open source 生態系統最早「覺醒」,並且也因為認可這個 ecosystem 而得好處的經典案例就是「IBM」了,而今年也能嗅到 Linux mobile phone 生態環境的成形,想必未來 2 年 Linux mobile phone 是大有可為的。因此,假如 Sun 不想在 Linux mobile phone 的歷史缺席,就算不解或無奈,也只能聰明的加入已成形的生態系統裡。

IBM 擁有許多作業系統的專利,透過智財保護,並進行授權或 royalty 似乎是天經地義的做法,但是這位老大哥選擇並且認真加入 open source 生態環境的運作,最後得到許多好處與回饋,也讓人見識到 ecosystem 讓人不可不為的威力。IBM 的 PowerPC 也選擇了 open source community 的系統,只不過 IBM 比 Sun Microsystems 更有智慧,我想這是未來將會被更熱烈討論的主題(ecosystem of open source community)。

把這個好消息看待得太嚴肅了,對 developer 來說,又多了一項新奇有趣的東西,值得期待!

以下是這則新聞的主要閱讀列表:

Sun GPLs Java, http://www.linux-watch.com/news/NS4348361333.html.
Sun GPLs Java, targets mobile phones, http://www.linuxdevices.com/news/NS6857451192.html.

November 15, 2006

深入淺出 insmod, #1

作者/陳俊宏
http://www.jollen.org

這篇文章的目的是為了解釋 Linux 驅動程式課程中,經常被詢問的部份,我把這個地方以紅色來標示:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

是的,就是 *owner 這個 field 常被問起。*owner 的用途倒底是什麼?(以下以 kernel 2.4.x 為主做說明)

insmod 基本觀念

我用流程的方式來說明。不過在這之前,先請第 128 號系統呼叫 sys_init_module 出場。sys_init_module system call 的主要用途是載入 kernel module,以下是他的原型宣告

long sys_init_module(const char *name_user, struct module *mod_user);

在這裡我們看到第 2 個參數 *mod_user 就是此行的重點 struct module。另外,sys_init_module 系統呼叫透過 glibc 的 init_module() system call 來叫用。以下是 INIT_MODULE(2):

NAME
       init_module - initialize a loadable module entry

SYNOPSIS
       #include <linux/module.h>

       int init_module(const char *name, struct module *image);

DESCRIPTION
       init_module loads the relocated module image into kernel space and runs
       the module's init function.

       The module image begins with a module structure and is followed by code
       and data as appropriate.  The module structure is defined as follows:

另外,INIT_MODULE(2) 提到「 This system call is only open to the superuser.」,因此 insmod 只能以 root 身份執行。insmod 的原始碼包含在 modutils 套件裡,insmod 實作呼叫 sys_init_module system call 的程式位於 util/sys_nim.c,函數名稱為 sys_init_module,程式碼內容簡單易懂,節錄如下:

#ifndef CONFIG_USE_SYSCALL

extern int init_module(const char *name, const struct module *info);

int
sys_init_module(const char *name, const struct module *info)
{
  return init_module(name, info);
}

#else

#define __NR_sys_init_module  __NR_init_module
_syscall2(int, sys_init_module, const char *, name,
          const struct module *, info)

#endif

請了解這裡的 sys_init_module 是一個 modutils 自行實作的函數,用來提供給 insmod 指令呼叫,與 kernel 的 sys_init_module system call 是二個不同的東西。insmod 指令的原始碼位於 modutils 套件裡的 insmod/insmod.c,我們已經了解到,insmod.c 會呼叫 util/sys_nim.c:sys_init_module() 來載入 kernel module。

insmod 的程式碼中,載入 kernel module 的工作主要是由 init_module() 來執行,我們透過 init_module() 的原始碼來說明幾件事件:

static int init_module(const char *m_name, struct obj_file *f,
		       unsigned long m_size, const char *blob_name,
		       unsigned int noload, unsigned int flag_load_map)
{
	struct module *module;
	struct obj_section *sec;
	void *image;
	int ret = 0;
	tgt_long m_addr;

	sec = obj_find_section(f, ".this");
	module = (struct module *) sec->contents;
	m_addr = sec->header.sh_addr;

	module->size_of_struct = sizeof(*module);
	module->size = m_size;
	module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0;

	sec = obj_find_section(f, "__ksymtab");
	if (sec && sec->header.sh_size) {
		module->syms = sec->header.sh_addr;
		module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p);
	}
	if (n_ext_modules_used) {
		sec = obj_find_section(f, ".kmodtab");
		module->deps = sec->header.sh_addr;
		module->ndeps = n_ext_modules_used;
	}
	module->init = obj_symbol_final_value(f, obj_find_symbol(f, "init_module"));
	module->cleanup = obj_symbol_final_value(f,
		obj_find_symbol(f, "cleanup_module"));

	sec = obj_find_section(f, "__ex_table");
	if (sec) {
		module->ex_table_start = sec->header.sh_addr;
		module->ex_table_end = sec->header.sh_addr + sec->header.sh_size;
	}
	sec = obj_find_section(f, ".text.init");
	if (sec) {
		module->runsize = sec->header.sh_addr - m_addr;
	}
	sec = obj_find_section(f, ".data.init");
	if (sec) {
		if (!module->runsize ||
		    module->runsize > sec->header.sh_addr - m_addr)
			module->runsize = sec->header.sh_addr - m_addr;
	}
	sec = obj_find_section(f, ARCHDATA_SEC_NAME);
	if (sec && sec->header.sh_size) {
		module->archdata_start = sec->header.sh_addr;
		module->archdata_end = module->archdata_start + sec->header.sh_size;
	}
	sec = obj_find_section(f, KALLSYMS_SEC_NAME);
	if (sec && sec->header.sh_size) {
		module->kallsyms_start = sec->header.sh_addr;
		module->kallsyms_end = module->kallsyms_start + sec->header.sh_size;
	}
	if (!arch_init_module(f, module))
		return 0;

	/*
	 * Whew!  All of the initialization is complete.
	 * Collect the final module image and give it to the kernel.
	 */
	image = xmalloc(m_size);
	obj_create_image(f, image);

	if (flag_load_map)
		print_load_map(f);

	if (blob_name) {
		int fd, l;
		fd = open(blob_name, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
		if (fd < 0) {
			error("open %s failed %m", blob_name);
			ret = -1;
		}
		else {
			if ((l = write(fd, image, m_size)) != m_size) {
				error("write %s failed %m", blob_name);
				ret = -1;
			}
			close(fd);
		}
	}

	if (ret == 0 && !noload) {
		fflush(stdout);		/* Flush any debugging output */
		ret = sys_init_module(m_name, (struct module *) image);
		if (ret) {
			error("init_module: %m");
			lprintf("Hint: insmod errors can be caused by incorrect module parameters, "
				"including invalid IO or IRQ parameters.\n"
			        "      You may find more information in syslog or the output from dmesg");
		}
	}

	free(image);

	return ret == 0;
}

重要的觀念如下:

1. struct module 是「module descriptor」,kernel 的 module manager 以 module descriptor 來維護 kernel module。我們把 struct module 稱為 module object

2. 看藍色的部份,運算子的左邊。init_module() 負責「setup module object」。

3. 看紅色的部份,module object 的內容主要是讀取自 kernel module object file 裡的特定 section。

4. 最後是綠色的部份,讀完 object file 裡的特定 section,並 setup 好 module object 後,就呼叫 sys_init_moddule() 來載入 module object 至 kernel-space。

了解主要的觀念與流程後,再來就是重要的小結了!

insmod 整體觀念

insmod 指令使用 sys_init_module 系統服務,將  kernel module 載入到 kernel 的 address space。例如,載入 hello.o 的命令為:

# insmod hello.o

要了解 kernel module 的載入過程,必須由 insmod 的原始碼開始討論。 這裡只是一開始,所以沒有對程式碼的細節做太多介紹,當然這是後面才需要做的功課。

關於 *owner 的小結

透過以上的說明,我們就可以來回答「*owner 的用途倒底是什麼?」的問題了。

*owner 的 data type 是 struct module,即 module object 的資料結構。在整個 kernel module 的載入過程中,module object 是由 insmod 指令所設置,並且是代表 kernel module object file。

所以,*owner 是 kernel module 的觀念,與「Linux 驅動程式」沒有關係。fops 才是Linux 驅動程式的觀念。

最後,為什麼 fops 裡要有 *owner 呢?答案就很明顯了:Linux 驅動以 fops->owner 來表示「我這個驅動程式是屬於(is owned)那一個 kernel module」

關於更多 *owner,我們可以想到的是,驅動程式的 usage count 是紀錄在 module object 裡,而不是 file operation(思考為什麼),所以當我們對 usage count 做 increment/decrement 時,就需要 module object。

另外,關於寫 Linux 驅動程式時,「要不要管 *owner」的答案也是很清楚了,基本上,我們可以不用理會這個 field,但是如果要給 *owner 欄位值的話,根據 Linux kernel 的說明,我們只要這麼寫即可:

static struct file_operations xxx_ops = {
	owner:		THIS_MODULE,
	...
};

THIS_MODULE 的定義是(include/linux/module.h):

#define THIS_MODULE (&__this_module)

__this_module 是一個外部符號,用來表示「這個驅動程式所屬的 module object」。如果是分層的驅動程式,那麼這個 field 就要填。

課堂上我們再以 busmouse.c / logibusmouse.c 來說明這個部份。

Also See

 

November 16, 2006

MontaVista 推出 Dev Rock 5 嵌入式 Linux 開發工具之《殺手級 IDE 快快出現!》

Embedded Linux 的開發工具一日千里,主意不錯而且發展速度快,顯現的是未來「bring Linux to devices」的工作將更快速(快但是不見得輕鬆),這也將會構成 embedded Linux 將來市佔率(使用 Linux 做為 device OS 解決方案的比率)能大幅並快速提升的要素之一:「快速平臺與軟硬解決方案」 (rapid prototyping platform and SW/HW solutions)。

由此可大膽推測,embedded Linux 後期的生態環境正在快速形成當中,或許再過 2~3 年,就會有殺手級的 embedded Linux 的開發工具(IDE)出現。倘若如此,developer 現在一個很重要的工作就是僅快去學習研究 embedded Linux 的基礎原理;未來才能善用工具,而不是只會用工具。

embedded Linux 的開發工具逐漸成形了,會這麼說是因為今天看到一則新聞:

MontaVista launches Dev Rocket 5 beta

試想一個情境:怎麼讓「自己寫好的 code」透過 IDE 環境「咚!」一聲就被整合到 target device 的 image 檔(root filesysetm)裡?(像是 TimeSys 的服務,很快,但仍要自己「整」一堆東西到他提供的 RFS 裡。)

推敲一下未來可能的 killer application 身影。

首先引用報導裡關於 Dev Rocket 5 的說明如下:

MontaVista Software has invited current customers to join an open beta program for its next-generation Eclipse-based embedded Linux development toolset.

很好,這是一個 Eclipse-based 的 IDE(如果您不知道 Eclipse 的重要性,網路上有很多前輩發表許多關於他的看法,非常精采)。

根據報導說明,我的想法是,Dev Rocket 能支援「MontaVista Linux Edition Management」,但是如果能把這個地方做成是比較萬用(一般化)的一個 feature,那麼會是比較有彈性而且有殺氣的。要產生 root filesystem image 檔,就要有一個好用的 Linux distribution "build" 工具;目前為特定 target device 建立 Linux distribution 的一套重量級工具是 OpenEmbedded。

OpenEmbedded 是一個舉足輕重的工具,他能方便地建立 embedded Linux distribution。例如之前講到的 Neo1973 Linux mobile phone 就用到 OpenEmbedded。現在好多人在玩的 Linksys NSLU-2 也都是 OpenEmbedded 的產物。

Dev Rocket 5 也支援「One-click Debugging」與「Platform Image Creation and Configuration」的功能;Dev Rocket 5 也有 plug-ins 的功能,能整入像是「UML modeling」這樣的模組進來。

MontaVista 對 embedded Linux 的貢獻良多,未來若是能將這些 IDE open 出來,發散一陣子再收斂起來,這所謂的 killer application 已經不遠了

Also See

November 19, 2006

ELF(Executable and Linking Format)格式教學文件, #1: ELF 簡介

ELF(Executable and Linking Format)是 object file 的檔案格式,其主要結構是以 section(節區)為主,我們可以利用 GNU binutils 套件的 objdump 工具來列出執行檔的 section 與其內容。例如,我想把 ls 命令的 ELF section 列印出來:

# objdump -x /bin/ls |more
...
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  08048114  08048114  00000114  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  08048128  08048128  00000128  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .hash         0000028c  08048148  08048148  00000148  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynsym       000005e0  080483d4  080483d4  000003d4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynstr       000003ea  080489b4  080489b4  000009b4  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .gnu.version  000000bc  08048d9e  08048d9e  00000d9e  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version_r 00000070  08048e5c  08048e5c  00000e5c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .rel.dyn      00000028  08048ecc  08048ecc  00000ecc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rel.plt      00000278  08048ef4  08048ef4  00000ef4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .init         00000017  0804916c  0804916c  0000116c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 10 .plt          00000500  08049184  08049184  00001184  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .text         0000ab4c  08049690  08049690  00001690  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .fini         0000001b  080541dc  080541dc  0000c1dc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .rodata       00003760  08054200  08054200  0000c200  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 14 .eh_frame_hdr 0000002c  08057960  08057960  0000f960  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame     0000010c  0805798c  0805798c  0000f98c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .data         00000114  08058000  08058000  00010000  2**5
                  CONTENTS, ALLOC, LOAD, DATA
 17 .dynamic      000000d0  08058114  08058114  00010114  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 18 .ctors        00000008  080581e4  080581e4  000101e4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 19 .dtors        00000008  080581ec  080581ec  000101ec  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 20 .jcr          00000004  080581f4  080581f4  000101f4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000150  080581f8  080581f8  000101f8  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .bss          00000368  08058360  08058360  00010360  2**5
                  ALLOC
...

如果要印出指定 section 的內容,例如 .data section:

# objdump -j .data -s /bin/ls

/bin/ls: file format elf32-i386
Contents of section .data: 8058000 00000000 00000000 f0810508 00000000 ................ 8058010 00000000 00000000 00000000 00000000 ................ 8058020 00000080 ffffffff 01000000 01000000 ................ 8058030 00000000 00000000 00000000 00000000 ................ 8058040 02000000 5c420508 01000000 5f420508 ....\B......_B.. 8058050 00000000 00000000 01000000 53720508 ............Sr.. 8058060 01000000 53720508 05000000 61420508 ....Sr......aB.. 8058070 05000000 67420508 02000000 76420508 ....gB......vB.. 8058080 05000000 6d420508 05000000 73420508 ....mB......sB.. 8058090 05000000 73420508 00000000 00000000 ....sB.......... 80580a0 00000000 00000000 05000000 79420508 ............yB.. 80580b0 05000000 6d420508 05000000 7f420508 ....mB.......B.. 80580c0 05000000 85420508 05000000 8b420508 .....B.......B.. 80580d0 05000000 91420508 00000000 97420508 .....B.......B.. 80580e0 a1420508 01000000 ffffffff 01000000 .B.............. 80580f0 90110508 01000000 01000000 00010000 ................ 8058100 a0850508 fc800508 e0750508 01000000 .........u...... 8058110 00000000 ....

Object file 主要有 3 種類別:

1. relocatable file
2. executable file
3. shared object file

relocatable file 即副檔名為 .o 的檔案、executable file 為一般的執行檔、shared object file 則是 *.so(shared libraries)檔案。ELF 是 object file 的檔案格式,其檔案格式如下圖。

jollen_blog-ELF_View.jpg
圖:ELF View(Execution View v.s. Linking View)

Linking view 指的是經由 assembler 或 linkage editor 編譯過,可被 CPU 執行的檔案格式,也就是儲存在儲存裝置上的程式格式(stored programs)。Execution view 指的是由 loader 載入後,程式執行時的格式,也就是存在於記憶體上的程式格式(process)。

當載入器(loader)將程式載入記憶體後,object file 就會是 execution view 的格式。了解 ELF 格式與學會如何讀取 ELF 內容,對於深入研究 Linux 內部的程式實作相當有幫助。例如,未來我們將會討論的 sys_init_module 實作,便需要對「如何讀取 ELF object」有一定程度的了解。

November 20, 2006

讓 kernel 常在我心:探討如何與 kernel 的發展同步

從事 Embedded Linux(GNU/Linux systems on devices)工作的朋友,除了日常的讀書功課外,另外一個重要的工作就是「隨時注意 Linux kernel 的發展狀況」。要能與 Linux kernel 的發展同步,嚴格來說,已經是一件吃重的工作了,不過還是可以列出幾個基本的工作原則如下:

1. 每天閱讀 linux-kernel 郵遞論壇(mailing-list)的「標題」。

2. 隨時上 kernel.org 看看最新釋出的 kernel 版本(或留意 linux-kernel-announce mailing-list)。

3. 閱讀釋出版本的 Changelog。

4. 不要與 git 系統的距離太遠,定時看看 git 系統,保持一定的「短距離」。

Mailing List

linux-kernel 上的 posts 每天的量大約在 200~350 篇左右,數量並不算少,要把每一篇都看過是不太可能的,因此以我自己的閱讀心態來說,我會建議以下的閱讀方式:

1. 看標題,如果是自己有興趣或正在留意的更新,我就會標記下來持續追蹤。

2. 如果有 Bugfix 的 patch 出現,我會看看這項修正的起因與原理,但為了不讓自己花費太多時間,如果我對這項 patch 的修正「原理」不甚熟悉,我便會跳過此 post。

以下是閱讀 linux-kernel list 必須知道的幾件事:

1. 如果有新的修正,第一時間都會發佈在此 list 上,並且標題的起頭一定是 "[PATCH n/m] subject" 這樣的格式。PATCH 表示這是一個 patch 的發佈,由於一個 patch 會以多篇 post 發佈,因此就用「n/m」來表示「這是第幾篇 patch,總共有幾篇。」,例如:

[PATCH 0/7] KVM: Kernel-based Virtual Machine
[PATCH 1/7] KVM: userspace interface
[PATCH 2/7] KVM: Intel virtual mode extensions definitions
[PATCH 3/7] KVM: kvm data structures
[PATCH 5/7] KVM: mmu virtualization
[PATCH 6/7] KVM: x86 emulator
[PATCH 7/7] KVM: plumbing

2. 不能在這裡詢問與 kernel 發展無關的問題,例如:工具的使用、系統設定、詢問是否有XXX驅動程式、請求協助測試等等,這些都是不能張貼的文章。另外,原本就該留意的非成文禮節一定要注意,像是 FAQ 能找到的東西,就不要去麻煩人家。

3. list 裡大部份都是 device driver 的討論,並且很多都是架構面或觀念面的討論與修正建議,所以當您參與討論時,千萬不要用「個人的主觀看法,或是沒有事實與理論根據」的角度發表意見;由於「觀念」的修正是 kernel 2.6 驅動程式的大討論方向,所以必須先把主題相關的東西先看懂看熟後再參與討論。

kernel.org

我會不定時來看看最新發佈的 kernel 版本,因此我們必須知道目前仍在維護的 kernel 版本與其分支狀況,大致說明如下:

1. 2.6 與 2.4 都是目前仍持續積極維護中的版本。以本文寫作的時間為例(2006/11/20),目前最新的版本分別是 2.6.18.3(2006/11/19 釋出)與 2.4.33.4(2006/11/19 釋出)。

2. 2.4 的釋出版本有「stable」與「prepatch」二個分支。

3. 2.6 的釋出版本有「stable」、「prepatch」、「snapshot」與「-mm patch」四個分支。

其中 '-mm' 系列的 2.6 kernel 是由 Andrew Morton 所釋出的 Linux 分支,主要性質以「實驗」與「新功能」為主 ,所以常常可以在這個分支的 kernel 裡找到不久前才剛發佈在 mailing-list 上的 patch。如果您不太知道怎麼處理 mailing-list 上的 patch 發佈,也可以等 -mm patch 的發佈。

另外,prepatch 就是所謂的 '-rc' 發佈,所以檔名會接 '-rc?' 字串,例如:linux-2.6.19-rc6。

Changlog

每個在 kernel.org 上所釋出的 kernel 都會有一份變更紀錄(change logs)的檔案,可以了解這個版本與分支的釋出做了什麼變更。

上到 kernel.org 後,每個釋出版本的後面,會有 4 個選項(視版本不同):

  • F = full source
  • V = view patch
  • VI = view incremental
  • C = current changesets

把「C」給按下去就會進到所謂的「git 系統」。

git 系統

git 是 kernel 用來維護發展中版本的系統,也就是所謂的「snapshot 」版本,snapshot 版本都是當天新鮮送達的 kernel,釋出時會在後面加上「-git?」字串。

我會不定時點「C」到 kernel 的 git 去看看 kernel 的最新(最近)動態,說不定有我正要找的 patch。git 系統裡也能看到 kernel 的 "commit" 訊息,包含 commit 的作者、時間與差異比較(diff),而且都可以很方便地瀏覽。

結語

隨時與 kernel 的發展同步,有幾個動機:

1. 學習,kernel developers 會討論與多觀念面的議題,很好的學習機會。

2. 新的 kernel 加入了哪些新驅動程式、修正掉哪些臭蟲與增強了哪些驅動程式的功能?

3. 留意我所使用的平臺(architecture)是否有重要的修正(eg. for i386, for ARM...etc)。

4. 如果新 kernel 有加入重要的 feature 或 Bugfix,我會把自己玩耍的 code patch 到新 kernel。

測試這些有趣的新版本,建議使用 QEMU 或是 User-Mode Linux 來進行。

November 22, 2006

ELF(Executable and Linking Format)格式教學文件, #2: ELF header 與範例規劃

本文接續之前的日記「ELF(Executable and Linking Format)格式教學文件, #1: ELF 簡介」,在了解 ELF 的用途後,再來我們先由「ELF Header」的部份開始看起,我們的目標是寫一個可以將 ELF 執行檔裡所有 section 讀取出來的程式。未來將繼續朝 user-space 的 ELF 處理前進。

如果您是 Jollen 的讀者,或許曾經在書上讀到這個章節,本系統的內容與書上內容大致相仿,但仍有一些小差異,建議您可再讀一次。我希望能以其它的出版形式,將這些內容都與大家分享。

ELF header:檔頭格式

ELF 於 SysV ABI 標準中定義,其中 ELF header 的結構如下表:

Field Description

e_ident

用來辨別檔案是否為ELF,並包含一些machine independent 的資料。

e_type

檔案的類型

e_machine

檔案的平臺

e_version

版本資訊

e_entry

程式的起始位址(process virtual address

e_phoff

program header table的檔案偏移值(offset),單位是bytes。如果沒有program header table則此值為0

e_shoff

section header table的檔案偏移值(offset),單位是bytes。如果沒有section header table則此值為0

e_flags

processor有關的旗標值

e_ehsize

ELF header的長度,單位是bytes

e_phentsize

program header table每個entry的長度(bytes),每個entry的長度都相等。

e_phnum

program header tableentry個數,若無program header table 則此欄的值為0

e_shentsize

section header table每個entry的長度(bytes),每個entry的長度都相等。

e_shnum

section header tableentry個數,若program header table則此欄的值為0

e_shstrndx

section header tableindex值,索引至section name string table entry。如果檔案沒有section name string table,則此欄的值為SHN_UNDEF

ELF 檔案格式最基本的就是它的檔頭(header)部份,ELF header 儲存 object file 的各種資訊。ELF header 的讀取方式非常簡單,我們將會實作讀取 ELF header 的程式,以強化我們所讀到的觀念。

ELF header 的資料結構定義在 elf.h 裡,如下:

/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half    e_type;                 /* Object file type */
  Elf32_Half    e_machine;              /* Architecture */
  Elf32_Word    e_version;              /* Object file version */
  Elf32_Addr    e_entry;                /* Entry point virtual address */
  Elf32_Off     e_phoff;                /* Program header table file offset */
  Elf32_Off     e_shoff;                /* Section header table file offset */
  Elf32_Word    e_flags;                /* Processor-specific flags */
  Elf32_Half    e_ehsize;               /* ELF header size in bytes */
  Elf32_Half    e_phentsize;            /* Program header table entry size */
  Elf32_Half    e_phnum;                /* Program header table entry count */
  Elf32_Half    e_shentsize;            /* Section header table entry size */
  Elf32_Half    e_shnum;                /* Section header table entry count */
  Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;

Section 與 section header table 的讀取與處理方式,會在介紹完 ELF header 後再做說明。

ELF 的範例程式

本系列日記共有 5 個範例程式,其功能差異整理如下表。

   讀取檔頭  分析檔頭  判斷ELF  處理Section  讀取Strtab  列印節區名稱
loader-0.1.c  ●  ●            
loader-0.2.c  ●  ●  ●         
loader-0.3.c  ●  ●  ●  ●      
loader-0.4.c  ●  ●  ●  ●  ●  ●
loader-0.5.c  ●  ●  ●  ●  ●  ●

為了能了解ELF格式,我們將會實作 6 大項功能如下:

1. 讀取檔頭:讀取 ELF 檔案的檔頭資訊。

2. 分析檔頭:分析讀取的檔頭資訊,例如分析 ELF 執行檔的編碼平臺。

3. 判斷 ELF:判斷所讀取的檔案是否為標準 ELF 格式的檔案。

4. 處理 Section:可以讀取所有 section 的資訊,並做簡單處理,例如找出是 string table 的 section。

5. 讀取 StrTab:讀取 section name string table 裡的資訊。

6. 列印節區名稱:處理 section name string table,可以根據 section name string table 來列印所有 section的名稱(ASCII string)。

我們將會以漸進式的方式來慢慢完成所有的功能,不同版本範例程式間的主要差異將會特別做說明,並詳細解釋新功能的程式實作。

另外,loader-0.4.cloader-0.5.c 看似功能相同,但 loader-0.5.c 主要是為了討論 loader-0.4.c 裡幾個奇怪的地方。我們會在講解loader-0.5.c 程式時再做更詳細的說明。

Loader 在載入 object file 前,必須先由 ELF header 取得 object file 的資訊並且判斷 object file 是否為 ELF 格式的執行檔,然後才能將 object file 載至記憶體。

Also See

November 23, 2006

我用來建立 Linux System Calls' Table 的程式

以 Perl 寫的簡單 script,可以用來產生 Linux 的系統呼叫表(system call's table)。雖然程式笨笨的,跑一次也要一段時間,不過還是幫了我許多忙。由於 Jollen 網站上的 Linux System Calls' Table 並不會針對每個版本做更新,所以我就把我用的 Perl script 提供出來給大家自行使用。

不過,2.4/2.6 不同版本間的 system call 都是一樣的,除了注意是否有新增的 system call 外,也不會有什麼差別。而且,也不常看到 kernel 在新增 system call。

這個 Perl Script 的使用方法請參考檔案內的 README 說明。下載檔案 [lsct_gen-0.1.tar.bz2(10 KBytes)]。另外,這個 script 也會把 system call 所在的檔案印出來。

這個版本只適合用來處理 2.6 系列的程式碼,無法處理 2.4 系列的 kernel source code。

November 24, 2006

ELF(Executable and Linking Format)格式教學文件, #3: 第一個範例:loader v0.1(讀 ELF 檔頭)

ELF 的第一個範例:loader v0.1

ELF header 儲存執行檔的重要資訊,我們必須先知道如何讀取 ELF 檔案的檔頭資訊(header),才能處理個別節區(section)。在 GNU/Linux 系統下,程式可以直接引用 elf.h 標頭檔。完整的程式範例 loader-0.1.c 列表如下。

/*
 * Copyright (C) 2003 www.jollen.org
 *
 * ELF programming. ver 0.1
 *
 */
#include <stdio.h>
#include <unistd.h>
#include <elf.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

void parse_machine(Elf32_Half machine)
{
   printf("Machine:	");
   switch (machine) {
      case EM_NONE: printf("No machine\n"); break;
      case EM_M32: printf("AT&T WE 32100\n"); break;
      case EM_SPARC: printf("SPARC\n"); break;
      case EM_386: printf("Intel 80386\n"); break;
      case EM_68K: printf("Motorola 68000\n"); break;
      case EM_88K: printf("Motorola 88000\n"); break;
      case EM_860: printf("Intel 80860\n"); break;
      case EM_MIPS: printf("MIPS RS3000 Big-Endian\n"); break;
       
      default: printf("Unknow\n");
   }
}

int main(int argc, char *argv[])
{
   int fd;
   Elf32_Ehdr f_header;

   if (argc != 2) {
      printf("Usage:	loader [filename]\n");
      return -1;
   }

   fd = open(argv[1], S_IRUSR);
   if (fd < 0) {
      printf("\nfile open error\n");
      return -1;
   }

   /* Read ELF Header */
   read(fd, &f_header, sizeof(Elf32_Ehdr));

   /* Parse header information */
   parse_machine(f_header.e_machine);
}

範例說明

程式 loader-0.1.c 首先宣告變數 f_header 用存放所讀取的檔頭資料:

Elf32_Ehdr f_header;

Elf32_Ehdr 的宣告在 elf.h 裡,這個 data type 是 SysV ABI 裡的 ELF object files 檔頭的標準資料結構。讀者的方式如同傳統的 C 語言一般,只要利用低階 I/O 函數將檔頭的部份讀出即可:

read(fd, &f_header, sizeof(Elf32_Ehdr));

目前我們只讀取 ELF header 的部份。ELF header 裡存放許多重要的 object file 資訊,其中一項為 e_machine 成員,因此程式接著來判斷此欄位的內容,並且列印出 object file 所支援的硬體平臺名稱。

parse_machine() 裡頭,我們判斷 e_machine 欄位的值,並且列印出相對應的硬體平臺名稱,例如:若 e_mahcine 的值為 EM_386,則印出 "Intel 80386" 字串。EM_386 與其它相關的常數都定義在 SysV ABI 標準裡,可在 elf.h 裡看到。這部份就留給大家當功課了。

執行結果

先將上面的程式碼 copy 下來存成 loader-0.1.c 後編譯成執行檔:

$ gcc -o loader-0.1 loader-0.1.c

接著再用 loader-0.1 來分析 ELF object file:

# ./loader-0.1 /bin/vi
Machine:        Intel 80386
Also See

November 26, 2006

ELF(Executable and Linking Format)格式教學文件, #4: 第一個範例:loader v0.2(ELF Identification)

ELF Identification

ELF header 資料結構裡的 e_ident 欄位用來判斷檔案是否為ELF格式的欄位,e_ident 欄位是一個陣列資料結構,其長度由 SysV ABI所定義的常數 EI_NIDENT指定。

EI_NIDENT 的值為 16 代表 e_ident 欄位有 16 個元素,SysV ABI 定義了 8 個常數來索引 e_ident 陣列的元素。

e_ident[] 索引值定義

Name  Value  說明
EI_MAG0  0  ELF 識別字元
EI_MAG1  1  ELF 識別字元
EI_MAG2  2  ELF 識別字元
EI_MAG3  3  ELF 識別字元
EI_CLASS  4  檔案類別 (class)
EI_DATA  5  資料編碼方式 (Data encoding)
EI_VERSION  6  檔案版本
EI_PAD  7  padding bytes 的開頭

loader-0.2.c主要的改變在於加入了判斷檔案是否為 ELF 格式的程式碼。要判斷檔案是否為 ELF 格式,必須根據 e_ident[] 裡的識別字元來做判斷:

  • e_ident[EI_MAG0] 必須等於 ELFMAG0

  • e_ident[EI_MAG1] 必須等於 ELFMAG1

  • e_ident[EI_MAG2] 必須等於 ELFMAG2

  • e_ident[EI_MAG3] 必須等於 ELFMAG3

表 ELF識別字元(magic number)定義

Name

 Value

 說明

ELFMAG0

 0x7f

 e_ident[EI_MAG0] 之值

ELFMAG1

 ‘E’

 e_ident[EI_MAG1] 之值

ELFMAG2

 ‘L’

 e_ident[EI_MAG2] 之值

ELFMAG3

 ‘F’

 e_ident[EI_MAG3] 之值

判斷是否為 ELF 格式

設計一個 elf_ident() 函數來判斷檔案是否為 ELF 格式,其程式碼如下:

int elf_ident(char *ident)
{
   if (*(ident+EI_MAG0) != ELFMAG0) return 0;
   if (*(ident+EI_MAG1) != ELFMAG1) return 0;
   if (*(ident+EI_MAG2) != ELFMAG2) return 0;
   if (*(ident+EI_MAG3) != ELFMAG3) return 0;

   return -1;
}

e_ident[] 裡還存放許多 ELF 的資訊。以下再舉一例,我們新增一個函數 parse_ident() 來分析 e_ident[] 的 "CLASS" 資訊:

void parse_ident(char *ident)
{
   printf("ELF Identification\n");

   printf("  Class:	");
   switch (*(ident+EI_CLASS)) {
      case ELFCLASSNONE: printf("Invalid class\n");
             break;
      case ELFCLASS32: printf("32-bit objects\n");
            break;
      case ELFCLASS64: printf("64-bit objects\n");
            break;
   }
}

程式列表:loader-0.2.c

/*
 * Copyright(c) 2003,2006 www.jollen.org
 *
 * ELF programming. ver 0.2
 *
 */
#include 
#include 
#include 
#include 
#include 
#include 

int elf_ident(char *ident)
{
   if (*(ident+EI_MAG0) != ELFMAG0) return 0;
   if (*(ident+EI_MAG1) != ELFMAG1) return 0;
   if (*(ident+EI_MAG2) != ELFMAG2) return 0;
   if (*(ident+EI_MAG3) != ELFMAG3) return 0;

   return -1;
}

void parse_ident(char *ident)
{
   printf("ELF Identification\n");

   printf("  Class:	");
   switch (*(ident+EI_CLASS)) {
      case ELFCLASSNONE: printf("Invalid class\n"); break;
      case ELFCLASS32: printf("32-bit objects\n"); break;
      case ELFCLASS64: printf("64-bit objects\n"); break;
   }
}

void parse_machine(Elf32_Half machine)
{
   printf("Machine:	");
   switch (machine) {
      case EM_NONE: printf("No machine\n"); break;
      case EM_M32: printf("AT&T WE 32100\n"); break;
      case EM_SPARC: printf("SPARC\n"); break;
      case EM_386: printf("Intel 80386\n"); break;
      case EM_68K: printf("Motorola 68000\n"); break;
      case EM_88K: printf("Motorola 88000\n"); break;
      case EM_860: printf("Intel 80860\n"); break;
      case EM_MIPS: printf("MIPS RS3000 Big-Endian\n"); break;
       
      default: printf("Unknow\n");
   }
}

int main(int argc, char *argv[])
{
   int fd;
   Elf32_Ehdr f_header;

   if (argc != 2) {
      printf("Usage:	loader [filename]\n");
      return -1;
   }

   fd = open(argv[1], S_IRUSR);
   if (fd < 0) {
      printf("\nfile open error\n");
      return -1;
   }

   /* Read ELF Header */
   read(fd, &f_header, sizeof(Elf32_Ehdr));

   /* Parse header information */
   if (elf_ident(f_header.e_ident)) {
      parse_ident(f_header.e_ident);
      parse_machine(f_header.e_machine);
   } else {
      printf("not a ELF binary file\n");
   }
}
Also See

November 28, 2006

Embedded Linux 與王建民

椅子坐太久除了手痛、腰酸與腳麻外,頭腦也會變的極度渾沌。所以在計畫讓屁股離開椅子前,就來看了一下「王建民」。有趣的是,我找了一下「王建民」跟「embedded linux」在 Google 的搜尋量比較,結果還挺有趣的。先來看一下「embedded linux」跟「wince」、「embedded system」關鍵字的搜尋關係:

embedded linux    wince    embedded system   


wang_40_1.png

結果 embedded linux 跟 embedded system 的關係相當密切,幾乎是貼在一起走的。至於跟 wince 做比較的話,則是保持一定的差距,看起來跟幾年前相差不遠。

再來是「embedded linux」PK「王建民」:

embedded linux 王建民   

wang_40_2.png

2005 年 Q1 以前,「王建民」幾乎是躺平的,大概只有 2004 Q3 亞洲盃時凸起來一下下。然後 2005 年 Q1 上大聯盟後是上市密月期,連拉數日長紅,接著到 2005 Q4 ~ 2005 Q1 是 MLB 休兵期間,再度躺平。

今年隨著 Wang 40 的精采表現而呈現的多頭走勢,沒想到在 Wang 40 回國後進入主升段,連爆長紅。雖然在 Wang 40 上市後,這期間有幾次紅線超越「Emebdded Linux」,不過這次 Wang 40 的主升段展開後,已經把「Embedded Linux」遠遠抛在腦後了。

如果再看下半部的新參考頁面「量」,Wang 40 老早就不把 embedded Linux 看在眼裡了。另外,如果你在 Google 找「王建民」,會發現右半部很「台灣味」:

wang_40_3.PNG

王建民,加油!

ELF(Executable and Linking Format)格式教學文件, #5: 讀 ELF Section(說明)

今天的內容「讀取 ELF section」是這系列 ELF 文章的重點戲。因為我們終於進入 ELF 的核心議題「節區的觀念」了。在 loader v0.5 以前的範例,都是屬於靜態的討論(linking view);在 loader v0.6 開始的討論中,我們將會開始提到動態的執行行為(execution view)。

如何讀取 ELF Section

我們分 2 個步驟來讀取 ELF 的 section 資訊:

1. 如圖一,一開始先讀取 section header table 的資訊。section header table 是所有 section 的紀錄表格,所有的 section 都要透過 section header table 才能得知其在檔案中的偏移位置(offset),如此一來才能讀取 section 的內容。

2. 如圖二,接著再根據 section header table 讀取檔案裡的每一個 section。

section header table 裡的 section 個數(section entries)紀錄於 ELF header 裡的 e_shnum 欄位,每個 section entry 的長度則是紀錄在 ELF header 的 e_shentsize 欄位,單位是bytes。

elf_sht.jpg
圖一:Section Header Table

ELF header 裡的 e_shoff 欄位,是 section header table 開始的檔案偏移位置 (file offset)。因此,我們只要由 e_shoff 偏移值開始讀取 e_shnum 個單位,每個單位為 e_shentsize(bytes),即可將整個 section header table 讀取出來。

elf_section_entry.jpg
圖二:Section Entries

SysV ABI 定義 section entry 的資料結構如下:

typedef struct {
  Elf32_Word	sh_name;
  Elf32_Word	sh_type;
  Elf32_Word	sh_flags;
  Elf32_Addr	sh_addr;
  Elf32_Off	sh_offset;
  Elf32_Word	sh_size;
  Elf32_Word	sh_link;
  Elf32_Word	sh_info;
  Elf32_Word	sh_addralign;
  Elf32_Word	sh_entsize;
} Elf32_Shdr;

Section header table 是 Elf32_Shdr data type 的陣列,其元素個數為 e_shnum,我們可以透過索引 Elf32_Shdr 陣列來讀取所有的 section,如圖二所示。

Section header(Elf32_Shdr)的欄位用途說明如下表。

Field Description
sh_name  section的名稱,需由string table查表取得。
sh_type  section的類型。
sh_flags  section的屬性。
sh_addr  section在記憶體裡的起始位址,但並非所有的section都會被載入至記憶體。
sh_offset  section在objct file裡的開始偏移值(offset),程式必須根據此欄位來讀取section的本文。
sh_size  section的長度(bytes)。
sh_link  可用來存放section header table的index link。
sh_info  可用來存放section的額外資訊。
sh_addralign  紀錄section的address alignment,例如有些section的 alignment為DWORD(dobuleword)。
sh_entsize  有些section的內容為entry長度固定的table,例如symbol table。此欄用來紀錄entry的長度,單位是bytes。

範例程式:loader v0.3

接著說明 loader v0.2 需要改寫的功能。

在主程式新增一個 parse_sections() 函數來讀取 ELF object file 的 section header table:

parse_sections(&f_header, fd);

parse_sections() 負責做2件工作:

1. 讀取 Section Header Table

2. 找出 Section Name String Table

同時,在主程式也要加入以下 2 個變數:

Elf32_Shdr header[40];
Elf32_Shdr *strtab; /* point to string table */

header[] 用來存放由 section header table 所讀取出來的所有 section entry,其型別為 Elf32_Shdrstrtab 指標用來指向 section name string table,其型別為 Elf32_Shdr。Section name string table 是一個特殊的 section,下一個範例再來處理這個 section。

將檔案讀寫指標移到 e_shoff 的地方,準備開始讀取 section header table:

lseek(fd, hdr->e_shoff, SEEK_SET);

然後再利用最簡單的方式,一次一個將所有的section entry讀取出來,並且判斷該section entry是否為string table:

   for (i = 0; i < hdr->e_shnum; i++) {
      read(fd, &header[i], sizeof(Elf32_Shdr));

      /* find out string table ! */
      if (header[i].sh_type == SHT_STRTAB) strtab = &header[i];
   }

最後,section 用途是根據他的類型來區分的,這個部份留待下次再做說明。

Also See

November 29, 2006

Debian ARM 爆起:思考社群發展模式的關鍵二個因素

Debian ARM 已成為 Debian 數十種平臺的 distribution 中的第三大。由 Debian 的 popularity-contest 統計資料來看,Debian ARM 已經超越 PowerPC 成為第三大的 Debian 架構分支。詳細報導在此

讓 Debian ARM 成為第三熱門的 Debian 版本的功臣就是今年大大有名的 NSLU2 計畫,如果您還不曉得 NSLU2 的名號,可以到NSLU2 的官方網站瞧瞧。當初 NSLU2 計畫發起時,便立下這樣的志願(節錄自 LinuxDevices.com 的報導):

to have the Linksys NSLU2 become a fully supported mainstream Linux distribution device.

這個目標達成了!Debian ARM 從第七名竄升到第三名,只花了九個月的時間;並且,有 90% 的 Debian ARM 使用者都是將 Debian ARM 安裝在 Linksys 的 NSLU2 device 上(節錄自 LinuxDevices.com 的報導):

Ninety percent of those ARM installations are NSLU2 devices.

NSLU2 是今年 open source community 發展模式中,具代表性的成功案例之一。Jollen 認為,當中最關鍵的因素除了眾所矚目的開放源碼社群模式外,有二項關鍵因素是值得推敲思考的:

1. 將 Linksys NSLU2 當做是「device」來推廣,而不是「product」。如果你到 NSLU2 的首頁去,一定能看到這句話:「Purchase a Linksys NSLU2 and join our community if you are interested in doing the same.」。

東西當做 device 來賣,顧客是 community,賣了東西也得到不少東西;再說,賣給 community 的數量也不見得比賣 product 給 end-user 少。

2. NSLU2 device 拿來給社群做發展與「把玩」並建立自己的 open source project(與 community)後,才能得到其他(、更多) community 的支持,更多社群跟你互補後,不但能加快本身的產品發展速度與進行更有效的推廣,也能吸引許多「end-user」的關注。對於「product」的長期發展絕對是正面的。我們可以看到,許多社群的開發者(developer)都是熱血的死忠擁護者,這些人也是很好的 promoter。

Debian ARM(或 NSLU2)之所以成功,在 LinuxDevices.com 上的報導裡已經講出重點了:

This is, of course, the outcome of a lot of hard work by a lot of people, including (but not limited to) the NSLU2-Linux core team and developers, the OpenEmbedded developers, the Debian-arm porters, and the Debian-installer and Debian-kernel teams.

可見閉門造車並不是好事,因為除了要不停的「重造車輪」外,也無法讓不同的社群響應你的計畫,或是跟你互補。

PReP / CHRP / OpenFirmware

最近有些朋友在問 PReP / CHRP / OpenFirmware 的關係,老實說這真的有點混亂,特別是 PReP / CHRP 是歷史所留下的遺跡,所以就用最簡潔的方式大概說明一下吧。

PReP(PowerPC Reference Platform)是最早的 PowerPC reference design,PReP 規格並沒有風行,原因是 IBM 是以「一言堂」的模式來制定這項規格。PReP 之後則是 CHRP(Common Hardware Reference Platform)的第二代 PowerPC reference design 規格,CHRP reference design 制定了作業系統支援的規格。CHRP 的另外一個重要內容則是定義 OpenFirmware 的標準。

OpenFirmware(也稱為 OpenBoot)的角色等於 PC 的 BIOS,也就是開機時期的軟體。OpenFirmware 相當強大,因為 OpenFirmware 採用的是 Forth 語言,因此撰寫 firmware 的模式是「Forth-based shell script」。

OpenFirmware 只是一個規格,目前有 open source 的 OpenFirmware 專案,也就是知名的 OpenBIOS。當然也有商業性質的 OpenFirmware 實作,不過既然 IBM 也貢獻了 FCODE 給 OpenBIOS,那麼我想未來 OpenBIOS 絕對是首選的 OpenFirmware 實作了。

最後要說明的是,PReP 與 CHRP 都不是目前 Power Architecture 的 reference design 規格了。早先 Jollen 在「RISC 嵌入式平臺 (PowerPC) 的 VGA 解決方案」的日記裡也大略提到過 OpenBIOS 與 OpenFirmware(FCODE suite),大家也可以參考一下。

新一代的 Power Architecture 規格叫做 PAPR,這是由 Power.org 組織所制定的新規格,主要是針對 Power 架構的 workstation 與 server 所制定的(no PC...),目前的最新版本是 v2.0。

關於 November 2006

此頁面包含了在November 2006發表於Jollen's Blog的所有日記,它們從老到新列出。

前一個存檔 October 2006

後一個存檔 December 2006

更多信息可在 主索引 頁和 歸檔 頁看到。

Top | 授權條款 | Jollen's Forum: Blog 評論、討論與搜尋
Copyright(c) 2006 www.jollen.org