Linux根文件系統分析之init和busybox

  Hi,你們好!我是CrazyCatJack。今天給你們講解Linux根文件系統的init進程和busybox的配置及編譯。html

  先簡單介紹一下,做爲一個嵌入式系統,要想在硬件上正常使用的話。它的軟件組成大概有這三部分:1)bootloader  2)嵌入式系統kernel  3)根文件系統 。這其實很是好理解,類比於PC上的操做系統,首先咱們須要相似BIOS的東東,來控制系統的啓動項,決定從哪裏啓動,怎樣啓動,啓動什麼。在嵌入式系統裏bootloader就起着這樣的做用。再者,咱們須要一個已經配置、編譯、連接好的內核。固然,若是本身有源碼的話,徹底能夠本身修改源碼,生成本身想要的內核。最後,咱們須要根文件系統,在windows下,咱們的系統有不少分區,那麼在嵌入式系統下,咱們就須要根文件系統來完成這項工做。PC上的應用程序都是從硬盤讀取,咱們嵌入式系統上的應用程序都是從根文件系統上讀取。所以,製做或修改出符合本身要求的根文件系統就變成了一件必需要作的事。今天CrazyCatJack給你們帶來的是根文件系統的分析,咱們只有分析出,在嵌入式系統下,根文件系統是怎樣配置、怎樣啓動應用程序。才能修改或製做出本身的根文件系統。也就是說:要理解才行。此次博客CrazyCatJack給你們分析,那麼下一篇blog就給你們演示本身構建根文件系統了,我會盡可能用簡潔明瞭的語言把本身會的知識分享給你們,讓你們最終也能作出本身的根文件系統^_^linux

 

 

1.原理分析shell

1.內容簡介小程序

  以前有博友給CrazyCatJack提建議,原理和代碼分開寫。最好還配上圖片和流程圖,所以,今天CCJ給你們配圖,相信更容易理解吧!今天講的內容以下面的流程圖:windows

 

  1)在Linux kernel的源代碼中,對如何啓動應用程序有着明確的定義。首先咱們須要掛載根文件系統,只有正確掛載了根文件系統,纔可以從根文件系統中讀出應用程序。咱們啓動的第一個程序就是init程序。init進程完成了對應用程序的各項配置(進程ID、執行時機、命令、終端、下一個執行的進程等),並最終依據配置執行了應用程序。模塊化

  2)要執行應用程序,首先進行配置。配置文件inittab裏有着對應用程序的詳細配置,這些都是C文件。init進程讀出配置、分析配置並配置應用程序、配置C庫(用到不少C庫裏的函數)。最後執行程序。函數

  3)busybox的話,實際上是一個方便移植的模塊。它主要的功能在其README自述文檔中有着精煉的定義:BusyBox combines tiny versions of many common UNIX utilities into a single small executable.它的確是很方便的工具,並且自己模塊化、可配置且極具移植性。它的這一特色充分體現了busybox的存在乎義和潛在價值。並且你們徹底沒必要擔憂它的執行效率。在它的自述文檔中,明白的寫着BusyBox has been written with size-optimization and limited resources in mind, both to produce small binaries and to reduce run-time memory usage.也就是說,不但將這些實用程序集結到了一個小的可執行文件中,並且它自己執行速度快,體積小,佔用運行內存少。(怎麼感受博主是在爲busybox作廣告  -_-|| ,但誰叫它這麼給力呢)。有不少的程序都是指向busybox的。以下圖:工具

 

 

  你們能夠看到這兩個程序都指向了busybox。也就是說,執行這個命令自己和執行busybox加上這個命令的效果是同樣的。以下圖:post

 

  可能有人會問:這有什麼好的?這樣寫命令豈不是很麻煩。每次都要多加一個busybox。那請問ls你是何時編譯安裝的呢?也就是說,若是不用busybox,那麼你須要本身從bin目錄下和sbin目錄下找到你要安裝的程序源代碼,手動一個一個的編譯生成文件,再安裝。而UNIX的經常使用工具上百個,你能一個一個的這樣作嗎?若是你能夠,那在下佩服!^_^學習

busybox有一整套的工具,將這些你須要的工具統一編譯安裝,瞬間生成大量實用工具。

 

 

2.代碼講解

