2016总结

2016-12-27

  • 写下这份总结,也意味着我的大学历程进入了最后一个阶段了,心中不知什么滋味。

自我沉默

  • 回想本年的年初,我还在工作的边缘徘徊。虽然过程艰难,但对于自己的追求还是有了一定的回报,大学里我努力所获得的最大回报莫过于这份坚持,十年,二十年后,几十年后,无论何时的自己,在回头看看当年的往事,一定会无比感谢这份坚持。
  • 怎么说,这一年大概是几年大学历程中最忙于奔波的时候,无论是应聘,还是写程序,看演讲,参加月考。
  • 记得开春的时刻,我在踏上返校的途中,甚至手里还攥着一本 剑指offer,在细细钻研。现在想想还有些忍不住发笑。
  • 无论如何,在三月初飞机降落在哈尔滨的那一刻,我还是有些紧张的,毕竟这是两年以来第一次实践,也就是正式的应聘吧。这其中有一些趣事,直到十天前,我才恍然大悟,原来机会总是在某些时候不知觉的来到你的身边,只是你未曾察觉让其错过了而已。但是就算没有这些机会也不会妨碍你接近自己的梦想,这些机会不过是锦上添花罢了。
  • 整个三月,都在仔细的筛选各个公司的实习招聘中度过,不知道是因为投的比较谨慎,亦或者学校211名头还有些用处,竟然都没有被刷去简历,这也算是稍微松了一口气,但是与去年不同的是,今年大部分互联网公司,无论内推与否,均需要笔试这一个环节,令我十分疑惑,个人觉得这一环节是我最讨厌的部分,也是最没有用的部分,原因大家都心知肚明。
  • 时间转瞬来到了四月份,由于女友的邀约,伴着招聘带来的些许压力,我还是直接踏上了秦皇岛的旅途,我觉得在这里也许能找到一些不一样的新鲜事物。

说的好像一个艺术家。

  • 无独有偶,我未来的东家实习生的招聘正好在这时候进行笔试,幸而带上了吃饭的家伙,否则现在就是另一番景象了,但是哪有那么多如果。我倒是觉得一切的偶然,都和之前所做的某些事情有着必然的联系。
  • 实际上,我之所以讨厌笔试的原因中,有一点占了挺大比重,就是我的笔试通过率低到发指,不知道是不是耿直还是什么,虽然周围室友都有些小手段,我还是依然坚持自己的做法,而实际上我在算法这方面的确有所欠缺,我还是比较喜欢在实际项目中应用算法与数据结构,而不是生硬的编写它们,因为我在挺早的大一下阅读算法导论之时就发现,即使我将书中的数据结构或者算法用具体语言实现之后,我在实际中我也需要对其进行极大的修正才能应用起来,这就极大的浪费了我的时间和精力去实现它们。

    • 与之相反,我在后半期的阅读中,并没有可以的实现某个算法和数据结构,而是理解它们的思想,尝试着自己去证明课后的习题。这样节省了一大把的时间,也能让我更快的投入到新的学习中去。
    • 并且从中学到了如何真正的把书读薄,那就是按部就班的顺序阅读并不是一个好的阅读习惯,在目录中节选自己希望的章节才是真正的阅读方式,因为目录是作者对这本书的初次总结,没有什么概论能比得上书的目录了。
  • 在秦皇岛这段欢快惬意的日子里,我成功的度过了东家的笔试,也不知道为何,我竟然忘记了它,反而对其他的实习笔试耿耿于怀,但是这都并不妨碍之后事情的发展,相信自己做过的一切事情,这是大学中屡试不爽的心理暗示。

  • 日子渐渐的过去,我在四月初的某一天返回了学校,继续我的项目。

    • 说起这个项目,其实还算是一个比较曲折的经历吧。
    • 记得在15年的学期末,我鼓起勇气投递了某金服的内推,实际上是找了一个员工帮我内推自己的简历,记得当时年少无知的我做的简历,现在翻出来看看,真是谢谢那个前辈没有当场退回。
    • 结果自然可想而知,连简历都没有过。如果当时不了了之的话,也就没有后来的我了,所以我说,这个世界上没有什么如果。
    • 在得知简历没有过的情况下,我还是厚着脸皮请教了不足,被告知了最重要的,也是最终决定我成功的一点,便是项目
    • 在仔细询问了项目的类型之后,我终于知道了下一阶段的路线。
  • 实际上这个项目在本月的月初,也就是去秦皇岛之前就接近尾声,剩下的就是完善项目细节了,毕竟这是一个网络项目,各种奇怪的问题层出不穷,这期间学到的各种调试技巧令我获益匪浅。

  • 时间到了四月的中旬,那时候的我还是处于松懈等待的状态,几乎是去图书馆看书,吃饭,和上一届聊天,这种状态中徘徊。

  • 记得十分清楚,那是一个晚上,正和一个学长聊着要去什么公司的时候,东家的笔试通过短信就来了,那是夜晚十点半,也就是学校熄灯之后,通知隔几天去面试。

  • 从未有这么激动过,不知道是因为从未曾有过这种一线互联网公司实习的经历,还是其他因素,总之我还是在激动过后,冷静地准备起了面试的东西,记得那几天互联网的面试技巧被我翻了一个底朝天。

  • 实际上真正的到了那一刻,也没有想象中的那么紧张,几乎第一面的时候,毫无压力的通过了,直到走出酒店以后,我甚至能感觉到自己已经通过了,一面还是注重基础,很遗憾我现在也没能找到当时一面的面试官。

  • 终面的时候,是我印象最深刻的面试,记得当时走进酒店房间,就感觉一股厚重的技术大手气势迎面扑来,我当时第一个动作就是笑了一下,缓解了自己的压力,在自我介绍之后,我还是有意的撇开了自己比较薄弱的手写算法环节,而导向了自己精心准备的项目环节。不出意外,对于我这种本科生而言,这个面试官二话不说,直接 git clone 开始现场 debug 我的代码,这与我的设想有些出入,当时刚刚放松的心情,顿时开始忐忑起来,趁着面试官在扫我的代码的间隙,我拿出手机也快速的过了一遍自己的代码。
  • 饶是如此,我还是在这过程中因为紧张而忘记了某些细节,例如EPIPE的意义是什么?这个问题,在后来出门以后,我都有点怀疑自己为何迟迟答不上来,是不是穿越了。
  • 在紧张的半小时项目问答环节之后,还是在意料之中,开始了Linux系统内核的某些问题。我在此处犯了一个最傻的错误,“epoll 和 poll区别,在能不能这方面有什么区别”。

