一直以來都覺得 beamer itemize 的項目間距太擁擠了。如果要把他調高一點,可以用:
\begin{itemize}
\addtolength{\itemsep}{0.25\baselineskip}}
\item{Item 1}
\item{Item 2}
\end{itemize}
當然如果一個一個加很麻煩,也可以定義一個新的 environment:
\newenvironment{xitemize}
{\begin{itemize}\addtolength{\itemsep}{0.25\baselineskip}}
{\end{itemize}}
這樣要用間距比較大的 itemize,就改成
\begin{xitemize}
\item{Item 1}
\item{Item 2}
\end{xitemize}
2013年2月19日 星期二
2013年1月20日 星期日
動手寫 Linux Driver
先前為了一個期末專題花了一點時間研究怎麼在 Linux 作業系統上寫一個 PCI Driver。寫過 Linux 驅動程式之後,覺得 Linux 的架構真的很漂亮!為了怕以後忘記怎麼寫,所以就把他寫下來記錄成一篇文章。
首先我們必須要準備開發 Linux 驅動程式所需的環境,在 Debian 上可以用以下的指令達到這個目的:
$ sudo apt-get install build-essential linux-headers-$(uname -r)
其中 build-essential 會安裝 gcc, make 等軟體開發必要的工具,而 linux-headers 會安裝開發 Linux 驅動程式必要的 SDK。因為 linux-headers 會隨核心的版本而有所不同,所以我們要使用 $(uname -r) 取得目前核心的版本。
所有的 Linux 驅動程式至少要包含一個 MODULE_LICENSE 用以宣告驅動程式的授權,另外還需要一個 init 與一個 exit 函式,分別處理驅動程式的起始與終止。以下就是一個什麼都沒有的空殼:
/* example.c */
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int example_init(void) {
printk("<1>EXAMPLE: init\n");
return 0;
}
static void example_exit(void) {
printk("<1>EXAMPLE: exit\n");
}
module_init(example_init);
module_exit(example_exit);
我們可以注意到裡面有一個 printk,他就相當於驅動程式設計當中的 printf。我們如果需要印任何除錯資訊,可以呼叫 printk,然後使用 sudo dmesg 觀看結果。編譯這個檔案之前,我們要先幫他寫 Makefile:
obj-m := example.o
ifeq ($(KERNELDIR),)
KERNELDIR=/lib/modules/$(shell uname -r)/build
endif
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
在這個 Makefile 裡面,我們會使用 obj-m 這個變數指定我們要編譯的模組,然後再呼叫 make 讓他載入 SDK 的 Makefile。我們先前安裝的 SDK 就會放在 /lib/modules/$(shell uname -r)/build 裡面。
接下來我們就可以用 make 編譯我們的模組,並使用以下指令載入、卸除模組:
$ sudo insmod ./example.ko
$ sudo rmmod example
如果要看我們的模組有沒有輸出任何訊息,可以使用:
$ sudo dmesg | tail
在 Unix 的設計哲學當中,所有的東西都是檔案,硬體也不例外。我們寫驅動程式的時候要提供一個檔案操作的介面給 Userspace 的程式。為了達到這個目的,我們必須再引入一個標頭檔:
#include <linux/fs.h>
然後定義若干檔案操作與 file_operations 這個資料結構:
static int example_open(struct inode *inode, struct file *filp) {
printk("<1>EXAMPLE: open\n");
return 0;
}
static int example_close(struct inode *inode, struct file *filp) {
printk("<1>EXAMPLE: close\n");
return 0;
}
static ssize_t example_read(struct file *filp, char *buf, size_t size, loff_t *f_pos) {
printk("<1>EXAMPLE: read (size=%zu)\n", size);
return 0;
}
static ssize_t example_write(struct file *filp, const char *buf, size_t size, loff_t *f_pos) {
printk("<1>EXAMPLE: write (size=%zu)\n", size);
return size;
}
static struct file_operations example_fops = {
.open = example_open,
.release = example_close,
.read = example_read,
.write = example_write,
};
然後在 example_init() 當中用 register_chrdev 把這個驅動程式註冊為一個 Character Device。
#define EXAMPLE_MAJOR 60
#define EXAMPLE_NAME "example"
static int example_init(void) {
int result;
printk("<1>EXAMPLE: init\n");
/* Register character device */
result = register_chrdev(EXAMPLE_MAJOR, EXAMPLE_NAME, &example_fops);
if (result < 0) {
printk("<1>EXAMPLE: Failed to register character device\n");
return result;
}
return 0;
}
值得一提的是第一個參數 EXAMPLE_MAJOR 可以是 60, 61, 62。如果是正式要釋出的 Driver,就必須要從 Documentation/devices.txt 選取適當的 Major ID。當然,在 example_exit() 我們也必需加上對應的 unregister:
static void example_exit(void) {
printk("<1>EXAMPLE: exit\n");
/* Unregister character device */
unregister_chrdev(EXAMPLE_MAJOR, EXAMPLE_NAME);
}
在重新編譯之後,我們可以用 insmod 載入驅動程式,然後使用 mknod 建立 Device File。然後我們就可以在 User Space 使用一般的檔案讀寫操作這個 Device。
$ sudo insmod ./example.ko
$ sudo mknod /dev/example c 60 0
# /dev/example 是我們要存放檔案的路徑,c 代表 Character Device,60 是這個驅動程式的 Major ID,0 是驅動程式的 Minor ID。
$ sudo chmod 666 /dev/example
# 為了方便測試,我們把這個 Device 改成所有人都可以讀寫。
$ echo -n 'abcd' > /dev/example
$ sudo dmesg | tail
在前一節當中我們提供了一個 API 讓 User Space 可以操作 Driver。但是其實我們是不能直接存取 buf 的內容。因為 Kernel Space 與 User Space 有不同的位址空間,所以不能直接存取他們。我們必須借助 copy_from_user 這個 API。
在使用這個 API 之前,我們必需引入 <asm/uaccess.h>:
#include <asm/uaccess.h>
然後我們就可以使用 copy_from_user 來存取 User Space 的位址空間,舉例來說:
ssize_t example_write(struct file *filp, const char *buf, size_t size, loff_t *f_pos) {
size_t pos;
uint8_t byte;
printk("<1>EXAMPLE: write (size=%zu)\n", size);
for (pos = 0; pos < size; ++pos) {
if (copy_from_user(&byte, buf + pos, 1) != 0) {
break;
}
printk("<1>EXAMPLE: write (buf[%zu] = %02x)\n", pos, (unsigned)byte);
}
return pos;
}
值得注意的是 copy_from_user() 會回傳剩下未完成的 byte 數。所以一般來說這個回傳值必須是 0 才是成功地讀入資料。要把資料從 Kernel Space 複製到 User Space 則是使用 copy_to_user() 函式,至於使用方法就不再贅述。
$ echo -n 'abcd' > /dev/example
$ sudo dmesg | tail
透過這個小練習,我們可以知道要怎麼開始寫一個 Linux Driver。在下一結我們會從 QEMU 的角度出發,建立一個 QEMU 的虛擬裝置,讓 QEMU Guest OS 的驅動程式可以和外面的 QEMU 虛擬裝置相互溝通。
建構編譯環境
首先我們必須要準備開發 Linux 驅動程式所需的環境,在 Debian 上可以用以下的指令達到這個目的:
$ sudo apt-get install build-essential linux-headers-$(uname -r)
其中 build-essential 會安裝 gcc, make 等軟體開發必要的工具,而 linux-headers 會安裝開發 Linux 驅動程式必要的 SDK。因為 linux-headers 會隨核心的版本而有所不同,所以我們要使用 $(uname -r) 取得目前核心的版本。
簡單的驅動程式
所有的 Linux 驅動程式至少要包含一個 MODULE_LICENSE 用以宣告驅動程式的授權,另外還需要一個 init 與一個 exit 函式,分別處理驅動程式的起始與終止。以下就是一個什麼都沒有的空殼:
/* example.c */
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int example_init(void) {
printk("<1>EXAMPLE: init\n");
return 0;
}
static void example_exit(void) {
printk("<1>EXAMPLE: exit\n");
}
module_init(example_init);
module_exit(example_exit);
我們可以注意到裡面有一個 printk,他就相當於驅動程式設計當中的 printf。我們如果需要印任何除錯資訊,可以呼叫 printk,然後使用 sudo dmesg 觀看結果。編譯這個檔案之前,我們要先幫他寫 Makefile:
obj-m := example.o
ifeq ($(KERNELDIR),)
KERNELDIR=/lib/modules/$(shell uname -r)/build
endif
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
在這個 Makefile 裡面,我們會使用 obj-m 這個變數指定我們要編譯的模組,然後再呼叫 make 讓他載入 SDK 的 Makefile。我們先前安裝的 SDK 就會放在 /lib/modules/$(shell uname -r)/build 裡面。
接下來我們就可以用 make 編譯我們的模組,並使用以下指令載入、卸除模組:
$ sudo insmod ./example.ko
$ sudo rmmod example
如果要看我們的模組有沒有輸出任何訊息,可以使用:
$ sudo dmesg | tail
註冊為 Character Device
在 Unix 的設計哲學當中,所有的東西都是檔案,硬體也不例外。我們寫驅動程式的時候要提供一個檔案操作的介面給 Userspace 的程式。為了達到這個目的,我們必須再引入一個標頭檔:
#include <linux/fs.h>
然後定義若干檔案操作與 file_operations 這個資料結構:
static int example_open(struct inode *inode, struct file *filp) {
printk("<1>EXAMPLE: open\n");
return 0;
}
static int example_close(struct inode *inode, struct file *filp) {
printk("<1>EXAMPLE: close\n");
return 0;
}
static ssize_t example_read(struct file *filp, char *buf, size_t size, loff_t *f_pos) {
printk("<1>EXAMPLE: read (size=%zu)\n", size);
return 0;
}
static ssize_t example_write(struct file *filp, const char *buf, size_t size, loff_t *f_pos) {
printk("<1>EXAMPLE: write (size=%zu)\n", size);
return size;
}
static struct file_operations example_fops = {
.open = example_open,
.release = example_close,
.read = example_read,
.write = example_write,
};
然後在 example_init() 當中用 register_chrdev 把這個驅動程式註冊為一個 Character Device。
#define EXAMPLE_MAJOR 60
#define EXAMPLE_NAME "example"
static int example_init(void) {
int result;
printk("<1>EXAMPLE: init\n");
/* Register character device */
result = register_chrdev(EXAMPLE_MAJOR, EXAMPLE_NAME, &example_fops);
if (result < 0) {
printk("<1>EXAMPLE: Failed to register character device\n");
return result;
}
return 0;
}
值得一提的是第一個參數 EXAMPLE_MAJOR 可以是 60, 61, 62。如果是正式要釋出的 Driver,就必須要從 Documentation/devices.txt 選取適當的 Major ID。當然,在 example_exit() 我們也必需加上對應的 unregister:
static void example_exit(void) {
printk("<1>EXAMPLE: exit\n");
/* Unregister character device */
unregister_chrdev(EXAMPLE_MAJOR, EXAMPLE_NAME);
}
在重新編譯之後,我們可以用 insmod 載入驅動程式,然後使用 mknod 建立 Device File。然後我們就可以在 User Space 使用一般的檔案讀寫操作這個 Device。
$ sudo insmod ./example.ko
$ sudo mknod /dev/example c 60 0
# /dev/example 是我們要存放檔案的路徑,c 代表 Character Device,60 是這個驅動程式的 Major ID,0 是驅動程式的 Minor ID。
$ sudo chmod 666 /dev/example
# 為了方便測試,我們把這個 Device 改成所有人都可以讀寫。
$ echo -n 'abcd' > /dev/example
$ sudo dmesg | tail
讀取 User Space 的資料
在前一節當中我們提供了一個 API 讓 User Space 可以操作 Driver。但是其實我們是不能直接存取 buf 的內容。因為 Kernel Space 與 User Space 有不同的位址空間,所以不能直接存取他們。我們必須借助 copy_from_user 這個 API。
在使用這個 API 之前,我們必需引入 <asm/uaccess.h>:
#include <asm/uaccess.h>
然後我們就可以使用 copy_from_user 來存取 User Space 的位址空間,舉例來說:
ssize_t example_write(struct file *filp, const char *buf, size_t size, loff_t *f_pos) {
size_t pos;
uint8_t byte;
printk("<1>EXAMPLE: write (size=%zu)\n", size);
for (pos = 0; pos < size; ++pos) {
if (copy_from_user(&byte, buf + pos, 1) != 0) {
break;
}
printk("<1>EXAMPLE: write (buf[%zu] = %02x)\n", pos, (unsigned)byte);
}
return pos;
}
值得注意的是 copy_from_user() 會回傳剩下未完成的 byte 數。所以一般來說這個回傳值必須是 0 才是成功地讀入資料。要把資料從 Kernel Space 複製到 User Space 則是使用 copy_to_user() 函式,至於使用方法就不再贅述。
$ echo -n 'abcd' > /dev/example
$ sudo dmesg | tail
小結
透過這個小練習,我們可以知道要怎麼開始寫一個 Linux Driver。在下一結我們會從 QEMU 的角度出發,建立一個 QEMU 的虛擬裝置,讓 QEMU Guest OS 的驅動程式可以和外面的 QEMU 虛擬裝置相互溝通。
參考資料
2011年10月23日 星期日
從 Android Open Source Project 下載 Android 原始碼
前一陣子因為受到 kernel.org 被駭的牽連,Android Open Source Project (AOSP) 的 git repository 也關閉了一段時間。大約二天之前 AOSP 回來了,不過網址有些許的變動,我就稍微記錄一下下載流程,以免日後忘記要怎麼弄。
要下載 AOSP 的程式碼,你至少需要 curl、git、python 這三個程式。在 Debian 或 Ubuntu 上面你只要使用:
$ sudo apt-get install curl git python
就可以把它們弄到手。接下來我們要先設定 git:
$ git config --global user.name 你的名字
$ git config --global user.email 你的email
接下來我們必需下載 repo 版本管理工具,我們可以在家目錄之下建立一個資料夾,並把這個資料夾加到 $PATH 裡面:
$ mkdir ~/bin
$ export PATH=~/bin:$PATH
# 備註:你可以把上面這行加到你的 .bashrc,這樣以後要用 repo 的時候就可以直接用。
然後下載 repo 這個工具:
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod +x ~/bin/repo
如果你做到這裡沒有遇到問題就可以開始下載程式碼。如果你遇到 SSL certificate problem 之類的問題可以看下面的常見問題。
接下來就是這篇文章的重點:下載 Android 程式碼。首先我們先建立一個用來放置 Android 程式碼的資料夾:
$ mkdir ~/android-src
$ cd ~/android-src
初始化 repo 相關的設定:
$ repo init -u https://android.googlesource.com/platform/manifest
接著 repo 會問你一些問題,通常使用預設值就可以了。最後就是按下:
$ repo sync
# 備註:你可以加上 -j2 或 -j4 平行下載。
之後經過漫長的等待,你就會有一份完整的 Android 程式碼!
下一步:你可以參考另一篇文章「編譯 Android AOSP」編譯整個 Android 系統。
--------------------------------------------------
有時候可能因為 TCP/IP 的實作可能遇上一些意外的問題,所以會一直卡在「Receiving Objects」,可以用以下指令調整 TCP 協定的設定:
$ sudo sysctl -w net.ipv4.tcp_window_scaling=0
然後在 repo sync 的時候,只使用單執行緒:
$ repo sync -j1
如果你遇到 SSL certificate problem 之類的問題,是因為你的系統提供的 curl 沒有內建 cacert 清冊 (也就是 https 憑證發行單位的清冊),curl 沒有辦法驗證 dl-ssl.google.com 的憑證是否正確。
你必需先行下載 cacert.pem。因為 cacert.pem 是防止中間人攻擊的重要機制,所以請務必要確保他的正確性,你可以使用 sha1sum 來檢查:
$ curl http://curl.haxx.se/ca/cacert.pem --ipv4 > ~/cacert.pem
$ sha1sum ~/cacert.pem
286c4a22fe2ed9e3b2d958e2800a8c16d867f063 cacert.pem
然後再使用以下指令下載 repo 這個工具:
$ curl --cacert ~/cacert.pem https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
另外,我們要讓 git 使用這個清冊驗證 Google 的 repository,所以我們要執行以下指令:
$ git config --global http.sslcainfo ~/cacert.pem
先準備必要的工具
要下載 AOSP 的程式碼,你至少需要 curl、git、python 這三個程式。在 Debian 或 Ubuntu 上面你只要使用:
$ sudo apt-get install curl git python
就可以把它們弄到手。接下來我們要先設定 git:
$ git config --global user.name 你的名字
$ git config --global user.email 你的email
下載 repo 版本管理工具
接下來我們必需下載 repo 版本管理工具,我們可以在家目錄之下建立一個資料夾,並把這個資料夾加到 $PATH 裡面:
$ mkdir ~/bin
$ export PATH=~/bin:$PATH
# 備註:你可以把上面這行加到你的 .bashrc,這樣以後要用 repo 的時候就可以直接用。
然後下載 repo 這個工具:
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod +x ~/bin/repo
如果你做到這裡沒有遇到問題就可以開始下載程式碼。如果你遇到 SSL certificate problem 之類的問題可以看下面的常見問題。
開始下載 Android 程式碼
接下來就是這篇文章的重點:下載 Android 程式碼。首先我們先建立一個用來放置 Android 程式碼的資料夾:
$ mkdir ~/android-src
$ cd ~/android-src
初始化 repo 相關的設定:
$ repo init -u https://android.googlesource.com/platform/manifest
接著 repo 會問你一些問題,通常使用預設值就可以了。最後就是按下:
$ repo sync
# 備註:你可以加上 -j2 或 -j4 平行下載。
之後經過漫長的等待,你就會有一份完整的 Android 程式碼!
下一步:你可以參考另一篇文章「編譯 Android AOSP」編譯整個 Android 系統。
--------------------------------------------------
常見問題
為什麼會一直停在 Receiving Objects 且網路毫無動靜?
有時候可能因為 TCP/IP 的實作可能遇上一些意外的問題,所以會一直卡在「Receiving Objects」,可以用以下指令調整 TCP 協定的設定:
$ sudo sysctl -w net.ipv4.tcp_window_scaling=0
然後在 repo sync 的時候,只使用單執行緒:
$ repo sync -j1
為什麼會遇到 SSL certificate problem 之類的問題?
如果你遇到 SSL certificate problem 之類的問題,是因為你的系統提供的 curl 沒有內建 cacert 清冊 (也就是 https 憑證發行單位的清冊),curl 沒有辦法驗證 dl-ssl.google.com 的憑證是否正確。
你必需先行下載 cacert.pem。因為 cacert.pem 是防止中間人攻擊的重要機制,所以請務必要確保他的正確性,你可以使用 sha1sum 來檢查:
$ curl http://curl.haxx.se/ca/cacert.pem --ipv4 > ~/cacert.pem
$ sha1sum ~/cacert.pem
286c4a22fe2ed9e3b2d958e2800a8c16d867f063 cacert.pem
然後再使用以下指令下載 repo 這個工具:
$ curl --cacert ~/cacert.pem https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
另外,我們要讓 git 使用這個清冊驗證 Google 的 repository,所以我們要執行以下指令:
$ git config --global http.sslcainfo ~/cacert.pem
參考資料
訂閱:
文章 (Atom)