2013年12月31日 星期二

編譯 busybox 並建立一個linux 系統


編譯 busybox 把 arm-none-linux-gnueabi- 換成你的編譯器。然後記得靜態編譯 busybox(在 menuconfig 裡選擇)
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- defconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- menuconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- install
編譯完後的檔案會放在 _install。把他做成 img
$ cd _install
$ find . | cpio -o --format=newc > ../rootfs.img

你可以用下面指令測試一下
qemu-system-arm -M versatilepb -m 128M -kernel zImage -initrd rootfs.img -append "root=/dev/ram rdinit=/bin/sh"

建立系統必要的目錄
$ cd _install
$ mkdir proc sys dev etc etc/init.d
$ cd ..




新增一個檔案,_install/etc/init.d/rcS,加入下面的內容
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s


讓剛剛的檔案可以被執行

chmod +x _install/etc/init.d/rcS
最後的驗收

qemu-system-arm -M versatilepb -m 128M -kernel zImage -initrd rootfs.img.gz -append "root=/dev/ram rdinit=/sbin/init"
參考資料

arm cross compile 常用指令備忘



製作 initramfs image

find . | cpio -H newc -o | gzip -9 > /boot/initrd.img
或是
find . | cpio -H newc -o > rootfs
解壓縮 initramfs image

gunzip -c /boot/initrd.img-2.6.24-19-generic | cpio -i

修改 initrd image

mount -t ext2 -o loop initrd.img temp/

編譯 arm 核心,arm-linux-gnueabi-請換成你用 cross_compiler

make ARCH=arm versatile_defconfigmake ARCH=arm menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- all

編譯完後,核心路徑在 arch/arm/boot


qemu-system-arm -M versatilepb -m 128M -kernel zImage -no-reboot


第一支程式

#include <stdio.h>
int main() {  printf("Hello World!\n");  while(1);}


編譯第一支程式
arm-linux-gnueabi-gcc -static    test.c   -o test 
製作 rootfs
echo test | cpio -o --format=newc > rootfs
執行第一支程式
qemu-system-arm -M versatilepb -m 128M -kernel zImage -initrd rootfs -append "root=/dev/ram rdinit=/test" -no-reboot
啟動核心後,核心會執行你的程式。

其他:如果你要測試編譯出來的程式,也可以直接用下面指令。
qemu-arm -L /usr/arm-linux-gnueabi/ a.out

參考資料

http://backreference.org/2010/07/04/modifying-initrdinitramfs-files/
http://balau82.wordpress.com/2010/03/22/compiling-linux-kernel-for-qemu-arm-emulator/

2013年12月29日 星期日

簡介 python 的物件生成 ( how to generate an instance in python)

    在開始介紹 python 怎麼產生 instnace 前,先來看一段簡單的程式碼


class MyStr1(str):
    def __init__(self, a='', b=''):
        super(MyStr1, self).__init__()
        self.a = a
        self.b = b


    這段程式碼看起來沒什麼,就繼承自 str 。然後 override __init__ 方法。所以我們可以用下面程式碼產生 instance。


 a = MyStr1("1")

   可是當我們執行下面程式碼時,

 a = MyStr1("1", "2")


    卻會出現

Traceback (most recent call last):
  File "", line 1, in 
TypeError: str() takes at most 1 argument (2 given)

    這到底發生什麼事? 會出現這個錯誤,是因為 str.__new__ 這個方法只接受一個參數,而你給他兩個。

    做個簡單的實驗。執行這兩行 str.__new__(str) 和 str.__new__(str, '1') 程式碼不會出錯。然而 str.__new__(str, '1', '2') 會出現上面的錯誤。所以可以合理懷疑問題真的出在這了。

     接下來來談一個 class 怎麼產生 instance。當 class 要產生 instance 時,他會呼叫 class 的 metaclss 的 __call__ 方法。而 metaclass 的 __call__ 方法大概長這樣:

def __call__(cls, *args, **kwargs):
    obj = cls.__new__(cls, *args, **kwargs)
    obj.__init__(*args, **kwargs)
    return obj

    所以執行 a = MyStr1("1", "2") 時,直譯器會先做 obj = str.__new__(str, *args, **kwargs) ,其中 *args 是 (1, 2, 3),可是因為 str.__new__ 只接受一個參數,因此發生錯誤。

    要怎麼知道直譯器在建立 instance 時,會把類別名稱後面的參數全都丟進 __new__ 和 __init__ 裡呢? 用一段簡單的程式即可說明。



class ClsA(object):
    def __init__(self, *args, **kwargs):
        print 'init: ', args, kwargs

    def __new__(cls, *args, **kwargs):
        print 'new: ', args, kwargs
        return super(ClsA, cls).__new__(cls, *args, **kwargs)

  
a = ClsA('a', 'b', 2)


   上面程式碼,當要建立 ClsA 的程式碼,他會先執行 __new__,而餵給 __new__ 的參數則是 ClsA 後面的參數。之後再把 ClsA 後面的參數餵給 __init__。而這也說明了為什麼一開始的程式碼會有問題。因為建立 instance 時,預設行為是把類別後面的參數全都丟給 __new__ 和 __init__。可是 str.__new__最多只接受一個參數,因此出錯。

    要如何解決第一個程式所遇到的問題呢?
    解法1.
    不要使用預設的 metaclass(type),自訂一個 meta class