1.檢測根文件系統並啓動init

  首先,在kernel源文件中的Main.c文件中,init_post函數完成了對根文件系統和控制檯的檢測,並啓動init進程。代碼以下:

 

DIR:Main.c-init_post函數

static
int noinline init_post(void) { free_initmem(); unlock_kernel(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy(); if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); (void) sys_dup(0); (void) sys_dup(0); if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); } run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); panic("No init found. Try passing init= option to kernel."); }

  這裏咱們看到,它首先作了一項工做:打開console文件。而後複製、複製。對應於代碼:

   open("/dev/console")  
   (void) sys_dup(0);    
   (void) sys_dup(0);   

  也就是說打開控制檯,這個控制檯會對應標準輸入、標準輸出、標準錯誤。它多是顯示器、鍵盤鼠標等等。接着往下看:

  if (execute_command) {

    run_init_process(execute_command);

  }
  其中execute_command爲命令行參數,在咱們uboot傳給內核的參數中,init設置了init=/linuxrc(關於uboot的源碼講解見我以前的博客),因此這裏的execute_command就等於/linuxrc。若是傳值成功則執行run_init_process,不然打印printk(KERN_WARNING "Failed to execute %s.  Attempting ""defaults...\n", execute_command);並接着往下執行,接着檢測其餘位置的init進程,若成功則執行,失敗則接着往下檢測,直到找到合適的init進程或者沒找到則打印panic("No init found.  Try passing init= option to kernel.");

  這裏咱們能夠作一個實驗,檢測實際的根文件系統檢測是否和這個init_post函數定義的同樣,若是和咱們分析的一致,則證實咱們的分析是正確的。那麼這裏咱們能夠先擦除root分區,也就是說擦除根文件系統,而後啓動只有bootloader和kernel的系統。結果Linux kernel在啓動過程當中,打印出了以下的信息:

 

VFS: Mounted root (yaffs filesystem).  
Freeing init memory: 140K
Warning: unable to open an initial console.
Failed to execute /linuxrc.  Attempting defaults...
Kernel panic - not syncing: No init found.  Try passing init= option to kernel.

  首先是VFS:掛載了根文件系統,可能你們會問,不是剛剛已經擦除了根文件系統,爲何說這裏掛載了?這是由於當咱們擦除了根文件系統的root分區後,Linux kernel認爲它是任意格式的根文件系統(其實分區裏面什麼都沒有),而默認的又是yaffs格式,因此這裏說掛載了yaffs格式的根文件系統。這裏的warning難道不是和咱們init_post函數中的printk(KERN_WARNING "Warning: unable to open an initial console.\n");相對應嗎?同理,Failed to execute /linuxrc.  Attempting defaults...和printk(KERN_WARNING "Failed to execute %s.  Attempting ""defaults...\n", execute_command);相對應。Kernel panic - not syncing: No init found.  Try passing init= option to kernel.

panic("No init found.  Try passing init= option to kernel.");相對應。

  這證實咱們的分析是正確的。

 

2.init進程分析

  在上面的內容簡介中,CCJ提到了busybox這個實用工具。這裏,咱們的init程序也是由busybox編譯生成的。若是你有busybox,打開它的文件,你會發現每一個小程序都有一個文件夾,裏面放的是它的編譯文件,必定會有XX.c。好比init.c、ls.c。在每一個XX.c中必定有XX_main函數,定義了這個小程序將如何執行。這裏咱們就打開init.c看init_main函數。init_main很長,CCJ不會把它直接拷貝過來讓你們看得眼暈,我會一點一點幫助你們分析。首先,init進程的執行順序大概是這樣的:讀取配置文件inittab(若讀取失敗則用默認配置),解析配置文件,根據配置執行應用程序。那麼這裏咱們先瞧瞧inittab配置文件是怎樣定義的:

 

查看inittab文件得知inittab格式:
Format
for each entry: # <id>:<runlevels>:<action>:<process> #id: The id field is used by BusyBox init to specify the controlling tty for the specified process to run on.  #runlevels: The runlevels field is completely ignored. #action: Valid actions include: sysinit, respawn, askfirst, wait, once, # restart, ctrlaltdel, and shutdown. #process: Specifies the process to be executed and it's command line.

  這是咱們配置相關的格式要求。CCJ幫你們整理出來了。通俗的講,這裏的<id>就是終端,標準輸入輸出和錯誤。這裏的<runlevels>如同註釋,是徹底忽略掉的。<action>是程序執行的時機,可取的值有sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, and shutdown.<process>表示將要執行的應用程序或腳本。

  在init_main函數中,調用了parse_inittab函數來讀取配置文件inittab。這裏咱們能夠經過默認的配置語句,倒推出默認的配置文件內容,是否是頗有意思?^_^咱們一塊兒來看:

 

