<?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/tag/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>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>使用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>Linux下使用tcpkill工具重置NFS连接</title>
		<link>https://zohead.com/archives/tcpkill-nfs/</link>
		<comments>https://zohead.com/archives/tcpkill-nfs/#comments</comments>
		<pubDate>Wed, 14 Sep 2016 18:00:14 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[网络技术]]></category>
		<category><![CDATA[Dsniff]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[NFS]]></category>
		<category><![CDATA[RST]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[tcpkill]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1272</guid>
		<description><![CDATA[有些时候在 Linux 系统中使用 NFS 挂载远程共享（使用默认的 TCP 方式）之后，偶尔会因为网络异常出现 NFS 连接出错的问题，这种情况下使用任何 ls 或者 df 等等常用的命令对 NFS 挂载目录进行简单的查看操作都可能卡顿几十秒乃至几分钟的时间。 此时如果在别的 Linux 客户机系统上又是可以正常访问 NFS 共享的，而 NFS 服务器端考虑到有多个客户机正在使用不方便直接重启服务，一般只能等待 NFS 挂载连接恢复正常或者重启客户端系统，这样还是很麻烦的，为此我专门找了个使用 tcpkill 命令重置 NFS 连接的方法分享给大家。 tcpkill 命令属于 Dsniff  [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>有些时候在 Linux 系统中使用 NFS 挂载远程共享（使用默认的 TCP 方式）之后，偶尔会因为网络异常出现 NFS 连接出错的问题，这种情况下使用任何 ls 或者 df 等等常用的命令对 NFS 挂载目录进行简单的查看操作都可能卡顿几十秒乃至几分钟的时间。</p>
<p>此时如果在别的 Linux 客户机系统上又是可以正常访问 NFS 共享的，而 NFS 服务器端考虑到有多个客户机正在使用不方便直接重启服务，一般只能等待 NFS 挂载连接恢复正常或者重启客户端系统，这样还是很麻烦的，为此我专门找了个使用 tcpkill 命令重置 NFS 连接的方法分享给大家。</p>
<p>tcpkill 命令属于 Dsniff 嗅探工具包，Dsniff 本身是一个高级的网络嗅探器，Dsniff 可以将制造的数据包注入到网络，一般 Linux 系统中可以找到对应的 dsniff 软件包进行安装，我测试使用的 CentOS 6.1 64 位系统使用的下面的 RPM 安装包：</p>
<p><a href="http://rpm.repo.onapp.com/ramdisk-hv/centos6/dsniff/">http://rpm.repo.onapp.com/ramdisk-hv/centos6/dsniff/</a></p>
<p>tcpkill 的工作原理是利用 libpcap 库监控符合过滤条件的 TCP 连接并等待，当该 TCP 连接上有数据传输时就会被 tcpkill 感知到，不过作为应用程序的 tcpkill 当然不能直接关闭 TCP 连接，此时就会构造一条 RST 报文发回去直接导致 TCP 连接异常关闭（其实 TCP RST 就是 GFW 对敏感网页进行屏蔽的老手段之一哈）。</p>
<p>对于 NFS 共享使用的 TCP 连接而言，通过 tcpkill 发送 RST 报文导致连接异常关闭之后，NFS 客户端就能知道原有的连接不可用，会自动重新开启新的 TCP 连接，这样就能达到我们需要的重置 NFS 连接的效果。</p>
<p>下面具体实验看看，首先在 NFS 服务器上使用 <code>netstat</code> 命令确认所有 TCP 方式挂载的 NFS 连接，这里使用 NFS 服务的 <code>2049</code> 端口进行过滤：</p>
<pre class="brush: bash; title: ; notranslate">
~ # netstat -anp 2&gt;/dev/null | grep 2049
tcp        0      0 0.0.0.0:2049            0.0.0.0:*               LISTEN      -
tcp        0      0 192.168.1.56:2049       192.168.1.52:953        ESTABLISHED -
tcp        0      0 192.168.1.56:2049       192.168.1.129:824       ESTABLISHED -
udp        0      0 0.0.0.0:2049            0.0.0.0:*                           -
</pre>
<p>可以看到 NFS 服务器上有 192.168.1.52 和 192.168.1.129 这两个客户端正在连接，看看 tcpkill 命令的用法：</p>
<pre class="brush: bash; title: ; notranslate">
~ # tcpkill [-i interface] [-1...9] expression
</pre>
<p>tcpkill 的 <code>-i</code> 参数指定绑定哪个网卡，<code>expression</code> 就是 tcpdump 的过滤表达式参数，具体如何使用可以参考 tcpdump 的帮助信息：</p>
<p><a href="http://www.tcpdump.org/tcpdump_man.html">http://www.tcpdump.org/tcpdump_man.html</a></p>
<p>我们就以关闭上面的 192.168.1.52 客户端使用的 NFS 连接为例：</p>
<pre class="brush: bash; title: ; notranslate">
~ # tcpkill -i eth0 host 192.168.1.52 and port 2049
tcpkill: listening on eth0 [host 192.168.1.52 and port 2049]
</pre>
<p>tcpkill 命令启动之后会一直等待新的数据传输，我们就可以在 NFS 客户端上随便运行 ls 或者 df 等命令查看 NFS 共享文件夹就可以让系统发送新的 NFS 数据（仍然是有问题的 NFS 连接所以可能会卡住），此时服务器端的 tcpkill 命令就有反应了：</p>
<pre class="brush: bash; title: ; notranslate">
~ # tcpkill -i eth0 host 192.168.1.52 and port 2049
tcpkill: listening on eth0 [host 192.168.1.52 and port 2049]
192.168.1.52:953 &gt; 192.168.1.56:2049: R 1715581713:1715581713(0) win 0
192.168.1.52:953 &gt; 192.168.1.56:2049: R 1715581896:1715581896(0) win 0
192.168.1.52:953 &gt; 192.168.1.56:2049: R 1715582262:1715582262(0) win 0
192.168.1.56:2049 &gt; 192.168.1.52:953: R 624540755:624540755(0) win 0
192.168.1.56:2049 &gt; 192.168.1.52:953: R 624540919:624540919(0) win 0
192.168.1.56:2049 &gt; 192.168.1.52:953: R 624541247:624541247(0) win 0
192.168.1.52:953 &gt; 192.168.1.56:2049: R 1715581801:1715581801(0) win 0
192.168.1.52:953 &gt; 192.168.1.56:2049: R 1715581984:1715581984(0) win 0
192.168.1.52:953 &gt; 192.168.1.56:2049: R 1715582350:1715582350(0) win 0
</pre>
<p>可以看到 tcpkill 已经发送了 RST 包导致 NFS 客户端重新连接，如果客户端的 NFS 共享操作已经恢复正常了，就可以退出 tcpkill 命令了。</p>
<p>从上面的例子我们也会发现 tcpkill 默认是只能“关闭”活跃有数据传输的 TCP 连接的；对于非活跃的连接我们可能需要主动发送 SYN 包，并根据返回的 TCP 序列号构造新的 RST 包再次发送以达到关闭连接的效果，看起来已经有网友实现了一个支持关闭非活跃连接的 tcpkill 程序，有兴趣的朋友也可以关注看看哦。最后祝大家中秋佳节玩的开心 ^_^。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/tcpkill-nfs/feed/</wfw:commentRss>
		<slash:comments>0</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>
		<item>
		<title>Linux下USB 3.0移动硬盘读写错误问题分析</title>
		<link>https://zohead.com/archives/linux-usbhdd-err/</link>
		<comments>https://zohead.com/archives/linux-usbhdd-err/#comments</comments>
		<pubDate>Tue, 08 Mar 2016 19:09:23 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[barrier]]></category>
		<category><![CDATA[Chromebook]]></category>
		<category><![CDATA[ext4]]></category>
		<category><![CDATA[FUA]]></category>
		<category><![CDATA[JMicron]]></category>
		<category><![CDATA[journal]]></category>
		<category><![CDATA[SCSI]]></category>
		<category><![CDATA[USB]]></category>
		<category><![CDATA[移动硬盘]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1188</guid>
		<description><![CDATA[为了解决 Chromebook 上自带 SSD 空间不足的问题，之前我在淘宝上购入了一个绿帆 F200 USB 3.0 移动硬盘盒，该硬盘盒使用的是 JMicron JMS567 这款使用还比较广泛的 SATA 6.0Gbps to USB 3.0 桥接芯片，准备配上 N 年前的神船笔记本淘汰下来的 2.5 寸硬盘给 Chromebook 使用，这样我就可以在 Chromebook 上安装的 Crouton Ubuntu 系统里无碍的使用各种编译开发环境了。 移动硬盘问题说明 首先我在宏碁 W700 Windows 10 平板上接上此移动硬盘建了一个 NTFS 分区并做各种读写拷贝大文件之类的 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>为了解决 Chromebook 上自带 SSD 空间不足的问题，之前我在淘宝上购入了一个绿帆 F200 USB 3.0 移动硬盘盒，该硬盘盒使用的是 JMicron JMS567 这款使用还比较广泛的 SATA 6.0Gbps to USB 3.0 桥接芯片，准备配上 N 年前的神船笔记本淘汰下来的 2.5 寸硬盘给 Chromebook 使用，这样我就可以在 Chromebook 上安装的 Crouton Ubuntu 系统里无碍的使用各种编译开发环境了。</p>
<h2 id="移动硬盘问题说明">移动硬盘问题说明</h2>
<p>首先我在宏碁 W700 Windows 10 平板上接上此移动硬盘建了一个 NTFS 分区并做各种读写拷贝大文件之类的测试没有任何问题；但是我把移动硬盘换到 Chromebook 上之后发现移动硬盘上新建的 ext4 分区读写会报错，无法做任何拷贝文件操作，先看看 Linux kernel 看到的 USB 3.0 移动硬盘的信息：</p>
<pre class="brush: bash; title: ; notranslate">
[ 9934.612465] usb 2-1: new SuperSpeed USB device number 2 using xhci_hcd
[ 9934.624741] usb 2-1: New USB device found, idVendor=152d, idProduct=0567
[ 9934.624768] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 9934.624787] usb 2-1: Product: lvfan USB3.0 storage
[ 9934.624802] usb 2-1: Manufacturer: JMicron
[ 9934.624817] usb 2-1: SerialNumber: 3224043AA81395
[ 9934.626473] scsi2 : usb-storage 2-1:1.0
[ 9935.627536] scsi 2:0:0:0: Direct-Access     lvfan                     0117 PQ: 0 ANSI: 6
[ 9935.629174] sd 2:0:0:0: [sdb] Spinning up disk...
[ 9936.629940] ...ready
[ 9938.634171] sd 2:0:0:0: [sdb] 312581808 512-byte logical blocks: (160 GB/149 GiB)
[ 9938.634552] sd 2:0:0:0: [sdb] Write Protect is off
[ 9938.634580] sd 2:0:0:0: [sdb] Mode Sense: 47 00 10 08
[ 9938.634955] sd 2:0:0:0: [sdb] Write cache: enabled, read cache: enabled, supports DPO and FUA
[ 9938.659685]  sdb: sdb1 sdb2
[ 9938.661548] sd 2:0:0:0: [sdb] Attached SCSI disk
</pre>
<p>这个是拷贝文件出错时候的内核日志：</p>
<pre class="brush: bash; title: ; notranslate">
[ 9910.412002] EXT4-fs (sdb2): mounted filesystem with ordered data mode. Opts: (null)
[ 9918.500515] sd 2:0:0:0: [sdb] Invalid command failure
[ 9918.500530] sd 2:0:0:0: [sdb]  
[ 9918.500537] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
[ 9918.500547] sd 2:0:0:0: [sdb]  
[ 9918.500554] Sense Key : Illegal Request [current] 
[ 9918.500570] sd 2:0:0:0: [sdb]  
[ 9918.500577] Add. Sense: Invalid field in cdb
[ 9918.500589] sd 2:0:0:0: [sdb] CDB: 
[ 9918.500596] Write(10): 2a 08 04 58 18 30 00 00 08 00
[ 9918.500641] end_request: critical target error, dev sdb, sector 72882224
[ 9918.500654] end_request: critical target error, dev sdb, sector 72882224
[ 9918.500695] Aborting journal on device sdb2-8.
[ 9922.109415] EXT4-fs error (device sdb2): ext4_journal_start_sb:349: Detected aborted journal
[ 9922.109439] EXT4-fs (sdb2): Remounting filesystem read-only
</pre>
<p>看起来是报某个扇区写操作出错，那么我先在 Chromebook 上用 dd 命令直接写对应报错的扇区：</p>
<pre class="brush: bash; title: ; notranslate">
chronos@localhost ~ $ sudo dd if=/dev/zero of=/dev/sdb bs=512 count=10 seek=72882224
10+0 records in
10+0 records out
5120 bytes (5.1 kB) copied, 0.0185829 s, 276 kB/s
</pre>
<p>可以看到写之前报错的扇区其实是没有问题的，而且我使用的 SATA 笔记本硬盘之前经过 mhdd 扫描测试没有发现坏道之类的。</p>
<p>无奈我删掉这个 ext4 分区，重新创建了 ext3 分区并格式化拷贝文件但还是存在类似的错误，最后更换为 ext2 或者 FAT32 文件系统才能正常写入文件，这个就非常奇怪了，接着我把移动硬盘换到 Remix OS PC 版 4.0.9 kernel 下挂载 ext4 读写还是有一样的问题，这样我们就必须要具体分析上面的报错信息了。</p>
<h2 id="读写错误问题分析">读写错误问题分析</h2>
<p>从上面的 kernel 日志中可以看到报错的 SCSI CDB 是：<code>2a 08 04 58 18 30 00 00 08 00</code>，而 0x2A 是 SCSI WRITE 10 命令，参考 SCSI 协议可以知道其命令格式为：</p>
<p><img src="http://res.cloudinary.com/digwht2y0/image/upload/v1737442940/scsi-rw-cmd.jpg" alt="SCSI WRITE 10命令" title="SCSI WRITE 10命令"></p>
<p>我们可以从上图知道 <code>04 58 18 30</code> 是写操作的实际扇区地址（也就是上面报错的 72882224 扇区），<code>08</code> 是写命令的标志位表示这个写命令启用了 <code>FUA</code>。</p>
<p>这里我对 <code>FUA</code>（Force Unit Access）做个简单的介绍：</p>
<ul>
<li>
<p>由于在 Linux 操作系统中从上层到下层多个地方都存在着缓存：page cache（vfs cache）-&gt; HBA/RAID卡自带的 cache -&gt; 磁盘自身的缓存，这样文件系统在写日志等关键的操作时就需要保证写入的数据被真实写到磁盘等物理介质中，而不是存留在各层 cache 里，这样可以防止系统掉电等情况导致数据不一致。</p>
</li>
<li>
<p>Linux kernel 中比较新的文件系统像 ext4、xfs、reiserfs 等则引入了 barrier 特性，遇到带 barrier 标志的写请求（或者 fsync 刷新请求）的时候必须保证之前的所有请求都已经写入到物理介质才能继续。老的 SCSI 或 SAS 磁盘支持通过 <code>SYNCHRONIZE CACHE</code> 命令刷新缓存数据，只不过这样会有额外的影响；新的 kernel 中对于支持 <code>FUA</code> 的设备则是通过发送带 <code>FUA</code> 标志的写请求来实现。</p>
</li>
<li>
<p>SCSI CDB 中的 <code>FUA</code> 标志位如果被置上则表示该请求必须通过访问物理介质实现，如果是带 <code>FUA</code> 的写命令就表示写到物理介质命令才算完成，带 <code>FUA</code> 的读命令则表示直接从物理介质读取绕过缓存。</p>
</li>
</ul>
<p>这样分析下来基本就可以推断出原因了：</p>
<p>由于 ext4、ext3 等文件系统在 kernel 中是默认启用和日志（journal）和 barrier 的，拷贝文件时需要更新文件系统日志，从最上面 kernel 报上来的移动硬盘信息显示是支持 <code>FUA</code> 的，如此日志的写入就是通过发送带 <code>FUA</code> 的写命令实现，然而我的 USB 3.0 移动硬盘响应带 <code>FUA</code> 标志的写命令出现问题导致 ext4、ext3 文件系统变为只读状态无法写入；而 ext2 和 FAT32 文件系统则是完全不带日志的，这样拷贝文件反而就没有问题。</p>
<blockquote>
<p><strong>提示</strong></p>
<p>有关我的移动硬盘使用的 JMicron JMS567 桥接芯片的详细信息请参考 <a href="http://www.jmicron.com/PDF/brief/jms567.pdf">JMicron 技术文档</a>。</p>
</blockquote>
<h2 id="解决方案">解决方案</h2>
<p>如果要顺利使用 ext4 分区，解决方案也有几种，一般的用户建议参考第二种解决方案，下面简单说明下：</p>
<h3 id="禁用-ext4-文件系统日志">禁用 ext4 文件系统日志</h3>
<p>这是比较简单粗暴的解决方案，ext4 文件系统相对 ext3 其中一个改进就是可以手工关闭日志，先卸载已挂载的 ext4 文件系统，然后运行下面的命令：</p>
<pre class="brush: bash; title: ; notranslate">
sudo tune2fs -O ^has_journal /dev/sdb2
</pre>
<p>这样就可以直接关闭 ext4 文件系统默认启用的日志功能，日志被禁用之后基本就类似 ext2 不需要专门发送带 <code>FUA</code> 的写请求了；当然这里我不建议这样做，因为日志功能对于移动硬盘这种热插拔频繁的设备还是相当有用的，可以减少文件出错或丢失的可能。</p>
<h3 id="关闭-ext4-文件系统-barrier">关闭 ext4 文件系统 barrier</h3>
<p>对于不愿意修改或者编译 Linux kernel 的用户这是比较好的解决方案咯，只是关闭 ext4 文件系统 barrier 特性可以保留最有用的日志功能，虽然相比默认启用 barrier 的情况仍然会有一点导致数据不一致的可能，但好处是修改之后就算把移动硬盘接到其它 Linux 机器上基本也能起作用，这样就比较划算了，具体运行下面的命令：</p>
<pre class="brush: bash; title: ; notranslate">
sudo tune2fs -o nobarrier /dev/sdb2
</pre>
<p>然后重新挂载 ext4 分区，一般的 Linux 系统都会在挂载该文件系统时自动禁用 barrier，Chromebook 上自带文件管理器的自动挂载磁盘功能实测有效。</p>
<h3 id="修改-kernel-禁用移动硬盘-fua">修改 kernel 禁用移动硬盘 FUA</h3>
<p>这种解决方案比较适合对编译 kernel 比较熟悉而且相对追求完美（^_^）的用户，不过相对的是需要依赖使用的 Linux 主机 kernel。</p>
<p>由于只禁用 ext4 文件系统的 barrier 还不能完全保证不会有什么别的地方需要用到带 <code>FUA</code> 的读写命令，如果能够修改 kernel 直接让我的这款移动硬盘设备报到系统中时就显示为不支持 <code>FUA</code>，这样就算遇到例如使用 barrier 等场合也可以通过 <code>SYNCHRONIZE CACHE</code> 命令来解决。</p>
<p>Linux kernel USB mass storage 驱动中内置了一个 unusual USB 设备列表，里面包含各种用户已经发现的有问题的 USB 设备及对应处理标志，该列表由 kernel 源代码中的 <code>drivers/usb/storage/unusual_devs.h</code> 文件负责维护。</p>
<p>我们可以先检查下 Chromebook 当前使用的 3.8.11 版本内核和 Remix OS PC 版使用的 4.0.9 版本内核源代码，却都能看到已经有用户报告的我的移动硬盘使用的 JMicron JMS567 芯片的 <code>FUA</code> 问题了：</p>
<pre class="brush: cpp; title: drivers/usb/storage/unusual_devs.h; notranslate">
UNUSUAL_DEV(  0x152d, 0x0567, 0x0114, 0x0114,
		&quot;JMicron&quot;,
		&quot;USB to ATA/ATAPI Bridge&quot;,
		USB_SC_DEVICE, USB_PR_DEVICE, NULL,
		US_FL_BROKEN_FUA ),
</pre>
<p>这个稍微有点奇怪，kernel 碰到带 <code>US_FL_BROKEN_FUA</code> 标志的 USB 设备会自动禁用 <code>FUA</code>，按说报上来的移动硬盘设备应该显示为不支持 <code>FUA</code> 的；不过稍微看下就发现上面的 <code>UNUSUAL_DEV</code> 条目限制了有问题的 USB 设备的 BCD 码固定为 <code>0x0114</code>，我们可以通过 <code>lsusb -v</code> 命令确认下移动硬盘设备的详细 USB 信息：</p>
<pre class="brush: bash; title: ; notranslate">
Bus 002 Device 004: ID 152d:0567 JMicron Technology Corp. / JMicron USA Technology Corp. 
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               3.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         9
  idVendor           0x152d JMicron Technology Corp. / JMicron USA Technology Corp.
  idProduct          0x0567 
  bcdDevice            1.17
</pre>
<p>可以看到移动硬盘的 USB 设备 BCD 码是 1.17 也就是 <code>0x0117</code>，这样 kernel 就不会认为我这款绿帆移动硬盘是有问题的 USB 设备进而做特殊处理了。</p>
<p>同样我们可以看看最新的 Linux 4.5-rc7 版本 kernel 中异常 USB 设备列表中的 JMS567 条目：</p>
<pre class="brush: cpp; title: drivers/usb/storage/unusual_devs.h; notranslate">
UNUSUAL_DEV(  0x152d, 0x0567, 0x0114, 0x0116,
		&quot;JMicron&quot;,
		&quot;USB to ATA/ATAPI Bridge&quot;,
		USB_SC_DEVICE, USB_PR_DEVICE, NULL,
		US_FL_BROKEN_FUA ),
</pre>
<p>稍微有点无奈最新的 Linux kernel 中的 BCD 码范围是从 0x0114 到 0x0116，还是不包含我这款移动硬盘。</p>
<p>解决步骤也就比较简单了，检出对应的内核源代码，修改 <code>drivers/usb/storage/unusual_devs.h</code> 文件可以把 JMS567 USB 设备的 BCD 码范围改为 0x0114 到 0x0117，也可以彻底点直接改成 0x0000 到 0x9999。</p>
<p>unusual_devs.h 文件修改完成后替换 usb-storage 模块并重新启动，顺利的话就可以看到报上来的 SCSI 磁盘设备显示为：</p>
<pre class="brush: bash; title: ; notranslate">
[ 7205.597952] sd 2:0:0:0: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
</pre>
<blockquote>
<p><strong>提示</strong></p>
<p>由于 Chromebook 和 Remix OS 中的 usb-storage 驱动都是直接集成在 kernel 中的，无法只编译 usb-storage 模块进行简单替换，因此需要完整编译出 kernel bzImage 直接替换。</p>
</blockquote>
<p><br/>
<p>这样 ext4 文件系统的日志还有 barrier 特性就能愉快的继续使用了，祝各位玩的开心～～～。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/linux-usbhdd-err/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>解决Linux下使用davfs2挂载坚果云的问题</title>
		<link>https://zohead.com/archives/davfs2-nutstore/</link>
		<comments>https://zohead.com/archives/davfs2-nutstore/#comments</comments>
		<pubDate>Wed, 28 Oct 2015 16:53:51 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[davfs2]]></category>
		<category><![CDATA[WebDAV]]></category>
		<category><![CDATA[云存储]]></category>
		<category><![CDATA[坚果云]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=1068</guid>
		<description><![CDATA[坚果云算是现在国内云存储提供商里比较另类的一个了，没有提供巨大的容量空间，也没有非常多的人使用他的分享功能，但好在基本功能非常全面，桌面同步客户端做的比较清爽，各种类型的客户端基本都能支持，特别像多个用户协作修改文件的同步、文件版本历史这些我比较看重的功能做的还不错。坚果云也有一定的付费用户，稍微坑一点的就是坚果云免费用户的初始容量只有 1GB，之后每个月免费用户可以再上传 1GB 的数据（免费用户同时限制每个月下行流量 3GB）。 坚果云虽然没有提供 API 功能，但好在其算是国内唯一提供 WebDAV 方式访问的网盘（国外的类似网盘也比较少，免费的像 Box.com 就算一个了），这样基本 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><span style="line-height: 1.5em;">坚果云算是现在国内</span>云存储提供商里比较另类的一个了，没有提供巨大的容量空间，也没有非常多的人使用他的分享功能，但好在基本功能非常全面，桌面同步客户端做的比较清爽，各种类型的客户端基本都能支持，特别像多个用户协作修改文件的同步、文件版本历史这些我比较看重的功能做的还不错。坚果云也有一定的付费用户，稍微坑一点的就是坚果云免费用户的初始容量只有 1GB，之后每个月免费用户可以再上传 1GB 的数据（免费用户同时限制每个月下行流量 3GB）。</p>
<p>坚果云虽然没有提供 API 功能，但好在其算是国内唯一提供 WebDAV 方式访问的网盘（国外的类似网盘也比较少，免费的像 Box.com 就算一个了），这样基本就可以在各种不同类型的客户端中不依靠其同步客户端就能读写云中的数据。之前我在 Windows 上使用自带的资源管理器以 WebDAV 方式访问坚果云看起来没什么问题，但到 Linux 下使用最流行的 davfs2 软件挂载坚果云 WebDAV 的时候却直接报错无法访问（WebDAV 功能需要在坚果云账户设置中的 “第三方应用管理” 里开启）。</p>
<p>Linux 终端下 davfs2 挂载坚果云的报错信息如下：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)root@localhost:~# mount -t 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@qq.com
Please enter the password to authenticate user xxx@qq.com with server
https://dav.jianguoyun.com/dav or hit enter for none.
  Password:
/sbin/mount.davfs: mounting failed; the server does not support WebDAV
</pre>
<p>直接提示服务器不支持 WebDAV 这就比较奇怪的，在网上没找到解决办法，因此决定直接下载 davfs2 源代码进行分析，davfs2 现在的项目主页已从 SourceForge 迁移到：</p>
<p><a href="http://savannah.nongnu.org/projects/davfs2" target="_blank">http://savannah.nongnu.org/projects/davfs2</a></p>
<p>使用 cvs 检出源代码之后（还用这么古老的版本管理软件值得吐槽哈），需要先安装 WebDAV 支持库 libneon 才能正常编译。</p>
<p>很快就能找到出错的地方 webdav.c：</p>
<pre class="brush: cpp; title: davfs2/src/webdav.c; notranslate">
int
dav_init_connection(const char *path)
{
    char *spath = ne_path_escape(path);
    ne_server_capabilities caps = {0, 0, 0};
    int ret = ne_options(session, spath, &amp;caps);

    if (!ret) {
        initialized = 1;
        if (!caps.dav_class1 &amp;&amp; !ignore_dav_header) {
            if (have_terminal) {
                error(EXIT_FAILURE, 0,
                      _(&quot;mounting failed; the server does not support WebDAV&quot;));
            } else {
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR),
                       _(&quot;mounting failed; the server does not support WebDAV&quot;));
                ret = EINVAL;
            }
        }
</pre>
<p>简单分析上面的源代码可知 mount.davfs 命令在挂载时和 WebDAV 服务器建立连接，并通过 libneon 库的 ne_options 函数发送 HTTP OPTIONS 请求获取 WebDAV 服务器能力，虽然返回成功但判断坚果云 WebDAV 服务器不支持 Class 1 WebDAV，因此直接报错挂载失败。</p>
<p>看到这里解决办法也就简单了，davfs2 提供了通过配置文件禁用 WebDAV 头检测的功能，直接修改 <strong>/etc/davfs2/davfs2.conf</strong> 配置文件注释并改为：</p>
<p><strong>ignore_dav_header 1</strong></p>
<p>然后再重新使用 mount 或者 mount.davfs 命令挂载坚果云就可以成功了，后续拷贝文件之类的看起来也算正常，不过运行 df 命令看到 WebDAV 返回的容量信息还是不对：</p>
<pre class="brush: bash; title: ; notranslate">
(trusty)zzm@localhost:~$ df -h /mnt
文件系统                        容量  已用  可用 已用% 挂载点
https://dav.jianguoyun.com/dav   26G   13G   13G   50% /mnt
</pre>
<p>不过这个就不用计较咯，祝玩的开心～～～</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/davfs2-nutstore/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>在Koding云平台上编译Chromebook kernel</title>
		<link>https://zohead.com/archives/koding-chromebook/</link>
		<comments>https://zohead.com/archives/koding-chromebook/#comments</comments>
		<pubDate>Sat, 21 Mar 2015 16:39:08 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[binfmt_misc]]></category>
		<category><![CDATA[Chrome OS]]></category>
		<category><![CDATA[Chromebook]]></category>
		<category><![CDATA[cifs]]></category>
		<category><![CDATA[Koding]]></category>
		<category><![CDATA[Secure Shell]]></category>
		<category><![CDATA[内核模块]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=880</guid>
		<description><![CDATA[最近从淘宝收了一台三星的 Series 3 ARM Chromebook，平时当作上网本用起来还是可以的，可以运行一些 Chrome packaged 原生 App，Chrome OS 的运行速度也还凑合，主要电池续航比一般的笔记本要给力多了，这篇博文的编辑及实际 kernel 编译操作我都是在 Chromebook 上完成的。 由于 Chromebook 底层使用的还是 Linux 内核，装了 Crouton 插件之后可以在 Chromebook 里以 chroot 的方式跑另外的 Ubuntu 系统，只不过 Chromebook 自带的内核模块有些缺失，比如我需要的 binfmt_misc [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>最近从淘宝收了一台三星的 Series 3 ARM Chromebook，平时当作上网本用起来还是可以的，可以运行一些 Chrome packaged 原生 App，Chrome OS 的运行速度也还凑合，主要电池续航比一般的笔记本要给力多了，这篇博文的编辑及实际 kernel 编译操作我都是在 Chromebook 上完成的。</p>
<p>由于 Chromebook 底层使用的还是 Linux 内核，装了 Crouton 插件之后可以在 Chromebook 里以 chroot 的方式跑另外的 Ubuntu 系统，只不过 Chromebook 自带的内核模块有些缺失，比如我需要的 binfmt_misc 和 cifs 模块都是没有的，特别是很有用的 cifs 模块用于挂载 Windows 机器上的共享目录。因此想自己编译 Chromebook 的 kernel 加入需要的内核模块。</p>
<p>三星 Series 3 ARM Chromebook 自带的 16GB SSD 毕竟有点小，ARM 系统里装一堆开发环境还是嫌麻烦，还有比较重要的一点是国内的网络环境 git clone 出 Google Source 上的 kernel 源代码速度实在是太慢了，于是就想着用之前注册的 Koding 云端开发平台上跑交叉编译器来编译 Chromebook 的 kernel。</p>
<p><a href="http://www.koding.com/R/zohead" target="_blank">Koding</a> 云端开发平台默认自带了 Node.js、Python、Ruby、Go 等开发环境支持，Koding 默认给了一个很强大的 Web IDE 编辑工具，可以在 Web 上直接登录 Shell 操作，而且 Koding 提供的 Linux 虚拟环境默认就安装了 gcc、Makefile 等工具，其免费套餐提供的 1GB RAM、3GB 硬盘空间用来编译 kernel 也勉强够用了。</p>
<p>先在 Chromebook 里按 Ctrl +Alt + T 键切换到 crosh Shell 里确认当前 kernel 版本：</p>
<pre class="brush: bash; title: ; notranslate">
chronos@localhost / $ cat /proc/version
Linux version 3.8.11 (chrome-bot@build44-m2) (gcc version 4.9.x-google 20140827 (prerelease) (4.9.1_cos_gg_2f9796c_4.9.1-r82) ) #1 SMP Sat Mar 7 06:47:35 PST 2015
</pre>
<p>可以看到现在用的是最新 Linux 3.8.11 版本，从 Google 官方提供的 kernel 版本库里确认需要使用的 kernel 分支：</p>
<p><a href="https://chromium.googlesource.com/chromiumos/third_party/kernel/" target="_blank">https://chromium.googlesource.com/chromiumos/third_party/kernel/</a></p>
<p>我需要的 3.8.11 版本就可以直接使用 chromeos-3.8 分支，现在可以 SSH 远程登录到 Koding Linux 系统上，幸运的是我们还可以在 Chromebook 安装 <a href="https://chrome.google.com/webstore/detail/secure-shell/pnhechapfaindjhompbnflcldabbghjo" target="_blank">Secure Shell</a> 这一牛叉 App 直接进行 SSH 登录，Secure Shell 支持 Koding 的 SSH 证书（需要自己登录到 Koding 的 Web Shell 里生成哈）形式，不过选择证书时需要注意把公钥和私钥文件同时选上。</p>
<p>Koding 云端开发平台默认使用的是 Ubuntu 14.04 LTS 64 位系统，这样也比较好，ARM 交叉编译器安装起来也很方便了，先 clone 出 kernel 源代码：</p>
<pre class="brush: bash; title: ; notranslate">
git clone https://chromium.googlesource.com/chromiumos/third_party/kernel -b chromeos-3.8
</pre>
<p>然后运行 <strong>sudo apt-get install gcc-arm-linux-gnueabi</strong> 安装交叉编译器，Ubuntu 提供的 ARM gcc 编译器是 4.7.2 版本。</p>
<p>接着需要把 Chromebook 当前的 kernel 配置导出来，在 Chromebook 上切换到 Shell，运行 <strong>sudo modprobe configs</strong> 命令，然后拷贝 <strong>/proc/config.gz</strong> 文件并解压缩，通过 scp 命令将解压缩出来的 <strong>config</strong> 文件拷贝到 Koding Linux 系统中。</p>
<p>在 Koding Linux 系统中进入刚才 clone 出来的 Linux kernel 源代码目录，将 Chromebook 上拷贝过来的 <strong>config</strong> 文件重命名为 <strong>.config</strong>，然后运行下面的命令进行 kernel 配置：</p>
<pre class="brush: bash; title: ; notranslate">
cd ~/kernel
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
</pre>
<p>我们可以在 Network Filesystem 中启用 cifs 模块，同时可以启用 binfmt_misc 模块，保存 kernel 配置，运行下面的命令直接开始内核编译：</p>
<pre class="brush: bash; title: ; notranslate">
cd ~/kernel
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
</pre>
<p>如果你不想编译完整的 Chromebook kernel，也可以使用这两个命令只编译需要的 binfmt_misc 和 cifs 模块，这样可以节省时间：</p>
<pre class="brush: bash; title: ; notranslate">
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- M=fs/cifs
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- fs/binfmt_misc.ko
</pre>
<p>需要注意的是这样编译出来的 cifs.ko 模块文件还是依赖 md4.ko 文件（会自动编译出来）的。</p>
<p>下面就是通过 scp 把编译出来的 cifs.ko、md4.ko、binfmt_misc.ko 文件拷贝到 Chromebook 系统中，我们可以将这 3 个文件放到 Linux 内核模块目录中（不过这一步需要在 Chromebook 的开发模式中禁用 Chrome OS 的 rootfs 分区锁定功能）：</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /lib/modules/3.8.11/kernel/fs/cifs
mv cifs.ko /lib/modules/3.8.11/kernel/fs/cifs
mv binfmt_misc.ko /lib/modules/3.8.11/kernel/fs
mv md4.ko /lib/modules/3.8.11/kernel/crypto
</pre>
<p>然后可以运行 sudo depmod -a 命令让系统自动生成内核模块依赖关系。</p>
<p>需要注意的是如果你把新编译出来的 ko 模块文件放在不是 rootfs 分区里（未禁用 Chrome OS 的 rootfs 分区保护等情况），这样 insmod binfmt_misc.ko 等命令加载模块会报错失败：</p>
<pre class="brush: plain; title: ; notranslate">
Chromium OS LSM: init_module denied module=&quot;/home/chronos/user/Downloads/binfmt_misc.ko&quot; pid=12499 cmdline=&quot;insmod binfmt_misc.ko&quot;
</pre>
<p>这是由于 Chrome OS 默认做了限制即不允许加载放在 rootfs 分区以外的文件系统上的内核模块文件，我们也可以运行下面的命令解除这个限制：</p>
<pre class="brush: bash; title: ; notranslate">
echo 0 &gt; /proc/sys/kernel/chromiumos/module_locking
</pre>
<p>当然如果你将模块拷贝到上面提到的标准内核模块目录中的话就不会有这个问题了。</p>
<p>经过测试这样的方法编译的模块使用起来还是没有问题的，当然还是有一个不爽之处就是如果 Chrome OS 升级之后内核版本也升级了，但这些模块也需要重新编译了，文中有任何问题还是欢迎提出指正哦，祝玩的开心 ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/koding-chromebook/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux kernel DM map_info接口改为per-bio data</title>
		<link>https://zohead.com/archives/dm-mapinfo-perbio/</link>
		<comments>https://zohead.com/archives/dm-mapinfo-perbio/#comments</comments>
		<pubDate>Wed, 11 Mar 2015 15:05:46 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Device mapper]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[map_context]]></category>
		<category><![CDATA[map_info]]></category>
		<category><![CDATA[per-bio data]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=870</guid>
		<description><![CDATA[最近在将一个之前版本的 Linux DM（device mapper）驱动移植到新的 3.18 版本 Linux kernel 上出现编译报错，提示 DM target_type 中的 dm_map_fn 等成员函数指针类型不正确。 看看老版本上 dm_map_fn 函数指针的声明： typedef int (*dm_map_fn) (struct dm_target *ti, struct bio *bio, union map_info *map_context); 这个是新的 3.18 版本 Linux 上的形式： typedef int (*dm_map_fn) (struct dm_ [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>最近在将一个之前版本的 Linux DM（device mapper）驱动移植到新的 3.18 版本 Linux kernel 上出现编译报错，提示 DM target_type 中的 dm_map_fn 等成员函数指针类型不正确。</p>
<p>看看老版本上 dm_map_fn 函数指针的声明：</p>
<p><strong>typedef int (*dm_map_fn) (struct dm_target *ti, struct bio *bio, union map_info *map_context);</strong></p>
<p>这个是新的 3.18 版本 Linux 上的形式：</p>
<p><strong>typedef int (*dm_map_fn) (struct dm_target *ti, struct bio *bio);</strong></p>
<p>其它几个报错的函数指针也比较类似，看起来明显就是新 kernel 中去掉了老的 map_info 类型参数。</p>
<p>有些类型 device mapper target（例如：multipath、snapshot 等驱动）需要在 dm_map_fn 处理函数中分配与 bio 相关的辅助数据并在 dm_endio_fn 处理函数中使用，老版本 Linux 中的标准处理流程就是在 dm_map_fn 中按需分配数据，并赋值给 map_info 类型的 map_context 的 ptr 指针，在 dm_endio_fn 中则需要判断 map_context 的 ptr 指针是否非空，并做对应的使用和释放操作。</p>
<p>先上个老版本 kernel 里的代码，以 dm-mirror 驱动为例；</p>
<pre class="brush: cpp; title: dm-raid1.c; notranslate">
static int mirror_map(struct dm_target *ti, struct bio *bio, union map_info *map_context)
{
	struct mirror_set *ms = (struct mirror_set *) ti-&gt;private;
	struct dm_raid1_read_record *read_record = NULL;

	...
	read_record = mempool_alloc(ms-&gt;read_record_pool, GFP_NOIO);
	if (likely(read_record)) {
		dm_bio_record(&amp;read_record-&gt;details, bio);
		map_context-&gt;ptr = read_record;
		read_record-&gt;m = m;
	}
	...
}

static int mirror_end_io(struct dm_target *ti, struct bio *bio, int error, union map_info *map_context)
{
	struct mirror_set *ms = (struct mirror_set *) ti-&gt;private;
	struct dm_raid1_read_record *read_record = map_context-&gt;ptr;

	...
	if (read_record) {
		mempool_free(read_record, ms-&gt;read_record_pool);
		map_context-&gt;ptr = NULL;
	}
	...
}
</pre>
<p>新 kernel 里则去掉了在 target 的处理函数里自己分配并保存自定义数据的方式，由于 kernel 中各种不同的子系统都可以可以请求 bio 子系统创建一个包含额外空余空间的 bio slab cache（per-bio data）并放在 bio 之前，这个空余空间就可以被其它驱动由于各种用途。</p>
<p>DM 中则使用 per-bio data 这一特性存放 target 驱动相关和 device mapper 相关的数据。DM target 驱动可以通过在 dm_ctr_fn 构造处理函数里设置 dm_target 结构体的自定义数据大小<span style="line-height: 19.2000007629395px;">（per-bio data size）</span><span style="line-height: 1.5;">的形式直接将数据保存到 bio 请求中，如果大小为 0 表示不需要额外的数据，如果大于 0 则 DM 会自动给每个 bio 请求加上指定大小的自定义数据，这样 target 驱动也就不需要额外的分配释放操作了。</span></p>
<p><span style="line-height: 1.5;">相对应的 dm_target 的 dm_map_fn 和 dm_endio_fn 处理函数中直接使用 dm_per_bio_data 函数就可以从 bio 里取出自定义数据，不需要自行管理数据也非常方便。</span></p>
<p>这样就可以来看看新版本 kernel 里的 dm-mirror 代码：</p>
<pre class="brush: cpp; title: dm-raid1.c; notranslate">
static int mirror_map(struct dm_target *ti, struct bio *bio)
{
	struct mirror_set *ms = ti-&gt;private;
	struct dm_raid1_bio_record *bio_record =
		dm_per_bio_data(bio, sizeof(struct dm_raid1_bio_record));

	bio_record-&gt;details.bi_bdev = NULL;
	...
	dm_bio_record(&amp;bio_record-&gt;details, bio);
	bio_record-&gt;m = m;
	...
}

static int mirror_end_io(struct dm_target *ti, struct bio *bio, int error)
{
	struct mirror_set *ms = (struct mirror_set *) ti-&gt;private;
	struct dm_raid1_bio_record *bio_record =
		dm_per_bio_data(bio, sizeof(struct dm_raid1_bio_record));

	...
	bio_record-&gt;details.bi_bdev = NULL;
	...
}

static int mirror_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
	...
	ti-&gt;per_bio_data_size = sizeof(struct dm_raid1_bio_record);
	...
}
</pre>
<p>实际使用中还是要注意 per_bio_data_size 最好不要太大。将 device mapper 驱动按 per-bio data 的形式进行修改，就可完成新 kernel 上的移植。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/dm-mapinfo-perbio/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
