kernel移植——从三星官方内核开始移植

以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除。

一、内核移植初体验

1、获取三星官方的内核源码包

三星SMDKV210开发板附带的光盘里有内核源码包:下载地址。

2、构建移植环境

(1)Windows下建立SI工程(用来分析)

  • 为了简单与方便,建立SI工程前先删除不必要的目录和文件,包括arch目录下非arm架构的目录文件,以及arch/arm目录下不是三星的mach-xxx、plat-xxx目录。

(2)在ubuntu下解压一份源码包(根据分析进行修改)

3、内核配置与编译

(1)在主Makefile文件的193行之后添加以下内容:

#added by xjh 
ARCH = arm
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-

(2)执行“make smdkv210_android_defconfig”。

  • 在arch/arm/configs目录下有很多配置文件,我们从中找到与三星相关的、与我们开发板最像的“smdkv210_android_defconfig”。

(3)执行“make menuconfig”。

  • 忽略弹出的配置界面,因为目前还不知道修整哪些内容。

(4)执行“make -j4”。

  • 直接执行make则会直接单线程编译,如果make -j4则会4线程编译,编译效率会提高。
  • 多少线程编译则要看是多少核的cpu:四核则可以4线程,双核则2线程。

4、通过tftp将编译得到的zImage下载到开发板

  • 编译得到的内核镜像在arch/arm/boot目录下。
  • 通过tftp将镜像下载到开发板的方法见博客:利用tftp将镜像下载到开发板_天糊土的博客

5、运行结果与分析

(1)运行结果

没有显示linux自解压代码打印出的“Uncompressing Linux... done, booting the kernel.”。


(2)现象分析

1)这个现象说明zImage没有被解压成功,因此问题出在解压相关的部分。

2)进一步分析,问题可能与“内核解压后的代码放在哪个内存地址处(暂且叫“解压地址”)”有关。这个解压地址可以由内核配置,如果不对,则内核不能运行。

3)内核的链接地址是一个虚拟地址,而自解压代码解压内核时需要物理地址(即解压地址是物理地址,因为此时mmu还没开,内核还没有开始运行),因此链接地址对应的物理地址必须等于解压地址,否则自解压之后内核无法运行。

4)因此现在问题变成:内核的链接等于多少?内核配置的解压地址是多少?

5)内核的链接地址与对应的物理地址,可以在arch/arm/kernel/head.S中可以查到。

PAGE_OFFSET这个宏定义在/arch/arm/include/asm/memory.h文件中,内容如下。

#define PAGE_OFFSET		UL(CONFIG_PAGE_OFFSET)

而在配置内核时生成的.config文件中,CONFIG_PAGE_OFFSET赋值为0xC0000000。

CONFIG_PAGE_OFFSET=0xC0000000

所以PAGE_OFFSET=0xC000_0000。

在/arch/arm/mach-s5pv210/include/mach/memory.h文件中,对PHYS_OFFSET这个宏有如下定义:

根据下面“(5)现象分析”,PHYS_OFFSET这个宏的值应该为0x30000000。

而TEXT_OFFSET这个宏的值应该为0x00008000。(待求证)

经上述分析得知,内核的链接地址KERNEL_RAM_VADDR为0xC0008000,对应的物理地址KERNEL_RAM_PADDR是0x30008000,那么解压地址应该是30008000。


(3)修改解压地址

解压地址定义在/arch/arm/mach-s5pv210/Makefile.boot文件中。

我们在该文件末尾添加以下内容:


(4)编译后重新下载,查看运行结果

因为只修改了一些链接参数,不需要重新配置编译,所以make很快完成。

运行结果如下图所示,自解压代码的解压信息已经显示出来,但内核还是没有运行。


(5)现象分析

1)我们在uboot中配置的地址空间是从3000 0000开始的,而不是2000 0000开始的。

2)但在/arch/arm/mach-s5pv210/include/mach/memory.h文件中,PHYS_OFFSET却定义为2000 0000。

3)因此需要将PHYS_OFFSET从20000000改到30000000,如下所示:


(6)重新编译,运行结果

内核打印出很多运行信息。

二、内核中的机器码

内核支持什么架构、支持哪款cpu,这是如何确定的?主要是通过机器码来确定的。内核中定义了一份机器码,uboot也会给内核传递一个机器码。在内核启动的汇编阶段,head.S文件对比uboot传给内核的机器码和内核定义的机器码,如果匹配则继续启动。

本节内容,主要讲内核如何定义机器码的。其实在内核源码——C语言阶段的start_kernel函数的第二点的第3点的第(4)点也有提及。


1、MACHINE_START宏

这个宏的内容如下:

(1)这个宏用来定义一个数据类型为struct machine_desc的变量,而且这个变量会被定义到一个特定段.arch.info.init,因此这个变量将来会被链接器链接到这个.arch.info.init段中。

(2)比如在/arch/arm/mach-s5pv210/mach-smdkv210.c中,有MACHINE_START(SMDKV210, "SMDKV210"),这就定义了一个数据类型为struct machine_desc的变量,这个变量名为__mach_desc_SMDKV210。这个变量是smdkv210这个开发板的抽象或者说表征。

static const struct machine_desc __mach_desc_SMDKV210	\__used							\__attribute__((__section__(".arch.info.init"))) = {	\.nr		= MACH_TYPE_SMDKV210,//机器码2456		\.name		= "SMDKV210",.phys_io	= S3C_PA_UART & 0xfff00000,.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,.boot_params	= S5P_PA_SDRAM + 0x100,.init_irq	= s5pv210_init_irq,.map_io		= smdkv210_map_io,.init_machine	= smdkv210_machine_init,.timer		= &s5p_systimer,
};
  • 结构体成员.nr=MACH_TYPE_SMDKV210就是这个开发板的机器码。机器码值“MACH_TYPE_SMDKV210”在/include/generated/mach-types.h文件中定义,该文件在内核配置过程中自动生成。mach-types.h文件维护着该版本内核所支持的全部机器码。

  • 结构体成员“.name= "SMDKV210"”,表示该开发板的名称。

  • 结构体成员“.init_machine = smdkv210_machine_init”,定义了一个函数smdkv210_machine_init,该函数是一个硬件初始化函数,包含了内核中各种硬件的初始化信息。

(3)在/arch/arm/目录下有许多mach-xxx目录(不同目录代表不同CPU),这些mach-xxx目录中又有许多mach-xxx文件(同目录下的不同文件,表示相同CPU的不同开发板)。分析发现,每个mach-xxx.c文件中,都通过MACHINE_START宏,定义了一个表征某个开发板的struct machine_desc类型的变量。这些结构体变量都放到了.arch.info.init段,表示当前内核可以支持这些机器码对应的开发板。

(4)我们的目标开发板叫做X210,采用S5PV210这款CPU。在三星版本内核中到不到X210开发板对应的mach-x210.c文件,但为了减少移植工作量,在三星版本内核的/arch/arm/mach-s5pv210目录下寻找一个mach-xxx.c文件,使得该文件对应的开发板与X210开发板最为接近(使用相同的CPU),并以此文件为基础进行移植。

(5)九鼎的X210开发板是根据三星SMDKV210开发板进行设计的,因此要找SMDKV210开发板对应的文件,按理应该是mach-smdkv210.c文件。但分析/arch/arm/mach-s5pv210/Makefile文件后得知,内核配置生成的.config文件中定义CONFIG_MACH_SMDKV210后,实际绑定的是mach-smdkc110.c这个文件。因此实际上mach-smdkv210.c这个文件根本没用到。

(6)所以后续将以/arch/arm/mach-s5pv210/mach-smdkc110.c这个文件为基础进行移植。


2、硬件驱动的加载和初始化函数执行

/arch/arm/mach-s5pv210/mach-smdkc110.c文件中,有MACHINE_START(SMDKV210, "SMDKV210"),它展开后内容如下:

static const struct machine_desc __mach_desc_SMDKV210	\__used							\__attribute__((__section__(".arch.info.init"))) = {	\.nr		= MACH_TYPE_SMDKV210,//机器码2456		\.name		= "SMDKV210",.phys_io	= S3C_PA_UART & 0xfff00000,.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,.boot_params	= S5P_PA_SDRAM + 0x100,.init_irq	= s5pv210_init_irq,.map_io		= smdkv210_map_io,.init_machine	= smdkv210_machine_init,//初始化各种硬件.timer		= &s5p_systimer,
};

注意“.init_machine    = smdkv210_machine_init,”,init_machine这个成员指向一个机器硬件初始化函数smdkv210_machine_init,这个函数绑定了内核启动过程中会初始化的各种硬件的信息,在此函数中添加的硬件才会被初始化,没有在此函数中添加的硬件不会被初始化。

关于smdkv210_machine_init的更多介绍,见下文第五点“网卡的问题与解决”,或者驱动栏目中的“Linux设备驱动模型”的内容。

三、电源管理IC的问题与解决

1、OOPS信息(内核死亡信息)

(1)内核启动后会有打印信息,这些信息隐藏了问题所在。认真分析这些信息,从中找到对的或者错误的一些信息片段,才能帮助我们找到问题,从而解决问题。

(2)内核启动过程的错误信息的特征

由PC和LR的值可以看出,程序执行到dev_driver_string或者max8698_pmic_probe(这两个是函数或者汇编中的标号)符号部分的时候出错了。我们从这两个符号出发去寻找、思考可能出错的地方,然后试图解决。


2、错误分析

(1)max8698_pmic_probe,看名字是max8698这个电源管理IC的驱动安装函数那部分出错了,应该是开发板系统中配置了支持这个电源管理IC,于是启动时去加载它的驱动,结果驱动在加载执行的过程中出错了OOPS。

(2)这个驱动加载时为什么会出错?结合X210开发板的硬件实际情况来分析,X210开发板上根本就没有max8698这个电源管理IC,既然硬件都没有,执行驱动肯定会出错。之前移植三星版本uboot时,在lowlevel_init.S中也调用了电源管理IC初始化函数(PMIC_init),结果报错,我们屏蔽掉该函数的调用后,uboot就可以成功运行下去。

(3)为什么uboot和内核,都默认调用这个电源管理IC的初始化代码?因为三星SMDKV210开发板中用了max8698这个电源管理IC,因此三星的uboot和kernel中都默认支持这个。但是X210中是没用的,因此uboot和内核中都需要去掉该代码模块。


3、错误解决

(1)如何解决错误

在uboot中是直接改源代码,屏蔽掉那个初始化函数解决的。但在内核中不能这么干,因为linux kernel是高度模块化、高度可配置的,内核中每一个模块都是被配置项条件编译的,因此要去掉某个模块的支持,只需要重新配置去掉选项即可,不用改源代码。所以我们的关键就是要找它对应的配置项。

(2)解决错误的过程

先make menuconfig,然后输入/,搜索"MAX8698"这几个关键字,然后看到这个配置项的路径,然后到路径下去按N键去掉这个模块的支持,保存,重新编译即可。

 (3)运行结果

重新编译后下载运行,发现此问题被解决,内核再次启动后直接运行到挂载rootfs才出错。


4、心得体会

(1)CONFIG_MFD_MAX8698这个配置宏,决定了drivers目录下的max8698对应的驱动程序源代码是否被编译,决定了kernel启动过程中是否会调用一些max8698的相关的代码。

(2)kernel是高度模块化和可配置化的,所以在内核中做任何事情(添加、更改、去掉一个模块)都必须按照内核设定的方案和流程。

四、iNand的问题与解决

1、OOPS信息

(1)内核试图挂载根文件系统时失败,失败原因是unknown-block(不能识别块设备)。

(2)分析backstrace,得知错误信息的来源于mount_block_root函数。

mount_block_root函数位于/init/do_mounts.c文件中,内容如下:


2、错误分析

(1)在kernel启动时,uboot给内核传递一个cmdline,其中“root=xxx”指定rootfs在哪个设备上,比如“root=/dev/mmcblk0p2”表示rootfs在mmc设备0的第2个分区(设备0,即在SD0通道上的设备,即iNand)。

(2)unknown-block(0,0),意思是没找到mmc设备0的第2分区。

(3)为什么没找到mmc设备0的第2分区?可能是kernel启动过程中加载mmc驱动时出现问题,驱动没有发现mmc设备0,于是问题定位在mmc相关的驱动方面。