总之还是很安全的度过了终面,这里的趣闻还是和这个终面面试官有关。知道前几天我才知道,我大二年无意中在微博上关注的某大手,竟然就是我终面的面试官。

  • 这次紧张的实习面试在五月份终于落下了帷幕,而我也成功的收到了东家实习生的通知,在七月初那个湿热的夏天,欣然前往。
  • 在学校进行了一些无聊的请假手续之后,在七月初的我,踏上了实习的旅途,那时候我还远远不知道即将到来的是一个翻天覆地的新认识。
  • 在这短短的两个月实习中,我经历了第一次必修挂科,第一次周围自己的学校是最差的,第一次和小组合作一个完整的前后终端项目,以及实习考核,加试,到最后的九月份拿到offer,事实上也就是这短短三个月的时间而已。

事后回想起这段经历,依旧有些不敢置信。实际上,对于这段经历的总结还是要沉着,遇事不能急躁,做事可以果断,相信没有最差的结果,只有更好的事态发展。

  • 直到收取 offer 的那一刻,我才开始准备接下来的事宜,由于一直在安全领域有所涉猎,所以我这几年一直没有放弃这方面的追求,在些许 offer 中我还是有特地投递一个安全公司的,而这也为我下半年的经历有了一个举足轻重的填补。

下半年总结

这大概是大学生涯中,最后一段实习了吧。互联网的经历已经不能满足我,在互联网的浪边看了许久,是时候该沉淀一下

  • 无所畏惧大概是我学到的第一个意识。万事开头难啊
  • 效率至上也是互联网的另一个标准,回想起八年前我所看到的IT行业,又是一种天翻地覆的变化,所以是时候该沉淀沉淀自己被行业所浮躁的心。
  • 这的确是一家文化很稳重的企业,步步为营,不加班,谦逊是每个同事的自带属性,即便是总技术经理,亦是待人客气。

新的挑战

  • 前期在学校的我,接触的无非就是系统调用,研究最多的还是网络程序的编写。依赖于伯克利套接字(Berkely Socket)的实现,我从未想过脱离内核实现的TCP/IP协议栈来实现一些东西

或者换句话说,从未有跳出过这个思路实现功能的想法,这一回的经验给了我许多想法,现在的程序员都活在上一代程序员打造的象牙塔里,屏蔽了许多原本该头疼的事情,即使提高了所谓的开发效率,然而却也限制了开发思路。