DIR: init.c-parse_inittab函數
     /* Reboot on Ctrl-Alt-Del */ new_init_action(CTRLALTDEL, "reboot", ""); /* Umount all filesystems on halt/reboot */ new_init_action(SHUTDOWN, "umount -a -r", ""); /* Swapoff on halt/reboot */ if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", ""); /* Prepare to restart init when a HUP is received */ new_init_action(RESTART, "init", ""); /* Askfirst shell on tty1-4 */ new_init_action(ASKFIRST, bb_default_login_shell, ""); new_init_action(ASKFIRST, bb_default_login_shell, VC_2); new_init_action(ASKFIRST, bb_default_login_shell, VC_3); new_init_action(ASKFIRST, bb_default_login_shell, VC_4); /* sysinit */ new_init_action(SYSINIT, INIT_SCRIPT, "");

  這是在讀取配置文件失效時的默認配置。這裏涉及到了一個函數 new_init_action 。它實際上的工做就是把各個程序的執行時機、命令行、控制檯參數分別賦值給結構體,並把這些結構體組成一個單鏈表。這也就是咱們所說的配置。它的聲明是:static void new_init_action(int action, const char *command, const char *cons);這三個參數不正是inittab配置文件中的配置命令嗎?他們分別對應於<action>、<process>、<id>。按照<id>:<runlevels>:<action>:<process>的順序將參數填充進去不就是咱們須要的默認配置文件嗎^_^

 

從默認的new_init_action反推出默認的配置文件
/* Reboot on Ctrl-Alt-Del */ new_init_action(CTRLALTDEL, "reboot", ""); ::ctrlaltdel:reboot /* Umount all filesystems on halt/reboot */ new_init_action(SHUTDOWN, "umount -a -r", ""); ::shutdown:umount -a -r /* Swapoff on halt/reboot */ if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", ""); //PC機上當內存不夠,就把應用程序移動到硬盤,而後讓新的應用程序讀入內存。在嵌入式Linux中不長用 /* Prepare to restart init when a HUP is received */ new_init_action(RESTART, "init", ""); ::restart:init /* Askfirst shell on tty1-4 */ new_init_action(ASKFIRST, bb_default_login_shell, ""); ::askfirst:-/bin/sh new_init_action(ASKFIRST, bb_default_login_shell, VC_2); tty2::askfirst:-/bin/sh new_init_action(ASKFIRST, bb_default_login_shell, VC_3); tty3::askfirst:-/bin/sh new_init_action(ASKFIRST, bb_default_login_shell, VC_4); tty4::askfirst:-/bin/sh /* sysinit */ new_init_action(SYSINIT, INIT_SCRIPT, ""); ::sysinit:/etc/init.d/rcS

  其中,粉色部分的內容就是咱們根據inittab配置文件格式,將C語句中new_init_action函數中的參數一一組合成默認配置文件語句,合起來就是默認配置文件的內容。這裏,CCJ認爲有必要深刻了解一下new_init_action函數。由於它是配置的核心:

 

DIR:init.c-new_init_action函數

static void new_init_action(int action, const char *command, const char *cons)
{
    struct init_action *new_action, *a, *last;

    if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST))
        return;

    /* Append to the end of the list */
    for (a = last = init_action_list; a; a = a->next) {
        /* don't enter action if it's already in the list,
         * but do overwrite existing actions */
        if ((strcmp(a->command, command) == 0)
         && (strcmp(a->terminal, cons) == 0)
        ) {
            a->action = action;
            return;
        }
        last = a;
    }

    new_action = xzalloc(sizeof(struct init_action));
    if (last) {
        last->next = new_action;
    } else {
        init_action_list = new_action;
    }
    strcpy(new_action->command, command);
    new_action->action = action;
    strcpy(new_action->terminal, cons);
    messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
        new_action->command, new_action->action, new_action->terminal);
}