(4)对比待移植的三星版本的内核启动信息、九鼎版本的内核启动信息,从中发现待移植的三星版本的内核启动时,没有找到MMC设备(内置的iNand和外置的SD卡都没有找到),这说明肯定是驱动的问题,因此要移植mmc驱动。


3、知识补充

(1)SD/iNand本身都是由一个一个的扇区组成的,裸机课程中X210的启动时,BL1在SD卡的1扇区开始往后存放,SD卡的0扇区是不用的。SD卡的0扇区是用来放置MBR的。

(2)MBR就是用来描述块设备的分区信息的,事先定义了一个通用的数据结构来描述块设备的分区,我们只要将分区信息写入MBR中即可对该设备完成分区。MBR默认就是在块设备的第0个扇区上存放的。

(3)uboot中的命令fdisk可以对iNand进行分区(执行“fdisk -c 0”即可),而且这个fdisk命令的内部已经用代码写死(将iNand分为多少个分区、每个分区的大小等等信息,已经用宏定义规定好,当然我们也可以修改这些宏定义)。内核通过读取iNand的MBR即可获取iNand的分区信息,不需要通过uboot给内核传参时传递这个分区信息。

(4)如果开发板使用的是nandFlash,分区表一般是在内核中用代码构建的。因此nand版本的内核移植时,一般都需要移植、更改nand分区表。


3、解决错误

暂略。因为涉及到驱动的内容,后续会补充。

五、网卡的问题与解决

1、OOPS信息

内核启动时,关于网卡部分的错误信息显示如下:


2、错误分析

(1)出错的原因是三星版本的内核还没有移植网卡驱动(有网卡驱动但没有移植)。

(2)在/arch/arm/mach-s5pv210/mach-smdkc110.c文件中,smdkc110_machine_init()函数是整个开发板所有硬件的初始化函数。此函数通过platform_add_devices()函数加载的硬件,将来启动时就会被初始化,没有被加载的硬件将来启动时就不管。所以网卡这个硬件相关的初始化内容,肯定位于smdkc110_machine_init()函数中。

(3)分析得知与网卡DM9000有关的是:smdkc110_devices结构体和smdkc110_dm9000_set()函数,需要分别做移植。

(4)smdkc110_dm9000_set()函数用来设置DM9000相关的SROM bank的寄存器,它相当于uboot中dm9000移植时的dm9000_pre_init函数,只是读写寄存器的函数名称不同而已。

(5)smdkc110_devices结构体中与网卡DM9000有关的设置,追踪如下。

在/arch/arm/mach-s5pv210/mach-smdkc110.c文件中有:

在/arch/arm/plat-s5p/devs.c中有:

在/arch/arm/plat-s5p/devs.c中有:


3、错误解决

(1)首先,在make menuconfig中添加DM9000支持。

通过在弹出的界面按/搜索“DM9000”,找到配置项所在路径,然后选择Y。其实这一步本来就是Y,因此这里不用设置。但是有些内核可能默认不是Y,因此要设置。

(2)接着,将九鼎移植好的/arch/arm/mach-s5pv210/mach-x210.c文件中的smdkc110_dm9000_set()函数,覆盖掉三星版本内核的/arch/arm/mach-s5pv210/mach-smdkc110.c文件中的smdkc110_dm9000_set()函数。

这主要是为了减少工作量,但修改的过程需要理解。

(3)接着,在arch/arm/plat-s5p/devs.c中修改DM9000相关的数据配置。

1)上图的S5P_PA_DM9000,定义在/arch/arm/mach-s5pv210/include/mach/map.h中,它表示DM9000的IO基地址,具体的值是与 DM9000接在哪个bank有关的。根据实际情况,将其改为0x88000300,如下所示。

2)因为九鼎的uboot源码/include/configs/x210_sd.h中有如下内容:

所以根据uboot的设置情况,下面的+2改为+4,如下所示。

3)根据实际情况,将两个IRQ_EINT9改成IRQ_EINT10。

(4)重新编译后下载运行,查看结果

网卡移植成功后,其启动信息显示如下:

六、内核启动汇编阶段的调试

1、调试的原因