Linux

  • 之前对于 Linux 内核的完全兼容性有一种盲目的自信,然而这一段实习却让我有了更好的认识,不要盲目相信 。这种细节的确需要时间的给予。
  • 在高性能网络开发中,最不可避免的还是网络开销,但是对于网络处理延迟却是被大家忽略了,或者换句话说,对比开发效率而被忽略了

    • 具体来说就是在一个网络包的生命周期里,我们关注的延迟往往是在网线/空气中的传输时间,但有多少人意识到,实际上从网卡收到包开始,到这个包真正送到你的应用层程序手里,者之间可是经历重重磨难。
    • 这对于高性能网络程序而言,可不是能够忽略的,诚然内核的TCP/IP协议栈为我们做了许多,但是Linux内核的TCP/IP协议栈为了顾虑许多不同的特定场景,必定不会为某方面做极致的优化,所谓中庸之道如是者也。
    • 那我们能做的就是在某些特定场景下,使用极致手段来替代,例如Intel的开源解决方案DPDK就是一个神奇的事物。
  • 当一个Linux不再像之前的那样方便了呢?

    • 实际上,前三年的Linux学习是在是太过风顺了,一切都由包管理或者预装包来解决,导致一些细节没有学习到。
    • 例如网络配置,软件包安装,卸载,内核更换等。
  • 实际上还是读书太少,眼界太窄,太多东西想学,但是又不知从何开始的感觉。

其他

  • 这个公司思维前卫,或者说敢于尝试新坑,与大公司相比,这种没有代码包袱的公司更易于尝试各种业界新框架,即便没有太过成熟的完整解决方案,但是可以靠技术人员的专研不断完善,这对新手而言是在是个不小的挑战,但也是一个很好的历练
  • 近来一直在研究高性能网络的软件架设,从最开始的Socket到后来的网卡直接读取DPDK,一直到心啊在研究IA架构下的软件代码优化问题
  • 一度让我以为很难有这种机会接触底层,现在却摆在我面前,不得不说这趟实习真的很值得。

  • 事实上,对于DPDK这个产品只能是之前的我对这个网络世界没有太多的认知,陷入的一个后花园,一个由操作系统提供的后花园,所以,在遇到新的问题不容易跳出思维圈

  • 近年来,很多产品敢于用新的高性能框架来实现,而这就是大公司所不能的地方,一方面是浪费人力物力时间,另一方面也是因为线上产品已经成型,贸然行动容易带来损失,加上老板如果不是技术出身,就更会否决这种想法,最多画地为牢,送你一纸集群实验罢了。

  • 如今来兴起的 由KVM作者共同编写的数据库核武器ScyllaDB号称是Cassandra性能的十倍,靠得就是DPDK带来的网络包零拷贝,其组件中的Seastar 网络库使用DPDK构建了一个用户态的TCP/IP协议栈,这个让我心动不已,正愁没有什么方向写一个自己的协议栈。

  • 在上一份实习的时候,做的工作相对而言比较繁杂,或者说是自己的能力还不够突出,在这里我反而能够倾尽全力来释放一下自己,毕竟这是自己最擅长的领域,Linux底层C网络软件开发。很令人着迷,捡起了许多渐渐被淡忘的知识

GCC

  • 首先是 gcc 这个著名的编译套件,其中编译链接选项是一个普通学校的玩具程序中无法体会到的,虽然有意识到这一点,但毕竟是纸上谈兵,这回在使用DPDK开发编译的时候,渐渐意识到这个问题,实际上没有什么好记录的,就是一段官方介绍,比什么都好用:

    -llibrary

    -l library

    Search the library named library when linking. (The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.)

    It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, ‘foo.o -lz bar.o’ searches library ‘z’ after file foo.o but before bar.o. If bar.o refers to functions in ‘z’, those functions may not be loaded.

    The linker searches a standard list of directories for the library, which is actually a file named liblibrary.a. The linker then uses this file as if it had been specified precisely by name.

    The directories searched include several standard system directories plus any that you specify with -L.

    Normally the files found this way are library files—archive files whose members are object files. The linker handles an archive file by scanning through it for members which define symbols that have so far been referenced but not defined. But if the file that is found is an ordinary object file, it is linked in the usual fashion. The only difference between using an -l option and specifying a file name is that -l surrounds library with ‘lib’ and ‘.a’ and searches several directories.

  • 上述这段话的只能观点就在黑体字附近,-l的顺序是能够影响编译的正确结果的!

