起因在于之前使用全键盘进行Linux操作的时候,无意间碰到了Page Up按键,开启了奇妙体验。把一条命令打到一半,然后按Page Up/Page Down就可以匹配历史记录,实在是太舒服了。

然而在使用Ubuntu的时候,突然发现就没有这个功能了,StackOverflow一查,才发现原来是需要手动配置的。

修改/etc/inputrc或者~/.inputrc,这么修改一下,就能把这个功能和Page Up/Page Down按键绑定:

$if mode=emacs

"\e[5~": history-search-backward
"\e[6~": history-search-forward

$endif

简单码两句。

主要是解决了对于Android多线程机制默认操作的疑惑。

学过Android开发的大概大部分知道,Android的前台线程是UI线程。但是根据开发组的意图来看,这个线程应该是空闲率越高越好。如果学过计算机网络的话,这点可以很轻松的理解,越高的资源占用率意味着越大的延迟,只有当占用率尽可能低的时候,才能满足快速、及时响应的需求。

而多线程在解决了线程占用率问题(通过将大部分高时延的任务下放到后台线程完成,这些线程的响应实验要求相对较小)的同时,也带来了同步问题。Android解决同步问题的方法非常简单粗暴,“谁创建,谁拥有”。换个说法,只有创建线程不安全对象的线程本身才具有修改线程数据的权利。

那么,如果外界有修改线程不安全对象数据的需求的话,那么就必须由所有者线程主动提供一个入口,以所有者线程的身份对资源进行操作。Android为这个理论提供的时间就是Message, MessageQueue, Looper, Handler四件套。四件套的具体功能就不做赘述了,网上到处都是,而其中最为关键的,也是最常见到的组件之一,就是Handler。作为外部线程来说,调用Handler的各类message方法可以向所有者线程发送申请以修改资源;作为所有者线程来说,实现handleMessage方法可以从外部接收,并且自定义地处理请求。

然后困惑之处就在于,按照书上的方法,只要实现一个Handler类就能完成对这种资源的访问。四个组件既然是一套,那为什么只要四分之一就能完成功能呢?最重要的绑定线程的工作,又是在何处完成?