(1)由之前的内容可知,内核启动汇编阶段主要体现在arch/arm/kernel/head.S文件。该文件中首先进行三个校验(CPU id的校验、机器码的校验、tag的校验),接着创建页表,然后构建C语言运行环境,最后跳转到start_kernel函数。基本上能运行到start_kernel,内核移植就不太会出问题了。

(2)但有时候移植的内核启动后,根本没有启动信息出来。针对这个问题,希望能有一种调试手段来确定问题所在。


2、调试的方法

(1)将led点亮和熄灭的汇编代码,复制粘贴到head.S文件中的合适位置,内核启动后根据led的表现来标明代码有无运行。

(2)典型的led函数如下所示。

/**************************************************************************
*函数作用:熄灭X210开发板的三颗LED灯
*函数说明
(1)寄存器r0,r1,r2在head.s文件中是用来给内核传参的,类似于全局变量。
因此不能用这些寄存器,我们可以使用那些没有被使用的寄存器,这里用r3,r4。
(2)X210开发板有四颗LED灯,启动时四颗都是半亮的。这个函数用来熄灭其中的三颗。
我们可以通过LED是否熄灭得知该函数之前的内核代码是否运行正常。
***************************************************************************/led:// 第一步:把0x11111111写入0xE0200240(GPJ0CON)位置ldr r3, =0x11111111	 ldr r4, =0xE0200240		str r3, [r4]  // 把r3中的数写入到r4中的数为地址的内存中去    // 第二步:把0xff写入0xE0200244(GPJ0DAT)位置ldr r3, =0xff // 开发板启动时led半亮ldr r4, =0xE0200244str r3, [r4]  // 把ff写入到GPJ0DAT寄存器中,引脚即输出高电平,三颗LED熄灭// 函数需要返回mov pc,lr          

3、动手测试

首先,在head.S文件中合适的地方(函数集中域处)定义这个led这个函数;

然后,在head.S文件中的某个地方调用这个led函数;

最后,重新编译内核并下载至开发板,通过查看LED的亮灭情况判断这段代码是否被运行。如果运行了,则说明调用这个led函数之前的内容都没有问题的;如果没有运行,则说明错误发生在调用这个led函数之前。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android中实时视频传输(摄像头实时视频传输)解决方案二

为什么80%的码农都做不了架构师?>>> 1、使用FFMpeg进行视频采集,使用Live555进行RTP传输,使用VideoView进行播放。 csdn提到:重载FrameSource,写一个服务类,可以从FrameSource的派生类读取帧数…

很好的 .NET 换肤软件 IrisSkin

当前.NET下提供换肤的控件有IrisSkin和DotNetSkin。但是DotNetSkin提供的Demo版本,功能有限制,一时找不到可以破解的完全版。IrisSkin 的功能不比DotNetSkin差,而且使用简单。不需要原始文件,只需要dll文件和皮肤文件。详细信息可…

定时器——S5PV210定时器的理论与操作

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。 一、定时器简介 1、定时器的含义 定时器作为SoC的外设,主要用来实现定时执行代码的功能,它相对SoC而言,就像闹钟相对于人的意义一样。定时器内部的计数器每隔一个…

gson的简单使用方法