内联汇编

  • 这是一个C/C++程序员的灰色地段,这几年一直找不到时间好好学习,但是却很有必要
  • 借此机会也好好实战了一番,总结了一点经验,记录一下
  1. 一般使用的是 AT&T格式

    • 格式为command src dst
    • 寄存器以%%开头,常量以$开头,基址寻址使用括号()而不是中括号[]

      __asm__("一系列标准的AT&A汇编指令"
          : “输出操作数1, 输出操作数2...”
          : "输入操作数, 输入操作数2, ..."
          : "寄存器1修饰, 寄存器2修饰, ..."
      );
      

      这是最标准的扩展内联汇编代码,一般看到的形式都是如此。__asm__是因为 ANSI C如此规定的。

      每个冒号前后是一个不同的分组,分组的意义已经写出来了,每个分组里可以有许多并列的个体,用冒号隔开,即便某个分组没有,也不能把冒号省略

      寄存器修饰的意思就是,告诉GCC我修改了这些寄存器的值,所以你别再用这个寄存器去存你要存的值了。

      输入/输出操作数略微有些复杂,情况比较多,其中之一就是我要把这个变量放到某个指定寄存器里,或者我这个变量不可以缓存进寄存器,只能直接在使用源地址上的值。

  • 之所以叫内联汇编,就是因为它是介于汇编和C/C++之间的桥梁,也意味着可以同时操作两边的变量。

    int a = 0, b;
    __asm__("movl %1 %%eax\n\t"
            "movl %%eax %0\n\t"
            : "=r" (b)
            : "r" (a)
            : "%eax"
    );·
    

    C/C++任意连续的字符串都会被拼接成一个,而\n\t是为了生成的汇编代码更好看,可以去掉。

    =r是代表输出,r代表的是输入,其中r的意思是选择任意一个可用的寄存器,可以将其记作register的缩写。

    类似%0这种的就是,从第一个冒号开始,往后的操作数的代号,按顺序递增。这个代号代指实际某个寄存器,而此时a, b使用的是不同的随机分配的寄存器

  • 如果想指定寄存器

    : "=a" (b)
    : "b" (a)
    

    这样就将变量b 指定存放在寄存器eax, 变量a制定存放在ebx

    : "=c" (b)
    : "0" (a)
    

    这样的意思是,ab 同时使用一个寄存器ecx, 同理

    : "=r" (b)
    : "0" (a)
    

    这个的意思就很明显了,两个变量使用相同的一个寄存器,但具体是哪个寄存器就不懂了。

    上述代码,不需要在寄存器修饰列表中添加任何寄存器,原因是这些指定的寄存器GCC知道它被用作它途!只有在汇编指令中显式使用了,才会需要加入这个列表。

  • 但是,一定要注意,不要想当然的去写内联汇编,现代的GCC编译器,能做的远比你想象的多!换句话说,你写的汇编不一定能比你写的C快!如果你对计算机程序的优化并没有很深刻的了解学习的话,请不要擅自使用内联汇编,贻笑大方。

Makefile

  • 网上十分多的经典教程,记录一点就是,Makefile实际上挺简单,只要记住一点就行,想要什么就生成什么,没有什么就生成什么,步步为营.out<-.o<-.c

  • 我还是倾向使用CMake这种现代化的东西。

内核

  • 记住一点,内核作者也是一个程序员,只要是程序员就会犯错。
  • 未来的自己总会为之前犯过的错买单的。遇到内核接口变动,不要慌,换一个内核吧。
  • 编译内核是一个讨厌的工作,尽量找合适的有预编译的内核版本吧。

Shell

Docker

  • Docker的意义就是隔离环境,在Windows下搭配Vagrant效果更佳,但是如果在Linux下的话,大可不必如此,虽然说网上到处是关于Docker的坑,但是在某些时候还是挺好用的
  • 例如测试,当一个项目依赖越多时,就越不容易测试,首当其冲的就是环境搭建!而Docker只需要搭建一次就好,远比虚拟机轻量,方便。镜像可以存储在云端,也可以存储在本地,各处去run
  • 另一个就是,在开发时会遇到,文件拷贝同步的问题,这时候可以使用Dockervolumn功能,来实现目录的同步,很方便。

    docker run -v `pwd`/host/path:/container/path container-name
    

    连接一个Docker的时候,尽量使用exec

    docker exec -it container-name /bin/bash