/* Set up a linked list of init_actions, to be read from inittab */
struct init_action {
   struct init_action *next;
   int action;
   pid_t pid;
   char command[INIT_BUFFS_SIZE];
   char terminal[CONSOLE_NAME_SIZE];
  };

  說實話,剛開始感受這個函數蠻繞的,CCJ根本看不懂它要幹嗎,可是咱們能夠寫在紙上,將它要作的工做,一步一步寫下來就明白了。無論是循環,仍是鏈表,仍是結構體。它禁不住推敲的。new_init_action函數用於配置,參數爲執行時機、命令行、控制檯。結構體指針new_action開始指向上一個配置過的程序(其存儲在結構體,參數是上一個程序的執行時機、命令行、控制檯),這裏首先進行了一個If判斷,若是控制檯等於bb_dev_null(宏定義等於 /dev/null)且action爲ASKFIRST那麼直接返回,不進行任何配置。接着這個for循環算是這個函數的一個重點吧,首先令結構體指針init_action_list賦值給a和last。這裏的init_action_list(宏定義爲NULL)開始爲NULL,後來指向第一個配置的程序。也就是說,遍歷全部配置過的程序,若是這個程序以前被配置過(命令行和控制檯同時等於當前遍歷的程序),那麼執行時機action被從新賦值爲當前值。通俗的說,這個for爲了不程序重複配置,查找以前配置過的程序有沒有當前要配置的程序,若是有,則只改變其執行時機action。命令行和控制檯不變。接下來,爲new_action從新分配內存,而且給它賦值,令它的各項信息等於當前的程序。在上面的if語句中,last->next=new_action,也就是說,將全部程序的配置結構體連城一個單鏈表。new_init_action函數講解完畢。

  通過上面的講解,咱們明白了Linux根文件系統中,對於程序的配置是在parse_inittab函數完成的,它打開配置文件inittab,將程序信息一一填入結構體init_action,並將它們鏈接成單鏈表。如今配置已經完成,下一步是執行了。接着看init_main中的代碼是怎樣執行應用程序的:

 

init_main程序簡要結構:

    init.c->init_main->

               parse_inittab

               run_actions(SYSINIT);
               run_actions(WAIT);
               run_actions(ONCE);

               while (1) {
                run_actions(RESPAWN);
                run_actions(ASKFIRST);
                wpid = wait(NULL);    
                while (wpid > 0) {
                    a->pid = 0;  
                    }
                }

  上面是簡化的init_main的程序結構,上面只有比較主要的幾個函數。第一個函數parse_inittab完成了配置。那麼下一步開始執行時機類型爲sysinit, respawn, askfirst, wait, once, restart, ctrlaltdel, and shutdown類型的應用程序。那麼正如你們看到的,執行應用程序主要涉及到的是run_actions函數。這裏咱們打開它:

 

DIR:init.c-run_actions函數

static void run_actions(int action)
{
    struct init_action *a, *tmp;

    for (a = init_action_list; a; a = tmp) {
        tmp = a->next;
        if (a->action == action) {
            /* a->terminal of "" means "init's console" */
            if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {
                delete_init_action(a);
            } else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
                waitfor(a, 0);
                delete_init_action(a);
            } else if (a->action & ONCE) {
                run(a);
                delete_init_action(a);
            } else if (a->action & (RESPAWN | ASKFIRST)) {
                /* Only run stuff with pid==0.  If they have
                 * a pid, that means it is still running */
                if (a->pid == 0) {
                    a->pid = run(a);
                }
            }
        }
    }
}

  這裏咱們以SYSINIT類型的程序爲例,能夠看到,在if的分支語句中,若是發現咱們傳入的參數是SYSINIT,即此刻運行SYSINIT類型的應用程序。那麼waitfor(a,0),就是執行應用程序,並等待它執行完畢。具體對應於waitfor函數的run(a);(建立process子進程)和waitpid(runpid, &status, 0);(等待它結束)。執行完waitfor後,會執行delete_init_action(a);這個函數的做用是這個SYSINIT類型的程序執行完一次,就在init_action_list鏈表裏刪除。那麼其餘執行時機類型的程序依此類推,前三個執行詳細以下:

 

 SYSINIT、WAIT、ONCE時機的程序運行機制

        run_actions(SYSINIT);   waitfor(a,
0); //執行應用程序,等待他執行完畢   run(a); //建立process子進程   BB_EXECVP(cmdpath, cmd);   waitpid(runpid, &status, 0);//等待它結束    delete_init_action(a); //執行完一次,就在init_action_list鏈表裏刪除 run_actions(WAIT);   waitfor(a, 0); //執行應用程序,等待他執行完畢   run(a); //建立process子進程   BB_EXECVP(cmdpath, cmd);   waitpid(runpid, &status, 0);//等待它結束   delete_init_action(a); //執行完一次,就在init_action_list鏈表裏刪除       run_actions(ONCE);   run(a); //建立process子進程 delete_init_action(a); //執行完一次,就在init_action_list鏈表裏刪除 //可見init進程不會等待ONCE子進程執行完畢。

  接下來的RESPAWN和ASKFIRST和上面的三個執行時機略有不一樣,它們處於while(1)循環裏,而且,只有當進程的PID號等於0時才從新執行該進程。其運行機制以下:

 

