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

相关文章
相关标签/搜索