不亦快斋 http://icerote.net/blog/ 程序人生 LXC容器内使用sshfs http://icerote.net/blog/post/144 http://icerote.net/blog/post/144 <div style="direction:ltr"> <p>为了避免所做的设置分散化,我设置了一个lxc容器专门用于管理工作,这样避免了不同机器之间到处相互可以登录。我可以在管理机器上做好配置,然后下发到各工作机器。但是有时候又确实需要在工作机之间传递数据,scp又不支持从一个远程机器复制到另一个远程机器,这时将其中一台机器通过sshfs挂载到本地就会方便很多。</p> <p>lxc默认没有fuse设备,需要手动创建:</p> <pre><code class="bash">mknod -m 666 /dev/fuse c 10 229 </code></pre> <p>在lxc的config中也许可以指定创建,但是我觉得不应该采用bind选项,这里只是想独立地mount到管理容器中,而不想干扰host机器。</p> </div> SSH的Github多账户配置 http://icerote.net/blog/post/143 http://icerote.net/blog/post/143 <div style="direction:ltr"> <p>又新开了一个github账户,以防不测。</p> <p>习惯了使用ssh key访问Github,在给新账户添加ssh公钥的时候,Github禁止添加重复的密钥,换句话说,两个账户不可以共享同一个密钥。给不同的用户设置不同的密钥本来很简单:</p> <pre><code class="config.ssh"> Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa </code></pre> <p>但是这个对于Github的多账户不行,因为所有账户用的都是同一个ssh账户<code>git</code>。解决办法仍然是通过修改<code>~/.ssh/config</code>:</p> <pre><code class="config.ssh"> Host github.com-user1 HostName github.com User git IdentityFile ~/.ssh/id_rsa_user1 Host github.com-user2 HostName github.com User git IdentityFile ~/.ssh/id_rsa_user2 </code></pre> <p>通过<code>-userX</code>这个Host后缀可以区分。</p> <hr /> <p>补充:</p> <p>上面的内容根本没抓到要点,但是我当时在尝试的时候完全是因为意外导致的:我开启了SSH的<code>ControlMaster</code>,而<code>ControlPath</code>的编码方式又完全不能区分两者,导致某些时候错用了私钥才成功的。根本原因在于Host指定了别名,并且仓库URL链接中的<code>github.com</code>要替换成相应的别名,这样才能真正成功。这样机制上就完全搞明白是怎么工作的了,和Git本身没太大关系,主要是SSH的选择在起作用。</p> <p>这么做对我来说是够用的,但不够友好和一般化。</p> </div> Linux路由配置笔记 http://icerote.net/blog/post/142 http://icerote.net/blog/post/142 <div style="direction:ltr"> <h2>硬件准备</h2> <ul> <li>CPU: <a href="https://www.intel.cn/content/www/cn/zh/products/processors/core/i3-processors/i3-8300t.html">i3-8300T, TDP 35W</a></li> <li>Memory: <a href="https://item.jd.com/2939894.html">海盗船 DDR4 2440, 8Gx2</a></li> <li>Motherboard: <a href="https://item.jd.com/5361693.html">华擎 Z370M-ITX/ac, Intel Z370/LGA 1151</a> <a href="http://www.asrock.com.tw/MB/Intel/Z370M-ITXac/index.cn.asp">官网链接</a> <ul> <li>关注特性: <ul> <li>双千兆网卡</li> <li>802.11ac wifi</li> <li>蓝牙 4.2</li> </ul> </li> <li>其他特性:2HDMI,1DisplayPort,三显示器同时输出</li> </ul> </li> <li>Storage: <a href="https://item.jd.com/12924238452.html">Intel 760P 512G</a>, 标称性能:read 3230MB/s, write: 1625MB/s 4k read: 340000 IOPS, 4k write: 275000 IOPS</li> <li>电源: <a href="https://item.jd.com/3945819.html">银欣 300W ST30SF SFX电源 80PLUS铜牌</a></li> <li>机箱: <a href="https://item.jd.com/100000111585.html">银欣(SG13B 珍宝13 SFF机箱</a></li> <li>风扇: <a href="https://item.jd.com/27824352454.html">酷冷至尊T520下压式CPU散热器</a></li> </ul> <h2>操作系统</h2> <p>Debian stretch (9.8)。安装完成后,apt source添加了Buster和sid。</p> <p>安装过程中用了wifi联网,提示需要3168-26.ucode,到另一台机器用<code>apt download firmware-iwlwifi</code> deb包下来,然后找个u盘,放在u盘的firmware目录下,继续安装。</p> <p>考虑到会重度使用lxc,btrfs对此有比较好的支持,因此建了一个lvm卷挂到/var/lib/lxc上。</p> <h2>系统配置</h2> <h3>网络配置</h3> <h4>主机网络配置</h4> <p>这块板子有两个千兆口,一个是I219-V, 另一个是I211 AT. 一个无线网卡,3168NGW。I219-V芯片官网报价$1.72,够廉价的,用来连wan合适。另一块I211算是小惊喜,除了支持4个收发队列外,还支持巨帧和VLAN,官网报价$2.13。3168NGW官网价格$5.00,比俩千兆口加起来都贵。</p> <p>物理机器上建两个bridge,brlan和brwan,分别用于桥接局域网和广域网。I219接在brwan上,I211和wifi接在brlan上。开一个lxc容器作为路由,有两块网卡,分跨两个bridge,作为路由。配置阶段为了方便,暂时给brwan分配了一个地址,等配置完就会去掉。物理host机器将来也通过路由容器访问外网,这样更安全。</p> <pre><code class="config.interfaces"> auto lo brlan brwan eth0 eth1 wlan0 iface lo inet loopback allow-hotplug eth0 eth1 iface brlan inet static bridge_ports eth0 wlan0 bridge_stp off address 192.168.1.10 netmask 255.255.255.0 gateway 192.168.1.1 iface brwan inet static bridge_ports eth1 bridge_stp off </code></pre> <p><code>brlan</code>上静态分配内网地址是必要的,因为路由容器启动会比主机晚,也方便容器出问题时直接通过ip地址访问主机。<code>brwan</code>上刻意不分配地址,禁止外网直接访问主机,主机访问外网也需要从<code>brlan</code>出发,经路由容器NAT,再经<code>brwan</code>出去。这样更安全些。为了方便远程配置网络,也可以在配置阶段先分配一个内网地址,等配置完成再去掉。</p> <p>主机上也不要配置路由,路由由容器充当。安全防御做在路由容器上,这样物理主机的防火墙似乎不需要了,毕竟连ip地址没有,路由也没有。这么做是否安全,我也没把握,希望有专家指正。</p> <p>理想状况下,主机也不需要配置wlan0,而是交给路由容器去配置。但是不知道怎么把wifi网卡这种设备转给容器访问,留待以后有机会再尝试吧。所以目前的做法是在主机跑hostapd,提供ap,dhcp和dns则由路由容器提供。</p> <h4>创建Lxc容器</h4> <p>创建两个lxc容器,一个用于路由,另一个用于测试。</p> <pre><code class="bash"> lxc-create -t download -n route -- -d debian -r sid -a amd64 lxc-copy -N test -n route </code></pre> <p>先编辑容器route的配置,/var/lib/lxc/route/config:</p> <pre><code class="config.lxc"> #eth0 lxc.net.0.type = veth lxc.net.0.flags = up lxc.net.0.link = brwan #eth1 lxc.net.1.type = veth lxc.net.1.flags = up lxc.net.1.link = brlan lxc.include = /usr/share/lxc/config/debian.common.conf lxc.tty.max = 4 lxc.arch = amd64 lxc.start.auto = 1 #自动启动 lxc.uts.name = route ### for openvpn lxc.mount.entry = /dev/net dev/net none bind,create=dir lxc.cgroup.devices.allow = c 10:200 rwm lxc.rootfs.path = btrfs:/var/lib/lxc/route/rootfs </code></pre> <p>test的配置,关键部分:</p> <pre><code class="config.lxc">lxc.net.0.type = veth lxc.net.0.flags = up lxc.net.0.link = brlan </code></pre> <p>新版本的lxc生成的config文件中会有这样两句:</p> <pre><code class="config.lxc"> lxc.apparmor.profile = generated lxc.apparmor.allow_nesting = 1 </code></pre> <p>在config文件所在目录还会生成一个apparmor目录,其中是apparmor的相关配置。我的route其实是以前配置好的,没有apparmor目录内的内容,导致dmesg信息中被一大堆apparmor的错误信息洗版了,什么有用信息都看不到了。修复方法就是复制一份好的过来,修改容器名称相关的部分就好。</p> <h4>路由配置</h4> <h5>基础网络配置</h5> <p><code>lxc-attach route</code>进入路由容器,先开启路由功能</p> <pre><code class="bash"> echo "1" &gt; /proc/sys/net/ipv4/ip_forward </code></pre> <p>编辑 <code>/etc/sysctl.d/10-network.conf</code>,添加:</p> <pre><code class="config.sysctl"> net.ipv4.ip_forward=1 net.core.default_qdisc=fq net.ipv4.tcp_congestion_control=bbr </code></pre> <p>后两行是设置Tcp拥塞控制算法为BBR,不是必须的。</p> <p>配置/etc/network/interfaces:</p> <pre><code class="config.interfaces"> auto lo eth0 eth1 iface lo inet loopback iface eth0 inet dhcp iface eth1 inet static address 192.168.1.1 netmask 255.255.255.0 </code></pre> <p><code>eth0</code>接外网,<code>eth1</code>接内网。因为自己是路由器,<code>eth1</code>上就不要指定<code>gateway</code>了。重启网卡:</p> <pre><code class="bash"> ifdown eth0 ifdown eth1 ifup eth0 ifup eth1 </code></pre> <p>接着开启NAT,其实一句就可以:<code>iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE</code></p> <p>重启路由后要能自动开启NAT,因此创建文件 /etc/network/if-up.d/iptables</p> <pre><code class="bash"> #! /bin/sh iptables -F iptables -t nat -F iptables -t mangle -F iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE </code></pre> <p>前三句是清空iptables的规则表。保存好文件后别忘了<code>chmod +x iptables</code></p> <h5>unbound配置</h5> <p>这个就是用来反域名污染的。配置如下:</p> <pre><code class="config.unbound"> server: do-daemonize: no interface: 127.0.0.1@1053 tcp-upstream: yes tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt forward-zone: name: "." forward-tls-upstream: yes forward-addr: 1.1.1.1@853#cloudflare-dns.com </code></pre> <p>一般的dns查询交给dnsmasq来完成,受污染的域名就交给unbound,走DNS-over-TLS</p> <h5>Dnsmasq配置</h5> <p>首先安装dnsmasq: <code>apt install -y dnsmasq</code></p> <p>先修改<code>/etc/resolv.conf</code></p> <pre><code class="config.resolv"> search lan nameserver 127.0.0.1 </code></pre> <p>让域名解析指向本机,这里的<code>lan</code>是你的本地域名后缀。你不要search这一行也可以,dnsmasq相应的不配就行了。</p> <p>创建文件<code>/etc/hosts.dnsmasq</code>如下:</p> <pre><code class="config.hosts"> 192.168.1.1 route route.lan 192.168.1.10 bare bare.lan </code></pre> <p>这个是添加路由和主机的域名到dnsmasq,因为这俩都不是通过dhcp分配地址的。</p> <p>不要动<code>/etc/dnsmasq.conf</code>,这个文件可以当作文档用,别改乱了。实际的配置可以放在<code>/etc/dnsmasq.d/default.conf</code>中:</p> <pre><code class="config.dnsmasq"> interface=eth1 #内网网关接口 dhcp-range=192.168.1.50,192.168.1.200,255.255.255.0,1h #地址池范围 no-hosts #忽略/etc/hosts文件 addn-hosts=/etc/hosts.dnsmasq #添加静态主机名 domain=lan #指定本地域名 cache-size=1500 except-interface=eth0 # 排除wan口 all-servers server=8.8.8.8 server=114.114.114.114 dhcp-option=121,0.0.0.0/0,192.168.1.1 </code></pre> <p>我这里的server用的指定的dns,如果wan口的eth0是通过dhcp获得ip的,而你又想使用isp提供的dns服务器,那就需要编辑dhclient的配置文件,加入一行:</p> <pre><code class="config.dnsmasq"> prepend domain-name-servers 127.0.0.1; </code></pre> <p>完了需要定制一下dnsmasq吧?在中国大陆没办法的事。参考<code>https://github.com/felixonmars/dnsmasq-china-list</code>。</p> <p>我在上面还跑了openvpn,这里就不多说了。</p> <h4>AP配置</h4> <p>在物理主机上安装hostapd,配置如下:</p> <pre><code class="config.hostapd"> interface=wlp3s0 driver=nl80211 bridge=brlan ssid=lostemple hw_mode=g #country_code=CN channel=1 ieee80211n=1 ieee80211ac=1 auth_algs=1 wpa=2 wpa_key_mgmt=WPA-PSK rsn_pairwise=CCMP wpa_passphrase=GreatWall </code></pre> <p>这样AP能起来。我本来觉得差不多就可以了,但是性能实在太差了。不但速度慢,信号也差,远一点就衰减得厉害。所以要加入下面的配置:</p> <pre><code class="config.hostapd"> ht_capab=[HT40+][DSSS_CCK-40][RX-STBC1][SHORT-GI-40][SHORT-GI-20] wmm_enabled=1 wmm_ac_bk_cwmin=4 wmm_ac_bk_cwmax=10 wmm_ac_bk_aifs=7 wmm_ac_bk_txop_limit=0 wmm_ac_bk_acm=0 wmm_ac_be_aifs=3 wmm_ac_be_cwmin=4 wmm_ac_be_cwmax=10 wmm_ac_be_txop_limit=0 wmm_ac_be_acm=0 wmm_ac_vi_aifs=2 wmm_ac_vi_cwmin=3 wmm_ac_vi_cwmax=4 wmm_ac_vi_txop_limit=94 wmm_ac_vi_acm=0 wmm_ac_vo_aifs=2 wmm_ac_vo_cwmin=2 wmm_ac_vo_cwmax=3 wmm_ac_vo_txop_limit=47 wmm_ac_vo_acm=0 </code></pre> <p>重点是ht_capab那一行。方括号内的都是参数,要参考hostapd.conf的文档,看ht_capab支持哪些,还要看<code>iw list</code>的输出,看网卡实际支持哪些功能,把网卡支持的给加进去。</p> <p>至于<code>wmm_enabled</code>,只是enable一下是不够的,后面那一堆wmm_XXX 参数必须都设置才能真正使得WMM生效。这些参数都是从hostapd.conf的建议值抄来的,具体怎么工作的我还没搞明白,不太想搞这个。</p> <p>反正,这么操作一下,这个软AP的性能终于赶上原来的无线路由了,性能和稳定性更好。但糟心的是信号传输距离表现不如旧无线路由,看来还是得买ap。</p> <h5>关于5G</h5> <p>为了开AP 5G频段,我至少花了20个小时吧,远超其他所有任务的时间总和加上写这篇笔记。悲剧的是没有搞定无线电监管的问题。最终在Intel 官网上找到官方说明,明确了就是不支持5G的AP模式,放弃了。尝试了很多次,对其他网卡有效的办法,对Intel的网卡就是无效。系统监管地区可以修改,但是无论怎么改,都不能影响网卡上的设置,我怀疑关键是卡在网卡的Firmware上。有两篇特别提到Intel网卡的文章,一个是说是有办法绕过监管,试了,行不通。另一个是一个俄国人问iwlwifi的开发者,说是旧FW曾经是可以的,为什么新的FW不行了。iwlwifi的那位表示很惊讶居然有旧FW能行,然后一再强调别这么干,有法律风险。之前还看到一篇说是修改驱动可以,我很怀疑。中途还打算试试看改FW,看了几眼iwlwifi的源代码和签名问题,放弃了。</p> <p>以后很长一段时间大概都没有胃口去碰wifi网卡了......</p> <h4>蓝牙</h4> <p>发现自己对蓝牙的应用方面的概念其实了解的很少,需要系统地补补课。bluetoothctl很强大,但我不太会用,虽然用它成功地和我的手机配对了。文档推荐的应用工具blueman是个图形界面工具,我不方便用。配置过程中,唯一值得一记的就是安装bluetoothd要修改一下systemd的配置文件<code>/etc/systemd/system/bluetooth.target.wants/bluetooth.service</code>, 禁用sap,修改如下:</p> <pre><code class="config.systemd"> ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=sap </code></pre> <h3>问题处理</h3> <ol> <li>想开unprivileged container,遇到好几个问题。一方面我升级到了最新的lxc 3.0.3,但是网上的文档都是老的,网站linuxcontainer的是新的,但是很多细节没交代清楚,又和以前不一样。<code>id_map</code>换成<code>idmap</code>这种还算好找的。apparmor的一些错误一番搜索之后修正了。</li> </ol> </div> Cppcms数据库迁移 http://icerote.net/blog/post/141 http://icerote.net/blog/post/141 <div style="direction:ltr"> <p>这个blog和wiki原来的数据库是sqlite,一直打算迁移到了postgresql上,拖到现在才搞。</p> <p>使用的迁移工具是<a href="https://github.com/dimitri/pgloader">pgloader</a>,非常方便。<a href="https://pgloader.readthedocs.io/en/latest/ref/sqlite.html">这篇</a>也可以参考,有其他数据库迁移到Postgres的指导。</p> <pre><code> pgloader SOURCE TARGET pgloader wikipp.db postgresql://user:password@host/database </code></pre> <p>值得一提的是postgresql 连接URL格式,可以参考<a href="https://stackoverflow.com/questions/3582552/postgresql-connection-url">https://stackoverflow.com/questions/3582552/postgresql-connection-url</a>。这种url格式新版本的libpq才能支持。</p> <p>另一个值得一提的是经SSL连接postgres。我之前的一个做法是让Postgres只监听loopback地址,再通过SSH隧道转发,这么做的优点是客户端可以用ssh的密钥而无需密码登录,但搞得有点复杂。今天顺带开了一下Postgres直接支持SSL连接。</p> <p>首先编辑<code>postgresql.conf</code>, 打开ssl:</p> <pre><code> ssl = on ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' </code></pre> <p><a href="https://www.postgresql.org/docs/9.1/ssl-tcp.html">文档</a> 说key文件的权限必须是600,但是我的机器上实际是640.</p> <p>编辑<code>pg_hba.conf</code>,添加:</p> <pre><code> hostssl database user address auth-method [auth-options] </code></pre> <p>并且把相应的host行删除,具体参考<a href="https://www.postgresql.org/docs/9.1/auth-pg-hba-conf.html">这里</a>。</p> <p>客户端的连接字符串也要相应修改,添加 <code>sslmode=require</code></p> <p>Wikipp的迁移很顺利,但是Cppblog创建新blog失败。查了一下,源码的bug,应该是键入错误,修复很简单。</p> <p>另一个问题是发现Markdown的支持有问题,没有支持Github风格的块代码。最后还是查了源代码搞定的。看源码还是终极的手段,相关源码其实并不复杂,比看网上文档还来得省力。 这几篇可以参考:</p> <ol> <li><a href="http://www.pell.portland.or.us/~orc/Code/discount/">Discount</a> 实际用的Markdown的库、命令行工具就是来自这个项目。名字实在无法让人联想到Markdown,以至于我一开始误以为找错了。这里面有libmarkdown2中所有flag的定义和解释,遗憾的是没有给出数值。另外,这也是一篇不错的Markdown扩展语法测参考。吐槽一下,libmarkdown2的man页面太简略了,还不如直接看头文件。</li> <li><a href="https://github.github.com/gfm/#task-list-items-extension-">GitHub Flavored Markdown Spec</a> Github的扩展语法。</li> <li><a href="https://michelf.ca/projects/php-markdown/extra/#def-list">php Markdown Extra</a> php扩展的列表语法。</li> <li><a href="http://tedwise.com/markdown/">Discount Markdown Syntax</a> 相当完整的Discount Markdown的语法。末尾的特殊字符表很有参考价值。</li> </ol> <p>于是给Blog扩展了一下Markdown的支持:</p> <pre><code>const int K_markdown_flags = 0x00000004| //MKD_NOPANTS 0x01000000| //MKD_DLEXTRA 0x02000000| //MKD_FENCEDCODE 0x08000000| //MKD_GITHUBTAGS 0x40000000| //MKD_LATEX 0; </code></pre> <p>修改后的代码:</p> <ul> <li><a href="https://github.com/wingfiring/cppcms">cppcms</a></li> <li><a href="https://github.com/wingfiring/cppdb">Cppdb</a></li> <li><a href="https://github.com/wingfiring/cppblog">Cppblog</a></li> <li><a href="https://github.com/wingfiring/wikipp">Wikipp</a></li> </ul> </div> 向量平行快速判定 http://icerote.net/blog/post/138 http://icerote.net/blog/post/138 <div style="direction:ltr"> <p>向量a・b点积若为0,则相互垂直,但是判断是否平行,则一般需要计算|a||b|,比较耗时。</p> <p>对于二维向量,可以通过判断a1b2=b1a2,但对于多维向量,要判断 a1/b1=a2/b2=a3/b3=...=an/bn, 考虑到bi可能为0,代码写起来比较麻烦。这里想到的一个办法.</p> <ol> <li>设i为0</li> <li>若ai和bi不都为0, 转4</li> <li>++i &lt;n 转2,否则返回true</li> <li>若bi为0,return all_of(bi, bn, equal_to(0))</li> <li>k = ai/bi</li> <li>return all_of([i -> n], k*bi == ai)</li> </ol> </div> 善用sort命令 http://icerote.net/blog/post/137 http://icerote.net/blog/post/137 <div style="direction:ltr"> <p>文本处理的时候难免需要排序,sort就是用来排序的工具.然而如果对sort不够熟悉,那很多时候不免觉得sort也力不从心.了解这些功能,可以让sort的能力更上一个台阶,见下表.</p> <p></p><table> <thead><tr><th>选项</th><th>长度</th><th>描述</th></tr></thead> <tbody> <tr><td>-b</td><td>--ignore-leading-blanks</td><td>默认情况下,对整行进行排序,从每行的第一个字符开始。这个选项导致 sort 程序忽略 每行开头的空格,从第一个非空白字符开始排序。</td></tr> <tr><td>-f</td><td>--ignore-case</td><td>让排序不区分大小写。</td></tr> <tr><td>-n</td><td>--numeric-sort</td><td>基于字符串的数值来排序。使用此选项允许根据数字值执行排序,而不是字母值。</td></tr> <tr><td>-r</td><td>--reverse</td><td>按相反顺序排序。结果按照降序排列,而不是升序。</td></tr> <tr><td>-k</td><td>--key=field1[,field2]</td><td>对从 field1到 field2之间的字符排序,而不是整个文本行</td></tr> <tr><td>-m</td><td>--merge</td><td>把每个参数看作是一个预先排好序的文件。把多个文件合并成一个排好序的文件,而没有执行额外的排序</td></tr> <tr><td>-o</td><td>--output=file</td><td>把排好序的输出结果发送到文件,而不是标准输出</td></tr> <tr><td>-t</td><td>--field-separator=char</td><td>定义域分隔字符。默认情况下,域由空格或制表符分隔</td></tr> </tbody></table></p> <p><p></p> <p>大部分很很好理解.<code>-n</code>可以按照实际数字排序,而没有的话就是按照字典排序.有了<code>-n</code>就可以避免11排在9前面这种尴尬了.<code>-k</code>对于多字段文本特别有用,比如让<code>ls</code>的结果按照文件大小排序,可以这么写:</p> <pre><code>ls -l /usr/bin | sort -nr -k 5 </code></pre> <p>我有时还需要按照字段的特定方式排序,例如对于<code>MM/DD/YYYY</code>格式的日期,想按照时间排序,sort也可以做到:</p> <pre><code>Ubuntu 8.10 10/30/2008 sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt </code></pre> <p>上面例子中的<code>3</code>表示第三个字段,<code>.7</code>表示字段的第7个字符开始,因为同时指定了<code>-n</code>,<code>-k 3.7n</code> 合起来就是<code>第3个字段的地7个字符开始的数值</code>.多个<code>-k</code>表示先按第一个k指定的字段排,在相同值的情况下再按后面的k指定字段排序.</p> <p>通常sort以空白作为字段的分隔符,但是也可以用-t指定其他的分隔符.</p> <p><code>-m</code>可以用来合并多个已经排好序的文件到一个文件中</p> <p>还有个<code>-u</code>参数,可以对排好序的结果做uniq,不必再调用<code>uniq</code>命令去重.</p> </div> bash 重定向技巧 http://icerote.net/blog/post/136 http://icerote.net/blog/post/136 <div style="direction:ltr"> <p>重定向的顺序安排非常重要,例如:</p> <pre><code>&gt;ls-output.txt 2&gt;&amp;1 </code></pre> <p>重定向标准错误到文件 ls-output.txt,但是如果命令顺序改为:</p> <pre><code>2&gt;&amp;1 &gt;ls-output.txt </code></pre> <p>则标准错误定向到屏幕。</p> <p>现在的 bash 版本提供了第二种方法,更精简合理的方法来执行这种联合的重定向:</p> <pre><code>ls -l /bin/usr &amp;&gt; ls-output.txt </code></pre> </div> 记一次字符串性能分析 http://icerote.net/blog/post/135 http://icerote.net/blog/post/135 <div style="direction:ltr"> <p>因为材质库增加了大量材质,不出意外地,Protein加载系统库时时间变长,也不出意外地,某产品那边表示性能很重要,必须解决.不多说了.</p> <p>虽说Protein 4.0的时候,因为使用了Xirang重写实现,性能比3.0快了1~2个数量级,但是那个结果其实并不是因为Xirang够快,而是Protein 3.0实在太慢了.</p> <p>Protein 4.0 其实也不快,以前虽然有过一些度量和分析,但是时间久远,,数据也没有存档.借这这一次,有机会再省视一下,发现大致有如下性能问题:</p> <ol> <li>内存分配次数问题</li> <li>隔离了实现的iterator</li> <li>低效的已序序列的并、交、差问题</li> <li>Xirang.type中将copy construction分解为default construction + assignment的问题</li> <li>string的效率问题</li> <li>lexical cast的问题</li> <li>二进制接口隔离导致的性能损失</li> <li>访问type member和sub object的性能损失</li> <li>deserialize 时接口调用带来的性能损失</li> </ol> <p>其中4、5和1的关系密切. 上面是做了优化后的结果了,优化之前1的情况还要糟糕得多.</p> <p>因为对string做了比较细致的测量,所以这里就做一下分析和记录.别的部分有心情再说.</p> <p>在一个典型应用的测试中,我记录了整个周期所有字符串的活动情况(注意,这不是某个时刻的系统快照,可能差异巨大):</p> <ul> <li>内容不同的字符串数量: 23916</li> <li>内存分配次数(除空串): 186959</li> <li>字符串共享计数: 1131117</li> <li>所有内容不同字符串长度合计: 1144861</li> </ul> <p>xirang中的string设计成了immutable的,带引用计数,但是没有做小字符串的优化.因此这里的一次内存分配必然对应一次构造,实际做了186959次内存分配.</p> <p>基于经验猜测,小字符串优化对性能影响是巨大的.那么在我的这个例子中,实际影响有多大呢?考虑在64位机器上实现string,那至少要能容纳一个指针,即8个字节.假设一个额外字节存储字符'\0',另一个字符用来做标记,那就是两个字符的额外开销.因此,我统计了长度分别为6,14,22,和30的情况如下表</p> <p></p><table> <thead><tr> <th>长度类别</th><th>数量</th><th>内存分配次数</th><th>共享计数</th><th>字符总长度</th> </tr></thead> <tbody> <tr><td>6</td><td>397</td><td>31784</td><td>111825</td><td>1728</td></tr> <tr><td>14</td><td>3402</td><td>110235</td><td>506131</td><td>33178</td></tr> <tr><td>22</td><td>6188</td><td>141249</td><td>819703</td><td>83360</td></tr> <tr><td>30</td><td>9963</td><td>150382</td><td>1006513</td><td>182629</td></tr> </tbody></table></p> <p> 百分比数据: </p> <table> <thead><tr> <th>长度类别</th><th>数量</th><th>内存分配次数</th><th>共享计数</th><th>字符总长度</th> </tr></thead> <tbody> <tr><td>6</td><td>1.66</td><td>17.00</td><td>9.89</td><td>0.15</td></tr> <tr><td>14</td><td>14.22</td><td>58.96</td><td>44.75</td><td>2.90</td></tr> <tr><td>22</td><td>25.87</td><td>75.55</td><td>72.47</td><td>7.28</td></tr> <tr><td>30</td><td>41.66</td><td>80.44</td><td>88.98</td><td>15.95</td></tr> </tbody></table> <p><p></p> <p>从上述数据来看,采用32字节大小的string可以削减80%以上的内存分配操作,但此时共享技术削减的次数更高,达到88%.因此,单从次数上来说,再增加string的大小不太可能会更经济了.但是这么分析并没有考虑到共享方式的计数器增减对cache的毒害效应,因为共享计数器必须是原子的.但无论如何,32字节可能是一个值得考虑的上界.另外,随着string变大,额外引进的内存开销增加也是巨大的.</p> <p>下面分析一下现有的string实现实际内存开销.一个string本身只含有一个指针,因此大小是8,数据部分包括计数器、hash值和size,在64位机器上大小是24,因此数据部分实际大小就是24 + N,考虑到对齐,数据部分大小将是32,40,48...这样的序列.</p> <p>对长度6字节以下的字符串而言,系统实际分配的内存,数据部分大小是32,加上string本体,合计40字节.计算内存大小的公式采用:</p> <p><code>本体大小*共享计数 + 数据大小*内存分配计数</code></p> <p>因此内存开销分别为:</p> <ul> <li>6字节以下: <code>8*111825 + 32*31784=1911688</code></li> <li>7~14字节: <code>8*506131 + 40*110235=8458448</code></li> <li>15~22字节: <code>8*819703 + 48*141249=13337576</code></li> <li>23~30字节: <code>8*1006513 + 56*150382=16473496</code></li> </ul> <p>如果为小字符串优化,当string大小取不同值时内存开销的变化:</p> <ul> <li>8字节: <code>8*111825=894600</code> <ul> <li>降低:<code>1911688 - 894600 = 1017088</code></li> </ul> </li> <li>16字节: <code>16*(111825 + 506131)=9887296</code> <ul> <li>降低: <code>(8458448 + 1911688) - 9887296 = 482840</code></li> </ul> </li> <li>24字节: <code>24*(111825 + 506131 + 819703)=34503816</code> <ul> <li>降低: <code>(13337576 + 8458448 + 1911688) - 34503816 = -10796104</code></li> </ul> </li> <li>32字节: <code>32*(111825 + 506131 + 819703 + 1006513)=78213504</code> <ul> <li>降低: <code>(16473496+ 13337576 + 8458448 + 1911688) - 78213504 = -38032296</code></li> </ul> </li> </ul> <p>因为数据搜集方法的原因,这里的内存增减变化只意味着内存使用足迹的变化,不等于某个时刻的系统状态.不难看出,16字节是一个转折点.从性能上来讲,考虑到cache,复制16个字节的数据很大概率上是远远快于修改引用计数器的.因此,对于Protein来说,有充分的理由实施小字符串优化,不但能够减少58.96%的内存分配操作,同时也降低内存使用,还能避免共享计数器对cache的毒害,一举三得.</p> <p>接下来从频率的角度分析.取top 1000的高频字符串:</p> <pre><code>Top N allocate share chars 100: 97675 726303 1716 200: 110745 888819 3894 300: 127047 942431 5446 400: 131816 967705 7878 600: 139471 989569 11372 800: 144086 1000744 15038 1000: 147762 1006869 18484 23916: 186959 1131117 1144861 百分比: Top N allocate share chars 100: 52.24 64.21 0.15 200: 59.23 78.58 0.34 300: 67.95 83.32 0.48 400: 70.51 85.55 0.69 600: 74.60 87.49 0.99 800: 77.07 88.47 1.31 1000: 79.03 89.02 1.61 23916: 100 100 100 </code></pre> <p>仅仅是前100条就占据了所有字符串一半以上内存分配操作和60%以上的共享计数开销,因此预置静态字符串池很可能是值得的.假设放300条数据,即便算上额外内存开销,也只需要10K左右--实际因为无需引用计数,将能降低内存占用,依赖于池的实现,这里不再详细计算了--但却能够消除60%~80%的内存分配和共享计数操作.代价是内存分配操作要换成查表操作,且查找失败的概率32.05%,这是额外惩罚.但对于共享计数操作部分,将是完全的净收益,因为不再需要修改共享计数器了.这里字符串池的查找性能将是至关重要的,完美hash表,前缀树都有可能是候选。</p> <p>字符串池、小字符串优化和共享数据,这三种措施可以同时实施,只是string的实现也需要分别处理这三种状态。这可能使实现变得有点复杂,但是对于Protein和使用的系统来说,字符串的性能是及其重要的,每一点微小的提高都是值得的.</p> </div> Linux 容器 (LXC) http://icerote.net/blog/post/9 http://icerote.net/blog/post/9 <div style="direction:ltr"> <h2>错误处理</h2> <ol> <li><blockquote><p><code>unshare: Operation not permitted</code></p></blockquote></li> </ol> <pre><code class="``"> echo 1 &gt; /sys/fs/cgroup/cpuset/cgroup.clone_children echo 1 &gt; /proc/sys/kernel/unprivileged_userns_clone </code></pre> <ol> <li><blockquote><p><code>WARN: could not reopen tty: Permission denied</code></p></blockquote></li> </ol> <p> 参考 <a href="https://gist.github.com/julianlam/4e2bd91d8dedee21ca6f">https://gist.github.com/julianlam/4e2bd91d8dedee21ca6f</a>,<a href="https://github.com/lxc/lxc/issues/181">https://github.com/lxc/lxc/issues/181</a> TL;DR 总之,这是因为先<code>su</code>到root,再<code>su -</code>到非特权用户,在此过程中,cgroup信息有丢失导致问题。</p> </div> Time Machine备份速度很慢 http://icerote.net/blog/post/134 http://icerote.net/blog/post/134 <div style="direction:ltr"> <p>Time Machine备份很慢,google之,发现下面的设置有效:</p> <pre><code>sudo sysctl debug.lowpri_throttle_enabled=0 </code></pre> <p>备份完再改回来:</p> <pre><code>sudo sysctl debug.lowpri_throttle_enabled=1 </code></pre> <p>备份过程中,可以通过 <code>fs_usage backupd</code> 查看文件访问详情。</p> </div>