RESPAWN和ASKFIRST運行機制
 
   while (1) {
                run_actions(RESPAWN);
                    if (a->pid == 0) {
                    a->pid = run(a);
                        }

                run_actions(ASKFIRST);
                    if (a->pid == 0) {
                    a->pid = run(a);
                        }
                wpid = wait(NULL);    //等待子進程X退出
                while (wpid > 0) {
                    a->pid = 0;   //子進程X退出後,就設置X的pid等於0.而後從新進入死循環,再次執行這個已經退出的子進程X。也就是說哪一個子進程退出,再從新執行哪一個,而不是所有再執行。
                    }
                }

  也就是說,只有當該進程執行完畢退出時,設置其PID爲0,而後再次執行該進程。RESPAWN和ASKFIRST執行時機的程序是要重複執行已經執行完畢的程序。對於其餘程序則不重複執行。可能你們會問,那麼RESPAWN和ASKFIRST執行時機的程序有什麼不一樣?大體有兩點:1)RESPAWN和ASKFIRST的執行順序不一樣。2)在run函數中,對於ASKFIRST類型的程序會先打印:"\nPlease press Enter to activate this console. "而且等待回車後纔會繼續執行。

  如今咱們已經講解了SYSINIT、WAIT、ONCE、RESPAWN、ASKFIRST類型的程序。至於CTRLALTDEL類型的程序則是在init_main函數的起始部分就作了信號量的定義。也就是說,同時按下CTRL+ALT+DEL鍵,就會向內核發送信號量,並執行run_actions(CTRLALTDEL);其定義以下:

  

 

DIR:init.c-init_main函數    

    signal(SIGHUP, exec_signal);
    signal(SIGQUIT, exec_signal);
 signal(SIGUSR1, shutdown_signal);
 signal(SIGUSR2, shutdown_signal); signal(SIGINT, ctrlaltdel_signal);
    signal(SIGTERM, shutdown_signal);
    signal(SIGCONT, cont_handler);
    signal(SIGSTOP, stop_handler);
    signal(SIGTSTP, stop_handler);    

  shutdown類型的程序則比較複雜,由於它完成的是要關閉系統的動做。在init.c中有一個函數shutdown_system就是完成關閉系統的工做,以下:

 

static void shutdown_system(void)
{
    sigset_t block_signals;

    /* run everything to be run at "shutdown".  This is done _prior_
     * to killing everything, in case people wish to use scripts to
     * shut things down gracefully... */
    run_actions(SHUTDOWN);

    /* first disable all our signals */
    sigemptyset(&block_signals);
    sigaddset(&block_signals, SIGHUP);
    sigaddset(&block_signals, SIGQUIT);
    sigaddset(&block_signals, SIGCHLD);
    sigaddset(&block_signals, SIGUSR1);
    sigaddset(&block_signals, SIGUSR2);
    sigaddset(&block_signals, SIGINT);
    sigaddset(&block_signals, SIGTERM);
    sigaddset(&block_signals, SIGCONT);
    sigaddset(&block_signals, SIGSTOP);
    sigaddset(&block_signals, SIGTSTP);
    sigprocmask(SIG_BLOCK, &block_signals, NULL);

    message(L_CONSOLE | L_LOG, "The system is going down NOW!");

    /* Allow Ctrl-Alt-Del to reboot system. */
    init_reboot(RB_ENABLE_CAD);

    /* Send signals to every process _except_ pid 1 */
    message(L_CONSOLE | L_LOG, "Sending SIG%s to all processes", "TERM");
    kill(-1, SIGTERM);
    sync();
    sleep(1);

    message(L_CONSOLE | L_LOG, "Sending SIG%s to all processes", "KILL");
    kill(-1, SIGKILL);
    sync();
    sleep(1);
}

  你們能夠看到,它先照例執行了run_actions(SHUTDOWN)。而後禁止了全部的信號傳送。打印"The system is going down NOW!" 而後關閉全部的進程,最後關閉系統。init進程講解完畢。

 