带着这个疑问,去网上转了一圈,最后在官方文档找到了答案( https://developer.android.com/reference/kotlin/android/os/Handler )。其中的奥妙就在于,我们的确只需要实现Handler就行了,在Handler类实体化的时候,内部就定义了一系列的操作,包括创建四件套中除Message外的其他两件(Message在四件套中主要作为数据型,而非逻辑型组件,只用于传递Handler请求)。根据书上所说,Looper类的构造器甚至还是私有的,一看就是不希望别人去动的那种。在IDE中多按几次ctrl看看源码,就能发现Handler的无参构造器会调用Looper.myLooper()方法,继续刨的话可以看见,Looper对象已经被ThreadLocal作为线程私有变量保管得好好的了。

总而言之,在日常使用Android Handler的时候,只要实现这个类就行了,其他工作其实是已经由Android框架本身完成了的,放心大胆使用即可。

鬼知道为什么0202年了还要用这个老古董

嵌入式老师今年不打算用硬件板子,说是会增加学生负担,于是给了一个IDE安装包。

我马上就下载下来了,很快啊。

仔细一看,安装包大小100多M,感觉好厉害,开发环境原来可以这么精简。结果安装界面一打开,一股熟悉的16色风格扑面而来,这才意识到原来是上个世纪的软件,最新版本1.2。安装,破解,一气呵成。

打开后,最直观的感觉就是字小得不行,图标都不带文字说明的,要靠鼠标悬浮去读取按钮的功能。

新建项目,创建C文件,编译运行,遇到了第一个问题:

error starting external process process error code 87 (0x57)

这是兼容性的问题,在右键菜单里调整到兼容模式运行基本可以解决问题。我选的是Windows 98/ Windows Me那项,可以正常工作。

然后回到ARM的入门操作,就遇到了第二个坑,汇编格式。没想到这个编译器还是活在按缩进来决定语义的时代(你是Python吗.jpg),折腾了老半天才整出一段正确的汇编代码。

num     EQU     2            

        AREA    code,code,readonly
        ENTRY

start
        MOV     r0, #0
        MOV     r1, #5
        MOV     r2, #2
        BL      func

stop
        SWI     0x123456

func                      
        CMP     r0, #num
        MOVHS   pc, lr
        ADR     r3, JumpTable 
        LDR     pc, [r3,r0,LSL#2]

JumpTable
        DCD     DoA
        DCD     DoS

DoA
        ADD     r0, r1, r2
        MOV     pc, lr

DoS
        SUB     r0, r1, r2
        MOV     pc,lr
        END     

总结一下,要注意的地方是伪指令不能顶头写,不然会被当成标签,然后就报错。

这操作太神奇了,完全没有想到还有这种妙用

这个表达式主要用在宏定义和条件分支两种情况:

一是宏定义的时候,可以避免由多个语句组成的宏,在没有{}的控制语句下产生错误的语义,如:

#define func() funcA(); funcB()

...

if(cond) func();

就会导致funcAfuncBcond == false的情况下产生错误的语义(原本计划是两条语句都不执行,而现在funcB必定执行)。

简单直观的解释:

if(cond)
    funcA();
funcB();

do {...} while(0)(注:这里while后面没有分号,目的是符合C/C++在语句后添加分号的风格,在调用宏的时候再加)则会被系统认为是一个单独的语句块,在面对分支结构的时候,就能够被正确地视作原子操作进行处理。

为什么不用{},而要用更复杂的do {...} while(0)呢?

直接上代码:

if(cond) { funcA(); funcB(); };

这个时候,原本在func末尾的分号成了累赘,反而会导致报错。这也是出于保持代码风格一致的考虑。

由于宏函数不存在返回值一说,而多语句的宏函数显然不会、也不应该产生返回值,所以无须担心把这类宏函数放到赋值语句中会怎么样——这和void类型的函数的行为是一致的。

二是简化条件汇集的情况。考虑这么一种情况:

if(condA) { funcA(); }
if(condB) { funcA(); funcB(); }
if(condC) { funcA(); funcB(); funcC(); }

如果真的在代码里这么写的话,想必是一座宏伟的代码金字塔了。随着条件增加,重复而又意义不大的函数体,会变得越来越长。于是有人考虑使用goto语句,合并重复的语句:

if(!condA) goto end;
funcA();

if(!condB) goto end;
funcB();

if(!condC) goto end();
funcC();

end:
    ...

但是对软件工程有所了解的人都会知道,在代码中使用goto是一种不清真的行为,会对代码的可读性和入口的单一性产生负面影响,影响后续的开发和维护工作。

继续观察,注意到上面例子中所有的情况下,只要到了某一条件不满足,就直接跳转到整个代码块的末尾。有什么语句可以跳过块内所有的剩余语句,立即跳出语句块?答案呼之欲出,break!于是,do {...} while(0); 作为能够使指令只执行一遍的循环结构,再次登场:

do {
    if(!condA) break;
    funcA();

    if(!condB) break;
    funcB();

    if(!condC) break;
    funcC();
} while(0);

这样既继承了原始代码不使用不规范函数的原则,又吸收了goto版本缩减重复代码的优点。
当然,如果有人想ifif,就当我没说

参考:http://www.spongeliu.com/415.html

参考文章中还提到了定义空宏和代码块功能,感觉这两个有点牵强。。定义空宏的话直接用分号应该也行,代码块功能也和循环语句没有必然的联系。

https://stackoverflow.com/questions/46667659/kubernetes-cannot-access-nodeport-from-other-machines

本来第一次配置集群的时候,是可以在局域网内访问的,之后关机重启,遇到DiskPressure这个Taint“污点(标记)”的时候,重新配置了一次集群(即kubeadm resetkubeadm init),然后以为是服务没跑起来,测试以后发现只能在本机访问了。

nmap扫了一遍端口,目标端口显示的不是closed,而是filtered,然后也隐隐猜测和防火墙有关,万万没想到,原因正是出在iptables上。而后了解到,firewalldiptables是两个不同的Linux防火墙工具,有着各自独立的规则。因而在配置Kubernetes时禁用了firewalld之后,iptables还是在工作的。而NodePort的访问,需要进行流量转发。

简单粗暴的解决方式(非生产环境):

iptables -A FORWARD -j ACCEPT

不过这个地方我有点没想明白:kube-proxy可以将Pod内的端口映射到主机端口的监听上,而在Linux中,默认的端口监听是监听在所有可用的interface网络接口上的,监听特定的网络接口是需要编程显式声明的,理论上不会需要进行不同网络接口间的流量转发。