gson和其他现有java json类库最大的不同时gson需要序列化得实体类不需要使用annotation来标识需要序列化得字段,同时gson又可以通过使用annotation来灵活配置需要序列化的字段。 下面是一个简单的例子: publicclassPerson {privateString name;privatein…

String.Format格式说明

C#格式化数值结果表字符说明示例输出C货币string.Format("{0:C3}", 2)$2.000D十进制string.Format("{0:D3}", 2)002E科学计数法1.20E0011.20E001G常规string.Format("{0:G}", 2)2N用分号隔开的数字string.Format("{0:N}", …

linux bjobs

bjobs 查看正在进行的job bkill job id 杀死job 转载于:https://www.cnblogs.com/zk-szd1314/p/11322460.html

uboot的移植——DM9000移植的理论基础

以下内容源于朱有鹏嵌入式课程的学习,如有侵权请告知删除。 一、网卡相关的基本知识 1、DM9000网卡芯片和SoC的连接 如上图所示,DM9000网卡芯片是通过SROM总线接口,或者说SROM控制器接入SoC的。下面分别介绍SROM控制器、DM9000网卡芯片的相关…

mysql“Access denied for user 'root'@'localhost'”问题的解决

我的系统是Ubuntu12.04,最近新装好的mysql在进入mysql工具时,总是有错误提示:# mysql -uroot -pEnter password:ERROR 1045 (28000): Access denied for user rootlocalhost (using password: NO)使用网上介绍的方法修改root用户的密码:# mys…

ServiceStack.Redis之IRedisClient第三篇

事实上,IRedisClient里面的很多方法,其实就是Redis的命令名。只要对Redis的命令熟悉一点就能够非常快速地理解和掌握这些方法,趁着现在对Redis不是特别了解,我也对着命令来了解一下这些方法。 一、属性 IRedisClient的属性如下&am…

javascript深入浅出

第一章 数据类型 1,六种数据类型:原始类型(number,string,boolean,null,undefined) object对象(Function Array Date) 2,隐式转换:Na…

mv命令:移动、重命名文件或文件夹

linux使用mv命令来移动、重命名文件或文件夹。 例如,将一个名为abc.txt的文件重命名为1234.txt:mv abc.txt 1234.txt 例如,将目录A重命名为B:mv A B 例如,将a.txt移动到/b下,并重命名为c.txt&#xff1a…

MTK优美代码赏析6:电话本里的快速排序和插入排序算法

MTK优美代码赏析6:电话本里的快速排序和插入排序算法 记得读书的时候学数据结构和一些程序基础的课程,学了很多的排序算法,当时感觉蛮有趣,也很简单,当大学的教育是以理论为主的,哪些教授们又没给咱举个实用的例子说明为什么要教我们这个,所以考完试就把这些没用的东东给忘了..…

Visual C++中最常用的类与API函数

这篇文章能让初学者快速了解visual C MFC中常见的核心的类与函数,虽然全部看下来有点枯燥,但对初学者快速了解MFC的框架结构很有好处。 常用类 CArchive类:用于二进制保存档案 CBitmap类:封装Windows的图形设备接口(GDI)位图 CBru…

vim编译器的使用方法(行号、删除、复制、查找等)

以下内容源于网络资源的学习与整理,如有侵权请告知删除。 vim是在Linux环境下的编辑器,它的常见使用操作如下。 1、显示行号 编辑器默认不显示行号,有二种办法可以显示行号。 (1)第一种是,暂时显示。在vim…

Imagination

Imagination Time Limit : 3000/1000ms (Java/Other) Memory Limit : 65535/32768K (Java/Other) Total Submission(s) : 8 Accepted Submission(s) : 3 Font: Times New Roman | Verdana | Georgia Font Size: ← → Problem Description There are four matrixs blow ,…

Day19 练习题

1.列举布尔值为False的值 0,[],{},(),False,’’,None 列如: if 0: 2.写函数: 根据范围获取其中被3和7整除的所有数的和,符合条件的数字个数以及符合条件的数字的总和 3.函数传递参数时,是否用的同一内存地址 name “hairui” de…

error U1087: cannot have : and :: dependents for same target

DDK下build -c错误:error U1087: cannot have : and :: dependents for same target 被build的source code所在path不能含有空格. 转载于:https://www.cnblogs.com/vcerror/p/4289079.html

网络通信基础常识

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。 1、网络通信属于进程间通信 进程间通信的一种方法是使用套接字socket,网络通信其实就是位于网络中不同主机上面的2个进程之间的通信。 2、网络通信的层次 网络通信的层次,即…

Android NDK工程创建与编译运行

首发地址:http://www.eoeandroid.com/thread-201993-1-1.html一、 工程创建1. 创建一个Android工程配置好开发环境并加载好对应版本sdk后,选择菜单File->New-> Project创建工程,工程类型选择Android Project,如下图所示&…

[Quatsch]Quantum Or Optics

小生预言,鉴于目前CG产业中学术界与生产界之间的差异,未来必将有一门独立的学科,专门用于研究自然界的材质特性与表现,从此艺术界与学术界将呈现完美之统一,应该先在MIT或者Cornell出现。本人姑且将这门学科命名为“应…