3.busybox的配置、編譯和安裝

  首先,咱們打開busybox自帶的INSTALL文件查看咱們該怎樣配置、編譯和安裝busybox。

Building:
=========

The BusyBox build process is similar to the Linux kernel build:

  make menuconfig     # This creates a file called ".config"
  make                # This creates the "busybox" executable
  make install        # or make CONFIG_PREFIX=/path/from/root install

The full list of configuration and install options is available by typing:

  make help

  文件中寫的很明確,編譯busybox和編譯linux kernel差很少。若是你們看了我以前有關linux內核的配置、編譯和鏈接的博客就不會對這三條命令感到陌生了。那麼首先要make menuconfig生成配置文件.config。而後make生成busybox可執行文件。最後make install安裝busybox。首先執行 make menuconfig:

 

  執行事後會出現圖形界面,這方便了咱們對busybox的配置,這裏咱們只須要手動選擇須要編譯安裝的項目,其中有不少實用的工具,最後進行保存就能夠了。你們能夠看到有不少的選項提供給咱們。選擇後,最後退出,save便可。而後執行make生成可執行busybox文件。下一步就是安裝了。

注意:若是你是在虛擬機上安裝busybox,安裝不可直接執行make INSTALL,必須在虛擬機下本身建立一個文件夾,將安裝路徑指向這個文件夾的路徑。再執行 make CONFIG_PREFIX=/path/from/root install  不然會破壞系統。

  通過簡單的配置編譯和安裝,咱們最終就能使用busybox這個方便的工具了。

 

敬告:

本文原創,歡迎你們學習轉載^_^

但請尊重博主CrazyCatJack的版權。

轉載請在顯著位置註明:

                 博主ID:CrazyCatJack

                 原始博文連接地址:http://www.cnblogs.com/CrazyCatJack/p/6184564.html

           

 

 

題外話:

  這大概是博主寫過最長的博客了吧。。。苦笑~寫以前沒有想到會寫這麼長,可是由於已經作出了流程圖,三個方面的內容就必須得寫下去了,本身挖的坑本身填了 T_T 。可是仍是以爲很值得的。剛開始寫博客就是爲了梳理本身的知識,將學過的內容鞏固,並不在意你們是否能看懂。(實際上我懷疑是否有人會看個人博客)因此對本身寫的內容要求不是很高。可是當博主發現竟然真的有人會看,那麼再這樣寫就有些對不起你們了。也有博友給博主提建議,改進博客的表現形式。博主悉心聽取了,正一步一步完善博文,方便更好的分享給你們。

  博主是一路自學過來的,從5一、stm3二、Freescale K系列、再到如今的ARM和嵌入式Linux。看視頻,看PDF,買板子,買傳感器,作項目,走到今天。相信不少博友也是和博主同樣吧。我之因此堅持到如今,就是但願創造更美好的事物。比起毀滅的力量,我更想獲得創造的力量。毀滅一個事物很容易,但創造一個事物很難。無論是機器人也好、智能穿戴設備也好,創造這些insteresting、exciting、creative的東西,這是個人理想。

  可是我知道一我的的力量是眇小的,雖然我作了一部分的工做,但這還遠遠不夠。我但願將知識分享給你們。集體的力量是不容小覷的。懂得分享纔會有更多思惟的火花碰撞,不一樣領域的人們彼此分享,幫助。這樣的力量是會改變時代的。就像人類大腦裏的神經元,一條神經元可能只會傳達極其簡單的訊息,好比0和1。可是當這種訊息在大量的神經元間彼此傳遞,愈來愈多,愈來愈快。就會造成很是高級的事物:思想。這是一條神經元遠遠沒法作到和想象獲得的。

 

 

 

 

 

 

CCJ

2016-12-16      22:02:20

相關文章
相關標籤/搜索