17
2017
局域网中通过 TUN 和软路由实现链路聚合
可怕,都两年没写东西了……
背景
前段时间奉老板命为实验室搭建 Linux 计算集群。之前实验室跑计算都是直接远程登录机房的 Windows 主机的,完全没有作业调度。听说要搞个 Linux 集群我还是蛮有兴趣的。(怎么又搞运维)
然而,实验室当初显然没有按照集群的配置来采购硬件。起步的其实只有三台计算用的服务器,没有配置存储服务器和网络设备。最初我是想至少添加一台登录+存储用的服务器吧,不然这个集群的拓扑实在有点坑。不过学长和老板觉得先用现有硬件搭建一个看看吧,所以我就拉了个旧主机将就着当登录节点了。既然如此,也没必要怂恿实验室加上万元添置 RDMA 模块了,搞个便宜的千兆交换机凑合着用就行了。
未名实验室的集群就基于这套不怎么专业的设备搭建起来了。为了充分榨干这一套设备的性能,后期我在文件系统、网络、软件配置等方面做了很多优化尝试。其中一个想法是:
集群中每台计算节点都装了四块千兆网卡,是否能够将所有网卡都利用起来,组成一个“四千兆”网卡,提高带宽?
警告:本文作者计算机网络课划水太多,文章充斥着大量非专业表述,请谨慎参考。
讨论
广义地讲,同时利用多个物理网卡发送/接收 packet,以提高网络带宽(突破单网卡带宽)和可用性的技术,都叫做链路聚合(Link aggregation)。
在网络协议栈中,不同层面都可以实现链路聚合:
- 物理层(layer 1):Wifi 一根天线信号不好,装两根(胡诌的);
- 数据链路层(layer 2):Linux 下的 Ethernet Bonding;
- 网络层(layer 3):操作路由,以数据包、连接或者目的主机为单位将包分散在多个端口上发送/接收,如 ECMP 以及后面会讲的软路由方法。
- ……
每种方法都有适合的应用场景,并行的粒度也不一样。比如说,作为一台网页服务器,要和世界各地大量的主机建立连接,改一改路由,让某些地区的连接走端口A上绑定的IP访问、另一些地区走端口B上绑定的IP访问,就能够均衡负载并且提高总带宽了,虽然单个连接还是不能突破单网卡带宽。
而我更在意的是局域网中两台机器之间的通信,希望单个连接也能够突破单网卡带宽。比如,集群两台机器之间要通过 NFS 或者通过 SSH 拷贝数据,我希望能够通过四块网卡的聚合达到 500MB/s 的传输速度。
此外网络拓扑也得明确一下:所有主机通过一台无网管千兆交换机相连。背板带宽足够大,不会成为瓶颈。
各种方法
先约定一下标记。假定我们有三台 Linux 主机(HostA、HostB、HostC),然后每台主机分别有两块网卡 eth0 和 eth1。三台主机的 eth0 上绑定的 IP 分别为 10.0.0.10/24、10.0.0.20/24、10.0.0.30/24;eth1 上绑定的 IP 分别为 10.0.0.11/24、10.0.0.21/24、10.0.0.31/24。
Ethernet Bonding
Ethernet Bonding 是 Linux 内核支持的一种将多块物理网卡组合成一块逻辑网卡的技术。IP 绑定在逻辑网卡上,链路聚合是在数据链路层进行的。
网上有很多资料,不再赘述了。比如:
对于几种以提高吞吐量为目的的 bonding 模式(比如 balance-rr),发送包是打散到每个物理端口上的,但接收包的负载均衡是通过不断发送 ARP 广播将 IP 绑定到不同端口实现的。由于 MAC 协议的限制,同一时刻一台主机上 MAC 表中某个 IP 只会对应到一个端口,发送到这个 IP 的包也只会涌向一个端口(虽然是从两个端口发送的)。因此,bonding 不能实现我需要的链路聚合。
ECMP 和 软路由
Equal-cost multi-path routing(ECMP),等价多路径路由,简单来讲就是为同一个目的地址配置“多个”“等价的”路由。
比如添加两个 ECMP 路由,使得到 10.0.0.0/24 可以走 eth0 和 eth1 两个端口出去:
ip route add 10.0.0.0/24 nexthop dev eth0 nexthop dev eth1
或者,要经过多个默认网关接入 Internet:
ip route add 0/0 nexthop via 网关1 dev eth0 nexthop via 网关2 dev eth1
Linux 内核根据报文源/目的 IP、源/目的端口(UDP/TCP)进行 hash 决定使用哪条 ECMP 路由发包(参考资料)。因此,至少对于一个 TCP/UDP 四元组,路由是固定的。另一个问题是,ECMP 只是决定发包的路由,不能做到接收包的负载均衡。如果要做收包的负载均衡,则需要发送方也配合、向两个端口发送。
由于我所有设备的所有端口是在一个局域网/交换机下,路由直接由 ARP 表决定了。同一时刻一个IP只能对应一个MAC,接收包只会从一个端口进来。所以只加入上面的 ECMP 路由、不做进一步配置的话,目前的效果是:
- 发包能够突破单网卡带宽,但必须建立多个连接(使得 ECMP hash 不同)
- 单 IP 收包还是只能从一个端口进来,不能突破单网卡带宽
所以,前面这样做 ECMP 还是没法实现我需要的链路聚合。
改进1:接收包负载均衡
其实,前面说的“同一时刻一个IP只能对应一个MAC”并不完全正确,其实每个网卡都有一个单独的 ARP 表。比如,可以通过 ip neigh show dev enp5s0
查询 enp5s0 上的 ARP 表。
这有什么意义呢?如果我们能让不同网卡拥有不同的 ARP 表记录:HostA 的 eth0 认为 10.0.0.20 在 HostB 的 eth0 上,HostA 的 eth1 认为 10.0.0.20 在 HostB 的 eth1 上…… 反之 HostB 看 HostA 亦然。这样,在做到发包的负载均衡的同时,也能够做到收包的负载均衡了。
怎么让不同网卡拥有不同的 ARP 表记录呢?最简单的方法是…… 多买几个路由器,把一组网卡(比如所有主机的 eth0)隔离在一个广播域中。不过,也可以用静态 ARP 表完成这件事:
arp -i eth0 -s 10.0.0.10 <MAC of HostA's eth0> arp -i eth1 -s 10.0.0.10 <MAC of HostA's eth1> arp -i eth0 -s 10.0.0.20 <MAC of HostB's eth0> arp -i eth1 -s 10.0.0.20 <MAC of HostB's eth1> arp -i eth0 -s 10.0.0.30 <MAC of HostC's eth0> arp -i eth1 -s 10.0.0.30 <MAC of HostC's eth1>
虽然乍一看有点难以维护,但实际上如果端口数量完全一样,所有主机使用同一份 ARP 表就行了。
改进2:以包为单位进行负载均衡
ECMP 的负载均衡“粒度”不能满足我的需求。其实以包为单位的负载均衡非常容易做的:用 iptables 给发包随机或者依次打上 1、2、…… N(N 为网卡数)的标记;然后设定策略路由,根据标号选择路由就好了。
iptables -t mangle -A OUTPUT -d 10.0.0.0/24 -m statistic --mode nth --every 2 --packet 0 -j MARK --set-mark 1 iptables -t mangle -A OUTPUT -d 10.0.0.0/24 -m statistic --mode nth --every 2 --packet 1 -j MARK --set-mark 2 ip rule add fwmark 1 table 10000 ip rule add fwmark 2 table 10001 ip route add 10.0.0.0/24 dev eth0 table 10000 ip route add 10.0.0.0/24 dev eth1 table 10001
不过还没完,Linux 内核有一个叫 Reverse Path Filtering 的保护机制。如果开启了“严格模式”(我的 Arch Linux 上默认如此),发送包的端口和上面绑定的 IP 不一致时会被内核过滤掉。可以通过内核参数 net.ipv4.conf.<interface>.rp_filter
控制这一行为(参考资料):
rp_filter - INTEGER 0 - No source validation. 1 - Strict mode as defined in RFC3704 Strict Reverse Path Each incoming packet is tested against the FIB and if the interface is not the best reverse path the packet check will fail. By default failed packets are discarded. 2 - Loose mode as defined in RFC3704 Loose Reverse Path Each incoming packet's source address is also tested against the FIB and if the source address is not reachable via any interface the packet check will fail.
一般设置成 2,也就是“宽松模式”就好了,这样只要验证源 IP 属于本机就行了:
net.ipv4.conf.eth0.rp_filter=2 net.ipv4.conf.eth1.rp_filter=2
现在,接收和发送双方的负载均衡都实现了,并且是 per-packet 的。如果用 iperf 测试,应该可以看到单连接就能够塞满多块网卡带宽。
到了这里,和 ECMP 也没啥关系了,纯粹是 iptables 配策略路由的普通软路由方案。
利用 TUN/TAP 设备简化软路由管理
前面用不同网卡绑定不同的静态 ARP 表绕开 ARP 广播实现了更复杂的软路由。其实也可以加一层 TUN/TAP 设备(通俗地讲就是一种“虚拟网卡”)来做真正的软路由,不用手动写 ARP 表了。
首先在每台机器上都创建一个 TUN 设备,随便绑定一个 IP:
ip tuntap add mode tun tun0 ip addr add 10.99.255.10/32 dev tun0 # HostA ip addr add 10.99.255.20/32 dev tun0 # HostB ip addr add 10.99.255.30/32 dev tun0 # HostC
然后接下来就是要设置 10.99.255.[10,20,30] 这个“虚拟”网段内的路由。这完全是软件上做的,还是随机打标签+策略路由:
iptables -t mangle -A OUTPUT -d 10.0.0.0/24 -m statistic --mode nth --every 2 --packet 0 -j MARK --set-mark 1 iptables -t mangle -A OUTPUT -d 10.0.0.0/24 -m statistic --mode nth --every 2 --packet 1 -j MARK --set-mark 2 ip rule add fwmark 1 table 10000 ip rule add fwmark 2 table 10001 ip route add 10.99.255.10 via 10.0.0.10 dev eth0 table 10000 ip route add 10.99.255.10 via 10.0.0.11 dev eth0 table 10001 ip route add 10.99.255.20 via 10.0.0.20 dev eth0 table 10000 ip route add 10.99.255.20 via 10.0.0.21 dev eth0 table 10001 ip route add 10.99.255.30 via 10.0.0.30 dev eth0 table 10000 ip route add 10.99.255.30 via 10.0.0.31 dev eth0 table 10001
和上个方法对比一下,其实加了个 TUN 的区别就在于把手写的静态 ARP 表换成了手写路由表(实质上 ARP 表也是种路由表)。不过自己设定的 IP 地址终究是比乱糟糟的 MAC 地址好看一些,并且该方法完全不影响原来物理网卡上绑定的 IP 及其路由。
到这里,我需要的链路聚合已经算比较优雅地实现了吧。为了更愉快地配合 Debian 的 ifupdown 使用,我写了个 if-up 脚本进行链路聚合(Gist)。放进 /etc/network/if-up.d 里,然后这么配置一下:
auto tun0 iface tun0 inet static address 10.99.255.10 netmask 255.255.255.255 mtu 9000 fakenet 10.99.255.0/24 table 10000 10001 10002 10003 rspec 10.99.255.1:10.3.23.1:10.3.23.1:10.3.23.1 \ 10.99.255.10:10.99.0.10:10.99.1.10:10.99.2.10:10.3.23.10 \ 10.99.255.20:10.99.0.20:10.99.1.20:10.99.2.20:10.3.23.20 \ 10.99.255.30:10.99.0.30:10.99.1.30:10.99.2.30:10.3.23.30 pre-up ip tuntap add mode tun tun0 post-down ip tuntap del mode tun tun
26
2015
网页中的 <body> 和 <html> 元素及其样式
HTML 中的 <body> 和 <html> 元素,不知道你有没有注意过。一个作为 HTML 内容部分的父元素,一个作为整个网页的根元素,应用到它们上的样式表有什么区别,又究竟会影响到网页上的哪一部分呢?
最近写新主页,遇到了一些麻烦,于是对该问题做了一些研究。
<body> 作为一个块元素
<body> 元素定义了 HTML 文档的内容。W3School 上说,大多数浏览器会赋予它下面的样式:
body { display: block; margin: 8px; }
这两条告诉了我们什么呢:
- body 被渲染成一个块元素;
- body 有 margin,可见它外面还有东西?
用一个简单的例子试一试:
其实 <body> 的行为就和一个普通的 <div> 差不多嘛:
- 如果不指定宽度,则宽度自动占满其父元素(?);
- 如果不指定高度,则高度设为刚好能容下内部元素;
- 默认 overflow 行为是显示溢出内容。
可是,你有没有想到什么问题?
<body> 的背景为什么溢出去了?
我们经常使用 body{ background: xxx; }
来设置整个网页的背景。但如果 <body> 真的是一个普通的块元素,而且还带有 margin,那么为什么这个 background 设置能应用到整个网页的绘画区域呢?
栈溢出 Applying a background to <html> and/or <body> 给出了解释。这是一个特例,概括来讲如下:
- 根元素 <html> 的背景决定了整个绘画区域的背景。但如果 <html> 元素背景为透明(默认值),就会取其 <body> 子元素的背景作为 <html> 的背景,其位置等属性变成相对于 <html> 元素的。
这和我们的日常观感一致(背景 svg:画布大小 128x128,在 (64,64) 处有一个半径为 64 的红色实心圆):
如果给 <html> 设置了背景,那么 <body> 的背景行为就和块元素一致了(点上面的“设置html背景”试一试)。
可是,上面是不是用到了 <html> 的背景来着?可见 <html> 元素也有样式、也需要绘制咯?
如果尝试用 F12 或者加 border 的方法来查看 <html> 元素在整个绘画区域中的位置,很容易产生“<html> 的确就是比 <body> 长宽更多一点的外围元素”这样的错觉。可是,你有没有注意过……
网页的滚动条是怎么来的?
突然转向了一个奇怪的话题。为什么网页太长了,就会出现滚动条(默认行为如此)?
我们都知道,一个块元素设置 overflow 样式为 auto,当其中内容过长超过块元素长宽时,就会出现滚动条。
——其实,网页有滚动条是因为 <html> 默认拥有 overflow:auto
的样式!而 <html> 元素的尺寸,正是窗口的尺寸。
来验证一下吧。DOM 中 document.documentElement 代表的其实就是 <html> 元素,访问 clientWidth、clientHeight 就可以得到其宽高(其实不太科学,详见此文)。我们搞了个超大的 <body>:
看一看 <html> 的尺寸,果然没有因为 <body> 而变大。改变浏览器窗口大小,还会发现其尺寸也会跟着变化。看来,<html> 也可以看作一个块元素咯。
不过,上面例子中给 <html> 设置的 border,位置好像不太对劲儿,纵向一直延续到底了。这个嘛,我还不知道怎么解释,没查到资料。但鉴于一般也没人会给 <html> 设置 border,姑且先搁置了吧。
这里有一篇简明的文章,探讨本节的问题。
<html> 和 <body> 上的 CSS
在 <html> 上应用 CSS 的机会并不多。而在 <body> 上,最重要的应用就是设置背景了。在 CSS2 中,不能在 background 属性中指定两个图片。所以以前有在 <html> 中指定另一个背景图片这样的应用。当然,除非为了兼容古代 IE,否则已经没必要这么做了。
此外,前面提到一般浏览器都会给 <body> 加上 margin。有时候想要让内部元素紧贴窗口边界,可以通过 margin: 0
来重置掉这一设置。
另外一个在 <html> 或 <body> 上设置 CSS 的情景,是让子元素继承样式。比方说设置全局字体样式,“*”选择器杀伤力可能有点大。可以用 html 选择器加上继承:
html { color: red; }
24
2015
雾霾中的星空
初中时,对天文颇感兴趣。自己花一百多买了个小天文望远镜,剩下就想搞一台能手动曝光的专业相机拍一拍星空。
家里只有一台特别傻瓜的富士数码相机。试过的童鞋应该都知道,用这种傻瓜相机拍星空基本是不可能的。能够比较清晰地捕捉到的星体,大概只有太阳和月球。
虽然拍出来的效果很烂,自己还是兴致勃勃地拍过不少。
2007/06/30 12:11 望远镜中的太阳(可见一黑子)
用望远镜观测太阳是一件比较危险的事,按理说需要套上专业滤镜的。我没钱买,于是自己凑了一套“滤光机构”:在目镜里放上家里遗留的3.5寸软盘的内芯(和胶卷差不多),物镜处再套上一个黑色塑料袋。
这个方法,自认为还是比较管用的。除了一次失手,中午观测太阳时,物镜上的黑色塑料袋质量太差,破了个洞,阳光穿过望远镜扫到我右键,真是亮瞎了。
2008年8月1日的日全食(发生在太阳落山时,所以过程不全),没用望远镜,在邻院三十层的大楼上拍的。当年的得意之作:
至于月亮,毕竟是近一些,透过望远镜能看到的细节可就多了。
2007/09/22 21:01 望远镜中的月球,不知道哪儿的一座环形山。
从望远镜中观测行星,天气较好时也能看到一些细节,比如土星环和木星的伽利略卫星。不过因为没有固定装置,通过目镜拍摄行星或恒星很是困难,所以没有留下照片。
在空气质量上佳的晴朗夜晚,找个稳当的地方放好相机,有可能拍到木星(下面那颗,我查了查,可能是心宿二吧):
2007年08月11日 19:58:22 于郑州
9
2014
把 /usr/share 压成 squashfs,以及写 systemd 的 mount 单元
好像一年多没写东西了诶…… 其实寒假就想写点啥,但都因为拖延症没写成。可见维护博客也是个挺麻烦的事。这次为了避免拖延,写点简单的东西好了。
前言
Archwiki 的 Maximizing performance 一文很早就提到 “Compressing /usr” 这么个优化方式。就是用 squashfs 压缩 /usr(squashfs 是只读的),然后上面加一层 aufs 实现读写支持。好处就是减小磁盘空间占用,从而减轻 I/O 负担,
不过后来 Linux 内核把 aufs 踢出去了,这个优化也就坑了。加上现在 Arch 现在基本把所有东西都放进了 /usr,init 进程对 /usr 依赖特别强,分出去可能会有各种毛病。于是就不再建议搞这个优化了。
前阵子搞学校图书馆的网络启动查询机。实现是:Arch 系统,文件放在 NFS 上的 squashfs 里,用 aufs 读写。于是重新折腾起 aufs + squashfs 了。后来索性在自己机器上也搞了个,不是压 /usr,而是压 /usr/share。这里就记录一下配置吧。
为什么要压缩 /usr/share,而不是别的?
- 除了安装、卸载、升级软件,对 /usr 几乎没有写操作。如果你不进行这些操作,弄个只读的 /usr 都没问题。这就是为什么一般教程只有说压 /usr 的,没有说压 /var 的。
- 压缩 /usr/share 比压整个 /usr 安全,没有 /usr/share 系统也能启动进 single 模式(虽然有些功能会受影响)。
- /usr/share 几乎是 /usr 中占用空间最大的一部分,且其中有很多小文本文件,压缩效率很高。
配置
先说说目录结构。文件系统相关文件都放在 /sfs/usr_share/:
-
squashfs 压缩档:
/sfs/usr_share/filesystem.sfs
-
aufs 只读分支:
/sfs/usr_share/ro
-
aufs 可写分支:
/sfs/usr_share/rw
下面的操作其实都可以开着系统在线完成的,除了最后删除 /usr/share 的部分。
创建 squashfs 压缩档
一条命令的事儿:
mksquashfs /usr/share /sfs/usr_share/filesystem.sfs \
-b 32k -comp lzo -Xcompression-level 9
稍微解释:
- -b 32k:设置 squashfs 块大小为 32k。大概就是一次读取的最小单位。设大点压缩率会更高,但是读取一个 block 所需的 I/O 操作也会变多。所以要取个适当的值。我试了,32k 就差不多,再大压缩率增加的也不明显。(这主要是因为 /usr/share 里大多是很小的碎文件)
- -comp lzo:压缩算法设置为 lzo。这个压缩算法的特点是快速、省资源,压缩比一般般。但这样其实正好适合文件系统压缩,本来文件系统压缩就是牺牲内存/CPU资源来节省I/O资源的。可以看看网上的评测,这个压缩率比 xz/lzma 差一些,但压缩/解压速度快了很多。
- 注意压缩算法千万别选 lz7。这个算法也是跟 lzo 差不多的特点,不过内核的 squashfs 模块似乎还不支持该算法……
- -Xcompression-level 9:使用最大比率的 lzo 压缩。据说这样只是压缩速度会变慢,解压速度几乎不受影响。
写 systemd 的 mount 单元
按道理说,应该在 fstab 里加上相关项就可以了。但实际上我遇到了很严重的问题,开机经常没挂上 /usr/share。我没仔细追原因,感觉好像是因为 systemd 对挂载顺序作出了些调整,导致了一些问题。最后试了试 systemd 引入的新东西,解决了问题。
众所周知,systemd 造了好多轮子,什么 crontab、网络管理器、日志服务 它都想给接替了。fstab 也是这样,systemd 引入了 .mount 单元这么个东西,用于控制文件系统挂载。
虽然 Arch 还没抛弃 fstab,但其实 fstab 中有些内容已经被 systemd 完全取代了。老 Arch 用户可能记得,以前 /dev、/tmp 的挂载都是要写进 fstab 的,但现在都不用写了。Arch 用户可以看看 systemd 包提供的 /usr/lib/systemd/system/tmp.mount
这个文件,就是 /tmp 的挂载配置。内容很好理解,就不贴了。
正如 systemd 引入的各种单元(unit)一样,mount 单元也可以写各种依赖啊什么的东西,从这点来讲它比 fstab 的功能要强大。这里挂载 /usr/share 就要用到依赖关系:可读分支要在 /usr/share 挂载之前完成挂载。
直接贴文件:
[Unit] Description = /usr/share, read-only branch [Mount] What = /sfs/usr_share/filesystem.sfs Where = /sfs/usr_share/ro Type = squashfs Options = defaults [Install] WantedBy = local-fs.target
[Unit] Description = /usr/share Requires = sfs-usr_share-ro.mount After = sfs-usr_share-ro.mount [Mount] What = none Where = /usr/share Type = aufs Options = br:/sfs/usr_share/rw=rw:/sfs/usr_share/ro=ro [Install] WantedBy = local-fs.target
内容也很好看懂。Requires、After 这和 systemd service 的写法基本一致(其实我也不太懂),[Mount] 下就是挂载的说明了,What 后是要挂载的文件系统,Where 是挂载到的地方,Type 是文件系统类型,Options 是挂载参数。相当于:
mount -t <Type> -o <Options> <What> <Where>
不知道有人注意到这个文件名没,尤其是第一个。sfs-usr_share-ro.mount 这样命名其实是 systemd 强制要求的(抄自 man systemd.mount):
反正就是要和你挂载到的路径一致,把路径里的「/」换成「 -」。挺蛋疼的。
测试、清理
其实现在重启应该就可以了,/usr/share 放那儿反正会被 mount 盖掉。重启后确认 /usr/share 确实挂上 aufs 了,原来的文件是否删除就看心情了吧。
后期保养
说句实话,给 Debian stable 之类的上这个优化可能更有效。Arch 你得经常滚啊,滚一滚 aufs 可写分支就变得很大,这时就得重新压一下 squashfs 了。
重新压的步骤和前面差不多,就是把你现在的 /usr/share 当成没压的重新压一遍,把可写分支清空。不过清空可写分支的操作得离线进行(至少要进 single 模式卸载掉原来的 aufs)。注意安全,不多说了。
随便写写,没看懂的人千万不要乱试哦!
30
2013
负整数的整除和取余运算
负整数间是怎么整除和取余数的呢?
数学上貌似没定义,但计算机确实能算。于是就试了试,想总结一下规律。
一不小心发现,C/C++ 和 Python 下的结果是不同的:
C/C++ |
Python |
精确值 |
|
---|---|---|---|
-14/3 |
-4 |
-5 |
-4.67 |
-14%3 |
-2 |
1 |
/ |
14/-3 |
-4 |
-5 |
-4.67 |
14%-3 |
2 |
-1 |
/ |
-14/-3 |
4 |
4 |
4.67 |
-14%-3 |
-2 |
-2 |
/ |
总结规律如下:
- 两种语言中,商和余数都符合 被除数=商x除数+余数 这一数学规律。
- 两种语言中,整除的方法不同:C/C++ 是向零取整(负数向上、正数向下取整),Python 是下取整。
以 n/3 和 n%3 为例,看看这两种处理方法的区别。
C 的情况,两种运算结果都关于0对称和反号:
n | -5 | -4 | -3 | -2 | -1 | 0 | +1 | +2 | +3 | +4 | +5 |
商 | -1 | -1 | -1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
余数 | -2 | -1 | 0 | -2 | -1 | 0 | 1 | 2 | 0 | 1 | 2 |
Python 的情况,运算结果是完全连续的:
n | -5 | -4 | -3 | -2 | -1 | 0 | +1 | +2 | +3 | +4 | +5 |
商 | -2 | -2 | -1 | -1 | -1 | 0 | 0 | 0 | 1 | 1 | 1 |
余数 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
不知道那种在数学上更好用。个人觉得 Python 的处理方式更优美一些吧。
至于其他语言的处理方法,应该也出不了这两种。我所知道的:
- C/C++ “向零取整式整除”:C/C++、Java、bash
- Python “下取整式整除”:Python、Perl、Lua
27
2013
一些关于读书的事
唉,最近越来越想写点小说读后感什么的,但是自己也归纳不清楚有什么读后感……
以下,胡乱写一些关于读书的事吧:
一、我的选书标准
选择文学、电影、动漫等各类艺术作品,我都有这么一个习惯:只看经过了大众评判的“好作品”。虽然可能有人觉得这是随波逐流、缺乏主见,但我有这么一个想法:光阴苦短,还是不要浪费在已经被广泛证明为糟粕的东西上了。
当然,除了上面这条,还是会根据喜好进行选择的。比如,最近我对远子学姐推荐的书籍有点兴趣,就当快餐看了好几部中短篇小说。
二、计算机书籍的困惑
我现在钻研最深的恐怕就是计算机知识了。但我的钻研不是一个洞钻到底,而是挖了好多浅坑——各方面知识都了解“一点”。这就造成了很麻烦的现状:我完全搞不明白自己接下来要攻关什么。
去书店着计算机书柜,我都会特别纠结:看看这个讲UNIX编程的,好想买;看看那个讲算法的,也想买…… 就像女人逛街那样。
计算机书籍一般都贵得要死(都是版权惹的祸),而且质量还不见得高(翻译是个大坑)。被几本价高质劣的计算机书籍坑了之后,我的理智现在完全胜过欲望了,会慎重点做选择的。
最近琢磨了琢磨,就好好钻研一下编程吧。以后万一混得不好,还可以跟程序员抢饭碗。
三、翻译
最近买的一本薛定谔的《生命是什么》,翻译实在是读不下去,几乎每个句子都得反复读几遍以明确意义,于是就弃了。嘛,大概这书本来就比较晦涩吧。
我看的书,十有八九是翻译书籍(不是我崇洋媚外,而是中国人在我感兴趣的领域实在少有建树)。我也读到过不少翻译质量低下的书。
翻译问题最突出的,就是计算机书籍和科普书籍。这也好理解:语文好的人不见得懂这些,懂这些的人又往往语文不怎么样。这些书籍,要是语言简练点还好说,把那些翻译拗口的句子多读两遍也不会怀孕。但要是还带着废话一堆(外国人喜欢扯废话,这大概是他们的幽默),就实在是让人有撕掉书的冲动。应该说,我偏爱简洁的语言。
其实,我也经常翻译东西,明白翻译没办法完美,尤其是东西方语言之间的翻译。但是嘛,最为读者,还是衷心地希望翻译能搞好一些。
四、日本文学
我发现,日本文学读起来普遍比西方文学舒服。大概有翻译的因素,同是汉日同为东方语言,隔膜小一些。另一点上面也说了,西方人好扯废话,而东方人的语言就简洁得多,我喜欢简洁的。
我读的第一本日本小说是村上春树的《挪威的森林》。这书对我来说,其实有点黄了(我在文化上还是挺保守的)。但是,我从中确实感受到了某种新鲜的感觉,有些忧伤但又美好的感觉。这在以前读的冒险小说里是体会不到的。后来还读了川端康成的总是捆绑卖的那三部小说,其中我最喜欢的是《雪国》,从中也能体会到类似的感觉。
日本文学里的另一个重要成员是轻小说。去年夏天,我看了动漫《凉宫春日的忧郁》,对其中的伪科学挺感兴趣,就买了小说看,于是入了轻小说的门(可恶的是,同学看到书名的第一反应都是“春宫”……)。
后来,我又看了《文学少女》。我特别喜欢这个系列。看着别人的故事,会想到自己的故事呢。这大概就是所谓的”共鸣“。而且,多亏了学姐,我对文学的兴趣有了很大提高。
以前,我以为轻小说是那种没有营养的消遣读物。可读了之后发现,感觉也挺有营养的嘛。
五、《刀剑神域》
唔,我特别想说说SAO,尽管在这里说有点不合气氛。读到第二个世界完结(拯救了Asuna)之后,就再也读不下去了。
后来琢磨了一下,大概是我实在受不了开挂男主了。唉,开挂就算了吧,还要是第一人称叙述,实在想砍了他。一般不应该是“主角本来是个吊死,但在各种人的帮助下,逐渐成长起来”这样的包含浓浓友情、亲情、爱情的温馨的励志故事吗?
我挺喜欢《凉宫春日》和《文学少女》。阿虚是个毫无特殊能力的纯吐槽角色,心叶是个学姐怎么扶也扶不起来的阿斗。想想看,这俩男主都比桐人可爱多了……
29
2013
Archlinux on ThinkPad L430
甲、前言
出于特殊需求,花将近3000买了这个 ThinkPad L430。
一、配置
- CPU:Intel Pentium B980 @ 2.40GHz #Sandy Bridge 的,主频还可以
- 显卡:HD 3000 核显 + Quadro NVS 5400M 独显 #奇葩的型号,好像是啥绘图卡,感觉和610M水准差不多
- 内存:2GB #跑Win7HB和Linux足矣
- 硬盘:320GB HDD
- 14吋屏幕、DVD刻录机、有摄像头、没指纹识别、预装Win7HB
二、吐槽
- 指纹识别呢?我一直觉得 ThinkPad 这个很厉害,结果发现便宜货上没有……
- 第一天就发现光驱是坏的,送回去修了,略郁闷。
- Win7 预装了一坨联想的东西,还有啥QQ电脑管家,去死吧……
28
2012
NetworkManager 配合使用 chnroutes (至少适用于 Arch)
高三很忙,好久没写东西了。不过也快熬出头了。今天就写点儿啥吧。
VPN是个好东西,翻那个啥效果不错,比goagent之类的要稳定。但如果不配置路由的话,VPN就会成为默认网关,包办所有对外连接。这反而会导致访问国内网站速度变慢,而且也挺浪费流量的。我搜了一下,找到了chnroutes这个工具:
利用来自APNIC的数据生成路由命令脚本,让VPN客户端在连接时自动执行。通过这些路由脚本,可以让用户在使用VPN作为默认网关时,不使用VPN访问中国国内IP,从而减轻VPN负担,并提高访问国内网站的速度。
如果使用openvpn之类的命令连接VPN的话,按照其readme配置即可。但我还是偏好GUI,想用NetworkManager来管理连接。NetworkManager的VPN连接程序是自带的,并不使用命令行工具的配置文件。
Google到了这篇文章:《Ubuntu 9.10下使用Network-manager 配置openvpn 》。
简要转述 chnroutes 使用方法:
-
克隆代码:
git clone git://github.com/GutenYe/chnroutes.git
-
终端下切到代码目录,执行:
python2 chnroutes.py
-
接下来会生成 vpn-up.sh、vpn-down.sh。vpn-up.sh 需要修改一下(原因参考上面提到的文章),把
开头的 “
OLDGW=$(...” 那一行删掉,改为:
OLDGW=$(ip route show | grep '^[^d].*proto static' | grep -v 'dev\s*tun' | awk 'NR==1' | sed -e 's/.*via \([^ ]*\) .*/\1/')
- 把 vpn-up.sh 随便放到个合适的地方吧(我放到了 /etc/openvpn/ )
Arch 的 NetworkManager 没 Ubuntu 那个名为 01ifupdown 的 dispatcher 。简单研究一下,发现dispatcher也不是啥复杂的玩意儿。要点如下:
- 接入/断开连接时,会依次调用 /etc/NetworkManager/dispatcher.d/ 下的 shell 脚本
- NetworkManager 会给脚本传入(至少)两个参数,第一个是连接名称,第二个是动作
- 动作有(至少)四种:up—接入普通连接,down—断开普通连接,vpn-up、vpn-down—接入/断开VPN连接
照葫芦画瓢写了个(请保存到 /etc/NetworkManager/dispatcher.d/10chnroutes):
#!/bin/sh -e
case "$2" in
vpn-up)
exec /etc/openvpn/vpn-up.sh
;;
vpn-down)
exit 0
;;
esac
exit 0
额,弱爆了是吧……别的发行版应该也能用。
按说vpn-down时应该执行一下vpn-down.sh的,但我发现加上了反而会导致一个(无伤大雅的)错误,而且不执行也没啥影响,VPN断开后路由表确实自动恢复了。这方面我不太懂,反正事实就是这样。
到这里,NetworkManager 连接 VPN 时就能自动配置路由了。在VPN连接后,找个国内看IP的网站测试看看吧。
另外,对 Ubuntu 用户的附注。那篇参考文章提到的 01ifupdown,在最新的Ubuntu里变复杂了,要修改成下面这样:
up|vpn-up)
export MODE="start"
export PHASE="post-up"
/etc/openvpn/vpn-up.sh
exec run-parts /etc/network/if-up.d
;;
4
2012
【火狐三则】增强组件的PKGBUILD;不错的dial插件;同步profile到内存盘
没有什么关联的三则。
1. 火狐addon的PKGBUILD:
从CVS获取代码编译最新版插件的PKGBUILD,对喜欢用测试版Fx的用户可能有点用。
community里面有稳定版的adblock-plus、noscript,是下载XPI解压然后打包的。自己写的话,可以参考其中确定安装目录的部分。
adblock-plus
从hg获取的代码有个metadata,里面可以提取Fx版本支持信息,所以写成动态获取依赖版本了。
另外,第一次知道,用某些CVS的PKGBUILD中不用自己写clone代码的部分…… makepkg会自动完成。
pkgname=firefox-adblock-plus-hg pkgver=3414 pkgrel=1 pkgdesc="plugin for firefox which disables script (mercurial developing version)" arch=('any') url="https://adblockplus.org/" license=('GPL') depends=("firefox") conflicts=("firefox-adblock-plus") provides=("firefox-adblock-plus") makedepends=('unzip' 'python2-jinja' 'mercurial') _hgroot="https://hg.adblockplus.org/" _hgrepo="adblockplus" build() { rm -rf "$srcdir/$_hgrepo-build" cp -rf "$srcdir/$_hgrepo" "$srcdir/$_hgrepo-build" cd "$srcdir/$_hgrepo-build" # determine supported firefox versions _fxver=($(sed -n 's/firefox=\(.*\)/\1/p' metadata | sed "s/\//\ /")) _abpver=$(sed -n 's/version=\(.*\)/\1/p' metadata) _emid=$(sed -n 's/id=\(.*\)/\1/p' metadata) depends=("firefox>=${_fxver[0]}" "firefox<=${_fxver[1]}") provides=("firefox-adblock-plus=$_abpver") python2 build.py build abp.xpi install -d "$pkgdir/usr/lib/firefox/extensions/$_emid" unzip -od "$pkgdir/usr/lib/firefox/extensions/$_emid" abp.xpi # unzip set filemode extracted from XPI file to 600 # I don't know why :3 chmod -R a+r "$pkgdir/usr/lib/firefox/extensions/$_emid" }
https-everywhere
我sed功力太差,不会从XPI中提取版本信息(其实是土方法太繁琐),所以这个就不搞动态版本了……
pkgname=firefox-https-everywhere-git pkgver=20120303 pkgrel=1 pkgdesc="an addon making firefox auto use HTTPS (git developing version)" arch=('any') url="https://www.eff.org/https-everywhere/" license=('GPL2') depends=("firefox>=3.0") provides=("firefox-https-everywhere") makedepends=('unzip' 'git') _gitname="https-everywhere" _gitroot="git://git.torproject.org/https-everywhere.git" build() { cd $srcdir msg "Connecting to the GIT server...." if [ -d $_gitname ]; then cd $_gitname && git pull origin && cd .. msg "The local files are updated." else git clone $_gitroot $_gitname fi msg "GIT checkout done" rm -rf "$_gitname-build" cp -rf "$_gitname" "$_gitname-build" cd "$_gitname-build" sed -i '1 i#\!/usr/bin/python2' trivial-validate.py ./makexpi.sh install -d "$pkgdir/usr/lib/firefox/extensions/https-everywhere@eff.org" unzip -od "$pkgdir/usr/lib/firefox/extensions/https-everywhere@eff.org" pkg/*.xpi }
2. FVD Speed Dial插件:
之前用Speed Dial,感觉界面很弱智……
今天发现了这个。自我感觉比Speed Dial强大多了,界面挺好看。
除了支持Speed Dial的分组之类的,Dial页还能显示最常访问、最近关闭等。
下载、截图自己看吧:https://addons.mozilla.org/en-US/firefox/addon/fvd-speed-dial/
3. profile-sync-daemon,同步浏览器配置到内存盘:
把用户profile(~/.mozilla)放到内存盘,可以提高Fx速度,并减少磁盘读写。考虑到这个比较麻烦,还要自己写脚本什么的,一直没搞过。
看Archwiki,发现了profile-sync-daemon这个daemon,挺很方便的。不仅支持Fx,还支持Chrome、Opera、Midori。graysky出品,必属精品。
从AUR安装,修改 /etc/psd.conf
,在USERS部分填上自己的用户名,rc.d start psd
启动就行了。