Test Doc
测试 测试2 测试3 测试4 hello
测试 测试2 测试3 测试4 hello
intro 在抬笔之前,纠结许久,过去的这一年,真的有这么多要记录的东西么,有必要花几个小时的时间去写么?我不知道。但同时,我的鼠标已点开了之前的博客,开始翻看前些年写的文字。看着看着,我找到了许多自己已经不怎么记得,但想起却感到十分温暖的那些瞬间和回忆。虽说都不是什么大事儿,但是如果不记录,它们可能真的就会遗失在记忆的荒野里了。 所以,我还是要写些文字,来记录我这平凡,也小有起伏的一年时间。 目标回顾 首先,回忆一下去年立的那些flag吧。 分享至少 5 篇较为优质的内容 目标完全没有达成,一是由于惰性,二是感觉自己积累不足 对领域内技术的认知上有明显提升,具有一定的专业性和深度 专业知识确有所增加,但离自己的期望还有差距,算是勉强达成 维持学习状态,保持对世界的好奇心,对世界的认知上更进一步 确有长进,但也不多,算是没有原地踏步吧 阅读,至少 5 本较篇幅较长的著作,减少看视频的时间 大篇幅的只有一本花了半年才读完的《红楼梦》;看视频时间有所减少,原因是从看变为了听 锻炼身体,体重维持在 80kg 以下 体重目标超额达成,但原因却不是锻炼身体 多陪陪家人,每周和家人通话 目标也算是基本做到 整体看来,六点Flag,勉强算下来,可以说是完成了一半吧,约等于不及格。 原因肯定是多方面的,不能单独归咎于时间不够。因为我知道就算是时间充裕,也很有可能无法完成上述任务。所以,今年我决定使用一个更加精确的目标系统——OKR对自己的目标进行量化确定/追踪,并且应当可以按照执行情况修改。希望通过新方法,可以让明年的自己及格吧。具体规划放在文末。 生活 这一年的生活给我最大的感悟就是“无常”。疫情无常,生活无常,生死也无常。 下面,就以几个生活中的小片段来简要概括这一年吧。 其一 年初的第一件大事是乐乐的婚礼。在他的婚礼上,我们这三个从小几乎天天在一起的铁哥们,终于能从天南地北百忙之中,在疫情笼罩下,时隔多年之,后再次聚到了一起。我呢,也终于第一次当了伴郎,第一次正经穿上了西装,也第一次从“台上人”视角见证了好朋友的婚礼。疫情之下,虽然没有大操大办,但几天的相处,让我深刻感受到了乐乐和嫂子的深厚感情。所以这里不说别的,祝福乐乐早得贵子,也祝福剑客早日结婚(相见不易,总得有个由头呀)。 刚布置好的婚房 此外,在见面后的聊天中,突然发现童年的好友都已成熟起来:聊天中少了许多幼年时对游戏和故事的痴迷、少年时对宇宙和未来的畅想,却添了对生活的感悟与吐槽,以及对肩上责任的担当。肩膀扛起的东西变重了,脚下也自然就会踏实起来,这或许才是人们仰望天空的底气之所在。 在家后山上看云 其二 虽说现代通讯非常方便,但我们几个也是各自有忙,所以自此一别,再次认真联系,大约是就年底了,原因是他们关心我的心理状况:在奶奶去世后的第三年,从小陪伴到大的另一位亲人——爷爷,也永远离我而去了。 坏事总是十分突然。当时兰州的疫情刚刚有所好转,各种交通刚刚解封,就得到了爷爷病情突然恶化的消息。也顾不得多想,做完核酸第二天的凌晨,就和姑父驱车从北京赶回家中。 一路上 幸运的是,我的决策是对的,在赶回家的那天晚上,见到了爷爷意识完好情况下的最后一面。他在这种情况下,还是像往日那样,一见面就担心我工作忙,穿的少,吃不好。在得知我一切都好后,他也逐渐安心下来。第二天,他的意识就再也没有清醒起来,生命也随之进入了倒计时。最终,在2021年11月23,他还是永远地离开了我们。那天凌晨六点医院门口的瑟瑟寒风,我一定毕生难忘。 爷爷一生很不容易。他生于解放前,小时虽然家里穷,但成绩优异,所以家里一路供他读到了初中毕业。后来,因为自然灾害无力负担,就征兵入伍,去了二炮,做了一个挂着空军名号的步兵。随着国家需要,他们一路辗转,从老家来到北京,又从北京到了茫茫戈壁。此后,他们部队在戈壁上克服了种种困难,为我们国家的国防做出了重要贡献。后来,他转业来到了当时为了“备战”和“备荒”而在西北大山深处筹备建立的884。他们再次发扬那个时代艰苦奋斗的精神,遇山开山,遇水架桥,硬是在只长荒草的群山深处,建起了一座现代的大型铜加工厂,为国家生产了许多战略物资。在他退休后,虽然终于不用为工作日夜操劳,却又开始为了我的成长费心费力,这一晃又是二十多年,直到现在。 而就在我刚力所能及可以养活自己,并且也有一些余力可以让他生活得好一些的时候,他却永远地离开了我。子欲养,却亲不待。 人这个物种大约就是这样,谁也无法脱开的生死的轮回,每个人都应当看开些。但生死事大,岂不痛哉! 爷爷的在党50年纪念章 今年也是建党100周年,在这里我要向爷爷这样为国家建设奉献了一生的老同志们,致以最崇高的敬意!虽然在你们身上没有那些波澜壮阔的功绩,也没有曲折动人的故事,但正是由于千千万万你们的付出,让共和国有底气走到今天,也是千千万万你们所坚持的初心,守住了和传承这个社会的正气。 其三 现在,让我把回忆的时间线再次拉回年初,去回忆另外一条主线。 从年初开始,我进入了频繁去杭州出差的状态。虽说每次的目的地都一样,但随着四季的变化,都有新的风景,好不惬意。比如仲春的龙井茶园、夏日的西子湖畔,还有秋日的大运河滨都让令人着迷。我也终于理解为何有如此多的文人墨客偏爱这里,留下了无数流传青史的千古墨宝。 2021飞行统计 这一年的多次往返让我对这座城市有了许多直观的感受,比如: 自然和人文风光不必多说,不仅好地方多,而且风景在四时都有所不同; 但是基础设施相比于北京也还存在不小的差距,不过可以看出一直都在进步 城市处于扩张阶段,老市民集中在老城区,新市民都在新城区,在新城区能明显感到接纳包容的年轻的城市文化,但老城区却相反 地价房价上涨很快,后上车的人成本变高,且造就了一批房产富豪 另外,更重要的是卤蛋也逐渐在这座城市安顿了下来,所以我之后还有大把时间在这里走走转转,期待能有更多更深入的了解。 烟雨龙井村 西湖落日 繁忙的大运河 其四 除此之外,在去年上半年我也经常进行体育锻炼,在周内最常去的地方是公司楼下的大望京公园,而在周末则非奥森公园莫属了。基本每周的跑步距离在15到20km左右,除此之外还会骑车上下班,一天大约能有十几公里。跑步的配速也逐渐从年初拉夸的七分钟,逐渐恢复到了年中的五分多钟。并且通过一起跑步,也和公司里的一些同事有了更多的交流,收获满满。 某次跑步记录 虽然我的运动量这么大,但是体重却几乎没什么变化。原因很简单——每天都得吃点好的。 跑完步当然要吃顿好的犒劳一下自己 肯定有很多人抱着和我一样的想法:已经这么努力运动了,在饮食上放松一下也没有问题。然而,事情没有这么简单。 在下半年里,我付出了沉重的代价:告别了火锅,告别了啤酒炸鸡,告别了我最爱的麦当劳,甚至告别了多数肉类。同时,我也告别了熬夜看视频,告别了久坐打游戏,也告别了我最爱的长跑。 现在看来,效果还行。体重从巅峰的接近85kg降低到了如今的不到75kg,身体也逐渐恢复正常。不过随着体重的减轻,肌肉们也在一同不断流失,但做什么,都总得付出些代价吧。 下半年的体重变化 钱花光了可以再赚,物品丢掉了可以再买,数据消失了都可以再造,但是健康失去了却很难重新恢复,关心之人去世了就更难弥补。正所谓“知易行难”,这些简单的大道理说起来容易,但若没有经历过,却很难真正理解。如今理解了,却也都付出了很重的代价。 最后,我发现猫咪真的是一个十分神奇的物种,不论什么时候,它们总能让人感到治愈,会让人多一些面对不确定性的勇气,所以就用一张球宝(室友的猫咪)的靓照作为本章的结尾吧。 球宝一瞥 工作 & 技术 坦率地讲,自己在去年工作中的收获是不小的。不仅是技术上做到了真正的入门,还有在心理上具有了一定的主动性。 回过头来审视从刚入职到现在的这一年半的时间,能明显感到自己从一个刚进入公司时几乎什么都不懂,只会低头自己琢磨,不会跟人沟通,甚至因为担心自己水平差,而感到有些自卑的职场新人,成长为了一个较为自信,能直面问题,从更多角度思考,并跟他人配合,共同解决问题的人。 我认为入职即将满一年的时候对自己来说是一个很重要的节点,原因很简单,那会儿刚好在年末评定绩效,所以在这段时间里,我终于有机会也不得不好好梳理和思考去年一年在我身上发生的事情,以及我面对事情所采取的态度和行为,并且也能将这些思考和主管进行直接而充分的沟通。什么是好的,什么是不好的,什么之后应该努力避免,什么事情还能做的更好,都逐渐变得明晰。这种明确的反馈对于一个人的成长有很大的帮助。 部门校招同学培训时的一年香蛋糕 当然,作为一个工程师,除了上面这些比较“务虚”的成长,一定还得聊聊自己技术的进展。 祸福相依,虽说自己干自己的,不懂和别人交流不是什么好事,但是也是由于多数时间都在低头琢磨,自己写代码,所以从刚入职到一周年左右时,我大概零零散散为项目提交了大约一万多行不到两万行代码。因为我所编写模块代码和其它模块有很多耦合,所以编码的时候我也被迫几乎阅读了我们组所负责项目(一个Rust编写的VMM)的大部分代码,这让我很快对这个项目的设计与架构有了大致的了解,也让我更加熟练地掌握了Rust语言。同时,这些工作也让我意外地获得了整个BU的第一届代码贡献奖。 代码贡献奖现场 去年有关技术上的成长大概可以归纳为以下几个方面: 入门虚拟化:从只会使用VmWare,到现在开始认真了解虚拟化的原理,阅读KVM源码,并实现了一些VMM的代码 开始学习Linux内核:阅读了一些和虚拟化以及业务相关子模块的部分源码,同时也编写了几个简单的内核模块。特别是在下半年,由于零零散散地参了一些创新项目,所以也开始对Linux运行所依赖的Arch,以及UML有了更多的理解与思考 了解容器生态:当前主要局限于单台节点中,比如runc/kata的实现原理,containerd的工作流程等等 在公司里,技术和业务一直都是牢不可分的,所以,我也借着开发这些代码的由头,了解到了当前云原生的发展形势、当前遇到的问题,以及众多业务场景(比如函数计算和弹性容器)。随着这些背景知识的输入,让我对整个云计算行业,特别是云原生业务的发展前景,抱有较为乐观的预期。 此外,还有一个好消息是我们大团队将我们平时所研发的操作系统分支及其上下游组件开源为“龙蜥社区”,而我们小团队所做的项目也会通过龙蜥社区和KataContainers这两个社区将源代码贡献出去。所以,后续应该就会有更多可聊的技术相关的东西了。 云栖大会中和“小龙人”的合影 经验 & 思考 经过去年一年的经历,也获得了一些的简单的经验,在这里也将其中一些分享出来。还有一些思考不成体系,略显混乱,就不买弄了,等以后有机会仔细整理,再发出来。 人的精力有限 可能因为之前比较年轻,所以总觉得无论有多少事情,我总能想办法做完,大不了熬上几个通宵。 但是到了现在,发现自己的精力已经远不如本科时候了,如果晚上熬夜,第二天可能就完全无法正常工作,这样效率可能比晚上好好休息还要低。这才让我真的意识到自己的精力有限,很多看起来很有兴趣,动动手就能做完的事情,其实自己并没有精力去做。 这对于我这样的完美主义者是一个非常大的打击。无限把事情做到完美的代价要么是很多更重要的事情无法完成,要么是自己的健康受到损害,而且甚至牺牲了这两者还是无法把你最想做的事情做到最好。 在痛苦地挣扎了一段时间后,我还是向规律妥协了:把做事的目的逐渐由把事情做完美变成了把事情做成。我发现影响一件事情成败的关键点只有几个,剩下都是锦上添花,因此做事情不能贪多,需要将它们的优先级进行排序,先做优先级高的,再做优先级低的,虽然有些优先级低的事情做完会让整体更加完美,但是你如果没有精力顾及这么多,那也只能放弃。 另外,虽然个人的精力有限,但是人多力量大呀。如果能把多个人的力量集合起来,就能完成超出个人能力的更大的事情。不过如何利用别人的力量一起完成一件事情,我暂时还没有特别成功的经验可以分享。 小事快速决策 一定有很多人像我一样,是比较纠结的性格,做决策时总是考虑再三,一直无法得到一个明确的结论。这在一般时候不会表现出什么问题,但如果面临的决策非常多,那么做决策这件事儿将会是时间地狱和精力黑洞。 举一个在工作中常见的例子:在开发一个模块的时候,虽然架构设计已经确定了,但在真正编码的时候依旧经常会面临怎么写比较好这个问题,比如大到一个消息通知机制应该怎么设计,小到这个变量应该叫什么名字。不知道大家会怎么样,我经常会为了这些事情纠结很久,甚至把每种方案都写一遍,看看哪个更好。但最终往往只有两种情况:要么是它不重要,怎么写都可以,要么是只有其中一个设计比较好,但是需要写完后面的代码你才会知道,现在纠结并没有什么作用,甚至可能纠结很久,还是会选择一条错误的道路。 因此,在当前决策对未来事情发展影响比较小的情况下,快速决策快速做选择快速试错才是比较优的策略,如果一味纠结,不仅会浪费很多时间,而且往往也不会增大把事情做对的概率。所以,纠结症患者不如放下心中的纠结,闷着头随便选一个,省时还省力。 不过上面的方法并不适用于重要事情的决策。因为重要事情决策可能会对后面产生重大影响,哪怕成功率只提高一点,也会有很大的价值。而决策的关键的是收集和整理信息,如果只顾着快,而忽略了很多有用的信息或没挖掘到信息中比较重要的点,导致决策出现失误,这就得不偿失了。 合理预估时间 在多人协作时,每个人的工作可能多少都会依赖别人的工作,而为了便于将每个人的工作进行组合,管理者一般会采用排期的形式把控项目的进度。所以,在工作中,就总会有人问你:你觉得这个事情多久能够完成? 但从个人的角度来看,面对一个稍微复杂一些的事情: 总是需要较长的时间完成 往往中间会因为吃饭/睡觉/开会/有其他更紧急事情等多种原因被分割成多段,保存/恢复工作状态需要消耗意志力和时间 事情完成的过程中几乎必然会出现许多意料之外的情况 因为事情紧急,为了督促自己尽快完成,可能会预估一个比较早的时间 人们的思考模式也决定了我们往往对一个事情的预估是偏乐观的 所以,这最终会导致个人对一件事情完成时间的预估是比较乐观的,在deadline之前一段时间往往会拼命去赶,就这样还不一定可以做完,导致延期,而且还可能会由于个人的延期导致项目节奏被打乱,从而导致项目的延期。 虽说项目规划者有责任考虑到这些原因,但作为参与人,也有必要对项目完成的时间进行较为合理的评估。目前,就个人经验看来,一件事情完成的时间往往是自己脑中认为可以完成的时间再增加50%以上。因此,在需要精确项目时间的时候,我们可以通过将预估完成时间直接*2的方法来为自己保留合理的裕度。 新年OKR 去年,直接用几条flag表达了对新的一年的期望,由于没有合理的checkpoint与拆分,导致很多都没有完成,今年我认为需要改变策略,用用新的OKR工具做做试验,看看是否能让自己真正动起来,将目标的完成率提高。 下面,就将今年的一些OKR列出来,不过使用博客跟踪OKR的效率肯定不高,所以后续会尝试配合一些跟踪目标的软件一起使用。 Object1:生活健康自律 健康的身体是一切的前提,所以我希望将身体健康,生活自律放在OKR的第一位 KR1: 早睡觉,不熬夜 睡眠质量对身体健康和工作效率都十分关键,因此早睡早起是必须要做到的目标。 由于之前都睡得比较晚,睡眠时间需要慢慢调整,所以将晚睡定义为在凌晨12:40之后上床睡觉 非特殊情况,每周最多有一天晚睡/熬夜 KR2:坚持锻炼 由于每天的工作都是对着电脑久坐,因此必须要让自己有时间动起来 锻炼以一定强度的有氧运动为主,且不能过于剧烈,可选的方案:慢跑、球类运动、骑行、爬山、跳绳等,时长以30分钟以上为宜 在三月份气温回暖后,非特殊情况(如生病/出门在外等)每周至少进行三次锻炼活动,三月份之前,每周至少两次 KR3:健康饮食、控制体重 饮食对身体健康的影响也十分显著,体重亦是如此,因此需要进行合理的安排 饮食目前没找到太好的定量约束方案,就限定一下吃饭速度吧,如果有一同吃饭的人,不能吃的比所有人都快 体重比较方便定量,在3月份气温回暖后,逐渐将体重控制在70KG(±3KG) Object2:知识的输入和输出 KR1:阅读原版书籍 只有阅读一手知识才能让自己真正理解知识,通过看视频/读别人的笔记获得知识的速度虽然快,但是可能会忽略掉其中的许多细节 能增长知识的书籍分为两种:技术书籍和人文社科书籍,它们的阅读方式和收获也是不同的,因此单独制定计划: 技术书籍两本: 计算机体系结构——量化研究方法 暂定 人文社科书籍五本: 八次危机 暂定 另外,暂定每周有五天时间每天阅读半小时 KR2:学习优秀源码 精读优秀的源代码和原版书籍一样重要,需要仔细阅读,暂定两个虚拟化的项目,后面再做补充 KVM QEMU KR3:输出内容 在阅读完别人的内容或者自己进行一些实践活动之后,如果不及时总结,可能会导致没有彻底理解,而总结的最好方式就是输出一篇较长的文章,既锻炼逻辑思维,也提升写作能力,因此暂定一年输出五篇文章 Object3:其他目标 除了主要方向外,还有一些其他的目标,暂时只列陪伴家人,后续再做补充 KR1:陪伴家人 陪伴家人的目标去年执行得不错,今年要继续保持 每周和家人电话 逢年过节没有疫情的情况下回家 The End 最后,就以前几天自己做的一个梦作为博客的结尾吧:不知何年何月何日,乘坐一架很大的宽体客机回家,但是飞到大约三分之二的地方,飞机突然失控坠向地面,好在紧急迫降成功,降落在了一个不知名的地方。这里到处都是荒漠,还有一些居民,只有天边模模糊糊可以看见草原。手机导航没有坏,它告诉我没有别的交通方式,只有步行导航,而且需要走很久很久才能到家。我也找了一圈,也的确没有看见其他的交通工具。许多乘客都因要很久才能到目的地而选择在这个荒漠小城留下,看情况再决定是否出发,但是我在找附近的居民买了一些馕作为干粮后,马上就出发了。 能不能走出荒漠,我不知道,至于能不能走回家,就更不知道了。但是梦中的我做出了坚决的选择:不要在意这些,走下去,马上出发!
在挺久之前,可能是刚读大学的时候,就隐约听说这本《禅与摩托车维修艺术》的大名,也得到了几位朋友推荐,但在当时,翻过几页后发现根本看不下去,就束之高阁了。这次终于在好奇心的不断怂恿之下花费了一些意志力重新拾起,终于随着情节的展开,同作者一起踏上了骑着摩托车横穿整个美国的长途旅行。 在最开始的认知中,我认为这场摩托车旅行一定是一个非常酷的故事,但后面发现虽然故事的确很酷,却非第一印象中的那种酷。 在这场旅行中,我在了解美国地理的同时,也被作者和“斐德洛”的暗中较量所吸引,对父亲与克里斯的矛盾所疑惑,更为寻找“良质”同作者(或者说是“斐德洛”)一起陷入了对当下的反思。 故事 先大概说说这个故事吧。本书讲述了作者与儿子(还有友人)骑着摩托车进行了横穿美国的长途旅行,在旅途中,他骑着摩托车通过多次“肖陶扩”(Chautauqua,看起来是一种露营+讲学的结合体,在上个世纪的美国比较流行)对过去的自己的思想与经历进行了回忆和反思,最终也与过去的自己及儿子和解的故事。 随着故事的深入,读者会发现作者的大脑中存在着两个人,一个是当下的他,即波西格,另一个是过去的他,即斐德洛。这都是由于斐德洛由于对“良质(Quality)”的狂热探求,导致他的精神出现问题,而后他接受了电击治疗,导致他的思维、记忆和性格产生了变化,变化后的人就是现在的他。 这是作者在故事结构上非常巧妙的安排之一,一开始读者读起来总觉得作者说的某些话语很奇怪,隐隐觉得哪里不对劲,有时候甚至会感到后背发凉。直到后面才知道那些话其实是“斐德洛”借作者之口说出来的,这才恍然大悟,并在不知不觉中接受了这个设定,为后面对斐德洛思想的探寻做足了铺垫。 另一个比较有意思的地方是“虚实结合”(不确定这个词用在这里是否准确)。在书中,作者对“禅”的思考和摩托车旅行是相互交织展开的。在对骑行的描写中可能随时会开始“肖陶扩”,转向对思想的讨论,而在讨论进行的过程中也可能会切换回对旅行的而描述。这样的切换在写作的时如果把握不好就会显得十分生硬,但是作者的处理还是相当到位的——旅行中的种种波折与斐德洛在思想上的进步与挫折以及作者和儿子的关系都在相互映照,它们的节奏也都完美契合。 不过,也不是所有的情节都是完全契合,而是在契合中又有升华。比如在第三部分的作者选择了和当年不同的路,致使他没有再次“发疯”。故事大概是作者从“斐德洛”的老朋友狄威斯家中出来以后,准备攀爬一座雪山,这座山不是一座普通的沿途小山,而是一座需要数天才能攀爬上去的山,而且还是斐德洛当年在思考良质的过程中会经常去爬的山。因此,这座山其实也不仅是现实中的一座山,其更是一个隐喻,隐含着斐德洛对良质的艰苦探索的过程。 而这次,他和儿子在进行持续几天的艰苦跋涉逐渐接近山顶的时候,因为他担心在山顶会产生”雪崩“,就选择不再登顶了。可以看出,雪崩在这里其实也是一个隐喻,这象征着一旦到了山的顶点,即找到所谓”良质“后,就会遇到雪崩,即作者思想上的崩溃。而这次作者选择不完全爬上山顶,就避免了再次雪崩的情况,只是在崩溃的边缘而没有完全失控。而最后,儿子或者说亲情“解救”了他(或者说是斐德洛),让他的神智重新回归了正常。 思想 作者的思考由旅行同伴约翰夫妇对学习摩托车维修的抗拒开始。他认为约翰夫妇由于从事的是艺术工作,所以害怕(或是说不喜欢)现代科技所使用的分析方法,因此主动屏蔽对机械运行原理的学习与思考,而只是将它们当作一个整体来考虑。 作者从这里引出了“古典的”和“浪漫的”这一对立认知形式。他认为古典的认知就是像现代科学这样,凭借理性对某一事物不断细分,以求认识其中的规律;而浪漫的则是凭借直觉与灵感,对事物进行把握。而当代这两种认知方式之间的分歧越来越严重,达到了难以对话的程度。正如他与约翰夫妇之间的分歧那样。 可以看出,作者很难找到一个合适的形容词对这两种思想进行表达,所以使用两个在我们语境里看起来不那么准确的词语进行描述。我认为古典的意思就是现代的理性主义,强调将事物划分为理性中不同的领域和范畴,并对范畴进行不断地细分,以求掌握事物运行规律的认知形式。而浪漫的则指一种综合地认知事物地方式,强调事物间的共性与联系,但往往由于世界的复杂性,无法用科学原理进行证明或证伪,所以有可能被认为是某种超越科学的“神秘主义”。而我之所以认为在我们语境里这两个描述不够准确,是因为“古典的”在西方世界里就是理性的化身,而我们则恰恰相反。 接着,作者对科学进行了反思,他认为科学也是某种潜藏在我们大脑中的“鬼魂”,相信科学的我们并不比相信宗教的古人更加智慧。这之后的作者便踏上了对“斐德洛”思想的回顾历程,因此后文的“他”一般均指斐德洛。 他在进行科学研究的过程中发现科学家(他自己当时就是在做化学相关的研究)总是在大胆假设和小心求证中不断前行,但是“大胆假设”环节看起来不太符合理性的逻辑,因为假设会随着科学范畴的增多爆炸式增长,这样就会导致我们会越来越无法从众多假设中找到符合逻辑的结论。 这其实正是现代科学所需要面对的一个很大的问题,更底层地看,这需要我们对自己的认知世界的方式,也就是经验主义进行反思。而作者的反思是通过休谟与康德的对话进行的。休谟曾经对人类的认知模式提出了三个问题:因果问题、归纳问题、应然与实然的问题,这三个问题直指经验主义,如果无法解答它们,那么我们建立在经验上的科学将没有任何价值。康德拯救了经验主义(并向我们抛出了三大批判),他通过引入“先验”这一概念,确认了只有经验是不够的,还需要我们认知世界的思考范式。虽然康德的思想惊艳到了斐德洛,但是斐德洛认为康德的《判断力批判》中对美学的理性解释却非常丑陋。 可以看到,作者在这里对科学的理性和感性对立的认知已经从当初的经验阶段(科学的危机)走向了理论阶段(认识论),他已经开始摆脱实践,转而走向了纯粹形而上的追寻。事实上,他也从最开始就读的大学退学,转而去韩国当兵,而后去印度修习“东方哲学”。 而后,他开始在蒙大拿州立大学教授修辞学。他希望他所在的大学是实质上的大学,而非形式上的大学,即空有大学的建筑,而没有大学的思想,他的脑中逐渐构建了一个理想中的“理性教堂”。他在那时,又开始了在课堂中的实践,但是发现了理性在修辞学上的矛盾之处——无法使用理性对修辞学进行归纳和总结,最后形成规律,因为作者在写出伟大作品的时候,并不会在脑中实现想好自己要用什么修辞,好的作品都是靠着灵感这种非理性的东西所完成的,那么他教授学生通过理性归纳得到的这些规律又有什么意义呢?所得到的都只是一堆拙劣的模仿。 而这其中,为了完成一部好的作品中,究竟是什么在起作用呢?就是“良质(Quality)”。他知道良质这个东西是真实存在的,写出好的作品、辨认好的作品都需要依靠良质,但良质却又不依靠理性存在。从这里开始,他踏上了找寻良质的一条“不归路”,从这里起,现在的作者和儿子也开始攀登雪山,全书逐渐迈向高潮。 一开始,他的一个重大发现是良质根本无法被定义,因为一旦被定义,或者说是被范畴所规定,良质就不再是良质了。所以他在读到《道德经》中“道可道,非常道”这句话时,才觉得遇见了知音,良质和“道”居然是一个东西。不仅如此,他认为良质和美学、佛学以及神学其实都是一个东西。宗教的、东方的与艺术的,这些无法被理性哲学所把握的(或者是被作者所鄙视的”范畴学家“所毁坏的)事物,在良质这里得到了统一。 可以看到,作者终于从西方的理性思维和二元论中跳脱出来开始意识到许多无法被理性所把握的事物的存在,但是很快我们将看到由于他所在的社会环境以及他的历史局限性,他还是无法摆脱被二元论分割以及被范畴所规定的命运。同时也可以看到作者希望对思想世界进行统一的野心。 现在的作者不仅阅读了斐德洛的手稿,还更广泛地阅读了其他人的作品,发现了许多类似的思潮,他们都希望能从不同的侧面爬上这座山峰(但殊不知老子已经在数千年前在山顶俯视他们了)。比如著名数学家和哲学家庞加莱,他意识到科学,包括数学只是一种解释世界的工具。我们能通过理性推到出无数可能的世界,但是无论它们在理性上有多么完美,但是最终被应用的一定是能更好解释现实世界的现象的学说。那如何从成千上万的理论中挑选出合适的理论呢?庞加莱认为这个是灵感,或者说是潜意识,但由于纠结于主体与客体分离的二元论中,他没有得到具体的答案,而作者认为答案就是”良质“。 接下来,作者探讨了良质的应用,特别是在解决某些问题(如修理摩托车时)被卡住的时候,良质会帮你度过难关。他提出了一个很有意思的比喻,古典的认知方式是火车的引擎,可以让火车开动,但是浪漫的认知方式(即良质)却像铁轨那样指引了火车前行的方向。 后面,他又对康德发起了挑战,认为人在意识到事物之前的那一刹那,产生作用的其实就是良质,这时候良质是一个一元论的概念,主客体是一体的。为什么他认为科技是丑陋的?因为科技是被严重二元化的,是被分析的,没有良质的存在。同时他也总结了能将良质应用于工作中的方法,即让内心进入真正的平静,从而获取进取心。 读到这里,看起来作者已经找到了良质的关键之所在,已经即将登顶那座雪山,但是情况却急转直下。因为他希望继续对良质进行深入研究,于是他希望去芝加哥大学的一个叫“观念分析与方法研究”的交叉学科就读博士学位,继续对良质进行探究。然而他去了之后,遇到的阻力不再是近代的休谟康德黑格尔,而是西方理性思维的源头——苏格拉底、柏拉图和亚里士多德。他与委员会,即这几位思想家在现代的化身,特别是亚里士多德的交锋才刚刚开始。他为了保护他的发现——良质,开始变得狼性,时刻准备与委员会进行战斗。但最后他的心智失去了平衡,走向了疯狂。 (为什么我这里没有介绍他们之间你来我往的交战过程呢?是因为我学艺不精,对古希腊哲学知之甚少,无法支撑我进行有效的总结。希望之后有机会补充吧。) 最后,现在的作者在儿子的牵引下,没有重蹈之前的覆辙,走向了一个完全不同的结局。 什么是良质 我相信所有读完这本书的人(包括我自己),在合上书之后,还是会不断思考这个问题:“良质”究竟是什么。 接下来是我个人的理解: 但凡问出这个问题,就意味着已经自己的思想已经被理性主义的幽灵所掌控,因为理性主义总是希望将事物归为一个范畴,然后进行分析总结,最终把握这个事物。 但是理性走到这里,却只能碰壁了,因为“良质”无法被范畴所规定。这里再次化用《道德经》开头的这段话:“道可道,非常道;名可名,非常名。无名天地之始,有名万物之母。”大概意思是,道只要被名说出来,那就不是原来的道了。那道德经怎么来解释这个道呢?就只能通篇说道不是什么,通过不是来表现出其是什么。所以我们也只能说出“良质”不是什么,而无法说出其是什么。 那么良质不是什么呢?这里的答案就很明确了,那就是能被范畴所规定的各类事物,也就是理性。 我觉得还需要对范畴还想多说两句,它只是我们大脑中对事物强加的一种观念,便于对这个事物进行把握。但是,这只是大脑的思维模式,并不是真实的物质世界,物质世界给我们大脑所传递的,只有一堆混沌的信号罢了,我们是通过某种观念(可能是先天的,也可能是经验)对信号进行处理,从而形成认知。而至于为什么会是现在这样,那就是另一个话题了,需要从语言与存在中找寻答案。 说完上面这些,可能还是不能完全让人明白什么是”良质“。但是如果此时还没有明白,我也没办法,只能寄希望于某一刻产生的思想火花了。其实并不只有我,古代的先贤们也没办法。因此,他们管这件事情叫“悟道”或者叫“开悟”,而佛学还有一个专门形容一个人开悟难度和可以开悟程度的词,就是“慧根”。 几点思考 首先,我想试着简单分析一下我所认为的作者精神崩溃的原因。 从书中的描写可以了解到,作者在最开始是一个纯正的理性信徒,但通过发现的数个矛盾,悟到了”良质“,发现了一个更广阔的非理性(也可以称之为感性)世界的存在。而后,他开始向理性世界的中心,即古希腊三杰在现代社会的代理人主动发出挑战,并在挑战的过程中精神崩溃。 可以看出,他前期的艰苦探索都是非常积极的,甚至在打破二元世界,接受一元论与感性世界的时候,都没有表现出太多的痛苦。但是在挑战理性权威的过程中,他开始变得好斗,并且把学术上的争论上升到了个人的层面,甚至有了一些被迫害妄想症的味道,这导致他最后吞下了苦果。 但是,他真的完全接受一元论,或者说是活在一元论中了么?我看未必。委员会最开始见到斐德洛的时候,希望使用范畴把他的研究,即良质进行规范。如果是我,我会认为已经跟你说了这么多,你这个人却还没有开悟,没法交流,直接扬长而去便可。但是他却留下来,在逻辑与范畴中开始斗争。这是一个非常不明智的决定,也是注定不会成功的斗争。这可能是由于他希望统一思想世界的野心,也可能是因为他作为一个出生开始就接受二元论教育的人,对一元论还无法向我们这样融汇贯通。 以上是他个人的局限性,当然还有历史的局限性。作者接受教育的时候,存在主义应该在美国还没有大面积流行起来,而共产主义在美国更是一种禁忌,这导致存在主义的两大先驱任务——马克思和海德格尔的思想和著作,完全没有进入作者的视野,如果他早些读到这些著作,可能会对良质拥有更加深刻地认识了。同样地,他也不会发起那场同古希腊先贤们地那场毫无意义地争斗,因为已经有人帮他做过了。他要做的,可能会是把这套理论进行实践,无论是在他擅长的修辞学上,还是在艺术上,甚至是像马克思那样在社会革命上。 另外,在读完以后,还看了知乎上对本书的一些讨论,和此书高达8.6的豆瓣评分和创记录的销量完全不同,几乎都是负面的评论,这让我很意外。看下来许多人几乎都觉得这本书完全不值得读,觉得是“机场文学”,里面讨论的哲学都是所谓“民科”级别的,如是云云。 我承认知乎上可能会有许多通晓哲学思想的大师,但是这本书对于一般读者而言,其难度其实并不低,甚至可能会有很多人会像当年的我那样,因为完全没有基础知识导致根本看不下去。读书也是“小马过河”,不能用自己的标准衡量别人。 哲学这门学科在希腊语中的本来的翻译是“爱智慧”,它不像科学那样有固定的范畴与研究方式。就像作者对“良质”的探索那样,虽然早有人从一条路抵达了山顶,但还是需要有更多的人从更多的路探索更多的山顶,虽然他们大部分人都停留在了山底或者是半山腰,但是这些探索不论是对个人还是对社会来说都是有意义的。绝不能当作者在芝加哥遇到的“委员会”中的那类人。 最后,回到本书最开始作者对科技的思考。作者认为他那个时代的科技产品都遵循“古典式”的思考方式,没有“浪漫式”的思考,这导致很多人不喜欢科技产品,更不愿意了解其原理。而在现在这个时代,我认为事情已经悄然发生了很大的转变。首先,我想到的就是乔布斯对科技与人文十字路口的论断,他用苹果的产品证明了科技与人文是可以相互融合的。另外,我也也看见身边越来越多的人成为了”全栈开发人员“,他们不仅对技术了如指掌,更对用户交互设计了然于心。最后,昨天夜间还看见了何同学的毕业视频,这不也正是这种精神的完美阐释么?不知波西格在成书多年以后,看见如此这般的当下,会有何感想。
时间很快,如今已经是2021年伊始了。虽然已经很久没有写过文章,但是,面对2020年,我总还纠结着要说些什么。 2020最大的主题就是变化。不仅有意料之中的变化,更多是意料之外的变化。 疫情 说到变化,总也绕不过的就是疫情。 我的2020年是以一次滑雪作为开始,在那场滑雪之后,我还在畅想在结束毕业论文写作之后,毕业前的时间我应该怎样度过,是要趁着有闲逛上大半个中国,还是省一些钱躲在实验室里啃啃买来一直没读的大黑书。 年初滑雪 放假回家后,就开始盘算:工作后肯定没法在家待太长时间,不妨这次放假在家里多待几天,多陪陪家人(多睡睡懒觉),不到交论文的那天,坚决不回学校。 然而现实远超我的想象,不仅交论文的那天没能回学校,甚至到了毕业那天,都不能回去。 事情的起因还是一月份开始听说有一种类似SARS的病毒已经开始悄然传播,那时候感觉病毒还很远,和我没有关系;直到除夕前一天,武汉封城,全国人民在家过年,我开始知道它的名字叫新冠病毒,而且十分严重,和我们每个人每天的生活息息相关;后面,我又通过躲在手机屏幕后的暗暗观察,发现这件事情不仅改变了我的计划,改变了中国人的生活,甚至整个人类都与此息息相关;而直到一年后的今天,疫情依旧不知疲倦地在全球蔓延。 是呀,仿佛离我们很遥远的事情,在短短一年内变成了每个人所面临的现实,这对于一直沿着一条可被经验预测的道路前行的我来说,的确是一种震撼。 另外还有一个比较深刻的感受,是“正常化偏误”。之前听的比较多的一个例子是一个小镇旁边有一座火山,那里的居民居然在火山爆发以后觉得问题不会那么严重,没有逃跑,导致伤亡惨重。我想我一定不会这么蠢,一定要最开始就逃走。但是,当疫情来临的时候,我在最开始就觉得应该是要买口罩了吧?但是又觉得会不会没这么糟,现在就去买口罩会不会显得自己很异类?就这样拖延了两天,发现城里已经几乎买不到正常价格的口罩了。过后,我才醒悟过来,原来自己也随时可能变成眼看着火山灰把自己掩埋的那个人,这也是我2020年学到的第一个教训:当你觉得应该行动的时候,就一定要开始行动,不要在乎别人眼里你是怎样的。 病情 在疫情不太紧急的四月份,在家赋闲,于是给自己送给一台手术作为生日礼物。人生第一次手术还挺成功,我的胆囊和矿泉水瓶盖大的结石永远地离我而去。不多说,希望看到这里的每一个人都能按时吃早饭,健康饮食吧。 毕业 在开始读研的时候,我预想了无数种毕业时候我会做些什么,至少应该和卤蛋同学补上之前本科毕业没拍成的毕业照。但万万没想,这次毕业没有聚餐,没有毕业照,甚至都没有亲手拿到打印出的沉甸甸的论文,只有腾讯会议上大家还算灿烂的笑容(毕竟所有同学都顺利毕业)。 毕业“合影” 毕业以后我的一个愿望是等疫情结束,能回到陪伴我三年时间的小灰楼302去逛逛,找潘老师以及师弟师妹们聊聊天,可惜变化永远都是猝不及防——当时老纪说的多年前位于澡堂三层的上古实验室居然变成了我们如今的新去处。 工作 人呐就是不知道,自己就不可预料。我绝对不知道,我作为一个搞网络搞SDN的,怎么就来做云原生了。 说实话,福厂的工作节奏还是有点快,让摸了七年鱼的我花了很久去适应了。不过好在我遇到了一群非常nice的同事,特别是几位师兄,从他们那里学习到了非常密集的知识,让我从一个无法无天的野生程序猿变成了一个(自认为)还算及格的底层工程师。 这半年来,我的工作主要集中在和容器沙箱相关的技术之中,从一个虚拟化的门外汉,到看见了门槛之所在。另外,通过熟读Firecracker源码,也让我对Rust和microVM有了一个全新的认识。 在公司遥看望京SOHO 读书or视频 今年唯一的遗憾就是没有阅读太多的书,特别是很多书读到一半就放下,并没有读完。分析是两个方面的原因:一是由于生活模式的切换,自由时间变少,时间碎片化严重,没有相对较长的时间和精力来进行阅读;另一方面是B站使用时间显著增长,妄图通过对知识区视频的学习跳过读书快速获取知识。 这里可以借这个机会稍微聊聊我对B站的体会:在深度使用B站接近1年后,就知识区和科技区来看,UP主的数目持续增多,优质稿件也不断变多,这就吸引我每天花费大把的精力在观看所关注UP主的视频上,甚至都不敢去刷推荐流,很担心又看上哪个新的up主,想去追视频但是已经没有时间了。我不知道这是好事还是坏事,但是对B站来说绝对是一件好事,每天都会冒出许多新的创作者,分享许多奇奇怪怪的新内容,而且这也的确能够吸引观看者花费更多注意力在B站上面。这一切的一切,让我想起了刚上大学时候的微信公众号,嗯,熟悉的感觉又回来了。 还是说回读书,我认为能够进行较长时间专注的阅读,特别是大部头的书,对我来说还是十分重要的。因为视频中的知识点往往都比较零散,而且很难讲深,构成体系,只能作为谈资泛泛去听。所以,我认为系统性地读书还是十分必要的,接下来我一定得压缩每天在B站上花费的时间,重新回来读书中来。 希望接下来的一段时间,先把剩下的半本《红楼梦》读完。 一点思考 论2020年对我影响最大的人,莫过于温铁军和他的《八次危机》了。在B站看见他的视频后,发现他的思想理论和主流观点差别很大,但是又有一种吸引我不断去看他的演讲去思考他说过的每一句话的魅力。原因很简单,我逐渐意识到,他说的可能才是对的。 回想起来,印象最深的莫过于“代价”和“矛盾”,大到国家发展现代化需要代价,维持现有制度也需要代价,小到个人,过上好日子需要代价,不被别人支配更需要代价。大概就是只要你想要改变现状,就总要付出点你当下拥有的资源,以便让你在未来的时空里拥有你想要的资源。之前中学老师的一句口头禅“出来混,总是要还的”大概就是这个现象的通俗版本吧。和代价孪生的是“矛盾”,因为代价总是需要牺牲一些来成全另一些,那么牺牲者与被牺牲者之间就会产生矛盾,那么我想这也可能也是导致矛盾在不同的时期动态变化的一个原因。正因为代价和矛盾的不断转换,推动了事物的不断发展,个人是这样,国家和世界也都是这样。 结语 昨天午睡的时候做了一个梦,梦见自己在搬家,搬家的对象中有一个大箱子,很难搬走。箱子里装的是一个游戏,游戏的在一些城堡中发生,城堡的主人既希望继续战斗,赢得更大的城堡,又担心自己进攻的时候自己的城堡被别人抢走。我纠结了很久,决定让主人公继续战斗,并终于搬走了这个箱子。但是做完这个决定后,感受到了无尽的空虚与劳累,就仿佛做了一个多大的选择一样。紧接着,突然惊醒,意识到这个城堡小游戏的经历原来可能是自己前一天晚上没睡好而在纠结是否要起床的投影。可真的就只是这样么?我想也不尽然。总之,既然已经在梦中做出了选择,就不如在2021继续践行吧。 Flags 最后是每年的保留节目,立Flag,希望明年都能完成: 分享至少5篇较为优质的内容 对领域内技术的认知上有明显提升,具有一定的专业性和深度 维持学习状态,保持对世界的好奇心,对世界的认知上更进一步 阅读,至少5本较篇幅较长的著作,减少看视频的时间 锻炼身体,体重维持在80kg以下 多陪陪家人,每周和家人通话
在上一篇博客使用Qemu和GDB对Linux内核进行调试中已经介绍了使用Qemu和GDB对Linux内核进行调试的方法,但是GDB调试对于用惯了GUI工具的人(比如我)来说并不是很直观,所以就希望尝试使用比较熟悉的GUI编辑器,如VSCode,对内核进行调试。 由于VSCode的调试方式同样基于GDB,所以需要先在GDB中测试没有问题。 插件 需要在VSCode的插件市场中安装微软官方的C/C++插件,该插件可用于IntellSence和GDB调试。 配置 为了使VSCode支持内核的调试,需要配置launch.json,特备注意需要配置setupCommands属性,以便在GDB启动后对其进行设置,大致的配置文件如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) linux", "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/vmlinux", "miDebuggerServerAddress": "localhost:1234", "args": [], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerArgs": "-n", "targetArchitecture": "x64", "setupCommands": [ { "text": "set arch i386:x86-64:intel", "ignoreFailures": false }, { "text": "dir .", "ignoreFailures": false }, { "text": "add-auto-load-safe-path ./", "ignoreFailures": false }, { "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } 在添加配置后,直接在VSCode中设置断点,然后启动Qemu,最后在VSCode中启动调试即可。 其它配置 如果不仅需要使用VSCode对内核进行调试,还希望进行编辑,特别是激活IntelliSence以及格式化等功能,还需要对VSCode进行进一步的配置,具体配置已经置于GitHub仓库https://github.com/imaginezz/vscode_config_debug_kernel中,可以直接Clone为内核文件夹中的.vscode目录。
使用Qemu对Linux内核进行调试是一种较为便捷的方式,近日进行了一番实践,并将大致步骤与其中一些小坑记录了下来。 环境 由于放长假赋闲在家,所以手头只有一台装有MacOS的MBP可用,而Linux内核的开发与调试使用Linux环境下会比较方便,所以就使用VMware Fusion创建了一台安装有Ubuntu 18.04系统的虚拟机。由于编译Linux内核及相关软件需要的资源较多,所以为虚拟机配置了双核CPU、2GB内存和20GB磁盘空间(笔记本本身资源有限),但实际使用(特别是物理内存和硬盘)捉襟见肘,于是又在系统中添加了3GB的SWAP内存并扩容了20GB的磁盘空间(其实还是不太够)才解决问题。 编译Linux内核 首先,尝试对内核进行编译,在编译前需要使用通过KConfig启动内核的调试配置。 下载内核源码 由于Linux内核代码量非常大,且由于国内网络大家都懂的原因,所以的下载内核源码是一项较为复杂的体力活动。 第一种方法是直接Clone Linux源码的Git仓库,当前,其仓库大约为3.7GB。在通过内核官网(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/)或者GitHub(https://github.com/torvalds/linux)进行Clone的过程中,经常会遇到连接断开的情况,非常捉急。而如果通过国内镜像源,如清华Kernel Git镜像进行Clone的时候,最开始速度飞快,但是后面速度会越来越慢。因此,如果不像我这样头铁的话,不建议使用这样的方式下载Kernel的源码。 另一种较为简单的方式是下载特定版本的源码,这些源码的tarball包可以从内核官网或者镜像站获得。我在实验中使用的内核版本为4.19,gz压缩包的大小约为150MB。 配置内核 如果是使用Git Clone的方式获取的内核源码,需要通过git checkout v4.19将内核源码置位4.19版本。 在编译之前,首先需要安装相关的依赖(如果提示缺少其它依赖按需安装即可) 1 sudo apt install libncurses5-dev libssl-dev bison flex libelf-dev gcc make openssl libc6-dev 在编译之前,需要使用KConfig对内核编译选项进行配置,在内核文件夹下,使用make menuconfig(命令行界面)或make gconfig(基于gtk的图形化界面)对内核进行配置。在配置时,需要打开如下选项: 1 2 3 Kernel hacking -> Kernel debugging Kernel hacking -> KGDB:kernel debugger Kernel hacking -> Compile time checks and compiler options -> Provide GDB scripts for kernel debugging 并保证如下选项没有开启: 1 Kernel hacking -> Compile time checks and compiler options -> Reduce debugging information 在退出配置后,可以发现内核目录中生成了一个名为.config的配置文件。 编译内核 配置完成后,就可以使用make编译内核,在多核CPU中可以使用make -jx启动多线程编译(x为启动的线程数)。 如果一切正常,在漫长的等待后,内核将编译完成。编译会在内核根目录下生成vmlinux 文件,它是编译出的原始内核文件(含有调试信息),而会在arch/x86/boot/bzImage目录下生成压缩后的内核文件(当然是在编译的体系结构为x86的情况下)。 编译安装GDB和Qemu 由于内核调试所需的GDB和Qemu版本可能会比apt源中的版本高,所以,最好自行编译安装这些软件。 编译安装GDB 首先,从官网(http://www.gnu.org/software/gdb/download/)下载GDB的源码并解压(这里使用的是官网中最新的GDB 9.1),需要注意的是,网上有些博客中提到需要修改GDB的源码,其实是不必要的,报错的原因是没有自动检测到目标体系结构的类型,所以只需设置该类型即可。 解压后进入GDB文件夹,执行下列指令,即可完成编译安装: 1 2 3 4 5 mkdir build cd build ../configure make -j4 sudo make install 最后,通过使用gdb -v确定gdb的版本是否为9.1,如果是,则说明安装成功。 编译安装Qemu 首先,从官网下载(https://www.qemu.org/download/#source)Qemu的源码并解压(这里使用的是Qemu 5.0.0)。 由于在Ubuntu GUI中使用Qemu还需要多媒体图形库SDL,所以需要首先使用apt安装sdl: 1 sudo apt install libsdl2-2.0-0 libsdl2-dev libsdl2-gfx-1.0-0 libsdl2-gfx-dev libsdl2-image-2.0-0 libsdl2-image-dev 进入Qemu目录后,执行./configure检查系统配置并生成Makefile,需要注意检查的时候是否检测到了SDL的支持,其输出的部分内容如下所示: 1 2 3 4 5 6 7 8 profiler no static build no SDL support yes (2.0.8) SDL image support yes GTK support no GTK GL support no VTE support no TLS priority NORMAL 然后执行make && make install即可完成Qemu的编译与安装。 在安装完Qemu后,会生成如qemu-xxx和qemu-system-xxx的一系列命令,用于仿真不同体系结构的用户态应用和操作系统,可以通过如qemu-system-x86_64 --version命令确认Qemu是否安装成功。 制作ROOTFS 在内核启动后需要一个带有init程序的rootfs,所以在调试内核前需要制作一个rootfs。 构建基于initrd的rootfs initrd是一种位于内存的根文件系统,它可以在硬盘被驱动之前载入系统。这里为了方便,只将一个简单的程序写入initrd,并将其作为init程序(即系统启动后的第一个用户态进程)。除此之外,也可以使用busybox作为initrd中的init程序。 创建一下简单的c程序,命名为fakeinit.c。 1 2 3 4 5 6 7 8 9 10 11 #include <stdio> int main() { printf("hello world!"); printf("hello linux!"); printf("hello world!"); printf("hello linux!"); fflush(stdout); while(1); return 0; } 然后使用gcc编译这段代码,在编译的时候需要使用静态链接,并且如果如果在配置内核的时候没有启用64位支持(64-bit kernel),则需要将代码编译为32位程序,方法是在gcc命令行中添加-m32选项。 编译命令如下: 1 2 gcc --static -o fakeinit fakeinit.c gcc --static -o fakeinit fakeinit.c -m32 (编译为32位可执行程序) 在编译后,使用cpio程序进行打包: 1 echo fakeinit | cpio -o --format=newc > initrd_rootfs.img 这样,一个基于initrd的rootfs即制作完成。 构建基于硬盘镜像的rootfs 这里使用busybox构建基于硬盘镜像的rootfs。其中,busybox是一个集成了数百个Linux常用命令和工具的单个软件,在对内核进行测试的时候非常方便,号称“The Swiss Army Knife of Embedded Linux”。 下载编译busybox 首先,从官网(https://busybox.net/downloads/)下载busybox的源码并解压(这里使用的是最新的busybox-1.31.1)。 在解压并进入busybox文件夹后,首先使用make gconfig或make menuconfig对其进行配置,需要启用如下选项: 1 Settings -> Build Options -> Build static binary (no shared libs) 如果需要将其编译为32位版本,则需要将-m32命令填入如下选项: 1 2 Settings -> Build Options -> Additional CFLAGS Settings -> Build Options -> Additional LDFLAGS 与内核相同,在退出后,会在目录中生成一个名为.config的配置文件。 然后,使用make命令编译busybox。 使用busybox创建rootfs 首先,创建一个空的磁盘镜像文件,然后将其格式化: 1 2 dd if=/dev/zero of=./busybox_rootfs.img bs=1M count=10 mkfs.ext3 ./busybox_rootfs.img 然后,挂载刚刚创建的磁盘镜像(需要使用loop设备): 1 2 mkdir rootfs_mount sudo mount -t ext3 -o loop ./busybox_rootfs.img ./rootfs_mount 接着,在busybox源码目录中,将编译好的busybox目标文件安装到rootfs文件夹: 1 make install CONFIG_PREFIX=/path/to/rootfs_mount/ 最后,配置busybox的init,并卸载rootfs: 1 2 3 4 5 mkdir /path/to/rootfs_mount/proc mkdir /path/to/rootfs_mount/dev mkdir /path/to/rootfs_mount/etc cp busybox-source-code/examples/bootfloppy/* /path/to/rootfs_mount/etc/ sudo umount /path/to/rootfs_mount 现在,一个基于busybox的rootfs磁盘镜像就制作成功了。 使用Qemu和GDB调试内核 使用Qemu启动内核 由于编译的内核体系结构为x86,所以使用qemu-system-x86_64程序来载入并启动内核。 如果使用intird作为rootfs,则具体命令为: 1 2 3 4 5 6 qemu-system-x86_64 \ -kernel ./linux/arch/x86/boot/bzImage \ # 指定编译好的内核镜像 -initrd ./rootfs/initrd_rootfs.img \ # 指定rootfs -serial stdio \ #指定使用stdio作为输入输出 -append "root=/dev/ram rdinit=/fakeinit console=ttyS0 nokaslr" \ # 内核参数,指定使用initrd作为rootfs,禁止地址空间布局随机化 -s -S # 指定Qemu在启动时暂停并启动gdb server,等待gdb的连入(端口默认为1234) 如果使用磁盘镜像作为rootfs,则具体命令为: 1 2 3 4 5 6 qemu-system-x86_64 \ -kernel ./linux/arch/x86/boot/bzImage \ -hda ./rootfs/busybox_rootfs.img \ # 指定磁盘镜像 -serial stdio \ -append "root=/dev/sda console=ttyS0 nokaslr" \ # 内核参数,指定root磁盘,禁止地址空间布局随机化 -s -S 使用GDB调试内核 最后一步,由于刚刚Qemu开启了远程调试,所以只需要将gdb通过连入即可: 1 gdb ./linux/vmlinux # 指定调试文件为包含调试信息的内核文件 如果此时直接在gdb调试器中使用target remote:1234连入Qemu的gdb server,则会出现报错Remote ‘g’ packet reply is too long,这是由于gdb没有正确识别调试目标的体系结构造成的(有些博客认为需要修改源代码屏蔽这个错误,实际上是不必要的),所以只需要在远程attach之前使用set arch i386:x86-64:intel设置目标体系结构即可。 例如,你希望在start_kernel函数设置断点进行调试,则在启动Qemu后,gdb的命令如下: 1 2 3 4 5 6 gdb ~/linux/vmlinux (gdb) set arch i386:x86-64:intel (gdb) add-auto-load-safe-path ~/linux (gdb) target remote:1234 (gdb) b start_kernel (gdb) c 可以发现,内核在启动后被中断在start_kernel函数上。 后记 内核文档 在内核的文档中,有一篇详细讲解了如何使用GDB调试内核。 该文档的最新版本可见于内核的官网:https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html。 而具体的版本就需要在内核源码中编译文档了,例如html版本的文档可以使用make htmldocs进行编译,在启动HTTP服务器后,可以在浏览器中进行访问,例如,http://127.0.0.1:8000/dev-tools/gdb-kernel-debugging.html。 参考来源 本文参考了两篇较为优质的博客: 用QEMU来调试内核 – 亲身体验篇 利用VS Code+Qemu+GDB调试Linux内核
最近在项目中遇到需要在 NodeJS 中调用 C++代码的问题,在此略作总结。 主要方案 在 NodeJS 中,和其他语言编写的代码通信主要有两种方案: 使用 AddOn 技术,使用 C++为 NodeJS 编写一个拓展,然后在代码中调用其他语言所编写的源码 or 动态库 使用 FFI(Foreign Function Interface)技术,直接在 Node 中引入其他语言所编写的动态链接库 在对这两种方式进行比较后,发现这两种方式各有优劣。 首先,AddOn 技术比较通用,它可以使用 C++代码来拓展 Node 的行为,很多库都是使用这种方式来完成一些比较底层操作(比如和操作系统的一些通信)的。但是它写起来比较麻烦,要编写一个 C++项目,还要按照 NodeJS 的规范 export 相应的函数,而且每次安装的时候都需要进行编译(以适应本地 Node 的版本)。如果只是调用一个 DLL,那就还需要在项目里重新包装一遍 DLL 的接口。 如果使用 FFI 技术,限制就会比较多,首先,它只能调用其他动态库,如果你想使用 C/C++完成更多功能的话,还需要再封装一层 DLL,另外,它只支持_cdecl调用约定(也就是 DLL 在导出的时候一定要标记用_cdecl编译命令),不支持_stdcall或者_fastcall调用。但是调用起来就会很方便,可以直接在 JS 代码中声明 DLL 的接口就可以了。 综上比较,如果只调用第三方 DLL(而且恰好是_cdecl导出),使用 FFI 就再合适不过了(虽然性能可能会有一定的损失,而且调试起来会有困难)。 其实,从理论上来讲,FFI 也是基于 AddOn 技术的,只是它可以帮你把在 JS 中定义的接口直接转换成 C 语言的接口,并利用 NodeJS 的 Buffer 内存,将其同载入的 DLL 共享。当然由于 FFI 的这种通用性,也导致了一定的性能损失。 下面就以在 Windows 平台上使用 FFI 为例,简单聊一下如何使用 NodeJS 和 C++编译而成的 DLL 通信吧。 FFI 使用准备 安装 NodeJS 可能你的环境中已经有 NodeJS 了,但是,如果是最新版本,在安装 FFI 的时候会出现各种兼容性的问题(比如编译无法通过,虽然已经有人提供了 patch,但是还没有被 merge 进主分支,为了避免出现 bug,还是暂时不用为妙)。所以可以安装 LTS 版本代替。 另外,还需要注意要调用的 DLL 是 32 位还是 64 位的,Node 的版本需要和 DLL 的版本匹配。因为如果 64 位 Node 调用了 32 位的 DLL,是无法成功装载的,反之亦然。 安装 Windows 的 C++工具链 这里有两种方案: 安装 Visual Studio,并安装相应的工具链。如果使用 VS 2019 版本的话,需要安装 C++桌面开发和 Windows SDK 相关的工具(Node v10 现在只支持 v141 版本的 MSVC),这种方式便于后续的调试工作(虽然也很艰难) 在安装 Node 之后,使用管理员权限运行 Powershell,并全局安装 windows-build-tools,参考命令 npm install --global --production windows-build-tools 安装 node-gyp node-gyp 是一个 Node 中基于 gyp 的跨平台的编译工具,用于编译其他库。 在安装的时候,需要使用 VC 的工具链,所以如果没有把工具链放在全局变量中,需要打开 VS 的Developer Powershell安装,该命令行一般在开始菜单的 Visual Studio 文件夹中。 参考命令:npm install -g node-gyp 安装 FFI 及 REF 下面的步骤依旧需要 VC 工具链,所以可能依旧需要在Developer Powershell中执行(建议常备该窗口,后面只要涉及到编译安装的命令都需要用到)。 安装 FFI 及相关工具的时候如果没有 VC 工具链,则会直接安装二进制代码,这样可能会出现包的 ABI 版本和 NodeJS 的 ABI 版本不符合的情况(在下面的 Tips 中会提到)。 现在,切换到项目的文件夹中,安装下面的包。其中,ffi 包是用以支持 FFI 功能的,ref 包是用以支持指针功能(原理是通过 Node 的 Buffer 内存,将 JS 的结构和 C 结构相互转换的)的,ref-*是用以支持高级结构的(比如数组和结构体) 1 2 3 4 npm install ffi -s npm install ref -s npm install ref-array -s npm install ref-struct -s 除此之外,如果想支持 VC 中常见的 wchar 类型,还可以安装 ref-wchar 包。 安装 electron-rebuild 包 如果是 electron 项目,还推荐安装 electron-rebuild 包,该包可以遍历 node_modules 目录下的所有包,并将其重新编译。 然后,推荐在 package.json 中配置 electron-rebuild 的命令: 1 2 3 "scripts": { "rebuild": "./node_modules/.bin/electron-rebuild" } 之后执行在需要重新编译的时候只需要执行npm run rebuild即可。 使用方式 简单概览 可以查看如下官方示例: 1 2 3 4 5 6 var ffi = require('ffi') var libm = ffi.Library('libm', { ceil: ['double', ['double']] }) libm.ceil(1.5) // 2 在引入 FFI 后,使用 FFI 调用了 libm 库(可能这个示例只能在类 Unix 系统中使用),一般拓展名为 libm.so,系统会在系统目录下搜索这个动态库,并将它使用动态链接器载入到 node 进程中。 接着,程序声明了 libm 库中的一个方法 ceil(向上取整),其中,该函数的返回值是 double 类型(第一重数组中的 doule),而该函数的入参也是一个 double 类型的值(第二重数组中的 double)。 最后,直接使用libm.ceil方法即可调用动态库中的函数,并返回正确的值。 这只是一个 FFI 的简单用例,更复杂的用法(主要是异步调用和回调函数)可以参考 FFI 的实例页https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial。 类型 FFI 的类型系统其实记住了 ref 库的类型,ref 库的类型系统基于 NodeJS 的 Buffer 内存,可以根据 Buffe 中数据的类型对 Buffer 内存中的数据进行访问和修改。 ref 自带的数据类型都是基本类型,比如 int 类型、bool 类型或者 string 类型。所有类型可以参考 ref 的wiki。 ref 中的多数类型都有简写,比如ref.types.int可以简写为int。 需要注意的是,char*可以写为string,对应的 ref 类型为ref.types.CString。值得注意的是,string 在 JS 中是基本类型,在 C 中却是引用类型。 对于指针类型,ref 提供了一个方法ref.refType()来得到,比如int*类型就可以使用ref.refType('int')得到。当然,为了省事,也可以直接用int*表示。 而对指针解引用,ref 库也提供了一个deref()方法。只要在对应类型的变量上使用该方法,就可以得到指针指向内容的变量。比如一个指向int*类型的 JS 变量 a_pointer,那么我们如果想得到具体的整数值,就可以使用a_pointer.deref()方法。 相反的,如果想获取某个变量的地址,就需要对某个变量使用ref()方法。 需要注意掌握类型与变量值的区别,使用ref.types、ref.refType或者是下面会提到的ref_struct({...})获得的类型,而如果想获得某个类型的变量,有两个方法,一个是从 FFI 函数的返回值中获取,另一个是在 Buffer 中开辟一个空间,来存放类型为所获得类型的变量,下面会具体讲到。 如果需要在 NodJS 的 Buffer 中开辟长度为某个类型的空间,可以使用ref.alloc()函数,只要将类型名传入即可。比如,想开辟一个类型为 int 的内存,就可以使用ref.alloc('int')得到。 此外,还有以下几点需要注意: 如果开辟类型为字符串的内存,推荐使用方法 ref.allocCString,其参数为一个 JS 的字符串。因为 C 语言的字符串在末尾有一个\0标识符,所以用这个方法可以更安全地得到 C 字符串。 如果在 C 语言中值为 NULL,则在 JS 中对应的值为 ref.NULL。 如果遇到指针类型,可以统一用'void'或者ref.types.void表示。 如果要表示一个函数的指针,可以使用'pointer'表示。 对于复合类型,比如数组或者结构体,ref 库本身没有提供相应的支持,需要使用 ref-array 和 ref-struct 库来实现,具体可以参考这两个库的文档。 另外,对于 Windows API 中较为常见的宽字符 wchar 类型,也有一个基于 ref 的库 ref-wchar 进行支持。 最后,附上 ref 的文档http://tootallnate.github.io/ref/,具体的 API 都可以在这里进行查阅。 调用外部符号 假设我们有如下 C 代码(并把它写的复杂一些): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* main.c */ typedef struct t_s_t{ int a; char b; } t_s; __declspec(dllexport) int add_one(int a) { return a + 1; } __declspec(dllexport) void struct_test(t_s** t_s_p) { *t_s_p = (t_s *)malloc(sizeof(t_s)); (*t_s_p)->a = 1; (*t_s_p)->b = 'd'; } 上面的代码中,声明了一个结构体t_s,以及两个函数add_one和struct_test。其中,函数前面的__declspec标记表示声明该函数为导出函数。VC 默认导出 C 函数时是用_cdecl调用约定。 其中,add_one方法的作用显而易见,是将传入参数加一再返回。而struct_test函数的作用是先在堆上开辟一个大小为生面声明结构体的内存空间,然后将该内存空间的指针赋给传入的参数,并将该结构体赋值。(这里的代码其实不够严谨,没有进行内存回收,但这不是本文的重点,所以先不做讨论) 需要注意的是,如果是 C++代码,需要使用extern "C"标记导出,否则会因为符号修饰和调用约定的问题导致无法通过源代码中的符号找到该函数。 我们可以使用 VS 的Developer Powershell对上述源码进行编译: 1 2 cl /c main.c Link /dll main.obj 编译后将生成 main.dll,我们在后面会用到这个动态库。 针对上述 C 函数,我们有如下 JS 代码,并假设和 C 代码在同一文件夹下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /* index.js */ const ffi = require('ffi') const ref = require('ref') const ref_struct = require('ref-struct') const t_s = ref_struct({ a: ref.types.int, b: ref.types.char }) const t_s_ref = ref.refType(t_s) const test_ffi = ffi.Library(__dirname + '\\main', { add_one: ['int', ['int']], // aka 'add_one': [ref.types.int, ['int']], struct_test: ['void', ['pointer']] // aka 'struct_test': ['void', [t_s_ref]], }) const result = test_ffi.add_one(20) console.log(result) //21 t_s_p = ref.alloc(t_s_ref) test_ffi.struct_test(t_s_p) console.log(t_s_p) console.log(t_s_p.deref()) console.log(t_s_p.deref().deref()) //a->1, b->'d' 首先,在代码中声明了一个结构体t_s,和 C 语言中的t_s*类型相对应。然后,我们还得到了结构体t_s的一个引用t_s_ref,和 C 语言中的t_s**类型对应。为什么在 C 语言中都多了一层指针呢?其原因和前面的字符串一样。 然后,声明了 test_ffi 变量,它调用了 ffi.Library 方法,该方法返回了 JS 中 DLL 的句柄和函数的声明,通过该变量可以进行 DLL 的调用。该方法有两个参数,一个是动态库的名称(可以略去拓展名 dll),另一个就是描述 C 语言函数的符号及其参数的对象。 该列表在上面已经简单介绍过了,对象的 key 是函数名,value 是一个数组,数组中第一个元素为函数的返回值的类型,第二个元素为另一个数组,它里面包含了函数的入参的类型。这些类型就用到了上一节中介绍的基于 ref 包的类型系统,类型可以用字符串表示,也可以用代码表示,可以参考代码中 aka 的注释。 接下来,代码通过test_ffi.add_one调用了 C 语言动态库中的add_one函数,可以看出,调用的方式和 JS 中的函数并无二致。但是需要注意参数类型千万不能传错,特别要注意 C 语言的 string 类型和 JS 中的 string 类型不同,要按照前文提到的方法进行转换。 然后,代码使用 ref.alloc 方法为 t_s_ref 变量开辟了一个内存空间(注意这只是一个指针大小的空间,并非结构体大小),并将该地址赋给 t_s_p 变量,然后将该变量传递给 struct_test 函数。因为 t_s_p 是一个二重指针,所以需要解两次引用,才能得到结构体真实的值。 回调函数 回调函数可以使用ffi.Callback()函数声明,该函数的第一个参数为返回值,第二个参数为入参列表,第三个参数为真实回调函数的闭包。 比如一个回调函数的定义如下,该函数会得到用户名和 id,并返回动作是否执行成功: 1 typedef int(*callback)(int, const char*); 那么,在 ffi 中,就可以使用如下方式声明该回调函数: 1 2 3 4 const callback_function = ffi.Callback('int', ['int', 'string'], (id, username) => { // do something return 1 }) 在声明之后,需要将该回调函数做为参数传入某个函数: 1 2 3 4 5 6 test_ffi.set_a_callback(callback_function) // Make an extra reference to the callback pointer to avoid GC process.on('exit', function() { callback_function }) 特别需要注意的是,设置完回调函数以后一定要保证该函数在 JS 中还存在一个引用(比如上面讲该函数的一个引用放在了 NodeJS 的 exit 事件中,这也是比较经典的做法)。否则,该函数将会被 NodeJS 的 GC 析构。其表现是:在程序刚开始执行的时候一切正常,但是执行了一会儿之后在调用这个回调函数,程序就会异常退出。如果用 VS 去对程序 Debug,就会发现该程序可能访问了非法指针,这是因为 DLL 代码中也存放了该回调函数的指针,但是在 JS 中该指针指向的地址因为没有被 JS 中的代码引用,所以被 CG 被释放,这样 DLL 中代码调用该地址的函数的时候,就会访问到非法的内存。 一些 Tips DLL 的调试方法 在使用 ffi 的过程中,可能发现最大的问题就是程序难以调试。特别在面对 DLL 的时候,就像针对一个黑盒操作一样,虽然已经对着头文件将他的 API 使用 FFI 翻译为 JS 的代码,但还是难以确定传参或返回值是否正确,在 C++的代码中该参数是否正确传入,传入后是否正确执行等等。这就需要一个能够调试的方法。 一个比较好用的方式是使用宇宙第一 IDE Visual Studio 的 Attach(附加)到进程的方式进行调试。但这种调试方法的前提是手中有 DLL 的源码或 PDB(符号)文件(如果没有的话就只能看到出现异常的代码附近的反汇编的代码了,而通常这些异常都是内存错误引起的,其实它附近的数据可能没有多大的意义)。 如果手上有源文件,那么首先打开工程,然后在 NodeJS 载入 DLL 之后,就可以在启动工程的时候选择“附加到进程”,在对话框中选择 NodeJS 进程即可进入调试界面。在调试界面里,可以插入断点,也可以看到断点附近的内存。 如果手上没有源文件,但是有 PDB 文件(或者少量的源码),可以使用 VS 打开一个空工程,然后在调试的设置中添加符号文件的位置,这样也可以进行断点调试,在调试的过程中可以查看代码有没有命中断点,在命中断点时,会引导你载入项目文件,如果有的话可以选择,否则可以查看断点附近的反汇编代码。 具体的调试方法可以参考 MSDN 文档:https://docs.microsoft.com/en-us/visualstudio/debugger/attach-to-running-processes-with-the-visual-studio-debugger?view=vs-2019,在这里就不过多叙述了。 如何载入在其他文件夹中的 DLL 如果 JS 文件和 DLL 文件不在同一文件夹中,可能会出现载入失败,会出现类似于“Dynamic Linking Error: Win32 error 126”的错误提示。 这时,就需要将 DLL 文件夹的路径放在系统寻找动态链接库的 PATH 中,但是 FFI 并没有提供此类接口。不过,好在 Windows API 提供了 SetDllDirectoryA 这个接口用以切换该进程中寻找 DLL 的 PATH,可以使用如下代码完成这个操作: 1 2 3 4 const kernel32_ffi = ffi.Library('kernel32', { SetDllDirectoryA: ['bool', ['string']] }) kernel32_ffi.SetDllDirectoryA(your_custom_dll_directory) 一些链接时候的错误提示 上面已经提到,如果动态库不在 PATH 中的话,会出现无法找到动态库的情况,这时候会报“Dynamic Linking Error: Win32 error 126”的错误,在另外一些时候,只要没有找到动态库,都会报该错误。如果出现该错误,就需要检查动态库的名称是否正确,检查动态库的版本是否正确(比如 32 位 Node 使用了 64 位的 DLL)等。 另外,还有一个“Dynamic Linking Error: Win32 error 127”错误比较常见,该错误指没有在 DLL 中找到对应的符号,这可能就需要检查在 ffi 中声明的函数名是否正确以及是否 DLL 版本有偏差了。 在 Electron 中使用 FFI 由于每一个 Electron 的版本是基于相应的 Node 和 Chrome 版本构建的,所以在使用 FFI 之前需要根据所使用的 Electron 版本安装本地的 NodeJS 版本,否则 FFI 可能会和 Node 版本不匹配,导致提示 ABI 版本不一致:xx was compiled against a different Node.js version using NODE_MODULE_VERSION x. This version of Node.js requires NODE_MODULE_VERSION xx(NodeJS 使用 NODE_MODULE_VERSION 来辨别 ABI 版本)。 这种情况下,可以使用前文提到的electron-rebuild对项目中的所有插件进行重新编译(需要注意本地 NodeJS 的 ABI 版本一定要和 Electron 中 NodeJS 的 ABI 版本一致)。 另外,需要注意的是,Electron 5 以上的版本使用了 NodeJS 12 的 ABI,但是当前的 Ref 库并不支持该 ABI,会导致编译失败。不过已经有人提交了 pull request 进行修复,相信之后会有一个可用的版本出来。 另外,也有了 NAPI 版本的 FFI 和 Ref,分别名为ffi-napi和ref-napi,和 ref 相关的包,比如 array 和 struct 拓展,也有了相应的 NAPI 版本,命名规则同上。使用 NAPI 的 Node C++ 拓展接口相对稳定,是今后的趋势。 最后,Electron 版本可以在https://electronjs.org/releases/stable中查看,而 NodeJS 及其 ABI 的版本可以在https://nodejs.org/en/download/releases/中查看。 一些资源 最后,在这里放上最近踩坑时候经常使用到的一些资源吧: ref 文档:http://tootallnate.github.io/ref/ ffi 文档:https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial 基于 ffi 的 win32 api:https://github.com/waitingsong/node-win32-api v2ex 中的 node-ffi 食用指南(难吃):https://www.v2ex.com/amp/t/474611
引用是C++相对于C而引入的一个重要特性,它使得许多地方的语法变得更加简洁,但是它们的底层究竟是怎么实现的呢? 在Wikipedia中,对指针有如下介绍: In computer science, a pointer is a programming language object that stores the memory address of another value located in computer memory. A pointer references a location in memory, and obtaining the value stored at that location is known as dereferencing the pointer. 从定义可以看出,指针从本质上来讲就是一个变量,其中所存储的就是其他变量的地址。 而C语言中的指针非常灵活,它可以任意指向某一个地址,不论这个地址究竟是否存在,或它究竟存储的是否为指针所代表类型的数据。 那么也不难想到,指针在实现的时候也是内存里的一个变量,它存有其他变量的地址。 在Wikipedia中,对引用有如下介绍: In computer science, a reference is a value that enables a program to indirectly access a particular datum, such as a variable’s value or a record, in the computer’s memory or in some other storage device. The reference is said to refer to the datum, and accessing the datum is called dereferencing the reference. In the C++ programming language, a reference is a simple reference datatype that is less powerful but safer than the pointer type inherited from C. The name C++ reference may cause confusion, as in computer science a reference is a general concept datatype, with pointers and C++ references being specific reference datatype implementations. The definition of a reference in C++ is such that it does not need to exist. It can be implemented as a new name for an existing object (similar to rename keyword in Ada). 从上面的定义可以看出,在C++中,引用可以狭义地认为是某一个变量的别名,它本身是并不存在的。 基于以上说法,我就一度认为引用只是C++编译器在编译时的一些黑魔法,它在运行的时候将两个解析到的符号链接成了一个,从而完成了引用,而在编译之后,引用与本体就是一个变量(一个寄存器或栈上的值)。 但是,事实却打了我的脸。 我们通过以下程序进行检验: 1 2 3 4 5 6 7 8 int main() { int a = 0; int *pa = &a; int &ra = a; ++(*pa); ++ra; } 该程序声明了一个变量a,然后分别声明指针pa指向a的地址,生命引用ra指向a,最后分别使用指针和地址对a进行了一次自加操作。 接下来,使用gcc -S test.cpp -o test.s -O0将上面的C++程序编译为汇编,看一下这些操作具体都是怎么实现的。(环境为MacOS,LLVM 10.0.1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 .section__TEXT,__text,regular,pure_instructions .build_version macos, 10, 14sdk_version 10, 14 .globl_main ## -- Begin function main .p2align4, 0x90 _main: ## @main .cfi_startproc ## %bb.0: ; 保护现场 pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 ; 保存栈指针 movq%rsp, %rbp .cfi_def_cfa_register %rbp ; 设置返回值为0 xorl%eax, %eax ; int a = 0 movl$0, -4(%rbp) ; t0 = &a leaq-4(%rbp), %rcx ; int *pa = t0 movq%rcx, -16(%rbp) ; int &ra = t0 movq%rcx, -24(%rbp) ; t0 = pa movq-16(%rbp), %rcx ; t1 = *t0 = *pa movl(%rcx), %edx ; ++t1 addl$1, %edx ; *t0 = *pa = t1 movl%edx, (%rcx) ; t0 = &ra = &a movq-24(%rbp), %rcx ; t1 = *t0 = *(&ra) = ra = a movl(%rcx), %edx ; ++t1 addl$1, %edx ; *t0 = *(&ra) = ra = a = t1 movl%edx, (%rcx) ; 恢复现场 popq%rbp ; 返回 retq .cfi_endproc ## -- End function .subsections_via_symbols 我已经将汇编中的关键代码加上了注释,可以看出,变量a,指针pa,以及引用ra都位于栈上,index分别在-4、-16、-24。需要注意的是,引用并不是直接复用了变量a的-4(%rbp)地址,而是像指针一样,使用了一个新地址,并且将leaq计算得到的a的地址写入了其中。 而在进行自加的时候,除了最开始将指针中的内容拷贝到寄存器中所用的地址不同以外,指针和引用所使用的方式是完全相同的。 这个结果令我非常意外,编译器其实是将开发者对引用的操作翻译成了对指针的操作。 最后,发现现代编译器还是很聪明的,如果将优化级别调到更高,就会发现它直接将中间的计算过程全部简化,直接返回,这是因为计算结果并没有任何输出,它是不必要的。如果将上面的代码从main函数转移到其他函数中,编译器这时虽然不能放弃计算其中的数值,但还是做了尽力的优化,直接返回结果(movl $2, %eax)。 进一步的实验 在文章发出后,有同学提出了质疑,认为可能只是MacOS上gcc编译器的特定操作,并不具有普适性,所以我在Linux和Windows上重复了上述实验。 在Linux环境中(发行版为Ubuntu 18.04,gcc版本为7.5.0)使用相同指令编译后得到的汇编码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 .file"test_ref.cpp" .text .globlmain .typemain, @function main: .LFB0: .cfi_startproc ; 保护现场 pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 ; 保存栈指针 movq%rsp, %rbp .cfi_def_cfa_register 6 subq$32, %rsp movq%fs:40, %rax movq%rax, -8(%rbp) ; 设置返回值为0 xorl%eax, %eax ; int a = 0 movl$0, -28(%rbp) ; t0 = &a leaq-28(%rbp), %rax ; int *pa = t0 movq%rax, -24(%rbp) ; t0 = &a leaq-28(%rbp), %rax ; int &ra = t0 movq%rax, -16(%rbp) ; t0 = pa movq-24(%rbp), %rax ; t1 = *t0 = *pa movl(%rax), %eax ; t2 = *t0 + 1 leal1(%rax), %edx ; t0 = pa movq-24(%rbp), %rax ; *t0 = *pa = t2 = *t0 + 1 movl%edx, (%rax) ; t0 = ra movq-16(%rbp), %rax ; t1 = *t0 = *pa movl(%rax), %eax ; t2 = *t0 + 1 leal1(%rax), %edx ; t0 = ra movq-16(%rbp), %rax ; *t0 = *ra = t2 = *t0 + 1 movl%edx, (%rax) ; 返回值设置为0 movl$0, %eax movq-8(%rbp), %rcx xorq%fs:40, %rcx je.L3 call__stack_chk_fail@PLT .L3: leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .sizemain, .-main .ident"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0" .section.note.GNU-stack,"",@progbits 可以看出与MacOS中gcc的编译结果基本相同。 在Windows环境中(Windows 10,vs2019,cl版本为19.23.28106.4),可以使用命令cl /Od /FA .\test_ref.cpp对源码进行编译,可以得到汇编代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 ; Listing generated by Microsoft (R) Optimizing Compiler Version 19.23.28106.4 TITLEC:\Users\jason\test\test_ref.cpp .686P .XMM include listing.inc .modelflat INCLUDELIB LIBCMT INCLUDELIB OLDNAMES PUBLIC_main ; Function compile flags: /Odtp _TEXTSEGMENT _ra$ = -12; size = 4 _pa$ = -8; size = 4 _a$ = -4; size = 4 _mainPROC ; File C:\Users\jason\test\test_ref.cpp ; Line 2 pushebp movebp, esp subesp, 12; 0000000cH ; Line 3 movDWORD PTR _a$[ebp], 0 ; Line 4 leaeax, DWORD PTR _a$[ebp] movDWORD PTR _pa$[ebp], eax ; Line 5 leaecx, DWORD PTR _a$[ebp] movDWORD PTR _ra$[ebp], ecx ; Line 6 movedx, DWORD PTR _pa$[ebp] moveax, DWORD PTR [edx] addeax, 1 movecx, DWORD PTR _pa$[ebp] movDWORD PTR [ecx], eax ; Line 7 movedx, DWORD PTR _ra$[ebp] moveax, DWORD PTR [edx] addeax, 1 movecx, DWORD PTR _ra$[ebp] movDWORD PTR [ecx], eax ; Line 8 xoreax, eax movesp, ebp popebp ret0 _mainENDP _TEXTENDS END cl编译器的汇编代码格式和gcc略有不同,但含义相近,并且可以比较轻易地通过上面标出的代码行数确定汇编代码的含义。可以看出,它使用的方法也和前两种相同。 这里再进一步,引入知乎上“XZiar”同学的评论,她的评论更加深入地理解其中的机制: 其实不是说“把引用解释成指针”吧。 在机器码层面,也不存在指针,只存在地址(指针其实还隐含了类型信息)。变量这个概念也是不存在的,只有“无格式数据”,被带格式的指令操作而已。 所以你看到引用和指针的效果一样,是因为在机器码层面,没有多余的信息去表明他们的区别了。 而在语言层面,引用的确可以理解为const指针 另外,她针对为什么汇编代码中引用把地址复制了一遍也进行了更深入的解释: 另外引用把地址复制一遍也是很正常的,编译器也的确没法在编译期完全分析出引用的具体指向。考虑如下代码: int a=0,b=1; int& c = flag ? a : b; 引用只不过因为const所以不能被重置,但具体指向什么,是可以运行期决定的。 到这里,对于指针和引用底层实现的探索也基本结束了,可以看出,在不启用编译器优化的情况下,主流编译器都会选择将C++中的引用解释为“const指针”。 但是,如果在启动编译器优化的情况下会是如何呢?在MacOS中,将源代码中的返回值改为a后(为了防止编译器优化后认为没有输出于是什么都不做),同时将编译器优化选项调整为O1和O2,其结果是相同的,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .section__TEXT,__text,regular,pure_instructions .build_version macos, 10, 15sdk_version 10, 15, 4 .globl_main ## -- Begin function main .p2align4, 0x90 _main: ## @main .cfi_startproc ## %bb.0: pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq%rsp, %rbp .cfi_def_cfa_register %rbp movl$2, %eax popq%rbp retq .cfi_endproc ## -- End function 可以看出汇编版本的代码中省略了所有和指针、引用及内存操作相关的代码,直接将返回值设置为2。 从这里可以看出,编译器的作用是将语言编写的代码翻译为合理的汇编代码,只要汇编代码可以源代码的真实意图执行即可。由于机器码可以表达的概念有限(基本上就是对于寄存器和内存的运算),而高级语言可以表达的概念十分多样,所以编译器就需要将高级语言中的各种复杂概念映射(也可以看做是翻译)为机器码中的简单概念,映射的过程可能会有多种方案,其最终选择是由编译器来决定的。在C++指针和引用的翻译中,主流的C++编译器都选择将它们映射为机器码中的地址,而舍弃了其中的类型信息。
自昨天下午看完《流浪地球》以后,内心久久难以平静。闭上眼睛后,大脑中出现的是流浪地球的主题曲《带着地球去流浪》和地球飞临木星时候大气交汇时那种磅礴而又难以用语言形容的场景。 这首歌里,一面是曹操的《观沧海》,描述了人类面对这个世界所发出的赞叹,描述出了大好河山的雄伟壮丽。而另一方面,又进行了古今对比,更体现出这场星际旅行的悲凉与无奈。伴着这首歌,我就知道必须要写些什么了。 (如果不能播放可以点击[这里](http://music.163.com/#/m/song?id=1341939931)跳入网易云音乐播放) 回忆 不知不觉,我进入了一段回忆,我可能是在想我是如何与科幻结缘,我又是对《流浪地球》有着怎样的期盼。 第一次开始对太空和空间科学感兴趣,是源于小学时候在 Windows98 电脑中的一个太空主题。我的印象很深,它的屏幕背景是由一个宇航员和一个太空站构成。桌面上的图标都被改成了星球的样子,而屏幕保护更是让宇航员和太空站动了起来。我在网上找到了一个动图: 每次从老旧的 CRT 显示器中看到这个桌面,我就仿佛置身于浩瀚缥缈的宇宙之中,一面是我所向往的科学殿堂,另一面是置身于宇宙中的无穷之美。 之后,大概是很多年之后,我逐渐对物理学有了很深的兴趣,痴迷于《果壳中的宇宙》和《时间简史》中描述的宇宙的本质、宇宙的演变和宇宙的图景。 另一方面,我也开始对科幻电影感兴趣,看了许多有有关于宇宙的科幻的电影:《星际穿越》、《星际迷航》、《地心引力》、《月球》、《火星救援》、等等等等。《星际穿越》中那个空旷无比的全景宇宙,以及在宇宙中硕大无比(像 IE 图标一样)的黑洞,一再地震撼着我的心灵。 再之后,就是接触到了《三体》的时候,那种激动到在期中考试前一天晚上彻夜阅读的心情,到现在都难以忘记。在看完三体以后,大呼过瘾,所以我又接二连三地去读大刘的其他作品。《球星闪电》、《流浪地球》、《乡村教师》、《赡养上帝》,这些都是读过就很难忘记的好作品。 而在前几年我又听到《三体》已经开机的声音,当时的心情是忐忑大于期望,我很担心这么宏伟的一个科幻世界被电影给毁掉,都不如 B 站 UP 主文曰小强用别的电影剪辑出的《速读三体》耐看。好在最后电影流产,我长舒一口气。 同样,在去年夏天首次听到《流浪地球》即将上映的时候我又是捏了一把汗。直到去年下半年来,随着电影的预告片一部一部地放出,以及看过电影的人对电影的评价都趋于正面,而且还有大刘作为监制,吴京也宣布加盟,我的心态逐渐从揪心转为期待。 随着主题曲《带着地球去流浪》及 其 MV 的放出,我对电影的画面彻底放心了,唯一的悬念就是剧情。 说回电影 回忆告一段落,现在就分几个维度来说说我对电影的感受。 视觉效果 从预告片到正片里的几乎所有视觉效果,都完完全全地震撼到了我,放眼其它国产片的特效,和《流浪地球》相比,无出其右。在我的感观中,电影已经达到了好莱坞的水准。 特别是在电影中看见地球的大气被木星捕获时候的画面。一方面,地球发动机喷射出淡蓝色的粒子流,另一方面,地球的大气又和木星的大气进行了交融。那时我才真的感受到木星和地球体积的对比。我想,站在地球上,看见如此庞大的一个星球完全占据了整个天空,那种压抑感、对无尽和死亡的恐惧以及在得知地球即将解体的消息后的无助是难以言表的,只借助用这样的画面呈现出来。 另一个让我感到很震撼的画面是电影开始时候,镜头从车队的特写一直向上拉动,到了华北平原,又到了行星发动机,最后到了宇宙上空的领航者号空间站。每一个画面中都充斥着那种苏联式的巨型机械感,让我非常兴奋。 其实这里的画面又让我有了一些游戏感,很像《红色警戒》2 或者 3 中雪天场景中的苏联。有一阵儿对红警入迷很深,做梦梦到的都是里面的场景,和电影中的十分相似。所以看到这里,让我十分惊诧,没想到梦中的画面居然在这部电影中被看到了。特别地,在最后刘启赶往苏拉威西转向发动机的时候,他的车子开入一个底座,然后底座开始旋转,这不就是红警 3 中的矿车嘛! 剧情 不得不说,大刘给流浪地球所设定的如此宏大的世界观给了电影很大的发挥空间。电影中的这一段地木相会在书中花了可能不到一页的笔墨。但是通过合理的剧情拓展,已经可以撑得起一部电影了(甚至还因为控制时间被剪辑掉了一部分)。 可能就是受到剪辑的影响,电影前一小段的剧情进展太快,很多事情都没有交代。比如刘启为何要离家出走,妹妹为何也想出去看看,这些年为何父子产生了这么大的矛盾,都没有交代清楚。有些剧情虽然可以脑补,但是多少会让人在现场的时候感到疑惑或者是突兀。不过,从他们兄妹两人踏出地下城电梯门的那一刹那,一切节奏都恢复了正常。 而剧情中令人印象最深的不是抢救火种,不是太空行走,也不是重启发动机,而是运载车启动时不断重复的那几句话:“道路千万条,安全第一条;行车不规范,亲人两行泪。”由于其严重的不押韵,一度让我听得百爪挠心。但是随着剧情的推进,这句话也逐渐融入了更多的情感,特别是在“姥爷”变成“冰雕”之后,在听见这句话,已经是对“老东西”的那种怀念。 另外,在电影最后还给了一个很大的彩蛋。透过主角的视角,可以看见地下城中有人喊着“还我阳光”的口号在进行游行。我立刻就想到原著中反派认为流浪地球计划是一个谎言,于是发生暴乱,占领了地球发动机的中控室,并将流浪地球计划的领导人都执行了最冷酷的“冻刑”。这是不是说明之后还会有《流浪地球 2》上映呢,内心又燃起了很强的期待。 一些有趣的点 整场电影都的基调都非常刘慈欣,在电影中,集体主义和生存主义被体现的淋漓尽致。比如,在地球停止自转后,地球上的人口锐减了一半(这不就是灭霸嘛),而进入地下城也需要抽签决定,如果没有抽到签,在地表的冰天雪地中能够存活下来的人怕是寥寥无几。但是电影有正当的理由——为了更多人,为了整个人类的生存,必须这样做。这种做法对于自由、平等、博爱所建立的西方价值观是一个颠覆,许多人怕是也无法接受这样的设定。 是的,我们会为了大多数人以及我们的子孙后代牺牲另一部分人。但是,如果按照好莱坞的套路,最后不会选择带着地球离开,而是带着人类文明的种子(也就是火种计划)离开。这样,活下来的可能就不是地球上的数万万人,而是领航者号空间站上的这些人。这样就人人平等了么?文明得以延续的基础是整个人类的大社会。如果只剩下空间站上的这些人,在他们离开的那一天,可能就已经不是人类了。 第二个是真实,非常的真实。在救援队试图借助电梯从峡谷中向上方运送火石的时候,一个队员为了救爷爷,被掉下的电梯活活砸死,而爷爷却也在被救后的几分钟内,因为衣服漏气,导致氧气消耗殆尽,最后自行解开头盔,成为了一座冰雕。队员的死并没有换来爷爷的活,而爷爷的死也并没有激发刘启“拯救人类”的斗志。这样的例子还有很多,比如随处可见送火种的人成为了冰雕。他们的死亡都几乎是没有任何意义的。电影脱离了必须要给死亡安上一个意义的俗套,也没有了失去了什么必定会得到什么的“真理”,让观影体验更加的真实。 另一个有意思的地方是“饱和式救援”。着同样也超越了西方模式化的超级英雄电影。 在那一类的电影中,一定是主角与家人不和,然后出走,之后是众人皆醉我独醒,发现了一个惊天的秘密,但大家并不理解他,除了他的几个好朋友。于是他叫上了他的好朋友,开始闯关似的拯救世界。最后,他凭借一己之力,扭转了局势,拯救了世界,并且还一不小心收获了爱情。 但是在人类生死存亡的关键时刻,哪有人敢怠慢。行星发动机的是全人类最后的希望,必须全力以赴。所以人类派出了远远超过发动机数量的团队,征召了所有可用的力量,来修复每个行星发动机。比如在主角们赶向苏拉威西发动机的时候,就已经有人提前到达,并成功重启了发动机;而主角们想到要点燃火星的时候,也被告知以色列的团队也已经早在几个小时前想到了这个方案,等等。这才是认了里面对灾难应该有的状态,而不是靠着主角团一路开挂,就拯救了全人类。英雄们是存在的,但不只有一个。 一些比较吐槽的点 首先,情节太过单薄的问题已经在上面说过了,这可能是因为剪辑的原因,希望以后可以看到完整版的电影。 其次就是最后用木星爆炸的冲击力来推离地球的设定,让我突然有一种“这个地方有点假吧”的出戏感。因为大概想一想,靠化学能量的非定向爆炸来将地球从如此大的一个引力场推走,是一个多么不靠谱的决定。不过科幻也总是包含很多幻想的成分,在没有更好的方案的情况下,我表示理解。 另一个是主题升华的问题。电影中一直表现出的是父亲对孩子的爱,但是却没有升华到对于地球上的人类,以及对于子孙后代的爱上。这样很容易让人感到父亲开着空间站去点燃木星是因为要救他的孩子,顺便拯救了全人类。这样就让我去思考到父亲在空间站中的决策是自私还是博爱,以牺牲“火种计划”为代价来换取概率几乎为 0 的成功是否值得。 最后 总的来说,《流浪地球》给中国科幻电影做了一个好的榜样,希望以后可以有更多类似的好电影上映。 “啊,地球,我的流浪地球啊!”
Intro 倏忽之间,又过去了一年。是时候向 2018 说一声再见了。 今年,我只在写 年关随笔 时立了一个 Flag,要练习深度写作,至少每个月输出一篇文章。可惜因为暑假时候偷懒,这个 Flag 也并没有达成。不过,我还是要在 2019 重新立起这样一个 Flag —— 2019 年每个月至少输出一篇文章,可以是技术,可以是随笔,也可以是观点。希望明年这个时候再回来看,不要像今年一样打脸。 再说回 2018。这一年相比前些年,在我的努力下,节奏逐渐慢了下来,让我有了许多时间静下心来思考、做事,也有时间和爱人相处、与朋友侃大山(至少在十一月之前是这样的)。同时,这一年又并不像以往那么顺遂,让我明白了人力有限,世间有太多力所不能及之事。 时光·记忆 在大自然面前,没有什么是能够永恒的。人不能够,事不能够,城也不能够,甚至文明也不能够。 在奶奶走后,我每每做梦都会回到小时候,梦见亲人们都在,他们都能和谐相处;梦见家乡没有衰败,还有很多人居住;梦见我很强,能够办到一切事情。 可惜梦中和现实却每每都是相反的。我无力改变太多,但至少可以做些什么。眼下能做的事情,就是把家乡的景,家乡的事儿多记录一些,等多年后,这里变成一片荒山野岭,无人问津的时候,人们还是能从我只字片语的记录之中,知道有这样一个地方,曾经有这样一批人,为了祖国的建设,把最好的青春和无尽的才华都奉献在这西北的茫茫荒山之中。 我在这里贴上几张年初所拍的照片吧。 这两张是白银市老城区白天和晚上的景观,拍摄于白银市人民医院综合楼楼顶。白银市从今年开始增加了许多城市亮化的工程,在白天看来就是一个暮气沉沉的工业老城,但是在晚上看来尤其漂亮。 下面的照片均是在西北铜加工厂(884)社区拍摄。 这是 884 附近的一座山,山上有一块形状酷似像狮子的石头,所以小时候我们都管这个山叫做“狮子山”。不过可能由于石头连年风化,已经随时会滚落下来,才有人在山脚下立了块标识危险牌子。 这是一条通往旁边村子的路,货车多半可能是准备进入大山深处,去拉从山中开采的石灰石。水泥路已经被一辆辆超载的大货车压得几乎没有了。 这是西北铜加工厂的正门(有两个,这是其一),在当年国企兴旺之时,上下班的时候人们进进出出摩肩接踵,但是现在物是人非,已经看不到什么人进出厂区的大门了。 这是通向厂门的桥,当年为了让职工可以在发洪水的时候安全上下班,就修了两座连通厂区和家属区的桥。桥下还有一个足球场,在发洪水的时候用作泄洪(我们称为“沙沟”),这两座桥似乎一个名为“兴旺桥”,一个名为“振兴桥”,但是年久失修,现在已经是两座危桥了。 这是奶奶之前工作过的食堂,由于年久失修,们都已经被水泥封了起来。透过门缝,还能看见火红的标语,一刹那间仿佛又回到了当年。 这是西铜的职工浴池,我们都叫大澡堂。前几年浴池终于也关闭了,我只能透过蒙着厚厚灰尘的玻璃撇入浴室的一角,找到当年的回忆。 这是当年“达利居”饭庄(之后改名为西铜宾馆)的舞厅。据说西铜鼎盛的时期,会有许多年轻的男男女女穿梭在其中。不过我记事的时候已经没有什么人了。 这是一座无名小山上凉亭的一角。当时厂里希望把这座山修成一座公园,于是修整了山道,修建了几座凉亭。不过还没修好,就已经开始亏损,这座公园也就只剩下这两个凉亭了。 以上都是我在西铜或者说是白银记忆的一个小角落,还有大量我的、同学的、长辈们的记忆有待挖掘。现在的西铜,已经破败不堪。一眼望去,只有一座座废弃的建筑和一望无际的荒山。一路走去,已经很少能碰见朝气蓬勃的年轻人,只有沟壑纵横的长辈们。这座城因他们而生,也注定会随着他们离去而消失殆尽。所以,我能做的,就是尽量用图片和文字来保留着座终将消失的城,留住他们珍贵的记忆。 出游 相比前几年,今年出行的地方没有那么多,但是每个地方都很值得好好留念。正所谓——世界很大,不能只看去了哪里,还要看是和谁一起去的。 苏州 2018 年出行的第一站是苏州,这也是我走过得祖国河山中最喜欢的地方之一。苏州不仅有数不尽的美丽景致,更有独特且很有内涵的地方文化,更有令人适宜的生活环境。更重要的呐,这还是和卤蛋同学第一次一起出远门旅行。刚好还欠卤蛋同学一个游记,就先在这里简要地写一下好了。 苏州城区不大,只靠地铁和公交在一个小时之内都可以到达。但是在城内逛游的时候真的可以感受到就像它的园林一般的一步一景。走着走着,就会出现一座很漂亮的古桥,又向前走两步,就到了一个小有名气的私家园林,再向前一看,发现这不是一家百年老字号嘛(快快快去吃好吃的别看了)。 另外,苏州话也非常有特色。虽然我听不懂,但是可以感到说话的人都还挺温柔的(就算骂人都骂的很柔),在苏州评弹里就更能体现了,每一个曲调都很柔美,让它所表达的故事格外的柔美。当地人也都十分热心,问路时候可以感受到他们十分有耐心,说话比较慢,令人舒适感倍增。当然,这也从侧面体现了他们生活比较安逸,节奏比较慢。 下面,我贴几张比较有意思的照片好了。 这是刚到苏州时候,沿着一条街向虎丘走,卤蛋同学在街边特别激动,左拍拍右拍拍。我刚好留意到他们街头布置得很细心,很能为街道环境和有宠物的人着想,在墙边设立了宠物便纸箱。 街角一转,居然到了到了河边。河边灰白色的天空衬着灰白色的墙,倒映在水里,十分宁静。这时,船夫划着一艘游船从远处徐徐开来,扰乱了湖水,也令人在心中荡起了浅浅的涟漪。 虎丘山中的一个沟壑。虽然小,但也有了一种悬崖峭壁的感觉,沟底的水中映出来天上的雨滴,植物颜色十分撞眼,犹如通往世外桃源的一条幽径。 市内的一座不知名公园。远望时感觉画面十分饱满,而近观又感到场景十分开阔,眼睛都舍不得多眨一下,生怕错过哪一处的美景。 苏州评弹剧场内部。老师底蕴深厚,唱腔优美。很多字发的都是古音,所以需要有一块屏幕向观众展示唱词。每唱一曲都会一下子把我带入古代天堂般繁华的苏州,置身闹市之中,但又让我进入心流。 黄天源的苏州小吃。如果苏州糕点说自己第二好吃,怕是没有什么地方敢说自己是第一了。不过最让我惊喜的是苏面,这是我除了牛肉面以外第二个感到能百吃不厌的面了。 除了这些,在苏州还有许多有趣的见闻,而且都还有许多没有去到的地方。以后可以有缘再见咯。 天津 在暑假的时候去了一趟天津。我在这次旅行中得到了一个惊人结论——天津和北京一样热。所以以后夏天还是老老实实待在实验室吹空调好了。 即使这么热,天津之眼还是要排很久的队,所以就随手拍了张照。 天津之行的时候还是我和卤蛋同学在一起一周年的纪念日,于是——再回来以后我受宠若惊呆若木鸡地收到了卤蛋同学送的花(hmmm 这应该也是人生中的第一束花),拿到花以后我甚至不知道该把它放在哪里,是不是应该把它插到水桶里养起来。 灵山 高中地理曾经学过——海拔每升高 100 米,气温下降 0.6 摄氏度。这大概是我在灵山之行中最直观的感受了。灵山是北京第一高峰,在门头沟区,几乎已经到了北京同河北的交界处,需要驱车三个小时才能赶到。 灵山是同潘老师、林博以及卤蛋同学我们一行四人去的。去爬山的时候天还是挺热的,所以我带了一件坛服去御寒。刚开始爬的时候我还很跳腾,但是到了一半左右,山势变陡,狂风大作,气温骤降。已经冷到了骨子里,无奈只好作罢。 灵山上的风景与山下迥异,像极了西北高原,居然出现了驴子和牦牛。下面是在灵山拍摄的一些照片。 活动 今年我也刻意减少了很多不必要的活动。在这说说几个比较有意思的吧。 Hack for Good 我最开心的不是又参与了一次 Hachathon,而是 Hackathon 成为了俱乐部每年的例行活动(而且这一场还是卤蛋同学参与办的)。 这次 Hachaton 弹幕派作为合作方参与到了其中,并且我也带团队来和大家一起 Hack 了两天,成果显著,做好了小程序和控制面板上线前的最后准备。 SDN 大赛 还有一件值得一提的事情是搞了两年的 SDN 大赛终于以二等奖为结果圆满收官。比赛中强哥、天骄、高钱、格格这些队友都十分给力,另外加上小雨姐和潘老师的助攻,让我们成功杀入了决赛圈。 现在看来,我做了一年的 P4 实验系统也随着这个比赛到了的尾声。一年以来,我总是在不断地在这个系统的 Bug 和 Debug 中度过,每天总是担心训练效果不是很理想(事实证明效果的确也不理想)。前些日子,基于这个系统的 INT 遍历算法中了一篇 Infocom,也算是这套系统的最大成就吧。 技术 今年最令我担心的就是我的技术了。因为离工作的日子越发的近,我却还是没有一门能拿得出手的技术。这些年来,我做了挺多多,了解了挺多,都是项目要用到什么就去学习什么,一直没有给自己找到一个能够系统深入的方向,说起来也很是惭愧。 不过下半年也算是有一个好的开始吧。我开始去刷 Leetcode(刷过的题目都可以在博客中的leetcode 板块看到),去看一些框架的源码,开始做 Go 语言的项目,去了解容器和一些 Linux 协议栈相关的知识。现在看来,能做的就只能是再接再厉,不要再掉链子了。 弹幕派 在这里我也给弹幕派做一个年终总结吧。 去年一年,弹幕派的增长效果还不错。在推广营销投入几乎为 0 ,且前有夹击后有追兵的情况下,靠着之前的老本,在用户量和营收上都得到了明显的增长。 在今年一年,一共有数万个用户注册了弹幕派账号,成功举办了几千场大大小小的活动。一共有数十万人参与到了活动现场的弹幕互动中,产生了数百万条弹幕,并产生了数万个付费订单。另外,弹幕派网站创造了数百万的 PV 和几十万的 UV。这在互联网公司看来可能都不算什么,但是对于我们团队来说,是一个不小的进步。 我们也投入了大量的精力在研发上面。我们在去年年初就将系统迁移到了 Swarm 集群中,并配置了半自动的编码、发布、测试、预览、上线的环境,经过一年的实践,系统运行良好。而在前端层面,我们将原有的多种技术栈进行了整合,都迁移到了以 Vue 为基础的技术栈中,这样团队成员在不同项目之间切换就变得游刃有余。而后端我们继续沿用了 Laravel+Workerman 的架构,并感到目前还远远没有到达天花板。 今年的状况就是这样,期待明年可以有更强劲的增长吧。 一些小事儿 今年有两次去同学家里做客的经历。一次是同卤蛋同学一起去胃寒家,一起做了一大桌菜(感谢心灵手巧的胃寒和蓉蓉);另一次是自己去牧野家,一起吃了一顿史上最小的小火锅(感谢牧野和小团子的精心准备)。 吃腻了学校的食堂,有时候觉得能和爱人在自己家里做饭吃(特别是再和朋友一起煮一锅热气腾腾的火锅,一起聊聊天),的确是很开心的。所以我有时候也开始对未来在外面打拼的时光充满期待。谁知道呢,或许也是一座围城吧。 年中的时候我也拿到了驾照,不过今年唯一一次路驾还是和牧野,从中关村开到了学校。以后有机会还得多开车,向着老司机的行列迈进。 年末的时候玩了今年的唯一一款游戏《古剑奇谭 3》,虽然游戏只花了 99,但是我体验到了几倍于价格的诚意。游戏画面精美,优化十分到位,我在大一时候买的 Y400 在现在还能基本流畅地进行游戏,即时战斗也颇为精彩。而在游戏中,文明的光芒、历史的厚重与人类的传承在我的眼前徐徐展开,而主角之间那种的真情也让我倍感舒适。比起之前玩的仙侠类的 RPG,古剑三的进步不止一点点。最近有机会我想专门写一篇相关的文章。 沉重的话题 2018 年这一年,有很多人离开了我。包括我的亲人,以及许多敬重的人。 奶奶是 2 月份走的,现在每每想起心中依旧难以平静。而在这之后,这一年难过的事情就没有中断。 回忆了一下,逝去的科学巨匠有霍金和高锟。霍金的黑洞理论其价值非我所能评论,但是他的《时间简史》以及《果壳中的宇宙》陪伴了我整个少年时期。而高锟所发明的光纤正构成了我们现在有线通信的根基。他们的逝世是全人类的一大损失,随着他们的离开,科学世界的光芒也变得暗淡了许多。搜索后我才知道,仅 2018 年,就有 31 名两院院士离开了我们。这怕是绝无仅有的。他们共和国的栋梁,他们的贡献深刻地影响着我们的生活,我深切缅怀他们。 除他们外,还有许多社会知名人士也相继逝世,他们许多人对我们的社会有着深刻的影响。联合国秘书长安南是一个小时候经常听到的名字,他所在的时候,也是联合国知名度最高的时候,他纵横捭阖,致力于解决国际争端,给全人类一个美好的世界。著名主持人李咏的去世也勾起了我对童年的回忆,在印象中,他是一个每天都很欢乐,手持锤子打电话砸金蛋的主持人,但从没想过他会这么快就离开这个世界。而他和哈林的爱情故事,更令我十分动容。除此之外,对文化影响很深的还有金庸、李敖和曾仕强。金庸无需我多言,小时候看的很多武侠小说和电视剧都出自他手,他告诉了我什么是江湖,也给我带来了无数乐趣。李敖和曾仕强是台湾的两位思想家。其中,曾仕强《易经》可能是百家讲坛最受欢迎的节目之一了,他讲的内容大多记不得了,但是他那种睿智而又儒雅的风格令我印象深刻。 最后,还有田家炳先生。田家炳先生在国内可能没有邵逸夫那么有名,但是他也捐助了无数学校,让这些学校可以改建校舍,购置教具,相互交流,提升教学质量。我所读的高中就是一所田家炳中学在上高中的时候,我们体验到了新的教学楼,用上了很高级的电子白板。而在我们毕业后,学校也将土操场进行了翻新,还新建了装备齐全的科学楼。这其中有很大程度上都是由于他老人家。作为一名田家炳中学的学生,听到他逝去的消息,我的心情相当沉痛。 而我们国家也正在经历“百年未有之大变局”。这一年随着贸易战开打,经济局势开始变得紧张起来。随着数字货币、互联网借贷和共享单车的迅速衰落,互联网产业也迎来了变数很大的一个时期。独角兽们即使股票纷纷破发,也要流血上市,而下半年来更是处处风传裁员浪潮。这一年注定是一场场艰难的战役。 The End 不论如何,时间是不会停下来等人的。现在,2018 年已经过去,2019 年已经到来。该面对的还是要面对,该承受的也要去承受。 上面所回忆的事情,不论好事坏事,不论是否开心,也只是生活中事情的一少部分,生活中更多的事情,是稀松平常,甚至结束以后就会忘记的。但是,这些事情其实才是生活的主旋律。倘若可以在这些事情之中发现美,生活也就会有更多的乐趣了。 2019 注定依旧是很有悬念的一年,不能守成,还需不断奋斗。加油吧!
问题 1 键盘弹起后会遮挡键盘上方的内容 在微信浏览器中,如果需要模拟一个类似微信聊天的窗口,那么一般情况下需要将输入框使用 fixed 定位放置在页面最下方。就像这样: 但是,在 IOS 中的虚拟键盘和 Android 里是不同的。在 IOS 中,虚拟键盘弹出以后,键盘上面的输入提示会比键盘弹出慢半拍,所以就会导致输入法的提示框将正常页面挡住的情况。 这时候,就需要在键盘弹出后,等待一段时间(几百毫秒),然后再将页面的滚动条进行调整,就可以让页面弹到键盘之上。 假设页面布局如下(使用了 Vue 框架),其中 Dialogues 组件是可以滚动的聊天内容,PageFooter 是使用 fixed 定位在页面底部的输入框: 1 2 3 4 5 6 7 8 9 10 <div id="index"> <div id="mainPanel"> <div id="dialogueContent"> <Dialogues></Dialogues> </div> </div> <footer> <PageFooter></PageFooter> </footer> </div> 然后,就可以在监听到软键盘打开事件(比如 dialogueContent 中 input 元素的 onclick 或者 onchange 事件)后,执行下面的语句,让 dialogContent 的滚动条向下滚动,这样里面的内容就不会被覆盖了。 1 2 3 4 5 6 7 let dialogueContent = document.querySelector('#dialogueContent') setTimeout(function() { dialogueContent.scrollTop = dialogueContent.scrollHeight setTimeout(function() { dialogueContent.scrollTop = dialogueContent.scrollHeight }, 250) }, 250) 至于为什么要触发两次呢,是因为 IOS 手机种类比较多,从五六年前 iPhone5s 到最新的 iPhoneXR 都有人在用,每种手机的响应速度也各不相同,有些手机可能在第一个 250ms 内还没有完成键盘弹出的工作,所以可以再加一个定时器来兼容旧的手机。 但问题也依旧存在,就是 fixed 定位的输入框在软键盘弹出以后就不会像 Android 一样固定在页面底部,而是可以上下滑动,类似于 absolute 定位。这时,可以将 body 设置为 absolute 定位,然后再将 MainPage 使用 absolute 定位于 body 底部。 问题 2 在虚拟键盘收起以后 body 定位的问题 紧接着,第二个问题就出现了。在点击虚拟键盘右上角的“完成按钮以后”,页面下方并没有回弹,而是定在了原来软键盘上方的位置,必须在页面上滑动两下才可以触发回弹。(特别是在大屏的 IOS 手机上) 大概是这样: 而在将 body 设置为 absolute 以后,情况更加离奇。页面 UI 是回弹了,但是触控事件响应的位置是没有回弹的,依旧是软键盘打开区域的上方。解决方法同样是必须在页面上方华东两下才能回弹。 大概是这样: 最开始的想法是去监听 resize 事件,如果软键盘收回,就强行调整 body 的高度,但是发现软键盘的弹出和收回并不会触发该事件,只得作罢。 最终,找到了一篇解决 类似问题的文章,才找到了解决方案。 大致思路就是在键盘收回以后,主动触发浏览器对页面的重绘操作。如何进行呢?只需要在监听到 onblur 事件以后,让页面滚动到原来的位置即可。 比如,组件模板中的 HTML 为 1 <input type="text" placeholder="发送内容" v-model="content" @blur="resizeWindow"> 然后可以在 JS 的 methods 中添加一个函数: 1 2 3 resizeWindow() { document.body.scrollTop = document.body.scrollTop } 这样,输入框在失去焦点以后会触发页面的重绘,刚刚的问题就随之而解了。
现在,Docker 已经成为了一个非常主流的虚拟化技术,它集合了 Linux 中的许多虚拟化技术,如 Namespace、cgroup 和 AUFS 等等,所以我们可以使用 Docker 搭建一个开箱即用的虚拟化容器。但是,Docker 网路在很多时候依旧不能满足应用场景中的需求,这就需要我们对 Docker 中的网络进行自定义了。 这篇博客就是关于位于不同虚拟机中的两个容器实现 vxlan 通信的实验。 拓扑和环境 首先讲一下实验拓扑吧,它大概长下面这个样子。 一共两台虚拟机,在两台虚拟机之间使用交换机连接。在虚拟机内部各有一个容器,容器和主机通过 bridge 连接,而这两个虚拟机之间需要通过 vxlan 进行连接。 而我所使用的虚拟机是位于 vSpere 中的两台主机,他们采用 vSphere 中的虚拟机端口组相连,并且该端口组开启了混杂模式。两台虚拟机均为 Ubutnu18.04 系统,Docker 也为最新版本。 我还基于 Docker Hub 中的 Ubuntu 镜像封装了一个带有 ifconfig、ping、ip 和 python 命令的镜像,可以通过docker pull bzzdzc/myubuntu命令获取。 当然你可以通过在容器中的命令行内运行如下 apt-get 命令来安装这些软件: 1 2 3 4 5 6 7 apt update // ifconfig apt install net-tools //ping apt install iputils-ping //ip apt install iproute2 开始搭建网络 配置 VM1 中的网络 现在开始在 VM1 中对网络进行配置。 关闭防火墙 1 sudo ufw disable 运行容器 需要注意的是,在运行 Docker 时,选择不连接网络,之后我会将它连接到自己建立的网桥上面去。 1 docker run -itd --network none --name myubuntu1 myubuntu 记录容器中主进程的名称空间 首先,查看容器主进程的 PID,然后将其保存到$pid 变量中。 1 2 docker inspect --format '{{.State.Pid}}' myubuntu1 pid=$(docker inspect --format '{{.State.Pid}}' myubuntu1) 然后,将该 pid 链接至/var/run/netns 中,以便 ip netns 命令可以访问到它。 1 2 sudo mkdir -p /var/run/netns sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid 创建 veth peer 我们通过创建一对 veth peer A 和 B 来维持虚拟机主机通容器之间的通信。 1 sudo ip link add A type veth peer name B 创建新的网桥 这一步创建了一个名为 br-vx 的网桥,并将其状态设置为 up。然后,为网桥分配 ip 地址 192.168.0.1/24。 1 2 3 sudo brctl addbr br-vx sudo ip link set br-vx up sudo ip addr add 192.168.0.1/24 dev br-vx 将物理端口和 veth 端口 A 分别链接至网桥 虚拟机的物理端口一般为 ens32,我们将该端口连接至刚创建的网桥中。 然后我们将刚刚创建的 veth 的 A 端口绑定到网桥中,并设置其为 up。 1 2 3 sudo brctl addif br-vx ens32 sudo brctl addif br-vx A sudo ip link set A up 将 veth 端口 B 绑定到容器的名称空间中 这一步将 veth 的 B 端口放置于容器中,并将其命名为容器中的 eth0. 1 2 3 sudo ip link set B netns $pid sudo ip netns exec $pid ip link set dev B name eth0 sudo ip netns exec $pid ip link set eth0 up 为容器中的 eth0 端分配 IP 地址 为容器中分配 IP 地址 192.168.0.100/24。 1 sudo ip netns exec $pid ip addr add 192.168.0.100/24 dev eth0 现在,如果配置正确,使用命令docker exec -it myubuntu1 bash进入容器中的命令行后,运行ping 192.168.0.1 ,应该已经可以得到主机的回应了。 配置 VM1 中的 vxlan 连接 这里需要现在容器中创建一个 vxlan 连接。我们规定它的 id 为 100,而 vxlan 底层网络连接两端的 IP 分别为两个容器的 IP 192.168.0.100(本段)和 192.168.0.101(对端)。 然后设置 vxlan 隧道的 IP 为 10.0.0.1/24。 1 2 3 4 sudo ip netns exec $pid ip link add vxlan1 type vxlan id 100 remote 192.168.0.101 local 192.168.0.100 dstport 4789 sudo ip netns exec $pid ip link set vxlan1 up sudo ip netns exec $pid ip addr add 10.0.0.1/24 dev vxlan1 配置 VM2 中的网络 VM2 中网络的配置方法和 VM1 中类似,只是分配的 IP 地址有些变化。 给 VM2 中网桥分配的 IP 地址变为 192.168.0.2/24 给 VM2 中容器里的 eth0 端口分配的 IP 地址变为 192.168.0.101/24 给 VM2 中 vxlan 连接分配的本端 IP 地址变为 10.0.0.2/24 另外,在 vxlan 网络配置好之前,就已经可以使用 ping 命令测试底层网络网路的连通性了。 1 2 3 4 5 6 //测试和容器的连通性 ping 192.168.0.101 //测试和VM1的连通信 ping 192.168.0.1 //测试和VM1中容器的连通性 ping 192.168.0.100 测试 vxlan 网络的连通性 这个其实非常简单,依旧是使用 ping 命令。 首先,在 VM1 中进入容器内测试: 1 2 docker exec -it myubuntu1 bash ping 10.0.0.2 然后,在 VM2 中进入容器内测试: 1 2 docker exec -it myubuntu1 bash ping 10.0.0.1 如果双方都可以连通,那就说明基于 vxlan 的 隧道已经创建成功。 其它注意事项 有关混杂模式 首先需要注意的是,由于我们在 VM 中创建了容器,而又把 VM 和容器中的端口都绑定到了网桥上,所以,从 VM 中出去的包的 MAC 地址可能 VM 的也可能是容器的。由于安全策略限制,esxi 中的虚拟交换机默认会丢弃非 VM MAC 地址的包,这时就必须要开启混杂模式,让它不对源 MAC 地址进行验证。具体原理可以参考:http://blog.51cto.com/9843231/2294188?source=drh 一些参考资料 理解 Docker(3):Docker 使用 Linux namespace 隔离容器的运行环境 Docker 核心实现技术(命名空间&控制组&联合文件系统&Linux 网络虚拟化支持) 使用 CentOS Linux Bridge 搭建 Vxlan 环境 Linux-虚拟网络设备-veth pair
intro 上学期同Thesharing以及 Stone 去北大旁听了将近一学期的《科技创新与创业》(课程网站:http://net.pku.edu.cn/dlib/pkuxstart/)。 这个课程由百度七剑客之一的雷鸣主持,邀请了很多行业内有名的企业家来讲课,几乎都是北大校友(感慨一下北大校友文化真的很棒)。 我想在这篇文章中总结一下他们所讲的一些能引起我思考的观点和内容,以及经过我提炼加工所得到的结论。 三角关系 这里的三角关系并不是指恋爱中的那种复杂关系,而是指一个行业中相互制约的几个要素之间的复杂关系。其中一种要素发生大的变化(一般是非连续性的),这个产业整体以及几个要素之间的相互关系也会随之发生变化,这往往预示着新机会的到来。 微博 CEO 来去之间举了一个例子——移动互联网中存在的三角关系:运营商、手机制造商和互联网公司。 这个三角关系中某个要素发生变革就会导致移动互联网行业的巨变,会有一波新的公司起来。比如运营商 4G 网络的普及,使得网速变得越来越快,流量变得越来越便宜,这就催生了短视频行业的兴起,这也催生了一系列的公司和产品,比如快手和抖音。 明略数据的吴明辉提出在大数据这个产业中也存在一个三角关系——人、数据源和场景。 数据源在未来物联网时代会发生很大的变化,数据世界中的主导位置可能会从原来的因特网中的数据变为物联网传感器中的数据,这对于很多公司来说是一个全新的机会。 我认为,这些三角关系代表了一个场景之下的几个利益方,某一个方面出现技术或者认知方面的突破,都会造成行业的重新洗牌。比如在虚拟现实产业链中,不仅仅有设备制造商和内容生产商,还会有运营商的机会,因为 VR 数据的传输需要大量的带宽。所以,5G 时代到来以后,VR 会不会又重新火起来呢? 产品方法论 很多嘉宾都不约而同地说到了俞军老师有关用户收益的产品方法论: 产品的价值(用户的收益)= 新体验 - 旧体验 - 迁移成本。 这条方法论十分重要,是衡量你做出的一个新产品是否可以干掉旧产品的一个关键因素(这条方法论也经常被SGanker提到)。 我认为这条方法论的基础是一个共识——真正的需求不是被创造出来的,而是来自人类的本性。你所创造的产品是使用了一个新的形式或者新的技术来包装这个古老的需求。所以,一定要想清楚,你在做这个产品的时候到底替代了谁,新的产品解决了什么旧的问题。 另外一个问题是如何区分真的需求和伪需求。这里 OFO 的戴威提供了一个方法——将这种需求通过英语 Need/Want 归类,如果是 Need,那么这个需求就是真的需求,如果是 Want,那么这个需求就是伪需求。 不仅仅要检查这个需求是真需求还是伪需求,更要思考这个需求是不是只有自己需要,还是你的朋友也需要,更或许是人人都需要。 这里也有一个技巧,就是把自己的产品和别人去讲解,看他们是否感兴趣。不过身边的人可能会担心得罪你,不愿意说你产品的坏话,所以可以要找陌生人后者敢于说真话的人来介绍你的产品,来了解这个需求到底是不是真需求。 顺势而为 说到顺势而为,我第一个想到的是雷军和他的顺为资本,以及他“风口上的猪”的理论。我想,他也是因为带着金山硬挺了这么多年才悟出的这个道理吧。我想,现在的小米就是他顺势而为的结果。 关于顺势而为,还有一句话我很喜欢,也想放在这里——“一个人的命运,不仅要看个人的奋斗,还要看历史的进程。”这句话从辩证唯物的立场讨论了为什么要顺势而为。 下面就来讨论一下如何顺势而为。 从更高的高度来看问题 微博 CEO 王高飞从很高的层次来分析了微博发展的时候遇到的许多问题以及解决方法。他和他的团队的思考更多的不是现在用户的需求,而是未来五年左右中国经济社会的发展可能会让某些用户的需求变得旺盛,微博就会在这里提前布局。而对于许多目前无法超越的竞争对手,微博选择了从大局考虑,不去正面竞争而更换别的赛道(关于赛道,下面还会提到)。 比如微博在 2012 年就判断了中国经济未来五年的趋势:比如移动互联网的增量更多来自于二三线城市。而微博当时发力的主要方向就是去做二三线城市的消费升级,上线了一系列针对这部分用户的产品。 但是这样的分析可能也会有所遗漏,比如微博没有想到五六线城市的下沉也会是一个机会,而这个机会造就了快手的极速增长。 从这些嘉宾的口中,我也大致总结了大家都认为未来可能会蓬勃发展的行业: 首先还是互联网和 AI。互联网会和传统行业更深地结合(也就是互联网+),而 AI 也会改造更多的行业。 另外一个风口是生物医药(我之前的确从未关注过)。由于生物医疗行业中基因组学和蛋白质学这些基础技术的突破,导致未来这个行业很可能会非常蓬勃地发展。现在,投资机构对于生物医药行业的投资已经是仅次于互联网行业的存在了。 随着中国人口红利的消失,很多产品野蛮生长的机会已经不是很大了。而大家对于消费升级的追求导致会有更多的创新品牌诞生。 人口红利消失也会导致 2B 的服务会越来越多。 所以如果要创业,那么一定要选择具有先发优势的行业,即判断未来几年的主力消费人群和用户增量会在什么地方。 最后需要提到的是,现在全面创业的热潮已经过去,很多创业公司纷纷死去,只有很少具有竞争优势的公司存活了下来。 赛道的选择十分重要 赛道理论是投资界的一个理论。他们将某一个细分的行业或领域称之为一个赛道。而这个赛道上会有许多类似的公司在竞争(很像在一起赛跑)。既然是比赛,那么肯定会有前几名,而投资机构就会投资头部的那些具有竞争优势的企。等这个赛道成熟以后,可怕的幂律就会发生作用,前几家公司会吃掉这个赛道中九成以上的市场,后面的公司几乎没有任何机会。 所以,选择一个自己可以具有竞争优势的赛道就显得十分重要了。关于不同赛道上的公司的信息,可以在一些咨询公司的网站上面看见(比如艾瑞咨询:http://www.iresearch.com.cn/)。 另外,你所选择的赛道要具有空间和时间上很强的成长性。有空间的成长性,行业的天花板高,大家有充足的空间赚到很多,才会有人愿意投入资本和时间将这个市场做大。有时间的成长性,说明你做的方向是正确的,等五年到十年以后,时间还是你的朋友。 市场、产品和技术之间的关系 因为身边同学多是技术出身,所以我接触到的很多人可能都认为技术对于一个公司而言最重要的。但事实可能并非如此——许多产品是由市场拉动而非技术推动的,一般情况下开发一个市场所花费的成本要远远大于技术。所以开发产品时应当从市场的痛点来着手,而非技术高低。 不仅市场比技术更重要,产品也比技术更重要。在设计产品的时候需要有用户视角,使用同理心去思考用户的感受,并培养用户使用产品时的参与感。这里好像又说到了产品方法论,既然说到了,不如再举一个例子:腾讯的 10/100/1000 法则。这个法则要求腾讯的产品经理每个月必须做 10 个用户调查,关注 100 个用户博客,收集并反馈 1000 个用户体验。 商业模式 一个好的受资本市场欢迎的商业模式需要四个要素:能赚钱(有人买单)、规模性(可复制)、有壁垒(不会被腾讯抄袭)和可持续(未来成长空间很大)。 更好的商业模式不仅考虑了自身的发展,还要考虑到产业链中整个链条的利益分配问题。 非连续性机会 驱动社会经济发展的核心要素是非连续性机会,只有抓住非连续性机会的公司才可能获得爆发式的发展,并且有机会获得垄断地位。 好公司 护城河理论 “护城河”理论是巴菲特提出的。他认为好的公司需要有一条护城河来避免来自外部的竞争。有以下几种创造护城河的方法: 一种护城河是单一产品规模,公司拥有一个使用规模非常大的产品,以形成规模效应。互联网公司,如腾讯,ebay,沃尔玛等公司都属于这一类。 另一种是知识产权,比如商标或者一些关键的技术专利。迪士尼、耐克等公司就属于这一类。 最后一种是客户转用其他产品需要很高的成本。比如 Oracle 和微软。 几乎所有的好公司都在致力于建立护城河以获取垄断地位,最终占领用户的心智。 如何与大公司竞争 我一度认为类似“如果腾讯也开始做你们的产品怎么办?”这种问题是无解的。不过听完课以后,我的思路产生了变化。原因有三: 首先,你做的业务有可能是巨头们看不上的不怎么赚钱的小业务,除非这个业务以后会成长成为一个巨无霸,那么巨头一般是没有精力或者成本来同你竞争的。但就是这种巨头看不上的业务可能能让你赚的盆满钵满,在未来的某个时间,有娱非连续性机会,这个业务也或许就会成为商业的主战场。 其次,巨头往往都是上市公司,上市公司往往背负着到很多方面的利益。如果他们需要从赚钱的业务上将资源倾斜到其他需要和创业公司竞争的地方,那么他们的股东和员工都可能会不乐意,甚至股价也会下跌很多。所以,他们很多时候可能会选择投资或者收购而非直接竞争。 最后,市场中一直都有许多资本,为了不贬值,它们必须被投资到有很大增值潜力的地方,大公司往往增长不会太大,而增长潜力很高的小公司却可以做到。所以资本市场会愿意把钱交给具有很大增值空间的小公司,和大公司去打的。所以,为了可以和大公司竞争,创业公司需要业务+资本的双轮驱动。不仅要做好自己的业务,也要积极向外寻求资本,这样才能有一息存活的机会。 垄断才能创造利润 能够创造价值的公司并不一定可以创造很好的价格。因为在尚未形成垄断的时候,市场上存在着很多竞争对手,博弈论一定会在这里发挥作用,导致你无法让产品或公司有一个很好的价格。 这里说的垄断不一定是产品的垄断,还可以是应用场景的垄断。场景垄断垄断的是消费者的心智。比如苹果公司的产品,虽然从来没有在市场上形成单一品类的垄断,但是它们加起来形成了一个生态系统,它们垄断了消费者的心智。苹果公司靠它获得了非常高的利润率。同样垄断也表现在股价上——苹果公司在前几天终于成为了地球上第一家市值突破万亿美元大关的公司。 这里我还可以用小米公司来举一个例子。小米公司的互联网手机模式在刚出来的时候,受到了众多用户的欢迎,增长非常迅速,表现在资本层面就是估值越来越高。但是随后随着荣耀、OV 等公司加入这个赛道,同小米形成了强力的竞争,小米模式出现了很多问题。小米公司的价值无疑是很高的,但是没有垄断。这种问题表现在股价上就是上市以后几乎两次破发,小米公司并没有得到和价值匹配的好的价格。但是它的生态链以及和用户建立信任关系的商业手段都是在为建立(不同于苹果模式的)新的垄断去做尝试。小米公司的模式究竟能走到何时何地,我们可以拭目以待。 创始人 先说一个结论,创始人的高度很大程度上决定了企业的高度。因为企业的文化、商业模式以及关键决策几乎都来自于创始人。不过创始人的高度也不是一成不变的,他们会随着公司的成长不断的学习和成长。 领导力 一家公司的创始人往往就是领导者。领导者和管理者有很大的区别。管理者只需要管理员工,按时按量完成任务即可。但是领导人需要通过他的很强的人格魅力来带领大家一起向前。所以领导人不仅需要做事,还需要做人,不仅要做事做人,更需要有很长远的目光。 做为领导者往往都是很孤独的。麦肯锡健康的樊琴还说,创业不仅孤独,而且几乎没有成就感。因为一切都是从零开始,而且一旦开始就永远没有尽头。 刚刚说过,创始人的高度很大程度上决定了企业的高度,所以创始人需要很强的学习能力,需要在企业成长的同时也不断学习,要和企业一同成长,甚至要比企业的成长还要迅速。但是,创始人不仅仅需要学习,还需要将他的决策力、执行力、组织力和感召力都输出给其他人,营造气氛,让大家一起干起来,也就是“使众人行”。 创始人的选择 OFO 的戴威给出了他选择创始人的思路。首先,公司只能有一个创始人。其次,创始人和联合创始人之间必须要比较知根知底(比如哥们),同时也最好可以兼具能力上的互补。 另外,价值观的统一也非常重要,创始人之间需要有强烈的相互认可,不然现在的兄弟可能会变成之后的友商。 最不易稳固的结构是只根据需要能力来选择一些不太熟悉的人,雷鸣认为这样的创始团队算是“草台班子”,而草台班子是迟早要散的。 投资人的选择 将好几位老师的话总结一下,就是:投资人不仅要选有钱的,还要选择除了钱以外可以给企业提供更多帮助的人。另外在敲定投资的时候,一些法律问题一定要了解清楚,不然可能会因为协议中的一些条款就让自己倾家荡产。 商业和社会成熟度 商业不是过家家,创始人需要很强的商业成熟度和社会成熟度。 商业成熟度主要反映在对商业本质的认识:比如有客户来源、商业模式、对竞争对手的认识和投资策略等等。创始人没有很好的商业成熟度,公司一定是无法存活的。 社会成熟度来源于步入社会以后对社会的认识,比如经验、能力、人脉和价值观。董小玲认为,这些成熟度在高校中是很难锻炼出来的,而在大学生走上社会的半年左右的时间里,会逐渐积聚。这个时候创业者既对社会有了清醒的认识,还没有忘记自己的理想,是创业的最佳时机。 家庭关系 令我诧异的是有很多创业者创业中断的原因不是融资失败、竞争对手或者政策问题等等来自外部的因素,而是因为家庭成员之间意见不一致的问题。这里的家庭成员多是另一半、自己的父母或者是另一半的父母。 因为创业面临了很大的不确定性,所以如果其他家庭成员(特别是另一半的家长)接受不了这种不稳定性而不同意你去创业,这无异于后院起火,创业很可能就会失败了。 这里董小玲提到了一个规律,就是如果父母对创业特别懂或者一点都不懂,这都好办,但是如果父母半懂不懂那可能麻烦就会大一些了。 所以如何平衡创业与家庭之间的关系,是往往被创业者忽略的一个很大的问题。 一些其他的话题 以下是嘉宾们对于一些热门话题的比较有趣的观点。 区块链 邓锋认为,对于区块链应该区分链圈和币圈。区块链解决了信任问题,也解决了价值重新分配的问题,而币圈都是骗子。 一个技术(比如区块链)的出现并不能颠覆一个行业,只能作为增量而存在。 数据 吴明辉认为数据是对世界的观察,帮助没有观察的人解决信息不对等的问题。它可以创造信任,降低决策成本,帮助决策者进行快速的决策。但另一个角度来看,数据不一定是真实的,因为它是主观的,它本身也没有任何价值。但是只要它存在,就可以创造信任,而通过信任就会产生很多的价值。 另外,历史的发展多是不连续的,而数据代表过去,所以过去的数据很难预测长期的未来,但是它可以预测短期的未来。 商业的本质也是在利用信息不对等来解决问题创造价值,如果利用数据来做生意,使得信息对等了,那么商业就不存在了。所以用数据做生意是商业中的一个悖论。 商业计划书 弘道资本的李晓光认为商业计划书的目的是为了获得投资,核心内容是你投资我可以赚大钱。商业计划书的质量决定了 VC 是否会找你进行面对面沟通。 商业计划书需要准备几种:五分钟版本、演示 PPT、完整计划书、未来财务预测(需要专业的财务模型)。 推荐书 课堂上有许多老师给出了推荐阅读的书目,我也在这里略作总结: 最后,使用戴威的一句话作为文章的结尾吧: 不要迷信于别人的经验和方法论,创业者应该在创业中不断尝试,找到属于自己的方法论。
最近得知《青年马克思》上映,觉得有必要一看。于是上网搜了一下院线的排片,不出所料,排片量几乎是最近热映的《复联 3》的五分之一,甚至周末都少有商业影院有排期。于是只能等到周一约上阿文,去附近的影院一睹青年时期马克思的“芳容”。 刚入座,还在和朋友聊最近的中兴联想发生的一系列事件,电影就毫无防备地开始了。此时一个大概容纳五六十人的小影厅里只稀疏地坐着六七个人,多数还是学生(祖国未来还是很有希望的嘛)。 电影里的场景暂且不表,先来说说我对电影整体的感受吧。 首先,作为一部传记电影,而且讲述的是一位思想家的成长历程,如果没有一些对马克思生平以及思想的了解,可能就会在电影院呼呼大睡了。这可能会是观影的一个门槛。 其次,电影内容很丰富。短短不到两个小时,电影讲述了马克思从莱茵报编辑,到论战蒲鲁东,最后是完成《共产党宣言》这三个时期,同时又穿插了马克思与燕妮之间深沉的爱,马克思同恩格斯之间深切的友谊以及恩格斯同玛丽之间跨越阶级的感情。 电影不但对历史事件做到了真实还原,同时还把人物表现得有血有肉,把离我们很远的两位“大胡子”思想家拉到了和我们相仿的年纪,也做着和我们类似的事情——有为理想的奋斗的激情,有现实中碰钉子的无奈,也有有酒逢知己千杯少的感慨,更有和自己爱人待在一起缠绵,这对从感性上开始对马恩他们两个在我国近似于“符号化”存在的人物真正地了解,真的是太重要了。 最后,我觉得电影的确还缺乏一些对马克思思想令人深思的阐述。它讲述了马克思的成长历程,但却没有讲述出马克思思想的成长历程。比如影片用挺久时间讲述了马克思用《哲学的贫困》来反驳蒲鲁东《贫困的哲学》,我们得到的也只有他反驳蒲鲁东关于他的无政府主义,我们却始终没有听到具体反驳的声音到底在哪里。最后片中大声朗读《共产党宣言》中的内容,但是我相信多数人只是感觉热血沸腾,却不知其中平实语言中蕴含的深刻哲理。不过马克思思想的确庞杂而繁杂,不能对一个两个小时的电影求全责备,能讲出这些,已经很不容易了。 电影中的一些桥段也十分精彩。 开场像一幅浓墨重彩的油画,很有欧洲片的感觉。场景是许多德国穷人在携家带口捡树上脱落的枯枝。然后警察就突然出现,穷人被他们无辜杀害驱逐抓捕。毫无疑问,这里是说马克思在大学刚毕业去莱茵报工作时期对《林木盗窃法》进行批判的事情。所以画面一转,就是马克思对莱茵报许多同事太过软弱的激烈吐槽,而警察开始在楼下围攻莱茵报办公室的场景。 那时候马克思一直希望用黑格尔的法哲学来批判莱茵省议会的所作所为,但是他的努力失败了,他意识到法律的本源并不是一种绝对精神,而是为了保护某一个阶级的阶级利益,更重要的是,他认识到当时的法律保护的不是人权,而根本上是资产阶级的私有财产。这是他从黑格尔学派转向辨证批判的一个开端,也很可能是“经济基础决定上层建筑”这一说法在思想上的一个开端。那时候的马克思应该和我们年岁相仿,二十三四岁的样子,对法律却有如此深刻的洞悉,我认为他不仅仅是“千古第一思想家”,他更是一位少年天才! 当然,这件事情的后果是《莱茵报》最终被查封,他和同伴们也去监狱反省人生,不久,他也将和刚成婚不久地妻子燕妮将前往巴黎,也会认识他一生的挚友恩格斯。 电影对马克思和燕妮的感情以及恩格斯和玛丽的感情都表达得也很到位,燕妮和玛丽在影片里不是一个很平很脸谱化的配角,而是两个活生生的女主。 燕妮生于贵族家庭,她受够了所在阶级的腐朽生活,抵抗住了来自各方的压力和马克思相爱,结婚。在当时贵族阶级还在享受生活的时候,她已经有如此感悟,可见她的思想境界一定非同寻常女性所能及。马克思也说,她一直是他思想灵感的重要来源。 在她和马克思刚结婚的时候,他们在家中打闹、斗嘴,到最后接吻、相拥而眠时候的场景让我印象深刻。说实话,以前我也从来没有想过(哪怕是一丝)他们俩之间的生活究竟是什么样的,但这一场景让我彻底明白,他们这对模范夫妇和一般小情侣的日常生活也没有太大的区别,让他们的爱情如此深刻的原因在于他们共同的理想和克服困难的斗志——随后,我便见证了这些。 第一次是她开玩笑给马恩合著的书取名叫《对批判的批判所做的批判》,后来这本书被命名为《神圣家族》,而副标题正是燕妮所起的《对批判的批判所做的批判》。可见她也是很有哲学思想,甚至是能和马克思探讨很多东西的。第二次是她几次在马克思被驱逐或者拿不到稿费饥寒交迫的时候毫无怨言,甚至在如此艰苦的条件下还成为了好几个孩子的母亲。如果没有满腔的革命热情和对马克思深沉的爱,她怎么能够如此地坚强。不过马克思也并非撇下孩子老婆不管的人,他也以同样的热情为这个家得以维继四处奔波,甚至答应了放弃写作并且低声下气做任何工作(然而他还是没有成功找到一份工作)。这些场景也让马克思的形象更加饱满,他也是和我们一样,为家庭为生计烦扰奔波的普通人。 玛丽也同样为恩格斯地事业做出了不可磨灭的贡献。恩格斯是一个“富二代”公子哥,但他却不想继承他父亲的衣钵,而是非常同情英国工人的状况,希望可以帮他们说话。所以他前往英国大大小小的工厂进行社会调查,但是由于他身份特殊,许多工人并不待见他。而玛丽帮助恩格斯联系了许多工厂中的工人,才使得他完成了《英国工人阶级状况》这一调查报告。后来她又促成了马恩同正义者同盟的领导者的见面,间接推动了《共产党宣言》的创作与发表。 马克思和恩格斯的友情也是片中也被着重呈现。片中用欲扬先抑而且有一些无厘头感的镜头把他们从相识到熟悉的经历表现出来。 马克思先是因为恩格斯代表的资产阶级而看不上他,但是当他们聊起天来的时候,甚是忘我,马克思居然都忘记他们是要去讨稿费才见的面。再之后,他们一起逃离法国警察追捕又相遇的镜头像极了一部小型警匪片,就像他俩是认识了多年的老搭档一样,在一路东躲西藏之后,又在一个隐秘的转角重新相逢,然后像没事儿人一样一起去喝酒。 在开怀大醉之后,马克思说出了那句令我震动的话:“哲学家们都在解释世界,而问题在于改变世界!”两位前辈就从这句话出发,一步一步地对这个世界进行剖析,并且将他们的一生贡献给了世界无产阶级的解放事业中去。 在后面的相处中,马克思是一个性格如火一般富有斗争热情又很有思想的角色,而恩格斯则显得内敛许多,他俩在性格上非常互补,成为了非常合适的搭档。特别是在最后由恩格斯拿着马克思的手稿主导正义者同盟的“改名大会”的时候,恩格斯不断呼吁寻求更多支持,马克思则火药味十足,他们两人配合,终于让《共产党宣言》成为了共产主义者同盟的行动纲领。 影片最后,马恩两家在海边度假,马克思说因为琐事太多,而且经济困难,所以没法写一部大部头的书,而且生活太累的时候,恩格斯说,欧洲的革命已经开始,资产阶级专制统治已经非常脆弱,各地的工人运动已经风起云涌,工人阶级将会自醒,解放事业可能很快就会成功。 这让我从电影中跳出来,想到了之后发生的事情,令人唏嘘不已——直到马克思死后,世界上第一个社会主义国家苏联才建立,而直到两百年后的今天,世界仍处于资本主义的笼罩之下,而现在的资本也披上了金融这层看似温和的外衣。 片中没有花很大笔墨介绍马克思同蒲鲁东的论战,而用很多镜头表现了他与魏特林的冲突。 魏特林也是当时工人阶级的代表者,主张是“人人皆兄弟”,并且企图用这个口号和富有激情的演讲来感动所有人从而实现革命。但是魏特林的思想却还非常不成熟,他没有意识到冲突的根本是阶级斗争,所以非常反对马克思这种在他看来很形而上的“纲领”,而他所讲的仁慈博爱这种对工人运动毫无意义的词汇也让马克思反感。终于有一次会议上马克思爆发,正式和魏特林决裂。 之后,在正义者同盟那位有智慧的老者的支持下,同盟最终站在了马克思这边,并同意马恩一起起草一份纲领性的宣言——《共产党宣言》。 很多人应该都记得魏特林反驳马克思的这句名言:“批判会吞噬一切存在,当没有其他东西可吞噬的时候,它就只能吞噬它自己。”他显然认为这里的批判就是黑格尔学派所说的批判,因为但是黑格尔学派的一些人主张对一些哲学家的批判进行再次的批判,所以他认为当他们把旧的东西批判干净的时候,只能再次批判批判过旧的事物的批判了。不过他没有理解马克思所认识到的批判是什么意思。他如果认真读了当时马恩所写的《神圣家族》以及《德意志意识形态》,就会明白当时在他们面前说的这句话是很幼稚的。 回想电影中的马克思,他对事业执着,对工作勤劳、对妻子挚爱,对家庭有责任心,对朋友真诚。他作为一个学者,把人性的真善美演绎得淋漓尽致。片中还没讲,他为了写成《资本论》,在大英图书馆中一坐就是几十年。 再看看我们当下的学者,他们中有很大一部分人做事情的并不是潜心的研究,也并没有什么对未来的理想,而当下这个时代,他们最喜欢做的事情就是疯狂地“捞钱”。更令人不齿的是,最近全国接二连三出现多起类似导师让学生叫“爸爸”,导师性侵女学生的案件,这些都无不令人唏嘘。 我认为马克思不仅为我们留下了几厚本文字作为遗产,他的勤勉好学,他的敢爱敢恨,他的崇高理想,也需要当下青年学生、学者们多去反思和学习。 更可怕的一件事情是马克思被符号化。从我们小学开始,就一直在听这两个大胡子老头的事迹,知道他们是好友,知道他们的思想非常厉害,对我们现在的社会影响深远。却又觉得他们十分遥远,不知他们究竟是什么样的人,更不知道他们的思想究竟是何物。 伴随着许多人对现实的不满,又受到西方意识形态的强烈灌输,以及他们从中学课本的只言片语出发所产生的对整个社会的片面的思考,许多人开始批评这两位老人,厌恶这两位老人,甚至诋毁这两位老人。看完电影以后我翻了翻豆瓣的影评,充满戾气。 除去头脑中的偏见,要从感性的认识开始。我想,这些人更应该去看看这部电影,去了解他们的生活,他们的思想,认识一个不再符号化的马恩。所以从这个角度来看,电影之所以没有提及那么多的马克思的思想,也是因为它为大众所准备的,可以让观众从能理解的角度认识这个活生生的马克思。 我想,看到电影中 18 世纪英国工人们被榨取剩余价值的惨烈场景,看到资产阶级警察们对无产阶级的草菅人命,不会有人还认为这个斗争是毫无价值的吧?如果没有工人阶级的运动,没有那么多人流血牺牲,现在的欧洲还会是当时的那个欧洲。不要再说什么现在生产力强大了,资本家就不会压迫工人了。影片中一位工厂主的话告诉我们:如果你不雇佣童工,如果不压迫工人,会有其他人为了获取更多的利益这么做,那么社会必要劳动时间会减少,产品的价值会下降,如果你不去这么做,你就会面临成本的上升,你就会破产。工人生产的产品越多,生产越快,那么他自己的价值却越低。别说这个规律只在那个时候起作用,在现在这个时代,对于各行各业,也都是完全一样的。 现在我们身在“高大上”的互联网公司,拿着看起来挺高的薪水,但是人人不还是在没日没夜每周末像机器一样的 996 中度过,你所创造的价值,到底又有多少归你所有了呢? 当下依旧是技术与资本的时代,只是资本披上了另一层温和一些的外衣。所以并不是现在马克思思想已经过时了,马克思主义仍是当下的一个幽灵,始终盘旋在我们这个资本主义世界上空。也正是因为有了这一柄达摩克里斯之剑,才使得现在的资本主义变得温和,让我们无产者在被资本支配的同时,也可以享受到资本所带来的生产力的提升而产生的富足而美好的物质生活。 所以,电影中的这两位主角,的的确确是如此的伟大。 另一个角度去向,当资本发展到今天,以如此这般更加猖獗更加繁荣的面孔出现,这不更是一个从未有过的最好的研究和发展马克思主义思想的历史时期么? 另外还想做两点科普。 第一,马克思不是经济学家。《资本论》的副标题是《政治经济学批判》,他是在用以辩证法为核心的新的叫做批判的科学来对经济学进行批判。所以虽然马克思在经济学领域有很深的造诣,(也正因为他如此了解经济学)他却做了对经济学(确切说也不能是经济学,而是对当下现实)最的深刻批判。这门新的科学也可以运用在其他的领域,只是马克思去世太早,还没有精力把这把利剑插向别的范畴。 第二,马克思所谓的共产主义,也并不是所有的东西都是公有制,不允许个人拥有一分一毫的个人物品。这里的共产指的是扬弃经济学中的资本,让所有物不再以私有财产的形式出现,而需要公有的是人们从事生产活动所需要的生产资料。许多人甚至认为共产主义连婚姻都要”共享“,这是多大的谬误。 PS:拖延了一周终于按照之前所列的大纲基本写完了。对于马克思思想,我也只知皮毛,有很多想不清楚,没弄懂的地方。所以如果大家发现文中存在谬误,希望可以批评指正。
在前端调试的时候,跨域一直都是一个比较麻烦的问题,这个在之前的文章关于跨域问题的一个解决方法中其实已经讨论了一些可以使用的方法。 如果要使用 JSONP,第一是需要修改的地方比较多,而且也不太符合前端发展的大趋势,如果使用 CORS 的话并没有 application/json 类型。而且更重要的是这只是在前端调试时候的需求,并不是在上线以后的需求,所以对后端有太多的入侵也不好。 所以就有一个念想突然在大脑中闪过——加入有一个代理不就可以解决这个问题了?但是又想了一下写起来还挺麻烦,于是就被搁置了。 直到前几天 Stone 提到其实 webpack-dev-server 早就想到并且已经帮我们实现了。 于是,我就在一个 Vue 项目中进行测试,发现真的很赞,既可以本地 Server 热加载,还可以直接跨域调用远程 API,完美解决了之前遇到的所有问题。 接下来我简要介绍一下步骤(以一个 Vue 脚手架建立的 webpack 项目为例):首先检查build/webpack.dev.conf.js中是否有 1 proxy: config.dev.proxyTable, 这个配置项,如果被注释掉,请打开注释,如果没有,请加入到 devServer 对象中 然后在 config/index.js 中的 dev 对象中加入 proxyTable 配置项: 1 2 3 4 5 6 7 proxyTable: { '/**': { target: 'http://api.xxx.com', changeOrigin: true, secure: false } }, 前面的键 /** 意思是代理所有请求,如果代理某些请求,可以将其改为诸如 /api 之类的字符串。 后面的 target 就是要代理到的网站,changeOrigin 的意思就是把 http 请求中的 Origin 字段进行变换,在浏览器接收到后端回复的时候,浏览器会以为这是本地请求,而在后端那边会以为是在站内的调用。 这样,通过这个简单的配置,就完美地解决了跨域的问题。 之后,在直接运行 1 npm run dev 的时候,就可以将测试前端中的 ajax 请求代理到后端服务器进行测试啦! 最后,贴上官方文档,具体的配置大家可以参考这里: https://webpack.js.org/configuration/dev-server/#devserver-proxy
偶然读到王德峰老师的一本已经绝版的小册子,叫《寻觅意义》。心想这个题目起的甚是有趣——其一我在想他会怎么写,结果读下去发现是他在各个大学讲座的讲稿的一个小集;其二说来也很巧,我最近也一直在反思意义究竟是什么,我做什么才有意义,我追求的意义是什么,有没有属于这个时代的意义,而真正的大义又是何物呢? 关于时代的意义,和妈妈聊天的时候,她关于她的意义是什么给了我如下的解答:“除了想让你过的开心快乐,剩下的意义就是多赚钱了,钱赚得多,我就感觉很踏实。”我想,前半句是家庭,是我和母亲很真挚的亲情;后半句是时代,是我和母亲以及所有存活于现代社会的人共同处于的环境。 我们所处的是这样一个被资本与技术所主导的时代。所以我理解大家的理想多是要去赚钱。不论男女老少,不论贫穷富贵,每个人的欲望也总和钱脱不开关系。而钱这个东西,究其根本,不过是一个几乎是全人类共同参与的一个游戏,同我们的能生存与否没有什么直接的依赖关系,只是在这个时代里,我们需要用钱去获得各种生存资源和对他人支配的权力。 所以与其说我们想去赚钱,不如说是这个时代规定了我们想要的是去赚钱。 然而,赚到再多的钱又有什么意义呢?大家拼命多赚钱,最后又能得到什么呢?许多富人甚至比穷人还要忙还要苦恼,他们赚了这么多钱,但是赚到了意义么? 是,赚到钱越多,我们可以获得的社会资源也就越多,可但是钱作为一个可以被数的数字,一个人究其一生,能获得的量总还是有限的。但是人心呢? 一个人可以在史书中穿越到几百年之前,可以在对未来的幻想中穿越到几千年后,甚至可以在对宇宙思考中时间横亘数百亿年。人不论活在哪个时间点,总可以在头脑中对过去和未来作出无限的延拓。所谓人心,是无限的。 倘若有钱的金钱来对无限的生命说:我就是你的意义!这句话还会成立么? 既然金钱的意义是仅对于我们这个时代的,而且对于无限生命来说,它也太微不足道了些,那意义,能够称得起对于生命有意义的东西,究竟是什么呢? 在很长的一段时间里,我曾认为它是宇宙的终极真理。就是随着以物理学为首的近代科学的不断深入探究,我们总能得到一个越来越接近宇宙真相的真理。 但是随着这条线仔细深入想下去,结果却令我失望。就举一个物理学的例子:在这一秒钟苹果从树上掉到了地上,牛顿说它是受到了重力的作用,好,那么请证明,在下一秒钟,还是会有苹果会受到重力落到地上。对于休谟的这个诘难,近代科学还无法解答。我们能说的只是:根据这么多年的人类观察历史,所有的事实皆是如此,所以我相信它会继续下落。 所以从根源上来讲,我们所坚信的自然科学,都是经验的产物。经验总有被打破的时候,那么我们通过经验所找到的这些自然科学的定理,是真的真理么? 我们所掌握的这些科学,倒是为我们控制其它事物提供了许许多多行之有效的方法,可以满足人类许许多多的需求,给资本帮了一个大忙。所以,科学主义,是西方近代理性形而上学的延伸,是笼罩在当代的一种意识形态,它的真理,是基于经验而派生出来的。 唉,通过科学的方法来寻找宇宙终极真理的路,在这里就完全被堵住了。 既然已经在我们外部探究了这么多,寻找意义却依然没有结果,如果转向我们自身,我们内部,又会怎样呢? 我不知道我成长了这二十多岁,已经被这个时代这个世界改变了多少,还依然保留了多少被许多前辈们所诟病的不成熟的“童心”呢? 我想,爱情算是其一,也需要被放在首位。 在爱情里面我相信命运,另一半不需要用什么物质上的可列举条件标准来衡量,需要做的是去等待。对的人可能就是上辈子所注定的,互相相处一段时间,甚至是交换一个眼神,那种爱情的感觉就来了,是一种无法欺骗内心的感觉,就可以确定——就是她! 而缘分呢,感觉就很像佛教中所讲的业。两个人上一世造的业(当然是善业),就像一颗种子一样,不断成长发芽,在这一世,你们就回彼此相遇,由你们共同来完成这一世的存在。当然,你也要在这一世需要珍惜且付诸行动,才能让前世所积攒的被白白消耗。 另外,在爱情中我也像“傻了一般”,并不会理性地计算得失。因为计算得失是商人相处时要去做的事情,跟恋人去计算,我的内心会有极大的阻力和抵触。 在爱情中,我可以感受到一种真实感,一种真真实实存在的,爱与被爱的感受,而非虚无的外物与金钱所能比拟的。 另一个是随着思考理解的深入才认识到的,就是艺术。 最开始醍醐灌顶般明白过来是因为看到了海德格尔的一句话:“艺术是真理的原始发生”。我就在想,为什么是艺术,而不是科学,成为了那个最朴素的真理? 相似的话尼采也说过:“人在事物中除了重新发现自己的入藏品而外再不会重新发现任何东西——这种再发现,自称科学。入藏品包括艺术、宗教、爱、自豪。”他也提到了科学的派生性,而真理来源于艺术、宗教与爱。 如王老师在小册子里所说的,艺术其实就是巫术。 古代人对于巫术,并不是在举行完仪式以后,就什么都不去做了,等着上天的恩赐,而是在仪式完成以后,大家要一起去完成一个凶险困难或对生死存亡意义重大的事情,比如大型狩猎、建造、以及秋收。通过巫术,让集体中的每个人凝结在一起,也给了人类以未来。 为什么会这样?我认为这是由于我们每个人可以理解超验的超自然的事物的存在(比如艺术中的感情、宗教中的神明,甚至是一个国家、一个政党,人们的品格、信念、爱情,更甚至是某个虚构的人格,比如超人,或者是。。。王尼玛)。 而从古至今的诗歌、音乐以及画作等等艺术,都是有此作用。这些艺术作品将人类对某种事物的情感封存在其中,当人们再看到这件作品的时候就可以激发出他们对于情感的共鸣和他们对超验世界共同的认识与体会。 是雕塑艺术让人们真正理解到了石料的存在,否则它们就是一堆用作建筑的物质,是音乐让人们真正理解到了声音的存在,否则它只是一种可以用来传递信息的波(虽然我们每天都通过声音收发了许多信息,却只有音乐能让我们听到真正的声音,这种对于人的感性的存在),绘画也是如此,让我们看到了光线真正的存在,否则它也只是一堆电磁波(然而电磁波也是我们描述它的一个方式,它真正的存在是什么呢,或许只有在绘画中才能真正表现出来)。 让人领悟到物质的感性存在或许就是艺术的伟大之处。 成年人向外部索取太多,而对内部听到的却越来越少,这大概就是孟子所说的“失其本心”吧。在这个只认金钱,意义丢失的时代,我想,不能害怕在时代的荒原中和多数人走上了不同的方向,要回到自己的内心,去真心体验人最真挚的情感,寻找绿洲的存在。说不定,就找到了那一片属于自己的绿洲了呢?
深度写作 最近看见沈向洋发表了一篇文章(地址:https://zhuanlan.zhihu.com/p/33771188),大意是说在这个 AI 的时代,虽然每天我们产生和接受的的零碎的信息量都非常巨大,但真正有意义的思考还是需要通过长篇写作来完成。写作可以帮我们理清思绪,可以清晰地表达观点和逻辑。 这也是我最近所担心的事情——我也已经很久没有进行过深度的写作了。 上次系统地写作还是中外文学名著鉴赏的期末作业,文章虽然可以大致表达我想说的表面意思,但是短短八百字的表达却支离破碎,根本无法把我更深成次的想法完整表述出来。在刚写完的时候没感觉到还沾沾自喜,觉得一下午写出来的文章应该还能看,之后读起来却就像是读一篇小学生记的流水账一般,像是喝了一大杯白开水还泛着水垢一般,毫无深度,甚至还难以读完。 感触更深的一次是上个月我想抽空写一下去年的年终总结,但刚提笔就不知道该从何说起,所以又去翻前几年的文章,发现当时的文风根本现在几乎无法模仿。我十分懊恼,故总结也一直搁置下去。 直到今天,我才有勇气重新打开编辑器开始写这篇文章。今天没有给文章定下主题,想到哪里就写哪里,但愿思路会更顺一些吧。 活着 正如认识存在需要通过感受虚无一样。认知活着也需要感受死亡。 这个假期的前半段就是虚无与死亡。虽然之前在各种文艺作品中已经见过了无数种死亡的情景,但自己有如此深的感受到却还是第一次——这次死亡的对象不再是文学作品中或者和自己关系不大的某个人,而是我打小就十分亲近的奶奶。 思绪回到寒假之前,刚听闻奶奶住院的消息时,虽然奶奶声音听起来依然慈祥有力,但了解病情后,我十分担忧,感觉就像是一块石头压在心头,怎么都喘不过气,每天晚上能见到的只有噩梦。果不其然,第二次我再打电话过去的时候,奶奶的声音已经有些孱弱,听起来也很是疲惫。 第二天一大早,姑姑给我发的信息只有几个字:能提前回来就提前回来。于是当天就提前请假、买高铁票、交接任务、安排事情、准备回家。我记得当天中午和卤蛋同学一起吃饭,有好几次我都集中不了注意力,大脑里的各种思绪像幻灯片一样飞快地闪过,却怎么也提炼不出头绪来。 回家后就径直到了奶奶住的医院,趁着奶奶比较清醒的时候,同奶奶讲了几句话,还给她看了前两天和卤蛋一起专门给她拍的合影,看得出奶奶当时还挺开心。现在我很感激当时的我所做的这个决定,这几句话大概是和奶奶最后一段完整的对话了。 之后几日,奶奶的身体情况一日不如一日,死神在不断地靠近这个历经近八十年岁月洗礼为家庭为儿孙操劳无数的坚强的女人。我知道,任何手段都无法阻止死神来临的脚步,能做的就是默默等待,当你一不留神,他就会悄无声息地到来。经过几天痛苦的思考,我想,我已经足够坚强,可以接受这个事实。另外,看着奶奶在病床上受苦的样子,有时候我也竟盼着死神早些光顾,她这辈子已经受了太多的苦,能让她早日解脱也未尝不是好事。 2 月 6 日晚上,奶奶情况突然恶化。奶奶弥留之际,爷爷放下多少年来和奶奶的吵吵闹闹,开始和奶奶讲心里话。虽然没有一个“爱”字,但句句都是直戳心灵的情话。这时我才知道,在无数的吵吵闹闹的背后,他们老两口之间更多的是无法用言语表达的爱和包容,才能一起走过这近六十年的风风雨雨。我当时就在想:这老爷子,干嘛就那么犟那么固执,非要到了这个时候才把这些心里话说出来呢,这可是我听过的世界上最动听的情话!可惜不知那时的奶奶究竟还能不能听到。 终于,在 2017 年 2 月 7 日凌晨 3 点 41 分,死神终于悄悄出现在我们身边,带走了这个在世上受尽苦难但又善良而伟大的灵魂。奶奶生前是基督徒,按照教义,她已经为世人受了太多罪过,我相信她的灵魂一定会上天堂,和主耶稣永永远远生活在一起,再也没有烦扰,再也没有痛苦。 此后几日,为奶奶守灵。爷爷奶奶的许多朋友都自行前来吊唁。跟他们聊起来,听他们讲起当年的故事,我才体会到能和爷爷奶奶做朋友的人,也都有着属于他们那个年代的诚实、正直、热心和血气方刚。说起他们的很多事迹,许多自诩深谙社会之道的人,甚至是现在刚成年的许多零零后们,都一定无法理解。这些爷爷奶奶,一生归来后,也仍是少年。 葬礼后,我像是突然醒悟似的,意识到奶奶的的确确,永远离开了我们。回家后,我有时候还是幻想她还在,还在厨房里忙忙碌碌准备我最爱吃的饭菜,还在用她歪歪扭扭新学到的字抄写歌词,还在那台跟她一起度过了几十年的缝纫机上缝缝补补。直到今天,在超市见到一个盒子的时候,我还是会下意识地说道:买上这个可以给奶奶盛放阵线用。接着才想到,她现在在天国,应该已经用不到针线盒了吧。 最后,借用《无问西东》中的一句话:逝者已矣,生者如斯。 在以后的日子里,应该收起悲伤,努力坚强地活下去,要更加好好对待自己所爱之人,如果爱她,就一定要大声地告诉她。这一定是奶奶也天堂所希望看见的。 情人节 卤蛋同学,今天是我们一起度过的第一个情人节。很巧,也是我们在一起的第 214 天。 和你在一起的这大半年里,每每想到你,我的嘴角都会露出微笑,每每拥抱你,我的内心都感到无比幸福。和你在一起,总有说不完的话,谈不完的心,也有逛不完的商场和公园。 你我从相识到相知再到相爱,每一步都像是上帝安排好的,十分自然地铺在我们面前,但又留下了许许多多值得我们回味的故事。 卤蛋,我爱你,就像爱生命。
Windows 桌面程序开发一些方案 开发 Windows GUI 程序的方案有很多,接触过比较流行的大概有三种,一种是 C++和 Qt,一种是 HTML5+浏览器内核,最后一种是 C#+WPF。另外古老的 WinForms 和更古老的 MFC 也不多说了。 Qt 的跨平台特性得以开发的项目可以跨平台,而且各种 C++的组件非常丰富。但是但是 Qt 本身库的并不小,我也不是很喜欢 QML 那种 JSON 的书写方式,而且 Qt Creator 用起来也不太顺手,所以一般没怎么用过这种开发模式进行桌面应用的开发。 H5 和浏览器内核是一个不错的方式,可以轻松跨平台,而且 H5+JS 可以有很快的开发速度。主流的方案有 Electron、nwjs、cef 和 wke(其中弹幕派所用的方案就是 wke),但是 Electron 同样体积巨大,不利于应用的分发。wke 虽小但也很久没有更新,内核很老,bug 比较多。最近志鹏同学正在研究之前 wke 开发者新开发的 miniblink 内核,相信这个方案会比较优秀。 WPF 必须依赖于.NetFramework,所以无法跨平台,而且 XP 也不自带.Net,需要用户安装。另外,XAML 虽然写起来麻烦一些,但是开发漂亮的 GUI 还是比较方便。而最新的 UWP 技术也利用了 WPF 的 XAML 进行 UI 的设计,转型起来并不困难。 起因 上周受到卤蛋同学的启发,如果需要使用 C++开发 GUI 程序难道只能用 Qt(或者北邮老师专用的 ege)?能不能使用熟悉的就技术来开发呢?于是在 Windows Dev Center 里找到了使用 C++和 XAML 开发 UWP 程序的方式。粗略看了一下,大概是使用经过微软扩展的 C++,名字叫 c++/cx,基于 Windows RT,但是不受.Net 的托管,也就是要自己处理垃圾回收(这方面理解不够深入,感觉大概是这个意思吧)。 但不管如何,可以使用 C++和 XAML 开发 Windows 应用还是让人眼前一亮,忍不住去尝试一下。所以,下面开工吧! 环境配置 开发环境必须使用 Visual Studio 2017,在新建项目中选择 Visua C++语言的 Windows 通用选项,建立一个空白应用即可。如果没有下载 SDK,上方会提示下载,如果没有显示该选项,说明需要在 VS 安装程序中安装一下 VS 对 C++ UWP 的支持。 在几秒种后,Visual Studio 就会打开一个新建的应用。假设你对 WPF 很熟悉,你可以看见熟悉的 XAML 设计器,在左侧的解决方案管理器中,也可以看见 C++的文件以代码隐藏文件的形式被嵌在 xaml 文件之后,包含一个.h 文件和一个.cpp 文件。整个工程的组织形式和 WPF 极其相似,这足以让人很激动了。 开始 Coding 进一步熟悉一下项目组织形式,可以发现,除了 C++的语法和 C#有区别,开发思路和 WPF 中的 C#是基本一致的,于是对应卤蛋的大作业题目,我建立了一些类的头文件和相关实现,需要注意的是字符串的处理问题,如果要支持中文需要使用 wstring(其实这个问题也并不仅仅是这种时候才会遇到),但关键在于 XAML 中的字符串使用了另外一种并不是 C++标准库中的字符串类型 Platform::String^,所以需要一些函数来在这些字符串中进行转换。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Platform::String^ Runtime::stops(std::string s) { return ref new Platform::String(stows(s).c_str()); } std::wstring Runtime::stows(std::string s) { std::wstring ws; ws.assign(s.begin(), s.end()); return ws; } std::string Runtime::pstos(Platform::String^ ps) { return wstos(std::wstring(ps->Data())); } std::string Runtime::wstos(std::wstring ws) { std::string s; s.assign(ws.begin(), ws.end()); return s; } std::wstring Runtime::pstows(Platform::String^ ps) { return std::wstring(ps->Data()); } Platform::String^ Runtime::wstops(std::wstring ws) { return ref new Platform::String(ws.c_str()); } 具体转换关系可以参考下图,图片来自http://www.cnblogs.com/nio-nio/p/3511843.html 在完成对底层数据对象的抽象以后,就可以着手设计界面和应用逻辑了。UI 的设计因为使用了 XAML,所以非常简单,所有的 XAML 特性都可以使用。而 UI 的事件绑定也和 C#非常类似,如果让 Vs 自动生成事件响应程序的话,它会在代码隐藏文件的头文件和实现文件中分别生成函数的声明和函数体,然后就可以直接在函数中写代码来控制它了。 简单的尝试 页面导航 首先说页面的导航,这里和 UWP 的概念非常吻合,只需要一行代码就可以实现。比如我现在需要导航到 UserPage.xaml,我只需要: 1 Frame->Navigate(UserPage::typeid); 就可以完成,有一点需要提醒的就是必须在.h 文件中引入 UserPage.xaml.h 才可以这样调用。 获取事件触发者 获取事件触发者也很容易,和 C#中思路相当,事件委托的 Handler 中触发者和参数会被以 sender 和 e 的形式传入,只需要做一些类型转换就可以获得到,下面是一个例子: 1 2 3 4 5 6 7 void App1::QuestionListPage::OnPointerPressed(Platform::Object ^sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^e) { StackPanel^ s = safe_cast<StackPanel^>(sender); int questionId =_wtoi(QA::Runtime::pstows( s->Name).c_str()); QA::Runtime::currentQuestion = QA::Runtime::questionList[questionId]; Frame->Navigate(QuestionPage::typeid); } 生成 XAML 元素 不多说,直接贴上代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 QuestionListPage::QuestionListPage() { InitializeComponent(); vector<QA::Question*>::iterator iter; for (iter = QA::Runtime::questionList.begin(); iter != QA::Runtime::questionList.end(); iter++) { auto questionStack = ref new StackPanel(); QA::Question *question = *iter; auto info = question->getInfo(); auto questionTitle = ref new TextBlock(); questionTitle->Text = L"问题:" + QA::Runtime::wstops(info[0]); questionTitle->FontSize = +30; auto userName = ref new TextBlock(); userName->Text = L"用户:" + QA::Runtime::wstops(QA::Runtime::currentUser->getUserName()); questionStack->Children->Append(questionTitle); questionStack->Children->Append(userName); questionStack->Name = question->getQuestionId().ToString(); questionStack->PointerPressed += ref new Windows::UI::Xaml::Input::PointerEventHandler(this, &App1::QuestionListPage::OnPointerPressed); questionList->Items->Append(questionStack); QA::Runtime::currentQuestion = question; } } 以上代码在界面初始化以后动态加载了一个问题列表,其中包含了 Title 和 UserName,并且给 StackPanel 绑定了一个叫做 OnPointerPressed 的事件。(就是之前获取触发者中的代码) 一切都非常简单,不得不说微软已经把 c++/cx 改造得很像 C#了,甚至连委托都被加入了进去。 全局变量 最后想说说的就是应用中全局变量的实现。 本程序采用了静态类的方式实现了全局变量和一些基础方法的封装(比如各种字符串转换函数)。只需要把需要全局的变量设置成 static,并且赋予初始值,在需要的时候调用即可。以下是代码中全局的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //classes.h class Runtime { public: static int userNum; static int questionNum; static int answerNum; static User *currentUser; static Question *currentQuestion; static vector<User*> userList; static vector<Question*> questionList; }; //classes.cpp int Runtime::userNum = 0; int Runtime::questionNum = 0; int Runtime::answerNum = 0; Question *Runtime::currentQuestion = nullptr; User *Runtime::currentUser = nullptr; vector<User*> Runtime::userList = vector<User*>(); vector<Question_> Runtime::questionList = vector<Question_>(); 对于全局方法,用法一样,不再赘述。 可用的资源 官方介绍:https://docs.microsoft.com/en-us/windows/uwp/get-started/create-a-basic-windows-10-app-in-cpp(目前也只找到了这一篇) C++ WinRT 介绍:https://msdn.microsoft.com/zh-cn/magazine/mt745094 官方 GitHub 仓库:https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples(每个例子的 C++文件夹中都是一些示例,文档有限,这些例子非常珍贵) 后记 说应用跑不起来是假的,自己写的应用,含着泪都要让它跑起来 不过仔细思考一下,如果不是必须要用 C++,这种开发方案作用并不很大。首先资料太少,连 MSDN 上面都只能搜到 API,几乎没有任何示例,而社区中也几乎一丁点都见不到对这种方案的讨论,所以如果遇到坑基本只能自己靠经验解决(但如果太大呢),比如研究事件委托怎么写我就研究了半个晚上,没有 API 也没有文档。另外这种方案开发出的应用也只能跑在 Windows10 中,对于老版本的 Windows 和其他系统(感觉 Xarmain 也不会支持这种模式),所以应用范围也很受限。
时日已至伏末,先别问秋老虎可不可怕,仅是鼻炎已经足以让我怀疑有一个连的人在想我。 话归正题,兰州在我心中的形象由两部分,一半像兰州烟上(我最喜欢吉祥兰州硬盒上的那个红色镶金的图案)那样:悠悠兰州,九天揽秀,另一半是爽朗直接,像兰州公交车司机一样,可以跳下车跟出租车对骂。总体来讲,用人来形容就是一个很飘渺但又古朴纯净的三四十岁胡子邋遢瘦削的硬汉。总之,矛盾满满。不过这一点不想展开去说,说说最近在兰州的见闻吧。 按照惯例,每次回家都要去张掖路逛逛,每次去逛也都盼着能有点什么不一样,但又担心不一样的地方多了我会忘记之前是什么样子。不过到目前为止,我的担心都是多余的。对我来讲,上半年改变最大的大概就是共享单车了。张掖路也是如此,但不是漫山遍野的自行车。为了不让单车进入,步行街的路障间隙更小了——将将够钻进去一条腿——进去以后擦一把汗,开始感慨前些日子的减肥真他丫有效,不然老夫就要卡死在这里了。 为什么念念不忘的是张掖路?除了之前总在这里买些衣服鞋子,让我逐渐摸清哪家店的沙发睡觉舒服以外,就是人多。我喜欢这里,总能让我沾沾人气儿,但又不必拘束自己。我喜欢看这里的人。 有吹着口哨吊儿郎当的“小社会”,有大包小包打招呼都腾不出手的时尚 girl,有大胆往前走绝不向两边看的外卖小哥,也有讲着今儿鸡蛋多少钱亚欧超市是不是减价的大妈大爷,更多的是手扣着手面色红润的小情侣们。手里拎本儿 Kindle,眼睛像磕睡狗一样眯起来望着这条街的,可能就只我一个了。 看着,我突然发现一个大问题——每个人,他们,居然,都,在讲话!成群结队成双成对的人在互相讲话,独自一个的人要么在讲电话,要么不知嘴里在嘟囔着什么。“怎么会这样”我自言自语。 为什么人人都会说话,为什么人人都在说话,人人都在说什么话?想回答这几个问题,着实有些难度,也很难在一篇小记中去讲。但培根问我的一个问题很有意思:“很多科学实验证明,猩猩也有语言,那么它跟人有什么不一样呢?它们有意识么?”这个问题就更有意思了。我认为解答这个问题的关键大概在于大猩猩有没有形成判断动词——是,也就是英文中的 be 动词。如果没有,那么它们还不能领会“存在”,只能通过语言表达诸存在者。在这个角度看,它们没有存在意义上的意识。再来看人类,就很有意思了。他们不仅仅可以领会经验上的存在物,更能领会超验的东西。比如——金钱、国家、社会、甚至是爱情。这些存在者的存在,没有意识,是无法领会的。 话又说回到爱情。之前看过一个理论,如果情侣之前无话可说,就很难有幸福感可言。说明充分的交流沟通是人与人之间构成伴侣的一个非常重要的条件。回过神看见商圈门前的一对对们,虽然高矮胖瘦形态各异,甚至肤色都有所不同。但都可以相爱——他们的十指都紧扣在一起,开心地讲着什么事情。 说的也巧,最近从臻那边听到了两个真实的故事。 一个是臻的父母。 他老爸最开始不会做饭,直到他妈妈怀了他,他爸就磕磕绊绊在他妈妈的指导下学习做饭,每次他妈妈晚上饿了,他爸都二话不说去做夜宵,再后来,就是他爸一直做饭了。 他爸妈也不怎么吵架,就算吵得最厉害,也不超过半个小时,他爸就问他妈要不要泡个茶歇一会儿,他妈妈也欣然同意。他有一次问起他爸妈,“问题还没解决为啥就不吵了?”他妈妈说:“其实吵架的时候就已经想好要让一些步了,反正这么多年都是这样。”他爸爸说:“和你妈一吵架,我就心疼了,她肯定也知道我的想法了,所以我还是老老实实服软吧。” 另一个是臻在旅途中还遇到的一对金婚夫妇,在他们家中借宿了一天,听到了许多他们过往的故事。 夫妻俩都很大年纪了,奶奶叫那个阿公“酷哥”,阿公叫那个奶奶“宝贝”。那天阿公晚上要出去一趟,两个人还要拥抱之后,吻一下额头,才肯分开。奶奶趁着爷爷出去,偷偷告诉臻,她这一辈子最开心的事情,就是把当年那个连一句想你都不会说的闷葫芦,变成了现在只要离开一会儿,就关切的不行,还要用爱你结尾的老小孩~ 听完这些故事,当时的心情已是惘然。 现在回想,能拥有这样的爱情的人,该是有多可爱呀。一辈子不长,撞上好运气,能享受大富大贵的可能都比遇到这样一个人并且相处的如此之美的机会大太多。所以,要珍惜身边的人儿,你相信她,她相信你,两人都相信这样的生活,以后才可能就是如此了。臻,你讲,是不是这样? 自张掖路一游之后,我对黄河风情线念念不忘。所以决定沿黄河南岸分别来一次骑行和跑步。 一路上最有意思的事情莫过于穿行在沿途的广场舞们中间,伴着异域风情高难度的新疆舞(是的,兰州今年流行这个),她们根本不为所动,就像冲进了一个正在被扭曲中的魔方,此情此景用一个字来形容,就是群魔乱舞。 另外,跑步的时候我还答应了一件事:未来要和“吃货”(此处简写)一起去东营逛逛,看看共和国最年轻的那片土地。或许,那边会的是一个比我曾经想去的巴颜喀拉山更神奇的地方呢。不,两个地方都要去,一起去看看究竟哪里更是希望。此处 at 她。 在黄河北岸骑车,穿过兰州音乐厅和城市规划馆的时候,我忍不住驻足,去欣赏她们。很有意思,一个被霓虹灯过度装点而另一个是被黑夜过度吞噬。终于,兰州也不再是那片朦朦胧胧的工业灰,她变得清晰年轻性感起来。 最后,贴几张雨后兰州的照片作结束吧。
好电影是值得多看几遍的。 说实话,这几个月我都没有进电影院,更没有看过最近有争论所谓的这些国产烂片或者说是美国大片。对这部电影,虽然没有对比,但不能阻碍我认为它是一部好片。 虽然两次看的是同一部电影,但我每次的关注点一定是完全不同的。第一遍是随着影片的进程,不去思考过多,而是把自己融入电影之中,心情和大脑都去主动跟随剧情的发展,完全按照导演和编剧的思路欣赏。如果这是一部好片,那么心情一定会起起伏伏,倘若恰巧情节还比较紧凑,那么一场电影看下来必定是淋漓大汗。第二遍呢,我会选择从片中走出来,在看的时候去思考人物的性格、导演对情节的安排以及场内观众们的表现,看完后必定会有许多新的发现,有时候或许还能看出哪里是被引入的时候经过剪裁的,人物应该在哪里可以更活。 首刷的时候,我大概和其他观众一样,随着两个小姑娘的视角,内心跟随她们一起成长,一起对父亲肃然起敬,甚至国歌响的那一刹那,我也跟随她们一起感动。 二刷的时候,我注意到了几个有趣的细节。 第一个是父亲不顾村里人的笑话,不顾官员的嘲讽,更不顾录像厅老板的眼神,每一次出场的眼神都温和却坚定;但却因自己特殊照顾女儿却导致女儿差点被体育学校开出流泪,这是多坚强的泪水! 第二个是父亲不论是在女儿消极怠练还是她们跑去参加别人的婚礼惹他生气甚至是女儿在教练那边学到了新的技巧而趾高气昂地嫌弃他的办法老旧的时候,他都没有打过一次女儿(每次都是她们的哥哥背锅)。父亲虽然严厉,但绝不毒辣,他对女儿的爱不亚于天下任何的父亲。 第三个是我认为是一个片段可能被删减,大概是父亲应该花几句话的时间去解释一下他为了让女儿摔跤不只是为了自己的梦想,还是为了女儿摆脱印度社会对于女性的不平等,让他的女儿过得更好一些。虽然在婚礼后新娘和女孩们的对话中可以看出一些,但依旧感觉不够浓厚,毕竟从父亲口中讲出来可以让电影主题更加深化,情节也更合人物性格一些(更可以让许多认为父亲是在强加自己的意志在女儿身上的人闭嘴)。而这一点我认为在中国人的心里认同性还是挺高。感谢牛三岁提供的生动素材,比如父母让孩子学习钢琴之类的乐器,这些乐器很可能是父母年轻时候未竟的梦想,现在让孩子去学,目的不仅仅是为了让子女去为他们比赛获奖,这也是提高子女的修养,子女小时候不理解,但一定终身受益。 第四点是我惊叹到导演居然在最后一刻安排将父亲关在小房间里。这一点对吉塔的成长太重要了,如果父亲在场,吉塔能赢,但这不是她自己,而是有她父亲的一半,她的意志还是父亲用来实现理想的一个附属。但是父亲被关在小黑屋里那一刻,吉塔变成了她自己,她在荧幕中完完全全活了起来,她赢的那一刻,她是在实践去做她自己。所以我看到父亲被关进去的那一刹那(虽然已经知道情节),还是深深舒了一口气,为吉塔感到高兴。 最后一点,是细细品味后,我惊讶到这部电影一切都是恰到好处。首先,这个故事的背景几乎没有只有印度人可以看懂的印度文化,里面表现出印度专有文化的几个镜头基本不用理解就可以完全明白。再说到这个故事,本身是一个励志故事,大概就是一个被所有人看不起的穷小子成功逆袭的路线,把故事背景换到中国,换到日本,甚至换到美国,都可以找到类似的题材。第三呢,这个电影虽然反映了印度的男女不平等、童婚、甚至是官员不作为等等问题,但都是点到为止,没有一丝一毫的多余,在最后吉塔击败白人选手,印度国歌响彻电影院的那一刹那,这些批判什么都不算,这部电影的主题是爱国!但又不脑残。电影的节奏安排也非常合理,在情节最紧张的时候,观众们都屏息凝神,而几乎每一个笑点,可以让从几岁到几十岁的人都会心一笑。特别是到了吉塔最后比赛的时候,拍摄得更是精妙。我扫视一下电影院,发现虽然大家都知道最后肯定会赢,但是眼神里都是紧张二字,在每一个得分点出现的时候,我看见旁边的姑娘更是激动得和屏幕里赛场中的观众一起鼓掌(要不是这里是电影院的话可能就要欢呼出来了)。一部电影,能做到这一点太难得了。 所以,这部电影这么火,并不是偶然,它是一部每一个细节都用心雕琢的好电影。 今天也是母亲节,这部片让我想起来老妈这么多年来对我的培养。 她和片中的父亲不一样,没有严厉的要求,更从没有将她的意志加在我的肩上。我从小到大,她是我的母亲,更是我的挚友。 老妈对我的教育方式虽然和片中父亲虽然完全不同,但有着相同的胸怀。宽松的环境反倒让我可以思考得更加深入,有机会去读书去接触他人。 这些都让我从侧面更加解我自己,知道自己应该去做什么,更明白自己在做什么,更可以为自己所做的事情负责。 所以我很庆幸,我(我想父母也是)早已经过了上文中所说的吉塔父亲被关进小黑屋的那一关。 再矫情的话我也说不出了,不过此刻确实非常想给老妈一个大大的拥抱。 最后的最后,附上几张看完电影后在学校中见到的如画一样的美景吧:
测试 测试2 测试3 测试4 hello
intro 在抬笔之前,纠结许久,过去的这一年,真的有这么多要记录的东西么,有必要花几个小时的时间去写么?我不知道。但同时,我的鼠标已点开了之前的博客,开始翻看前些年写的文字。看着看着,我找到了许多自己已经不怎么记得,但想起却感到十分温暖的那些瞬间和回忆。虽说都不是什么大事儿,但是如果不记录,它们可能真的就会遗失在记忆的荒野里了。 所以,我还是要写些文字,来记录我这平凡,也小有起伏的一年时间。 目标回顾 首先,回忆一下去年立的那些flag吧。 分享至少 5 篇较为优质的内容 目标完全没有达成,一是由于惰性,二是感觉自己积累不足 对领域内技术的认知上有明显提升,具有一定的专业性和深度 专业知识确有所增加,但离自己的期望还有差距,算是勉强达成 维持学习状态,保持对世界的好奇心,对世界的认知上更进一步 确有长进,但也不多,算是没有原地踏步吧 阅读,至少 5 本较篇幅较长的著作,减少看视频的时间 大篇幅的只有一本花了半年才读完的《红楼梦》;看视频时间有所减少,原因是从看变为了听 锻炼身体,体重维持在 80kg 以下 体重目标超额达成,但原因却不是锻炼身体 多陪陪家人,每周和家人通话 目标也算是基本做到 整体看来,六点Flag,勉强算下来,可以说是完成了一半吧,约等于不及格。 原因肯定是多方面的,不能单独归咎于时间不够。因为我知道就算是时间充裕,也很有可能无法完成上述任务。所以,今年我决定使用一个更加精确的目标系统——OKR对自己的目标进行量化确定/追踪,并且应当可以按照执行情况修改。希望通过新方法,可以让明年的自己及格吧。具体规划放在文末。 生活 这一年的生活给我最大的感悟就是“无常”。疫情无常,生活无常,生死也无常。 下面,就以几个生活中的小片段来简要概括这一年吧。 其一 年初的第一件大事是乐乐的婚礼。在他的婚礼上,我们这三个从小几乎天天在一起的铁哥们,终于能从天南地北百忙之中,在疫情笼罩下,时隔多年之,后再次聚到了一起。我呢,也终于第一次当了伴郎,第一次正经穿上了西装,也第一次从“台上人”视角见证了好朋友的婚礼。疫情之下,虽然没有大操大办,但几天的相处,让我深刻感受到了乐乐和嫂子的深厚感情。所以这里不说别的,祝福乐乐早得贵子,也祝福剑客早日结婚(相见不易,总得有个由头呀)。 刚布置好的婚房 此外,在见面后的聊天中,突然发现童年的好友都已成熟起来:聊天中少了许多幼年时对游戏和故事的痴迷、少年时对宇宙和未来的畅想,却添了对生活的感悟与吐槽,以及对肩上责任的担当。肩膀扛起的东西变重了,脚下也自然就会踏实起来,这或许才是人们仰望天空的底气之所在。 在家后山上看云 其二 虽说现代通讯非常方便,但我们几个也是各自有忙,所以自此一别,再次认真联系,大约是就年底了,原因是他们关心我的心理状况:在奶奶去世后的第三年,从小陪伴到大的另一位亲人——爷爷,也永远离我而去了。 坏事总是十分突然。当时兰州的疫情刚刚有所好转,各种交通刚刚解封,就得到了爷爷病情突然恶化的消息。也顾不得多想,做完核酸第二天的凌晨,就和姑父驱车从北京赶回家中。 一路上 幸运的是,我的决策是对的,在赶回家的那天晚上,见到了爷爷意识完好情况下的最后一面。他在这种情况下,还是像往日那样,一见面就担心我工作忙,穿的少,吃不好。在得知我一切都好后,他也逐渐安心下来。第二天,他的意识就再也没有清醒起来,生命也随之进入了倒计时。最终,在2021年11月23,他还是永远地离开了我们。那天凌晨六点医院门口的瑟瑟寒风,我一定毕生难忘。 爷爷一生很不容易。他生于解放前,小时虽然家里穷,但成绩优异,所以家里一路供他读到了初中毕业。后来,因为自然灾害无力负担,就征兵入伍,去了二炮,做了一个挂着空军名号的步兵。随着国家需要,他们一路辗转,从老家来到北京,又从北京到了茫茫戈壁。此后,他们部队在戈壁上克服了种种困难,为我们国家的国防做出了重要贡献。后来,他转业来到了当时为了“备战”和“备荒”而在西北大山深处筹备建立的884。他们再次发扬那个时代艰苦奋斗的精神,遇山开山,遇水架桥,硬是在只长荒草的群山深处,建起了一座现代的大型铜加工厂,为国家生产了许多战略物资。在他退休后,虽然终于不用为工作日夜操劳,却又开始为了我的成长费心费力,这一晃又是二十多年,直到现在。 而就在我刚力所能及可以养活自己,并且也有一些余力可以让他生活得好一些的时候,他却永远地离开了我。子欲养,却亲不待。 人这个物种大约就是这样,谁也无法脱开的生死的轮回,每个人都应当看开些。但生死事大,岂不痛哉! 爷爷的在党50年纪念章 今年也是建党100周年,在这里我要向爷爷这样为国家建设奉献了一生的老同志们,致以最崇高的敬意!虽然在你们身上没有那些波澜壮阔的功绩,也没有曲折动人的故事,但正是由于千千万万你们的付出,让共和国有底气走到今天,也是千千万万你们所坚持的初心,守住了和传承这个社会的正气。 其三 现在,让我把回忆的时间线再次拉回年初,去回忆另外一条主线。 从年初开始,我进入了频繁去杭州出差的状态。虽说每次的目的地都一样,但随着四季的变化,都有新的风景,好不惬意。比如仲春的龙井茶园、夏日的西子湖畔,还有秋日的大运河滨都让令人着迷。我也终于理解为何有如此多的文人墨客偏爱这里,留下了无数流传青史的千古墨宝。 2021飞行统计 这一年的多次往返让我对这座城市有了许多直观的感受,比如: 自然和人文风光不必多说,不仅好地方多,而且风景在四时都有所不同; 但是基础设施相比于北京也还存在不小的差距,不过可以看出一直都在进步 城市处于扩张阶段,老市民集中在老城区,新市民都在新城区,在新城区能明显感到接纳包容的年轻的城市文化,但老城区却相反 地价房价上涨很快,后上车的人成本变高,且造就了一批房产富豪 另外,更重要的是卤蛋也逐渐在这座城市安顿了下来,所以我之后还有大把时间在这里走走转转,期待能有更多更深入的了解。 烟雨龙井村 西湖落日 繁忙的大运河 其四 除此之外,在去年上半年我也经常进行体育锻炼,在周内最常去的地方是公司楼下的大望京公园,而在周末则非奥森公园莫属了。基本每周的跑步距离在15到20km左右,除此之外还会骑车上下班,一天大约能有十几公里。跑步的配速也逐渐从年初拉夸的七分钟,逐渐恢复到了年中的五分多钟。并且通过一起跑步,也和公司里的一些同事有了更多的交流,收获满满。 某次跑步记录 虽然我的运动量这么大,但是体重却几乎没什么变化。原因很简单——每天都得吃点好的。 跑完步当然要吃顿好的犒劳一下自己 肯定有很多人抱着和我一样的想法:已经这么努力运动了,在饮食上放松一下也没有问题。然而,事情没有这么简单。 在下半年里,我付出了沉重的代价:告别了火锅,告别了啤酒炸鸡,告别了我最爱的麦当劳,甚至告别了多数肉类。同时,我也告别了熬夜看视频,告别了久坐打游戏,也告别了我最爱的长跑。 现在看来,效果还行。体重从巅峰的接近85kg降低到了如今的不到75kg,身体也逐渐恢复正常。不过随着体重的减轻,肌肉们也在一同不断流失,但做什么,都总得付出些代价吧。 下半年的体重变化 钱花光了可以再赚,物品丢掉了可以再买,数据消失了都可以再造,但是健康失去了却很难重新恢复,关心之人去世了就更难弥补。正所谓“知易行难”,这些简单的大道理说起来容易,但若没有经历过,却很难真正理解。如今理解了,却也都付出了很重的代价。 最后,我发现猫咪真的是一个十分神奇的物种,不论什么时候,它们总能让人感到治愈,会让人多一些面对不确定性的勇气,所以就用一张球宝(室友的猫咪)的靓照作为本章的结尾吧。 球宝一瞥 工作 & 技术 坦率地讲,自己在去年工作中的收获是不小的。不仅是技术上做到了真正的入门,还有在心理上具有了一定的主动性。 回过头来审视从刚入职到现在的这一年半的时间,能明显感到自己从一个刚进入公司时几乎什么都不懂,只会低头自己琢磨,不会跟人沟通,甚至因为担心自己水平差,而感到有些自卑的职场新人,成长为了一个较为自信,能直面问题,从更多角度思考,并跟他人配合,共同解决问题的人。 我认为入职即将满一年的时候对自己来说是一个很重要的节点,原因很简单,那会儿刚好在年末评定绩效,所以在这段时间里,我终于有机会也不得不好好梳理和思考去年一年在我身上发生的事情,以及我面对事情所采取的态度和行为,并且也能将这些思考和主管进行直接而充分的沟通。什么是好的,什么是不好的,什么之后应该努力避免,什么事情还能做的更好,都逐渐变得明晰。这种明确的反馈对于一个人的成长有很大的帮助。 部门校招同学培训时的一年香蛋糕 当然,作为一个工程师,除了上面这些比较“务虚”的成长,一定还得聊聊自己技术的进展。 祸福相依,虽说自己干自己的,不懂和别人交流不是什么好事,但是也是由于多数时间都在低头琢磨,自己写代码,所以从刚入职到一周年左右时,我大概零零散散为项目提交了大约一万多行不到两万行代码。因为我所编写模块代码和其它模块有很多耦合,所以编码的时候我也被迫几乎阅读了我们组所负责项目(一个Rust编写的VMM)的大部分代码,这让我很快对这个项目的设计与架构有了大致的了解,也让我更加熟练地掌握了Rust语言。同时,这些工作也让我意外地获得了整个BU的第一届代码贡献奖。 代码贡献奖现场 去年有关技术上的成长大概可以归纳为以下几个方面: 入门虚拟化:从只会使用VmWare,到现在开始认真了解虚拟化的原理,阅读KVM源码,并实现了一些VMM的代码 开始学习Linux内核:阅读了一些和虚拟化以及业务相关子模块的部分源码,同时也编写了几个简单的内核模块。特别是在下半年,由于零零散散地参了一些创新项目,所以也开始对Linux运行所依赖的Arch,以及UML有了更多的理解与思考 了解容器生态:当前主要局限于单台节点中,比如runc/kata的实现原理,containerd的工作流程等等 在公司里,技术和业务一直都是牢不可分的,所以,我也借着开发这些代码的由头,了解到了当前云原生的发展形势、当前遇到的问题,以及众多业务场景(比如函数计算和弹性容器)。随着这些背景知识的输入,让我对整个云计算行业,特别是云原生业务的发展前景,抱有较为乐观的预期。 此外,还有一个好消息是我们大团队将我们平时所研发的操作系统分支及其上下游组件开源为“龙蜥社区”,而我们小团队所做的项目也会通过龙蜥社区和KataContainers这两个社区将源代码贡献出去。所以,后续应该就会有更多可聊的技术相关的东西了。 云栖大会中和“小龙人”的合影 经验 & 思考 经过去年一年的经历,也获得了一些的简单的经验,在这里也将其中一些分享出来。还有一些思考不成体系,略显混乱,就不买弄了,等以后有机会仔细整理,再发出来。 人的精力有限 可能因为之前比较年轻,所以总觉得无论有多少事情,我总能想办法做完,大不了熬上几个通宵。 但是到了现在,发现自己的精力已经远不如本科时候了,如果晚上熬夜,第二天可能就完全无法正常工作,这样效率可能比晚上好好休息还要低。这才让我真的意识到自己的精力有限,很多看起来很有兴趣,动动手就能做完的事情,其实自己并没有精力去做。 这对于我这样的完美主义者是一个非常大的打击。无限把事情做到完美的代价要么是很多更重要的事情无法完成,要么是自己的健康受到损害,而且甚至牺牲了这两者还是无法把你最想做的事情做到最好。 在痛苦地挣扎了一段时间后,我还是向规律妥协了:把做事的目的逐渐由把事情做完美变成了把事情做成。我发现影响一件事情成败的关键点只有几个,剩下都是锦上添花,因此做事情不能贪多,需要将它们的优先级进行排序,先做优先级高的,再做优先级低的,虽然有些优先级低的事情做完会让整体更加完美,但是你如果没有精力顾及这么多,那也只能放弃。 另外,虽然个人的精力有限,但是人多力量大呀。如果能把多个人的力量集合起来,就能完成超出个人能力的更大的事情。不过如何利用别人的力量一起完成一件事情,我暂时还没有特别成功的经验可以分享。 小事快速决策 一定有很多人像我一样,是比较纠结的性格,做决策时总是考虑再三,一直无法得到一个明确的结论。这在一般时候不会表现出什么问题,但如果面临的决策非常多,那么做决策这件事儿将会是时间地狱和精力黑洞。 举一个在工作中常见的例子:在开发一个模块的时候,虽然架构设计已经确定了,但在真正编码的时候依旧经常会面临怎么写比较好这个问题,比如大到一个消息通知机制应该怎么设计,小到这个变量应该叫什么名字。不知道大家会怎么样,我经常会为了这些事情纠结很久,甚至把每种方案都写一遍,看看哪个更好。但最终往往只有两种情况:要么是它不重要,怎么写都可以,要么是只有其中一个设计比较好,但是需要写完后面的代码你才会知道,现在纠结并没有什么作用,甚至可能纠结很久,还是会选择一条错误的道路。 因此,在当前决策对未来事情发展影响比较小的情况下,快速决策快速做选择快速试错才是比较优的策略,如果一味纠结,不仅会浪费很多时间,而且往往也不会增大把事情做对的概率。所以,纠结症患者不如放下心中的纠结,闷着头随便选一个,省时还省力。 不过上面的方法并不适用于重要事情的决策。因为重要事情决策可能会对后面产生重大影响,哪怕成功率只提高一点,也会有很大的价值。而决策的关键的是收集和整理信息,如果只顾着快,而忽略了很多有用的信息或没挖掘到信息中比较重要的点,导致决策出现失误,这就得不偿失了。 合理预估时间 在多人协作时,每个人的工作可能多少都会依赖别人的工作,而为了便于将每个人的工作进行组合,管理者一般会采用排期的形式把控项目的进度。所以,在工作中,就总会有人问你:你觉得这个事情多久能够完成? 但从个人的角度来看,面对一个稍微复杂一些的事情: 总是需要较长的时间完成 往往中间会因为吃饭/睡觉/开会/有其他更紧急事情等多种原因被分割成多段,保存/恢复工作状态需要消耗意志力和时间 事情完成的过程中几乎必然会出现许多意料之外的情况 因为事情紧急,为了督促自己尽快完成,可能会预估一个比较早的时间 人们的思考模式也决定了我们往往对一个事情的预估是偏乐观的 所以,这最终会导致个人对一件事情完成时间的预估是比较乐观的,在deadline之前一段时间往往会拼命去赶,就这样还不一定可以做完,导致延期,而且还可能会由于个人的延期导致项目节奏被打乱,从而导致项目的延期。 虽说项目规划者有责任考虑到这些原因,但作为参与人,也有必要对项目完成的时间进行较为合理的评估。目前,就个人经验看来,一件事情完成的时间往往是自己脑中认为可以完成的时间再增加50%以上。因此,在需要精确项目时间的时候,我们可以通过将预估完成时间直接*2的方法来为自己保留合理的裕度。 新年OKR 去年,直接用几条flag表达了对新的一年的期望,由于没有合理的checkpoint与拆分,导致很多都没有完成,今年我认为需要改变策略,用用新的OKR工具做做试验,看看是否能让自己真正动起来,将目标的完成率提高。 下面,就将今年的一些OKR列出来,不过使用博客跟踪OKR的效率肯定不高,所以后续会尝试配合一些跟踪目标的软件一起使用。 Object1:生活健康自律 健康的身体是一切的前提,所以我希望将身体健康,生活自律放在OKR的第一位 KR1: 早睡觉,不熬夜 睡眠质量对身体健康和工作效率都十分关键,因此早睡早起是必须要做到的目标。 由于之前都睡得比较晚,睡眠时间需要慢慢调整,所以将晚睡定义为在凌晨12:40之后上床睡觉 非特殊情况,每周最多有一天晚睡/熬夜 KR2:坚持锻炼 由于每天的工作都是对着电脑久坐,因此必须要让自己有时间动起来 锻炼以一定强度的有氧运动为主,且不能过于剧烈,可选的方案:慢跑、球类运动、骑行、爬山、跳绳等,时长以30分钟以上为宜 在三月份气温回暖后,非特殊情况(如生病/出门在外等)每周至少进行三次锻炼活动,三月份之前,每周至少两次 KR3:健康饮食、控制体重 饮食对身体健康的影响也十分显著,体重亦是如此,因此需要进行合理的安排 饮食目前没找到太好的定量约束方案,就限定一下吃饭速度吧,如果有一同吃饭的人,不能吃的比所有人都快 体重比较方便定量,在3月份气温回暖后,逐渐将体重控制在70KG(±3KG) Object2:知识的输入和输出 KR1:阅读原版书籍 只有阅读一手知识才能让自己真正理解知识,通过看视频/读别人的笔记获得知识的速度虽然快,但是可能会忽略掉其中的许多细节 能增长知识的书籍分为两种:技术书籍和人文社科书籍,它们的阅读方式和收获也是不同的,因此单独制定计划: 技术书籍两本: 计算机体系结构——量化研究方法 暂定 人文社科书籍五本: 八次危机 暂定 另外,暂定每周有五天时间每天阅读半小时 KR2:学习优秀源码 精读优秀的源代码和原版书籍一样重要,需要仔细阅读,暂定两个虚拟化的项目,后面再做补充 KVM QEMU KR3:输出内容 在阅读完别人的内容或者自己进行一些实践活动之后,如果不及时总结,可能会导致没有彻底理解,而总结的最好方式就是输出一篇较长的文章,既锻炼逻辑思维,也提升写作能力,因此暂定一年输出五篇文章 Object3:其他目标 除了主要方向外,还有一些其他的目标,暂时只列陪伴家人,后续再做补充 KR1:陪伴家人 陪伴家人的目标去年执行得不错,今年要继续保持 每周和家人电话 逢年过节没有疫情的情况下回家 The End 最后,就以前几天自己做的一个梦作为博客的结尾吧:不知何年何月何日,乘坐一架很大的宽体客机回家,但是飞到大约三分之二的地方,飞机突然失控坠向地面,好在紧急迫降成功,降落在了一个不知名的地方。这里到处都是荒漠,还有一些居民,只有天边模模糊糊可以看见草原。手机导航没有坏,它告诉我没有别的交通方式,只有步行导航,而且需要走很久很久才能到家。我也找了一圈,也的确没有看见其他的交通工具。许多乘客都因要很久才能到目的地而选择在这个荒漠小城留下,看情况再决定是否出发,但是我在找附近的居民买了一些馕作为干粮后,马上就出发了。 能不能走出荒漠,我不知道,至于能不能走回家,就更不知道了。但是梦中的我做出了坚决的选择:不要在意这些,走下去,马上出发!
在挺久之前,可能是刚读大学的时候,就隐约听说这本《禅与摩托车维修艺术》的大名,也得到了几位朋友推荐,但在当时,翻过几页后发现根本看不下去,就束之高阁了。这次终于在好奇心的不断怂恿之下花费了一些意志力重新拾起,终于随着情节的展开,同作者一起踏上了骑着摩托车横穿整个美国的长途旅行。 在最开始的认知中,我认为这场摩托车旅行一定是一个非常酷的故事,但后面发现虽然故事的确很酷,却非第一印象中的那种酷。 在这场旅行中,我在了解美国地理的同时,也被作者和“斐德洛”的暗中较量所吸引,对父亲与克里斯的矛盾所疑惑,更为寻找“良质”同作者(或者说是“斐德洛”)一起陷入了对当下的反思。 故事 先大概说说这个故事吧。本书讲述了作者与儿子(还有友人)骑着摩托车进行了横穿美国的长途旅行,在旅途中,他骑着摩托车通过多次“肖陶扩”(Chautauqua,看起来是一种露营+讲学的结合体,在上个世纪的美国比较流行)对过去的自己的思想与经历进行了回忆和反思,最终也与过去的自己及儿子和解的故事。 随着故事的深入,读者会发现作者的大脑中存在着两个人,一个是当下的他,即波西格,另一个是过去的他,即斐德洛。这都是由于斐德洛由于对“良质(Quality)”的狂热探求,导致他的精神出现问题,而后他接受了电击治疗,导致他的思维、记忆和性格产生了变化,变化后的人就是现在的他。 这是作者在故事结构上非常巧妙的安排之一,一开始读者读起来总觉得作者说的某些话语很奇怪,隐隐觉得哪里不对劲,有时候甚至会感到后背发凉。直到后面才知道那些话其实是“斐德洛”借作者之口说出来的,这才恍然大悟,并在不知不觉中接受了这个设定,为后面对斐德洛思想的探寻做足了铺垫。 另一个比较有意思的地方是“虚实结合”(不确定这个词用在这里是否准确)。在书中,作者对“禅”的思考和摩托车旅行是相互交织展开的。在对骑行的描写中可能随时会开始“肖陶扩”,转向对思想的讨论,而在讨论进行的过程中也可能会切换回对旅行的而描述。这样的切换在写作的时如果把握不好就会显得十分生硬,但是作者的处理还是相当到位的——旅行中的种种波折与斐德洛在思想上的进步与挫折以及作者和儿子的关系都在相互映照,它们的节奏也都完美契合。 不过,也不是所有的情节都是完全契合,而是在契合中又有升华。比如在第三部分的作者选择了和当年不同的路,致使他没有再次“发疯”。故事大概是作者从“斐德洛”的老朋友狄威斯家中出来以后,准备攀爬一座雪山,这座山不是一座普通的沿途小山,而是一座需要数天才能攀爬上去的山,而且还是斐德洛当年在思考良质的过程中会经常去爬的山。因此,这座山其实也不仅是现实中的一座山,其更是一个隐喻,隐含着斐德洛对良质的艰苦探索的过程。 而这次,他和儿子在进行持续几天的艰苦跋涉逐渐接近山顶的时候,因为他担心在山顶会产生”雪崩“,就选择不再登顶了。可以看出,雪崩在这里其实也是一个隐喻,这象征着一旦到了山的顶点,即找到所谓”良质“后,就会遇到雪崩,即作者思想上的崩溃。而这次作者选择不完全爬上山顶,就避免了再次雪崩的情况,只是在崩溃的边缘而没有完全失控。而最后,儿子或者说亲情“解救”了他(或者说是斐德洛),让他的神智重新回归了正常。 思想 作者的思考由旅行同伴约翰夫妇对学习摩托车维修的抗拒开始。他认为约翰夫妇由于从事的是艺术工作,所以害怕(或是说不喜欢)现代科技所使用的分析方法,因此主动屏蔽对机械运行原理的学习与思考,而只是将它们当作一个整体来考虑。 作者从这里引出了“古典的”和“浪漫的”这一对立认知形式。他认为古典的认知就是像现代科学这样,凭借理性对某一事物不断细分,以求认识其中的规律;而浪漫的则是凭借直觉与灵感,对事物进行把握。而当代这两种认知方式之间的分歧越来越严重,达到了难以对话的程度。正如他与约翰夫妇之间的分歧那样。 可以看出,作者很难找到一个合适的形容词对这两种思想进行表达,所以使用两个在我们语境里看起来不那么准确的词语进行描述。我认为古典的意思就是现代的理性主义,强调将事物划分为理性中不同的领域和范畴,并对范畴进行不断地细分,以求掌握事物运行规律的认知形式。而浪漫的则指一种综合地认知事物地方式,强调事物间的共性与联系,但往往由于世界的复杂性,无法用科学原理进行证明或证伪,所以有可能被认为是某种超越科学的“神秘主义”。而我之所以认为在我们语境里这两个描述不够准确,是因为“古典的”在西方世界里就是理性的化身,而我们则恰恰相反。 接着,作者对科学进行了反思,他认为科学也是某种潜藏在我们大脑中的“鬼魂”,相信科学的我们并不比相信宗教的古人更加智慧。这之后的作者便踏上了对“斐德洛”思想的回顾历程,因此后文的“他”一般均指斐德洛。 他在进行科学研究的过程中发现科学家(他自己当时就是在做化学相关的研究)总是在大胆假设和小心求证中不断前行,但是“大胆假设”环节看起来不太符合理性的逻辑,因为假设会随着科学范畴的增多爆炸式增长,这样就会导致我们会越来越无法从众多假设中找到符合逻辑的结论。 这其实正是现代科学所需要面对的一个很大的问题,更底层地看,这需要我们对自己的认知世界的方式,也就是经验主义进行反思。而作者的反思是通过休谟与康德的对话进行的。休谟曾经对人类的认知模式提出了三个问题:因果问题、归纳问题、应然与实然的问题,这三个问题直指经验主义,如果无法解答它们,那么我们建立在经验上的科学将没有任何价值。康德拯救了经验主义(并向我们抛出了三大批判),他通过引入“先验”这一概念,确认了只有经验是不够的,还需要我们认知世界的思考范式。虽然康德的思想惊艳到了斐德洛,但是斐德洛认为康德的《判断力批判》中对美学的理性解释却非常丑陋。 可以看到,作者在这里对科学的理性和感性对立的认知已经从当初的经验阶段(科学的危机)走向了理论阶段(认识论),他已经开始摆脱实践,转而走向了纯粹形而上的追寻。事实上,他也从最开始就读的大学退学,转而去韩国当兵,而后去印度修习“东方哲学”。 而后,他开始在蒙大拿州立大学教授修辞学。他希望他所在的大学是实质上的大学,而非形式上的大学,即空有大学的建筑,而没有大学的思想,他的脑中逐渐构建了一个理想中的“理性教堂”。他在那时,又开始了在课堂中的实践,但是发现了理性在修辞学上的矛盾之处——无法使用理性对修辞学进行归纳和总结,最后形成规律,因为作者在写出伟大作品的时候,并不会在脑中实现想好自己要用什么修辞,好的作品都是靠着灵感这种非理性的东西所完成的,那么他教授学生通过理性归纳得到的这些规律又有什么意义呢?所得到的都只是一堆拙劣的模仿。 而这其中,为了完成一部好的作品中,究竟是什么在起作用呢?就是“良质(Quality)”。他知道良质这个东西是真实存在的,写出好的作品、辨认好的作品都需要依靠良质,但良质却又不依靠理性存在。从这里开始,他踏上了找寻良质的一条“不归路”,从这里起,现在的作者和儿子也开始攀登雪山,全书逐渐迈向高潮。 一开始,他的一个重大发现是良质根本无法被定义,因为一旦被定义,或者说是被范畴所规定,良质就不再是良质了。所以他在读到《道德经》中“道可道,非常道”这句话时,才觉得遇见了知音,良质和“道”居然是一个东西。不仅如此,他认为良质和美学、佛学以及神学其实都是一个东西。宗教的、东方的与艺术的,这些无法被理性哲学所把握的(或者是被作者所鄙视的”范畴学家“所毁坏的)事物,在良质这里得到了统一。 可以看到,作者终于从西方的理性思维和二元论中跳脱出来开始意识到许多无法被理性所把握的事物的存在,但是很快我们将看到由于他所在的社会环境以及他的历史局限性,他还是无法摆脱被二元论分割以及被范畴所规定的命运。同时也可以看到作者希望对思想世界进行统一的野心。 现在的作者不仅阅读了斐德洛的手稿,还更广泛地阅读了其他人的作品,发现了许多类似的思潮,他们都希望能从不同的侧面爬上这座山峰(但殊不知老子已经在数千年前在山顶俯视他们了)。比如著名数学家和哲学家庞加莱,他意识到科学,包括数学只是一种解释世界的工具。我们能通过理性推到出无数可能的世界,但是无论它们在理性上有多么完美,但是最终被应用的一定是能更好解释现实世界的现象的学说。那如何从成千上万的理论中挑选出合适的理论呢?庞加莱认为这个是灵感,或者说是潜意识,但由于纠结于主体与客体分离的二元论中,他没有得到具体的答案,而作者认为答案就是”良质“。 接下来,作者探讨了良质的应用,特别是在解决某些问题(如修理摩托车时)被卡住的时候,良质会帮你度过难关。他提出了一个很有意思的比喻,古典的认知方式是火车的引擎,可以让火车开动,但是浪漫的认知方式(即良质)却像铁轨那样指引了火车前行的方向。 后面,他又对康德发起了挑战,认为人在意识到事物之前的那一刹那,产生作用的其实就是良质,这时候良质是一个一元论的概念,主客体是一体的。为什么他认为科技是丑陋的?因为科技是被严重二元化的,是被分析的,没有良质的存在。同时他也总结了能将良质应用于工作中的方法,即让内心进入真正的平静,从而获取进取心。 读到这里,看起来作者已经找到了良质的关键之所在,已经即将登顶那座雪山,但是情况却急转直下。因为他希望继续对良质进行深入研究,于是他希望去芝加哥大学的一个叫“观念分析与方法研究”的交叉学科就读博士学位,继续对良质进行探究。然而他去了之后,遇到的阻力不再是近代的休谟康德黑格尔,而是西方理性思维的源头——苏格拉底、柏拉图和亚里士多德。他与委员会,即这几位思想家在现代的化身,特别是亚里士多德的交锋才刚刚开始。他为了保护他的发现——良质,开始变得狼性,时刻准备与委员会进行战斗。但最后他的心智失去了平衡,走向了疯狂。 (为什么我这里没有介绍他们之间你来我往的交战过程呢?是因为我学艺不精,对古希腊哲学知之甚少,无法支撑我进行有效的总结。希望之后有机会补充吧。) 最后,现在的作者在儿子的牵引下,没有重蹈之前的覆辙,走向了一个完全不同的结局。 什么是良质 我相信所有读完这本书的人(包括我自己),在合上书之后,还是会不断思考这个问题:“良质”究竟是什么。 接下来是我个人的理解: 但凡问出这个问题,就意味着已经自己的思想已经被理性主义的幽灵所掌控,因为理性主义总是希望将事物归为一个范畴,然后进行分析总结,最终把握这个事物。 但是理性走到这里,却只能碰壁了,因为“良质”无法被范畴所规定。这里再次化用《道德经》开头的这段话:“道可道,非常道;名可名,非常名。无名天地之始,有名万物之母。”大概意思是,道只要被名说出来,那就不是原来的道了。那道德经怎么来解释这个道呢?就只能通篇说道不是什么,通过不是来表现出其是什么。所以我们也只能说出“良质”不是什么,而无法说出其是什么。 那么良质不是什么呢?这里的答案就很明确了,那就是能被范畴所规定的各类事物,也就是理性。 我觉得还需要对范畴还想多说两句,它只是我们大脑中对事物强加的一种观念,便于对这个事物进行把握。但是,这只是大脑的思维模式,并不是真实的物质世界,物质世界给我们大脑所传递的,只有一堆混沌的信号罢了,我们是通过某种观念(可能是先天的,也可能是经验)对信号进行处理,从而形成认知。而至于为什么会是现在这样,那就是另一个话题了,需要从语言与存在中找寻答案。 说完上面这些,可能还是不能完全让人明白什么是”良质“。但是如果此时还没有明白,我也没办法,只能寄希望于某一刻产生的思想火花了。其实并不只有我,古代的先贤们也没办法。因此,他们管这件事情叫“悟道”或者叫“开悟”,而佛学还有一个专门形容一个人开悟难度和可以开悟程度的词,就是“慧根”。 几点思考 首先,我想试着简单分析一下我所认为的作者精神崩溃的原因。 从书中的描写可以了解到,作者在最开始是一个纯正的理性信徒,但通过发现的数个矛盾,悟到了”良质“,发现了一个更广阔的非理性(也可以称之为感性)世界的存在。而后,他开始向理性世界的中心,即古希腊三杰在现代社会的代理人主动发出挑战,并在挑战的过程中精神崩溃。 可以看出,他前期的艰苦探索都是非常积极的,甚至在打破二元世界,接受一元论与感性世界的时候,都没有表现出太多的痛苦。但是在挑战理性权威的过程中,他开始变得好斗,并且把学术上的争论上升到了个人的层面,甚至有了一些被迫害妄想症的味道,这导致他最后吞下了苦果。 但是,他真的完全接受一元论,或者说是活在一元论中了么?我看未必。委员会最开始见到斐德洛的时候,希望使用范畴把他的研究,即良质进行规范。如果是我,我会认为已经跟你说了这么多,你这个人却还没有开悟,没法交流,直接扬长而去便可。但是他却留下来,在逻辑与范畴中开始斗争。这是一个非常不明智的决定,也是注定不会成功的斗争。这可能是由于他希望统一思想世界的野心,也可能是因为他作为一个出生开始就接受二元论教育的人,对一元论还无法向我们这样融汇贯通。 以上是他个人的局限性,当然还有历史的局限性。作者接受教育的时候,存在主义应该在美国还没有大面积流行起来,而共产主义在美国更是一种禁忌,这导致存在主义的两大先驱任务——马克思和海德格尔的思想和著作,完全没有进入作者的视野,如果他早些读到这些著作,可能会对良质拥有更加深刻地认识了。同样地,他也不会发起那场同古希腊先贤们地那场毫无意义地争斗,因为已经有人帮他做过了。他要做的,可能会是把这套理论进行实践,无论是在他擅长的修辞学上,还是在艺术上,甚至是像马克思那样在社会革命上。 另外,在读完以后,还看了知乎上对本书的一些讨论,和此书高达8.6的豆瓣评分和创记录的销量完全不同,几乎都是负面的评论,这让我很意外。看下来许多人几乎都觉得这本书完全不值得读,觉得是“机场文学”,里面讨论的哲学都是所谓“民科”级别的,如是云云。 我承认知乎上可能会有许多通晓哲学思想的大师,但是这本书对于一般读者而言,其难度其实并不低,甚至可能会有很多人会像当年的我那样,因为完全没有基础知识导致根本看不下去。读书也是“小马过河”,不能用自己的标准衡量别人。 哲学这门学科在希腊语中的本来的翻译是“爱智慧”,它不像科学那样有固定的范畴与研究方式。就像作者对“良质”的探索那样,虽然早有人从一条路抵达了山顶,但还是需要有更多的人从更多的路探索更多的山顶,虽然他们大部分人都停留在了山底或者是半山腰,但是这些探索不论是对个人还是对社会来说都是有意义的。绝不能当作者在芝加哥遇到的“委员会”中的那类人。 最后,回到本书最开始作者对科技的思考。作者认为他那个时代的科技产品都遵循“古典式”的思考方式,没有“浪漫式”的思考,这导致很多人不喜欢科技产品,更不愿意了解其原理。而在现在这个时代,我认为事情已经悄然发生了很大的转变。首先,我想到的就是乔布斯对科技与人文十字路口的论断,他用苹果的产品证明了科技与人文是可以相互融合的。另外,我也也看见身边越来越多的人成为了”全栈开发人员“,他们不仅对技术了如指掌,更对用户交互设计了然于心。最后,昨天夜间还看见了何同学的毕业视频,这不也正是这种精神的完美阐释么?不知波西格在成书多年以后,看见如此这般的当下,会有何感想。
时间很快,如今已经是2021年伊始了。虽然已经很久没有写过文章,但是,面对2020年,我总还纠结着要说些什么。 2020最大的主题就是变化。不仅有意料之中的变化,更多是意料之外的变化。 疫情 说到变化,总也绕不过的就是疫情。 我的2020年是以一次滑雪作为开始,在那场滑雪之后,我还在畅想在结束毕业论文写作之后,毕业前的时间我应该怎样度过,是要趁着有闲逛上大半个中国,还是省一些钱躲在实验室里啃啃买来一直没读的大黑书。 年初滑雪 放假回家后,就开始盘算:工作后肯定没法在家待太长时间,不妨这次放假在家里多待几天,多陪陪家人(多睡睡懒觉),不到交论文的那天,坚决不回学校。 然而现实远超我的想象,不仅交论文的那天没能回学校,甚至到了毕业那天,都不能回去。 事情的起因还是一月份开始听说有一种类似SARS的病毒已经开始悄然传播,那时候感觉病毒还很远,和我没有关系;直到除夕前一天,武汉封城,全国人民在家过年,我开始知道它的名字叫新冠病毒,而且十分严重,和我们每个人每天的生活息息相关;后面,我又通过躲在手机屏幕后的暗暗观察,发现这件事情不仅改变了我的计划,改变了中国人的生活,甚至整个人类都与此息息相关;而直到一年后的今天,疫情依旧不知疲倦地在全球蔓延。 是呀,仿佛离我们很遥远的事情,在短短一年内变成了每个人所面临的现实,这对于一直沿着一条可被经验预测的道路前行的我来说,的确是一种震撼。 另外还有一个比较深刻的感受,是“正常化偏误”。之前听的比较多的一个例子是一个小镇旁边有一座火山,那里的居民居然在火山爆发以后觉得问题不会那么严重,没有逃跑,导致伤亡惨重。我想我一定不会这么蠢,一定要最开始就逃走。但是,当疫情来临的时候,我在最开始就觉得应该是要买口罩了吧?但是又觉得会不会没这么糟,现在就去买口罩会不会显得自己很异类?就这样拖延了两天,发现城里已经几乎买不到正常价格的口罩了。过后,我才醒悟过来,原来自己也随时可能变成眼看着火山灰把自己掩埋的那个人,这也是我2020年学到的第一个教训:当你觉得应该行动的时候,就一定要开始行动,不要在乎别人眼里你是怎样的。 病情 在疫情不太紧急的四月份,在家赋闲,于是给自己送给一台手术作为生日礼物。人生第一次手术还挺成功,我的胆囊和矿泉水瓶盖大的结石永远地离我而去。不多说,希望看到这里的每一个人都能按时吃早饭,健康饮食吧。 毕业 在开始读研的时候,我预想了无数种毕业时候我会做些什么,至少应该和卤蛋同学补上之前本科毕业没拍成的毕业照。但万万没想,这次毕业没有聚餐,没有毕业照,甚至都没有亲手拿到打印出的沉甸甸的论文,只有腾讯会议上大家还算灿烂的笑容(毕竟所有同学都顺利毕业)。 毕业“合影” 毕业以后我的一个愿望是等疫情结束,能回到陪伴我三年时间的小灰楼302去逛逛,找潘老师以及师弟师妹们聊聊天,可惜变化永远都是猝不及防——当时老纪说的多年前位于澡堂三层的上古实验室居然变成了我们如今的新去处。 工作 人呐就是不知道,自己就不可预料。我绝对不知道,我作为一个搞网络搞SDN的,怎么就来做云原生了。 说实话,福厂的工作节奏还是有点快,让摸了七年鱼的我花了很久去适应了。不过好在我遇到了一群非常nice的同事,特别是几位师兄,从他们那里学习到了非常密集的知识,让我从一个无法无天的野生程序猿变成了一个(自认为)还算及格的底层工程师。 这半年来,我的工作主要集中在和容器沙箱相关的技术之中,从一个虚拟化的门外汉,到看见了门槛之所在。另外,通过熟读Firecracker源码,也让我对Rust和microVM有了一个全新的认识。 在公司遥看望京SOHO 读书or视频 今年唯一的遗憾就是没有阅读太多的书,特别是很多书读到一半就放下,并没有读完。分析是两个方面的原因:一是由于生活模式的切换,自由时间变少,时间碎片化严重,没有相对较长的时间和精力来进行阅读;另一方面是B站使用时间显著增长,妄图通过对知识区视频的学习跳过读书快速获取知识。 这里可以借这个机会稍微聊聊我对B站的体会:在深度使用B站接近1年后,就知识区和科技区来看,UP主的数目持续增多,优质稿件也不断变多,这就吸引我每天花费大把的精力在观看所关注UP主的视频上,甚至都不敢去刷推荐流,很担心又看上哪个新的up主,想去追视频但是已经没有时间了。我不知道这是好事还是坏事,但是对B站来说绝对是一件好事,每天都会冒出许多新的创作者,分享许多奇奇怪怪的新内容,而且这也的确能够吸引观看者花费更多注意力在B站上面。这一切的一切,让我想起了刚上大学时候的微信公众号,嗯,熟悉的感觉又回来了。 还是说回读书,我认为能够进行较长时间专注的阅读,特别是大部头的书,对我来说还是十分重要的。因为视频中的知识点往往都比较零散,而且很难讲深,构成体系,只能作为谈资泛泛去听。所以,我认为系统性地读书还是十分必要的,接下来我一定得压缩每天在B站上花费的时间,重新回来读书中来。 希望接下来的一段时间,先把剩下的半本《红楼梦》读完。 一点思考 论2020年对我影响最大的人,莫过于温铁军和他的《八次危机》了。在B站看见他的视频后,发现他的思想理论和主流观点差别很大,但是又有一种吸引我不断去看他的演讲去思考他说过的每一句话的魅力。原因很简单,我逐渐意识到,他说的可能才是对的。 回想起来,印象最深的莫过于“代价”和“矛盾”,大到国家发展现代化需要代价,维持现有制度也需要代价,小到个人,过上好日子需要代价,不被别人支配更需要代价。大概就是只要你想要改变现状,就总要付出点你当下拥有的资源,以便让你在未来的时空里拥有你想要的资源。之前中学老师的一句口头禅“出来混,总是要还的”大概就是这个现象的通俗版本吧。和代价孪生的是“矛盾”,因为代价总是需要牺牲一些来成全另一些,那么牺牲者与被牺牲者之间就会产生矛盾,那么我想这也可能也是导致矛盾在不同的时期动态变化的一个原因。正因为代价和矛盾的不断转换,推动了事物的不断发展,个人是这样,国家和世界也都是这样。 结语 昨天午睡的时候做了一个梦,梦见自己在搬家,搬家的对象中有一个大箱子,很难搬走。箱子里装的是一个游戏,游戏的在一些城堡中发生,城堡的主人既希望继续战斗,赢得更大的城堡,又担心自己进攻的时候自己的城堡被别人抢走。我纠结了很久,决定让主人公继续战斗,并终于搬走了这个箱子。但是做完这个决定后,感受到了无尽的空虚与劳累,就仿佛做了一个多大的选择一样。紧接着,突然惊醒,意识到这个城堡小游戏的经历原来可能是自己前一天晚上没睡好而在纠结是否要起床的投影。可真的就只是这样么?我想也不尽然。总之,既然已经在梦中做出了选择,就不如在2021继续践行吧。 Flags 最后是每年的保留节目,立Flag,希望明年都能完成: 分享至少5篇较为优质的内容 对领域内技术的认知上有明显提升,具有一定的专业性和深度 维持学习状态,保持对世界的好奇心,对世界的认知上更进一步 阅读,至少5本较篇幅较长的著作,减少看视频的时间 锻炼身体,体重维持在80kg以下 多陪陪家人,每周和家人通话
在上一篇博客使用Qemu和GDB对Linux内核进行调试中已经介绍了使用Qemu和GDB对Linux内核进行调试的方法,但是GDB调试对于用惯了GUI工具的人(比如我)来说并不是很直观,所以就希望尝试使用比较熟悉的GUI编辑器,如VSCode,对内核进行调试。 由于VSCode的调试方式同样基于GDB,所以需要先在GDB中测试没有问题。 插件 需要在VSCode的插件市场中安装微软官方的C/C++插件,该插件可用于IntellSence和GDB调试。 配置 为了使VSCode支持内核的调试,需要配置launch.json,特备注意需要配置setupCommands属性,以便在GDB启动后对其进行设置,大致的配置文件如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) linux", "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/vmlinux", "miDebuggerServerAddress": "localhost:1234", "args": [], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerArgs": "-n", "targetArchitecture": "x64", "setupCommands": [ { "text": "set arch i386:x86-64:intel", "ignoreFailures": false }, { "text": "dir .", "ignoreFailures": false }, { "text": "add-auto-load-safe-path ./", "ignoreFailures": false }, { "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } 在添加配置后,直接在VSCode中设置断点,然后启动Qemu,最后在VSCode中启动调试即可。 其它配置 如果不仅需要使用VSCode对内核进行调试,还希望进行编辑,特别是激活IntelliSence以及格式化等功能,还需要对VSCode进行进一步的配置,具体配置已经置于GitHub仓库https://github.com/imaginezz/vscode_config_debug_kernel中,可以直接Clone为内核文件夹中的.vscode目录。
使用Qemu对Linux内核进行调试是一种较为便捷的方式,近日进行了一番实践,并将大致步骤与其中一些小坑记录了下来。 环境 由于放长假赋闲在家,所以手头只有一台装有MacOS的MBP可用,而Linux内核的开发与调试使用Linux环境下会比较方便,所以就使用VMware Fusion创建了一台安装有Ubuntu 18.04系统的虚拟机。由于编译Linux内核及相关软件需要的资源较多,所以为虚拟机配置了双核CPU、2GB内存和20GB磁盘空间(笔记本本身资源有限),但实际使用(特别是物理内存和硬盘)捉襟见肘,于是又在系统中添加了3GB的SWAP内存并扩容了20GB的磁盘空间(其实还是不太够)才解决问题。 编译Linux内核 首先,尝试对内核进行编译,在编译前需要使用通过KConfig启动内核的调试配置。 下载内核源码 由于Linux内核代码量非常大,且由于国内网络大家都懂的原因,所以的下载内核源码是一项较为复杂的体力活动。 第一种方法是直接Clone Linux源码的Git仓库,当前,其仓库大约为3.7GB。在通过内核官网(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/)或者GitHub(https://github.com/torvalds/linux)进行Clone的过程中,经常会遇到连接断开的情况,非常捉急。而如果通过国内镜像源,如清华Kernel Git镜像进行Clone的时候,最开始速度飞快,但是后面速度会越来越慢。因此,如果不像我这样头铁的话,不建议使用这样的方式下载Kernel的源码。 另一种较为简单的方式是下载特定版本的源码,这些源码的tarball包可以从内核官网或者镜像站获得。我在实验中使用的内核版本为4.19,gz压缩包的大小约为150MB。 配置内核 如果是使用Git Clone的方式获取的内核源码,需要通过git checkout v4.19将内核源码置位4.19版本。 在编译之前,首先需要安装相关的依赖(如果提示缺少其它依赖按需安装即可) 1 sudo apt install libncurses5-dev libssl-dev bison flex libelf-dev gcc make openssl libc6-dev 在编译之前,需要使用KConfig对内核编译选项进行配置,在内核文件夹下,使用make menuconfig(命令行界面)或make gconfig(基于gtk的图形化界面)对内核进行配置。在配置时,需要打开如下选项: 1 2 3 Kernel hacking -> Kernel debugging Kernel hacking -> KGDB:kernel debugger Kernel hacking -> Compile time checks and compiler options -> Provide GDB scripts for kernel debugging 并保证如下选项没有开启: 1 Kernel hacking -> Compile time checks and compiler options -> Reduce debugging information 在退出配置后,可以发现内核目录中生成了一个名为.config的配置文件。 编译内核 配置完成后,就可以使用make编译内核,在多核CPU中可以使用make -jx启动多线程编译(x为启动的线程数)。 如果一切正常,在漫长的等待后,内核将编译完成。编译会在内核根目录下生成vmlinux 文件,它是编译出的原始内核文件(含有调试信息),而会在arch/x86/boot/bzImage目录下生成压缩后的内核文件(当然是在编译的体系结构为x86的情况下)。 编译安装GDB和Qemu 由于内核调试所需的GDB和Qemu版本可能会比apt源中的版本高,所以,最好自行编译安装这些软件。 编译安装GDB 首先,从官网(http://www.gnu.org/software/gdb/download/)下载GDB的源码并解压(这里使用的是官网中最新的GDB 9.1),需要注意的是,网上有些博客中提到需要修改GDB的源码,其实是不必要的,报错的原因是没有自动检测到目标体系结构的类型,所以只需设置该类型即可。 解压后进入GDB文件夹,执行下列指令,即可完成编译安装: 1 2 3 4 5 mkdir build cd build ../configure make -j4 sudo make install 最后,通过使用gdb -v确定gdb的版本是否为9.1,如果是,则说明安装成功。 编译安装Qemu 首先,从官网下载(https://www.qemu.org/download/#source)Qemu的源码并解压(这里使用的是Qemu 5.0.0)。 由于在Ubuntu GUI中使用Qemu还需要多媒体图形库SDL,所以需要首先使用apt安装sdl: 1 sudo apt install libsdl2-2.0-0 libsdl2-dev libsdl2-gfx-1.0-0 libsdl2-gfx-dev libsdl2-image-2.0-0 libsdl2-image-dev 进入Qemu目录后,执行./configure检查系统配置并生成Makefile,需要注意检查的时候是否检测到了SDL的支持,其输出的部分内容如下所示: 1 2 3 4 5 6 7 8 profiler no static build no SDL support yes (2.0.8) SDL image support yes GTK support no GTK GL support no VTE support no TLS priority NORMAL 然后执行make && make install即可完成Qemu的编译与安装。 在安装完Qemu后,会生成如qemu-xxx和qemu-system-xxx的一系列命令,用于仿真不同体系结构的用户态应用和操作系统,可以通过如qemu-system-x86_64 --version命令确认Qemu是否安装成功。 制作ROOTFS 在内核启动后需要一个带有init程序的rootfs,所以在调试内核前需要制作一个rootfs。 构建基于initrd的rootfs initrd是一种位于内存的根文件系统,它可以在硬盘被驱动之前载入系统。这里为了方便,只将一个简单的程序写入initrd,并将其作为init程序(即系统启动后的第一个用户态进程)。除此之外,也可以使用busybox作为initrd中的init程序。 创建一下简单的c程序,命名为fakeinit.c。 1 2 3 4 5 6 7 8 9 10 11 #include <stdio> int main() { printf("hello world!"); printf("hello linux!"); printf("hello world!"); printf("hello linux!"); fflush(stdout); while(1); return 0; } 然后使用gcc编译这段代码,在编译的时候需要使用静态链接,并且如果如果在配置内核的时候没有启用64位支持(64-bit kernel),则需要将代码编译为32位程序,方法是在gcc命令行中添加-m32选项。 编译命令如下: 1 2 gcc --static -o fakeinit fakeinit.c gcc --static -o fakeinit fakeinit.c -m32 (编译为32位可执行程序) 在编译后,使用cpio程序进行打包: 1 echo fakeinit | cpio -o --format=newc > initrd_rootfs.img 这样,一个基于initrd的rootfs即制作完成。 构建基于硬盘镜像的rootfs 这里使用busybox构建基于硬盘镜像的rootfs。其中,busybox是一个集成了数百个Linux常用命令和工具的单个软件,在对内核进行测试的时候非常方便,号称“The Swiss Army Knife of Embedded Linux”。 下载编译busybox 首先,从官网(https://busybox.net/downloads/)下载busybox的源码并解压(这里使用的是最新的busybox-1.31.1)。 在解压并进入busybox文件夹后,首先使用make gconfig或make menuconfig对其进行配置,需要启用如下选项: 1 Settings -> Build Options -> Build static binary (no shared libs) 如果需要将其编译为32位版本,则需要将-m32命令填入如下选项: 1 2 Settings -> Build Options -> Additional CFLAGS Settings -> Build Options -> Additional LDFLAGS 与内核相同,在退出后,会在目录中生成一个名为.config的配置文件。 然后,使用make命令编译busybox。 使用busybox创建rootfs 首先,创建一个空的磁盘镜像文件,然后将其格式化: 1 2 dd if=/dev/zero of=./busybox_rootfs.img bs=1M count=10 mkfs.ext3 ./busybox_rootfs.img 然后,挂载刚刚创建的磁盘镜像(需要使用loop设备): 1 2 mkdir rootfs_mount sudo mount -t ext3 -o loop ./busybox_rootfs.img ./rootfs_mount 接着,在busybox源码目录中,将编译好的busybox目标文件安装到rootfs文件夹: 1 make install CONFIG_PREFIX=/path/to/rootfs_mount/ 最后,配置busybox的init,并卸载rootfs: 1 2 3 4 5 mkdir /path/to/rootfs_mount/proc mkdir /path/to/rootfs_mount/dev mkdir /path/to/rootfs_mount/etc cp busybox-source-code/examples/bootfloppy/* /path/to/rootfs_mount/etc/ sudo umount /path/to/rootfs_mount 现在,一个基于busybox的rootfs磁盘镜像就制作成功了。 使用Qemu和GDB调试内核 使用Qemu启动内核 由于编译的内核体系结构为x86,所以使用qemu-system-x86_64程序来载入并启动内核。 如果使用intird作为rootfs,则具体命令为: 1 2 3 4 5 6 qemu-system-x86_64 \ -kernel ./linux/arch/x86/boot/bzImage \ # 指定编译好的内核镜像 -initrd ./rootfs/initrd_rootfs.img \ # 指定rootfs -serial stdio \ #指定使用stdio作为输入输出 -append "root=/dev/ram rdinit=/fakeinit console=ttyS0 nokaslr" \ # 内核参数,指定使用initrd作为rootfs,禁止地址空间布局随机化 -s -S # 指定Qemu在启动时暂停并启动gdb server,等待gdb的连入(端口默认为1234) 如果使用磁盘镜像作为rootfs,则具体命令为: 1 2 3 4 5 6 qemu-system-x86_64 \ -kernel ./linux/arch/x86/boot/bzImage \ -hda ./rootfs/busybox_rootfs.img \ # 指定磁盘镜像 -serial stdio \ -append "root=/dev/sda console=ttyS0 nokaslr" \ # 内核参数,指定root磁盘,禁止地址空间布局随机化 -s -S 使用GDB调试内核 最后一步,由于刚刚Qemu开启了远程调试,所以只需要将gdb通过连入即可: 1 gdb ./linux/vmlinux # 指定调试文件为包含调试信息的内核文件 如果此时直接在gdb调试器中使用target remote:1234连入Qemu的gdb server,则会出现报错Remote ‘g’ packet reply is too long,这是由于gdb没有正确识别调试目标的体系结构造成的(有些博客认为需要修改源代码屏蔽这个错误,实际上是不必要的),所以只需要在远程attach之前使用set arch i386:x86-64:intel设置目标体系结构即可。 例如,你希望在start_kernel函数设置断点进行调试,则在启动Qemu后,gdb的命令如下: 1 2 3 4 5 6 gdb ~/linux/vmlinux (gdb) set arch i386:x86-64:intel (gdb) add-auto-load-safe-path ~/linux (gdb) target remote:1234 (gdb) b start_kernel (gdb) c 可以发现,内核在启动后被中断在start_kernel函数上。 后记 内核文档 在内核的文档中,有一篇详细讲解了如何使用GDB调试内核。 该文档的最新版本可见于内核的官网:https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html。 而具体的版本就需要在内核源码中编译文档了,例如html版本的文档可以使用make htmldocs进行编译,在启动HTTP服务器后,可以在浏览器中进行访问,例如,http://127.0.0.1:8000/dev-tools/gdb-kernel-debugging.html。 参考来源 本文参考了两篇较为优质的博客: 用QEMU来调试内核 – 亲身体验篇 利用VS Code+Qemu+GDB调试Linux内核
最近在项目中遇到需要在 NodeJS 中调用 C++代码的问题,在此略作总结。 主要方案 在 NodeJS 中,和其他语言编写的代码通信主要有两种方案: 使用 AddOn 技术,使用 C++为 NodeJS 编写一个拓展,然后在代码中调用其他语言所编写的源码 or 动态库 使用 FFI(Foreign Function Interface)技术,直接在 Node 中引入其他语言所编写的动态链接库 在对这两种方式进行比较后,发现这两种方式各有优劣。 首先,AddOn 技术比较通用,它可以使用 C++代码来拓展 Node 的行为,很多库都是使用这种方式来完成一些比较底层操作(比如和操作系统的一些通信)的。但是它写起来比较麻烦,要编写一个 C++项目,还要按照 NodeJS 的规范 export 相应的函数,而且每次安装的时候都需要进行编译(以适应本地 Node 的版本)。如果只是调用一个 DLL,那就还需要在项目里重新包装一遍 DLL 的接口。 如果使用 FFI 技术,限制就会比较多,首先,它只能调用其他动态库,如果你想使用 C/C++完成更多功能的话,还需要再封装一层 DLL,另外,它只支持_cdecl调用约定(也就是 DLL 在导出的时候一定要标记用_cdecl编译命令),不支持_stdcall或者_fastcall调用。但是调用起来就会很方便,可以直接在 JS 代码中声明 DLL 的接口就可以了。 综上比较,如果只调用第三方 DLL(而且恰好是_cdecl导出),使用 FFI 就再合适不过了(虽然性能可能会有一定的损失,而且调试起来会有困难)。 其实,从理论上来讲,FFI 也是基于 AddOn 技术的,只是它可以帮你把在 JS 中定义的接口直接转换成 C 语言的接口,并利用 NodeJS 的 Buffer 内存,将其同载入的 DLL 共享。当然由于 FFI 的这种通用性,也导致了一定的性能损失。 下面就以在 Windows 平台上使用 FFI 为例,简单聊一下如何使用 NodeJS 和 C++编译而成的 DLL 通信吧。 FFI 使用准备 安装 NodeJS 可能你的环境中已经有 NodeJS 了,但是,如果是最新版本,在安装 FFI 的时候会出现各种兼容性的问题(比如编译无法通过,虽然已经有人提供了 patch,但是还没有被 merge 进主分支,为了避免出现 bug,还是暂时不用为妙)。所以可以安装 LTS 版本代替。 另外,还需要注意要调用的 DLL 是 32 位还是 64 位的,Node 的版本需要和 DLL 的版本匹配。因为如果 64 位 Node 调用了 32 位的 DLL,是无法成功装载的,反之亦然。 安装 Windows 的 C++工具链 这里有两种方案: 安装 Visual Studio,并安装相应的工具链。如果使用 VS 2019 版本的话,需要安装 C++桌面开发和 Windows SDK 相关的工具(Node v10 现在只支持 v141 版本的 MSVC),这种方式便于后续的调试工作(虽然也很艰难) 在安装 Node 之后,使用管理员权限运行 Powershell,并全局安装 windows-build-tools,参考命令 npm install --global --production windows-build-tools 安装 node-gyp node-gyp 是一个 Node 中基于 gyp 的跨平台的编译工具,用于编译其他库。 在安装的时候,需要使用 VC 的工具链,所以如果没有把工具链放在全局变量中,需要打开 VS 的Developer Powershell安装,该命令行一般在开始菜单的 Visual Studio 文件夹中。 参考命令:npm install -g node-gyp 安装 FFI 及 REF 下面的步骤依旧需要 VC 工具链,所以可能依旧需要在Developer Powershell中执行(建议常备该窗口,后面只要涉及到编译安装的命令都需要用到)。 安装 FFI 及相关工具的时候如果没有 VC 工具链,则会直接安装二进制代码,这样可能会出现包的 ABI 版本和 NodeJS 的 ABI 版本不符合的情况(在下面的 Tips 中会提到)。 现在,切换到项目的文件夹中,安装下面的包。其中,ffi 包是用以支持 FFI 功能的,ref 包是用以支持指针功能(原理是通过 Node 的 Buffer 内存,将 JS 的结构和 C 结构相互转换的)的,ref-*是用以支持高级结构的(比如数组和结构体) 1 2 3 4 npm install ffi -s npm install ref -s npm install ref-array -s npm install ref-struct -s 除此之外,如果想支持 VC 中常见的 wchar 类型,还可以安装 ref-wchar 包。 安装 electron-rebuild 包 如果是 electron 项目,还推荐安装 electron-rebuild 包,该包可以遍历 node_modules 目录下的所有包,并将其重新编译。 然后,推荐在 package.json 中配置 electron-rebuild 的命令: 1 2 3 "scripts": { "rebuild": "./node_modules/.bin/electron-rebuild" } 之后执行在需要重新编译的时候只需要执行npm run rebuild即可。 使用方式 简单概览 可以查看如下官方示例: 1 2 3 4 5 6 var ffi = require('ffi') var libm = ffi.Library('libm', { ceil: ['double', ['double']] }) libm.ceil(1.5) // 2 在引入 FFI 后,使用 FFI 调用了 libm 库(可能这个示例只能在类 Unix 系统中使用),一般拓展名为 libm.so,系统会在系统目录下搜索这个动态库,并将它使用动态链接器载入到 node 进程中。 接着,程序声明了 libm 库中的一个方法 ceil(向上取整),其中,该函数的返回值是 double 类型(第一重数组中的 doule),而该函数的入参也是一个 double 类型的值(第二重数组中的 double)。 最后,直接使用libm.ceil方法即可调用动态库中的函数,并返回正确的值。 这只是一个 FFI 的简单用例,更复杂的用法(主要是异步调用和回调函数)可以参考 FFI 的实例页https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial。 类型 FFI 的类型系统其实记住了 ref 库的类型,ref 库的类型系统基于 NodeJS 的 Buffer 内存,可以根据 Buffe 中数据的类型对 Buffer 内存中的数据进行访问和修改。 ref 自带的数据类型都是基本类型,比如 int 类型、bool 类型或者 string 类型。所有类型可以参考 ref 的wiki。 ref 中的多数类型都有简写,比如ref.types.int可以简写为int。 需要注意的是,char*可以写为string,对应的 ref 类型为ref.types.CString。值得注意的是,string 在 JS 中是基本类型,在 C 中却是引用类型。 对于指针类型,ref 提供了一个方法ref.refType()来得到,比如int*类型就可以使用ref.refType('int')得到。当然,为了省事,也可以直接用int*表示。 而对指针解引用,ref 库也提供了一个deref()方法。只要在对应类型的变量上使用该方法,就可以得到指针指向内容的变量。比如一个指向int*类型的 JS 变量 a_pointer,那么我们如果想得到具体的整数值,就可以使用a_pointer.deref()方法。 相反的,如果想获取某个变量的地址,就需要对某个变量使用ref()方法。 需要注意掌握类型与变量值的区别,使用ref.types、ref.refType或者是下面会提到的ref_struct({...})获得的类型,而如果想获得某个类型的变量,有两个方法,一个是从 FFI 函数的返回值中获取,另一个是在 Buffer 中开辟一个空间,来存放类型为所获得类型的变量,下面会具体讲到。 如果需要在 NodJS 的 Buffer 中开辟长度为某个类型的空间,可以使用ref.alloc()函数,只要将类型名传入即可。比如,想开辟一个类型为 int 的内存,就可以使用ref.alloc('int')得到。 此外,还有以下几点需要注意: 如果开辟类型为字符串的内存,推荐使用方法 ref.allocCString,其参数为一个 JS 的字符串。因为 C 语言的字符串在末尾有一个\0标识符,所以用这个方法可以更安全地得到 C 字符串。 如果在 C 语言中值为 NULL,则在 JS 中对应的值为 ref.NULL。 如果遇到指针类型,可以统一用'void'或者ref.types.void表示。 如果要表示一个函数的指针,可以使用'pointer'表示。 对于复合类型,比如数组或者结构体,ref 库本身没有提供相应的支持,需要使用 ref-array 和 ref-struct 库来实现,具体可以参考这两个库的文档。 另外,对于 Windows API 中较为常见的宽字符 wchar 类型,也有一个基于 ref 的库 ref-wchar 进行支持。 最后,附上 ref 的文档http://tootallnate.github.io/ref/,具体的 API 都可以在这里进行查阅。 调用外部符号 假设我们有如下 C 代码(并把它写的复杂一些): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* main.c */ typedef struct t_s_t{ int a; char b; } t_s; __declspec(dllexport) int add_one(int a) { return a + 1; } __declspec(dllexport) void struct_test(t_s** t_s_p) { *t_s_p = (t_s *)malloc(sizeof(t_s)); (*t_s_p)->a = 1; (*t_s_p)->b = 'd'; } 上面的代码中,声明了一个结构体t_s,以及两个函数add_one和struct_test。其中,函数前面的__declspec标记表示声明该函数为导出函数。VC 默认导出 C 函数时是用_cdecl调用约定。 其中,add_one方法的作用显而易见,是将传入参数加一再返回。而struct_test函数的作用是先在堆上开辟一个大小为生面声明结构体的内存空间,然后将该内存空间的指针赋给传入的参数,并将该结构体赋值。(这里的代码其实不够严谨,没有进行内存回收,但这不是本文的重点,所以先不做讨论) 需要注意的是,如果是 C++代码,需要使用extern "C"标记导出,否则会因为符号修饰和调用约定的问题导致无法通过源代码中的符号找到该函数。 我们可以使用 VS 的Developer Powershell对上述源码进行编译: 1 2 cl /c main.c Link /dll main.obj 编译后将生成 main.dll,我们在后面会用到这个动态库。 针对上述 C 函数,我们有如下 JS 代码,并假设和 C 代码在同一文件夹下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /* index.js */ const ffi = require('ffi') const ref = require('ref') const ref_struct = require('ref-struct') const t_s = ref_struct({ a: ref.types.int, b: ref.types.char }) const t_s_ref = ref.refType(t_s) const test_ffi = ffi.Library(__dirname + '\\main', { add_one: ['int', ['int']], // aka 'add_one': [ref.types.int, ['int']], struct_test: ['void', ['pointer']] // aka 'struct_test': ['void', [t_s_ref]], }) const result = test_ffi.add_one(20) console.log(result) //21 t_s_p = ref.alloc(t_s_ref) test_ffi.struct_test(t_s_p) console.log(t_s_p) console.log(t_s_p.deref()) console.log(t_s_p.deref().deref()) //a->1, b->'d' 首先,在代码中声明了一个结构体t_s,和 C 语言中的t_s*类型相对应。然后,我们还得到了结构体t_s的一个引用t_s_ref,和 C 语言中的t_s**类型对应。为什么在 C 语言中都多了一层指针呢?其原因和前面的字符串一样。 然后,声明了 test_ffi 变量,它调用了 ffi.Library 方法,该方法返回了 JS 中 DLL 的句柄和函数的声明,通过该变量可以进行 DLL 的调用。该方法有两个参数,一个是动态库的名称(可以略去拓展名 dll),另一个就是描述 C 语言函数的符号及其参数的对象。 该列表在上面已经简单介绍过了,对象的 key 是函数名,value 是一个数组,数组中第一个元素为函数的返回值的类型,第二个元素为另一个数组,它里面包含了函数的入参的类型。这些类型就用到了上一节中介绍的基于 ref 包的类型系统,类型可以用字符串表示,也可以用代码表示,可以参考代码中 aka 的注释。 接下来,代码通过test_ffi.add_one调用了 C 语言动态库中的add_one函数,可以看出,调用的方式和 JS 中的函数并无二致。但是需要注意参数类型千万不能传错,特别要注意 C 语言的 string 类型和 JS 中的 string 类型不同,要按照前文提到的方法进行转换。 然后,代码使用 ref.alloc 方法为 t_s_ref 变量开辟了一个内存空间(注意这只是一个指针大小的空间,并非结构体大小),并将该地址赋给 t_s_p 变量,然后将该变量传递给 struct_test 函数。因为 t_s_p 是一个二重指针,所以需要解两次引用,才能得到结构体真实的值。 回调函数 回调函数可以使用ffi.Callback()函数声明,该函数的第一个参数为返回值,第二个参数为入参列表,第三个参数为真实回调函数的闭包。 比如一个回调函数的定义如下,该函数会得到用户名和 id,并返回动作是否执行成功: 1 typedef int(*callback)(int, const char*); 那么,在 ffi 中,就可以使用如下方式声明该回调函数: 1 2 3 4 const callback_function = ffi.Callback('int', ['int', 'string'], (id, username) => { // do something return 1 }) 在声明之后,需要将该回调函数做为参数传入某个函数: 1 2 3 4 5 6 test_ffi.set_a_callback(callback_function) // Make an extra reference to the callback pointer to avoid GC process.on('exit', function() { callback_function }) 特别需要注意的是,设置完回调函数以后一定要保证该函数在 JS 中还存在一个引用(比如上面讲该函数的一个引用放在了 NodeJS 的 exit 事件中,这也是比较经典的做法)。否则,该函数将会被 NodeJS 的 GC 析构。其表现是:在程序刚开始执行的时候一切正常,但是执行了一会儿之后在调用这个回调函数,程序就会异常退出。如果用 VS 去对程序 Debug,就会发现该程序可能访问了非法指针,这是因为 DLL 代码中也存放了该回调函数的指针,但是在 JS 中该指针指向的地址因为没有被 JS 中的代码引用,所以被 CG 被释放,这样 DLL 中代码调用该地址的函数的时候,就会访问到非法的内存。 一些 Tips DLL 的调试方法 在使用 ffi 的过程中,可能发现最大的问题就是程序难以调试。特别在面对 DLL 的时候,就像针对一个黑盒操作一样,虽然已经对着头文件将他的 API 使用 FFI 翻译为 JS 的代码,但还是难以确定传参或返回值是否正确,在 C++的代码中该参数是否正确传入,传入后是否正确执行等等。这就需要一个能够调试的方法。 一个比较好用的方式是使用宇宙第一 IDE Visual Studio 的 Attach(附加)到进程的方式进行调试。但这种调试方法的前提是手中有 DLL 的源码或 PDB(符号)文件(如果没有的话就只能看到出现异常的代码附近的反汇编的代码了,而通常这些异常都是内存错误引起的,其实它附近的数据可能没有多大的意义)。 如果手上有源文件,那么首先打开工程,然后在 NodeJS 载入 DLL 之后,就可以在启动工程的时候选择“附加到进程”,在对话框中选择 NodeJS 进程即可进入调试界面。在调试界面里,可以插入断点,也可以看到断点附近的内存。 如果手上没有源文件,但是有 PDB 文件(或者少量的源码),可以使用 VS 打开一个空工程,然后在调试的设置中添加符号文件的位置,这样也可以进行断点调试,在调试的过程中可以查看代码有没有命中断点,在命中断点时,会引导你载入项目文件,如果有的话可以选择,否则可以查看断点附近的反汇编代码。 具体的调试方法可以参考 MSDN 文档:https://docs.microsoft.com/en-us/visualstudio/debugger/attach-to-running-processes-with-the-visual-studio-debugger?view=vs-2019,在这里就不过多叙述了。 如何载入在其他文件夹中的 DLL 如果 JS 文件和 DLL 文件不在同一文件夹中,可能会出现载入失败,会出现类似于“Dynamic Linking Error: Win32 error 126”的错误提示。 这时,就需要将 DLL 文件夹的路径放在系统寻找动态链接库的 PATH 中,但是 FFI 并没有提供此类接口。不过,好在 Windows API 提供了 SetDllDirectoryA 这个接口用以切换该进程中寻找 DLL 的 PATH,可以使用如下代码完成这个操作: 1 2 3 4 const kernel32_ffi = ffi.Library('kernel32', { SetDllDirectoryA: ['bool', ['string']] }) kernel32_ffi.SetDllDirectoryA(your_custom_dll_directory) 一些链接时候的错误提示 上面已经提到,如果动态库不在 PATH 中的话,会出现无法找到动态库的情况,这时候会报“Dynamic Linking Error: Win32 error 126”的错误,在另外一些时候,只要没有找到动态库,都会报该错误。如果出现该错误,就需要检查动态库的名称是否正确,检查动态库的版本是否正确(比如 32 位 Node 使用了 64 位的 DLL)等。 另外,还有一个“Dynamic Linking Error: Win32 error 127”错误比较常见,该错误指没有在 DLL 中找到对应的符号,这可能就需要检查在 ffi 中声明的函数名是否正确以及是否 DLL 版本有偏差了。 在 Electron 中使用 FFI 由于每一个 Electron 的版本是基于相应的 Node 和 Chrome 版本构建的,所以在使用 FFI 之前需要根据所使用的 Electron 版本安装本地的 NodeJS 版本,否则 FFI 可能会和 Node 版本不匹配,导致提示 ABI 版本不一致:xx was compiled against a different Node.js version using NODE_MODULE_VERSION x. This version of Node.js requires NODE_MODULE_VERSION xx(NodeJS 使用 NODE_MODULE_VERSION 来辨别 ABI 版本)。 这种情况下,可以使用前文提到的electron-rebuild对项目中的所有插件进行重新编译(需要注意本地 NodeJS 的 ABI 版本一定要和 Electron 中 NodeJS 的 ABI 版本一致)。 另外,需要注意的是,Electron 5 以上的版本使用了 NodeJS 12 的 ABI,但是当前的 Ref 库并不支持该 ABI,会导致编译失败。不过已经有人提交了 pull request 进行修复,相信之后会有一个可用的版本出来。 另外,也有了 NAPI 版本的 FFI 和 Ref,分别名为ffi-napi和ref-napi,和 ref 相关的包,比如 array 和 struct 拓展,也有了相应的 NAPI 版本,命名规则同上。使用 NAPI 的 Node C++ 拓展接口相对稳定,是今后的趋势。 最后,Electron 版本可以在https://electronjs.org/releases/stable中查看,而 NodeJS 及其 ABI 的版本可以在https://nodejs.org/en/download/releases/中查看。 一些资源 最后,在这里放上最近踩坑时候经常使用到的一些资源吧: ref 文档:http://tootallnate.github.io/ref/ ffi 文档:https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial 基于 ffi 的 win32 api:https://github.com/waitingsong/node-win32-api v2ex 中的 node-ffi 食用指南(难吃):https://www.v2ex.com/amp/t/474611
引用是C++相对于C而引入的一个重要特性,它使得许多地方的语法变得更加简洁,但是它们的底层究竟是怎么实现的呢? 在Wikipedia中,对指针有如下介绍: In computer science, a pointer is a programming language object that stores the memory address of another value located in computer memory. A pointer references a location in memory, and obtaining the value stored at that location is known as dereferencing the pointer. 从定义可以看出,指针从本质上来讲就是一个变量,其中所存储的就是其他变量的地址。 而C语言中的指针非常灵活,它可以任意指向某一个地址,不论这个地址究竟是否存在,或它究竟存储的是否为指针所代表类型的数据。 那么也不难想到,指针在实现的时候也是内存里的一个变量,它存有其他变量的地址。 在Wikipedia中,对引用有如下介绍: In computer science, a reference is a value that enables a program to indirectly access a particular datum, such as a variable’s value or a record, in the computer’s memory or in some other storage device. The reference is said to refer to the datum, and accessing the datum is called dereferencing the reference. In the C++ programming language, a reference is a simple reference datatype that is less powerful but safer than the pointer type inherited from C. The name C++ reference may cause confusion, as in computer science a reference is a general concept datatype, with pointers and C++ references being specific reference datatype implementations. The definition of a reference in C++ is such that it does not need to exist. It can be implemented as a new name for an existing object (similar to rename keyword in Ada). 从上面的定义可以看出,在C++中,引用可以狭义地认为是某一个变量的别名,它本身是并不存在的。 基于以上说法,我就一度认为引用只是C++编译器在编译时的一些黑魔法,它在运行的时候将两个解析到的符号链接成了一个,从而完成了引用,而在编译之后,引用与本体就是一个变量(一个寄存器或栈上的值)。 但是,事实却打了我的脸。 我们通过以下程序进行检验: 1 2 3 4 5 6 7 8 int main() { int a = 0; int *pa = &a; int &ra = a; ++(*pa); ++ra; } 该程序声明了一个变量a,然后分别声明指针pa指向a的地址,生命引用ra指向a,最后分别使用指针和地址对a进行了一次自加操作。 接下来,使用gcc -S test.cpp -o test.s -O0将上面的C++程序编译为汇编,看一下这些操作具体都是怎么实现的。(环境为MacOS,LLVM 10.0.1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 .section__TEXT,__text,regular,pure_instructions .build_version macos, 10, 14sdk_version 10, 14 .globl_main ## -- Begin function main .p2align4, 0x90 _main: ## @main .cfi_startproc ## %bb.0: ; 保护现场 pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 ; 保存栈指针 movq%rsp, %rbp .cfi_def_cfa_register %rbp ; 设置返回值为0 xorl%eax, %eax ; int a = 0 movl$0, -4(%rbp) ; t0 = &a leaq-4(%rbp), %rcx ; int *pa = t0 movq%rcx, -16(%rbp) ; int &ra = t0 movq%rcx, -24(%rbp) ; t0 = pa movq-16(%rbp), %rcx ; t1 = *t0 = *pa movl(%rcx), %edx ; ++t1 addl$1, %edx ; *t0 = *pa = t1 movl%edx, (%rcx) ; t0 = &ra = &a movq-24(%rbp), %rcx ; t1 = *t0 = *(&ra) = ra = a movl(%rcx), %edx ; ++t1 addl$1, %edx ; *t0 = *(&ra) = ra = a = t1 movl%edx, (%rcx) ; 恢复现场 popq%rbp ; 返回 retq .cfi_endproc ## -- End function .subsections_via_symbols 我已经将汇编中的关键代码加上了注释,可以看出,变量a,指针pa,以及引用ra都位于栈上,index分别在-4、-16、-24。需要注意的是,引用并不是直接复用了变量a的-4(%rbp)地址,而是像指针一样,使用了一个新地址,并且将leaq计算得到的a的地址写入了其中。 而在进行自加的时候,除了最开始将指针中的内容拷贝到寄存器中所用的地址不同以外,指针和引用所使用的方式是完全相同的。 这个结果令我非常意外,编译器其实是将开发者对引用的操作翻译成了对指针的操作。 最后,发现现代编译器还是很聪明的,如果将优化级别调到更高,就会发现它直接将中间的计算过程全部简化,直接返回,这是因为计算结果并没有任何输出,它是不必要的。如果将上面的代码从main函数转移到其他函数中,编译器这时虽然不能放弃计算其中的数值,但还是做了尽力的优化,直接返回结果(movl $2, %eax)。 进一步的实验 在文章发出后,有同学提出了质疑,认为可能只是MacOS上gcc编译器的特定操作,并不具有普适性,所以我在Linux和Windows上重复了上述实验。 在Linux环境中(发行版为Ubuntu 18.04,gcc版本为7.5.0)使用相同指令编译后得到的汇编码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 .file"test_ref.cpp" .text .globlmain .typemain, @function main: .LFB0: .cfi_startproc ; 保护现场 pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 ; 保存栈指针 movq%rsp, %rbp .cfi_def_cfa_register 6 subq$32, %rsp movq%fs:40, %rax movq%rax, -8(%rbp) ; 设置返回值为0 xorl%eax, %eax ; int a = 0 movl$0, -28(%rbp) ; t0 = &a leaq-28(%rbp), %rax ; int *pa = t0 movq%rax, -24(%rbp) ; t0 = &a leaq-28(%rbp), %rax ; int &ra = t0 movq%rax, -16(%rbp) ; t0 = pa movq-24(%rbp), %rax ; t1 = *t0 = *pa movl(%rax), %eax ; t2 = *t0 + 1 leal1(%rax), %edx ; t0 = pa movq-24(%rbp), %rax ; *t0 = *pa = t2 = *t0 + 1 movl%edx, (%rax) ; t0 = ra movq-16(%rbp), %rax ; t1 = *t0 = *pa movl(%rax), %eax ; t2 = *t0 + 1 leal1(%rax), %edx ; t0 = ra movq-16(%rbp), %rax ; *t0 = *ra = t2 = *t0 + 1 movl%edx, (%rax) ; 返回值设置为0 movl$0, %eax movq-8(%rbp), %rcx xorq%fs:40, %rcx je.L3 call__stack_chk_fail@PLT .L3: leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .sizemain, .-main .ident"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0" .section.note.GNU-stack,"",@progbits 可以看出与MacOS中gcc的编译结果基本相同。 在Windows环境中(Windows 10,vs2019,cl版本为19.23.28106.4),可以使用命令cl /Od /FA .\test_ref.cpp对源码进行编译,可以得到汇编代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 ; Listing generated by Microsoft (R) Optimizing Compiler Version 19.23.28106.4 TITLEC:\Users\jason\test\test_ref.cpp .686P .XMM include listing.inc .modelflat INCLUDELIB LIBCMT INCLUDELIB OLDNAMES PUBLIC_main ; Function compile flags: /Odtp _TEXTSEGMENT _ra$ = -12; size = 4 _pa$ = -8; size = 4 _a$ = -4; size = 4 _mainPROC ; File C:\Users\jason\test\test_ref.cpp ; Line 2 pushebp movebp, esp subesp, 12; 0000000cH ; Line 3 movDWORD PTR _a$[ebp], 0 ; Line 4 leaeax, DWORD PTR _a$[ebp] movDWORD PTR _pa$[ebp], eax ; Line 5 leaecx, DWORD PTR _a$[ebp] movDWORD PTR _ra$[ebp], ecx ; Line 6 movedx, DWORD PTR _pa$[ebp] moveax, DWORD PTR [edx] addeax, 1 movecx, DWORD PTR _pa$[ebp] movDWORD PTR [ecx], eax ; Line 7 movedx, DWORD PTR _ra$[ebp] moveax, DWORD PTR [edx] addeax, 1 movecx, DWORD PTR _ra$[ebp] movDWORD PTR [ecx], eax ; Line 8 xoreax, eax movesp, ebp popebp ret0 _mainENDP _TEXTENDS END cl编译器的汇编代码格式和gcc略有不同,但含义相近,并且可以比较轻易地通过上面标出的代码行数确定汇编代码的含义。可以看出,它使用的方法也和前两种相同。 这里再进一步,引入知乎上“XZiar”同学的评论,她的评论更加深入地理解其中的机制: 其实不是说“把引用解释成指针”吧。 在机器码层面,也不存在指针,只存在地址(指针其实还隐含了类型信息)。变量这个概念也是不存在的,只有“无格式数据”,被带格式的指令操作而已。 所以你看到引用和指针的效果一样,是因为在机器码层面,没有多余的信息去表明他们的区别了。 而在语言层面,引用的确可以理解为const指针 另外,她针对为什么汇编代码中引用把地址复制了一遍也进行了更深入的解释: 另外引用把地址复制一遍也是很正常的,编译器也的确没法在编译期完全分析出引用的具体指向。考虑如下代码: int a=0,b=1; int& c = flag ? a : b; 引用只不过因为const所以不能被重置,但具体指向什么,是可以运行期决定的。 到这里,对于指针和引用底层实现的探索也基本结束了,可以看出,在不启用编译器优化的情况下,主流编译器都会选择将C++中的引用解释为“const指针”。 但是,如果在启动编译器优化的情况下会是如何呢?在MacOS中,将源代码中的返回值改为a后(为了防止编译器优化后认为没有输出于是什么都不做),同时将编译器优化选项调整为O1和O2,其结果是相同的,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .section__TEXT,__text,regular,pure_instructions .build_version macos, 10, 15sdk_version 10, 15, 4 .globl_main ## -- Begin function main .p2align4, 0x90 _main: ## @main .cfi_startproc ## %bb.0: pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq%rsp, %rbp .cfi_def_cfa_register %rbp movl$2, %eax popq%rbp retq .cfi_endproc ## -- End function 可以看出汇编版本的代码中省略了所有和指针、引用及内存操作相关的代码,直接将返回值设置为2。 从这里可以看出,编译器的作用是将语言编写的代码翻译为合理的汇编代码,只要汇编代码可以源代码的真实意图执行即可。由于机器码可以表达的概念有限(基本上就是对于寄存器和内存的运算),而高级语言可以表达的概念十分多样,所以编译器就需要将高级语言中的各种复杂概念映射(也可以看做是翻译)为机器码中的简单概念,映射的过程可能会有多种方案,其最终选择是由编译器来决定的。在C++指针和引用的翻译中,主流的C++编译器都选择将它们映射为机器码中的地址,而舍弃了其中的类型信息。
自昨天下午看完《流浪地球》以后,内心久久难以平静。闭上眼睛后,大脑中出现的是流浪地球的主题曲《带着地球去流浪》和地球飞临木星时候大气交汇时那种磅礴而又难以用语言形容的场景。 这首歌里,一面是曹操的《观沧海》,描述了人类面对这个世界所发出的赞叹,描述出了大好河山的雄伟壮丽。而另一方面,又进行了古今对比,更体现出这场星际旅行的悲凉与无奈。伴着这首歌,我就知道必须要写些什么了。 (如果不能播放可以点击[这里](http://music.163.com/#/m/song?id=1341939931)跳入网易云音乐播放) 回忆 不知不觉,我进入了一段回忆,我可能是在想我是如何与科幻结缘,我又是对《流浪地球》有着怎样的期盼。 第一次开始对太空和空间科学感兴趣,是源于小学时候在 Windows98 电脑中的一个太空主题。我的印象很深,它的屏幕背景是由一个宇航员和一个太空站构成。桌面上的图标都被改成了星球的样子,而屏幕保护更是让宇航员和太空站动了起来。我在网上找到了一个动图: 每次从老旧的 CRT 显示器中看到这个桌面,我就仿佛置身于浩瀚缥缈的宇宙之中,一面是我所向往的科学殿堂,另一面是置身于宇宙中的无穷之美。 之后,大概是很多年之后,我逐渐对物理学有了很深的兴趣,痴迷于《果壳中的宇宙》和《时间简史》中描述的宇宙的本质、宇宙的演变和宇宙的图景。 另一方面,我也开始对科幻电影感兴趣,看了许多有有关于宇宙的科幻的电影:《星际穿越》、《星际迷航》、《地心引力》、《月球》、《火星救援》、等等等等。《星际穿越》中那个空旷无比的全景宇宙,以及在宇宙中硕大无比(像 IE 图标一样)的黑洞,一再地震撼着我的心灵。 再之后,就是接触到了《三体》的时候,那种激动到在期中考试前一天晚上彻夜阅读的心情,到现在都难以忘记。在看完三体以后,大呼过瘾,所以我又接二连三地去读大刘的其他作品。《球星闪电》、《流浪地球》、《乡村教师》、《赡养上帝》,这些都是读过就很难忘记的好作品。 而在前几年我又听到《三体》已经开机的声音,当时的心情是忐忑大于期望,我很担心这么宏伟的一个科幻世界被电影给毁掉,都不如 B 站 UP 主文曰小强用别的电影剪辑出的《速读三体》耐看。好在最后电影流产,我长舒一口气。 同样,在去年夏天首次听到《流浪地球》即将上映的时候我又是捏了一把汗。直到去年下半年来,随着电影的预告片一部一部地放出,以及看过电影的人对电影的评价都趋于正面,而且还有大刘作为监制,吴京也宣布加盟,我的心态逐渐从揪心转为期待。 随着主题曲《带着地球去流浪》及 其 MV 的放出,我对电影的画面彻底放心了,唯一的悬念就是剧情。 说回电影 回忆告一段落,现在就分几个维度来说说我对电影的感受。 视觉效果 从预告片到正片里的几乎所有视觉效果,都完完全全地震撼到了我,放眼其它国产片的特效,和《流浪地球》相比,无出其右。在我的感观中,电影已经达到了好莱坞的水准。 特别是在电影中看见地球的大气被木星捕获时候的画面。一方面,地球发动机喷射出淡蓝色的粒子流,另一方面,地球的大气又和木星的大气进行了交融。那时我才真的感受到木星和地球体积的对比。我想,站在地球上,看见如此庞大的一个星球完全占据了整个天空,那种压抑感、对无尽和死亡的恐惧以及在得知地球即将解体的消息后的无助是难以言表的,只借助用这样的画面呈现出来。 另一个让我感到很震撼的画面是电影开始时候,镜头从车队的特写一直向上拉动,到了华北平原,又到了行星发动机,最后到了宇宙上空的领航者号空间站。每一个画面中都充斥着那种苏联式的巨型机械感,让我非常兴奋。 其实这里的画面又让我有了一些游戏感,很像《红色警戒》2 或者 3 中雪天场景中的苏联。有一阵儿对红警入迷很深,做梦梦到的都是里面的场景,和电影中的十分相似。所以看到这里,让我十分惊诧,没想到梦中的画面居然在这部电影中被看到了。特别地,在最后刘启赶往苏拉威西转向发动机的时候,他的车子开入一个底座,然后底座开始旋转,这不就是红警 3 中的矿车嘛! 剧情 不得不说,大刘给流浪地球所设定的如此宏大的世界观给了电影很大的发挥空间。电影中的这一段地木相会在书中花了可能不到一页的笔墨。但是通过合理的剧情拓展,已经可以撑得起一部电影了(甚至还因为控制时间被剪辑掉了一部分)。 可能就是受到剪辑的影响,电影前一小段的剧情进展太快,很多事情都没有交代。比如刘启为何要离家出走,妹妹为何也想出去看看,这些年为何父子产生了这么大的矛盾,都没有交代清楚。有些剧情虽然可以脑补,但是多少会让人在现场的时候感到疑惑或者是突兀。不过,从他们兄妹两人踏出地下城电梯门的那一刹那,一切节奏都恢复了正常。 而剧情中令人印象最深的不是抢救火种,不是太空行走,也不是重启发动机,而是运载车启动时不断重复的那几句话:“道路千万条,安全第一条;行车不规范,亲人两行泪。”由于其严重的不押韵,一度让我听得百爪挠心。但是随着剧情的推进,这句话也逐渐融入了更多的情感,特别是在“姥爷”变成“冰雕”之后,在听见这句话,已经是对“老东西”的那种怀念。 另外,在电影最后还给了一个很大的彩蛋。透过主角的视角,可以看见地下城中有人喊着“还我阳光”的口号在进行游行。我立刻就想到原著中反派认为流浪地球计划是一个谎言,于是发生暴乱,占领了地球发动机的中控室,并将流浪地球计划的领导人都执行了最冷酷的“冻刑”。这是不是说明之后还会有《流浪地球 2》上映呢,内心又燃起了很强的期待。 一些有趣的点 整场电影都的基调都非常刘慈欣,在电影中,集体主义和生存主义被体现的淋漓尽致。比如,在地球停止自转后,地球上的人口锐减了一半(这不就是灭霸嘛),而进入地下城也需要抽签决定,如果没有抽到签,在地表的冰天雪地中能够存活下来的人怕是寥寥无几。但是电影有正当的理由——为了更多人,为了整个人类的生存,必须这样做。这种做法对于自由、平等、博爱所建立的西方价值观是一个颠覆,许多人怕是也无法接受这样的设定。 是的,我们会为了大多数人以及我们的子孙后代牺牲另一部分人。但是,如果按照好莱坞的套路,最后不会选择带着地球离开,而是带着人类文明的种子(也就是火种计划)离开。这样,活下来的可能就不是地球上的数万万人,而是领航者号空间站上的这些人。这样就人人平等了么?文明得以延续的基础是整个人类的大社会。如果只剩下空间站上的这些人,在他们离开的那一天,可能就已经不是人类了。 第二个是真实,非常的真实。在救援队试图借助电梯从峡谷中向上方运送火石的时候,一个队员为了救爷爷,被掉下的电梯活活砸死,而爷爷却也在被救后的几分钟内,因为衣服漏气,导致氧气消耗殆尽,最后自行解开头盔,成为了一座冰雕。队员的死并没有换来爷爷的活,而爷爷的死也并没有激发刘启“拯救人类”的斗志。这样的例子还有很多,比如随处可见送火种的人成为了冰雕。他们的死亡都几乎是没有任何意义的。电影脱离了必须要给死亡安上一个意义的俗套,也没有了失去了什么必定会得到什么的“真理”,让观影体验更加的真实。 另一个有意思的地方是“饱和式救援”。着同样也超越了西方模式化的超级英雄电影。 在那一类的电影中,一定是主角与家人不和,然后出走,之后是众人皆醉我独醒,发现了一个惊天的秘密,但大家并不理解他,除了他的几个好朋友。于是他叫上了他的好朋友,开始闯关似的拯救世界。最后,他凭借一己之力,扭转了局势,拯救了世界,并且还一不小心收获了爱情。 但是在人类生死存亡的关键时刻,哪有人敢怠慢。行星发动机的是全人类最后的希望,必须全力以赴。所以人类派出了远远超过发动机数量的团队,征召了所有可用的力量,来修复每个行星发动机。比如在主角们赶向苏拉威西发动机的时候,就已经有人提前到达,并成功重启了发动机;而主角们想到要点燃火星的时候,也被告知以色列的团队也已经早在几个小时前想到了这个方案,等等。这才是认了里面对灾难应该有的状态,而不是靠着主角团一路开挂,就拯救了全人类。英雄们是存在的,但不只有一个。 一些比较吐槽的点 首先,情节太过单薄的问题已经在上面说过了,这可能是因为剪辑的原因,希望以后可以看到完整版的电影。 其次就是最后用木星爆炸的冲击力来推离地球的设定,让我突然有一种“这个地方有点假吧”的出戏感。因为大概想一想,靠化学能量的非定向爆炸来将地球从如此大的一个引力场推走,是一个多么不靠谱的决定。不过科幻也总是包含很多幻想的成分,在没有更好的方案的情况下,我表示理解。 另一个是主题升华的问题。电影中一直表现出的是父亲对孩子的爱,但是却没有升华到对于地球上的人类,以及对于子孙后代的爱上。这样很容易让人感到父亲开着空间站去点燃木星是因为要救他的孩子,顺便拯救了全人类。这样就让我去思考到父亲在空间站中的决策是自私还是博爱,以牺牲“火种计划”为代价来换取概率几乎为 0 的成功是否值得。 最后 总的来说,《流浪地球》给中国科幻电影做了一个好的榜样,希望以后可以有更多类似的好电影上映。 “啊,地球,我的流浪地球啊!”
Intro 倏忽之间,又过去了一年。是时候向 2018 说一声再见了。 今年,我只在写 年关随笔 时立了一个 Flag,要练习深度写作,至少每个月输出一篇文章。可惜因为暑假时候偷懒,这个 Flag 也并没有达成。不过,我还是要在 2019 重新立起这样一个 Flag —— 2019 年每个月至少输出一篇文章,可以是技术,可以是随笔,也可以是观点。希望明年这个时候再回来看,不要像今年一样打脸。 再说回 2018。这一年相比前些年,在我的努力下,节奏逐渐慢了下来,让我有了许多时间静下心来思考、做事,也有时间和爱人相处、与朋友侃大山(至少在十一月之前是这样的)。同时,这一年又并不像以往那么顺遂,让我明白了人力有限,世间有太多力所不能及之事。 时光·记忆 在大自然面前,没有什么是能够永恒的。人不能够,事不能够,城也不能够,甚至文明也不能够。 在奶奶走后,我每每做梦都会回到小时候,梦见亲人们都在,他们都能和谐相处;梦见家乡没有衰败,还有很多人居住;梦见我很强,能够办到一切事情。 可惜梦中和现实却每每都是相反的。我无力改变太多,但至少可以做些什么。眼下能做的事情,就是把家乡的景,家乡的事儿多记录一些,等多年后,这里变成一片荒山野岭,无人问津的时候,人们还是能从我只字片语的记录之中,知道有这样一个地方,曾经有这样一批人,为了祖国的建设,把最好的青春和无尽的才华都奉献在这西北的茫茫荒山之中。 我在这里贴上几张年初所拍的照片吧。 这两张是白银市老城区白天和晚上的景观,拍摄于白银市人民医院综合楼楼顶。白银市从今年开始增加了许多城市亮化的工程,在白天看来就是一个暮气沉沉的工业老城,但是在晚上看来尤其漂亮。 下面的照片均是在西北铜加工厂(884)社区拍摄。 这是 884 附近的一座山,山上有一块形状酷似像狮子的石头,所以小时候我们都管这个山叫做“狮子山”。不过可能由于石头连年风化,已经随时会滚落下来,才有人在山脚下立了块标识危险牌子。 这是一条通往旁边村子的路,货车多半可能是准备进入大山深处,去拉从山中开采的石灰石。水泥路已经被一辆辆超载的大货车压得几乎没有了。 这是西北铜加工厂的正门(有两个,这是其一),在当年国企兴旺之时,上下班的时候人们进进出出摩肩接踵,但是现在物是人非,已经看不到什么人进出厂区的大门了。 这是通向厂门的桥,当年为了让职工可以在发洪水的时候安全上下班,就修了两座连通厂区和家属区的桥。桥下还有一个足球场,在发洪水的时候用作泄洪(我们称为“沙沟”),这两座桥似乎一个名为“兴旺桥”,一个名为“振兴桥”,但是年久失修,现在已经是两座危桥了。 这是奶奶之前工作过的食堂,由于年久失修,们都已经被水泥封了起来。透过门缝,还能看见火红的标语,一刹那间仿佛又回到了当年。 这是西铜的职工浴池,我们都叫大澡堂。前几年浴池终于也关闭了,我只能透过蒙着厚厚灰尘的玻璃撇入浴室的一角,找到当年的回忆。 这是当年“达利居”饭庄(之后改名为西铜宾馆)的舞厅。据说西铜鼎盛的时期,会有许多年轻的男男女女穿梭在其中。不过我记事的时候已经没有什么人了。 这是一座无名小山上凉亭的一角。当时厂里希望把这座山修成一座公园,于是修整了山道,修建了几座凉亭。不过还没修好,就已经开始亏损,这座公园也就只剩下这两个凉亭了。 以上都是我在西铜或者说是白银记忆的一个小角落,还有大量我的、同学的、长辈们的记忆有待挖掘。现在的西铜,已经破败不堪。一眼望去,只有一座座废弃的建筑和一望无际的荒山。一路走去,已经很少能碰见朝气蓬勃的年轻人,只有沟壑纵横的长辈们。这座城因他们而生,也注定会随着他们离去而消失殆尽。所以,我能做的,就是尽量用图片和文字来保留着座终将消失的城,留住他们珍贵的记忆。 出游 相比前几年,今年出行的地方没有那么多,但是每个地方都很值得好好留念。正所谓——世界很大,不能只看去了哪里,还要看是和谁一起去的。 苏州 2018 年出行的第一站是苏州,这也是我走过得祖国河山中最喜欢的地方之一。苏州不仅有数不尽的美丽景致,更有独特且很有内涵的地方文化,更有令人适宜的生活环境。更重要的呐,这还是和卤蛋同学第一次一起出远门旅行。刚好还欠卤蛋同学一个游记,就先在这里简要地写一下好了。 苏州城区不大,只靠地铁和公交在一个小时之内都可以到达。但是在城内逛游的时候真的可以感受到就像它的园林一般的一步一景。走着走着,就会出现一座很漂亮的古桥,又向前走两步,就到了一个小有名气的私家园林,再向前一看,发现这不是一家百年老字号嘛(快快快去吃好吃的别看了)。 另外,苏州话也非常有特色。虽然我听不懂,但是可以感到说话的人都还挺温柔的(就算骂人都骂的很柔),在苏州评弹里就更能体现了,每一个曲调都很柔美,让它所表达的故事格外的柔美。当地人也都十分热心,问路时候可以感受到他们十分有耐心,说话比较慢,令人舒适感倍增。当然,这也从侧面体现了他们生活比较安逸,节奏比较慢。 下面,我贴几张比较有意思的照片好了。 这是刚到苏州时候,沿着一条街向虎丘走,卤蛋同学在街边特别激动,左拍拍右拍拍。我刚好留意到他们街头布置得很细心,很能为街道环境和有宠物的人着想,在墙边设立了宠物便纸箱。 街角一转,居然到了到了河边。河边灰白色的天空衬着灰白色的墙,倒映在水里,十分宁静。这时,船夫划着一艘游船从远处徐徐开来,扰乱了湖水,也令人在心中荡起了浅浅的涟漪。 虎丘山中的一个沟壑。虽然小,但也有了一种悬崖峭壁的感觉,沟底的水中映出来天上的雨滴,植物颜色十分撞眼,犹如通往世外桃源的一条幽径。 市内的一座不知名公园。远望时感觉画面十分饱满,而近观又感到场景十分开阔,眼睛都舍不得多眨一下,生怕错过哪一处的美景。 苏州评弹剧场内部。老师底蕴深厚,唱腔优美。很多字发的都是古音,所以需要有一块屏幕向观众展示唱词。每唱一曲都会一下子把我带入古代天堂般繁华的苏州,置身闹市之中,但又让我进入心流。 黄天源的苏州小吃。如果苏州糕点说自己第二好吃,怕是没有什么地方敢说自己是第一了。不过最让我惊喜的是苏面,这是我除了牛肉面以外第二个感到能百吃不厌的面了。 除了这些,在苏州还有许多有趣的见闻,而且都还有许多没有去到的地方。以后可以有缘再见咯。 天津 在暑假的时候去了一趟天津。我在这次旅行中得到了一个惊人结论——天津和北京一样热。所以以后夏天还是老老实实待在实验室吹空调好了。 即使这么热,天津之眼还是要排很久的队,所以就随手拍了张照。 天津之行的时候还是我和卤蛋同学在一起一周年的纪念日,于是——再回来以后我受宠若惊呆若木鸡地收到了卤蛋同学送的花(hmmm 这应该也是人生中的第一束花),拿到花以后我甚至不知道该把它放在哪里,是不是应该把它插到水桶里养起来。 灵山 高中地理曾经学过——海拔每升高 100 米,气温下降 0.6 摄氏度。这大概是我在灵山之行中最直观的感受了。灵山是北京第一高峰,在门头沟区,几乎已经到了北京同河北的交界处,需要驱车三个小时才能赶到。 灵山是同潘老师、林博以及卤蛋同学我们一行四人去的。去爬山的时候天还是挺热的,所以我带了一件坛服去御寒。刚开始爬的时候我还很跳腾,但是到了一半左右,山势变陡,狂风大作,气温骤降。已经冷到了骨子里,无奈只好作罢。 灵山上的风景与山下迥异,像极了西北高原,居然出现了驴子和牦牛。下面是在灵山拍摄的一些照片。 活动 今年我也刻意减少了很多不必要的活动。在这说说几个比较有意思的吧。 Hack for Good 我最开心的不是又参与了一次 Hachathon,而是 Hackathon 成为了俱乐部每年的例行活动(而且这一场还是卤蛋同学参与办的)。 这次 Hachaton 弹幕派作为合作方参与到了其中,并且我也带团队来和大家一起 Hack 了两天,成果显著,做好了小程序和控制面板上线前的最后准备。 SDN 大赛 还有一件值得一提的事情是搞了两年的 SDN 大赛终于以二等奖为结果圆满收官。比赛中强哥、天骄、高钱、格格这些队友都十分给力,另外加上小雨姐和潘老师的助攻,让我们成功杀入了决赛圈。 现在看来,我做了一年的 P4 实验系统也随着这个比赛到了的尾声。一年以来,我总是在不断地在这个系统的 Bug 和 Debug 中度过,每天总是担心训练效果不是很理想(事实证明效果的确也不理想)。前些日子,基于这个系统的 INT 遍历算法中了一篇 Infocom,也算是这套系统的最大成就吧。 技术 今年最令我担心的就是我的技术了。因为离工作的日子越发的近,我却还是没有一门能拿得出手的技术。这些年来,我做了挺多多,了解了挺多,都是项目要用到什么就去学习什么,一直没有给自己找到一个能够系统深入的方向,说起来也很是惭愧。 不过下半年也算是有一个好的开始吧。我开始去刷 Leetcode(刷过的题目都可以在博客中的leetcode 板块看到),去看一些框架的源码,开始做 Go 语言的项目,去了解容器和一些 Linux 协议栈相关的知识。现在看来,能做的就只能是再接再厉,不要再掉链子了。 弹幕派 在这里我也给弹幕派做一个年终总结吧。 去年一年,弹幕派的增长效果还不错。在推广营销投入几乎为 0 ,且前有夹击后有追兵的情况下,靠着之前的老本,在用户量和营收上都得到了明显的增长。 在今年一年,一共有数万个用户注册了弹幕派账号,成功举办了几千场大大小小的活动。一共有数十万人参与到了活动现场的弹幕互动中,产生了数百万条弹幕,并产生了数万个付费订单。另外,弹幕派网站创造了数百万的 PV 和几十万的 UV。这在互联网公司看来可能都不算什么,但是对于我们团队来说,是一个不小的进步。 我们也投入了大量的精力在研发上面。我们在去年年初就将系统迁移到了 Swarm 集群中,并配置了半自动的编码、发布、测试、预览、上线的环境,经过一年的实践,系统运行良好。而在前端层面,我们将原有的多种技术栈进行了整合,都迁移到了以 Vue 为基础的技术栈中,这样团队成员在不同项目之间切换就变得游刃有余。而后端我们继续沿用了 Laravel+Workerman 的架构,并感到目前还远远没有到达天花板。 今年的状况就是这样,期待明年可以有更强劲的增长吧。 一些小事儿 今年有两次去同学家里做客的经历。一次是同卤蛋同学一起去胃寒家,一起做了一大桌菜(感谢心灵手巧的胃寒和蓉蓉);另一次是自己去牧野家,一起吃了一顿史上最小的小火锅(感谢牧野和小团子的精心准备)。 吃腻了学校的食堂,有时候觉得能和爱人在自己家里做饭吃(特别是再和朋友一起煮一锅热气腾腾的火锅,一起聊聊天),的确是很开心的。所以我有时候也开始对未来在外面打拼的时光充满期待。谁知道呢,或许也是一座围城吧。 年中的时候我也拿到了驾照,不过今年唯一一次路驾还是和牧野,从中关村开到了学校。以后有机会还得多开车,向着老司机的行列迈进。 年末的时候玩了今年的唯一一款游戏《古剑奇谭 3》,虽然游戏只花了 99,但是我体验到了几倍于价格的诚意。游戏画面精美,优化十分到位,我在大一时候买的 Y400 在现在还能基本流畅地进行游戏,即时战斗也颇为精彩。而在游戏中,文明的光芒、历史的厚重与人类的传承在我的眼前徐徐展开,而主角之间那种的真情也让我倍感舒适。比起之前玩的仙侠类的 RPG,古剑三的进步不止一点点。最近有机会我想专门写一篇相关的文章。 沉重的话题 2018 年这一年,有很多人离开了我。包括我的亲人,以及许多敬重的人。 奶奶是 2 月份走的,现在每每想起心中依旧难以平静。而在这之后,这一年难过的事情就没有中断。 回忆了一下,逝去的科学巨匠有霍金和高锟。霍金的黑洞理论其价值非我所能评论,但是他的《时间简史》以及《果壳中的宇宙》陪伴了我整个少年时期。而高锟所发明的光纤正构成了我们现在有线通信的根基。他们的逝世是全人类的一大损失,随着他们的离开,科学世界的光芒也变得暗淡了许多。搜索后我才知道,仅 2018 年,就有 31 名两院院士离开了我们。这怕是绝无仅有的。他们共和国的栋梁,他们的贡献深刻地影响着我们的生活,我深切缅怀他们。 除他们外,还有许多社会知名人士也相继逝世,他们许多人对我们的社会有着深刻的影响。联合国秘书长安南是一个小时候经常听到的名字,他所在的时候,也是联合国知名度最高的时候,他纵横捭阖,致力于解决国际争端,给全人类一个美好的世界。著名主持人李咏的去世也勾起了我对童年的回忆,在印象中,他是一个每天都很欢乐,手持锤子打电话砸金蛋的主持人,但从没想过他会这么快就离开这个世界。而他和哈林的爱情故事,更令我十分动容。除此之外,对文化影响很深的还有金庸、李敖和曾仕强。金庸无需我多言,小时候看的很多武侠小说和电视剧都出自他手,他告诉了我什么是江湖,也给我带来了无数乐趣。李敖和曾仕强是台湾的两位思想家。其中,曾仕强《易经》可能是百家讲坛最受欢迎的节目之一了,他讲的内容大多记不得了,但是他那种睿智而又儒雅的风格令我印象深刻。 最后,还有田家炳先生。田家炳先生在国内可能没有邵逸夫那么有名,但是他也捐助了无数学校,让这些学校可以改建校舍,购置教具,相互交流,提升教学质量。我所读的高中就是一所田家炳中学在上高中的时候,我们体验到了新的教学楼,用上了很高级的电子白板。而在我们毕业后,学校也将土操场进行了翻新,还新建了装备齐全的科学楼。这其中有很大程度上都是由于他老人家。作为一名田家炳中学的学生,听到他逝去的消息,我的心情相当沉痛。 而我们国家也正在经历“百年未有之大变局”。这一年随着贸易战开打,经济局势开始变得紧张起来。随着数字货币、互联网借贷和共享单车的迅速衰落,互联网产业也迎来了变数很大的一个时期。独角兽们即使股票纷纷破发,也要流血上市,而下半年来更是处处风传裁员浪潮。这一年注定是一场场艰难的战役。 The End 不论如何,时间是不会停下来等人的。现在,2018 年已经过去,2019 年已经到来。该面对的还是要面对,该承受的也要去承受。 上面所回忆的事情,不论好事坏事,不论是否开心,也只是生活中事情的一少部分,生活中更多的事情,是稀松平常,甚至结束以后就会忘记的。但是,这些事情其实才是生活的主旋律。倘若可以在这些事情之中发现美,生活也就会有更多的乐趣了。 2019 注定依旧是很有悬念的一年,不能守成,还需不断奋斗。加油吧!
问题 1 键盘弹起后会遮挡键盘上方的内容 在微信浏览器中,如果需要模拟一个类似微信聊天的窗口,那么一般情况下需要将输入框使用 fixed 定位放置在页面最下方。就像这样: 但是,在 IOS 中的虚拟键盘和 Android 里是不同的。在 IOS 中,虚拟键盘弹出以后,键盘上面的输入提示会比键盘弹出慢半拍,所以就会导致输入法的提示框将正常页面挡住的情况。 这时候,就需要在键盘弹出后,等待一段时间(几百毫秒),然后再将页面的滚动条进行调整,就可以让页面弹到键盘之上。 假设页面布局如下(使用了 Vue 框架),其中 Dialogues 组件是可以滚动的聊天内容,PageFooter 是使用 fixed 定位在页面底部的输入框: 1 2 3 4 5 6 7 8 9 10 <div id="index"> <div id="mainPanel"> <div id="dialogueContent"> <Dialogues></Dialogues> </div> </div> <footer> <PageFooter></PageFooter> </footer> </div> 然后,就可以在监听到软键盘打开事件(比如 dialogueContent 中 input 元素的 onclick 或者 onchange 事件)后,执行下面的语句,让 dialogContent 的滚动条向下滚动,这样里面的内容就不会被覆盖了。 1 2 3 4 5 6 7 let dialogueContent = document.querySelector('#dialogueContent') setTimeout(function() { dialogueContent.scrollTop = dialogueContent.scrollHeight setTimeout(function() { dialogueContent.scrollTop = dialogueContent.scrollHeight }, 250) }, 250) 至于为什么要触发两次呢,是因为 IOS 手机种类比较多,从五六年前 iPhone5s 到最新的 iPhoneXR 都有人在用,每种手机的响应速度也各不相同,有些手机可能在第一个 250ms 内还没有完成键盘弹出的工作,所以可以再加一个定时器来兼容旧的手机。 但问题也依旧存在,就是 fixed 定位的输入框在软键盘弹出以后就不会像 Android 一样固定在页面底部,而是可以上下滑动,类似于 absolute 定位。这时,可以将 body 设置为 absolute 定位,然后再将 MainPage 使用 absolute 定位于 body 底部。 问题 2 在虚拟键盘收起以后 body 定位的问题 紧接着,第二个问题就出现了。在点击虚拟键盘右上角的“完成按钮以后”,页面下方并没有回弹,而是定在了原来软键盘上方的位置,必须在页面上滑动两下才可以触发回弹。(特别是在大屏的 IOS 手机上) 大概是这样: 而在将 body 设置为 absolute 以后,情况更加离奇。页面 UI 是回弹了,但是触控事件响应的位置是没有回弹的,依旧是软键盘打开区域的上方。解决方法同样是必须在页面上方华东两下才能回弹。 大概是这样: 最开始的想法是去监听 resize 事件,如果软键盘收回,就强行调整 body 的高度,但是发现软键盘的弹出和收回并不会触发该事件,只得作罢。 最终,找到了一篇解决 类似问题的文章,才找到了解决方案。 大致思路就是在键盘收回以后,主动触发浏览器对页面的重绘操作。如何进行呢?只需要在监听到 onblur 事件以后,让页面滚动到原来的位置即可。 比如,组件模板中的 HTML 为 1 <input type="text" placeholder="发送内容" v-model="content" @blur="resizeWindow"> 然后可以在 JS 的 methods 中添加一个函数: 1 2 3 resizeWindow() { document.body.scrollTop = document.body.scrollTop } 这样,输入框在失去焦点以后会触发页面的重绘,刚刚的问题就随之而解了。
现在,Docker 已经成为了一个非常主流的虚拟化技术,它集合了 Linux 中的许多虚拟化技术,如 Namespace、cgroup 和 AUFS 等等,所以我们可以使用 Docker 搭建一个开箱即用的虚拟化容器。但是,Docker 网路在很多时候依旧不能满足应用场景中的需求,这就需要我们对 Docker 中的网络进行自定义了。 这篇博客就是关于位于不同虚拟机中的两个容器实现 vxlan 通信的实验。 拓扑和环境 首先讲一下实验拓扑吧,它大概长下面这个样子。 一共两台虚拟机,在两台虚拟机之间使用交换机连接。在虚拟机内部各有一个容器,容器和主机通过 bridge 连接,而这两个虚拟机之间需要通过 vxlan 进行连接。 而我所使用的虚拟机是位于 vSpere 中的两台主机,他们采用 vSphere 中的虚拟机端口组相连,并且该端口组开启了混杂模式。两台虚拟机均为 Ubutnu18.04 系统,Docker 也为最新版本。 我还基于 Docker Hub 中的 Ubuntu 镜像封装了一个带有 ifconfig、ping、ip 和 python 命令的镜像,可以通过docker pull bzzdzc/myubuntu命令获取。 当然你可以通过在容器中的命令行内运行如下 apt-get 命令来安装这些软件: 1 2 3 4 5 6 7 apt update // ifconfig apt install net-tools //ping apt install iputils-ping //ip apt install iproute2 开始搭建网络 配置 VM1 中的网络 现在开始在 VM1 中对网络进行配置。 关闭防火墙 1 sudo ufw disable 运行容器 需要注意的是,在运行 Docker 时,选择不连接网络,之后我会将它连接到自己建立的网桥上面去。 1 docker run -itd --network none --name myubuntu1 myubuntu 记录容器中主进程的名称空间 首先,查看容器主进程的 PID,然后将其保存到$pid 变量中。 1 2 docker inspect --format '{{.State.Pid}}' myubuntu1 pid=$(docker inspect --format '{{.State.Pid}}' myubuntu1) 然后,将该 pid 链接至/var/run/netns 中,以便 ip netns 命令可以访问到它。 1 2 sudo mkdir -p /var/run/netns sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid 创建 veth peer 我们通过创建一对 veth peer A 和 B 来维持虚拟机主机通容器之间的通信。 1 sudo ip link add A type veth peer name B 创建新的网桥 这一步创建了一个名为 br-vx 的网桥,并将其状态设置为 up。然后,为网桥分配 ip 地址 192.168.0.1/24。 1 2 3 sudo brctl addbr br-vx sudo ip link set br-vx up sudo ip addr add 192.168.0.1/24 dev br-vx 将物理端口和 veth 端口 A 分别链接至网桥 虚拟机的物理端口一般为 ens32,我们将该端口连接至刚创建的网桥中。 然后我们将刚刚创建的 veth 的 A 端口绑定到网桥中,并设置其为 up。 1 2 3 sudo brctl addif br-vx ens32 sudo brctl addif br-vx A sudo ip link set A up 将 veth 端口 B 绑定到容器的名称空间中 这一步将 veth 的 B 端口放置于容器中,并将其命名为容器中的 eth0. 1 2 3 sudo ip link set B netns $pid sudo ip netns exec $pid ip link set dev B name eth0 sudo ip netns exec $pid ip link set eth0 up 为容器中的 eth0 端分配 IP 地址 为容器中分配 IP 地址 192.168.0.100/24。 1 sudo ip netns exec $pid ip addr add 192.168.0.100/24 dev eth0 现在,如果配置正确,使用命令docker exec -it myubuntu1 bash进入容器中的命令行后,运行ping 192.168.0.1 ,应该已经可以得到主机的回应了。 配置 VM1 中的 vxlan 连接 这里需要现在容器中创建一个 vxlan 连接。我们规定它的 id 为 100,而 vxlan 底层网络连接两端的 IP 分别为两个容器的 IP 192.168.0.100(本段)和 192.168.0.101(对端)。 然后设置 vxlan 隧道的 IP 为 10.0.0.1/24。 1 2 3 4 sudo ip netns exec $pid ip link add vxlan1 type vxlan id 100 remote 192.168.0.101 local 192.168.0.100 dstport 4789 sudo ip netns exec $pid ip link set vxlan1 up sudo ip netns exec $pid ip addr add 10.0.0.1/24 dev vxlan1 配置 VM2 中的网络 VM2 中网络的配置方法和 VM1 中类似,只是分配的 IP 地址有些变化。 给 VM2 中网桥分配的 IP 地址变为 192.168.0.2/24 给 VM2 中容器里的 eth0 端口分配的 IP 地址变为 192.168.0.101/24 给 VM2 中 vxlan 连接分配的本端 IP 地址变为 10.0.0.2/24 另外,在 vxlan 网络配置好之前,就已经可以使用 ping 命令测试底层网络网路的连通性了。 1 2 3 4 5 6 //测试和容器的连通性 ping 192.168.0.101 //测试和VM1的连通信 ping 192.168.0.1 //测试和VM1中容器的连通性 ping 192.168.0.100 测试 vxlan 网络的连通性 这个其实非常简单,依旧是使用 ping 命令。 首先,在 VM1 中进入容器内测试: 1 2 docker exec -it myubuntu1 bash ping 10.0.0.2 然后,在 VM2 中进入容器内测试: 1 2 docker exec -it myubuntu1 bash ping 10.0.0.1 如果双方都可以连通,那就说明基于 vxlan 的 隧道已经创建成功。 其它注意事项 有关混杂模式 首先需要注意的是,由于我们在 VM 中创建了容器,而又把 VM 和容器中的端口都绑定到了网桥上,所以,从 VM 中出去的包的 MAC 地址可能 VM 的也可能是容器的。由于安全策略限制,esxi 中的虚拟交换机默认会丢弃非 VM MAC 地址的包,这时就必须要开启混杂模式,让它不对源 MAC 地址进行验证。具体原理可以参考:http://blog.51cto.com/9843231/2294188?source=drh 一些参考资料 理解 Docker(3):Docker 使用 Linux namespace 隔离容器的运行环境 Docker 核心实现技术(命名空间&控制组&联合文件系统&Linux 网络虚拟化支持) 使用 CentOS Linux Bridge 搭建 Vxlan 环境 Linux-虚拟网络设备-veth pair
intro 上学期同Thesharing以及 Stone 去北大旁听了将近一学期的《科技创新与创业》(课程网站:http://net.pku.edu.cn/dlib/pkuxstart/)。 这个课程由百度七剑客之一的雷鸣主持,邀请了很多行业内有名的企业家来讲课,几乎都是北大校友(感慨一下北大校友文化真的很棒)。 我想在这篇文章中总结一下他们所讲的一些能引起我思考的观点和内容,以及经过我提炼加工所得到的结论。 三角关系 这里的三角关系并不是指恋爱中的那种复杂关系,而是指一个行业中相互制约的几个要素之间的复杂关系。其中一种要素发生大的变化(一般是非连续性的),这个产业整体以及几个要素之间的相互关系也会随之发生变化,这往往预示着新机会的到来。 微博 CEO 来去之间举了一个例子——移动互联网中存在的三角关系:运营商、手机制造商和互联网公司。 这个三角关系中某个要素发生变革就会导致移动互联网行业的巨变,会有一波新的公司起来。比如运营商 4G 网络的普及,使得网速变得越来越快,流量变得越来越便宜,这就催生了短视频行业的兴起,这也催生了一系列的公司和产品,比如快手和抖音。 明略数据的吴明辉提出在大数据这个产业中也存在一个三角关系——人、数据源和场景。 数据源在未来物联网时代会发生很大的变化,数据世界中的主导位置可能会从原来的因特网中的数据变为物联网传感器中的数据,这对于很多公司来说是一个全新的机会。 我认为,这些三角关系代表了一个场景之下的几个利益方,某一个方面出现技术或者认知方面的突破,都会造成行业的重新洗牌。比如在虚拟现实产业链中,不仅仅有设备制造商和内容生产商,还会有运营商的机会,因为 VR 数据的传输需要大量的带宽。所以,5G 时代到来以后,VR 会不会又重新火起来呢? 产品方法论 很多嘉宾都不约而同地说到了俞军老师有关用户收益的产品方法论: 产品的价值(用户的收益)= 新体验 - 旧体验 - 迁移成本。 这条方法论十分重要,是衡量你做出的一个新产品是否可以干掉旧产品的一个关键因素(这条方法论也经常被SGanker提到)。 我认为这条方法论的基础是一个共识——真正的需求不是被创造出来的,而是来自人类的本性。你所创造的产品是使用了一个新的形式或者新的技术来包装这个古老的需求。所以,一定要想清楚,你在做这个产品的时候到底替代了谁,新的产品解决了什么旧的问题。 另外一个问题是如何区分真的需求和伪需求。这里 OFO 的戴威提供了一个方法——将这种需求通过英语 Need/Want 归类,如果是 Need,那么这个需求就是真的需求,如果是 Want,那么这个需求就是伪需求。 不仅仅要检查这个需求是真需求还是伪需求,更要思考这个需求是不是只有自己需要,还是你的朋友也需要,更或许是人人都需要。 这里也有一个技巧,就是把自己的产品和别人去讲解,看他们是否感兴趣。不过身边的人可能会担心得罪你,不愿意说你产品的坏话,所以可以要找陌生人后者敢于说真话的人来介绍你的产品,来了解这个需求到底是不是真需求。 顺势而为 说到顺势而为,我第一个想到的是雷军和他的顺为资本,以及他“风口上的猪”的理论。我想,他也是因为带着金山硬挺了这么多年才悟出的这个道理吧。我想,现在的小米就是他顺势而为的结果。 关于顺势而为,还有一句话我很喜欢,也想放在这里——“一个人的命运,不仅要看个人的奋斗,还要看历史的进程。”这句话从辩证唯物的立场讨论了为什么要顺势而为。 下面就来讨论一下如何顺势而为。 从更高的高度来看问题 微博 CEO 王高飞从很高的层次来分析了微博发展的时候遇到的许多问题以及解决方法。他和他的团队的思考更多的不是现在用户的需求,而是未来五年左右中国经济社会的发展可能会让某些用户的需求变得旺盛,微博就会在这里提前布局。而对于许多目前无法超越的竞争对手,微博选择了从大局考虑,不去正面竞争而更换别的赛道(关于赛道,下面还会提到)。 比如微博在 2012 年就判断了中国经济未来五年的趋势:比如移动互联网的增量更多来自于二三线城市。而微博当时发力的主要方向就是去做二三线城市的消费升级,上线了一系列针对这部分用户的产品。 但是这样的分析可能也会有所遗漏,比如微博没有想到五六线城市的下沉也会是一个机会,而这个机会造就了快手的极速增长。 从这些嘉宾的口中,我也大致总结了大家都认为未来可能会蓬勃发展的行业: 首先还是互联网和 AI。互联网会和传统行业更深地结合(也就是互联网+),而 AI 也会改造更多的行业。 另外一个风口是生物医药(我之前的确从未关注过)。由于生物医疗行业中基因组学和蛋白质学这些基础技术的突破,导致未来这个行业很可能会非常蓬勃地发展。现在,投资机构对于生物医药行业的投资已经是仅次于互联网行业的存在了。 随着中国人口红利的消失,很多产品野蛮生长的机会已经不是很大了。而大家对于消费升级的追求导致会有更多的创新品牌诞生。 人口红利消失也会导致 2B 的服务会越来越多。 所以如果要创业,那么一定要选择具有先发优势的行业,即判断未来几年的主力消费人群和用户增量会在什么地方。 最后需要提到的是,现在全面创业的热潮已经过去,很多创业公司纷纷死去,只有很少具有竞争优势的公司存活了下来。 赛道的选择十分重要 赛道理论是投资界的一个理论。他们将某一个细分的行业或领域称之为一个赛道。而这个赛道上会有许多类似的公司在竞争(很像在一起赛跑)。既然是比赛,那么肯定会有前几名,而投资机构就会投资头部的那些具有竞争优势的企。等这个赛道成熟以后,可怕的幂律就会发生作用,前几家公司会吃掉这个赛道中九成以上的市场,后面的公司几乎没有任何机会。 所以,选择一个自己可以具有竞争优势的赛道就显得十分重要了。关于不同赛道上的公司的信息,可以在一些咨询公司的网站上面看见(比如艾瑞咨询:http://www.iresearch.com.cn/)。 另外,你所选择的赛道要具有空间和时间上很强的成长性。有空间的成长性,行业的天花板高,大家有充足的空间赚到很多,才会有人愿意投入资本和时间将这个市场做大。有时间的成长性,说明你做的方向是正确的,等五年到十年以后,时间还是你的朋友。 市场、产品和技术之间的关系 因为身边同学多是技术出身,所以我接触到的很多人可能都认为技术对于一个公司而言最重要的。但事实可能并非如此——许多产品是由市场拉动而非技术推动的,一般情况下开发一个市场所花费的成本要远远大于技术。所以开发产品时应当从市场的痛点来着手,而非技术高低。 不仅市场比技术更重要,产品也比技术更重要。在设计产品的时候需要有用户视角,使用同理心去思考用户的感受,并培养用户使用产品时的参与感。这里好像又说到了产品方法论,既然说到了,不如再举一个例子:腾讯的 10/100/1000 法则。这个法则要求腾讯的产品经理每个月必须做 10 个用户调查,关注 100 个用户博客,收集并反馈 1000 个用户体验。 商业模式 一个好的受资本市场欢迎的商业模式需要四个要素:能赚钱(有人买单)、规模性(可复制)、有壁垒(不会被腾讯抄袭)和可持续(未来成长空间很大)。 更好的商业模式不仅考虑了自身的发展,还要考虑到产业链中整个链条的利益分配问题。 非连续性机会 驱动社会经济发展的核心要素是非连续性机会,只有抓住非连续性机会的公司才可能获得爆发式的发展,并且有机会获得垄断地位。 好公司 护城河理论 “护城河”理论是巴菲特提出的。他认为好的公司需要有一条护城河来避免来自外部的竞争。有以下几种创造护城河的方法: 一种护城河是单一产品规模,公司拥有一个使用规模非常大的产品,以形成规模效应。互联网公司,如腾讯,ebay,沃尔玛等公司都属于这一类。 另一种是知识产权,比如商标或者一些关键的技术专利。迪士尼、耐克等公司就属于这一类。 最后一种是客户转用其他产品需要很高的成本。比如 Oracle 和微软。 几乎所有的好公司都在致力于建立护城河以获取垄断地位,最终占领用户的心智。 如何与大公司竞争 我一度认为类似“如果腾讯也开始做你们的产品怎么办?”这种问题是无解的。不过听完课以后,我的思路产生了变化。原因有三: 首先,你做的业务有可能是巨头们看不上的不怎么赚钱的小业务,除非这个业务以后会成长成为一个巨无霸,那么巨头一般是没有精力或者成本来同你竞争的。但就是这种巨头看不上的业务可能能让你赚的盆满钵满,在未来的某个时间,有娱非连续性机会,这个业务也或许就会成为商业的主战场。 其次,巨头往往都是上市公司,上市公司往往背负着到很多方面的利益。如果他们需要从赚钱的业务上将资源倾斜到其他需要和创业公司竞争的地方,那么他们的股东和员工都可能会不乐意,甚至股价也会下跌很多。所以,他们很多时候可能会选择投资或者收购而非直接竞争。 最后,市场中一直都有许多资本,为了不贬值,它们必须被投资到有很大增值潜力的地方,大公司往往增长不会太大,而增长潜力很高的小公司却可以做到。所以资本市场会愿意把钱交给具有很大增值空间的小公司,和大公司去打的。所以,为了可以和大公司竞争,创业公司需要业务+资本的双轮驱动。不仅要做好自己的业务,也要积极向外寻求资本,这样才能有一息存活的机会。 垄断才能创造利润 能够创造价值的公司并不一定可以创造很好的价格。因为在尚未形成垄断的时候,市场上存在着很多竞争对手,博弈论一定会在这里发挥作用,导致你无法让产品或公司有一个很好的价格。 这里说的垄断不一定是产品的垄断,还可以是应用场景的垄断。场景垄断垄断的是消费者的心智。比如苹果公司的产品,虽然从来没有在市场上形成单一品类的垄断,但是它们加起来形成了一个生态系统,它们垄断了消费者的心智。苹果公司靠它获得了非常高的利润率。同样垄断也表现在股价上——苹果公司在前几天终于成为了地球上第一家市值突破万亿美元大关的公司。 这里我还可以用小米公司来举一个例子。小米公司的互联网手机模式在刚出来的时候,受到了众多用户的欢迎,增长非常迅速,表现在资本层面就是估值越来越高。但是随后随着荣耀、OV 等公司加入这个赛道,同小米形成了强力的竞争,小米模式出现了很多问题。小米公司的价值无疑是很高的,但是没有垄断。这种问题表现在股价上就是上市以后几乎两次破发,小米公司并没有得到和价值匹配的好的价格。但是它的生态链以及和用户建立信任关系的商业手段都是在为建立(不同于苹果模式的)新的垄断去做尝试。小米公司的模式究竟能走到何时何地,我们可以拭目以待。 创始人 先说一个结论,创始人的高度很大程度上决定了企业的高度。因为企业的文化、商业模式以及关键决策几乎都来自于创始人。不过创始人的高度也不是一成不变的,他们会随着公司的成长不断的学习和成长。 领导力 一家公司的创始人往往就是领导者。领导者和管理者有很大的区别。管理者只需要管理员工,按时按量完成任务即可。但是领导人需要通过他的很强的人格魅力来带领大家一起向前。所以领导人不仅需要做事,还需要做人,不仅要做事做人,更需要有很长远的目光。 做为领导者往往都是很孤独的。麦肯锡健康的樊琴还说,创业不仅孤独,而且几乎没有成就感。因为一切都是从零开始,而且一旦开始就永远没有尽头。 刚刚说过,创始人的高度很大程度上决定了企业的高度,所以创始人需要很强的学习能力,需要在企业成长的同时也不断学习,要和企业一同成长,甚至要比企业的成长还要迅速。但是,创始人不仅仅需要学习,还需要将他的决策力、执行力、组织力和感召力都输出给其他人,营造气氛,让大家一起干起来,也就是“使众人行”。 创始人的选择 OFO 的戴威给出了他选择创始人的思路。首先,公司只能有一个创始人。其次,创始人和联合创始人之间必须要比较知根知底(比如哥们),同时也最好可以兼具能力上的互补。 另外,价值观的统一也非常重要,创始人之间需要有强烈的相互认可,不然现在的兄弟可能会变成之后的友商。 最不易稳固的结构是只根据需要能力来选择一些不太熟悉的人,雷鸣认为这样的创始团队算是“草台班子”,而草台班子是迟早要散的。 投资人的选择 将好几位老师的话总结一下,就是:投资人不仅要选有钱的,还要选择除了钱以外可以给企业提供更多帮助的人。另外在敲定投资的时候,一些法律问题一定要了解清楚,不然可能会因为协议中的一些条款就让自己倾家荡产。 商业和社会成熟度 商业不是过家家,创始人需要很强的商业成熟度和社会成熟度。 商业成熟度主要反映在对商业本质的认识:比如有客户来源、商业模式、对竞争对手的认识和投资策略等等。创始人没有很好的商业成熟度,公司一定是无法存活的。 社会成熟度来源于步入社会以后对社会的认识,比如经验、能力、人脉和价值观。董小玲认为,这些成熟度在高校中是很难锻炼出来的,而在大学生走上社会的半年左右的时间里,会逐渐积聚。这个时候创业者既对社会有了清醒的认识,还没有忘记自己的理想,是创业的最佳时机。 家庭关系 令我诧异的是有很多创业者创业中断的原因不是融资失败、竞争对手或者政策问题等等来自外部的因素,而是因为家庭成员之间意见不一致的问题。这里的家庭成员多是另一半、自己的父母或者是另一半的父母。 因为创业面临了很大的不确定性,所以如果其他家庭成员(特别是另一半的家长)接受不了这种不稳定性而不同意你去创业,这无异于后院起火,创业很可能就会失败了。 这里董小玲提到了一个规律,就是如果父母对创业特别懂或者一点都不懂,这都好办,但是如果父母半懂不懂那可能麻烦就会大一些了。 所以如何平衡创业与家庭之间的关系,是往往被创业者忽略的一个很大的问题。 一些其他的话题 以下是嘉宾们对于一些热门话题的比较有趣的观点。 区块链 邓锋认为,对于区块链应该区分链圈和币圈。区块链解决了信任问题,也解决了价值重新分配的问题,而币圈都是骗子。 一个技术(比如区块链)的出现并不能颠覆一个行业,只能作为增量而存在。 数据 吴明辉认为数据是对世界的观察,帮助没有观察的人解决信息不对等的问题。它可以创造信任,降低决策成本,帮助决策者进行快速的决策。但另一个角度来看,数据不一定是真实的,因为它是主观的,它本身也没有任何价值。但是只要它存在,就可以创造信任,而通过信任就会产生很多的价值。 另外,历史的发展多是不连续的,而数据代表过去,所以过去的数据很难预测长期的未来,但是它可以预测短期的未来。 商业的本质也是在利用信息不对等来解决问题创造价值,如果利用数据来做生意,使得信息对等了,那么商业就不存在了。所以用数据做生意是商业中的一个悖论。 商业计划书 弘道资本的李晓光认为商业计划书的目的是为了获得投资,核心内容是你投资我可以赚大钱。商业计划书的质量决定了 VC 是否会找你进行面对面沟通。 商业计划书需要准备几种:五分钟版本、演示 PPT、完整计划书、未来财务预测(需要专业的财务模型)。 推荐书 课堂上有许多老师给出了推荐阅读的书目,我也在这里略作总结: 最后,使用戴威的一句话作为文章的结尾吧: 不要迷信于别人的经验和方法论,创业者应该在创业中不断尝试,找到属于自己的方法论。
最近得知《青年马克思》上映,觉得有必要一看。于是上网搜了一下院线的排片,不出所料,排片量几乎是最近热映的《复联 3》的五分之一,甚至周末都少有商业影院有排期。于是只能等到周一约上阿文,去附近的影院一睹青年时期马克思的“芳容”。 刚入座,还在和朋友聊最近的中兴联想发生的一系列事件,电影就毫无防备地开始了。此时一个大概容纳五六十人的小影厅里只稀疏地坐着六七个人,多数还是学生(祖国未来还是很有希望的嘛)。 电影里的场景暂且不表,先来说说我对电影整体的感受吧。 首先,作为一部传记电影,而且讲述的是一位思想家的成长历程,如果没有一些对马克思生平以及思想的了解,可能就会在电影院呼呼大睡了。这可能会是观影的一个门槛。 其次,电影内容很丰富。短短不到两个小时,电影讲述了马克思从莱茵报编辑,到论战蒲鲁东,最后是完成《共产党宣言》这三个时期,同时又穿插了马克思与燕妮之间深沉的爱,马克思同恩格斯之间深切的友谊以及恩格斯同玛丽之间跨越阶级的感情。 电影不但对历史事件做到了真实还原,同时还把人物表现得有血有肉,把离我们很远的两位“大胡子”思想家拉到了和我们相仿的年纪,也做着和我们类似的事情——有为理想的奋斗的激情,有现实中碰钉子的无奈,也有有酒逢知己千杯少的感慨,更有和自己爱人待在一起缠绵,这对从感性上开始对马恩他们两个在我国近似于“符号化”存在的人物真正地了解,真的是太重要了。 最后,我觉得电影的确还缺乏一些对马克思思想令人深思的阐述。它讲述了马克思的成长历程,但却没有讲述出马克思思想的成长历程。比如影片用挺久时间讲述了马克思用《哲学的贫困》来反驳蒲鲁东《贫困的哲学》,我们得到的也只有他反驳蒲鲁东关于他的无政府主义,我们却始终没有听到具体反驳的声音到底在哪里。最后片中大声朗读《共产党宣言》中的内容,但是我相信多数人只是感觉热血沸腾,却不知其中平实语言中蕴含的深刻哲理。不过马克思思想的确庞杂而繁杂,不能对一个两个小时的电影求全责备,能讲出这些,已经很不容易了。 电影中的一些桥段也十分精彩。 开场像一幅浓墨重彩的油画,很有欧洲片的感觉。场景是许多德国穷人在携家带口捡树上脱落的枯枝。然后警察就突然出现,穷人被他们无辜杀害驱逐抓捕。毫无疑问,这里是说马克思在大学刚毕业去莱茵报工作时期对《林木盗窃法》进行批判的事情。所以画面一转,就是马克思对莱茵报许多同事太过软弱的激烈吐槽,而警察开始在楼下围攻莱茵报办公室的场景。 那时候马克思一直希望用黑格尔的法哲学来批判莱茵省议会的所作所为,但是他的努力失败了,他意识到法律的本源并不是一种绝对精神,而是为了保护某一个阶级的阶级利益,更重要的是,他认识到当时的法律保护的不是人权,而根本上是资产阶级的私有财产。这是他从黑格尔学派转向辨证批判的一个开端,也很可能是“经济基础决定上层建筑”这一说法在思想上的一个开端。那时候的马克思应该和我们年岁相仿,二十三四岁的样子,对法律却有如此深刻的洞悉,我认为他不仅仅是“千古第一思想家”,他更是一位少年天才! 当然,这件事情的后果是《莱茵报》最终被查封,他和同伴们也去监狱反省人生,不久,他也将和刚成婚不久地妻子燕妮将前往巴黎,也会认识他一生的挚友恩格斯。 电影对马克思和燕妮的感情以及恩格斯和玛丽的感情都表达得也很到位,燕妮和玛丽在影片里不是一个很平很脸谱化的配角,而是两个活生生的女主。 燕妮生于贵族家庭,她受够了所在阶级的腐朽生活,抵抗住了来自各方的压力和马克思相爱,结婚。在当时贵族阶级还在享受生活的时候,她已经有如此感悟,可见她的思想境界一定非同寻常女性所能及。马克思也说,她一直是他思想灵感的重要来源。 在她和马克思刚结婚的时候,他们在家中打闹、斗嘴,到最后接吻、相拥而眠时候的场景让我印象深刻。说实话,以前我也从来没有想过(哪怕是一丝)他们俩之间的生活究竟是什么样的,但这一场景让我彻底明白,他们这对模范夫妇和一般小情侣的日常生活也没有太大的区别,让他们的爱情如此深刻的原因在于他们共同的理想和克服困难的斗志——随后,我便见证了这些。 第一次是她开玩笑给马恩合著的书取名叫《对批判的批判所做的批判》,后来这本书被命名为《神圣家族》,而副标题正是燕妮所起的《对批判的批判所做的批判》。可见她也是很有哲学思想,甚至是能和马克思探讨很多东西的。第二次是她几次在马克思被驱逐或者拿不到稿费饥寒交迫的时候毫无怨言,甚至在如此艰苦的条件下还成为了好几个孩子的母亲。如果没有满腔的革命热情和对马克思深沉的爱,她怎么能够如此地坚强。不过马克思也并非撇下孩子老婆不管的人,他也以同样的热情为这个家得以维继四处奔波,甚至答应了放弃写作并且低声下气做任何工作(然而他还是没有成功找到一份工作)。这些场景也让马克思的形象更加饱满,他也是和我们一样,为家庭为生计烦扰奔波的普通人。 玛丽也同样为恩格斯地事业做出了不可磨灭的贡献。恩格斯是一个“富二代”公子哥,但他却不想继承他父亲的衣钵,而是非常同情英国工人的状况,希望可以帮他们说话。所以他前往英国大大小小的工厂进行社会调查,但是由于他身份特殊,许多工人并不待见他。而玛丽帮助恩格斯联系了许多工厂中的工人,才使得他完成了《英国工人阶级状况》这一调查报告。后来她又促成了马恩同正义者同盟的领导者的见面,间接推动了《共产党宣言》的创作与发表。 马克思和恩格斯的友情也是片中也被着重呈现。片中用欲扬先抑而且有一些无厘头感的镜头把他们从相识到熟悉的经历表现出来。 马克思先是因为恩格斯代表的资产阶级而看不上他,但是当他们聊起天来的时候,甚是忘我,马克思居然都忘记他们是要去讨稿费才见的面。再之后,他们一起逃离法国警察追捕又相遇的镜头像极了一部小型警匪片,就像他俩是认识了多年的老搭档一样,在一路东躲西藏之后,又在一个隐秘的转角重新相逢,然后像没事儿人一样一起去喝酒。 在开怀大醉之后,马克思说出了那句令我震动的话:“哲学家们都在解释世界,而问题在于改变世界!”两位前辈就从这句话出发,一步一步地对这个世界进行剖析,并且将他们的一生贡献给了世界无产阶级的解放事业中去。 在后面的相处中,马克思是一个性格如火一般富有斗争热情又很有思想的角色,而恩格斯则显得内敛许多,他俩在性格上非常互补,成为了非常合适的搭档。特别是在最后由恩格斯拿着马克思的手稿主导正义者同盟的“改名大会”的时候,恩格斯不断呼吁寻求更多支持,马克思则火药味十足,他们两人配合,终于让《共产党宣言》成为了共产主义者同盟的行动纲领。 影片最后,马恩两家在海边度假,马克思说因为琐事太多,而且经济困难,所以没法写一部大部头的书,而且生活太累的时候,恩格斯说,欧洲的革命已经开始,资产阶级专制统治已经非常脆弱,各地的工人运动已经风起云涌,工人阶级将会自醒,解放事业可能很快就会成功。 这让我从电影中跳出来,想到了之后发生的事情,令人唏嘘不已——直到马克思死后,世界上第一个社会主义国家苏联才建立,而直到两百年后的今天,世界仍处于资本主义的笼罩之下,而现在的资本也披上了金融这层看似温和的外衣。 片中没有花很大笔墨介绍马克思同蒲鲁东的论战,而用很多镜头表现了他与魏特林的冲突。 魏特林也是当时工人阶级的代表者,主张是“人人皆兄弟”,并且企图用这个口号和富有激情的演讲来感动所有人从而实现革命。但是魏特林的思想却还非常不成熟,他没有意识到冲突的根本是阶级斗争,所以非常反对马克思这种在他看来很形而上的“纲领”,而他所讲的仁慈博爱这种对工人运动毫无意义的词汇也让马克思反感。终于有一次会议上马克思爆发,正式和魏特林决裂。 之后,在正义者同盟那位有智慧的老者的支持下,同盟最终站在了马克思这边,并同意马恩一起起草一份纲领性的宣言——《共产党宣言》。 很多人应该都记得魏特林反驳马克思的这句名言:“批判会吞噬一切存在,当没有其他东西可吞噬的时候,它就只能吞噬它自己。”他显然认为这里的批判就是黑格尔学派所说的批判,因为但是黑格尔学派的一些人主张对一些哲学家的批判进行再次的批判,所以他认为当他们把旧的东西批判干净的时候,只能再次批判批判过旧的事物的批判了。不过他没有理解马克思所认识到的批判是什么意思。他如果认真读了当时马恩所写的《神圣家族》以及《德意志意识形态》,就会明白当时在他们面前说的这句话是很幼稚的。 回想电影中的马克思,他对事业执着,对工作勤劳、对妻子挚爱,对家庭有责任心,对朋友真诚。他作为一个学者,把人性的真善美演绎得淋漓尽致。片中还没讲,他为了写成《资本论》,在大英图书馆中一坐就是几十年。 再看看我们当下的学者,他们中有很大一部分人做事情的并不是潜心的研究,也并没有什么对未来的理想,而当下这个时代,他们最喜欢做的事情就是疯狂地“捞钱”。更令人不齿的是,最近全国接二连三出现多起类似导师让学生叫“爸爸”,导师性侵女学生的案件,这些都无不令人唏嘘。 我认为马克思不仅为我们留下了几厚本文字作为遗产,他的勤勉好学,他的敢爱敢恨,他的崇高理想,也需要当下青年学生、学者们多去反思和学习。 更可怕的一件事情是马克思被符号化。从我们小学开始,就一直在听这两个大胡子老头的事迹,知道他们是好友,知道他们的思想非常厉害,对我们现在的社会影响深远。却又觉得他们十分遥远,不知他们究竟是什么样的人,更不知道他们的思想究竟是何物。 伴随着许多人对现实的不满,又受到西方意识形态的强烈灌输,以及他们从中学课本的只言片语出发所产生的对整个社会的片面的思考,许多人开始批评这两位老人,厌恶这两位老人,甚至诋毁这两位老人。看完电影以后我翻了翻豆瓣的影评,充满戾气。 除去头脑中的偏见,要从感性的认识开始。我想,这些人更应该去看看这部电影,去了解他们的生活,他们的思想,认识一个不再符号化的马恩。所以从这个角度来看,电影之所以没有提及那么多的马克思的思想,也是因为它为大众所准备的,可以让观众从能理解的角度认识这个活生生的马克思。 我想,看到电影中 18 世纪英国工人们被榨取剩余价值的惨烈场景,看到资产阶级警察们对无产阶级的草菅人命,不会有人还认为这个斗争是毫无价值的吧?如果没有工人阶级的运动,没有那么多人流血牺牲,现在的欧洲还会是当时的那个欧洲。不要再说什么现在生产力强大了,资本家就不会压迫工人了。影片中一位工厂主的话告诉我们:如果你不雇佣童工,如果不压迫工人,会有其他人为了获取更多的利益这么做,那么社会必要劳动时间会减少,产品的价值会下降,如果你不去这么做,你就会面临成本的上升,你就会破产。工人生产的产品越多,生产越快,那么他自己的价值却越低。别说这个规律只在那个时候起作用,在现在这个时代,对于各行各业,也都是完全一样的。 现在我们身在“高大上”的互联网公司,拿着看起来挺高的薪水,但是人人不还是在没日没夜每周末像机器一样的 996 中度过,你所创造的价值,到底又有多少归你所有了呢? 当下依旧是技术与资本的时代,只是资本披上了另一层温和一些的外衣。所以并不是现在马克思思想已经过时了,马克思主义仍是当下的一个幽灵,始终盘旋在我们这个资本主义世界上空。也正是因为有了这一柄达摩克里斯之剑,才使得现在的资本主义变得温和,让我们无产者在被资本支配的同时,也可以享受到资本所带来的生产力的提升而产生的富足而美好的物质生活。 所以,电影中的这两位主角,的的确确是如此的伟大。 另一个角度去向,当资本发展到今天,以如此这般更加猖獗更加繁荣的面孔出现,这不更是一个从未有过的最好的研究和发展马克思主义思想的历史时期么? 另外还想做两点科普。 第一,马克思不是经济学家。《资本论》的副标题是《政治经济学批判》,他是在用以辩证法为核心的新的叫做批判的科学来对经济学进行批判。所以虽然马克思在经济学领域有很深的造诣,(也正因为他如此了解经济学)他却做了对经济学(确切说也不能是经济学,而是对当下现实)最的深刻批判。这门新的科学也可以运用在其他的领域,只是马克思去世太早,还没有精力把这把利剑插向别的范畴。 第二,马克思所谓的共产主义,也并不是所有的东西都是公有制,不允许个人拥有一分一毫的个人物品。这里的共产指的是扬弃经济学中的资本,让所有物不再以私有财产的形式出现,而需要公有的是人们从事生产活动所需要的生产资料。许多人甚至认为共产主义连婚姻都要”共享“,这是多大的谬误。 PS:拖延了一周终于按照之前所列的大纲基本写完了。对于马克思思想,我也只知皮毛,有很多想不清楚,没弄懂的地方。所以如果大家发现文中存在谬误,希望可以批评指正。
在前端调试的时候,跨域一直都是一个比较麻烦的问题,这个在之前的文章关于跨域问题的一个解决方法中其实已经讨论了一些可以使用的方法。 如果要使用 JSONP,第一是需要修改的地方比较多,而且也不太符合前端发展的大趋势,如果使用 CORS 的话并没有 application/json 类型。而且更重要的是这只是在前端调试时候的需求,并不是在上线以后的需求,所以对后端有太多的入侵也不好。 所以就有一个念想突然在大脑中闪过——加入有一个代理不就可以解决这个问题了?但是又想了一下写起来还挺麻烦,于是就被搁置了。 直到前几天 Stone 提到其实 webpack-dev-server 早就想到并且已经帮我们实现了。 于是,我就在一个 Vue 项目中进行测试,发现真的很赞,既可以本地 Server 热加载,还可以直接跨域调用远程 API,完美解决了之前遇到的所有问题。 接下来我简要介绍一下步骤(以一个 Vue 脚手架建立的 webpack 项目为例):首先检查build/webpack.dev.conf.js中是否有 1 proxy: config.dev.proxyTable, 这个配置项,如果被注释掉,请打开注释,如果没有,请加入到 devServer 对象中 然后在 config/index.js 中的 dev 对象中加入 proxyTable 配置项: 1 2 3 4 5 6 7 proxyTable: { '/**': { target: 'http://api.xxx.com', changeOrigin: true, secure: false } }, 前面的键 /** 意思是代理所有请求,如果代理某些请求,可以将其改为诸如 /api 之类的字符串。 后面的 target 就是要代理到的网站,changeOrigin 的意思就是把 http 请求中的 Origin 字段进行变换,在浏览器接收到后端回复的时候,浏览器会以为这是本地请求,而在后端那边会以为是在站内的调用。 这样,通过这个简单的配置,就完美地解决了跨域的问题。 之后,在直接运行 1 npm run dev 的时候,就可以将测试前端中的 ajax 请求代理到后端服务器进行测试啦! 最后,贴上官方文档,具体的配置大家可以参考这里: https://webpack.js.org/configuration/dev-server/#devserver-proxy
偶然读到王德峰老师的一本已经绝版的小册子,叫《寻觅意义》。心想这个题目起的甚是有趣——其一我在想他会怎么写,结果读下去发现是他在各个大学讲座的讲稿的一个小集;其二说来也很巧,我最近也一直在反思意义究竟是什么,我做什么才有意义,我追求的意义是什么,有没有属于这个时代的意义,而真正的大义又是何物呢? 关于时代的意义,和妈妈聊天的时候,她关于她的意义是什么给了我如下的解答:“除了想让你过的开心快乐,剩下的意义就是多赚钱了,钱赚得多,我就感觉很踏实。”我想,前半句是家庭,是我和母亲很真挚的亲情;后半句是时代,是我和母亲以及所有存活于现代社会的人共同处于的环境。 我们所处的是这样一个被资本与技术所主导的时代。所以我理解大家的理想多是要去赚钱。不论男女老少,不论贫穷富贵,每个人的欲望也总和钱脱不开关系。而钱这个东西,究其根本,不过是一个几乎是全人类共同参与的一个游戏,同我们的能生存与否没有什么直接的依赖关系,只是在这个时代里,我们需要用钱去获得各种生存资源和对他人支配的权力。 所以与其说我们想去赚钱,不如说是这个时代规定了我们想要的是去赚钱。 然而,赚到再多的钱又有什么意义呢?大家拼命多赚钱,最后又能得到什么呢?许多富人甚至比穷人还要忙还要苦恼,他们赚了这么多钱,但是赚到了意义么? 是,赚到钱越多,我们可以获得的社会资源也就越多,可但是钱作为一个可以被数的数字,一个人究其一生,能获得的量总还是有限的。但是人心呢? 一个人可以在史书中穿越到几百年之前,可以在对未来的幻想中穿越到几千年后,甚至可以在对宇宙思考中时间横亘数百亿年。人不论活在哪个时间点,总可以在头脑中对过去和未来作出无限的延拓。所谓人心,是无限的。 倘若有钱的金钱来对无限的生命说:我就是你的意义!这句话还会成立么? 既然金钱的意义是仅对于我们这个时代的,而且对于无限生命来说,它也太微不足道了些,那意义,能够称得起对于生命有意义的东西,究竟是什么呢? 在很长的一段时间里,我曾认为它是宇宙的终极真理。就是随着以物理学为首的近代科学的不断深入探究,我们总能得到一个越来越接近宇宙真相的真理。 但是随着这条线仔细深入想下去,结果却令我失望。就举一个物理学的例子:在这一秒钟苹果从树上掉到了地上,牛顿说它是受到了重力的作用,好,那么请证明,在下一秒钟,还是会有苹果会受到重力落到地上。对于休谟的这个诘难,近代科学还无法解答。我们能说的只是:根据这么多年的人类观察历史,所有的事实皆是如此,所以我相信它会继续下落。 所以从根源上来讲,我们所坚信的自然科学,都是经验的产物。经验总有被打破的时候,那么我们通过经验所找到的这些自然科学的定理,是真的真理么? 我们所掌握的这些科学,倒是为我们控制其它事物提供了许许多多行之有效的方法,可以满足人类许许多多的需求,给资本帮了一个大忙。所以,科学主义,是西方近代理性形而上学的延伸,是笼罩在当代的一种意识形态,它的真理,是基于经验而派生出来的。 唉,通过科学的方法来寻找宇宙终极真理的路,在这里就完全被堵住了。 既然已经在我们外部探究了这么多,寻找意义却依然没有结果,如果转向我们自身,我们内部,又会怎样呢? 我不知道我成长了这二十多岁,已经被这个时代这个世界改变了多少,还依然保留了多少被许多前辈们所诟病的不成熟的“童心”呢? 我想,爱情算是其一,也需要被放在首位。 在爱情里面我相信命运,另一半不需要用什么物质上的可列举条件标准来衡量,需要做的是去等待。对的人可能就是上辈子所注定的,互相相处一段时间,甚至是交换一个眼神,那种爱情的感觉就来了,是一种无法欺骗内心的感觉,就可以确定——就是她! 而缘分呢,感觉就很像佛教中所讲的业。两个人上一世造的业(当然是善业),就像一颗种子一样,不断成长发芽,在这一世,你们就回彼此相遇,由你们共同来完成这一世的存在。当然,你也要在这一世需要珍惜且付诸行动,才能让前世所积攒的被白白消耗。 另外,在爱情中我也像“傻了一般”,并不会理性地计算得失。因为计算得失是商人相处时要去做的事情,跟恋人去计算,我的内心会有极大的阻力和抵触。 在爱情中,我可以感受到一种真实感,一种真真实实存在的,爱与被爱的感受,而非虚无的外物与金钱所能比拟的。 另一个是随着思考理解的深入才认识到的,就是艺术。 最开始醍醐灌顶般明白过来是因为看到了海德格尔的一句话:“艺术是真理的原始发生”。我就在想,为什么是艺术,而不是科学,成为了那个最朴素的真理? 相似的话尼采也说过:“人在事物中除了重新发现自己的入藏品而外再不会重新发现任何东西——这种再发现,自称科学。入藏品包括艺术、宗教、爱、自豪。”他也提到了科学的派生性,而真理来源于艺术、宗教与爱。 如王老师在小册子里所说的,艺术其实就是巫术。 古代人对于巫术,并不是在举行完仪式以后,就什么都不去做了,等着上天的恩赐,而是在仪式完成以后,大家要一起去完成一个凶险困难或对生死存亡意义重大的事情,比如大型狩猎、建造、以及秋收。通过巫术,让集体中的每个人凝结在一起,也给了人类以未来。 为什么会这样?我认为这是由于我们每个人可以理解超验的超自然的事物的存在(比如艺术中的感情、宗教中的神明,甚至是一个国家、一个政党,人们的品格、信念、爱情,更甚至是某个虚构的人格,比如超人,或者是。。。王尼玛)。 而从古至今的诗歌、音乐以及画作等等艺术,都是有此作用。这些艺术作品将人类对某种事物的情感封存在其中,当人们再看到这件作品的时候就可以激发出他们对于情感的共鸣和他们对超验世界共同的认识与体会。 是雕塑艺术让人们真正理解到了石料的存在,否则它们就是一堆用作建筑的物质,是音乐让人们真正理解到了声音的存在,否则它只是一种可以用来传递信息的波(虽然我们每天都通过声音收发了许多信息,却只有音乐能让我们听到真正的声音,这种对于人的感性的存在),绘画也是如此,让我们看到了光线真正的存在,否则它也只是一堆电磁波(然而电磁波也是我们描述它的一个方式,它真正的存在是什么呢,或许只有在绘画中才能真正表现出来)。 让人领悟到物质的感性存在或许就是艺术的伟大之处。 成年人向外部索取太多,而对内部听到的却越来越少,这大概就是孟子所说的“失其本心”吧。在这个只认金钱,意义丢失的时代,我想,不能害怕在时代的荒原中和多数人走上了不同的方向,要回到自己的内心,去真心体验人最真挚的情感,寻找绿洲的存在。说不定,就找到了那一片属于自己的绿洲了呢?
深度写作 最近看见沈向洋发表了一篇文章(地址:https://zhuanlan.zhihu.com/p/33771188),大意是说在这个 AI 的时代,虽然每天我们产生和接受的的零碎的信息量都非常巨大,但真正有意义的思考还是需要通过长篇写作来完成。写作可以帮我们理清思绪,可以清晰地表达观点和逻辑。 这也是我最近所担心的事情——我也已经很久没有进行过深度的写作了。 上次系统地写作还是中外文学名著鉴赏的期末作业,文章虽然可以大致表达我想说的表面意思,但是短短八百字的表达却支离破碎,根本无法把我更深成次的想法完整表述出来。在刚写完的时候没感觉到还沾沾自喜,觉得一下午写出来的文章应该还能看,之后读起来却就像是读一篇小学生记的流水账一般,像是喝了一大杯白开水还泛着水垢一般,毫无深度,甚至还难以读完。 感触更深的一次是上个月我想抽空写一下去年的年终总结,但刚提笔就不知道该从何说起,所以又去翻前几年的文章,发现当时的文风根本现在几乎无法模仿。我十分懊恼,故总结也一直搁置下去。 直到今天,我才有勇气重新打开编辑器开始写这篇文章。今天没有给文章定下主题,想到哪里就写哪里,但愿思路会更顺一些吧。 活着 正如认识存在需要通过感受虚无一样。认知活着也需要感受死亡。 这个假期的前半段就是虚无与死亡。虽然之前在各种文艺作品中已经见过了无数种死亡的情景,但自己有如此深的感受到却还是第一次——这次死亡的对象不再是文学作品中或者和自己关系不大的某个人,而是我打小就十分亲近的奶奶。 思绪回到寒假之前,刚听闻奶奶住院的消息时,虽然奶奶声音听起来依然慈祥有力,但了解病情后,我十分担忧,感觉就像是一块石头压在心头,怎么都喘不过气,每天晚上能见到的只有噩梦。果不其然,第二次我再打电话过去的时候,奶奶的声音已经有些孱弱,听起来也很是疲惫。 第二天一大早,姑姑给我发的信息只有几个字:能提前回来就提前回来。于是当天就提前请假、买高铁票、交接任务、安排事情、准备回家。我记得当天中午和卤蛋同学一起吃饭,有好几次我都集中不了注意力,大脑里的各种思绪像幻灯片一样飞快地闪过,却怎么也提炼不出头绪来。 回家后就径直到了奶奶住的医院,趁着奶奶比较清醒的时候,同奶奶讲了几句话,还给她看了前两天和卤蛋一起专门给她拍的合影,看得出奶奶当时还挺开心。现在我很感激当时的我所做的这个决定,这几句话大概是和奶奶最后一段完整的对话了。 之后几日,奶奶的身体情况一日不如一日,死神在不断地靠近这个历经近八十年岁月洗礼为家庭为儿孙操劳无数的坚强的女人。我知道,任何手段都无法阻止死神来临的脚步,能做的就是默默等待,当你一不留神,他就会悄无声息地到来。经过几天痛苦的思考,我想,我已经足够坚强,可以接受这个事实。另外,看着奶奶在病床上受苦的样子,有时候我也竟盼着死神早些光顾,她这辈子已经受了太多的苦,能让她早日解脱也未尝不是好事。 2 月 6 日晚上,奶奶情况突然恶化。奶奶弥留之际,爷爷放下多少年来和奶奶的吵吵闹闹,开始和奶奶讲心里话。虽然没有一个“爱”字,但句句都是直戳心灵的情话。这时我才知道,在无数的吵吵闹闹的背后,他们老两口之间更多的是无法用言语表达的爱和包容,才能一起走过这近六十年的风风雨雨。我当时就在想:这老爷子,干嘛就那么犟那么固执,非要到了这个时候才把这些心里话说出来呢,这可是我听过的世界上最动听的情话!可惜不知那时的奶奶究竟还能不能听到。 终于,在 2017 年 2 月 7 日凌晨 3 点 41 分,死神终于悄悄出现在我们身边,带走了这个在世上受尽苦难但又善良而伟大的灵魂。奶奶生前是基督徒,按照教义,她已经为世人受了太多罪过,我相信她的灵魂一定会上天堂,和主耶稣永永远远生活在一起,再也没有烦扰,再也没有痛苦。 此后几日,为奶奶守灵。爷爷奶奶的许多朋友都自行前来吊唁。跟他们聊起来,听他们讲起当年的故事,我才体会到能和爷爷奶奶做朋友的人,也都有着属于他们那个年代的诚实、正直、热心和血气方刚。说起他们的很多事迹,许多自诩深谙社会之道的人,甚至是现在刚成年的许多零零后们,都一定无法理解。这些爷爷奶奶,一生归来后,也仍是少年。 葬礼后,我像是突然醒悟似的,意识到奶奶的的确确,永远离开了我们。回家后,我有时候还是幻想她还在,还在厨房里忙忙碌碌准备我最爱吃的饭菜,还在用她歪歪扭扭新学到的字抄写歌词,还在那台跟她一起度过了几十年的缝纫机上缝缝补补。直到今天,在超市见到一个盒子的时候,我还是会下意识地说道:买上这个可以给奶奶盛放阵线用。接着才想到,她现在在天国,应该已经用不到针线盒了吧。 最后,借用《无问西东》中的一句话:逝者已矣,生者如斯。 在以后的日子里,应该收起悲伤,努力坚强地活下去,要更加好好对待自己所爱之人,如果爱她,就一定要大声地告诉她。这一定是奶奶也天堂所希望看见的。 情人节 卤蛋同学,今天是我们一起度过的第一个情人节。很巧,也是我们在一起的第 214 天。 和你在一起的这大半年里,每每想到你,我的嘴角都会露出微笑,每每拥抱你,我的内心都感到无比幸福。和你在一起,总有说不完的话,谈不完的心,也有逛不完的商场和公园。 你我从相识到相知再到相爱,每一步都像是上帝安排好的,十分自然地铺在我们面前,但又留下了许许多多值得我们回味的故事。 卤蛋,我爱你,就像爱生命。
Windows 桌面程序开发一些方案 开发 Windows GUI 程序的方案有很多,接触过比较流行的大概有三种,一种是 C++和 Qt,一种是 HTML5+浏览器内核,最后一种是 C#+WPF。另外古老的 WinForms 和更古老的 MFC 也不多说了。 Qt 的跨平台特性得以开发的项目可以跨平台,而且各种 C++的组件非常丰富。但是但是 Qt 本身库的并不小,我也不是很喜欢 QML 那种 JSON 的书写方式,而且 Qt Creator 用起来也不太顺手,所以一般没怎么用过这种开发模式进行桌面应用的开发。 H5 和浏览器内核是一个不错的方式,可以轻松跨平台,而且 H5+JS 可以有很快的开发速度。主流的方案有 Electron、nwjs、cef 和 wke(其中弹幕派所用的方案就是 wke),但是 Electron 同样体积巨大,不利于应用的分发。wke 虽小但也很久没有更新,内核很老,bug 比较多。最近志鹏同学正在研究之前 wke 开发者新开发的 miniblink 内核,相信这个方案会比较优秀。 WPF 必须依赖于.NetFramework,所以无法跨平台,而且 XP 也不自带.Net,需要用户安装。另外,XAML 虽然写起来麻烦一些,但是开发漂亮的 GUI 还是比较方便。而最新的 UWP 技术也利用了 WPF 的 XAML 进行 UI 的设计,转型起来并不困难。 起因 上周受到卤蛋同学的启发,如果需要使用 C++开发 GUI 程序难道只能用 Qt(或者北邮老师专用的 ege)?能不能使用熟悉的就技术来开发呢?于是在 Windows Dev Center 里找到了使用 C++和 XAML 开发 UWP 程序的方式。粗略看了一下,大概是使用经过微软扩展的 C++,名字叫 c++/cx,基于 Windows RT,但是不受.Net 的托管,也就是要自己处理垃圾回收(这方面理解不够深入,感觉大概是这个意思吧)。 但不管如何,可以使用 C++和 XAML 开发 Windows 应用还是让人眼前一亮,忍不住去尝试一下。所以,下面开工吧! 环境配置 开发环境必须使用 Visual Studio 2017,在新建项目中选择 Visua C++语言的 Windows 通用选项,建立一个空白应用即可。如果没有下载 SDK,上方会提示下载,如果没有显示该选项,说明需要在 VS 安装程序中安装一下 VS 对 C++ UWP 的支持。 在几秒种后,Visual Studio 就会打开一个新建的应用。假设你对 WPF 很熟悉,你可以看见熟悉的 XAML 设计器,在左侧的解决方案管理器中,也可以看见 C++的文件以代码隐藏文件的形式被嵌在 xaml 文件之后,包含一个.h 文件和一个.cpp 文件。整个工程的组织形式和 WPF 极其相似,这足以让人很激动了。 开始 Coding 进一步熟悉一下项目组织形式,可以发现,除了 C++的语法和 C#有区别,开发思路和 WPF 中的 C#是基本一致的,于是对应卤蛋的大作业题目,我建立了一些类的头文件和相关实现,需要注意的是字符串的处理问题,如果要支持中文需要使用 wstring(其实这个问题也并不仅仅是这种时候才会遇到),但关键在于 XAML 中的字符串使用了另外一种并不是 C++标准库中的字符串类型 Platform::String^,所以需要一些函数来在这些字符串中进行转换。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Platform::String^ Runtime::stops(std::string s) { return ref new Platform::String(stows(s).c_str()); } std::wstring Runtime::stows(std::string s) { std::wstring ws; ws.assign(s.begin(), s.end()); return ws; } std::string Runtime::pstos(Platform::String^ ps) { return wstos(std::wstring(ps->Data())); } std::string Runtime::wstos(std::wstring ws) { std::string s; s.assign(ws.begin(), ws.end()); return s; } std::wstring Runtime::pstows(Platform::String^ ps) { return std::wstring(ps->Data()); } Platform::String^ Runtime::wstops(std::wstring ws) { return ref new Platform::String(ws.c_str()); } 具体转换关系可以参考下图,图片来自http://www.cnblogs.com/nio-nio/p/3511843.html 在完成对底层数据对象的抽象以后,就可以着手设计界面和应用逻辑了。UI 的设计因为使用了 XAML,所以非常简单,所有的 XAML 特性都可以使用。而 UI 的事件绑定也和 C#非常类似,如果让 Vs 自动生成事件响应程序的话,它会在代码隐藏文件的头文件和实现文件中分别生成函数的声明和函数体,然后就可以直接在函数中写代码来控制它了。 简单的尝试 页面导航 首先说页面的导航,这里和 UWP 的概念非常吻合,只需要一行代码就可以实现。比如我现在需要导航到 UserPage.xaml,我只需要: 1 Frame->Navigate(UserPage::typeid); 就可以完成,有一点需要提醒的就是必须在.h 文件中引入 UserPage.xaml.h 才可以这样调用。 获取事件触发者 获取事件触发者也很容易,和 C#中思路相当,事件委托的 Handler 中触发者和参数会被以 sender 和 e 的形式传入,只需要做一些类型转换就可以获得到,下面是一个例子: 1 2 3 4 5 6 7 void App1::QuestionListPage::OnPointerPressed(Platform::Object ^sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^e) { StackPanel^ s = safe_cast<StackPanel^>(sender); int questionId =_wtoi(QA::Runtime::pstows( s->Name).c_str()); QA::Runtime::currentQuestion = QA::Runtime::questionList[questionId]; Frame->Navigate(QuestionPage::typeid); } 生成 XAML 元素 不多说,直接贴上代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 QuestionListPage::QuestionListPage() { InitializeComponent(); vector<QA::Question*>::iterator iter; for (iter = QA::Runtime::questionList.begin(); iter != QA::Runtime::questionList.end(); iter++) { auto questionStack = ref new StackPanel(); QA::Question *question = *iter; auto info = question->getInfo(); auto questionTitle = ref new TextBlock(); questionTitle->Text = L"问题:" + QA::Runtime::wstops(info[0]); questionTitle->FontSize = +30; auto userName = ref new TextBlock(); userName->Text = L"用户:" + QA::Runtime::wstops(QA::Runtime::currentUser->getUserName()); questionStack->Children->Append(questionTitle); questionStack->Children->Append(userName); questionStack->Name = question->getQuestionId().ToString(); questionStack->PointerPressed += ref new Windows::UI::Xaml::Input::PointerEventHandler(this, &App1::QuestionListPage::OnPointerPressed); questionList->Items->Append(questionStack); QA::Runtime::currentQuestion = question; } } 以上代码在界面初始化以后动态加载了一个问题列表,其中包含了 Title 和 UserName,并且给 StackPanel 绑定了一个叫做 OnPointerPressed 的事件。(就是之前获取触发者中的代码) 一切都非常简单,不得不说微软已经把 c++/cx 改造得很像 C#了,甚至连委托都被加入了进去。 全局变量 最后想说说的就是应用中全局变量的实现。 本程序采用了静态类的方式实现了全局变量和一些基础方法的封装(比如各种字符串转换函数)。只需要把需要全局的变量设置成 static,并且赋予初始值,在需要的时候调用即可。以下是代码中全局的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //classes.h class Runtime { public: static int userNum; static int questionNum; static int answerNum; static User *currentUser; static Question *currentQuestion; static vector<User*> userList; static vector<Question*> questionList; }; //classes.cpp int Runtime::userNum = 0; int Runtime::questionNum = 0; int Runtime::answerNum = 0; Question *Runtime::currentQuestion = nullptr; User *Runtime::currentUser = nullptr; vector<User*> Runtime::userList = vector<User*>(); vector<Question_> Runtime::questionList = vector<Question_>(); 对于全局方法,用法一样,不再赘述。 可用的资源 官方介绍:https://docs.microsoft.com/en-us/windows/uwp/get-started/create-a-basic-windows-10-app-in-cpp(目前也只找到了这一篇) C++ WinRT 介绍:https://msdn.microsoft.com/zh-cn/magazine/mt745094 官方 GitHub 仓库:https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples(每个例子的 C++文件夹中都是一些示例,文档有限,这些例子非常珍贵) 后记 说应用跑不起来是假的,自己写的应用,含着泪都要让它跑起来 不过仔细思考一下,如果不是必须要用 C++,这种开发方案作用并不很大。首先资料太少,连 MSDN 上面都只能搜到 API,几乎没有任何示例,而社区中也几乎一丁点都见不到对这种方案的讨论,所以如果遇到坑基本只能自己靠经验解决(但如果太大呢),比如研究事件委托怎么写我就研究了半个晚上,没有 API 也没有文档。另外这种方案开发出的应用也只能跑在 Windows10 中,对于老版本的 Windows 和其他系统(感觉 Xarmain 也不会支持这种模式),所以应用范围也很受限。
时日已至伏末,先别问秋老虎可不可怕,仅是鼻炎已经足以让我怀疑有一个连的人在想我。 话归正题,兰州在我心中的形象由两部分,一半像兰州烟上(我最喜欢吉祥兰州硬盒上的那个红色镶金的图案)那样:悠悠兰州,九天揽秀,另一半是爽朗直接,像兰州公交车司机一样,可以跳下车跟出租车对骂。总体来讲,用人来形容就是一个很飘渺但又古朴纯净的三四十岁胡子邋遢瘦削的硬汉。总之,矛盾满满。不过这一点不想展开去说,说说最近在兰州的见闻吧。 按照惯例,每次回家都要去张掖路逛逛,每次去逛也都盼着能有点什么不一样,但又担心不一样的地方多了我会忘记之前是什么样子。不过到目前为止,我的担心都是多余的。对我来讲,上半年改变最大的大概就是共享单车了。张掖路也是如此,但不是漫山遍野的自行车。为了不让单车进入,步行街的路障间隙更小了——将将够钻进去一条腿——进去以后擦一把汗,开始感慨前些日子的减肥真他丫有效,不然老夫就要卡死在这里了。 为什么念念不忘的是张掖路?除了之前总在这里买些衣服鞋子,让我逐渐摸清哪家店的沙发睡觉舒服以外,就是人多。我喜欢这里,总能让我沾沾人气儿,但又不必拘束自己。我喜欢看这里的人。 有吹着口哨吊儿郎当的“小社会”,有大包小包打招呼都腾不出手的时尚 girl,有大胆往前走绝不向两边看的外卖小哥,也有讲着今儿鸡蛋多少钱亚欧超市是不是减价的大妈大爷,更多的是手扣着手面色红润的小情侣们。手里拎本儿 Kindle,眼睛像磕睡狗一样眯起来望着这条街的,可能就只我一个了。 看着,我突然发现一个大问题——每个人,他们,居然,都,在讲话!成群结队成双成对的人在互相讲话,独自一个的人要么在讲电话,要么不知嘴里在嘟囔着什么。“怎么会这样”我自言自语。 为什么人人都会说话,为什么人人都在说话,人人都在说什么话?想回答这几个问题,着实有些难度,也很难在一篇小记中去讲。但培根问我的一个问题很有意思:“很多科学实验证明,猩猩也有语言,那么它跟人有什么不一样呢?它们有意识么?”这个问题就更有意思了。我认为解答这个问题的关键大概在于大猩猩有没有形成判断动词——是,也就是英文中的 be 动词。如果没有,那么它们还不能领会“存在”,只能通过语言表达诸存在者。在这个角度看,它们没有存在意义上的意识。再来看人类,就很有意思了。他们不仅仅可以领会经验上的存在物,更能领会超验的东西。比如——金钱、国家、社会、甚至是爱情。这些存在者的存在,没有意识,是无法领会的。 话又说回到爱情。之前看过一个理论,如果情侣之前无话可说,就很难有幸福感可言。说明充分的交流沟通是人与人之间构成伴侣的一个非常重要的条件。回过神看见商圈门前的一对对们,虽然高矮胖瘦形态各异,甚至肤色都有所不同。但都可以相爱——他们的十指都紧扣在一起,开心地讲着什么事情。 说的也巧,最近从臻那边听到了两个真实的故事。 一个是臻的父母。 他老爸最开始不会做饭,直到他妈妈怀了他,他爸就磕磕绊绊在他妈妈的指导下学习做饭,每次他妈妈晚上饿了,他爸都二话不说去做夜宵,再后来,就是他爸一直做饭了。 他爸妈也不怎么吵架,就算吵得最厉害,也不超过半个小时,他爸就问他妈要不要泡个茶歇一会儿,他妈妈也欣然同意。他有一次问起他爸妈,“问题还没解决为啥就不吵了?”他妈妈说:“其实吵架的时候就已经想好要让一些步了,反正这么多年都是这样。”他爸爸说:“和你妈一吵架,我就心疼了,她肯定也知道我的想法了,所以我还是老老实实服软吧。” 另一个是臻在旅途中还遇到的一对金婚夫妇,在他们家中借宿了一天,听到了许多他们过往的故事。 夫妻俩都很大年纪了,奶奶叫那个阿公“酷哥”,阿公叫那个奶奶“宝贝”。那天阿公晚上要出去一趟,两个人还要拥抱之后,吻一下额头,才肯分开。奶奶趁着爷爷出去,偷偷告诉臻,她这一辈子最开心的事情,就是把当年那个连一句想你都不会说的闷葫芦,变成了现在只要离开一会儿,就关切的不行,还要用爱你结尾的老小孩~ 听完这些故事,当时的心情已是惘然。 现在回想,能拥有这样的爱情的人,该是有多可爱呀。一辈子不长,撞上好运气,能享受大富大贵的可能都比遇到这样一个人并且相处的如此之美的机会大太多。所以,要珍惜身边的人儿,你相信她,她相信你,两人都相信这样的生活,以后才可能就是如此了。臻,你讲,是不是这样? 自张掖路一游之后,我对黄河风情线念念不忘。所以决定沿黄河南岸分别来一次骑行和跑步。 一路上最有意思的事情莫过于穿行在沿途的广场舞们中间,伴着异域风情高难度的新疆舞(是的,兰州今年流行这个),她们根本不为所动,就像冲进了一个正在被扭曲中的魔方,此情此景用一个字来形容,就是群魔乱舞。 另外,跑步的时候我还答应了一件事:未来要和“吃货”(此处简写)一起去东营逛逛,看看共和国最年轻的那片土地。或许,那边会的是一个比我曾经想去的巴颜喀拉山更神奇的地方呢。不,两个地方都要去,一起去看看究竟哪里更是希望。此处 at 她。 在黄河北岸骑车,穿过兰州音乐厅和城市规划馆的时候,我忍不住驻足,去欣赏她们。很有意思,一个被霓虹灯过度装点而另一个是被黑夜过度吞噬。终于,兰州也不再是那片朦朦胧胧的工业灰,她变得清晰年轻性感起来。 最后,贴几张雨后兰州的照片作结束吧。
好电影是值得多看几遍的。 说实话,这几个月我都没有进电影院,更没有看过最近有争论所谓的这些国产烂片或者说是美国大片。对这部电影,虽然没有对比,但不能阻碍我认为它是一部好片。 虽然两次看的是同一部电影,但我每次的关注点一定是完全不同的。第一遍是随着影片的进程,不去思考过多,而是把自己融入电影之中,心情和大脑都去主动跟随剧情的发展,完全按照导演和编剧的思路欣赏。如果这是一部好片,那么心情一定会起起伏伏,倘若恰巧情节还比较紧凑,那么一场电影看下来必定是淋漓大汗。第二遍呢,我会选择从片中走出来,在看的时候去思考人物的性格、导演对情节的安排以及场内观众们的表现,看完后必定会有许多新的发现,有时候或许还能看出哪里是被引入的时候经过剪裁的,人物应该在哪里可以更活。 首刷的时候,我大概和其他观众一样,随着两个小姑娘的视角,内心跟随她们一起成长,一起对父亲肃然起敬,甚至国歌响的那一刹那,我也跟随她们一起感动。 二刷的时候,我注意到了几个有趣的细节。 第一个是父亲不顾村里人的笑话,不顾官员的嘲讽,更不顾录像厅老板的眼神,每一次出场的眼神都温和却坚定;但却因自己特殊照顾女儿却导致女儿差点被体育学校开出流泪,这是多坚强的泪水! 第二个是父亲不论是在女儿消极怠练还是她们跑去参加别人的婚礼惹他生气甚至是女儿在教练那边学到了新的技巧而趾高气昂地嫌弃他的办法老旧的时候,他都没有打过一次女儿(每次都是她们的哥哥背锅)。父亲虽然严厉,但绝不毒辣,他对女儿的爱不亚于天下任何的父亲。 第三个是我认为是一个片段可能被删减,大概是父亲应该花几句话的时间去解释一下他为了让女儿摔跤不只是为了自己的梦想,还是为了女儿摆脱印度社会对于女性的不平等,让他的女儿过得更好一些。虽然在婚礼后新娘和女孩们的对话中可以看出一些,但依旧感觉不够浓厚,毕竟从父亲口中讲出来可以让电影主题更加深化,情节也更合人物性格一些(更可以让许多认为父亲是在强加自己的意志在女儿身上的人闭嘴)。而这一点我认为在中国人的心里认同性还是挺高。感谢牛三岁提供的生动素材,比如父母让孩子学习钢琴之类的乐器,这些乐器很可能是父母年轻时候未竟的梦想,现在让孩子去学,目的不仅仅是为了让子女去为他们比赛获奖,这也是提高子女的修养,子女小时候不理解,但一定终身受益。 第四点是我惊叹到导演居然在最后一刻安排将父亲关在小房间里。这一点对吉塔的成长太重要了,如果父亲在场,吉塔能赢,但这不是她自己,而是有她父亲的一半,她的意志还是父亲用来实现理想的一个附属。但是父亲被关在小黑屋里那一刻,吉塔变成了她自己,她在荧幕中完完全全活了起来,她赢的那一刻,她是在实践去做她自己。所以我看到父亲被关进去的那一刹那(虽然已经知道情节),还是深深舒了一口气,为吉塔感到高兴。 最后一点,是细细品味后,我惊讶到这部电影一切都是恰到好处。首先,这个故事的背景几乎没有只有印度人可以看懂的印度文化,里面表现出印度专有文化的几个镜头基本不用理解就可以完全明白。再说到这个故事,本身是一个励志故事,大概就是一个被所有人看不起的穷小子成功逆袭的路线,把故事背景换到中国,换到日本,甚至换到美国,都可以找到类似的题材。第三呢,这个电影虽然反映了印度的男女不平等、童婚、甚至是官员不作为等等问题,但都是点到为止,没有一丝一毫的多余,在最后吉塔击败白人选手,印度国歌响彻电影院的那一刹那,这些批判什么都不算,这部电影的主题是爱国!但又不脑残。电影的节奏安排也非常合理,在情节最紧张的时候,观众们都屏息凝神,而几乎每一个笑点,可以让从几岁到几十岁的人都会心一笑。特别是到了吉塔最后比赛的时候,拍摄得更是精妙。我扫视一下电影院,发现虽然大家都知道最后肯定会赢,但是眼神里都是紧张二字,在每一个得分点出现的时候,我看见旁边的姑娘更是激动得和屏幕里赛场中的观众一起鼓掌(要不是这里是电影院的话可能就要欢呼出来了)。一部电影,能做到这一点太难得了。 所以,这部电影这么火,并不是偶然,它是一部每一个细节都用心雕琢的好电影。 今天也是母亲节,这部片让我想起来老妈这么多年来对我的培养。 她和片中的父亲不一样,没有严厉的要求,更从没有将她的意志加在我的肩上。我从小到大,她是我的母亲,更是我的挚友。 老妈对我的教育方式虽然和片中父亲虽然完全不同,但有着相同的胸怀。宽松的环境反倒让我可以思考得更加深入,有机会去读书去接触他人。 这些都让我从侧面更加解我自己,知道自己应该去做什么,更明白自己在做什么,更可以为自己所做的事情负责。 所以我很庆幸,我(我想父母也是)早已经过了上文中所说的吉塔父亲被关进小黑屋的那一关。 再矫情的话我也说不出了,不过此刻确实非常想给老妈一个大大的拥抱。 最后的最后,附上几张看完电影后在学校中见到的如画一样的美景吧: