<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Soul Of Free Loop &#187; Linux</title>
	<atom:link href="https://zohead.com/archives/category/technology/linux/feed/" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>Linux实现惠尔顿上网认证客户端</title>
		<link>https://zohead.com/archives/wholeton-linux-client/</link>
		<comments>https://zohead.com/archives/wholeton-linux-client/#comments</comments>
		<pubDate>Sun, 15 May 2022 03:34:10 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[WebSocket]]></category>
		<category><![CDATA[惠尔顿]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1745</guid>
		<description><![CDATA[最近公司由于某些特殊原因，需要上一套惠尔顿的上网行为管理系统，局域网内的所有设备在连接互联网时都需要通过惠尔顿上网认证客户端进行登录，或者需要管理员在上网行为管理中设置免监控。 惠尔顿官方只提供了 Windows 上的客户端进行用户登录，也可以访问网页版进行临时登录，效果如下： 不过 Linux 服务器设备就没法登录了，为此我写了个 Python 程序模拟惠尔顿的网页版客户端实现 Linux / macOS 等系统下的上网认证功能。 Python 程序 我编写的 wholeton-auth.py Python 程序可以通过 Pastebin 分享链接 下载，写的很简单粗糙，这里贴出来说明一下： [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>最近公司由于某些特殊原因，需要上一套惠尔顿的上网行为管理系统，局域网内的所有设备在连接互联网时都需要通过惠尔顿上网认证客户端进行登录，或者需要管理员在上网行为管理中设置免监控。</p>
<p>惠尔顿官方只提供了 Windows 上的客户端进行用户登录，也可以访问网页版进行临时登录，效果如下：</p>
<p><img src="https://images.weserv.nl/?url=http://res.cloudinary.com/digwht2y0/image/upload/v1737442965/wholeton-login.jpg" alt="惠尔顿上网认证系统网页版"></p>
<p>不过 Linux 服务器设备就没法登录了，为此我写了个 Python 程序模拟惠尔顿的网页版客户端实现 Linux / macOS 等系统下的上网认证功能。</p>
<h2 id="python-wholeton-client">Python 程序</h2>
<p>我编写的 <code>wholeton-auth.py</code> Python 程序可以通过 <a href="https://pastebin.com/raw/5XhfVS2S">Pastebin 分享链接</a> 下载，写的很简单粗糙，这里贴出来说明一下：</p>
<pre class="brush: python; highlight: [20,21,22,23,24,25]; title: ; notranslate">
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import socket
from datetime import datetime
from uuid import getnode
import urllib
try:
    import urllib2
except Exception:
    from urllib import request as urllib2
try:
    import Cookie as cookies
except Exception:
    from http import cookies
import websocket
import json

wholeton_host = '192.168.1.254'
wholeton_user = 'test'
wholeton_pass = '123456'
wholeton_ip = ''
wholeton_mac = ''
update_secs = 28800

def url_encode(obj):
    try:
        return urllib.urlencode(obj)
    except Exception:
        return urllib.parse.urlencode(obj)

def get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP

def get_mac():
    return ':'.join((&quot;%012x&quot; % getnode())[i:i+2] for i in range(0, 12, 2))

if not wholeton_ip:
    wholeton_ip = get_ip()

if not wholeton_mac:
    wholeton_mac = get_mac()

uri_keys = { 'id' : 0, 'url' : 'mail.126.com', 'user' : wholeton_ip, 'mac' : wholeton_mac }
uri_data = url_encode(uri_keys).replace('%3A', ':')

auth_data = url_encode({ 'param[UserName]' : wholeton_user, 'param[UserPswd]' : wholeton_pass, 'uri' : uri_data, 'force' : 0 })
# convert for python 3
if sys.version_info[0] == 3:
    auth_data = auth_data.encode('ascii')

ws = None
loop = True

try:
    while loop:
        resp = urllib2.urlopen('http://' + wholeton_host + '/user-login-auth?' + uri_data, timeout = 5, data = auth_data)

        # get session cookie
        cookie = cookies.SimpleCookie()
        cookie.load(resp.info()['Set-Cookie'])

        resp_data = resp.read()
        if resp_data:
            print('Login response:')
            print(resp_data)

        ws = websocket.WebSocket()
        ws.connect('ws://' + wholeton_host + '/go-ws/user-auth', cookie = 'fms_session=' + cookie.get('fms_session').value, origin = 'http://' + wholeton_host)

        dt_start = datetime.now()
        while ws:
            try:
                ws_data = ws.recv()
            except KeyboardInterrupt:
                loop = False
                break
            except:
                break

            if ws_data:
                dt_now = datetime.now()
                if (dt_now - dt_start).seconds &gt;= update_secs:
                    break

                print(dt_now)
                print(ws_data)

                ws_obj = json.loads(ws_data)
                if ws_obj and ws_obj[&quot;type&quot;] == &quot;logged-out&quot;:
                    break

        if ws:
            ws.close()
        ws = None
except KeyboardInterrupt:
    pass

if ws:
    ws.close()
</pre>
<p>为了支持比较老的服务器系统，此 Python 程序同时支持 Python 2 和 Python 3，在 Python 2.6 和 Python 3.6 版本上做了测试。为了减少依赖，本程序基本都是用的 Python 自带的模块，并针对 Python 2 和 Python 3 进行了区分处理。</p>
<p>程序首先通过惠尔顿网页版的 HTTP 登录接口进行登录，登录成功之后获取会话信息，然后建立 WebSocket 连接，并保持 WebSocket 连接状态，如果关闭了连接上网认证功能就会自动失效。</p>
<p>由于本程序需要建立 WebSocket 连接，也不需要任何 WebSocket 服务器的功能，所以没有用各种功能齐全的 WebSocket 库，只额外使用了 <a href="https://pypi.org/project/websocket-client/">websocket-client</a> 这个非常简单的 WebSocket 客户端库。</p>
<p>另外最新 1.3.2 版本的 websocket-client 库只支持 Python 3.7 及以上版本，为了能给 Python 2.6 版本使用，本程序使用的是 websocket-client 老一点的 <a href="https://pypi.org/project/websocket-client/0.59.0/">0.59.0</a> 版本，websocket-client 0.59.0 版本支持 Python 2.6 以上或 Python 3.4 以上版本，而且只依赖 <a href="https://pypi.org/project/six/">six</a> 这个 Python 兼容库，同时 six 库也使用 <a href="https://pypi.org/project/six/1.13.0/">1.13.0</a> 版本以兼容 Python 2.6 版本。</p>
<p>可以使用 pip 命令先安装 six 1.13.0 版本和 websocket-client 0.59.0 版本：</p>
<pre class="brush: bash; title: ; notranslate">
~$ pip install six==1.13.0
~$ pip install websocket-client==0.59.0
</pre>
<h2 id="wholton-using">上网认证使用</h2>
<p>上面依赖的库安装好之后，首先需要修改 <code>wholeton-auth.py</code> Python 程序中的配置（上面已经高亮显示了）：</p>
<pre class="brush: plain; title: ; notranslate">
wholeton_host = '192.168.1.254'
wholeton_user = 'test'
wholeton_pass = '123456'
wholeton_ip = ''
wholeton_mac = ''
update_secs = 28800
</pre>
<p>简单说明如下：</p>
<ul>
<li><code>wholeton_host</code> 指定惠尔顿上网行为管理系统 IP 地址；</li>
<li><code>wholeton_user</code> 和 <code>wholeton_pass</code> 指定上网行为管理的用户名和密码；</li>
<li><code>wholeton_ip</code> 为本机 IP 地址，默认为空，程序会自动获取，也可以自行修改覆盖；</li>
<li><code>wholeton_mac</code> 为本机上网网卡（也就是连接上网行为管理系统的网卡）的 MAC 地址，默认为空，程序也会自动获取（这里为了图省事使用的 <code>uuid</code> 库的 <code>getnode</code> 函数获取网卡 MAC 地址），也可以自行修改覆盖；</li>
<li><code>update_secs</code> 指定登录多长时间之后就强制断开连接进行重新登录，单位为秒，默认为 <code>28800</code> 秒也就是 8 个小时。</li>
</ul>
<p>因为我这边使用本程序测试发现惠尔顿的上网认证系统即使在保持 WebSocket 连接打开的情况下，大概 9 ~ 10 个小时之后也会无法上网，所以大家可以根据实际情况测试确认之后，修改程序里的 <code>update_secs</code> 重新登录时间。</p>
<p>修改完配置之后，使用起来就很简单了，根据 Python 版本在终端里运行 <code>python2 wholeton-auth.py</code> 或者 <code>python3 wholeton-auth.py</code> 就可以了，程序运行之后会在标准输出中显示上网认证服务器返回的响应数据。</p>
<p>最后本程序还是需要一直保持运行的，大家可以根据自己的需要通过 screen 或者服务等方式来实现后台运行哦。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/wholeton-linux-client/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>ARM64 Linux下ARM容器使用yum的问题</title>
		<link>https://zohead.com/archives/arm64-arm-container-yum/</link>
		<comments>https://zohead.com/archives/arm64-arm-container-yum/#comments</comments>
		<pubDate>Thu, 29 Oct 2020 16:52:31 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[aarch64]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[ARM64]]></category>
		<category><![CDATA[armhf]]></category>
		<category><![CDATA[RPM]]></category>
		<category><![CDATA[YUM]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1689</guid>
		<description><![CDATA[ARM 容器问题 前段时间需要验证某客户的 32 位 ARM Linux 程序，由于我们只有现成的 ARM64 设备和系统，而 ARM64 兼容 32 位 armel 和 armhf，因此想到直接在 ARM64（aarch64）Linux 系统中运行 32 位 ARM（armhf）容器来进行测试。 客户程序使用的是 CentOS 7 32 位 ARM 开发环境，这里我使用 LXC 官方镜像来创建一个完整的 CentOS 7 容器系统： 32 位 ARM 容器可以正常启动，但使用 yum 安装任何软件包会报错： yum 修改 上面 yum 命令的报错提示找不到 repo 为 base/7/arm [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="arm-container-issue">ARM 容器问题</h2>
<p>前段时间需要验证某客户的 32 位 ARM Linux 程序，由于我们只有现成的 ARM64 设备和系统，而 ARM64 兼容 32 位 armel 和 armhf，因此想到直接在 ARM64（aarch64）Linux 系统中运行 32 位 ARM（armhf）容器来进行测试。</p>
<p>客户程序使用的是 CentOS 7 32 位 ARM 开发环境，这里我使用 LXC 官方镜像来创建一个完整的 CentOS 7 容器系统：</p>
<pre class="brush: bash; title: ; notranslate">
~ # lxc-create -n arm -t download --dir=/var/lib/lxc/arm/rootfs -- --server mirrors.tuna.tsinghua.edu.cn/lxc-images -d centos -r 7 -a armhf
</pre>
<p>32 位 ARM 容器可以正常启动，但使用 yum 安装任何软件包会报错：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum install openssh-server
Loaded plugins: fastestmirror
Determining fastest mirrors


 One of the configured repositories failed (Unknown),
 and yum doesn't have enough cached data to continue. At this point the only
 safe thing yum can do is fail. There are a few ways to work &quot;fix&quot; this:

     1. Contact the upstream for the repository and get them to fix the problem.

     2. Reconfigure the baseurl/etc. for the repository, to point to a working
        upstream. This is most often useful if you are using a newer
        distribution release than is supported by the repository (and the
        packages for the previous distribution release still work).

     3. Run the command with the repository temporarily disabled
            yum --disablerepo=&lt;repoid&gt; ...

     4. Disable the repository permanently, so yum won't use it by default. Yum
        will then just ignore the repository until you permanently enable it
        again or use --enablerepo for temporary usage:

            yum-config-manager --disable &lt;repoid&gt;
        or
            subscription-manager repos --disable=&lt;repoid&gt;

     5. Configure the failing repository to be skipped, if it is unavailable.
        Note that yum will try to contact the repo. when it runs most commands,
        so will have to try and fail each time (and thus. yum will be be much
        slower). If it is a very temporary problem though, this is often a nice
        compromise:

            yum-config-manager --save --setopt=&lt;repoid&gt;.skip_if_unavailable=true

Cannot find a valid baseurl for repo: base/7/armv8l
</pre>
<h2 id="yum-patch">yum 修改</h2>
<p>上面 yum 命令的报错提示找不到 repo 为 <code>base/7/armv8l</code> 的地址，我们可以在容器中使用 <code>uname</code> 命令确认当前的系统架构为 <code>armv8l</code>（ARM64 内核运行 32 位 ARM 程序）：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# uname -m
armv8l
</pre>
<p>而在主机端则可以看到真正的系统架构为 <code>aarch64</code>：</p>
<pre class="brush: bash; title: ; notranslate">
~ # uname -m
aarch64
</pre>
<p>正是由于 yum 不能正确识别 <code>armv8l</code>这个特殊的系统架构，导致命令执行失败。</p>
<p>我们可以修改 <code>/usr/lib/python2.7/site-packages/rpmUtils/arch.py</code> 文件，使 yum 可以正确处理 <code>armv8l</code> 架构，以下为 patch 修改内容：</p>
<pre class="brush: diff; title: ; notranslate">
diff -rNp a/arch.py b/arch.py
*** a/arch.py   Thu Oct 29 23:46:21 2020
--- b/arch.py   Thu Oct 29 23:48:55 2020
*************** arches = {
*** 73,78 ****
--- 73,79 ----
      &quot;armv5tel&quot;: &quot;noarch&quot;,
  
      #arm hardware floating point
+     &quot;armv8l&quot;: &quot;armv7hl&quot;,
      &quot;armv7hnl&quot;: &quot;armv7hl&quot;,
      &quot;armv7hl&quot;: &quot;noarch&quot;,
  
*************** def getBaseArch(myarch=None):
*** 442,447 ****
--- 443,450 ----
          return &quot;ppc&quot;
      elif myarch.startswith(&quot;arm64&quot;):
          return &quot;arm64&quot;
+     elif myarch == &quot;armv8l&quot;:
+         return &quot;armhfp&quot;
      elif myarch.startswith(&quot;armv7h&quot;):
          return &quot;armhfp&quot;
      elif myarch.startswith(&quot;arm&quot;):
</pre>
<p>此时再运行 yum 就可以正确找到软件包了：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum install openssh-server
Loaded plugins: fastestmirror
Determining fastest mirrors
 * base: mirrors.bfsu.edu.cn
 * extras: mirrors.bfsu.edu.cn
 * updates: mirrors.bfsu.edu.cn
base                                                                                                                                                                                              | 3.6 kB  00:00:00
extras                                                                                                                                                                                            | 2.9 kB  00:00:00
updates                                                                                                                                                                                           | 2.9 kB  00:00:00
(1/4): base/7/armhfp/group_gz                                                                                                                                                                     | 153 kB  00:00:00
(2/4): extras/7/armhfp/primary_db                                                                                                                                                                 | 170 kB  00:00:00
(3/4): updates/7/armhfp/primary_db                                                                                                                                                                | 562 kB  00:00:00
(4/4): base/7/armhfp/primary_db                                                                                                                                                                   | 4.1 MB  00:00:00
Resolving Dependencies
--&gt; Running transaction check
---&gt; Package openssh-server.armv7hl 0:7.4p1-21.el7 will be installed
--&gt; Processing Dependency: libwrap.so.0 for package: openssh-server-7.4p1-21.el7.armv7hl
--&gt; Running transaction check
---&gt; Package tcp_wrappers-libs.armv7hl 0:7.6-77.el7 will be installed
--&gt; Finished Dependency Resolution

Dependencies Resolved

=========================================================================================================================================================================================================================
 Package                                                    Arch                                             Version                                                 Repository                                     Size
=========================================================================================================================================================================================================================
Installing:
 openssh-server                                             armv7hl                                          7.4p1-21.el7                                            base                                          446 k
Installing for dependencies:
 tcp_wrappers-libs                                          armv7hl                                          7.6-77.el7                                              base                                           64 k

Transaction Summary
=========================================================================================================================================================================================================================
Install  1 Package (+1 Dependent package)

Total download size: 510 k
Installed size: 1.0 M
Is this ok [y/d/N]: y
Downloading packages:
(1/2): tcp_wrappers-libs-7.6-77.el7.armv7hl.rpm                                                                                                                                                   |  64 kB  00:00:00
(2/2): openssh-server-7.4p1-21.el7.armv7hl.rpm                                                                                                                                                    | 446 kB  00:00:00
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                                                                    1.7 MB/s | 510 kB  00:00:00
Running transaction check
Running transaction test


Transaction check error:
  package tcp_wrappers-libs-7.6-77.el7.armv7hl is intended for a different architecture
  package openssh-server-7.4p1-21.el7.armv7hl is intended for a different architecture
</pre>
<p>不过这次轮到 rpm 报错了，提示 RPM 包的架构不相符。</p>
<h2 id="rpm-patch">rpm 修改</h2>
<p>我们先试着手工安装 yum 下载下来但安装失败的 RPM 包，可以看到还是相同的报错：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# rpm -ivh /var/cache/yum/armhfp/7/base/packages/tcp_wrappers-libs-7.6-77.el7.armv7hl.rpm
Preparing...                          ################################# [100%]
        package tcp_wrappers-libs-7.6-77.el7.armv7hl is intended for a different architecture
</pre>
<p>这也是由于 rpm 命令不能正确处理 <code>armv8l</code> 架构，其实对于这种 RPM 包的架构不相符的情况，我们可以为 rpm 命令增加 <code>--ignorearch</code> 参数强制安装：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# rpm -ivh --ignorearch /var/cache/yum/armhfp/7/base/packages/tcp_wrappers-libs-7.6-77.el7.armv7hl.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:tcp_wrappers-libs-7.6-77.el7     ################################# [100%]
</pre>
<p>当然我们的最终目的是要让 yum 能够自动安装软件包，因此还需要让 rpm 命令也能正确识别 <code>armv8l</code> 架构。</p>
<p>为此我们需要修改 <code>/usr/lib/rpm/rpmrc</code> 文件，将 <code>armv8l</code> 视为 <code>armv7hnl</code>：</p>
<pre class="brush: diff; title: ; notranslate">
diff -rNp a/rpmrc b/rpmrc 
*** a/rpmrc     Fri Oct 30 00:15:17 2020
--- b/rpmrc     Fri Oct 30 00:16:32 2020
*************** arch_compat: armv5tel: armv4tl
*** 398,403 ****
--- 398,404 ----
  arch_compat: armv4tl: armv4l
  arch_compat: armv4l: armv3l
  arch_compat: armv3l: noarch
+ arch_compat: armv8l: armv7hnl
  arch_compat: armv7hnl: armv7hl
  arch_compat: armv7hl: noarch
</pre>
<p>此时我们再使用 yum 命令就可以正确下载和安装软件包了：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum install openssh-server
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.bfsu.edu.cn
 * extras: mirrors.bfsu.edu.cn
 * updates: mirrors.bfsu.edu.cn
Resolving Dependencies
--&gt; Running transaction check
---&gt; Package openssh-server.armv7hl 0:7.4p1-21.el7 will be installed
--&gt; Finished Dependency Resolution

Dependencies Resolved

=========================================================================================================================================================================================================================
 Package                                                  Arch                                              Version                                                 Repository                                      Size
=========================================================================================================================================================================================================================
Installing:
 openssh-server                                           armv7hl                                           7.4p1-21.el7                                            base                                           446 k

Transaction Summary
=========================================================================================================================================================================================================================
Install  1 Package

Total size: 446 k
Installed size: 921 k
Is this ok [y/d/N]: y
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
  Installing : openssh-server-7.4p1-21.el7.armv7hl                                                                                                                                                                   1/1
  Verifying  : openssh-server-7.4p1-21.el7.armv7hl                                                                                                                                                                   1/1

Installed:
  openssh-server.armv7hl 0:7.4p1-21.el7

Complete!
</pre>
<p>后续安装其它软件包也都没有问题了，例如安装基础开发包：</p>
<pre class="brush: bash; title: ; notranslate">
bash-4.2# yum -y install gcc make openssl-devel glibc-static openssl-static
</pre>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/arm64-arm-container-yum/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>龙芯MIPS64 QEMU ioctl的问题</title>
		<link>https://zohead.com/archives/loongson-qemu-ioctl/</link>
		<comments>https://zohead.com/archives/loongson-qemu-ioctl/#comments</comments>
		<pubDate>Wed, 26 Dec 2018 16:59:20 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[ioctl]]></category>
		<category><![CDATA[MIPS64]]></category>
		<category><![CDATA[QEMU]]></category>
		<category><![CDATA[龙芯]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1561</guid>
		<description><![CDATA[龙芯 QEMU 问题 最近搞了一块使用国产龙芯 3A3000 处理器的开源开发板，顺便捣鼓移植我们的服务程序，安装龙芯开源社区的 Loongnix 系统之后测试下来 3A3000 处理器的性能还是基本能用的。龙芯处理器自带的内存控制器也能到 DDR3-1600 了，只是目前还在使用 HT3.0 总线确实拖累系统性能。 我们部署的一些服务目前还依赖几个 x86 系统的闭源程序，无法直接在龙芯 MIPS64 系统下运行，就想到使用 QEMU 的 User-mode emulation 来实现了。 Loongnix 系统中的 qemu-i386 运行比较简单的 x86 程序一般都没有问题，不过我发现 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="loongson-qemu-issue">龙芯 QEMU 问题</h2>
<p>最近搞了一块使用国产<a href="http://www.loongson.cn/product/cpu/3/3A3000.html" target="_blank">龙芯 3A3000</a> 处理器的开源开发板，顺便捣鼓移植我们的服务程序，安装龙芯开源社区的 <a href="http://www.loongnix.org/" target="_blank">Loongnix</a> 系统之后测试下来 3A3000 处理器的性能还是基本能用的。龙芯处理器自带的内存控制器也能到 DDR3-1600 了，只是目前还在使用 HT3.0 总线确实拖累系统性能。</p>
<p>我们部署的一些服务目前还依赖几个 x86 系统的闭源程序，无法直接在龙芯 MIPS64 系统下运行，就想到使用 QEMU 的 User-mode emulation 来实现了。</p>
<p>Loongnix 系统中的 qemu-i386 运行比较简单的 x86 程序一般都没有问题，不过我发现即使用打了 ioctl patch 的 QEMU 运行其中一个使用了私有 ioctl 调用的 x86 程序仍然会出错，通过开启 qemu-i386 的 strace 可以看到错误信息：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@loongnix:~$ qemu-i386 -strace /lib/i386-linux-gnu/ld-linux.so.2 --library-path /lib/i386-linux-gnu /usr/sbin/test-agent

2459 ioctl(5,-1055634175,-42368,0,0,-42056) = -1 errno=25 (Inappropriate ioctl for device)
2459 ioctl(5,-1055634175,-41488,0,0,-41176) = -1 errno=25 (Inappropriate ioctl for device)
2459 ioctl(5,-1055634175,-44192,0,0,-43880) = -1 errno=25 (Inappropriate ioctl for device)
</pre>
<p>上面的 <code>/usr/sbin/test-agent</code> 就是要模拟运行的 x86 二进制程序，<code>/lib/i386-linux-gnu</code> 是我在龙芯 MIPS64 系统上增加的 x86 程序依赖基础库目录，其中最重要的就是 <code>libc.so.6</code> 和 <code>ld-linux.so.2</code> 库了。</p>
<p>从 strace 数据应该可以发现该 ioctl 已经发到设备，但是 kernel 返回 <code>Inappropriate ioctl</code> 错误，而 <code>-1055634175</code>  则表示 x86 二进制程序发送的是 compat ioctl，此时我才想起来确认下 qemu-i386 程序是否正确：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@loongnix:~$ file `which qemu-i386`
/usr/bin/qemu-i386: ELF 64-bit LSB executable, MIPS, MIPS64 rel2 version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=39cac8103f052b4ffa161f615159c888024ef4e3, for GNU/Linux 2.6.32, stripped
</pre>
<p>龙芯 Loongnix MIPS64 系统目前兼容几种 ABI：</p>
<ul>
<li>O32：MIPS 32 位处理器使用；</li>
<li>N64：只适用于 MIPS 64 位处理器，使用 64 位指针和 long integers；</li>
<li>N32：只适用于 MIPS 64 位处理器，在保留 MIPS N64 ABI 的几乎所有特性的情况下，使用 32 位指针和 long integers。</li>
</ul>
<p>这个 qemu-i386 使用的是 MIPS64 N64 ABI，而 qemu-i386 实际运行的 x86 二进制程序在龙芯 64 位 kernel 下会发送 compat ioctl，这样最终 qemu-i386 程序调用 compat ioctl 时就显然会有问题，报 <code>Inappropriate ioctl</code> 也就不奇怪了。</p>
<p>解决方法也就很简单了，我只要重新编译出使用 N32 或者 O32 ABI 的 qemu-i386 程序应该就能解决，为了性能考虑可以尽量使用 N32 ABI。</p>
<h2 id="n32-abi-qemu">编译 N32 ABI QEMU</h2>
<p>首先需要在 Loongnix MIPS64 系统下安装编译 QEMU 需要的 N32 版本的开发包，这里我只安装了 QEMU 用户模式需要的开发包：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@loongnix:~$ yum install glibc-devel.mipsn32el glib2-devel.mipsn32el zlib-devel.mipsn32el libffi.mipsn32el pixman-devel.mipsn32el
</pre>
<blockquote>
<p><strong>提示</strong></p>
<p>Loongnix 系统的 N64 相关库文件一般放在 <code>/usr/lib64</code> 目录下，N32 相关库文件在 <code>/usr/lib32</code> 目录下，而 O32 相关库文件则在 <code>/usr/lib</code> 目录下。</p>
</blockquote>
<p>然后进入 QEMU 源代码目录重新 configure，增加特定编译参数指定生成 N32 位的程序：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@loongnix:qemu$ PKG_CONFIG_LIBDIR=/usr/lib32/pkgconfig ./configure --target-list=i386-linux-user --disable-kvm --disable-xen --enable-pie --extra-cflags=-mabi=n32
</pre>
<p>简单说明一下：</p>
<ul>
<li><code>PKG_CONFIG_LIBDIR</code> 环境变量指定使用什么版本的 pkgconfig 查找库及开发包，这里使用 <code>/usr/lib32/pkgconfig</code> N32 版本；</li>
<li><code>--target-list</code> 指定 QEMU 编译目标，这里我们只需要 qemu-i386，因此把 KVM、Xen 等功能也禁用了；</li>
<li><code>--extra-cflags</code> 指定额外编译参数为 <code>-mabi=n32</code>，这样编译和链接时都是 N32 版本了。</li>
</ul>
<p>重新编译安装之后可以看到新的 qemu-i386 使用的就是 N32 ABI 了：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@loongnix:~$ file `which qemu-i386`
/usr/local/bin/qemu-i386: ELF 32-bit LSB executable, MIPS, N32 MIPS64 rel2 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=12e0025c42a9a63b8045c31159950f9b185f8745, stripped
</pre>
<p>最后测试使用新的 qemu-i386 运行依赖 ioctl 的 x86 二进制程序，现在就没有直接报错的问题了。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/loongson-qemu-ioctl/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Linux修改ELF解决glibc兼容性问题</title>
		<link>https://zohead.com/archives/mod-elf-glibc/</link>
		<comments>https://zohead.com/archives/mod-elf-glibc/#comments</comments>
		<pubDate>Wed, 27 Jun 2018 16:27:52 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[ELF]]></category>
		<category><![CDATA[glibc]]></category>
		<category><![CDATA[memcpy]]></category>
		<category><![CDATA[objdump]]></category>
		<category><![CDATA[patchelf]]></category>
		<category><![CDATA[readelf]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1539</guid>
		<description><![CDATA[Linux glibc 问题 相信有不少 Linux 用户都碰到过运行第三方（非系统自带软件源）发布的程序时的 glibc 兼容性问题，这一般是由于当前 Linux 系统上的 GNU C 库（glibc）版本比较老导致的，例如我在 CentOS 6 64 位系统上运行某第三方闭源软件时会报： CentOS 6 自带的 glibc 还是很老的 2.12 版本，而下载的第三方程序依赖 glibc 2.17 版本，这种情况要么自己重新编译程序，要么只能升级系统的 glibc 版本。由于我使用的程序是第三方编写并且是闭源软件无法自己编译，升级 glibc 固然可能能解决问题，但是 glibc 做为最核 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="linux-glibc-problem">Linux glibc 问题</h2>
<p>相信有不少 Linux 用户都碰到过运行第三方（非系统自带软件源）发布的程序时的 glibc 兼容性问题，这一般是由于当前 Linux 系统上的 GNU C 库（glibc）版本比较老导致的，例如我在 CentOS 6 64 位系统上运行某第三方闭源软件时会报：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# ldd tester
./tester: /lib64/libc.so.6: version `GLIBC_2.17' not found (required by ./tester)
./tester: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./tester)
        linux-vdso.so.1 =&gt;  (0x00007ffe795fe000)
        libpthread.so.0 =&gt; /lib64/libpthread.so.0 (0x00007fc7d4c73000)
        libOpenCL.so.1 =&gt; /usr/lib64/libOpenCL.so.1 (0x00007fc7d4a55000)
        libdl.so.2 =&gt; /lib64/libdl.so.2 (0x00007fc7d4851000)
        libm.so.6 =&gt; /lib64/libm.so.6 (0x00007fc7d45cd000)
        libgcc_s.so.1 =&gt; /lib64/libgcc_s.so.1 (0x00007fc7d43b7000)
        libc.so.6 =&gt; /lib64/libc.so.6 (0x00007fc7d4023000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fc7d4e90000)
</pre>
<p>CentOS 6 自带的 glibc 还是很老的 2.12 版本，而下载的第三方程序依赖 glibc 2.17 版本，这种情况要么自己重新编译程序，要么只能升级系统的 glibc 版本。由于我使用的程序是第三方编写并且是闭源软件无法自己编译，升级 glibc 固然可能能解决问题，但是 glibc 做为最核心的基础库，在生产环境上直接升级毕竟动作还是太大，因此希望还是能有更好的解决途径。</p>
<h2 id="glibc-dump">问题分析</h2>
<p>首先我们可以检查一下程序使用了新版本 glibc 的哪些符号，使用 <code>objdump</code> 命令可以查看 ELF 文件的动态符号信息：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# objdump -T tester | grep GLIBC_2.1.*
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.14  memcpy
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.17  clock_gettime
</pre>
<p>从上面的输出可以看到程序使用了 glibc 2.14 版本的 <code>memcpy</code> 函数和 glibc 2.17 版本的 <code>clock_gettime</code> 函数，而这两个常用的函数按说应该是 glibc 很早就已经支持了的，我们可以确认一下当前系统 glibc 提供的符号版本：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# objdump -T /lib64/libc.so.6 | grep memcpy
0000000000091300  w   DF .text  0000000000000009  GLIBC_2.2.5 wmemcpy
0000000000101070 g    DF .text  000000000000001b  GLIBC_2.4   __wmemcpy_chk
00000000000896b0 g    DF .text  0000000000000465  GLIBC_2.2.5 memcpy
00000000000896a0 g    DF .text  0000000000000009  GLIBC_2.3.4 __memcpy_chk
[root@centos6-dev ~]# objdump -T /lib64/libc.so.6 | grep clock_gettime
000000000038f800 g    DO .bss   0000000000000008  GLIBC_PRIVATE __vdso_clock_gettime
</pre>
<p>这里可以看出 CentOS 6 的 glibc 库提供的 <code>memcpy</code> 实现是 2.2.5 版本的，另外 <code>libc</code> 没有直接实现 <code>clock_gettime</code> 函数，因为老版本 glibc 里 <code>clock_gettime</code> 是由 <code>librt</code> 库提供 <code>clock_gettime</code> 支持的，而且同样也是 2.2.5 版本：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# objdump -T /lib64/librt.so.1 | grep clock_gettime
0000000000000000      DO *UND*  0000000000000000  GLIBC_PRIVATE __vdso_clock_gettime
0000000000003e70 g    DF .text  000000000000008b  GLIBC_2.2.5 clock_gettime
</pre>
<p>看过这里就基本明白了，第三方程序的开发者是在自带新版本 glibc 的 Linux 系统上编译的，<code>memcpy</code> 和 <code>clock_gettime</code> 的实现默认使用了该系统上 glibc 所提供的最新版本，这样在低版本 glibc 系统中就无法正常运行。</p>
<h2 id="fix-glibc">解决方法</h2>
<p>虽然我们无法重新编译第三方程序，但如果可以修改 ELF 文件强制让 LD 库加载程序时使用老版本的 <code>memcpy</code> 和 <code>clock_gettime</code> 实现，应该就可以避免升级 glibc。</p>
<h3 id="dump-elf">分析 ELF</h3>
<p>首先用 <code>readelf</code> 命令查看 ELF 的符号表，由于该命令输出非常多，这里只贴出我们关心的信息：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# readelf -sV tester

Symbol table '.dynsym' contains 4583 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
    ......
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (5)
    ......
    67: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND clock_gettime@GLIBC_2.17 (16)
    ......
  4582: 0000000000794260    70 FUNC    WEAK   DEFAULT   12 _ZNSt15basic_streambufIwS

Version symbols section '.gnu.version' contains 4583 entries:
 Addr: 000000000045b508  Offset: 0x05b508  Link: 4 (.dynsym)
  000:   0 (*local*)       0 (*local*)       2 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)
  004:   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)
  008:   4 (GLIBC_2.3.2)   3 (GLIBC_2.2.5)   0 (*local*)       5 (GLIBC_2.14)
  ......
  040:   2 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)   3 (GLIBC_2.2.5)  10 (GLIBC_2.17)
  ......
  11e0:   1 (*global*)      1 (*global*)      1 (*global*)      1 (*global*)
  11e4:   1 (*global*)      1 (*global*)      1 (*global*)

Version needs section '.gnu.version_r' contains 6 entries:
 Addr: 0x000000000045d8d8  Offset: 0x05d8d8  Link: 5 (.dynstr)
  000000: Version: 1  File: ld-linux-x86-64.so.2  Cnt: 1
  0x0010:   Name: GLIBC_2.3  Flags: none  Version: 17
  0x0020: Version: 1  File: libgcc_s.so.1  Cnt: 3
  0x0030:   Name: GCC_3.0  Flags: none  Version: 13
  0x0040:   Name: GCC_3.3  Flags: none  Version: 11
  0x0050:   Name: GCC_4.2.0  Flags: none  Version: 10
  0x0060: Version: 1  File: libm.so.6  Cnt: 1
  0x0070:   Name: GLIBC_2.2.5  Flags: none  Version: 8
  0x0080: Version: 1  File: libpthread.so.0  Cnt: 2
  0x0090:   Name: GLIBC_2.3.2  Flags: none  Version: 15
  0x00a0:   Name: GLIBC_2.2.5  Flags: none  Version: 7
  0x00b0: Version: 1  File: libc.so.6  Cnt: 10
  0x00c0:   Name: GLIBC_2.8  Flags: none  Version: 19
  0x00d0:   Name: GLIBC_2.9  Flags: none  Version: 18
  0x00e0:   Name: GLIBC_2.17  Flags: none  Version: 16
  0x00f0:   Name: GLIBC_2.4  Flags: none  Version: 14
  0x0100:   Name: GLIBC_2.3.4  Flags: none  Version: 12
  0x0110:   Name: GLIBC_2.3  Flags: none  Version: 9
  0x0120:   Name: GLIBC_2.7  Flags: none  Version: 6
  0x0130:   Name: GLIBC_2.14  Flags: none  Version: 5
  0x0140:   Name: GLIBC_2.3.2  Flags: none  Version: 4
  0x0150:   Name: GLIBC_2.2.5  Flags: none  Version: 3
  0x0160: Version: 1  File: libdl.so.2  Cnt: 1
  0x0170:   Name: GLIBC_2.2.5  Flags: none  Version: 2
</pre>
<p>我们可以在 ELF 的 <code>.dynsym</code> 动态符号表中看到程序用于动态链接的所有导入导出符号，<code>memcpy</code> 和 <code>clock_gettime</code> 后面括号里的数字就是十进制的版本号（分别为 <code>5</code> 和 <code>16</code>），而我们需要格外关注的是下面的 <code>.gnu.version</code> 和 <code>.gnu.version_r</code> 符号版本信息段。</p>
<p><code>.gnu.version</code> 表包含所有动态符号的版本信息，<code>.dynsym</code> 动态符号表中的每个符号都可以在 <code>.gnu.version</code> 中看到对应的条目（<code>.dynsym</code> 中一共 4583 个符号刚好与 <code>.gnu.version</code> 的结束位置 0x11e7 相等）。</p>
<p>从上面的输出可以看到 <code>.gnu.version</code> 表从 <code>0x05b508</code> 偏移量开始，我们可以看看对应偏移量的十六进制数据：</p>
<pre class="brush: plain; title: ; notranslate">
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0005B500                          00 00 00 00 02 00 03 00          ........
0005B510  03 00 03 00 03 00 03 00 04 00 03 00 00 00 05 00  ................
0005B520  03 00 03 00 06 00 00 00 03 00 07 00 08 00 08 00  ................
0005B530  03 00 09 00 03 00 03 00 0A 00 07 00 03 00 00 00  ................
0005B540  03 00 03 00 0B 00 07 00 03 00 03 00 00 00 07 00  ................
0005B550  00 00 03 00 03 00 03 00 03 00 0C 00 09 00 00 00  ................
0005B560  07 00 03 00 03 00 07 00 03 00 07 00 0C 00 00 00  ................
0005B570  0D 00 03 00 07 00 07 00 0E 00 0F 00 03 00 0D 00  ................
0005B580  03 00 03 00 03 00 03 00 02 00 03 00 03 00 10 00  ................
0005B590  03 00 00 00 03 00 07 00 08 00 07 00 07 00 03 00  ................
0005B5A0  03 00 0D 00 03 00 00 00 03 00 03 00 03 00 00 00  ................
</pre>
<p><code>.gnu.version</code> 中的每个条目占用两个字节，其值为符号的版本，由此可以看到其中第 0x0b 个符号（也就是 <code>.dynsym</code> 表中的 <code>memcpy@GLIBC_2.14</code> 符号）的偏移量即为 0x05b51e（0x05b508 + 0x0b x 2），该偏移量的值 0x0005 也刚好和 <code>.dynsym</code> 表中的值对应，当然 <code>clock_gettime</code> 符号对应的偏移量 0x05b58e 的值 0x0010 同样也是如此。</p>
<p>下面关键的 <code>.gnu.version_r</code> 表示二进制程序实际依赖的库文件版本，从输出中也能看到 <code>.gnu.version_r</code> 表是按照不同的库文件进行分段显示的，每个条目占用 0x10 也就是 16 个字节，该表是从 <code>0x05d8d8</code> 偏移量开始，我们看看 <code>GLIBC_2.17</code> 也就是 <code>0x05d9b8</code> 处的十六进制数据：</p>
<pre class="brush: plain; title: ; notranslate">
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0005D9B0  B0 70 03 00 10 00 00 00 97 91 96 06 00 00 10 00  °p......—‘–.....
0005D9C0  BA 70 03 00 10 00 00 00 14 69 69 0D 00 00 0E 00  ºp.......ii.....
0005D9D0  C5 70 03 00 10 00 00 00 74 19 69 09 00 00 0C 00  Åp......t.i.....
0005D9E0  CF 70 03 00 10 00 00 00 13 69 69 0D 00 00 09 00  Ïp.......ii.....
0005D9F0  6A 70 03 00 10 00 00 00 17 69 69 0D 00 00 06 00  jp.......ii.....
0005DA00  DB 70 03 00 10 00 00 00 94 91 96 06 00 00 05 00  Ûp......”‘–.....
0005DA10  E5 70 03 00 10 00 00 00 72 19 69 09 00 00 04 00  åp......r.i.....
0005DA20  9A 70 03 00 10 00 00 00 75 1A 69 09 00 00 03 00  šp......u.i.....
0005DA30  8E 70 03 00 00 00 00 00 01 00 01 00 D8 03 00 00  Žp..........Ø...
</pre>
<p><code>.gnu.version_r</code> 表中每个条目是 16 个字节的 <code>Elfxx_Vernaux</code> 结构体，其声明如下（<code>Elfxx_Half</code> 占用 2 个字节，<code>Elfxx_Word</code> 占用 4 个字节）：</p>
<pre class="brush: cpp; title: ; notranslate">
typedef struct {
	Elfxx_Word    vna_hash;
	Elfxx_Half    vna_flags;
	Elfxx_Half    vna_other;
	Elfxx_Word    vna_name;
	Elfxx_Word    vna_next;
} Elfxx_Vernaux;
</pre>
<p><code>vna_hash</code> 为 4 个字节的库名称（也就是上面的 <code>GLIBC_2.17</code> 字符串）的 hash 值，<code>vna_other</code> 为对应的 <code>.gnu.version</code> 表中符号的版本值，<code>vna_name</code> 指向库名称字符串的偏移量（也可以在 ELF 头中找到），<code>vna_next</code> 为下一个条目的位置（一般固定为 <code>0x00000010</code>）。</p>
<p>由上面的输出我们可以看到 <code>GLIBC_2.17</code> 对应的 <code>0x05d9b8</code> 处的开始的 4 个字节 <code>vna_hash</code> hash 值为 <code>0x06969197</code>，而 <code>vna_other</code> 的值 <code>0x0010</code>（输出里的 <code>Version: 16</code>）也与 <code>.gnu.version</code> 中 <code>clock_gettime</code> 符号的值一致。同样 <code>GLIBC_2.14</code> 也与 <code>memcpy</code> 符号的值相符。</p>
<h3 id="mod-elf-symbol">修改 ELF 符号表</h3>
<p>由于 Linux 系统中的 LD 库（也就是 <code>/lib64/ld-linux-x86-64.so.2</code> 库）加载 ELF 时检查 <code>.gnu.version_r</code> 表中的符号，我们可以使用任何一款十六进制编辑器来修改 <code>.gnu.version_r</code> 表中的符号值来强制使用老版本的函数实现。</p>
<p>首先我们发现 <code>.gnu.version_r</code> 的 <code>libc.so.6</code> 段下面有 10 个条目，最后一个则是我们需要的 <code>GLIBC_2.2.5</code> 版本的符号（从上面的十六进制输出中我们可以看到该符号的偏移量为 <code>0x05da28</code>，<code>vna_hash</code> 值为 <code>0x09691A75</code>，<code>vna_other</code> 版本值为 <code>0x0003</code>，<code>vna_name</code> 字符串名称指向 <code>0003708E</code> 地址），因为这样我们才可以在不修改 ELF 文件大小的前提下直接将 <code>libc.so.6</code> 段下的其它高版本条目指向老版本条目的值。</p>
<p>例如 <code>GLIBC_2.17</code> 对应的 <code>0x05d9b8</code> 偏移量，我们可以直接将 <code>vna_hash</code> 值改为 <code>GLIBC_2.2.5</code> 的 <code>0x09691A75</code> 值，将 <code>vna_name</code> 改为 <code>0003708E</code> 值，为了保持和 <code>.gnu.version</code> 表中的版本值一致，这里我们就不修改 <code>vna_other</code> 值了。</p>
<p>对于 <code>GLIBC_2.14</code> 偏移量我们也修改成同样的值，修改保存之后的 ELF 文件再使用 <code>readelf</code> 命令检查就能看到变化了（只列出了修改的 <code>.gnu.version-r</code> 表）：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# readelf -sV tester
......
Version needs section '.gnu.version_r' contains 6 entries:
 Addr: 0x000000000045d8d8  Offset: 0x05e8d8  Link: 2 (.dynstr)
  000000: Version: 1  File: ld-linux-x86-64.so.2  Cnt: 1
  0x0010:   Name: GLIBC_2.3  Flags: none  Version: 17
  0x0020: Version: 1  File: libgcc_s.so.1  Cnt: 3
  0x0030:   Name: GCC_3.0  Flags: none  Version: 13
  0x0040:   Name: GCC_3.3  Flags: none  Version: 11
  0x0050:   Name: GCC_4.2.0  Flags: none  Version: 10
  0x0060: Version: 1  File: libm.so.6  Cnt: 1
  0x0070:   Name: GLIBC_2.2.5  Flags: none  Version: 8
  0x0080: Version: 1  File: libpthread.so.0  Cnt: 2
  0x0090:   Name: GLIBC_2.3.2  Flags: none  Version: 15
  0x00a0:   Name: GLIBC_2.2.5  Flags: none  Version: 7
  0x00b0: Version: 1  File: libc.so.6  Cnt: 10
  0x00c0:   Name: GLIBC_2.8  Flags: none  Version: 19
  0x00d0:   Name: GLIBC_2.9  Flags: none  Version: 18
  0x00e0:   Name: GLIBC_2.2.5  Flags: none  Version: 16
  0x00f0:   Name: GLIBC_2.4  Flags: none  Version: 14
  0x0100:   Name: GLIBC_2.3.4  Flags: none  Version: 12
  0x0110:   Name: GLIBC_2.3  Flags: none  Version: 9
  0x0120:   Name: GLIBC_2.7  Flags: none  Version: 6
  0x0130:   Name: GLIBC_2.2.5  Flags: none  Version: 5
  0x0140:   Name: GLIBC_2.3.2  Flags: none  Version: 4
  0x0150:   Name: GLIBC_2.2.5  Flags: none  Version: 3
  0x0160: Version: 1  File: libdl.so.2  Cnt: 1
  0x0170:   Name: GLIBC_2.2.5  Flags: none  Version: 2
</pre>
<h3 id="patchelf-mod-elf">patchelf 修改 ELF 文件</h3>
<p>一般的程序如果只使用了高版本 <code>memcpy</code> 的话，一般这样修改之后程序就可以运行了。但不巧我使用的第三方程序还使用了高版本 glibc 中的 <code>clock_gettime</code>，只是这样修改的话由于 CentOS 6 的 <code>libc</code> 2.12 库并没有提供 <code>clock_gettime</code>，运行时还是会报错。</p>
<p>这个时候我们就需要请出大杀器 <a href="https://github.com/NixOS/patchelf" target="_blank">PatchELF</a> 了，这个小工具由 NixOS 团队开发，可以直接增加、删除、替换 ELF 文件依赖的库文件，使用起来也非常简单。</p>
<p>检出 PatchELF 的源代码，按照 GitHub 仓库上介绍的步骤编译安装就可以使用了（一般发行版自带的 patchelf 工具版本较老不支持一些新的功能）。</p>
<p>虽然 CentOS 6 的 <code>libc</code> 库没有提供 <code>clock_gettime</code> 实现，但好在 glibc 自带的 <code>librt</code> 库里还是提供了的，因此我们可以使用 patchelf 工具修改原版的程序文件，让程序优先加载 <code>librt</code> 库，这样程序就能正确加载 <code>clock_gettime</code> 符号了：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# patchelf --add-needed librt.so.1 tester
</pre>
<p>然后按照上面介绍的方法用十六进制编辑器修改新生成的 ELF 文件的 <code>.gnu.version_r</code> 表（因为 patchelf 运行之后新 ELF 文件的符号表就和之前的不一样了），将 <code>GLIBC_2.17</code> 和 <code>GLIBC_2.14</code> 统一改为 <code>GLIBC_2.2.5</code> 符号，保存 ELF 文件之后就可以看到效果了：</p>
<pre class="brush: bash; title: ; notranslate">
[root@centos6-dev ~]# ldd tester
        linux-vdso.so.1 =&gt;  (0x00007fffc17ee000)
        librt.so.1 =&gt; /lib64/librt.so.1 (0x00007f7f84dca000)
        libpthread.so.0 =&gt; /lib64/libpthread.so.0 (0x00007f7f84bad000)
        libOpenCL.so.1 =&gt; /usr/lib64/libOpenCL.so.1 (0x00007f7f8498f000)
        libdl.so.2 =&gt; /lib64/libdl.so.2 (0x00007f7f8478b000)
        libm.so.6 =&gt; /lib64/libm.so.6 (0x00007f7f84507000)
        libgcc_s.so.1 =&gt; /lib64/libgcc_s.so.1 (0x00007f7f842f1000)
        libc.so.6 =&gt; /lib64/libc.so.6 (0x00007f7f83f5d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f7f84fd2000)
</pre>
<p>从 <code>ldd</code> 命令的输出中可以看到修改后的程序会加载 <code>librt</code> 库，而且也没有 glibc 版本的报错了，经过测试程序运行起来也没有问题了。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/mod-elf-glibc/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Linux C++程序使用Intel OpenCL的问题</title>
		<link>https://zohead.com/archives/cplus-intel-opencl/</link>
		<comments>https://zohead.com/archives/cplus-intel-opencl/#comments</comments>
		<pubDate>Sat, 26 May 2018 15:25:46 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[OpenCL]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[Intel]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1531</guid>
		<description><![CDATA[Intel OpenCL 问题 最近想在 Linux 环境下使用 Intel 核显测试我们的某个程序，该程序是用 C++ 写的，主要调用 OpenCL 库使用 GPU 进行运算。之前我用 AMD 和 NVIDIA 的显卡都测试过没有问题，还以为会比较顺利，结果在程序刚开始调用 clGetPlatformIDs 检测 OpenCL platform 的时候就直接报错退出了。 我的测试环境使用的是 Intel i3-6100 CPU，该处理器自带 Intel HD Graphics 530 核显，操作系统则是 CentOS 6.9 64 位（比较老，为了和使用环境一致），安装的也是 Intel 官 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="intel-opencl-bug">Intel OpenCL 问题</h2>
<p>最近想在 Linux 环境下使用 Intel 核显测试我们的某个程序，该程序是用 C++ 写的，主要调用 OpenCL 库使用 GPU 进行运算。之前我用 AMD 和 NVIDIA 的显卡都测试过没有问题，还以为会比较顺利，结果在程序刚开始调用 <code>clGetPlatformIDs</code> 检测 OpenCL platform 的时候就直接报错退出了。</p>
<p>我的测试环境使用的是 Intel i3-6100 CPU，该处理器自带 Intel HD Graphics 530 核显，操作系统则是 CentOS 6.9 64 位（比较老，为了和使用环境一致），安装的也是 Intel 官方网站上提供的 Intel OpenCL 2.0 Driver（SRB 5.0 版本），具体可以参考 Intel 官网的 <a href="https://software.intel.com/en-us/articles/opencl-drivers" target="_blank">OpenCL™ Drivers and Runtimes for Intel® Architecture</a> 介绍。</p>
<p>值得一提的是 Intel OpenCL Driver 安装完成之后，运行 <code>clinfo</code> 命令是可以正常识别到核显的，而且我使用另外一个通过 OpenCL 进行计算的 C 程序来测试又是完全没问题的。</p>
<h2 id="debug-opencl">调试分析</h2>
<p>初步考虑可能 Intel OpenCL 在此系统和 C++ 程序存在兼容性问题，为此我先写了一个最简单的 C++ 程序来检测 OpenCL platform：</p>
<pre class="brush: cpp; highlight: [9]; title: cl.cpp; notranslate">
#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;CL/cl.h&gt;

void init_cl()
{
	cl_uint plats = 0;
	std::cout &lt;&lt; &quot;Begin get OpenCL platform.&quot; &lt;&lt; std::endl;
	clGetPlatformIDs(0, NULL, &amp;plats);
	std::cout &lt;&lt; &quot;OpenCL platforms: &quot; &lt;&lt; plats &lt;&lt; std::endl;
}

int main(int argc, char **argv)
{
	std::string str = &quot;cl&quot;;
	init_cl();
	return 0;
}
</pre>
<p>使用 <code>g++</code> 编译该测试程序，编译时启用优化：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# g++ -O2 -o cl cl.cpp -lOpenCL -lstdc++
</pre>
<p>测试程序使用 AMD 和 NVIDIA 显卡的 OpenCL 库都可以正常检测到 platform，使用 Intel 核显则会遇到下面的错误：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost tmp]# ./cl
Begin get OpenCL platform.
*** glibc detected *** ./cl: free(): invalid pointer: 0x0000003de60f32c0 ***
======= Backtrace: =========
/lib64/libc.so.6[0x328cc75716]
/usr/lib64/libstdc++.so.6(_ZNSs6assignERKSs+0x85)[0x3de5e9d565]
/opt/intel/opencl/libigdmcl.so(+0x2040ba)[0x7f75360a00ba]
/opt/intel/opencl/libigdmcl.so(+0x1e6cc2)[0x7f7536082cc2]
/lib64/ld-linux-x86-64.so.2[0x328c40e4ef]
/lib64/ld-linux-x86-64.so.2[0x328c412bf2]
/lib64/ld-linux-x86-64.so.2[0x328c40e106]
/lib64/ld-linux-x86-64.so.2[0x328c41245a]
/lib64/libdl.so.2[0x328c800f66]
/lib64/ld-linux-x86-64.so.2[0x328c40e106]
/lib64/libdl.so.2[0x328c80129c]
/lib64/libdl.so.2(dlopen+0x31)[0x328c800ee1]
/opt/intel/opencl/libigdrcl.so(+0x124bd3)[0x7f7538082bd3]
/opt/intel/opencl/libigdrcl.so(+0xf4890)[0x7f7538052890]
/opt/intel/opencl/libigdrcl.so(+0xf4e26)[0x7f7538052e26]
/opt/intel/opencl/libigdrcl.so(+0xe8982)[0x7f7538046982]
/opt/intel/opencl/libigdrcl.so(+0xd0f3c)[0x7f753802ef3c]
/opt/intel/opencl/libigdrcl.so(clIcdGetPlatformIDsKHR+0x2b)[0x7f753801a0ab]
/opt/intel/opencl/libIntelOpenCL.so(+0x234e4)[0x7f75388c34e4]
/opt/intel/opencl/libIntelOpenCL.so(+0x3f20)[0x7f75388a3f20]
/usr/lib64/libOpenCL.so.1(+0x298b)[0x7f7538f0b98b]
/usr/lib64/libOpenCL.so.1(+0x4907)[0x7f7538f0d907]
/lib64/libpthread.so.0(pthread_once+0x53)[0x7f7538adfe03]
/usr/lib64/libOpenCL.so.1(clGetPlatformIDs+0x11)[0x7f7538f0bf21]
./cl[0x400c6e]
./cl[0x400d31]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x328cc1ec9d]
./cl[0x400b09]
======= Memory map: ========
00400000-00402000 r-xp 00000000 00:11 10291                              /tmp/cl
00601000-00602000 rw-p 00001000 00:11 10291                              /tmp/cl
0179d000-017be000 rw-p 00000000 00:00 0                                  [heap]
328c400000-328c420000 r-xp 00000000 fd:00 7124                           /lib64/ld-2.12.so
328c61f000-328c620000 r--p 0001f000 fd:00 7124                           /lib64/ld-2.12.so
328c620000-328c621000 rw-p 00020000 fd:00 7124                           /lib64/ld-2.12.so
328c621000-328c622000 rw-p 00000000 00:00 0
328c800000-328c802000 r-xp 00000000 fd:00 7116                           /lib64/libdl-2.12.so
328c802000-328ca02000 ---p 00002000 fd:00 7116                           /lib64/libdl-2.12.so
328ca02000-328ca03000 r--p 00002000 fd:00 7116                           /lib64/libdl-2.12.so
328ca03000-328ca04000 rw-p 00003000 fd:00 7116                           /lib64/libdl-2.12.so
328cc00000-328cd87000 r-xp 00000000 fd:00 6933                           /lib64/libc-2.12.so
328cd87000-328cf87000 ---p 00187000 fd:00 6933                           /lib64/libc-2.12.so
328cf87000-328cf8b000 r--p 00187000 fd:00 6933                           /lib64/libc-2.12.so
328cf8b000-328cf8c000 rw-p 0018b000 fd:00 6933                           /lib64/libc-2.12.so
328cf8c000-328cf91000 rw-p 00000000 00:00 0
328d400000-328d483000 r-xp 00000000 fd:00 6818                           /lib64/libm-2.12.so
328d483000-328d682000 ---p 00083000 fd:00 6818                           /lib64/libm-2.12.so
328d682000-328d683000 r--p 00082000 fd:00 6818                           /lib64/libm-2.12.so
328d683000-328d684000 rw-p 00083000 fd:00 6818                           /lib64/libm-2.12.so
328d800000-328d807000 r-xp 00000000 fd:00 7077                           /lib64/librt-2.12.so
328d807000-328da06000 ---p 00007000 fd:00 7077                           /lib64/librt-2.12.so
328da06000-328da07000 r--p 00006000 fd:00 7077                           /lib64/librt-2.12.so
328da07000-328da08000 rw-p 00007000 fd:00 7077                           /lib64/librt-2.12.so
3de5e00000-3de5ee8000 r-xp 00000000 00:11 9623                           /usr/lib64/libstdc++.so.6.0.13
3de5ee8000-3de60e8000 ---p 000e8000 00:11 9623                           /usr/lib64/libstdc++.so.6.0.13
3de60e8000-3de60ef000 r--p 000e8000 00:11 9623                           /usr/lib64/libstdc++.so.6.0.13
3de60ef000-3de60f1000 rw-p 000ef000 00:11 9623                           /usr/lib64/libstdc++.so.6.0.13
3de60f1000-3de6106000 rw-p 00000000 00:00 0
7f7535c86000-7f7535c9b000 r-xp 00000000 fd:00 7143                       /lib64/libz.so.1.2.3
7f7535c9b000-7f7535e9a000 ---p 00015000 fd:00 7143                       /lib64/libz.so.1.2.3
7f7535e9a000-7f7535e9b000 r--p 00014000 fd:00 7143                       /lib64/libz.so.1.2.3
7f7535e9b000-7f7535e9c000 rw-p 00015000 fd:00 7143                       /lib64/libz.so.1.2.3
7f7535e9c000-7f7536b3a000 r-xp 00000000 07:05 10                         /opt/intel/opencl/libigdmcl.so
7f7536b3a000-7f7536d3a000 ---p 00c9e000 07:05 10                         /opt/intel/opencl/libigdmcl.so
7f7536d3a000-7f7536d91000 r--p 00c9e000 07:05 10                         /opt/intel/opencl/libigdmcl.so
7f7536d91000-7f7536d93000 rw-p 00cf5000 07:05 10                         /opt/intel/opencl/libigdmcl.so
7f7536d93000-7f7536d9f000 rw-p 00000000 00:00 0
7f7536d9f000-7f7536da7000 r-xp 00000000 fd:00 13144                      /usr/lib64/libpciaccess.so.0.11.1
7f7536da7000-7f7536fa7000 ---p 00008000 fd:00 13144                      /usr/lib64/libpciaccess.so.0.11.1
7f7536fa7000-7f7536fa8000 rw-p 00008000 fd:00 13144                      /usr/lib64/libpciaccess.so.0.11.1
7f7536fa8000-7f7537351000 r-xp 00000000 07:05 12                         /opt/intel/opencl/libmd.so
7f7537351000-7f7537550000 ---p 003a9000 07:05 12                         /opt/intel/opencl/libmd.so
7f7537550000-7f7537554000 r--p 003a8000 07:05 12                         /opt/intel/opencl/libmd.so
7f7537554000-7f7537556000 rw-p 003ac000 07:05 12                         /opt/intel/opencl/libmd.so
7f7537556000-7f753755d000 rw-p 00000000 00:00 0
7f753755d000-7f753755e000 ---p 00000000 00:00 0
7f753755e000-7f7537f5e000 rw-p 00000000 00:00 0
7f7537f5e000-7f7538152000 r-xp 00000000 07:05 11                         /opt/intel/opencl/libigdrcl.so
7f7538152000-7f7538351000 ---p 001f4000 07:05 11                         /opt/intel/opencl/libigdrcl.so
7f7538351000-7f7538357000 r--p 001f3000 07:05 11                         /opt/intel/opencl/libigdrcl.so
7f7538357000-7f7538894000 rw-p 001f9000 07:05 11                         /opt/intel/opencl/libigdrcl.so
7f7538894000-7f75388a0000 rw-p 00000000 00:00 0
7f75388a0000-7f75388cb000 r-xp 00000000 07:05 2                          /opt/intel/opencl/libIntelOpenCL.so
7f75388cb000-7f7538acb000 ---p 0002b000 07:05 2                          /opt/intel/opencl/libIntelOpenCL.so
7f7538acb000-7f7538acc000 r--p 0002b000 07:05 2                          /opt/intel/opencl/libIntelOpenCL.so
7f7538acc000-7f7538acd000 rw-p 0002c000 07:05 2                          /opt/intel/opencl/libIntelOpenCL.so
7f7538acd000-7f7538ad3000 rw-p 00000000 00:00 0
7f7538ad3000-7f7538aea000 r-xp 00000000 fd:00 7139                       /lib64/libpthread-2.12.so
7f7538aea000-7f7538cea000 ---p 00017000 fd:00 7139                       /lib64/libpthread-2.12.so
7f7538cea000-7f7538ceb000 r--p 00017000 fd:00 7139                       /lib64/libpthread-2.12.so
7f7538ceb000-7f7538cec000 rw-p 00018000 fd:00 7139                       /lib64/libpthread-2.12.so
7f7538cec000-7f7538cf2000 rw-p 00000000 00:00 0
7f7538cf2000-7f7538d08000 r-xp 00000000 fd:00 7140                       /lib64/libgcc_s-4.4.7-20120601.so.1
7f7538d08000-7f7538f07000 ---p 00016000 fd:00 7140                       /lib64/libgcc_s-4.4.7-20120601.so.1
7f7538f07000-7f7538f08000 rw-p 00015000 fd:00 7140                       /lib64/libgcc_s-4.4.7-20120601.so.1
7f7538f08000-7f7538f09000 rw-p 00000000 00:00 0
7f7538f09000-7f7538f10000 r-xp 00000000 fd:00 12875                      /usr/lib64/libOpenCL.so.1
7f7538f10000-7f753910f000 ---p 00007000 fd:00 12875                      /usr/lib64/libOpenCL.so.1
7f753910f000-7f7539110000 rw-p 00006000 fd:00 12875                      /usr/lib64/libOpenCL.so.1
7f7539110000-7f7539111000 rw-p 00000000 00:00 0
7ffe17048000-7ffe17069000 rw-p 00000000 00:00 0                          [stack]
7ffe170de000-7ffe170e1000 r--p 00000000 00:00 0                          [vvar]
7ffe170e1000-7ffe170e3000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted
</pre>
<p>从错误输出来看是 <code>clGetPlatformIDs</code> 调用过程中 Intel OpenCL Driver 内部就出现问题了，释放了无效的指针导致程序异常退出。</p>
<p>几番修改测试之后我发现两个现象：</p>
<ol>
<li>如果 <code>g++</code> 编译时不使用优化选项（<code>-O2</code> 参数去掉或者改为 <code>-O0</code>），则程序可以正常运行；</li>
<li>如果去掉第 15 行的 <code>std::string</code> 变量定义代码，即使保持优化选项开启并链接 libstdc++ 库，程序也可以正常运行。</li>
</ol>
<p>看起来只要 libstdc++ 库确实被使用了，优化过的 C++ 程序调用 Intel OpenCL 就会出现异常，而由于我们的程序需要开启编译器优化，因此要找到其它解决办法。</p>
<h2 id="fix-intel-opencl">解决方法</h2>
<h3 id="opt-off">局部关闭编译器优化</h3>
<p>我们可以在整个程序开启编译器优化的情况下，只关闭对调用 OpenCL 库那部分代码的优化。下面是两种局部关闭编译器优化的方法，通过这两种方法修改的测试程序编译之后都可以正常运行。</p>
<h4 id="pragma-opt">pragma 指令</h4>
<p>把测试程序的代码改成这样：</p>
<pre class="brush: cpp; title: ; notranslate">
#pragma GCC push_options
#pragma GCC optimize (&quot;O0&quot;)
void init_cl()
{
	...
}
#pragma GCC pop_options
</pre>
<p>使用 pragma 指令指定 <code>#pragma GCC push_options</code> 和 <code>#pragma GCC pop_options</code> 包起来的这部分代码的编译选项，好处是可以包含多个函数。</p>
<h4 id="attribute-opt">__attribute__ 属性</h4>
<p>我们也可以直接通过 GNU 编译器的 <code>__attribute__</code> 编译器属性指定某个函数的优化选项：</p>
<pre class="brush: cpp; title: ; notranslate">
void __attribute__((optimize(&quot;O0&quot;))) init_cl()
</pre>
<h3 id="update-libstdc++">升级 libstdc++ 库</h3>
<p>不过有点悲剧的是我们的 C++ 程序通过上面两种方法关闭 OpenCL 调用部分代码的编译器优化之后，运行之后仍然异常退出。另外我把程序挪到 CentOS 7.1 64 位系统上运行则没有任何问题，最终考虑要通过升级 libstdc++ 库（CentOS 6.9 上的是 4.4.7-18 版本）来解决此问题。</p>
<p>本来打算通过手工编译新版本 GCC 来生成新版本的 libstdc++ 库，不过还好搜索之后发现网上已经有现成的 CentOS 6 上的新版本 libstdc++ 了，具体可以参考 SAP 公司提供的 <a href="https://help.sap.com/viewer/d1469c16b1ed48bcb589c3a3f828cbc4/3.2/en-US/4236c03b3a1b42bdb7dd954f416e557d.html" target="_blank">compat-sap-c++ for RedHat 6.7+</a>。</p>
<p>按照 SAP 网站上的说明安装 <code>compat-sap-c++</code> 包，我们也可以直接替换系统链接：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# curl -O ftp://ftp.pbone.net/mirror/ftp.scientificlinux.org/linux/scientific/6.7/x86_64/updates/fastbugs/compat-sap-c++-4.8.2-16.el6.x86_64.rpm
[root@localhost ~]# rpm -ivh compat-sap-c++-4.8.2-16.el6.x86_64.rpm
[root@localhost ~]# ln -sfn /opt/rh/SAP/lib64/compat-sap-c++.so /usr/lib64/libstdc++.so.6
</pre>
<p>替换 libstdc++ 为 4.8.2-16 版本之后再运行 OpenCL 测试程序就会发现即使开启编译器优化也没有问题了：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost tmp]# ./cl
Begin get OpenCL platform.
OpenCL platforms: 1
</pre>
<p>看来 Intel 官网上介绍的 Intel OpenCL Driver 只验证了 CentOS 7.2、7.3 及 Ubuntu 16.04 系统下的兼容性还算靠谱的，如果要在老一点的 Linux 系统上使用 Intel OpenCL 还是需要尽量避免碰坑，最后祝大家玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/cplus-intel-opencl/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在Docker容器中使用FUSE文件系统</title>
		<link>https://zohead.com/archives/docker-fuse/</link>
		<comments>https://zohead.com/archives/docker-fuse/#comments</comments>
		<pubDate>Tue, 19 Dec 2017 16:32:18 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[User-mode Linux]]></category>
		<category><![CDATA[FUSE]]></category>
		<category><![CDATA[httpfs2]]></category>
		<category><![CDATA[NFS]]></category>
		<category><![CDATA[Slirp]]></category>
		<category><![CDATA[UML]]></category>
		<category><![CDATA[VDE]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[文件系统]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1518</guid>
		<description><![CDATA[容器使用 FUSE 的问题 我们一般使用的 Docker 容器都是非特权容器，也就是说容器内的 root 用户并不拥有真正的 root 权限，这就导致很多属于系统管理员的操作都被禁用了。 最近有个在 IBM Bluemix 容器内部挂载 FUSE 文件系统的需求，例如我使用 davfs2 挂载 WebDAV 服务器不出意外地会报错： mount.davfs 命令报错表示无法打开 fuse 设备，而 fuse 设备实际上是存在的（说明 fuse 模块也已经加载了）： 从容器内部可以查看到 cgroup 实际允许访问的设备，并没有包含 fuse 设备： 手工允许 fuse 设备自然也是不可行的：  [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="docker-fuse-problem">容器使用 FUSE 的问题</h2>
<p>我们一般使用的 Docker 容器都是非特权容器，也就是说容器内的 root 用户并不拥有真正的 root 权限，这就导致很多属于系统管理员的操作都被禁用了。</p>
<p>最近有个在 IBM Bluemix 容器内部挂载 FUSE 文件系统的需求，例如我使用 davfs2 挂载 WebDAV 服务器不出意外地会报错：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# mount.davfs https://dav.jianguoyun.com/dav/ /mnt/
Please enter the username to authenticate with server
https://dav.jianguoyun.com/dav/ or hit enter for none.
  Username: xxx@xx.com
Please enter the password to authenticate user fcoe@qq.com with server
https://dav.jianguoyun.com/dav/ or hit enter for none.
  Password: 
mount.davfs: can't open fuse device
mount.davfs: trying coda kernel file system
mount.davfs: no free coda device to mount
</pre>
<p>mount.davfs 命令报错表示无法打开 fuse 设备，而 fuse 设备实际上是存在的（说明 fuse 模块也已经加载了）：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# cat /sys/devices/virtual/misc/fuse/dev
10:229
</pre>
<p>从容器内部可以查看到 cgroup 实际允许访问的设备，并没有包含 fuse 设备：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# cat /sys/fs/cgroup/devices/devices.list
c 1:5 rwm
c 1:3 rwm
c 1:9 rwm
c 1:8 rwm
c 5:0 rwm
c 5:1 rwm
c *:* m
b *:* m
c 1:7 rwm
c 136:* rwm
c 5:2 rwm
c 10:200 rwm
</pre>
<p>手工允许 fuse 设备自然也是不可行的：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# echo &quot;c 10:229 rwm&quot; &gt; /sys/fs/cgroup/devices/devices.allow
-bash: /sys/fs/cgroup/devices/devices.allow: Permission denied
</pre>
<p>另外由于 Bluemix 提供的是非特权容器，即使允许访问 /dev/fuse 设备也会因为没有 mount 权限而挂载失败。</p>
<h2 id="uml-modify">UML 系统修改</h2>
<p>虽然 Docker 容器内部不能直接挂载使用 FUSE 文件系统，但我想到如果用 User-mode Linux（以下简称 UML） 来实现在应用层再运行一个 Linux kernel，就可以在 UML guest 系统中挂载 FUSE 文件系统了，而且 UML 系统中也可以通过 hostfs 直接访问容器本身的文件系统。</p>
<p>有关 UML 的介绍和编译使用可以参考我之前写的 <a href="https://zohead.com/archives/openvz-uml-bbr/">小内存OpenVZ VPS使用UML开启BBR</a> 文章。</p>
<p>首先我们需要修改 UML kernel 配置：</p>
<ul>
<li>增加对 FUSE 的支持，这个是必须的了，否则无法在 UML guest 系统中使用 FUSE 文件系统；</li>
<li>增加了对 xfs、btrfs、ext4、squashfs、iso9660 等常见文件系统的支持（方便在 UML 系统中挂载各种文件系统镜像）；</li>
<li>另外为了支持将 UML guest 的文件系统导出，启用了 sunrpc 和 NFS 服务器支持；</li>
<li>UML 网络配置中增加了对 Slirp 和 VDE 网卡的支持。</li>
</ul>
<p>UML kernel 目前支持常见的几种网络模式：</p>
<ul>
<li>TUN/TAP <br />
最简单和常用的模式，不过 host kernel 需要支持 tun 或者 tap 设备，这个在 Docker 容器中一般都不可用的；</li>
<li>SLIP <br />
SLIP 串行线路 IP 支持，现在一般很少用到了，同样 host kernel 需要支持 slip 设备；</li>
<li>Slirp <br />
通过用户层的 slirp 程序实现 SLIP 连接，好处是不依赖任何内核层的设备，而且 slirp 可以支持非 root 用户使用，不过 Slirp 只支持模拟 IP 协议的数据包，详细可以参考 <a href="http://slirp.sourceforge.net/" target="_blank">Slirp</a> 开源项目的官方网站。</li>
<li>VDE <br />
VDE（Virtual Distributed Ethernet）可以在不同的计算机间实现软件定义的以太网络，同样支持在用户层以非 root 用户身份来运行，目前 Linux 上的 QEMU 和 KVM 虚拟机都支持 VDE 虚拟网络。</li>
</ul>
<p>鉴于我们需要在 Docker 容器中运行 UML 系统，目前只能使用 Slirp 和 VDE 模式的网卡，另外单独的 slirp 程序使用起来相对 VDE 也更简单（支持 VDE 的 UML kernel 可以直接调用 slirp 程序，不像 VDE 还需要预先使用 <code>vde_switch</code> 等命令配置软件交换机），因此这里的 UML 系统就使用 Slirp 网卡了。</p>
<p>当然原来基于 busybox 的 UML 系统用户层也做了些修改：</p>
<ul>
<li>增加 libfuse 支持挂载 FUSE 文件系统；</li>
<li>增加 rpcbind、nfs-common、nfs-kernel-server 等软件包，支持在 UML 系统中运行 NFS 服务器，导出 UML guest 的文件系统；</li>
<li>增加 dropbear，支持 SSH 和 SFTP 服务器和客户端了；</li>
<li>增加 httpfs2 FUSE 文件系统支持，来自 GitHub 上的 <a href="https://github.com/Tomas-M/httpfs2-enhanced" target="_blank">httpfs2-enhanced</a> 项目，方便挂载访问 HTTP 和 HTTPS 远程文件；</li>
<li>增加 archivemount FUSE 文件系统支持，方便直接挂载 zip、rar、tar.gz 等各种格式的压缩包以支持直接访问压缩包中的文件；</li>
<li>增加 <a href="http://curlftpfs.sourceforge.net" target="_blank">CurlFtpFs</a> FUSE 文件系统支持，支持挂载 FTP 远程文件；</li>
<li>增加 <a href="http://savannah.nongnu.org/projects/davfs2" target="_blank">davfs2</a> FUSE 文件系统支持，支持挂载远程 WebDAV 目录；</li>
<li>增加 <a href="https://github.com/libfuse/sshfs" target="_blank">sshfs</a> FUSE 文件系统支持，支持通过 SFTP 方式挂载远程主机目录。</li>
</ul>
<blockquote>
<p><strong>提示</strong></p>
<ul>
<li>httpfs2-enhanced 最好使用支持 SSL 和多线程的版本，可以加快响应速度并能挂载 HTTPS 的远程文件；</li>
<li>我在测试中发现 httpfs2-enhanced 工具在国内的网络环境下挂载某些 HTTP 远程文件存在一些问题，因此做了简单的修改，并 fork 到我自己的 GitHub 仓库里了。有需要的朋友可以参考我修改过的 <a href="https://github.com/zohead/httpfs2-enhanced" target="_blank">httpfs2-enhanced</a>，检出其中的 <a href="https://github.com/zohead/httpfs2-enhanced/tree/http-fix" target="_blank">http-fix</a> 分支即可，我也已经针对原项目提交了 Pull request。</li>
</ul>
</blockquote>
<p>为了方便使用，我给原来的 <code>uml-linux.sh</code> 执行脚本增加了新的 <code>uml.conf</code> 配置文件：</p>
<pre class="brush: bash; title: ; notranslate">
UML_ID=&quot;umlvm&quot;
UML_MEM=&quot;64M&quot;

UML_NETMODE=&quot;slirp&quot;
#HOST_ADDR=&quot;192.168.0.3&quot;
#UML_ADDR=&quot;192.168.0.4&quot;
#UML_DNS=&quot;&quot;

TAP_DEV=&quot;tap0&quot;
ROUTER_DEV=&quot;eth0&quot;
VDE_SWITCH=&quot;&quot;

REV_TCP_PORTS=&quot;&quot;
REV_UDP_PORTS=&quot;&quot;
#FWD_TCP_PORTS=&quot;111 892 2049 32803 662&quot;
#FWD_UDP_PORTS=&quot;111 892 2049 947 32769 662 660&quot;
</pre>
<p>简单说明如下：</p>
<ul>
<li><code>UML_MEM</code> 指定为 UML 系统分配多少内存，默认 64 MB；</li>
<li><code>UML_NETMODE</code> 比较重要，指定 UML 系统的网卡模式，目前支持 <code>tuntap</code>、<code>slirp</code>、<code>vde</code> 这三个选项；</li>
<li>如果是 <code>tuntap</code> 网卡模式，<code>TAP_DEV</code> 指定 host 主机上的 TUN 网卡设备名称，可以使用 <code>HOST_ADDR</code> 配置 host 主机 TUN 网卡的 IP 地址，<code>UML_ADDR</code> 配置 UML guest 主机的 IP 地址（最好和 <code>HOST_ADDR</code> 在同一网段），如果需要端口转发，那么还需要修改 <code>ROUTER_DEV</code> 指定 host 主机用于转发的物理网卡设备名称；</li>
<li>如果是 <code>slirp</code> 网卡模式，那么会直接使用 Slirp 默认固定的专用地址：<code>10.0.2.2</code> 为 host 主机地址，<code>10.0.2.15</code> 为 UML guest 主机的 IP 地址，并自动将 UML guest 系统内的 DNS 服务器地址设置为 <code>10.0.2.3</code> 通过 host 主机进行域名解析；</li>
<li>如果是 <code>vde</code> 网卡模式，那么必须修改 <code>VDE_SWITCH</code> 指定 VDE 软件交换机的路径，VDE 软件交换机需要通过 <code>vde_switch</code> 等命令预先配置，详细使用说明可以参考 <a href="http://wiki.virtualsquare.org/wiki/index.php/VDE_Basic_Networking" target="_blank">Virtualsquare VDE Wiki</a> 页面；</li>
<li><code>FWD_TCP_PORTS</code> 和 <code>FWD_UDP_PORTS</code> 指定进行转发的 TCP 和 UDP 端口列表（多个转发端口以空格隔开），转发端口支持 <code>10080-80</code> 这种形式（表示将 host 主机的 10080 端口转到 UML guest 的 80 端口），上面 <code>uml.conf</code> 中的注释列出来的是 UML 系统中对外的 NFS 服务器所需要的端口（根据实际情况也可以只允许 111、892、2049 这几个端口）。</li>
</ul>
<p>为了能够根据 <code>uml.conf</code> 文件配置 Slirp 的端口转发功能，我还为 slirp 程序增加了一个 <code>slirp.sh</code> wrapper 脚本：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
DIR=&quot;$( cd &quot;$( dirname &quot;$0&quot; )&quot; &amp;&amp; pwd )&quot;

[ -f $DIR/uml.conf ] &amp;&amp; . $DIR/uml.conf

CMD=`which slirp-fullbolt 2&gt;/dev/null || which slirp`

for i in $FWD_TCP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		continue
	fi
	CMD=&quot;$CMD \&quot;redir ${i%-*} ${i##*-}\&quot;&quot;
done
for i in $FWD_UDP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		continue
	fi
	CMD=&quot;$CMD \&quot;redir udp ${i%-*} ${i##*-}\&quot;&quot;
done

eval &quot;exec $CMD&quot;
</pre>
<p>由于 UML kernel 调用 slirp 程序时不支持附加参数，这里才通过 <code>slirp.sh</code> 脚本来实现，功能也非常简单，通过 slirp 程序的 redir 选项配置端口转发。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>为了让 UML guest 系统的 Slirp 网络真正达到接近 host 主机的网络性能，host 主机上编译 slirp 程序时必须打开 <code>FULL_BOLT</code> 开关，并为 slirp 源代码打上 <a href="https://www.mail-archive.com/user-mode-linux-user@lists.sourceforge.net/msg07014.html" target="_blank">real full bolt</a> 的 patch，否则 UML guest 系统内通过 Slirp 访问外网的速度会很慢。</p>
<p>值得庆幸的是 Debian、Ubuntu 系统自带的 slirp 软件包一般都打上了这个 patch，而且提供了 <code>slirp-fullbolt</code> 和 <code>slirp</code> 这两个程序分别对应 <code>FULL_BOLT</code> 开启和关闭的 Slirp，我的 <code>slirp.sh</code> 脚本也对此做了判断，优先使用速度更快的 <code>slirp-fullbolt</code> 程序。</p>
</blockquote>
<p>至于新的启动 UML 系统的脚本 <code>uml-linux.sh</code> 稍微有点长，这里就不贴出来了，和原来相比的改动就是增加对 Slirp 和 VDE 网络的支持。</p>
<p>另外新的 <code>uml-linux.sh</code> 脚本改为默认前台方式启动 UML 系统；如果需要以后台方式启动 UML 系统，则可以用 <code>uml-linux.sh -D</code> 的方式来运行。</p>
<h2 id="uml-mount-fuse">使用 UML 挂载 FUSE 文件系统</h2>
<p>修改之后支持 FUSE 和 NFS 服务器的 UML 系统可以从这里下载（百度网盘备用）：</p>
<p><a href="https://zohead.com/downloads/uml-fuse-nfsd-x64.tar.xz">https://zohead.com/downloads/uml-fuse-nfsd-x64.tar.xz</a> <br />
<a href="https://pan.baidu.com/s/1bp6l7B5" target="_blank">https://pan.baidu.com/s/1bp6l7B5</a></p>
<p>解压缩下载下来的 <code>uml-fuse-nfsd-x64.tar.xz</code> 文件，运行其中的 <code>uml-linux</code> 脚本就可以启动 UML 系统了，此 UML 系统的 root 用户默认密码为 <code>uml</code>。</p>
<p>下面我以挂载 HTTP 远程 iso 文件中的安装包为例，介绍如何在 UML 系统中使用 FUSE。</p>
<p>首先使用 httpfs2 挂载阿里云上的 CentOS 6.9 iso 文件（这里为 httpfs2 指定 <code>-c /dev/null</code> 参数是为了去掉 httpfs2 的网络访问输出日志），挂载成功之后可以查看挂载点中的文件：</p>
<pre class="brush: bash; title: ; notranslate">
~ # httpfs2 -c /dev/null http://mirrors.aliyun.com/centos/6/isos/x86_64/CentOS-6.9-x86_64-minimal.iso /tmp
file name:      CentOS-6.9-x86_64-minimal.iso
host name:      mirrors.aliyun.com
port number:    80
protocol:       http
request path:   /centos/6/isos/x86_64/CentOS-6.9-x86_64-minimal.iso
auth data:      (null)
httpfs2: main: connecting to mirrors.aliyun.com port 80.
No SSL session data.
httpfs2: main: closing socket.
httpfs2: main: connecting to mirrors.aliyun.com port 80.
httpfs2: main: keeping socket open.
file size:      427819008
httpfs2: main: closing socket.
~ # ls -l /tmp
total 0
-r--r--r--    1 root     root     427819008 Mar 29  2017 CentOS-6.9-x86_64-minimal.iso
</pre>
<p>我们可以发现 httpfs2 的挂载点中实际上就只有一个远程文件名，我们可以直接挂载这个 iso 文件：</p>
<pre class="brush: bash; title: ; notranslate">
~ # mount -t iso9660 -o ro,loop /tmp/CentOS-6.9-x86_64-minimal.iso /media
~ # ls /media
CentOS_BuildTag                GPL                            RPM-GPG-KEY-CentOS-6           RPM-GPG-KEY-CentOS-Testing-6   isolinux
EFI                            Packages                       RPM-GPG-KEY-CentOS-Debug-6     TRANS.TBL                      repodata
EULA                           RELEASE-NOTES-en-US.html       RPM-GPG-KEY-CentOS-Security-6  images
</pre>
<p>到这里就可以直接查看远程 iso 文件中的软件包了，你可以很方便地将远程 iso 中的软件包拷贝到 host 主机的文件系统中。</p>
<p>如果你想直接拷贝软件包中的特定文件，也可以通过 archivemount 来实现哦：</p>
<pre class="brush: bash; title: ; notranslate">
~ # archivemount /media/Packages/sed-4.2.1-10.el6.x86_64.rpm /mnt
fuse: missing mountpoint parameter
~ # ls /mnt
bin  usr
</pre>
<p>这样直接复制软件包中的文件就实在太方便了（请忽略 archivemount 时的 fuse 警告，实际不影响使用）。</p>
<p>当然如果你想挂载 FTP 远程文件就可以通过 curlftpfs 命令来实现，也可以使用 mount.davfs 命令挂载 WebDAV 服务器上的文件（例如直接访问坚果云中的文件）。</p>
<h2 id="export-uml-fuse">导出 UML FUSE 文件系统</h2>
<p>有些情况下我们需要将 UML 系统中的 FUSE 挂载点导出给 host 主机或者网络中的其它主机使用，这时可以通过 hostfs、SFTP、NFS 等方式实现，分别简单说明一下。</p>
<h3 id="hostfs-export-fuse">hostfs 导出 FUSE</h3>
<p>这是最简单的方式，由于 UML 直接使用 hostfs 访问 host 主机的文件系统，因此 UML 系统内可以直接将 FUSE 中的文件拷贝到 hostfs 文件系统，只是这种方式 host 主机并不能直接访问 UML guest 主机的 FUSE 文件系统。</p>
<h3 id="sftp-export-fuse">SFTP 导出 FUSE</h3>
<p>这种方式也很简单，由于 UML 系统启动时自动运行了 dropbear 服务，我们可以先修改 <code>uml.conf</code> 配置文件设置 SSH 端口转发，将 host 主机的 2222 端口通过 Slirp 转发到 UML guest 系统内的 22 端口（如果 host 主机本身并没有运行 SSH 服务器，那甚至可以配置为 <code>FWD_TCP_PORTS="22"</code> 直接转发 22 端口）：</p>
<pre class="brush: bash; title: ; notranslate">
FWD_TCP_PORTS=&quot;2222-22&quot;
</pre>
<p>此时 host 主机就可以使用 scp 命令直接从 UML 的 FUSE 文件系统中拷贝文件了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# scp -P 2222 root@192.168.1.52:/mnt/bin/sed /home
</pre>
<blockquote>
<p><strong>注意</strong></p>
<p>上面命令中的 <code>192.168.1.52</code> 应根据实际情况替换为 host 主机上实际访问网络的网卡 IP 地址，不能使用 localhost 或者 127.0.0.1，因为 slirp 默认会自动选择访问网络的网卡，并不会进行本地转发。</p>
</blockquote>
<h3 id="nfs-export-fuse">NFS 导出 FUSE</h3>
<p>首先需要修改 <code>uml.conf</code> 配置文件中的 <code>FWD_TCP_PORTS</code> 和 <code>FWD_UDP_PORTS</code> 值，将默认的 NFS 服务需要的 TCP 和 UDP 端口注释去掉，表示将这些端口转发到 UML 系统内。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>如果你需要在 host 主机上直接 NFS 挂载 UML 系统里的 FUSE 文件系统，由于 sunrpc 的 111 端口无法修改而且需要转发到 UML 系统内，这种情况下 host 主机的 portmap 或者 rpcbind 服务需要保持关闭状态，防止 111 端口被占用。</p>
</blockquote>
<p>使用 <code>uml-linux.sh</code> 脚本启动 UML 系统，启动完成之后通过集成的 FUSE 相关工具挂载需要访问的 FUSE 文件系统。</p>
<p>假设需要导出 UML 系统中的 <code>/mnt</code> FUSE 文件系统，UML 中默认的 NFS 导出目录配置文件 <code>/etc/exports</code> 如下：</p>
<pre class="brush: plain; title: ; notranslate">
/mnt 0.0.0.0/0.0.0.0(async,insecure,no_subtree_check,rw,no_root_squash,fsid=0)
</pre>
<p>上面的配置文件表示将 /mnt 目录通过 NFS 导出，默认允许所有主机访问，你可以根据需要修改导出目录路径和允许访问的主机地址。</p>
<p>接着在 UML 系统中通过集成的服务脚本启动 rpcbind 和 nfs 服务：</p>
<pre class="brush: bash; title: ; notranslate">
~ # /etc/init.d/rpcbind start
Starting up rpcbind...
~ # /etc/init.d/nfs start
Starting nfs service:
fs.nfs.nlm_tcpport = 32803
fs.nfs.nlm_udpport = 32769
Exporting directories for NFS...
Starting NFS daemon: 
rpc.nfsd: address family inet6 not supported by protocol TCP
NFSD: the nfsdcld client tracking upcall will be removed in 3.10. Please transition to using nfsdcltrack.
NFSD: starting 90-second grace period (net 000000006035a880)
Starting NFS mountd:
</pre>
<p>如果一切正常的话，此时就可以在外部通过 NFS 挂载 UML 系统导出的 FUSE 文件系统进行访问了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# mount -t nfs -o nolock,proto=tcp,mountproto=tcp 192.168.1.52:/mnt /media
</pre>
<blockquote>
<p><strong>提示</strong></p>
<p><code>nolock</code> 参数是为了防止如果挂载的主机上没有运行 portmap 或者 rpcbind 服务导致挂载失败的问题（如果直接在 host 主机上挂载，host 的对应服务需要关闭）； <br />
  <code>proto=tcp</code> 和 <code>mountproto=tcp</code> 参数指定 NFS 数据和挂载请求都使用 TCP 协议，防止使用的随机 UDP 端口无法被 slirp 转发的问题。</p>
</blockquote>
<p>这里需要说明的是如果直接在 Docker 容器（UML 的 host 主机）上挂载 NFS，虽然绝大多数 Docker 容器平台都默认支持 NFS 文件系统，但在非特权容器内部由于没有权限 mount 还是会失败的。</p>
<h2 id="summary">总结</h2>
<p>本文介绍的在容器中使用 UML 挂载 FUSE 文件系统并通过 NFS 导出 UML 文件系统的方法是一个比较小众的需求，不过也算达到我的目的了。</p>
<p>对于非特权 Docker 容器来说，虽然还不能直接挂载 UML guest 的文件系统，但初步看起来还是可以通过 <a href="https://github.com/lkl/linux" target="_blank">LKL</a>（Linux Kernel Library）在应用层访问 UML 网络并实现 NFS 挂载的，只是 LKL 库的 NFS 挂载目录并不能直接给 Docker 容器中的普通应用程序使用。</p>
<p>最后祝大家在即将到来的 2018 年能继续玩地开心 ^_^。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/docker-fuse/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>使用Software Collections编译最新Linux kernel</title>
		<link>https://zohead.com/archives/scl-linux-kernel/</link>
		<comments>https://zohead.com/archives/scl-linux-kernel/#comments</comments>
		<pubDate>Thu, 07 Sep 2017 16:08:52 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[binutils]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[gcc]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[Software Collections]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1502</guid>
		<description><![CDATA[关于 Software Collections 使用 Software Collections（SCL）的契机是最近我需要在 CentOS 6 系统上编译最新版本的 Linux kernel，主要用于测试 Btrfs 的数据校验对于 AVX2 指令的支持情况。然而大家都知道 CentOS 6 自带的软件包版本都是非常老的，glibc 维持在 2.12 版本，老的 gcc 4.4 版本则根本就不支持 AVX2 指令，这样编译 Linux kernel 时会自动关闭 async xor 对 AVX2 的支持。 如果要自己动手编译新版本 gcc，这感觉还是有点吃力不讨好，幸好 Red Hat 也注意 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="about-scl">关于 Software Collections</h2>
<p>使用 Software Collections（SCL）的契机是最近我需要在 CentOS 6 系统上编译最新版本的 Linux kernel，主要用于测试 Btrfs 的数据校验对于 AVX2 指令的支持情况。然而大家都知道 CentOS 6 自带的软件包版本都是非常老的，glibc 维持在 2.12 版本，老的 gcc 4.4 版本则根本就不支持 AVX2 指令，这样编译 Linux kernel 时会自动关闭 async xor 对 AVX2 的支持。</p>
<p>如果要自己动手编译新版本 gcc，这感觉还是有点吃力不讨好，幸好 Red Hat 也注意到这个问题了，我们可以通过 Software Collections 实现在一个系统上安装多个版本的开发工具或者运行环境，仓库里高版本的软件包通常都是由 Red Hat 维护的，比较常见的需求例如将 CentOS 6 自带的 Python 升级为 2.7 版本。</p>
<p>大家可以访问 Software Collections 官网了解更多信息：</p>
<p><a href="https://www.softwarecollections.org/en/" target="_blank">https://www.softwarecollections.org/en/</a></p>
<h2 id="install-scl-gcc">安装 gcc 新版本</h2>
<p>首先需要安装 SCL 支持，CentOS 6 通过 yum 安装还是非常简单的：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# yum install centos-release-scl
Dependencies Resolved

================================================================
 Package              Arch    Version            Repository Size
================================================================
Installing:
 centos-release-scl   noarch  10:7-3.el6.centos  extras     12 k

Transaction Summary
================================================================
Install       1 Package(s)

Total download size: 12 k
Installed size: 19 k
Is this ok [y/N]: y
Downloading Packages:
centos-release-scl-7-3.el6.centos.noarch.rpm | 12 kB    00:00     
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : 10:centos-release-scl-7-3.el6.centos.noarch
duration: 120(ms)
Installed products updated.

Installed:
  centos-release-scl.noarch 10:7-3.el6.centos                               

Complete!
</pre>
<p>然后安装 SCL 工具：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# yum install scl-utils scl-utils-build
</pre>
<p>运行 <code>yum makecache</code> 更新缓存之后，就可以查找 SCL 提供了哪些新版本的 gcc 和 binutils 包了：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# yum list | grep -E &quot;devtoolset-.*-(gcc|binutils)&quot;
devtoolset-3-binutils.x86_64                2.24-18.el6           centos-sclo-rh
devtoolset-3-binutils-devel.x86_64          2.24-18.el6           centos-sclo-rh
devtoolset-3-gcc.x86_64                     4.9.2-6.2.el6         centos-sclo-rh
devtoolset-3-gcc-c++.x86_64                 4.9.2-6.2.el6         centos-sclo-rh
devtoolset-3-gcc-gfortran.x86_64            4.9.2-6.2.el6         centos-sclo-rh
devtoolset-3-gcc-plugin-devel.x86_64        4.9.2-6.2.el6         centos-sclo-rh
devtoolset-4-binutils.x86_64                2.25.1-8.el6          centos-sclo-rh
devtoolset-4-binutils-devel.x86_64          2.25.1-8.el6          centos-sclo-rh
devtoolset-4-gcc.x86_64                     5.3.1-6.1.el6         centos-sclo-rh
devtoolset-4-gcc-c++.x86_64                 5.3.1-6.1.el6         centos-sclo-rh
devtoolset-4-gcc-gdb-plugin.x86_64          5.3.1-6.1.el6         centos-sclo-rh
devtoolset-4-gcc-gfortran.x86_64            5.3.1-6.1.el6         centos-sclo-rh
devtoolset-4-gcc-plugin-devel.x86_64        5.3.1-6.1.el6         centos-sclo-rh
devtoolset-6-binutils.x86_64                2.27-8.el6            centos-sclo-rh
devtoolset-6-binutils-devel.x86_64          2.27-8.el6            centos-sclo-rh
devtoolset-6-gcc.x86_64                     6.2.1-3.1.el6         centos-sclo-rh
devtoolset-6-gcc-c++.x86_64                 6.2.1-3.1.el6         centos-sclo-rh
devtoolset-6-gcc-gdb-plugin.x86_64          6.2.1-3.1.el6         centos-sclo-rh
devtoolset-6-gcc-gfortran.x86_64            6.2.1-3.1.el6         centos-sclo-rh
devtoolset-6-gcc-plugin-devel.x86_64        6.2.1-3.1.el6         centos-sclo-rh
</pre>
<p>可以看到 SCL 提供了 4.9.2、5.3.1、6.2.1 这几个新版本的 gcc 支持，与 binutils 一起分别属于几个不同版本的 devtoolset 软件包。</p>
<p>初步确认 gcc 5.3.1 就可以支持 AVX2 指令，因此直接安装对应 devtoolset-4 版本的 gcc 和 binutils 软件包：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# yum install devtoolset-4-gcc.x86_64 devtoolset-4-binutils.x86_64
</pre>
<h2 id="use-scl">使用 SCL</h2>
<p>软件包安装完成之后，我们可以使用 <code>scl -l</code> 命令查看 SCL 安装的软件包列表：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# scl -l
devtoolset-4
python27
</pre>
<p>当然 SCL 并不会直接替换系统自带的老版本软件包，需要通过 scl 命令切换使用：</p>
<pre class="brush: bash; title: ; notranslate">
scl enable &lt;scl-package-name&gt; &lt;command&gt;
</pre>
<p>这样我想开启一个使用新版本 gcc 的 shell，直接运行命令：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# scl enable devtoolset-4 bash
</pre>
<p>这样会启动一个新的 bash shell，可以在其中看到 gcc 已经是新的 5.3.1 版本了：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# gcc --version
gcc (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
</pre>
<p>当然如果不想开启新的 shell，也可以进入 Linux kernel 源代码目录直接使用新版本 gcc 来编译：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~/linux-4.10]# scl enable devtoolset-4 make
</pre>
<p>最后提醒一下 Software Collections 的软件包是可以不安装直接在线搜索的哦，从 SCL 官网的 collections 里就可以搜索：</p>
<p><a href="https://www.softwarecollections.org/en/scls/" target="_blank">https://www.softwarecollections.org/en/scls/</a></p>
<p>搜索结果里就会有 SCL 软件包的安装方法，例如 Python 2.7 搜索到之后直接运行 <code>yum install python27</code> 就可以安装使用了，祝大家玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/scl-linux-kernel/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>小内存OpenVZ VPS使用UML开启BBR</title>
		<link>https://zohead.com/archives/openvz-uml-bbr/</link>
		<comments>https://zohead.com/archives/openvz-uml-bbr/#comments</comments>
		<pubDate>Mon, 06 Mar 2017 18:39:55 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[User-mode Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[BBR]]></category>
		<category><![CDATA[busybox]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[OpenVZ]]></category>
		<category><![CDATA[Shadowsocks]]></category>
		<category><![CDATA[ShadowsocksR]]></category>
		<category><![CDATA[TAP]]></category>
		<category><![CDATA[UML]]></category>
		<category><![CDATA[VPS]]></category>
		<category><![CDATA[翻墙]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1365</guid>
		<description><![CDATA[前言 Bottleneck Bandwidth and RTT（BBR）是 Google 最近提交到 Linux 4.9 版本内核的新 TCP 拥塞控制算法，BBR 相对于 Linux 中已经用了好多年的默认 CUBIC 算法的主要改进是在有一定丢包率的网络链路上充分利用带宽，另外也能有效降低延迟。这对于很多使用美帝 VPS + Shadowsocks 爬墙方案的人来说应该还是比较有用的，目前从一些更新了内核开启 BBR 的 KVM VPS 用户的反馈来看效果也还是不错的。 我目前使用的爬墙服务器是 AlphaRacks 的低配 OpenVZ VPS，内存只有 96 MB，到国内的线路质量倒还 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="preface">前言</h2>
<p><a href="https://github.com/google/bbr">Bottleneck Bandwidth and RTT（BBR）</a>是 Google 最近提交到 Linux 4.9 版本内核的新 TCP 拥塞控制算法，BBR 相对于 Linux 中已经用了好多年的默认 CUBIC 算法的主要改进是在有一定丢包率的网络链路上充分利用带宽，另外也能有效降低延迟。这对于很多使用美帝 VPS + Shadowsocks 爬墙方案的人来说应该还是比较有用的，目前从一些更新了内核开启 BBR 的 KVM VPS 用户的反馈来看效果也还是不错的。</p>
<p>我目前使用的爬墙服务器是 AlphaRacks 的低配 OpenVZ VPS，内存只有 96 MB，到国内的线路质量倒还凑合，OpenVZ 虽然没办法直接升级 kernel ，但看到 AlphaRacks 控制面板里能开启 TUN/TAP，那可以考虑捣鼓使用 User-mode Linux（UML）来开启 BBR 了。</p>
<p>User-mode Linux 和 QEMU 这种虚拟机稍微有点类似，但 UML 是直接将 Linux 内核编译成可执行的 ELF 应用层程序，这样可以直接在普通 Linux 环境中运行及调试其它版本 Linux 内核的功能，有关 UML 的详细信息请参考其<a href="http://user-mode-linux.sourceforge.net/">官方网站</a>。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>不想自己编译 UML 内核和构建 UML rootfs 的朋友可以直接拉到「<a href="#use-uml-ss-system">使用 UML Shadowsocks 系统</a>」这部分哦。</p>
</blockquote>
<h2 id="compile-uml-kernel">编译 User-mode Linux 内核</h2>
<h3 id="compile-prepare">准备编译环境</h3>
<p>我是直接在 Windows 10 RS2 64 位系统上编译 UML 内核的，使用 Win10 自带的 Bash on Ubuntu 14.04 环境编译还是非常方便的，而且编译速度相比使用虚拟机也是快了很多。</p>
<p>首先安装编译需要的软件包：</p>
<blockquote>
<p><strong>备注</strong></p>
<p>由于我使用的 OpenVZ VPS 系统是 32 位的，所以这里安装 <code>libc-dev</code> 和 <code>libvdeplug-dev</code> 开发包时都指定了使用 32 位版本；如果你需要编译 64 位版本的 UML 内核，那把下面的 <code>-i386</code> 去掉即可。</p>
</blockquote>
<pre class="brush: bash; title: ; notranslate">
zzm@ZZM-P238:~$ sudo apt-get install build-essential libncurses5-dev libc6-dev-i386 libvdeplug-dev-i386
</pre>
<p>这里安装 <code>libvdeplug-dev</code> 开发包是为了支持 UML 的 <a href="http://wiki.virtualsquare.org/wiki/index.php/VDE_Basic_Networking#Step_3:_run_a_User-Mode_Linux">Virtual Distributed Ethernet（VDE）</a>模式网卡设备，VDE 主要是为了让 UML 的网络功能在类似 Docker 这种连 TUN/TAP 都不支持的系统上也能工作起来。</p>
<p>接着可以下载最新 Linux 4.10.1 版本内核源代码，并解压缩准备配置内核选项，4.10.1 版本源代码解压之后不到 800 MB。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>如果像我一样直接在 Win10 Bash 环境中编译，那么最好将内核源代码解压到 Win10 Bash 的 VolFs 文件系统中（也就是 Ubuntu rootfs 目录，一般直接 cd 到 <code>HOME</code> 主目录即可）。</p>
</blockquote>
<h3 id="uml-kernel-config">UML 内核配置</h3>
<p>如果使用默认的 UML 内核配置是比较简单的，直接下面两步即可，第二步内核配置界面里可以手工开启 BBR 功能：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@ZZM-P238:~/linux-4.10.1$ cp arch/um/configs/i386_defconfig .config
zzm@ZZM-P238:~/linux-4.10.1$ make menuconfig ARCH=um
</pre>
<p>不过为了能编译出体积最小又能满足 BBR 爬墙需要的 kernel，我还是使用自己修改的最小配置，首先将下面的内容保存为 <code>mini.config</code> 文件（如果需要编译 64 位 kernel，那直接把开头的 <code>CONFIG_64BIT=n</code> 这一行去掉即可）：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@ZZM-P238:~/linux-4.10.1$ cat mini.config
CONFIG_64BIT=n
CONFIG_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y
CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=n
CONFIG_HOSTFS=y
CONFIG_MCONSOLE=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_KERNEL_STACK_ORDER=1
CONFIG_SWAP=n
CONFIG_SYSVIPC=y
CONFIG_EXPERT=n
CONFIG_EMBEDDED=n
CONFIG_SLUB_DEBUG=n
CONFIG_BLOCK=y
CONFIG_LBDAF=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_BLK_DEV_BSG=n
CONFIG_BLK_DEV=y
CONFIG_BLK_DEV_UBD=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_IOSCHED_DEADLINE=y
CONFIG_IOSCHED_CFQ=n
CONFIG_DEFAULT_IOSCHED=&quot;deadline&quot;
CONFIG_NETDEVICES=n
CONFIG_STDERR_CONSOLE=y
CONFIG_SSL=y
CONFIG_NULL_CHAN=y
CONFIG_PORT_CHAN=y
CONFIG_PTY_CHAN=y
CONFIG_TTY_CHAN=y
CONFIG_XTERM_CHAN=y
CONFIG_UNIX98_PTYS=y
CONFIG_EXT2_FS=y
CONFIG_PROC_FS=y
CONFIG_PROC_SYSCTL=y
CONFIG_TMPFS=y
CONFIG_SYSFS=y
CONFIG_SCHED_DEBUG=n
CONFIG_NET=y
CONFIG_UNIX=y
CONFIG_INET=y
CONFIG_TCP_CONG_ADVANCED=y
CONFIG_TCP_CONG_CUBIC=y
CONFIG_TCP_CONG_BBR=y
CONFIG_DEFAULT_BBR=y
CONFIG_DEFAULT_TCP_CONG=&quot;bbr&quot;
CONFIG_IPV6=n
CONFIG_SYN_COOKIES=y
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_QFQ=y
CONFIG_NET_SCH_CODEL=y
CONFIG_NET_SCH_FQ_CODEL=y
CONFIG_NET_SCH_FQ=y
CONFIG_NET_SCH_FIFO=y
CONFIG_UML_NET=y
CONFIG_UML_NET_TUNTAP=y
CONFIG_UML_NET_VDE=y
CONFIG_DEBUG_KERNEL=n
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
</pre>
<p>简单说明一下上面的内核配置选项：</p>
<ul>
<li>启用了 UML 的 hostfs 及 mconsole 等很实用的功能；</li>
<li>支持 UML 的 ubd 块设备，也开启了 loop 设备支持，去掉了 ramdisk 等没什么用的其它设备支持；</li>
<li>支持 proc、sysfs、tmpfs 等虚拟文件系统，真实的文件系统只保留了 ext2 支持，需要 ext3 或者 ext4 支持的朋友请自行修改；</li>
<li>开启 BBR 并设置为默认拥塞控制算法，另外需要为 BBR 启用 Fair Queue 包调度器；</li>
<li>去掉了 IPv6 支持，去掉了所有的网卡设备驱动；</li>
<li>去掉了几乎所有的内核调试信息。</li>
</ul>
<p>然后使用 <code>mini.config</code> 生成新的内核配置文件：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@ZZM-P238:~/linux-4.10.1$ make ARCH=um allnoconfig KCONFIG_ALLCONFIG=mini.config
</pre>
<h3 id="compile-kernel">编译内核</h3>
<p>内核选项配置完毕之后，编译就非常简单了：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@ZZM-P238:~/linux-4.10.1$ make ARCH=um vmlinux -j4
</pre>
<p>这里我开启了 4 个并发编译（make 的 <code>-j4</code> 参数）支持，UML 的 vmlinux 可执行程序在我的笔记本上不到 4 分钟就编译完成了，最终编译出来的 vmlinux 文件大小大概为 3 - 4 MB。</p>
<p>你可以试着运行编译出来的 vmlinux 可执行程序查看编译时使用的内核配置：</p>
<pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# ./vmlinux --showconfig
</pre>
<h2 id="prepare-uml-rootfs">准备 UML rootfs</h2>
<h3 id="prepare-rootfs-ss-libev">准备 rootfs 和 shadowsocks-libev</h3>
<p>既然 UML 内核我都是按最小化编译的，那运行的 rootfs 自然不能用像 Ubuntu、CentOS 那种「庞大」的系统了，这里我直接使用最小化的 busybox rootfs + Shadowsocks 服务端程序和运行库的方式，除了 busybox 基础环境和 Shadowsocks 服务端程序基本没有添加任何其它东西。</p>
<p>最终我将 UML vmlinux 内核文件和 rootfs 目录一起压缩打包起来大小也不过才 3 MB，这样能非常方便地上传到 VPS 上和分享给其他人。当然缺点就是如果需要安装其它的软件那只能手工拷贝到 rootfs 目录中了。</p>
<p>具体 busybox rootfs 的制作方法这里我就不做详细说明了，感兴趣的朋友可以参考相关的文档了解一下，我分别制作了 32 位和 64 位的 busybox rootfs 目录。</p>
<p>另外为了减少 UML 系统的程序依赖和内存占用，Shadowsocks 服务端我选择的是 <a href="https://github.com/shadowsocks/shadowsocks-libev">shadowsocks-libev</a>，这里我也分别编译了最新 3.0.3 版本 32 位和 64 位的服务端程序。</p>
<p>32 位 shadowsocks-libev 我是在 32 位 Ubuntu 14.04 系统下编译的，依赖的动态库也是使用的 Ubuntu 14.04 自带的库文件；64 位 shadowsocks-libev 则是在 64 位 CentOS 6 系统下编译的，所以 64 位运行库版本和 32 位的不同。你问我为什么不用同一个系统？因为我刚好有这两个系统的 VPS 能用来编译，实在是懒的换了 ^_^。</p>
<h3 id="test-uml-rootfs">测试 UML rootfs</h3>
<p>由于 OpenVZ 系统内核不支持 loop 设备，不能挂载文件系统映像进行修改，这里我直接使用 UML 的 hostfs 将 OpenVZ 主机系统的一个目录设为 UML 系统的 rootfs。</p>
<p>现在 UML hostfs 并没有某些网友说的存在只读的限制，而且相比使用 loop 设备或者 ubd 文件系统这种方式，hostfs 并不需要预先产生一个很大的文件，主机也能直接修改 UML rootfs 目录内容，UML 内核也根本不需要支持 ext3、ext4 这种真实的文件系统，综合比较下来实在是没有什么理由不用 hostfs。</p>
<p>首先安装 UML 工具 <code>uml-utilities</code> 软件包，后面的 <code>tunctl</code> 和 <code>uml_mconsole</code> 等命令会用到：</p>
<pre class="brush: bash; title: ; notranslate">
root@zohead:~# apt-get install uml-utilities
</pre>
<p>接着可以配置 OpenVZ 系统上的 TUN/TAP 虚拟网卡和开启端口转发：</p>
<pre class="brush: bash; title: ; notranslate">
root@zohead:~# tunctl -u root
Set 'tap0' persistent and owned by uid 0
root@zohead:~# ifconfig tap0 192.168.0.3 up
root@zohead:~# echo 1 &gt; /proc/sys/net/ipv4/ip_forward
root@zohead:~# iptables -t nat -I POSTROUTING -o venet0 -j MASQUERADE
root@zohead:~# iptables -t nat -A PREROUTING -i venet0 -p tcp --dport 9443 -j DNAT --to-destination 192.168.0.4
root@zohead:~# iptables -t nat -A PREROUTING -i venet0 -p udp --dport 9443 -j DNAT --to-destination 192.168.0.4
</pre>
<blockquote>
<p><strong>备注</strong></p>
<p>上面的命令中假设 TUN/TAP 虚拟网卡 IP 地址为 <code>192.168.0.3</code>（可以自行修改），UML 系统网卡 IP 地址为 <code>192.168.0.4</code>（需要和 TUN/TAP 虚拟网卡地址在同一网段），只将 OpenVZ 主机系统上 <code>9443</code> 端口的 TCP 和 UDP 数据转发到 UML 系统网卡上。</p>
</blockquote>
<p>最后是启动 UML 系统的例子：</p>
<pre class="brush: bash; title: ; notranslate">
root@zohead:~# ./vmlinux-i686 umid=shadowsocks root=/dev/root rootfstype=hostfs rootflags=/root/umlfs-i686 rw mem=48M init=/sbin/init eth0=tuntap,tap0
</pre>
<p>简单说明一下 UML vmlinux 程序的参数：</p>
<ul>
<li><code>umid</code> 参数指定 UML 系统 ID，建议每个 UML 系统都使用的固定的 ID，不然每次运行都会在 <code>~/.uml</code> 目录下产生新的临时目录；</li>
<li><code>rootflags</code> 参数指定 UML rootfs 路径，这里 <strong>必须</strong> 使用绝对路径；</li>
<li><code>mem</code> 参数指定 UML 系统的内存大小，经过我的测试，只运行 shadowsocks-libev 服务端的话就算分配 32 MB 内存看起来也没什么压力；</li>
<li><code>init</code> 参数指定 UML 系统的 init 进程，不建议修改；</li>
<li><code>eth0=tuntap,tap0</code> 则是指定 UML 的 tuntap 模式网卡，使用 OpenVZ 主机系统的 <code>tap0</code> 虚拟网卡设备。</li>
</ul>
<h2 id="use-uml-ss-system">使用 UML Shadowsocks 系统</h2>
<h3 id="uml-linux-sh">uml-linux.sh 脚本</h3>
<p>为了使用起来方便，我把启动 UML 系统和设置端口转发的操作写成了一个 <code>uml-linux.sh</code> 脚本，这样 OpenVZ 系统启动之后可以根据配置自动运行并配置端口转发：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
UML_ID=&quot;shadowsocks&quot;
TAP_DEV=&quot;tap0&quot;
ROUTER_DEV=&quot;venet0&quot;
TAP_ADDR=&quot;192.168.0.3&quot;
UML_ADDR=&quot;192.168.0.4&quot;
REV_TCP_PORTS=&quot;&quot;
REV_UDP_PORTS=&quot;&quot;
FWD_TCP_PORTS=&quot;9443&quot;
FWD_UDP_PORTS=&quot;9443&quot;

DIR=&quot;$( cd &quot;$( dirname &quot;$0&quot; )&quot; &amp;&amp; pwd )&quot;

[ ! -d /sys/class/net/$TAP_DEV ] &amp;&amp; tunctl -t $TAP_DEV
ifconfig $TAP_DEV $TAP_ADDR up

echo 1 &gt; /proc/sys/net/ipv4/ip_forward

iptables -t nat -I POSTROUTING -o $ROUTER_DEV -j MASQUERADE

for i in $REV_TCP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		P_TMP=&quot;&quot;
	else
		P_TMP=&quot;--dport $i &quot;
	fi
	iptables -t nat -A PREROUTING -p tcp $P_TMP-j RETURN
done
for i in $REV_UDP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		P_TMP=&quot;&quot;
	else
		P_TMP=&quot;--dport $i &quot;
	fi
	iptables -t nat -A PREROUTING -p udp $P_TMP-j RETURN
done

for i in $FWD_TCP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		P_TMP=&quot;&quot;
	else
		P_TMP=&quot;--dport $i &quot;
	fi
	iptables -t nat -A PREROUTING -i $ROUTER_DEV -p tcp $P_TMP-j DNAT --to-destination $UML_ADDR
done
for i in $FWD_UDP_PORTS; do
	if [ &quot;x$i&quot; = &quot;x*&quot; ]; then
		P_TMP=&quot;&quot;
	else
		P_TMP=&quot;--dport $i &quot;
	fi
	iptables -t nat -A PREROUTING -i $ROUTER_DEV -p udp $P_TMP-j DNAT --to-destination $UML_ADDR
done

nohup $DIR/vmlinux-i686 umid=$UML_ID root=/dev/root rootfstype=hostfs rootflags=$DIR/umlfs-i686 rw mem=48M init=/sbin/init con=pts eth0=tuntap,$TAP_DEV,,$UML_ADDR &amp;
</pre>
<p>一般只要修改脚本最开始的几行配置即可（最后一行 vmlinux 程序和 UML rootfs 路径默认使用脚本所在路径，如果不同请自行修改）：</p>
<ul>
<li><code>TAP_ADDR</code> 为 TUN/TAP 虚拟网卡地址，<code>UML_ADDR</code> 为 UML 系统内网卡地址；</li>
<li><code>REV_TCP_PORTS</code> 和 <code>REV_UDP_PORTS</code> 分别指定 OpenVZ 主机系统需要保留哪些 TCP 和 UDP 端口不转发到 UML 系统（多个以空格隔开），一般如果 UML 系统使用固定的一些端口，那么默认留空即可；</li>
<li><code>FWD_TCP_PORTS</code> 和 <code>FWD_UDP_PORTS</code> 分别指定哪些 TCP 和 UDP 端口需要转发到 UML 系统（默认只写了一个 <code>9443</code>，实际如果需要多个则以空格隔开），如果需要指定端口范围也可以写成 <code>15000:20000</code>，如果需要所有端口都转发到 UML 系统那可以直接写成 <code>*</code>（此时 <code>REV_TCP_PORTS</code> 一般最少要留一个端口用于 SSH 远程登录）；</li>
<li>需要分配给 UML 系统的内存大小请自行修改 vmlinux 的 <code>mem</code> 参数。</li>
</ul>
<h3 id="uml-ss-config">UML Shadowsocks 系统配置</h3>
<p>另外 UML 系统内部的网卡地址配置及启动 shadowsocks-libev 服务端的操作都在 <code>/etc/rc.local</code> 脚本中：</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
if [ -d /sys/class/net/eth0 ]; then
	ifconfig eth0 192.168.0.4 up
	route add default gw 192.168.0.3 dev eth0
	sleep 2
fi

SS_DNS=&quot;&quot;

[ -f /etc/default/pdnsd ] &amp;&amp; . /etc/default/pdnsd

# start local DNS server if needed
if [ &quot;x$START_DAEMON&quot; = &quot;xyes&quot; -o &quot;x$START_DAEMON&quot; = &quot;xYES&quot; ]; then
	pdnsd -d
	[ $? -eq 0 ] &amp;&amp; SS_DNS=&quot;-d 127.0.0.1 &quot;
fi

for i in `ls /etc/shadowsocks-libev/*.json 2&gt;/dev/null`; do
	SS_NAME=`basename $i .json 2&gt;/dev/null`
	start-stop-daemon -n shadowsocks-$SS_NAME -x ss-server -b -S -- -c $i -u $SS_DNS--fast-open
done

exit 0
</pre>
<p>简单说明一下：</p>
<ul>
<li>这里的 <code>192.168.0.4</code> 网卡 IP 地址就对应上面的 <code>UML_ADDR</code>，<code>192.168.0.3</code> 网关地址就是 <code>TAP_ADDR</code>，因此如果上面脚本中的地址修改了，那么 UML rootfs 的 <code>/etc/rc.local</code> 中的地址也需要修改；</li>
<li>你可以将 shadowsocks-libev 配置文件放到 <code>/etc/shadowsocks-libev</code> 目录中，此脚本会自动遍历并一一启动 <code>ss-server</code> 服务器，同时默认也开启了 TCP Fast Open 和 UDP 转发；</li>
<li>另外 rootfs 里集成了 <code>pdnsd</code> DNS 服务器，如果需要实现 DNS 缓存加速或者 UML 系统的 UDP DNS 解析存在问题，都可以启用 <code>pdnsd</code> 本地 DNS 服务器。</li>
</ul>
<p>配置修改完成之后，运行 <code>uml-linux.sh</code> 脚本就会在后台启动 UML 系统，并根据需要自动启动 shadowsocks-libev 服务器。</p>
<h3 id="control-uml-system">控制 UML 系统</h3>
<p>如果你需要登录到 UML 系统，可以先用 <code>uml_mconsole</code> 命令得到 UML 系统分配到的虚拟终端（第二个参数就是 UML 系统 ID）：</p>
<pre class="brush: bash; title: ; notranslate">
root@zohead:~# uml_mconsole shadowsocks config con0
OK pts:/dev/pts/2
</pre>
<p>然后使用 <code>screen</code> 命令就可以连接到该虚拟终端上（默认什么都不显示，需要按回车键终端才会出来）：</p>
<pre class="brush: bash; title: ; notranslate">
root@zohead:~# screen /dev/pts/2
</pre>
<p>如果想在 OpenVZ 主机系统里直接关闭整个 UML 系统，那也非常简单：</p>
<pre class="brush: bash; title: ; notranslate">
root@zohead:~# uml_mconsole shadowsocks halt
</pre>
<h3 id="uml-ss-download">UML Shadowsocks 系统下载</h3>
<p>为了方便大家使用，我把 32 位和 64 位的 UML 4.10.1 版本 vmlinux 内核程序及配置和 Shadowsocks 服务端 rootfs 目录、<code>uml-linux.sh</code> 脚本一起打包分享给大家。</p>
<p>32 位的 UML Shadowsocks 系统（百度网盘备用）：</p>
<p><a href="https://zohead.com/downloads/uml-shadowsocks-i686.tar.xz">https://zohead.com/downloads/uml-shadowsocks-i686.tar.xz</a> <br />
<a href="https://pan.baidu.com/s/1kVdoWcB" target="_blank">https://pan.baidu.com/s/1kVdoWcB</a></p>
<p>64 位的 UML Shadowsocks 系统：</p>
<p><a href="https://zohead.com/downloads/uml-shadowsocks-x64.tar.xz">https://zohead.com/downloads/uml-shadowsocks-x64.tar.xz</a> <br />
<a href="https://pan.baidu.com/s/1dFDta9N" target="_blank">https://pan.baidu.com/s/1dFDta9N</a></p>
<p>将上面的文件下载到 VPS 某个目录并解压缩，修改 <code>uml-linux.sh</code> 文件中的转发端口，将 <a href="https://wiki.archlinux.org/index.php/Shadowsocks" target="_blank">Shadowsocks 服务端配置文件</a>放到 rootfs 的 <code>/etc/shadowsocks-libev</code> 目录，之后以 root 身份运行 <code>uml-linux.sh</code> 脚本即可。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>如果需要让 ShadowsocksR 服务器使用 TCP 等方式进行域名解析，可以修改 <code>/etc/default/pdnsd</code> 文件将其中的 <code>START_DAEMON</code> 改为 <code>yes</code> 来启用 <code>pdnsd</code> 本地 DNS 服务器。</p>
</blockquote>
<h3 id="uml-ssr-download">UML ShadowsocksR 系统</h3>
<p>考虑到某些网友的需要，我还生成了 32 和 64 位的 UML ShadowsocksR 系统，都是使用的最新支持混淆的 Python 版本 ShadowsocksR 服务器，有需要的朋友可以下载测试（百度网盘为备用下载地址）。</p>
<p>32 位的 UML ShadowsocksR 系统：</p>
<p><a href="https://zohead.com/downloads/uml-shadowsocksr-i686.tar.xz">https://zohead.com/downloads/uml-shadowsocksr-i686.tar.xz</a> <br />
<a href="https://pan.baidu.com/s/1eSDrWye">https://pan.baidu.com/s/1eSDrWye</a></p>
<p>64 位的 UML ShadowsocksR 系统：</p>
<p><a href="https://zohead.com/downloads/uml-shadowsocksr-x64.tar.xz">https://zohead.com/downloads/uml-shadowsocksr-x64.tar.xz</a> <br />
<a href="https://pan.baidu.com/s/1o7F3qpc">https://pan.baidu.com/s/1o7F3qpc</a></p>
<p>ShadowsocksR 系统的 rootfs 只包含了基础 Python 环境和 ShadowsocksR 服务器程序，同样下载并解压缩，由于包含了 Python 环境，该 rootfs 解压缩之后接近 30 MB。</p>
<p>使用前可根据需要修改 <code>uml-linux.sh</code> 文件中的转发端口，将 <a href="https://github.com/breakwa11/shadowsocks-rss/wiki/config.json" target="_blank">ShadowsocksR 服务器 JSON 格式配置文件</a>放到 rootfs 的 <code>/etc/shadowsocks</code> 目录，之后以 root 身份运行 <code>uml-linux.sh</code> 脚本即可（启用 <code>pdnsd</code> 本地 DNS 服务器的方法和上面的相同）。</p>
<h2 id="uml-bbr-compare">UML 系统 BBR 效果</h2>
<p>最后对比一下我的 AlphaRacks OpenVZ VPS 上启用 BBR 前后的对比速度，这里我就直接观看同一个 1080p 的 YouTube 视频来对比统计信息了。</p>
<p>在我的江苏移动宽带环境下，启用 BBR 之前 YouTube 视频连接速度一般只能跑到 1Mbps 左右：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442863/openvz-ss-speed.png" alt="OpenVZ 未启用 BBR 的 YouTube 速度" title="OpenVZ 未启用 BBR 的 YouTube 速度"></p>
<p>而在使用 UML Shadowsocks 系统开启 BBR 之后能跑到 2Mbps，不过有的时候速度还是不太稳定：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442867/openvz-ss-speed-bbr.png" alt="OpenVZ 启用 UML BBR 的 YouTube 速度" title="OpenVZ 启用 UML BBR 的 YouTube 速度"></p>
<p>UML 目前也是主要用于调试验证 Linux 内核功能的，总之 OpenVZ 使用 UML 开启 BBR 的实际使用效果还是需要长期观察测试的，本文也主要关注 UML 系统下使用 Shadowsocks 的问题，并没有考虑通用的情况，如果文章中有任何问题还请提出指正，祝大家玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/openvz-uml-bbr/feed/</wfw:commentRss>
		<slash:comments>40</slash:comments>
		</item>
		<item>
		<title>Bluemix容器系统更新udev的问题</title>
		<link>https://zohead.com/archives/bluemix-udev/</link>
		<comments>https://zohead.com/archives/bluemix-udev/#comments</comments>
		<pubDate>Tue, 29 Nov 2016 14:18:02 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Bluemix]]></category>
		<category><![CDATA[deb]]></category>
		<category><![CDATA[udev]]></category>
		<category><![CDATA[容器]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1306</guid>
		<description><![CDATA[Bluemix 更新 udev 的问题 最近在 IBM Bluemix 管理控制台中查看容器状态时发现一直会报 策略违例 问题，进入容器详细信息界面，可以看到漏洞顾问程序扫描到的容器系统中存在的违例情况，主要是系统中某些软件包版本比较老，Bluemix 建议进行安全更新升级： 由于我的 Bluemix 容器使用的是 Ubuntu 14.04 系统，起初想着直接运行 apt-get 命令将系统中的软件包都进行升级应该就差不多可以搞定了： 但是在升级 udev 软件包的时候发现能正确下载软件包但升级失败，提示 /etc/modprobe.d 内核模块配置目录无法正常写入： 我如果跳过升级 udev [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="bluemix-update-udev-issue">Bluemix 更新 udev 的问题</h2>
<p>最近在 IBM Bluemix 管理控制台中查看容器状态时发现一直会报 策略违例 问题，进入容器详细信息界面，可以看到漏洞顾问程序扫描到的容器系统中存在的违例情况，主要是系统中某些软件包版本比较老，Bluemix 建议进行安全更新升级：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737370618/bluemix-violation.jpg" alt="Bluemix容器策略违例" title="Bluemix容器策略违例"></p>
<p>由于我的 Bluemix 容器使用的是 Ubuntu 14.04 系统，起初想着直接运行 <code>apt-get</code> 命令将系统中的软件包都进行升级应该就差不多可以搞定了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# apt-get update
root@instance-007a20ff:~# apt-get upgrade
</pre>
<p>但是在升级 udev 软件包的时候发现能正确下载软件包但升级失败，提示 <code>/etc/modprobe.d</code> 内核模块配置目录无法正常写入：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# apt-get install udev
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages will be upgraded:
  udev
1 upgraded, 0 newly installed, 0 to remove and 26 not upgraded.
28 not fully installed or removed.
Need to get 735 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ trusty-updates/main udev amd64 204-5ubuntu20.19 [735 kB]
Fetched 735 kB in 1s (437 kB/s)
(Reading database ... 30255 files and directories currently installed.)
Preparing to unpack .../udev_204-5ubuntu20.19_amd64.deb ...
Adding 'diversion of /bin/udevadm to /bin/udevadm.upgrade by fake-udev'
Unpacking udev (204-5ubuntu20.19) over (204-5ubuntu20.15) ...
dpkg: error processing archive /var/cache/apt/archives/udev_204-5ubuntu20.19_amd64.deb (--unpack):
 unable to create `/etc/modprobe.d/fbdev-blacklist.conf.dpkg-new' (while processing `./etc/modprobe.d/fbdev-blacklist.conf'): Permission denied
dpkg-deb: error: subprocess paste was killed by signal (Broken pipe)
Removing 'diversion of /bin/udevadm to /bin/udevadm.upgrade by fake-udev'
Processing triggers for ureadahead (0.100.0-16) ...
Errors were encountered while processing:
 /var/cache/apt/archives/udev_204-5ubuntu20.19_amd64.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)
</pre>
<p>我如果跳过升级 udev 包，又会由于 cpio 等软件包依赖新版本的 udev 包导致其它的软件包也无法正常升级，因此最好能解决 udev 包的升级问题。</p>
<p>接着我测试在报错的 <code>/etc/modprobe.d</code> 目录中创建文件或者拷贝文件，发现始终都是报 Permission denied 错误。看起来很有可能是由于 Bluemix 容器的限制导致无法在该目录中进行写操作。要解决 udev 包升级的问题看来需要重新打包 udev 进行安装。</p>
<h2 id="repack-udev">重新打包 udev</h2>
<p>首先使用 <code>apt-get</code> 命令下载最新版本的 udev 安装包：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# apt-get download udev
Get:1 http://archive.ubuntu.com/ubuntu/ trusty-updates/main udev amd64 204-5ubuntu20.19 [735 kB]
Fetched 735 kB in 0s (905 kB/s)
</pre>
<p>创建用于解压缩及重新构建 deb 包的目录：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# mkdir extract extract/DEBIAN build
</pre>
<p>开始解压缩 deb 安装包中的文件以及专用的 control 文件：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg -X udev_204-5ubuntu20.19_amd64.deb extract/
root@instance-007a20ff:~# dpkg -e udev_204-5ubuntu20.19_amd64.deb extract/DEBIAN/
</pre>
<p>接着就可以删除不需要的 <code>fbdev-blacklist.conf</code> 文件了，需要注意的是 control 目录中也需要移除对应项：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# rm -f extract/etc/modprobe.d/fbdev-blacklist.conf
root@instance-007a20ff:~# sed -i '/fbdev-blacklist.conf/d' extract/DEBIAN/conffiles
</pre>
<p>最后就可以打包新的 deb 安装文件了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg-deb -b extract build
dpkg-deb: warning: 'extract/DEBIAN/control' contains user-defined field 'Original-Maintainer'
dpkg-deb: warning: ignoring 1 warning about the control file(s)

dpkg-deb: building package `udev' in `build/udev_204-5ubuntu20.19_amd64.deb'.
</pre>
<h2 id="upgrade-udev">升级 udev 相关软件包</h2>
<p>到这一步就可以直接用 dpkg 命令安装我们重新打包的 udev 安装文件了：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg -i build/udev_204-5ubuntu20.19_amd64.deb
(Reading database ... 30255 files and directories currently installed.)
Preparing to unpack .../udev_204-5ubuntu20.19_amd64.deb ...
Adding 'diversion of /bin/udevadm to /bin/udevadm.upgrade by fake-udev'
Unpacking udev (204-5ubuntu20.19) over (204-5ubuntu20.15) ...
dpkg: dependency problems prevent configuration of udev:
 udev depends on libdbus-1-3 (&gt;= 1.0.2); however:
  Package libdbus-1-3:amd64 is not configured yet.
 udev depends on libudev1 (= 204-5ubuntu20.19); however:
  Package libudev1:amd64 is not configured yet.

dpkg: error processing package udev (--install):
 dependency problems - leaving unconfigured
Processing triggers for man-db (2.6.7.1-1ubuntu1) ...
Processing triggers for ureadahead (0.100.0-16) ...
Errors were encountered while processing:
 udev
</pre>
<p>这里的报错信息可以先不用管，后面我们再运行 <code>apt-get upgrade</code> 命令升级完其它的软件包就没有问题了。此时用 dpkg 命令就可以检查新的 udev 包状态：</p>
<pre class="brush: bash; title: ; notranslate">
root@instance-007a20ff:~# dpkg -l udev
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                   Version          Architecture     Description
+++-======================-================-================-=================================================
ii  udev                   204-5ubuntu20.19 amd64            /dev/ and hotplug management daemon
</pre>
<p>将 Bluemix 报告的有安全风险的软件包都进行升级，等到 Bluemix 重新进行漏洞扫描之后，再访问管理控制台就可以看到 Ubuntu 容器系统已经不会报策略违例了。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/bluemix-udev/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Win10年度更新开启Bash on Ubuntu</title>
		<link>https://zohead.com/archives/win10-bash-ubuntu/</link>
		<comments>https://zohead.com/archives/win10-bash-ubuntu/#comments</comments>
		<pubDate>Sat, 06 Aug 2016 17:41:05 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Redstone]]></category>
		<category><![CDATA[rsync]]></category>
		<category><![CDATA[Win10]]></category>
		<category><![CDATA[备份]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1262</guid>
		<description><![CDATA[关于 Bash on Ubuntu 之前为了能在 Windows 上使用 Bash 等类似 Unix（Linux）系统的体验，我都是安装了 Cygwin、MSYS 等运行环境，都体验上都存在一些问题：Cygwin 上的程序基本都需要重新编译出基于其运行库（cygwin1.dll）的版本，MSYS 虽然提供了 Linux 下常用的开发工具链、移植过的运行库等等，但基本还是受限于 Windows API 本身的。 当我看到 Windows 10 Build 14316 内部预览版时爆出的 Bash on Ubuntu 功能之后还是比较期待的，因为微软并不是以虚拟机的方式运行 Ubuntu 系统，而 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="关于-bash-on-ubuntu">关于 Bash on Ubuntu</h2>
<p>之前为了能在 Windows 上使用 Bash 等类似 Unix（Linux）系统的体验，我都是安装了 Cygwin、MSYS 等运行环境，都体验上都存在一些问题：Cygwin 上的程序基本都需要重新编译出基于其运行库（cygwin1.dll）的版本，MSYS 虽然提供了 Linux 下常用的开发工具链、移植过的运行库等等，但基本还是受限于 Windows API 本身的。</p>
<p>当我看到 Windows 10 Build 14316 内部预览版时爆出的 Bash on Ubuntu 功能之后还是比较期待的，因为微软并不是以虚拟机的方式运行 Ubuntu 系统，而是直接集成了新的 <strong><a href="https://blogs.msdn.microsoft.com/wsl/">Windows Subsystem for Linux</a></strong> 子系统，这样能直接在 Bash on Ubuntu 环境里编译运行 Linux 程序还是非常方便的。</p>
<p>考虑到预览版稳定性的问题，我一直没有加入 Win10 Insider 预览体验会员，这几天看到 Win10 Redstone 1 年度更新到来之后终于可以直接体验了。虽然我使用的 Win10 企业版一直接收不到更新推送，而且使用微软官方的 Media Creation Tool 也无法在企业版上更新，但还好还是可以直接下载 Redstone 1 企业版的 iso 进行更新。</p>
<h2 id="安装体验">安装体验</h2>
<p>Win10 年度更新的持续数小时的痛苦安装过程这里就不表了，更新完成后首先在设置中开启开发者支持，然后在 控制面板 - 程序和功能 中开启 <strong>Windows Subsystem for Linux</strong> 功能，开启之后需要重启系统，重启之后在命令行中运行 <strong>bash</strong> 命令下载安装，根据你的网络情况可能又需要一两个小时（强烈建议挂代理的），安装完成之后会提示配置用户，接着就可以正常启动 Bash on Ubuntu 了：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442968/win10-bash.jpg" alt="Bash on Ubuntu on Windows 10" title="Bash on Ubuntu on Windows 10"></p>
<p>第一步就可以先运行 <code>sudo apt-get update</code> 更新 Ubuntu 软件仓库，看起来一切正常：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442967/win10-apt-get.jpg" alt="Win10 上使用 apt-get" title="Win10 上使用 apt-get"></p>
<p>另外可以确认下 Windows 10 上运行的 Ubuntu 子系统的基本情况：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442968/win10-bash-kernel.jpg" alt="Windows 10 的 Linux 子系统" title="Windows 10 的 Linux 子系统"></p>
<p>从上面的截图可以看出 Bash on Ubuntu 会把所有 Windows 分区都挂载到 <code>/mnt</code> 目录下方便使用（这点和 Cygwin 的 <code>/cygdrive/c</code> 以及 MSYS 的 <code>/c</code> 路径有点类似哦），默认运行的是 Ubuntu 14.04 LTS 系统，用户看到的 Linux kernel 是 3.4.0 版本。</p>
<p>接着就可以安装运行一些常用的 Linux 命令了，awk、sed、svn、git 等和系统内核关系不大的命令都能正常工作，<code>ifconfig</code> 和 <code>ping</code> 等命令会运行失败，体验一圈下来还是能满足我的使用需求的。</p>
<h2 id="rsync-备份性能测试">rsync 备份性能测试</h2>
<p>我之前写过一篇文章 <a href="https://zohead.com/archives/rsync-performance-linux-cygwin-msys/">rsync在 Linux/cygwin/msys 环境下的备份性能对比</a> 对比 Linux 原生的 rsync 命令和 Windows 上几种常见的非原生 rsync 实现的备份性能，结果不出意外 Linux 原生 rsync 的效果是最好的。</p>
<p>现在既然 Win10 已经支持了还算完整的 Ubuntu 子系统，那么我就可以拿 Ubuntu 子系统里的 rsync 命令来测试一下备份文件的性能到底如何了，而且这里值得一提的是 Bash on Ubuntu 安装好之后就已经自带 rsync 命令了（没有也可以妥妥的 apt-get 自动安装哦）。</p>
<p>先看看 Bash on Ubuntu 自带的 rsync 命令是 3.1.0 版本的，可以直接和我们目前使用的存储服务器配合测试：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442969/win10-rsync.jpg" alt="rsync on Windows 10" title="rsync on Windows 10"></p>
<p>为了对比方便我在相同的客户端上分别运行 RHEL6 Linux 系统和开启了 Bash on Ubuntu 的 Win10 系统使用 rsync 命令进行文件备份的上传和下载性能测试，存储服务器上运行的是标准 rsync 服务器（没有开启 SSH 用户验证）。</p>
<p>测试中也使用和上面的备份性能对比文章相似的配置，服务器使用 24 个 SATA 盘建立 RAID0 磁盘阵列做底层存储，客户端和服务器之间都是千兆网络，客户端上的文件读写都使用内存文件系统（Linux 上使用 <code>tmpfs</code>，Windows 上使用 <code>ImDisk</code> 工具生成内存盘）防止客户端本地硬盘读写性能成为瓶颈。</p>
<p>先看看 RHEL6 Linux 系统下的 rsync 备份性能结果：</p>
<pre class="brush: bash; title: ; notranslate">
/dev/shm # rsync -hv 0.dat test@192.168.1.35::sync/
Password:
0.dat

sent 1.61G bytes  received 27 bytes  82.50M bytes/sec
total size is 1.61G  speedup is 1.00

/dev/shm # rsync -hv test@192.168.1.35::sync/0.dat .
Password:
0.dat

sent 59 bytes  received 1.61G bytes  91.93M bytes/sec
total size is 1.61G  speedup is 1.00
</pre>
<p>可以看到 Linux 系统下 rsync 写一个大概 1.6 GB 的文件可以达到 82.50 MB/s，读可以到 91.93 MB/s。</p>
<p>然后在 Win10 的 Bash 环境中同样使用自带 rsync 命令进行测试：</p>
<pre class="brush: bash; title: ; notranslate">
zzm@ZZM-VOLANS:~$ rsync -hv 1.dat 192.168.1.35::sync/
1.dat

sent 1.57G bytes  received 30 bytes  89.90M bytes/sec
total size is 1.57G  speedup is 1.00

zzm@ZZM-VOLANS:/mnt/h$ rsync -hv 192.168.1.35::sync/1.dat .
1.dat

sent 45 bytes  received 1.57G bytes  95.34M bytes/sec
total size is 1.57G  speedup is 1.00
</pre>
<p>看起来结果还是比较惊喜的，Win10 Bash 上的 rsync 命令可以基本跑满千兆网卡的带宽，备份性能甚至比 Linux 上的效果还好了那么一点点。这样不得不说我之前写的那篇文章里总结的 Windows 推荐使用 Cygwin 做 rsync 客户端的建议在 Bash on Ubuntu 推出之后就要过时咯。</p>
<h2 id="总结">总结</h2>
<p>从 Win10 年度更新的简单试用和性能测试情况来看，Bash on Ubuntu 目前还算比较符合我的预期的，毕竟我最想要的直接在 Windows 上编译 Linux 二进制程序就更加方便了，看起来也不需要专门开虚拟机来进行编译工作中需要用到的 Linux 内核和应用程序了。当然目前 Bash on Ubuntu 仍然有不少问题和限制（毕竟还是 beta 阶段），还是希望微软能在后续更新中解决咯，祝大家玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/win10-bash-ubuntu/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