class CustomMetaCls(type):
    def __call__(cls, *args, **kwargs):
        if len(args):
            content = args[0]
        else:
            content = ''
        obj = cls.__new__(cls, content)
        obj.__init__(*args, **kwargs)
        return obj

  
class MyStr(str):
    __metaclass__ = CustomMetaCls
 
    def __init__(self, a='', b=''):
        super(MyStr, self).__init__(a)
        self.a = a
        self.b = b


b = MyStr('a', 'b')


    解法2.
    override __new__

class MyStr1(str):
    def __init__(self, a='', b=''):
        super(MyStr1, self).__init__()
        self.a = a
        self.b = b

    def __new__(cls, *args, **kwargs):
        print args, kwargs
        if len(args):
            content = args[0]
        else:
            content = ''
        return str.__new__(cls, content)

2013年12月25日 星期三

docker 原理簡介

    Docker 官網上的簡介,寫了"Docker is an open-source project to easily create lightweight, portable, self-sufficient containers from any application."。不過 containers 是啥呢? 有點難解釋,基本上可以想像成一個 sandbox。不過 docker 還可以在上面跑完整的 linux distributions。

    先看看 Docker 怎麼實作的。他是建立在 lxc 和 aufs 的基礎上。那 lxc 是什麼呢? 官網上講"LXC is a userspace interface for the Linux kernel containment features."。官網上還有這一段話。

Features

Current LXC uses the following kernel features to contain processes:
  • Kernel namespaces (ipc, uts, mount, pid, network and user)
  • Apparmor and SELinux profiles
  • Seccomp policies
  • Chroots (using pivot_root)
  • Kernel capabilities
  • Control groups (cgroups)
    他用到 kernel 許多特性,不過和 container 有關的主要是 kernel namespace 和 control groups。不過能做到 container 主要是靠 kernel namespace。


    namespace 是什麼呢? 以 pid namespace 為例,在每個 namespace 裡,每個 pid 都是唯一的。而不同的 namespace 裡, pid 可以重複。因此在 A namespace 哩,會有一個自以為 pid = 1 的程序,在 B namespace 哩,也會有一個自以為 pid = 1 的程序。如果 A 和 B 繼承自最上層的 namespace (也就是 host 所在的 namespace,叫做 C namespace ),那麼從 C namespace 的觀點來看,前面那兩個自以為是 pid = 1 的程序,其實他的 pid 都不為 1 。這樣可以做到程序隔離的功能,也可以做到虛擬化。 lxc 用到的 namespace 就 ipc, uts, mount, pid, network and user。這篇文章有詳細說明 docker 用到的 namespace 作用。http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part

    這樣要怎麼做到模擬作業系統呢? 一個 linux distributions 就是一個 linux kernel 加上很多其他程式。 namespace 沒辦法跑 kernel。不過那不重要,反正核心都差不多。剩下的程式,就可以把它丟進 namespace 跑,而那些程式在跑的時候,會以為整台電腦都是他的(因為被 namespace) 隔離了。所以他會建立 init(pid = 1) 程序,再做一些其他開機步驟。有 system call 就由 host 的 kernel 來處理。他活在虛擬的世界,還活得很開心。

虛線表示那兩個是同一個。箭頭表示程序間的父子關係。有箭頭的表示子程序。

                       
    如上圖所示, A namespace 的程序無法看到 root namespace 的程序。因此,我們可以在 A namespace 跑 centos 的開機程序,也可跑 ubuntu 的開機程序。在 A namespace 哩,無法察覺 root namespace 存在,他們會以為自己就是 root namespace。

    解決了程序的問題後,還有一個問題,檔案系統。基本上,用原本系統的檔案系統即可做到模擬作業系統,不過 docker 選擇使用 aufs,選用 aufs 還有個好處是可以做版本控制。

    Aufs,全名叫 advanced multi layered unification filesystem。恩,名字不太重要。基本上就分層。以下圖為例:

                                   


       上圖表示至少兩個檔案系統,你可以從 Delete file B 這個往下看,那你看到的檔案系統會是對 Base 加了C(B一加刪,最後看不到)。而從 Add file E 往下看,則看到的檔案系統是對 Base 加了 ADE 這三個檔案。每層只記錄和上一層的差異。要顯示完整的作業系統則把自己到 base 各層 union 起來。

       介紹完 lxc 和 aufs , 來看看 docker 如何整合。



    如上圖所示, docker 把客端作業系統安裝在 aufs 上,你可以 commit 你的修改,他會把你的修改當作一層新的 layer 蓋在上一層。當你要 run 客端作業系統時,他會在你要跑的 image 上面再蓋一層,叫做 container(image 是 read only)。之後 docker 會建立新的 namespace,把 namespace 的 root directory,然後執行 debian 的 init 程序(開機程序),這樣客端作業系統就成功執行了。


如果你想更了解 docker,強烈推薦閱讀  http://blog.dotcloud.com/category/under-the-hood
也可以參考我演講投影片:http://www.slideshare.net/ya790026/docker-33456641

參考資料: