-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
1 lines (1 loc) · 110 KB
/
index.json
1
[{"content":"最近公司有个现场设备在使用过程中没有声音输出,单独排查了硬件和软件,发现都没有问题,而且只有应用软件内部播放的声音没有,手动播放其他音频文件有声音,排查了很久终于解决,记录如下。\n现象描述 应用程序使用aplay播放音频,代码如下:\nsystem(\u0026#34;aplay /tmp/audio.wav\u0026#34;); 其中audio.wav已经由tts程序生成;\n应用会在一些情况下无法播放音频,并且通过应用程序调用的aplay进程一直存在,但是在shell手动输入命令aplay /tmp/audio.wav 则可以正常播放;\n使用strace跟踪该aplay进程如下:\nlstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-uuANIccKO515\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-uuANIccKO515\u0026#34;, \u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime.tmp\u0026#34;) = -1 EEXIST (File exists) rmdir(\u0026#34;/tmp/pulse-uuANIccKO515\u0026#34;) = 0 clock_nanosleep_time64(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=13180137686533969536}, 0xbeeae878) = 0 readlink(\u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime\u0026#34;, \u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 99) = 23 lstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-YR6cDgJaYrX3\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-YR6cDgJaYrX3\u0026#34;, \u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime.tmp\u0026#34;) = -1 EEXIST (File exists) rmdir(\u0026#34;/tmp/pulse-YR6cDgJaYrX3\u0026#34;) = 0 clock_nanosleep_time64(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=13180137686533969536}, 0xbeeae878) = 0 readlink(\u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime\u0026#34;, \u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 99) = 23 lstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-VojE9bAtv053\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-VojE9bAtv053\u0026#34;, \u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime.tmp\u0026#34;) = -1 EEXIST (File exists) rmdir(\u0026#34;/tmp/pulse-VojE9bAtv053\u0026#34;) = 0 clock_nanosleep_time64(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=13180137686533969536}, 0xbeeae878) = 0 readlink(\u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime\u0026#34;, \u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 99) = 23 lstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-37BF0qAO8xQz\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-37BF0qAO8xQz\u0026#34;, \u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime.tmp\u0026#34;) = -1 EEXIST (File exists) rmdir(\u0026#34;/tmp/pulse-37BF0qAO8xQz\u0026#34;) = 0 clock_nanosleep_time64(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=13180137686533969536}, 0xbeeae878) = 0 readlink(\u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime\u0026#34;, \u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 99) = 23 lstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-BnzpFkgozqQy\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-BnzpFkgozqQy\u0026#34;, \u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime.tmp\u0026#34;) = -1 EEXIST (File exists) rmdir(\u0026#34;/tmp/pulse-BnzpFkgozqQy\u0026#34;) = 0 可见aplay进程进入了死循环;\n查找pulseaudio源代码core-util.c发现如下:\nchar *pa_get_runtime_dir(void) { \t\tchar *d, *k = NULL, *p = NULL, *t = NULL, *mid; \tmode_t m; \t/* The runtime directory shall contain dynamic data that needs NOT * to be kept across reboots and is usually private to the user, * except in system mode, where it might be accessible by other * users, too. Since we need POSIX locking and UNIX sockets in * this directory, we try XDG_RUNTIME_DIR first, and if that isn\u0026#39;t * set create a directory in $HOME and link it to a random subdir * in /tmp, if it was not explicitly configured. */ \t\tm = pa_in_system_mode() ? 0755U : 0700U; \t\t/* Use the explicitly configured value if it is set */ \td = getenv(\u0026#34;PULSE_RUNTIME_PATH\u0026#34;); \t\tif (d) { \tif (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) \u0026lt; 0) { \tpa_log_error(\u0026#34;Failed to create secure directory (%s): %s\u0026#34;, d, pa_cstrerror(errno)); \tgoto fail; \t} \t\treturn pa_xstrdup(d); \t} ... //这里有其他代码 \tfor (;;) { \t\t/* OK, first let\u0026#39;s check if the \u0026#34;runtime\u0026#34; symlink already exists */ \t\t... //这里有其他代码 \t\t/* Hmm, so the link points to some nonexisting or invalid * dir. Let\u0026#39;s replace it by a new link. We first create a * temporary link and then rename that to allow concurrent * execution of this function. */ \t\t\tt = pa_sprintf_malloc(\u0026#34;%s.tmp\u0026#34;, k); \t\tif (make_random_dir_and_link(0700, t) \u0026lt; 0) { \t\tif (errno != EEXIST) { \tpa_log_error(\u0026#34;Failed to symlink %s: %s\u0026#34;, t, pa_cstrerror(errno)); \tgoto fail; \t} \tpa_xfree(t); \tt = NULL; \t\t/* Hmm, someone else was quicker then us. Let\u0026#39;s give * him some time to finish, and retry. */ \tpa_msleep(10); \tcontinue; \t\t} \t\t} ... //这里有其他代码 } 当make_random_dir_and_link返回错误,并且错误原因为EEXIST是就一直循环,make_random_dir_and_link 实现如下:\nstatic int make_random_dir_and_link(mode_t m, const char *k) { \tchar *p; \tif (!(p = make_random_dir(m))) \treturn -1; #ifdef HAVE_SYMLINK \tif (symlink(p, k) \u0026lt; 0) { \tint saved_errno = errno; \tif (errno != EEXIST) \tpa_log_error(\u0026#34;Failed to symlink %s to %s: %s\u0026#34;, k, p, pa_cstrerror(errno)); \trmdir(p); \tpa_xfree(p); \terrno = saved_errno; \treturn -1; \t} #else \tpa_xfree(p); \treturn -1; #endif \tpa_xfree(p); \treturn 0; } 理一理 pulseaudio 作为Linux下的声音服务器,提供中间层供应用程序调用声卡,输出音频; pulseaudio 使用时会建立runtime目录用于动态数据的交换; pulseaudio 获取runtime目录通过pa_get_runtime_dir函数完成; pulseaudio 会新建xxxx-rumtime.tmp 然后rename 这个目录,去掉.tmp后缀,作为runtime目录; 如果runtime目录中有***-runtime.tmp目录,那么pulseaudio就会认为有其他应用在使用runtime目录,并且一直死循环重试创建,此时会造成死循环无法播放声音; 为什么应用程序调用会死循环而shell调用不会死循环 函数pa_get_runtime_dir前面几行会检测是否有PULSE_RUNTIME_PATH环境变量,很显然shell中有设置此环境变量,但是应用程序没有此环境变量;\nroot@X:~/.config/pulse# echo $PULSE_RUNTIME_PATH /var/run/pulse 为什么会出现.tmp文件 猜测有一种可能是在创建完xxx.tmp目录后还没有rename之前应用退出、设备断电,导致存在此文件;\n复现 可以通过如下方式100%复现这种情况\ncd /home/root/.config/pulse ln -s ebf2404ed9e14dee9acb852691860a81-runtime ebf2404ed9e14dee9acb852691860a81-runtime.tmp rm -rf /tmp/pulse-PKdhtXMmr18n //要看下.config/pulse是否链接的此文件 ./app //运行应用程序,没有声音 解决方式 开机脚本中删除目录rm -rf /home/root/.config/pulse/* system函数调用前设置PULSE_RUNTIME_PATH环境变量 ","permalink":"https://tinyx.me/post/aplay/","summary":"最近公司有个现场设备在使用过程中没有声音输出,单独排查了硬件和软件,发现都没有问题,而且只有应用软件内部播放的声音没有,手动播放其他音频文件有声音,排查了很久终于解决,记录如下。\n现象描述 应用程序使用aplay播放音频,代码如下:\nsystem(\u0026#34;aplay /tmp/audio.wav\u0026#34;); 其中audio.wav已经由tts程序生成;\n应用会在一些情况下无法播放音频,并且通过应用程序调用的aplay进程一直存在,但是在shell手动输入命令aplay /tmp/audio.wav 则可以正常播放;\n使用strace跟踪该aplay进程如下:\nlstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-uuANIccKO515\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-uuANIccKO515\u0026#34;, \u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime.tmp\u0026#34;) = -1 EEXIST (File exists) rmdir(\u0026#34;/tmp/pulse-uuANIccKO515\u0026#34;) = 0 clock_nanosleep_time64(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=13180137686533969536}, 0xbeeae878) = 0 readlink(\u0026#34;/home/root/.config/pulse/e239376a56f6468e8c604babcc95431d-runtime\u0026#34;, \u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 99) = 23 lstat64(\u0026#34;/tmp/pulse-PKdhtXMmr18n\u0026#34;, 0xbeeae8c8) = -1 ENOENT (No such file or directory) umask(077) = 022 mkdir(\u0026#34;/tmp/pulse-YR6cDgJaYrX3\u0026#34;, 0700) = 0 umask(022) = 077 symlink(\u0026#34;/tmp/pulse-YR6cDgJaYrX3\u0026#34;, \u0026#34;/home/root/.","title":"Linux设备使用aplay无法播放声音"},{"content":"最近一段时间买了个 NAS,用了之后感觉确实很不错,基本上就是一个家用的服务器,什么东西都可以往上扔。唯一后悔的就是买便宜了(毕竟穷),DS216Play 使用的是 STM STiH412 的 32位处理器,既然是 32位,所以一个大杀器-Docker 没法用了,也就是很多基于 Docker 的开箱即用第三方软件没法用。还有就是内置的 VideoStation 是从 IMDb 抓取封面信息,而 IMDb 是被墙了的。于是准备安装个 SS(ShadowSocks),顺便把 Dropbox 里面将近 100G 文件同步回来,之前买的 SSLedge 一个月才 30G 流量,要同步三个月才能完全把 Dropbox 上面文件同步回来,自建的 SS 直接是 1T,相当划算。\n安装 ipkg 开始之前需要安装 ipkg 包管理器,这样后面很多软件包就能使用了。因为没有找到适合 STiH412 处理器的安装脚本,所以我们下一个相似的:\n$ wget http://ipkg.nslu2-linux.org/feeds/optware/cs08q1armel/cross/unstable/syno-mvkw-bootstrap_1.2-7_arm.xsh $ chmod 700 syno-mvkw-bootstrap_1.2-7_arm.xsh $ sh syno-mvkw-bootstrap_1.2-7_arm.xsh 运行之后会报错,并生成一个 bootstrap 目录,进去 bootstrap 目录并修改 bootstrap.sh 文件\n #if ! grep Feroceon-KW /proc/cpuinfo \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then # echo \u0026#34;Error: CPU not Marvell Kirkwood, probably wrong bootstrap.xsh\u0026#34; # exit 3 #fi 找到以上文本加 # 注释掉保存退出,然后运行:\n ./bootstrap.sh 接着重启 NAS,重启完成后更新一下 ipkg update 如果无误就说明安装完成。\n安装 SS shadowsocks(python) 第一个想到的是直接用 python 版的 SS,装个 Python 然后 pip 一下就能用了,首先在 NAS 的套件中心里面安装 python2 和 python 3,然后安装 SS:\nwget https://bootstrap.pypa.io/get-pip.py python get-pip.py pip install shadowsocks 如果 wget 有问题可以 sudo ipkg remove wget 再安装 sudo ipkg install wget-ssl 新建或编辑 shadowsocks.json 文件内容如下:\n{ \u0026#34;server\u0026#34;:\u0026#34;my_server_ip\u0026#34;, \u0026#34;server_port\u0026#34;:8388, \u0026#34;local_address\u0026#34;: \u0026#34;127.0.0.1\u0026#34;, \u0026#34;local_port\u0026#34;:1080, \u0026#34;password\u0026#34;:\u0026#34;mypassword\u0026#34;, \u0026#34;timeout\u0026#34;:300, \u0026#34;method\u0026#34;:\u0026#34;aes-256-cfb\u0026#34;, \u0026#34;fast_open\u0026#34;: false } 最后执行命令 sslocal -c shadowsocks.json -d start 运行。 这个时候问题出现了,即使有库文件 /usr/lib/libcrypto.so 运行也会出现错误:\n File \u0026#34;/usr/lib/python2.7/ctypes/util.py\u0026#34;, line 247, in find_library raise RuntimeError(\u0026#34;can not find library %s\u0026#34; % name) RuntimeError: can not find library crypto 找了一圈没找到答案,不知道是不是 SS 的问题。所以果断换 方案二:使用 C 语言编写的 shadowsocks-libev。\nshadowsocks-libev 因为怕搞坏自己的生产环境,所以直接在 VPS 上编译得了,使用的是 64bit Ubuntu 14.03。 先要在 Synology Open Source Project 找到 DS216Play 的交叉编译工具:monaco-gcc493_glibc220_hard-GPL.txz 然后 SSH 连接 VPS 进行编译工作:\napt-get -y install make binutils mkdir ss cd ss wget http://iweb.dl.sourceforge.net/project/dsgpl/DSM%206.0%20Tool%20Chains/STMicroelectronics%20Monaco%20Linux%203.10.77/monaco-gcc493_glibc220_hard-GPL.txz tar xvf monaco-gcc493_glibc220_hard-GPL.txz export PATH=\u0026#34;/root/ss/arm-unknown-linux-gnueabi/bin:$PATH\u0026#34; export CC=/root/ss/arm-unknown-linux-gnueabi/bin/arm-unknown-linux-gnueabi-gcc export LD=/root/ss/arm-unknown-linux-gnueabi/bin/arm-unknown-linux-gnueabi-ld export RANLIB=/root/ss/arm-unknown-linux-gnueabi/bin/arm-unknown-linux-gnueabi-ranlib export CFLAGS=\u0026#34;-I/root/ss/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/include\u0026#34; export LDFLAGS=\u0026#34;-L/root/ss/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/lib\u0026#34; # 依赖zlib,下载编译 wget http://zlib.net/zlib-1.2.8.tar.gz tar xvf zlib-1.2.8.tar.gz cd zlib-1.2.8/ ./configure --prefix=/root/dist/zlib-1.2.8 make \u0026amp; make install # 依赖openssl,下载编译 wget https://www.openssl.org/source/openssl-1.0.2h.tar.gz tar xvf openssl-1.0.2h.tar.gz cd openssl-1.0.2h ./Configure dist --prefix=/root/dist/openssl-1.0.2h make make install # 编译shadowsocks-libev wget https://github.com/shadowsocks/shadowsocks-libev/archive/v2.4.6.tar.gz tar xvf v2.4.6.tar.gz cd shadowsocks-libev-2.4.6 # 配置 需要注意的是--host选项,目标NAS不同值可能也会不同 # 详见Synology开发指南的Compile Open Source Projects章节 ./configure \\ --with-zlib=/root/dist/zlib-1.2.8 \\ --with-openssl=/root/dist/openssl-1.0.2h \\ --prefix=/root/dist/ss \\ --host=arm-unknown-linux-gnueabi make make install 这样 ss 就会编译到 /root/dist/ss 目录,这个时候打包:\ntar cvf shadowsocks.tar ss/ 另开一个本地终端从 VPS 取回文件:\nscp [email protected]:/root/dist/shadowsocks.tar . 运行 需要知道的是 shadowsocks 是一个 socket 代理,而群晖 NAS 只支持 HTTP 代理,所以我们需要 Privoxy软件转换下,幸运的是 ipkg 里面刚好有此软件包。\nsudo ipkg install privoxy 新建 shadowsocks 配置文件 config.json,内容如下:\n{ \u0026#34;server\u0026#34;:\u0026#34;xxx.xx.xx.xx\u0026#34;, \u0026#34;server_port\u0026#34;:1984, \u0026#34;local_port\u0026#34;:16800, \u0026#34;password\u0026#34;:\u0026#34;xxxx\u0026#34;, \u0026#34;method\u0026#34;:\u0026#34;aes-256-cfb\u0026#34;, \u0026#34;timeout\u0026#34;:60 } 新建 Privoxy 配置文件 privoxy.config:\nlisten-address 127.0.0.1:16801 #监听本地的16801端口 forward / . forward-socks5 .dropbox.com 127.0.0.1:16800 . #把访问 dropbox 的数据都通过ss 的代理端口转发出去 forward-socks5 .tmdb.org 127.0.0.1:16800 . #把访问 tmdb 的数据都通过ss 的代理端口转发出去 #forward-socks5 / 127.0.0.1:16800 . #全部转发 表示监听本地 16801 的端口数据转发到本地的socks5 16800 端口。这里只有两个网站的数据经过 ss 代理,一个是 Dropbox ,另一个是 tmdb(VideoStation 封面数据抓取网址)。如果需要更多可以一个个添加进去或者使用 actionfiles。\n后台运行:\n./ss-local -c config.json \u0026amp; privoxy privoxy.config 然后进 NAS 设置一下就 OK 了: 编译好的 shadowsocks 文件下载:DS216Play-Utility,以后 DS216Play 相关的自己编译的工具会放在 GitHub 上。\n自动运行 最后写一段自动运行脚本,放在 NAS 的任务计划中,设置每二十分钟运行一下,因为发现两个进程会有意外退出的情况,还没找原因:\n#!/bin/sh echo \u0026#34;Please run it with source command!\u0026#34; i1=`ps -ef | grep -E \u0026#34;ss-local*\u0026#34;|grep -v grep|awk \u0026#39;{print $2}\u0026#39;` if (kill -9 $i1) then { \techo \u0026#39;ss killed\u0026#39; } else { \techo \u0026#39;no ss found!\u0026#39; } fi ~/Software/ShadowSocks/bin/ss-local -c ~/Software/ShadowSocks/bin/config.json \u0026amp; echo \u0026#34;ss lunched!\u0026#34; i2=`ps -ef | grep -E \u0026#34;privoxy*\u0026#34;|grep -v grep|awk \u0026#39;{print $2}\u0026#39;` if (kill -9 $i2) then { \techo \u0026#39;privoxy killed\u0026#39; } else { \techo \u0026#39;no privoxy found!\u0026#39; } fi privoxy ~/Software/ShadowSocks/privoxy.config echo \u0026#34;privoxy lunched!\u0026#34; 参考 在Synology群晖NAS上使用shadowsocks-libev、polipo ","permalink":"https://tinyx.me/post/NAS-Shadowsocks/","summary":"最近一段时间买了个 NAS,用了之后感觉确实很不错,基本上就是一个家用的服务器,什么东西都可以往上扔。唯一后悔的就是买便宜了(毕竟穷),DS216Play 使用的是 STM STiH412 的 32位处理器,既然是 32位,所以一个大杀器-Docker 没法用了,也就是很多基于 Docker 的开箱即用第三方软件没法用。还有就是内置的 VideoStation 是从 IMDb 抓取封面信息,而 IMDb 是被墙了的。于是准备安装个 SS(ShadowSocks),顺便把 Dropbox 里面将近 100G 文件同步回来,之前买的 SSLedge 一个月才 30G 流量,要同步三个月才能完全把 Dropbox 上面文件同步回来,自建的 SS 直接是 1T,相当划算。\n安装 ipkg 开始之前需要安装 ipkg 包管理器,这样后面很多软件包就能使用了。因为没有找到适合 STiH412 处理器的安装脚本,所以我们下一个相似的:\n$ wget http://ipkg.nslu2-linux.org/feeds/optware/cs08q1armel/cross/unstable/syno-mvkw-bootstrap_1.2-7_arm.xsh $ chmod 700 syno-mvkw-bootstrap_1.2-7_arm.xsh $ sh syno-mvkw-bootstrap_1.2-7_arm.xsh 运行之后会报错,并生成一个 bootstrap 目录,进去 bootstrap 目录并修改 bootstrap.sh 文件\n #if ! grep Feroceon-KW /proc/cpuinfo \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then # echo \u0026#34;Error: CPU not Marvell Kirkwood, probably wrong bootstrap.","title":"Synology DS216Play 安装 ShadowSocks"},{"content":"基于 Linux 3.8\n内核启动 BeagleBone Black 从零到一 (1 MLO、U-Boot) 这篇文章里讲到 U-Boot 启动之后会运行 uEnv.txt 文件里面 uenvcmd 指代的命令,我们也通常把加载镜像文件等命令写在这里。\nU-Boot 有很多方法能启动内核,通常使用的是 bootm 或者 bootz 命令:\nU-Boot# help bootz bootz - boot Linux zImage image from memory Usage: bootz [addr [initrd[:size]] [fdt]] - boot Linux zImage stored in memory The argument \u0026#39;initrd\u0026#39; is optional and specifies the address of the initrd in memory. The optional argument \u0026#39;:size\u0026#39; allows specifying the size of RAW initrd. When booting a Linux kernel which requires a flat device-tree a third argument is required which is the address of the device-tree blob. To boot that kernel without an initrd image, use a \u0026#39;-\u0026#39; for the second argument. If you do not pass a third a bd_info struct will be passed instead U-Boot# help bootm bootm - boot application image from memory Usage: bootm [addr [arg ...]] - boot application image stored in memory passing arguments \u0026#39;arg ...\u0026#39;; when booting a Linux kernel, \u0026#39;arg\u0026#39; can be the address of an initrd image When booting a Linux kernel which requires a flat device-tree a third argument is required which is the address of the device-tree blob. To boot that kernel without an initrd image, use a \u0026#39;-\u0026#39; for the second argument. If you do not pass a third a bd_info struct will be passed instead For the new multi component uImage format (FIT) addresses must be extened to include component or configuration unit name: addr:\u0026lt;subimg_uname\u0026gt; - direct component image specification addr#\u0026lt;conf_uname\u0026gt; - configuration specification Use iminfo command to get the list of existing component images and configurations. Sub-commands to do part of the bootm sequence. The sub-commands must be issued in the order below (it\u0026#39;s ok to not issue all sub-commands): start [addr [arg ...]] loados - load OS image ramdisk - relocate initrd, set env initrd_start/initrd_end fdt - relocate flat device tree cmdline - OS specific command line processing/setup bdt - OS specific bd_t processing prep - OS specific prep before relocation or go go - start OS bootz 启动存储在内存中的 zImage ,支持 device-tree,其中一些参数: addr:内核地址 initrd:ramdisk 镜像地址,如果没有指定的 initrd 需要传递一个 - 给启动指令。什么是 ramdisk 镜像呢?我们下面会讲到。 fdt: flat device tree 地址,如果没有指定 fdt 地址,会传入一个 bd_info 结构体。什么是 Device Tree 呢?我们下面也会讲到。\nLinux 启动需要的文件 Linux 启动的时候需要挂载根文件系统来运行初始化程序,再总结 bootz 命令,所以 U-Boot 启动 Linux 内核需要以下文件: 内核镜像、initrd 文件(ramdisk 镜像)、DTB(Device Tree Binary)、根文件系统 不是必要但是也是需要的文件: System.map\nLinux 内核镜像 vm 代表 Virtual Memory,Linux 支持虚拟内存,因此得名 vm。Linux 内核按引导程序的不同分不同的格式,主要有:\n vmlinux\t: 最原始的内核静态链接后可执行的文件格式 ELF,没有压缩,不可启动的内核镜像,可能在调试阶段才会用到。 vmlinux.bin : 和 vmlinux 类似不过是可启动的 RAW Binary 格式。所有的标志和重定位信息都被丢弃,由 vmlinux 通过命令 objcopy -O binary vmlinux vmlinux.bin 生成。 vmlinuz\t: 经过压缩过的 vmlinux 可启动镜像。可启动表示有能力加载操作系统到内存中运行,vmlinuz 通常由 zImage 和 bzImage 拷贝而来。 zImage\t: 比zImage 是 ARM linux 常用的一种压缩镜像文件,它是由 vmlinux 加上解压代码经 gzip 压缩而成,通常放在小尺寸 ROM 中(小于 512K),zImage 解压缩内核到低容量内存(640K)。由命令 make zImage 生成。 bzImage\t: bz 表示 big zImage,其格式与 zImage 类似,但采用了不同的压缩算法。bzImage 需要解压缩内核到高容量内存(1M以上)。由命令 make bzImage 生成。 uImage\t: uImage 是 uboot 专用的镜像文件,它是在 zImage 之前加上一个长度为 0x40 的头信息(tag),在头信息内说明了该镜像文件的类型、加载 位置、生成时间、大小等信息。其 0x40 之后与 zImage 没区别。 编译及生成: BeagleBone 的内核镜像托管在 Github 编译之前可能需要安装的库\n➜ sudo apt-get install lzop ➜ sudo apt-get install libncurses5-dev libncursesw5-dev 源码里面已经包含了 BeagleBone 的配置文件:\n➜ git clone git://github.com/beagleboard/linux.git ➜ cd linux ➜ git checkout 3.8 ➜ make distclean ➜ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- bb.org_defconfig ➜ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs -j4 编译完成后会在 arch/arm/boot 目录下面生成 zImage 镜像和 dtbs 文件。 或者也可以使用 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig 或者 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- xconfig 来自己配置需要编译的内容。 如果使用 make xconfig 则需要安装 qt 和一些依赖,如果使用虚拟机不能使用 ssh 的方式远程编译,需要打开虚拟的图形界面进行编译。 xconfig 的一些依赖:\n➜ sudo apt-get update ➜ sudo apt-get install libqt4-dev pkg-config ➜ sudo apt-get install g++ initrd、initramfs Linux kernel 在自身初始化完成之后,需要能够找到并运行第一个用户程序(这个程序通常叫做 “init” 程序)。用户程序存在于文件系统之中,因此,内核必须找到并挂载一个文件系统才可以成功完成系统的引导过程。\u0008根文件系统(rootfs) 所在的储存装置很可能极难寻找,比方说 SCSI 装置就需要复杂且耗时的程序,若用 RAID 系统更是需要看配置情况而定,同样的问题也发生在 USB storage 上,因为 kernel 得花上更长的等待与配置时间,又或者需要远端挂载 rootfs,不仅得处理网路装置的问题,甚至还得考虑相关的伺服器认证、通讯往返时间等议题。为解决此问题,Linux kernel 提出了一个 RAM disk 的解决方案,把一些启动所必须的用户程序和驱动模块放在 RAM disk 中,这个 RAM disk 看上去和普通的 disk 一样,有文件系统,有 cache,内核启动时,首先把 RAM disk 挂载起来,等到 init 程序和一些必要模块运行起来之后,再切到真正的文件系统之中。更重要的是,有了 initrd(init ram disk) 我们可在放置某些特别的程式,一来作为挂载 rootfs 作准备,比方说硬体初始化、解密、解压缩等等,二来提示使用者或系统管理员目前的状态,这对于消费性电子产品来说,有很大的意义。 整体来说,如果能增加开机的弹性(比方说配合简单的shell script 即可达成USB/SCSI 初始化动作,若透过 kernel code 实做,恐怕上百千行是免不掉的),又能适度降低 kernel image 本身的设计复杂度与空间使用量,采取 initrd 是很不错的方式,所以几乎各大Linux distribution 都有提供 initrd,以解决在不同硬体、不同装置上开机的技术议题。 如果仔细考虑一下,initrd 虽然解决了问题但并不完美。 比如,disk 有 cache 机制,对于 RAM disk 来说,这个 cache 机制就显得很多余且浪费空间;disk 需要文件系统,那文件系统(如 ext2 等)必须被编译进 kernel 而不能作为模块来使用。 Linux 2.6 kernel 提出了一种新的实现机制,即 initramfs。顾名思义,initramfs 只是一种 RAM filesystem 而不是 disk。initramfs 实际是一个 cpio 归档,启动所需的用户程序和驱动模块被归档成一个文件。因此,不需要 cache,也不需要文件系统。\n引用:关于 initramfs \n The only purpose of an initramfs is to mount the root filesystem. The initramfs is a complete set of directories that you would find on a normal root filesystem. It is bundled into a single cpio archive and compressed with one of several compression algorithms. initramfs 的唯一用途就是挂载根文件系统。initramfs 就是一个普通文件系统目录的完整的集合。它用几个压缩算法打包成 cpio 格式。 There are only four primary reasons to have an initramfs in the LFS environment: loading the rootfs from a network, loading it from an LVM logical volume, having an encrypted rootfs where a password is required, or for the convenience of specifying the rootfs as a LABEL or UUID. Anything else usually means that the kernel was not configured properly. 在 LFS (Linux From Scratch) 中 initramfs 的存在主要有四个主要的原因:从网络加载根文件系统、从 LMM 逻辑卷加载的根文件系统、加密过的需要密码的根文件系统或者方便的指定根文件系统为 LABLE 或者 UUID。还有别的通常意味着内核配置不正确。 For most distributions, kernel modules are the biggest reason to have an initramfs. In a general distribution, there are many unknowns such as file system types and disk layouts. In a way, this is the opposite of LFS where the system capabilities and layout are known and a custom kernel is normally built. In this situation, an initramfs is rarely needed. 对于大多数分发版本来说,需要 initramfs 最大的原因是内核模块。在一般的分发中,有很多未知的文件系统和磁盘分布,在某种程度下,这是在系统能力和布局未知的情况。 如果说系统能力和布局已知,内核已经建立的情况下很少需要 initramfs。\n 上面提到的 RAM DISK 为 image-initrd,RAM filesystem 为 cpio-initrd,内核启动的时候会先判断是否为 cpio 格式,如果不是的话会当作 image-initrd 处理。\ncpio-initrd 和 image-initrd 处理流程可以参考:Linux2.6 内核的 Initrd 机制解析。\n编译及生成: Linux 2.6 以来都是默认使用 initramfs(\u0026ldquo;General setup\u0026rdquo; 的子項目 \u0026ldquo;Initial RAM filesystem and RAM disk (initramfs/initrd) suppot\u0026rdquo; 是默认勾选的) ,只是编译的输出很多还沿袭传统使用 initrd 的名字。编译的时候会生成 usr/initramfs_data.cpio 文件,内核编译的时候会把这个文件编译进内核。如果配置选项里面 \u0026ldquo;Initramfs source file\u0026rdquo; 为空(不指定 CONFIG_INITRAMFS_SOURCE),会默认编译出一个空的 initramfs 包(没有 init 文件)。 这样一来 Linux 内核编译的时候如果按默认方式编译(勾选 \u0026ldquo;initial RAM filesystem and RAM disk (initramfs/initrd) suppot\u0026rdquo;) 则有四种方法生成 initramfs 文件: 1. 不指定 CONFIG_INITRAMFS_SOURCE 则会编译出一个空的 initramfs 2. 为 CONFIG_INITRAMFS_SOURCE 指定一个现有的 initramfs 文件 如果你有一个现有的 initramfs_data.cpio.gz 文件,可以指定给 CONFIG_INITRAMFS_SOURCE ,内核编译时会自动检测文件类型然后链接到内核镜像中去。 3. 为 CONFIG_INITRAMFS_SOURCE 指定一个现有的根文件系统目录 如果为 CONFIG_INITRAMFS_SOURCE 指定一个目录,编译的时候会自动把此目录打包成 cpio 文件,但是不是用的标准的 cpio 命令打包,内核编译时会在指定的目录生成一个描述目录结构的文本文件 gen_initramfs_list.sh,然后给 gen_init_cpio 程序(在 usr 目录下)调用,就会生成 cpio 包。 4. 使用 initramfs_list 配置文件配置 这就是刚刚第三种方法讲的配置文件 gen_initramfs_list.sh,不过我们这里是手动创建配置文件而不是自动生成。手动创建后放入一个目录中,然后指定 CONFIG_INITRAMFS_SOURCE 为那个目录编译的时候就可以生成相应的 cpio 包。 例子:\nfile /init usr/hello 500 0 0 dir /dev 755 0 0 这个内容表示把 usr/hello 指定为文件系统中的 init 文件,权限为 500,uid 和 gid 都为0,并且创建 /dev 文件夹,权限为 755,uid 和 gid 都为 0。 详细使用方法请看:Tech Tip: How to use initramfs.\n值得注意的是我们按照这个方法制作出来的内核镜像实际上比原来的大了许多,这是因为我们是将 initramfs 根文件系统直接合并到内核镜像里边了。这样,启动内核时的参数就不需要添加 initrd=…… 来指定 initramfs 的位置了(或者配置内核的时候直接不勾选 \u0026ldquo;Initial RAM filesystem and RAM disk (initramfs/initrd) suppot\u0026rdquo;,但是为了可扩展性一般默认勾选)。 启动命令为:\nbootz ${loadaddr} - ${fdtaddr}; 当然如果想指定外部的 initramfs 文件来启动 Linux,首先直接指定 CONFIG_INITRAMFS_SOURCE 为空,这样就会链接一个空的 initramfs (一百多字节)进内核,然后指定外部 initramfs 的位置来启动。 命令为:\nbootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr}; 手动生成 initramfs 文件:\n➜ mkdir sub ➜ cp hello sub/init ➜ cd sub ➜ find . | cpio -o -H newc | gzip \u0026gt; ../initramfs_data.cpio.gz ➜ cd .. ➜ rm -rf sub System.map System.map 是一个特定内核的内核符号表。它是你当前运行的内核的符号链接。Linux 内核是一个很复杂的代码块,有许许多多的全局符号。内核并不使用符号名。它是通过变量或函数的地址(指针)来使用变量或函数的,而不是使用 size_t BytesRead,内核更喜欢使用(例如) c0343f20 来引用这个变量。 当你编译一个新内核时,各个符号名的地址要发生变化,你的老的 System.map 具有的是错误的符号信息。每次内核编译时产生一个新的 System.map,你应当用新的System.map 来取代老的 System.map。 虽然内核本身并不真正使用 System.map,但其它程序比如 klogd, lsof 和 ps 等软件需要一个正确的 System.map。如果你使用错误的或没有 System.map,klogd 的输出将是不可靠的,这对于排除程序故障会带来困难。没有 System.map,你可能会面临一些令人烦恼的提示信息。 另外少数驱动需要 System.map 来解析符号,没有为你当前运行的特定内核创建的System.map 它们就不能正常工作。 Linux 的内核日志守护进程 klogd 为了执行名称-地址解析,klogd 需要使用System.map。System.map 应当放在使用它的软件能够找到它的地方。执行:man klogd 可知,如果没有将 System.map 作为一个变量的位置给 klogd,那么它将按照下面的顺序,在三个地方查找 System.map: /boot/System.map /System.map /usr/src/linux/System.map System.map 也有版本信息,klogd 能够智能地查找正确的映象(map)文件。\n编译及生成: System.map 文件在编译内核的时候会自动生成在源码根目录下,每次重新编译内核都要替换掉相应的 System.map。\nDevice Tree 机制 Kernel 3.x 版本之后开始支持 Device Tree 机制。 在早期的ARM Linux Kernel中,板级细节(BSP)使用代码形式,存放在 arch/arm/ 目录。名字通常为:plat-xxxx 或者 mach-xxxx,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾。板级代码只对相应开发板有用,每次升级 Kernel 都需要对板级代码进行升级和测试,ARM 对应的开发板又种类繁多所以这些代码越来越臃肿庞大。 引入了 Device Tree 之后,改变了原来用硬编码的方式把板级代码嵌入在内核里面,改用通过 Bootloader 传递 DB 文件的方式把硬件信息传递给 Kernel,实现了内核和板级代码的低耦合,减少内核代码的臃肿降低维护人员的成本。 Uboot 的主线代码从 v1.1.3 开始就支持 DT 了,其对ARM的支持和 Kernel 对 Device Tree 的支持是同期完成的,在 Uboot 中需要在配置文件中加入 #define CONFIG_OF_LIBFDT 配置项即可,当我们将 DTB 文件在 Uboot 里加载到内存中后,通过 fdt addr 0xnnnnnnnn 命令来设置 DTB 文件对应地址,这样就可以使用 fdt resize、fdt print 等命令对 DTB 文件进行操作了。对于 ARM,使用 bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr} 命令来启动 Kernel。 更多背景介绍:Device Tree 背景介绍\n编译及生成: DTS:Device Tree Source 位于目录 linux/arch/arm/boot/dts DTC:Device Tree Compiler 位于目录 linux/scripts/dtc/dtc DTB:Device Tree Binary 生成在 linux/arch/arm/boot/dts DTB 是由 DTS 通过 DTC 编译而成。 在构建内核的时候可以使用 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs 生成。 Device Tree Binary 在编译 linux 内核的时候自动根据 arch/arm/boot/dts/Makefile 指定的方式生成,也可以通过 dtc 手动编译:\n➜ ./scripts/dtc/dtc -I dts -O dtb -o ./devicetree.dtb ./beaglebone.dts DTS 组成和结构专门写一篇文章来讲。\n根文件系统 根文件系统是内核挂载的第一个文件系统,根文件系统为分层文件树的顶点。它包含对于系统操作很关键的文件和目录,包括设备目录和用于引导系统的程序。根文件系统还包含可安装文件系统以连接到根文件系统层次结构的安装点。 Linux 只有一个文件树,整个文件系统是以一个树根 \u0026ldquo;/\u0026rdquo; 为起点的,所有的文件和外部设备都以文件的形式挂结在这个文件树上,包括硬盘、软盘、光驱、调制解调器等,这和以 \u0026ldquo;驱动器盘符” 为基础的Windows系统是大不相同的。\n编译及生成: 根文件系统的制作可以自己建立各个系统文件夹添加需要的库和启动文件然后打包成可分发的镜像。 目前一般使用 buildroot 或者 busybox 来制作根文件系统,buildroot 比 busybox 更强大并且包含了 busybox 。\n下载 buildroot 源码:\n➜ git clone git://git.buildroot.net/buildroot 为 Beaglebone 配置 buildroot:\n➜ make beaglebone_defconfig 如果只编译文件系统,需要做一些修改:\n➜ make menuconfig 取消勾选 Kernel --\u0026gt; Linux Kernel,这里不编译内核; 取消勾选 bootloaders --\u0026gt; u-boot,也不编译 u-boot;\n Target options ---\u0026gt;: Floating point strategy (NEON) ARM instruction set (Thumb2) Toolchain ---\u0026gt; Toolchain type (External toolchain) Toolchain (Linaro ARM 2014.08) Toolchain origin (Toolchain to be downloaded and installed) //让 buildroot 自己下载 Target packages ---\u0026gt; Networking applications ---\u0026gt; [*] dhcpcd [*] openssh Text editors and viewers ---\u0026gt; [*] nano Filesystem images ---\u0026gt; ext2/3/4 variant (ext4) ---\u0026gt; (X) ext4 保存然后:\n➜ make make 的时候 buildroot 会联网下载一些编译工具或者依赖包。最后会在 output 文件夹中生成相应的文件系统,把生成的文件系统烧写进 SD 卡中:\n➜ sudo dd if=buildroot/output/images/rootfs.ext4 of=/dev/sdc2 值得注意的是用以上命令烧写之后可以看到 SD 卡使用率变成 100%,没有空间写别的文件进去了。 这是因为使用 dd 命令不仅仅把文件而是把整个文件系统都烧写到 SD 卡中了,包括磁盘使用率的 metadata 描述。虽然 SD 卡上还有很多剩余空间,但是剩余空间还没有合并到特定的分区上,隐藏在 SD 卡分区的末尾。 这个时候使用 resize 命令就能合并了(在 ext2、ext3和ext4上都能使用,并且执行命令的时候会提示使用 e2fsck 检查下文件系统,按提示操作即可):\n➜ sudo resize2fs /dev/sda99 如果我们在 buildroot 中的 toolchain 指定外部编译工具为之前在 Ubuntu 上面 apt-get 的交叉编译器,那么编译的时候则会出现错误信息:\n Distribution toolchains are unsuitable for use by Buildroot, as they were configured in a way that makes them non-relocatable, and contain a lot of pre-built libraries that would conflict with the ones Buildroot wants to build.\n 这是因为 Ubuntu 得到的交叉编译器被配置成不可重定位的,而且包含了一些与 buildroot 相冲突的库,官方解释:\n Distro toolchains, i.ie. toolchains coing with distributions, will almost invariably be unsuitable for use with Buildroot:\n they are mostly non-relocatable; their sysroot is tainted with a lot of extra libraries. Especially, the toolchains coming with Ubuntu (really, all the Debian familly of distros) are configured with \u0026ndash;sysroot=/ which makes them non-relocatable, and they already contain quite some libraries that conflict (in any combination of version, API or ABI) with what Buildroot wants to build (i.e. extra libraries, some not even present in Buildroot\u0026hellip;) but also their mere preence when Buildroot does not expect them to be already built (so that a package would enable features when it should not).\n 所以我们要自己下载交叉编译工具或者让 buildroot 自动下载。\n开始完整启动 Linux 准备:\n SD 卡已经分好两个区,区 1 为 FTA 格式,区 2 为 EXT4 格式,区 1 前面留有 1 MB 的空间供 U-Boot 使用 U-Boot 已经编译好并把 MLO 和 uboot.img 文件烧写到 SD 卡的前 1M 起始位置,以便处理器用 RAW Mode 启动 U-boot,具体请看BeagleBone Black 从零到一 (1 MLO、U-Boot); Linux 已经编译好并且生成了 zImage、System.map; initramfs 已经制作好并且生成了 initrd.img;(如果没有特殊的启动前要求此项可以忽略); 文件系统已经制作好并且烧入到 SD 卡的 ext4 分区中; 新建 boot 目录:\n➜ mkdir boot 拷贝内核编译出来的 zImage 和 System.map 至 boot 目录并重命名:\n➜ cp zImage ~/boot/ ➜ cp System.map ~/boot/ ➜ mv ~/boot/zImage ~/boot/vmlinuz 把 initrd.img 和 dtb 文件拷贝到 boot 文件夹,再把整个 boot 文件夹拷贝到 ext4 分区的根目录。 在 FTA 分区中新建 uEnv.txt 文件,让 U-Boot 启动之后自动运行相应的指令启动 Linux:\n## uEnv.txt 可以引用默认的环境变量,在 include/configs/xxx.h 里面定义 ## 因为 SDRAM 的地址是从 0x80000000开始的,这里设置镜像、dtb、initrd加载到的地址分别为 0x82000000、0x88000000、0x88080000 loadaddr=0x82000000 fdtaddr=0x88000000 rdaddr=0x88080000 ## 设置 initrd_high=0xffffffff fdt_high=0xffffffff ## 设置命令为先打印 debug 信息,然后从第 0 个 MMC 设备的第 2 个分区里面的boot 文件夹中的 vmliuz 文件加载到 loadaddr 处 loadximage=echo debug: [/boot/vmlinuz] ... ; load mmc 0:2 ${loadaddr} /boot/vmlinuz} ## 同样的加载 dtb 文件 loadxfdt=echo debug: [/boot/{fdtfile}] ... ;load mmc 0:2 ${fdtaddr} /boot/${fdtfile} ## 加载 initrd loadxrd=echo debug: [/boot/initrd.img] ... ; load mmc 0:2 ${rdaddr} /boot/initrd.img; setenv rdsize ${filesize} ## 加载第二个分区的 uEnv.txt 文件 loaduEnvtxt=load mmc 0:2 ${loadaddr} /boot/uEnv.txt ; env import -t ${loadaddr} ${filesize}; ## 检查 ${dtb} 字符串长度是否不为 0,如果不为 0 就赋值给 fdtfile check_dtb=if test -n ${dtb}; then setenv fdtfile ${dtb};fi; ## 运行前面指定的命令 loadall=run loaduEnvtxt; run check_dtb; run loadximage; run loadxrd; run loadxfdt; ## 设置传递给内核的参数,其中 cmdline 是在第二个分区也就是放置 linux 镜像的分区内的 boot 文件夹中的 uEnv.txt 加载的 mmcargs=setenv bootargs console=tty0 console=${console} ${optargs} ${cape_disable} ${cape_enable} root=/dev/mmcblk0p2 rootfstype=${mmcrootfstype} ${cmdline} ## 根据前面几篇文章 U-Boot 的配置,启动后会运行 uenvcmd uenvcmd=run loadall; run mmcargs; echo debug: [${bootargs}] ... ; echo debug: [bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr}] ... ; bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr}; SD 卡中 ext4 分区中 boot 文件夹中再创建一个 uEnv.txt,这边的 uEnv.txt 便于向 U-Boot 传递一些内核参数之类的,不用每次换了内核之类的都去修改 U-Boot 那边的文件:\nuname_r=3.8.13-bone79 #uuid= #dtb= cmdline=coherent_pool=1M quiet init=/lib/systemd/systemd cape_universal=enable 都设置好了之后,把 SD 卡插入 beaglebone 中并且按住 S2 键上电就能从 SD 卡中启动了。\n参考: vmlinuz Definition 制作 initramfs/initrd 镜像 System.map 解析 Device Tree:背景介绍 Linux2.6 内核的 Initrd 机制解析 深入理解 Linux 2.6 的 initramfs 機制 (上) Why do I need initramfs ? How to use initramfs Initramfs Tutorial ramfs, rootfs and initramfs ","permalink":"https://tinyx.me/post/BBB-Prepare-booting/","summary":"基于 Linux 3.8\n内核启动 BeagleBone Black 从零到一 (1 MLO、U-Boot) 这篇文章里讲到 U-Boot 启动之后会运行 uEnv.txt 文件里面 uenvcmd 指代的命令,我们也通常把加载镜像文件等命令写在这里。\nU-Boot 有很多方法能启动内核,通常使用的是 bootm 或者 bootz 命令:\nU-Boot# help bootz bootz - boot Linux zImage image from memory Usage: bootz [addr [initrd[:size]] [fdt]] - boot Linux zImage stored in memory The argument \u0026#39;initrd\u0026#39; is optional and specifies the address of the initrd in memory. The optional argument \u0026#39;:size\u0026#39; allows specifying the size of RAW initrd.","title":"BeagleBone Black 从零到一 (3 Linux 镜像、initramfs、Device Tree及根文件系统)"},{"content":"BeagleBone 官方镜像: BeagleBoard.org Latest Firmware Images\n下载推荐的镜像: bone-debian-7.9-lxde-4gb-armhf-2015-11-12-4gb.img.xz\n.xz 文件解压:\nxz -d bone-debian-7.9-lxde-4gb-armhf-2015-11-12-4gb.img.xz 得到 .img 文件。\n挂载镜像 在 Linux 下挂载 .img 文件,\n可见直接挂载的话会出现:mount: you must specify the filesystem type。 首先,要说明一下的是,这情况是由于 img 文件的开头包含了 MBR,以致于系统无法识别。img 文件相当于一个包含操作系统的硬盘,而我们只需要挂载它的文件系统部分,所以需要绕过 MBR。\n绕过 MBR 挂载文件系统有两种方法。\n第一种: 首先使用 fdisk 查看分区信息:\n看到第一个分区从第 2048 个扇区开始, Units = sectors of 1 * 512 = 512 bytes,Unit = 2048 * 512 = 1048576,所以我们偏移 1028576 个字节挂载第一个分区; 第二个分区从第 198656 个扇区开始,Unit = 198656 * 512 = 101711872,偏移 101711872 个字节挂载第二个分区;\n第二种: 安装 kpartx,先把镜像挂载到 loop 设备上,然后使用 kpartx 创建分区表映射,并挂载相应的映射。\n镜像分析 从 AM335x 的启动流程可知,如果需要启动 U-Boot,FAT 启动模式下必须得有一个在 FAT 格式的活跃主分区中名为 MLO 的文件供 ROM Code 运行。但是我们分析镜像里面第一个 FAT 分区并没有发现 MLO 文件。\n 早期的 BeagleBone Black 映像会把 MLO 和 u-boot.img 放在活跃主分区(active primary partition)的 FAT 文件系统的根目录里。SD 的映像会包含两个分区,一个 FAT32 分区和一个 Linux ext3/4 分区。全功能的第二阶段 U-Boot 会包含硬编码的环境变量,它可以靠这些环境变量提供的配置进一步加载 uEnv.txt,之所以使用硬编码的环境变量和 uEnv.txt 是因为采用这种配置方式的 U-Boot 不能用 saveenv 保存环境变量。\n 最近,BeagleBone Black 采用所谓 RAW模式 的 U-Boot 配置方式。在 RAW 模式下,内置 ROM 代码会依次从 sector #0 (offset 0x00000)、sector #256 (0x20000)、sector #512 (0x40000)和sector #768 (0x60000) 这几个位置来搜索 TOC structure/Configuration Header(这个数据结构可以在TI的AM335x Technical Reference Manual中查到)。 如果你还想将MMC作为一个磁盘,那么 MMC 上就必须有一个 MBR。在这种配置下,你就不能把 U-Boot SPL/MLO放在sector #0 了(参见Wikipedia里的MBR结构)。由于前面提到 ROM 代码会顺序搜索可用的MLO,因此把 U-Boot SPL/MLO 放到0x20000 (sector #256)就可以解决这个问题。\n 随后,U-boot SPL 可以从 0x60000 进一步加载完整功能的 U-Boot。从块存储设备运行 U-Boot 的优点之一是可以保存环境变量,也就不再需要加载 uEnv.txt。\n 所以目前 Beaglebone Black 官方镜像把 MLO 和 U-Boot 数据写入在第一个分区之前的扇区内,因为我们的第一个分区的数据从 sector 2048 开始,每个 sector 为 512字节,所以足足有 1MB 的存储空间供我们使用。 当把 MLO 和 U-Boot 用 RAW Mode 写入的时候,处理器 SPL 阶段会自动判断是 RAW Mode 还是 FTA Mode 启动。\nMBR 主引导记录(Master Boot Record,缩写:MBR),又叫做主引导扇区,是计算机开机后访问硬盘时所必须要读取的首个扇区。主引导扇区记录着硬盘本身的相关信息以及硬盘各个分区的大小及位置信息,是数据信息的重要入口。 标准 MBR 结构如下图:\n使用 dd 命令提取出镜像前 2048 扇区(2048*512 字节)的数据,查看前 512 字节是否存在 MBR:\n可以看到地址 0x01BE\u0026ndash;0x01FD 的确存在分区表,且有 MBR 有效标志 0x55AA,但是我们提取出来为 0xAA55 ,这里肯定是大小端不一致造成的。\n根据硬盘分区表结构信息分析出来的分区情况和 fdisk 工具查看的分区情况一致。\n启动代码 前面镜像分析出,MBR 占用了第一个扇区,所以启动代码 MLO 和 U-Boot 肯定存储在地址 0x20000、0x40000或0x60000处。 RAW 模式下存储也有一定格式,如果是通用设备,则 TOC structure/Configuration Header 则必须存储在第一个扇区中,并且紧跟着是 GP Header。\n The raw mode is detected by reading sectors #0, #256, #512, #768. The content of these sectors is then verified for presence of a TOC structure as described in Section 26.1.9. In the case of a GP Device, a Configuration Header (CH) must be located in the first sector followed by a GP header. The CH might be void (only containing a CHSETTINGS item for which the Valid field is zero).\n 还是分析刚刚提取出来的数据, 从地址 0x20000 开始的第一个扇区:\n看数据的确包含了 RAW Mode 的配置头。 那么这个扇区之后紧跟着就是包含 GP Header 的镜像文件数据了,从地址 0x0000 开始为数据大小,地址 0x0004 开始为要拷贝至的目标地址,0x0008 地址开始接下来就是镜像数据,结构如下:\n还是用 hexdump 分析:\n大小端转换下得到:镜像数据大小为 0x0000fe4c = 65100 bytes,目标地址为 0x402f0400,此地址为 AM335x 的内部 SRAM 的地址,所以 RAM Code 会把 MLO 拷贝到内部 SRAM 运行。\nMLO 启动之后会在地址 0x60000 也就是第 0x300 扇区开始加载 U-Boot。(MLO 会分别在 RAW 模式 FAT 等模式下查找可用的 u-boot.img)。我们使用 hexdump 工具也的确能从地址 0x60000 开始发现有效数据。\nU-Boot 启动后会根据 U-Boot 配置的文件位置信息(DFU_ALT_INFO_MMC)查找可用的 uEnv.txt 文件,并运行相关的命令:\n##These are needed to be compliant with Angstrom\u0026#39;s 2013.06.20 u-boot. loadaddr=0x82000000 fdtaddr=0x88000000 rdaddr=0x88080000 initrd_high=0xffffffff fdt_high=0xffffffff ##These are needed to be compliant with Debian 2014-05-14 u-boot. loadximage=echo debug: [/boot/vmlinuz-${uname_r}] ... ; load mmc 0:2 ${loadaddr} /boot/vmlinuz-${uname_r} loadxfdt=echo debug: [/boot/dtbs/${uname_r}/${fdtfile}] ... ;load mmc 0:2 ${fdtaddr} /boot/dtbs/${uname_r}/${fdtfile} loadxrd=echo debug: [/boot/initrd.img-${uname_r}] ... ; load mmc 0:2 ${rdaddr} /boot/initrd.img-${uname_r}; setenv rdsize ${filesize} loaduEnvtxt=load mmc 0:2 ${loadaddr} /boot/uEnv.txt ; env import -t ${loadaddr} ${filesize}; check_dtb=if test -n ${dtb}; then setenv fdtfile ${dtb};fi; loadall=run loaduEnvtxt; run check_dtb; run loadximage; run loadxrd; run loadxfdt; mmcargs=setenv bootargs console=tty0 console=${console} ${optargs} ${cape_disable} ${cape_enable} root=/dev/mmcblk0p2 rootfstype=${mmcrootfstype} ${cmdline} uenvcmd=run loadall; run mmcargs; echo debug: [${bootargs}] ... ; echo debug: [bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr}] ... ; bootz ${loadaddr} ${rdaddr}:${rdsize} ${fdtaddr}; 官方镜像里面的 uEnv.txt 主要包含从哪个位置加载内核镜像的命令,U-Boot 加载文件后后会运行 uenvcmd 命令,这样就能启动 Linux 内核了。\n如何烧写 将Bootloader映像用 RAW 模式保存到eMMC/SD卡中,使用如下命令将文件写入到SD卡或者eMMC的起始位置:\n# 这里的conv=ontrunc加不加都行,因为对于块设备来说,notrunc没有作用 # 每个block为512字节,跳过256个block,写入256个block dd if=MLO of=/dev/sdb bs=512 seek=256 count=256 conv=notrunc dd if=u-boot.img of=/dev/sdb bs=512 seek=768 count=1024 conv=notrunc 如果你写入的是一个可插拔SD卡,那么用下面的命令来确保内容都确实写入到了卡中:\nsudo blockdev --flushbufs /dev/sdb 参考 主引导扇区MBR的解析 ; Beaglebone Black Notes; Linux操作系统 读取MBR信息 ","permalink":"https://tinyx.me/post/BeagleBone-Image/","summary":"BeagleBone 官方镜像: BeagleBoard.org Latest Firmware Images\n下载推荐的镜像: bone-debian-7.9-lxde-4gb-armhf-2015-11-12-4gb.img.xz\n.xz 文件解压:\nxz -d bone-debian-7.9-lxde-4gb-armhf-2015-11-12-4gb.img.xz 得到 .img 文件。\n挂载镜像 在 Linux 下挂载 .img 文件,\n可见直接挂载的话会出现:mount: you must specify the filesystem type。 首先,要说明一下的是,这情况是由于 img 文件的开头包含了 MBR,以致于系统无法识别。img 文件相当于一个包含操作系统的硬盘,而我们只需要挂载它的文件系统部分,所以需要绕过 MBR。\n绕过 MBR 挂载文件系统有两种方法。\n第一种: 首先使用 fdisk 查看分区信息:\n看到第一个分区从第 2048 个扇区开始, Units = sectors of 1 * 512 = 512 bytes,Unit = 2048 * 512 = 1048576,所以我们偏移 1028576 个字节挂载第一个分区; 第二个分区从第 198656 个扇区开始,Unit = 198656 * 512 = 101711872,偏移 101711872 个字节挂载第二个分区;","title":"解析 BeagleBone Black 官方镜像"},{"content":"更新:2016-04-01\n什么是 U-Boot 熟悉嵌入式开发的应该都听过它,U-boot 就是启动系统前的一段引导程序,虽然是引导程序,但是功能非常强大。\n这一篇主要讲解如何从无到有运行 U-Boot,关于 U-Boot 引导 Linux 的部分放在另外一篇文章讲解。\nU-Boot 之前的版本以版本号命名如:0.1.0, 0.2.0 这几年改为了以时间和日期命名:U-Boot 2016.03。\n使用 git 获得 U-Boot 的源码:\ngit clone git://git.denx.de/u-boot.git 目前我使用的是 2016.02 的版本。\nMLO 及其启动过程 上一篇文章,我们了解了 BeagleBone 有个 SPL 过程,就在这个时候读取 MLO 文件,MLO 文件其实是个精简版的 U-Boot,也是由 U-Boot 生成,但是功能有限,只初始化了部分资源如 DDR,然后启动 U-Boot。\nMLO 文件是如何编译出来的 分析 MLO 的编译过程之前需要知道编译原理和 Makefile 等相关知识。 我们先找找 Makefile 看看能不能找到什么。建议使用 Sublime 编辑器。用全局查找功能查找 MLO 关键字。\n找到 u-boot/scripts/Makefile.spl 文件 117行:\nMLO MLO.byteswap: $(obj)/u-boot-spl.bin FORCE \t$(call if_changed,mkimage) 可以看到 MLO 文件是由 u-boot-spl.bin 文件通过 mkimage 命令生成的。 再查到 u-boot/Makefile 文件 1310 行:\nspl/u-boot-spl.bin: spl/u-boot-spl \t@: spl/u-boot-spl: tools prepare $(if $(CONFIG_OF_SEPARATE),dts/dt.dtb) \t$(Q)$(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all u-boot-spl.bin 文件是还是由 u-boot/scripts/Makefile.spl 文件生成。 文件 u-boot/scripts/Makefile.spl 168 行 定义了 u-boot-spl.bin 的生成:\nifeq ($(CONFIG_SPL_OF_CONTROL),y) $(obj)/$(SPL_BIN)-dtb.bin: $(obj)/$(SPL_BIN)-nodtb.bin $(obj)/$(SPL_BIN)-pad.bin \\ $(obj)/$(SPL_BIN).dtb FORCE $(call if_changed,cat) $(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-dtb.bin FORCE $(call if_changed,copy) else $(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-nodtb.bin FORCE $(call if_changed,copy) endif 因为 SPL_BIN 在 第32行 定义为 u-boot-spl:\n{% codeblock lang:make u-boot/scripts/Makefile.spl %} ifeq ($(CONFIG_TPL_BUILD),y) SPL_BIN := u-boot-tpl else SPL_BIN := u-boot-spl endif {% endcodeblock %}\n由 168 行 上面的定义可以知道 u-boot-spl.bin 和 u-boot-spl-nodtb.bin 有关系。\n接着查找到第223行:\n$(obj)/$(SPL_BIN)-nodtb.bin: $(obj)/$(SPL_BIN) FORCE \t$(call if_changed,objcopy) u-boot-spl-nodtb.bin 是通过 objcopy 命令由 u-boot-spl 生成。\n再看第246行:\n$(obj)/$(SPL_BIN): $(u-boot-spl-init) $(u-boot-spl-main) $(obj)/u-boot-spl.lds FORCE \t$(call if_changed,u-boot-spl) 所以u-boot-spl 是由 u-boot-spl.lds 链接文件生成的 ,但是目录下面有几个u-boot-spl.lds文件,到底是哪个 lds 文件呢,上面是 $(obj)/u-boot-spl.lds, obj 在 1310 行 编译 u-boot-spl.bin 的时候赋值为 obj=spl,所以我们需要看 u-boot/spl/u-boot-spl.lds 这个文件,但是如果你之前没有编译过这个文件是没有的。这个文件是如何生成的呢?我们稍后再看,先看 lds 文件的内容:\nMEMORY { .sram : ORIGIN = 0x402F0400, LENGTH = (0x4030B800 - 0x402F0400) } MEMORY { .sdram : ORIGIN = 0x80a00000, LENGTH = 0x80000 } OUTPUT_FORMAT(\u0026#34;elf32-littlearm\u0026#34;, \u0026#34;elf32-littlearm\u0026#34;, \u0026#34;elf32-littlearm\u0026#34;) OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { .text : { __start = .; *(.vectors) arch/arm/cpu/armv7/start.o (.text) *(.text*) } \u0026gt;.sram . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } \u0026gt;.sram . = ALIGN(4); .data : { *(SORT_BY_ALIGNMENT(.data*)) } \u0026gt;.sram .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } \u0026gt;.sram . = ALIGN(4); __image_copy_end = .; .end : { *(.__end) } \u0026gt;.sram .bss : { . = ALIGN(4); __bss_start = .; *(.bss*) . = ALIGN(4); __bss_end = .; } \u0026gt;.sdram } 链接文件里面说明了内存布局,arch/arm/cpu/armv7/start.o 代码段都放在 SRAM 中,所以 arch/arm/cpu/armv7/start.S 就是我们要找的东西了。\nlds 链接文件的生成 u-boot/spl/u-boot-spl.lds 这个文件的生成在 u-boot/scripts/Makefile.spl 有解释:\n$(obj)/u-boot-spl.lds: $(LDSCRIPT) FORCE \t$(call if_changed_dep,cpp_lds) LDSCRIPT 的定义:\n# Linker Script ifdef CONFIG_SPL_LDSCRIPT # need to strip off double quotes LDSCRIPT := $(addprefix $(srctree)/,$(CONFIG_SPL_LDSCRIPT:\u0026#34;%\u0026#34;=%)) endif ifeq ($(wildcard $(LDSCRIPT)),) \tLDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot-spl.lds endif ifeq ($(wildcard $(LDSCRIPT)),) \tLDSCRIPT := $(srctree)/$(CPUDIR)/u-boot-spl.lds endif ifeq ($(wildcard $(LDSCRIPT)),) \tLDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot-spl.lds endif ifeq ($(wildcard $(LDSCRIPT)),) $(error could not find linker script) endif 可见 Makefile.spl 文件中先是判断有没有指定的 lds 文件,如果没有指定的,就查找 board 文件夹中目标板目录下面有没有 lds 文件,如果没有就查找相应的 cpu 目录,因为我们目标器件是 am335x,所以发现有 u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds 再通过 cpp_lds 命令编译成,cpp_lds 是一组命令的集合,具体定义还是在 Makefile.spl 文件中,我们查看 u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds 也发现 MLO 文件代码是在 start.S 文件中。\nMLO 程序分析 查看 start.S 分析下 MLO 程序具体的执行流程,MLO 的 makefile 会根据 CONFIG_SPL_BUILD 编译不同的源文件,同样的在源码内也通过 CONFIG_SPL_BUILD 控制不同的代码执行,前面一部分 MLO 文件和 U-Boot 是类似的,进入到 _main 函数中两个程序的功能就开始出现差异了:\nreset //(arch/arm/cpu/armv7/start.S) save_boot_params_ret //(arch/arm/cpu/armv7/start.S) |- disable interrupts |- cpu_init_cp15 //(arch/arm/cpu/armv7/start.S) | |- Invalidate L1 I/D | |- disable MMU stuff and caches |- cpu_init_crit //(arch/arm/cpu/armv7/start.S) | |- lowlevel_init //(arch/arm/cpu/armv7/lowlevel_init.S) | |- Setup a temporary stack | |- Set up global data | |- s_init //(arch/arm/cpu/armv7/am33xx/board.c) | |- watchdog_disable | |- set_uart_mux_conf | |- setup_clocks_for_console | |- uart_soft_reset |- _main //(arch/arm/lib/crt0.S) |(MLO)如果是 MLO 文件 |- board_init_f //(arch/arm/cpu/armv7/am33xx/board.c) | |- board_early_init_f //(arch/arm/cpu/armv7/am33xx/board.c) | | |- prcm_init | | |- set_mux_conf_regs | |- sdram_init //(board/ti/am335x/board.c) 初始化 DDR |- spl_relocate_stack_gd |- board_init_r //(common/spl/spl.c) |- ... |- spl_load_image //根据不同的启动方式加载 u-boot 镜像, |- jump_to_image_no_args //进入u-boot代码运行 |(U-Boot)如果是U-Boot 镜像 |- board_init_f //(common/board_f.c) | |- ... | |- initcall_run_list(init_sequence_f) | |- ... | |- relocate_code //(arch/arm/lib/relocate.S) 代码重定位 |- relocate_vectors //(arch/arm/lib/relocate.S) 向量表重定义 |- Set up final (full) environment |- board_init_r //(common/board_r.c) |- initcall_run_list(init_sequence_r)//初始化各种外设 |- main_loop() 当 U-Boot 重定位好代码、向量表之后,运行 board_init_r 函数,此函数会调用 init_sequence_r 列表里面的函数初始化各种外设驱动,最后在 main_loop() 函数中运行,U-Boot 有个 bootdelay 延时启动,如果不手动停止 U-Boot 会自动运行 bootcmd 包含的命令。\n内核引导这部分放在另外一篇文章详细讲解。\nU-Boot 编译 编译 U-Boot 编译 U-Boot 前我们需要安装交叉编译器:\n# sudo apt-get install gcc-arm-linux-gnueabihf 下载 U-Boot 源码:\n# git clone git://git.denx.de/u-boot.git 因为 U-Boot 官方已经支持了 Beaglebone Black 所以配置文件也已经自带了,编译输入如下命令:\n# make distclean # make am335x_boneblack_defconfig # ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make 片刻后会生成 MLO 和 u-boot.img 文件。\n配置 U-Boot 参数 有两种方式可以配置 U-Boot 的一些参数,分别是 uEnv.txt 和 boot.src 文件。 U-Boot 启动的时候会在启动分区寻找这两个文件。\n boot.scr: This file is a U-Boot script. It contains instructions for U-Boot. Using these instruction, the kernel is loaded into memory, and (optionally) a ramdisk is loaded. boot.scr can also pass parameters to the kernel. This file is a compiled script, and cannot be edited directly. In some cases, boot.scr loads further instructions and configuration parameters from a text file.\n uEnv.txt: A file with additional boot parameters. This file can be read by boot.scr, or by the boot sequence if there is no script file. uEnv.txt is a regular text file that can be edited. This file should have Unix line ending, so a compatible program must be used when editing this file.\nU-Boot 启动的时候如果不打断会调用 bootcmd 包含的命令来执行,通常 bootcmd 会调用 bootscript 脚本也就是 boot.scr 里面的命令进行执行, boot.scr 通常也会先读取 uEnv.txt 确定额外参数,因为 boot.src 文件必须通过 boot.cmd 文件编译而来, uEnv.txt 则是可以任意编辑,这样可配置性就大大提高了。如果没有 boot.src 文件,U-Boot 有默认配置的 bootcmd 命令。\n在 Beagelbone Black 中我们不需要额外的 boot.scr 文件,用默认的命令即可,默认的命令为:\n#define CONFIG_BOOTCOMMAND \\ \u0026#34;run findfdt; \u0026#34; \\ \u0026#34;run distro_bootcmd\u0026#34; run distro_bootcmd 最终会调用 run mmcboot 命令加载 uEnv.txt 文件,并且会运行 uEnv.txt 文件里面 uenvcmd 指代的命令。\nuEnv.txt 从网络启动例子:\nconsole=ttyO0,115200n8 ipaddr=192.168.23.2 serverip=192.168.23.1 rootpath=/exports/rootfs netargs=setenv bootargs console=${console} ${optargs} root=/dev/nfs nfsroot=${serverip}:${rootpath},${nfsopts} rw ip=${ipaddr}:${serverip}:192.168.23.1:255.255.255.0:beaglebone:eth0:none:192.168.23.1 netboot=echo Booting from network ...; tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} ${fdtfile}; run netargs; bootz ${loadaddr} - ${fdtaddr} uenvcmd=run netboot 制作 U-Boot 的 SD 启动卡 制作 SD 启动卡之前首先需要为 SD 卡分区, ROM Code 启动的时候如果是从 MMC 设备加载启动代码,ROM Code 会从第一个活动分区寻找名为 \u0026ldquo;MLO\u0026rdquo; 的文件,并且此分区必须为 FAT文件系统。所以制作 U-Boot 的启动卡只需要一个带有 MLO 和 U-Boot 镜像的 FAT 格式的 SD 卡,如果需要启动 Linux 内核还需要别的分区,我们以后再讲。\n有两种方式可以制作包含 U-Boot 的可启动的 SD 卡,一种是用 RAW Mode 的方式,还有一种是用 FTA 的方式。\nRAW Mode 和烧写方式在这篇文章里面有讲:解析 BeagleBone Black 官方镜像。\nFTA 模式下只要建立一个 FTA 分区再把 MLO 和 uboot.img 文件拷贝进去即可。\n我是使用的 USB 读卡器,插入后 Linux /dev/ 目录会显示 /dev/sd* 设备,我这里多出两个设备分别显示 /dev/sdb 和 /dev/sdb1 ,其中 /dev/sdb 表示一整个物理磁盘, /dev/sdb1 表示的是具体的分区。\n使用命令 sudo fdisk /dev/sdb 管理磁盘:\na : toggle a bootable flag(设置或取消启动表示)\nb : edit bsd disklabel(编辑 bsd disklabel)\nc : toggle the dos compatibility flag\nd : delete a partition (删除一个分区)\nl : list known partition types (列出已知的分区类型)\nm : print this menu (打印次列表)\nn : add a new partition (增加一个新分区)\no : create a new empty DOS partition table (建立一个新的空 DOS 分区表)\np : print the partition table (打印分区表)\nq : quit without saving changes (不保存退出)\ns : create a new empty Sun disklabel\nt : change a partition\u0026rsquo;s system id\nu : change display/entry units\nv : verify the partition table (验证分区表)\nw : write table to disk and exit (把分区表写入磁盘)\nx : extra functionality (experts only) (额外的功能)\n新建启动分区:\nCommand (m for help): p Disk /dev/sdb: 7746 MB, 7746879488 bytes 24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00000000 Device Boot Start End Blocks Id System /dev/sdb1 * 2048 15130623 7564288 c W95 FAT32 (LBA) Command (m for help): m Command action a toggle a bootable flag b edit bsd disklabel c toggle the dos compatibility flag d delete a partition l list known partition types m print this menu n add a new partition o create a new empty DOS partition table p print the partition table q quit without saving changes s create a new empty Sun disklabel t change a partition\u0026#39;s system id u change display/entry units v verify the partition table w write table to disk and exit x extra functionality (experts only) Command (m for help): d Selected partition 1 Command (m for help): p Disk /dev/sdb: 7746 MB, 7746879488 bytes 24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00000000 Device Boot Start End Blocks Id System Command (m for help): n Partition type: p primary (0 primary, 0 extended, 4 free) e extended Select (default p): p Partition number (1-4, default 1): Using default value 1 First sector (2048-15130623, default 2048): Using default value 2048 Last sector, +sectors or +size{K,M,G} (2048-15130623, default 15130623): Using default value 15130623 Command (m for help): p Disk /dev/sdb: 7746 MB, 7746879488 bytes 24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00000000 Device Boot Start End Blocks Id System /dev/sdb1 2048 15130623 7564288 83 Linux Command (m for help): t Selected partition 1 Hex code (type L to list codes): c Changed system type of partition 1 to c (W95 FAT32 (LBA)) Command (m for help): a Partition number (1-4): 1 Command (m for help): p Disk /dev/sdb: 7746 MB, 7746879488 bytes 24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00000000 Device Boot Start End Blocks Id System /dev/sdb1 * 2048 15130623 7564288 c W95 FAT32 (LBA) Command (m for help): w The partition table has been altered! Calling ioctl() to re-read partition table. WARNING: If you have created or modified any DOS 6.x partitions, please see the fdisk manual page for additional information. Syncing disks. 建立好新的分区之后需要命名并格式化:\n# sudo mkfs.vfat -F 32 -n boot /dev/sdb1 格式化之后挂载磁盘并把 MLO 文件和 u-boot.img 文件拷贝进去:\n# sudo mount /dev/sdb1 /media/jg/boot # sudo cp MLO /media/jg/boot/MLO # ls /media/jg/boot MLO # sudo cp u-boot.img /media/jg/boot/u-boot.img # ls /media/jg/boot MLO u-boot.img # sync # sudo umount /media/jg/boot 接着把 SD 卡插入 Beaglebone Black 并且按着 S2 按钮上电,从串口打印出的信息我们可以看到 U-Boot 已经可以正常启动了:\nU-Boot SPL 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20) Trying to boot from MMC Card doesn\u0026#39;t support part_switch MMC partition switch failed *** Warning - MMC partition switch failed, using default environment reading u-boot.img reading u-boot.img U-Boot 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20 +0800) Watchdog enabled I2C: ready DRAM: 512 MiB MMC: OMAP SD/MMC: 0, OMAP SD/MMC: 1 *** Warning - bad CRC, using default environment Net: \u0026lt;ethaddr\u0026gt; not set. Validating first E-fuse MAC cpsw, usb_ether Press SPACE to abort autoboot in 2 seconds switch to partitions #0, OK mmc0 is current device Scanning mmc 0:1... switch to partitions #0, OK mmc0 is current device SD/MMC found on device 0 reading boot.scr ** Unable to read file boot.scr ** reading uEnv.txt ** Unable to read file uEnv.txt ** ** File not found /boot/zImage ** switch to partitions #0, OK mmc1(part 0) is current device Scanning mmc 1:1... switch to partitions #0, OK mmc1(part 0) is current device SD/MMC found on device 1 reading boot.scr ** Unable to read file boot.scr ** reading uEnv.txt ** Unable to read file uEnv.txt ** ** File not found /boot/zImage ** ## Error: \u0026#34;bootcmd_nand0\u0026#34; not defined cpsw Waiting for PHY auto negotiation to complete......... TIMEOUT ! BOOTP broadcast 1 BOOTP broadcast 2 BOOTP broadcast 3 BOOTP broadcast 4 启动之后,前面一段打印信息是 MLO 程序打印出来的,读取 U-Boot 之后开始运行完整的 U-Boot,之后程序扫描各个设备读取 boot.scr 和 uEnv.txt 文件,接着再读取是否有 Linux 内核可以运行。\n参考资料 Beaglebone Black——制作自己的SD启动卡 U-Boot on BeagleBone Black AM335x U-Boot User\u0026rsquo;s Guide Pandaboard bootload(uboot) 启动流程探究 u-boot启动流程 ","permalink":"https://tinyx.me/post/BBB-Uboot/","summary":"更新:2016-04-01\n什么是 U-Boot 熟悉嵌入式开发的应该都听过它,U-boot 就是启动系统前的一段引导程序,虽然是引导程序,但是功能非常强大。\n这一篇主要讲解如何从无到有运行 U-Boot,关于 U-Boot 引导 Linux 的部分放在另外一篇文章讲解。\nU-Boot 之前的版本以版本号命名如:0.1.0, 0.2.0 这几年改为了以时间和日期命名:U-Boot 2016.03。\n使用 git 获得 U-Boot 的源码:\ngit clone git://git.denx.de/u-boot.git 目前我使用的是 2016.02 的版本。\nMLO 及其启动过程 上一篇文章,我们了解了 BeagleBone 有个 SPL 过程,就在这个时候读取 MLO 文件,MLO 文件其实是个精简版的 U-Boot,也是由 U-Boot 生成,但是功能有限,只初始化了部分资源如 DDR,然后启动 U-Boot。\nMLO 文件是如何编译出来的 分析 MLO 的编译过程之前需要知道编译原理和 Makefile 等相关知识。 我们先找找 Makefile 看看能不能找到什么。建议使用 Sublime 编辑器。用全局查找功能查找 MLO 关键字。\n找到 u-boot/scripts/Makefile.spl 文件 117行:\nMLO MLO.byteswap: $(obj)/u-boot-spl.bin FORCE \t$(call if_changed,mkimage) 可以看到 MLO 文件是由 u-boot-spl.","title":"BeagleBone Black 从零到一 (2 MLO、U-Boot)"},{"content":"BeagleBone 的核心处理器是 AM3358,理解启动过程需要查看相应的数据手册。\nAM335x 有四个不同的启动阶段:\n ROM SPL(Secondary Program Loader) U-BOOT Linux Kernel 为什么 AM335x 有多个启动阶段呢?直接从启动源加载 U-Boot 启动不是很方便?\n首先,第一步的 ROM Code 不可能太复杂,ROM大小是一个限制,最重要的还是因为其所能获取的系统信息太少,所以处理器只能使用简单的方法去寻找能完成复杂功能的代码,而后将处理器初始化工作交给它来完成。其次,SPL 也不能很复杂,因为 ROM Code 只能将它加载到处理器内部 RAM 中运行,一般处理器的内部 RAM 都要小于128KB,其大小注定其不能完成很复杂的功能,所以 SPL 需要初始化 DDR 再加更复杂的代码加载到 DDR 中运行。到了在 DDR RAM 中执行的 Bootloader(U-Boot算一个),其功能就比较完善了,此时就可以启动操作系统了。\n第一阶段:ROM Code ROM Code 是芯片上电复位之后第一块自动运行的代码。ROM Code 固化在芯片中不能被用户修改。 ROM Code 存储在片上地址 0x40000000-0x40040000 处。\nROM Code 有两个主要的功能:\n 配置设备并初始化主要的外设 建立堆栈 配置开门狗定时器1(设置到三分钟) 配置 PLL 和系统时钟 为下一阶段启动做准备 为下一阶段启动检查启动源(SPL) 把下一启动阶段的 bootloader 代码移动到内存中准备运行 ROM Code 针对不同的启动源有不同的启动过程,但最终还是为了把启动源的代码搬运至内部 RAM 中运行,这里我们主要解析下针对 MMC/SD 卡设备,它的启动过程是怎么样的。\n 初始化 MMC/SD 驱动 检测是内存还是SD卡 配置地址 判断存储设备是 RAW 模式还是 FAT 模式 如果是 RAW 模式就按照 参考手册 Table 26-20 所定义的地址搬运代码至内部 RAM 中 如果是 FAT 模式,就读取主引导记录并读取启动扇区查找 MLO 文件,并搬运到 RAM 中执行 具体见参考手册第二十六章。\n第二阶段:SPL 第二阶段叫做 SPL,但有些时候也叫 MLO。SPL 是启动 U-Boot 前的一个阶段,并且必须从 启动源(MMC、USB、SD、UART等)加载到内部 RAM 中。SPL 只有极少的功能,SPL 主要的职责就是初始化 DDR,时钟等最小系统,以读取 U-Boot 并加载到 DDR 中。\nMLO 文件是从哪来的呢?\n在编译 U-boot 的时候也会生成相应目标板的 MLO 文件,好像老版本的 U-boot 没有这功能,具体请看另外一篇文章。\n第三阶段:U-Boot U-boot 主要的工作就是正确加载 Kernel。和 SPL 类似,U-boot 也是要加载下一个阶段的 image,但是 U-Boot 提供了更多外设的支持和更多的调试工具。所以,U-Boot 也要进行各个模块的配置,上述 SPL 配置的部分,除了 DDR 外,U-Boot 也会根据需求重新配置(这里重置主要是U-Boot 是一个开源工程,其要兼容某些特殊的芯片,从而需要做重载)。此外,U-Boot 也会对网口,SD卡等根据需求进行配置。 完成配置后,U-Boot 会从相应的存储器或者外设读取 Kernel,并传递参数给 Kernel,运行 Kernel。\nU-Boot 允许用户通过串行终端在内核启动环境中做一些强大的基于命令的控制。用户可以控制很多启动参数和内核启动命令。另外 U-Boot 环境变量是可配置的,这些变量保存在存储媒介的 uEnv.txt 中。 更多内容可以查看 AM335x U-Boot 用户指南。\n第四阶段:启动 Linux 内核 uImage 是与描述内核头信息交换过的内核镜像。这个头信息是一个 64kB的信息块,包含了目标架构、操作系统、内核尺寸、入口等信息。U-boot 启动 uImage的时候,头信息会在命令行输出。\n在读出头文件之后,U-Boot 开始启动 Linux 内核。\n其他 AM335x 能从不同的源启动:MMC/SD,NAND,NOR,UART,ETHERNET,USB,SPI和I2C。能从哪个源启动由启动阶段决定。比如,SPL/U-Boot 只能从能被 ROM Code 识别的源启动。 ROM 和 SPL bootloader在内部 RAM 中运行,U-boot 和内核在 DDR 中运行。 更多信息可以看《技术参考指南》的第二章。\nBeagleBone 启动 BBB 板子上的 AM3335x 处理器上可以通过配置选择不同的接口启动,如数据手册 第二十六章所写。BBB 通过 S2 这个按键选择不同的启动顺序,默认情况下(S2 没有按下)启动过程如下:\n MMC1(板载 eMMC) MMC0(microSD) UART0 USB0 如果按下 S2 按钮,启动过程如下:\n SPI0 MMC0(microSD) UART0 USB0 ","permalink":"https://tinyx.me/post/BBB-bootmode/","summary":"BeagleBone 的核心处理器是 AM3358,理解启动过程需要查看相应的数据手册。\nAM335x 有四个不同的启动阶段:\n ROM SPL(Secondary Program Loader) U-BOOT Linux Kernel 为什么 AM335x 有多个启动阶段呢?直接从启动源加载 U-Boot 启动不是很方便?\n首先,第一步的 ROM Code 不可能太复杂,ROM大小是一个限制,最重要的还是因为其所能获取的系统信息太少,所以处理器只能使用简单的方法去寻找能完成复杂功能的代码,而后将处理器初始化工作交给它来完成。其次,SPL 也不能很复杂,因为 ROM Code 只能将它加载到处理器内部 RAM 中运行,一般处理器的内部 RAM 都要小于128KB,其大小注定其不能完成很复杂的功能,所以 SPL 需要初始化 DDR 再加更复杂的代码加载到 DDR 中运行。到了在 DDR RAM 中执行的 Bootloader(U-Boot算一个),其功能就比较完善了,此时就可以启动操作系统了。\n第一阶段:ROM Code ROM Code 是芯片上电复位之后第一块自动运行的代码。ROM Code 固化在芯片中不能被用户修改。 ROM Code 存储在片上地址 0x40000000-0x40040000 处。\nROM Code 有两个主要的功能:\n 配置设备并初始化主要的外设 建立堆栈 配置开门狗定时器1(设置到三分钟) 配置 PLL 和系统时钟 为下一阶段启动做准备 为下一阶段启动检查启动源(SPL) 把下一启动阶段的 bootloader 代码移动到内存中准备运行 ROM Code 针对不同的启动源有不同的启动过程,但最终还是为了把启动源的代码搬运至内部 RAM 中运行,这里我们主要解析下针对 MMC/SD 卡设备,它的启动过程是怎么样的。","title":"BeagleBone Black 从零到一 (1 启动流程)"},{"content":"买了个 BeagleBone Black 准备从底层到应用层把整个 Linux 开发流程过一遍,并写一系列教程。\n硬件组成 还是在学习 EtherCAT 的时候了解到 AM335x 这块芯片,它带有可编程实时单元子系统和工业通讯单元子系统 (PRU-ICSS),可以做一些实时性的工作,也可以开发相应的现场总线应用。\n环境搭建 不管是在 Windows 上还是在 OS X 上做 Linux 开发,还是建议大家用虚拟机搭建一个 Linux 平台,这样可以在开发中节省很多时间,具体可以看这篇《建立一个便捷高效的嵌入式 Linux 开发环境》。\nOS X 下驱动安装 按照官网的教程安装驱动后有的时候会出现还是识别不了 Beaglebone 的情况。这个时候需要按以下步骤来:\n 安装 HoRNDIS-rel7 驱动,目前 release 7 是最新的。 重启 OSX 进入命令行,运行以下命令: sudo rm -rf /System/Library/Extensions/HoRNDIS.kext sudo rm -rf /Library/Extensions/HoRNDIS.kext 安装 HoRNDIS-rel8pre1.pkg 重启 OSX 现在插上 USB 可以见到 Finder 里面会出现外置移动设备,并且浏览器可以通过地址 http://192.168.7.2 访问 BeagleBone,如下图:\n通过 Terminal 访问 BeagleBone 安装好驱动并且连接上 USB 后,可以在 /dev/ 文件夹看到 USB 转 TTY 设备:\n并且我们可以通过 screen 命令 访问 tty 设备,命令如下\nscreen /dev/tty.usbmodemFD123 又或者,我们可以通过 BeagleBone 上面的串口访问,我们需要一根 USB 转 TTL 的线连接 BeagleBone:\n线序定义:红+5V, 黑GND, 白RXD,绿TXD\nUSB-TTL与BB-Black连接方法是: USB-TTL \u0026lt;\u0026mdash;\u0026gt; BB-Black TXD\u0026lt;\u0026mdash;\u0026mdash;-\u0026gt;RXD RXD\u0026lt;\u0026mdash;\u0026mdash;-\u0026gt;TXD GND\u0026lt;\u0026mdash;\u0026mdash;-\u0026gt;GND\n同样的,我们会在 /dev/ 目录下发现一个 tty.usbserial 设备,并且可以通过 screen /dev/tty.usbserial 115200 命令访问。\n至此,我们已经初步打通了电脑和 BBB 的连接。\n","permalink":"https://tinyx.me/post/BBB0-Hardware/","summary":"\u003cp\u003e买了个 BeagleBone Black 准备从底层到应用层把整个 Linux 开发流程过一遍,并写一系列教程。\u003c/p\u003e","title":"BeagleBone Black 从零到一 (0 硬件及环境搭建)"},{"content":"更新:2016-04-15\n0 前言 我们经常需要在 Linux 环境下进行嵌入式开发,但是目前又不能完全抛弃 Windows 或者 OS X,所以我们经常需要用虚拟机来搭建开发环境,但是虚拟机又很臃肿,而且如果配置不高的电脑会运行的很慢。有没有一种方便在各电脑间拷贝运行速度又很快的解决方案呢?当然是有的。以下是几种解决方案。\n首先虚拟机如果想要运行速度快必须使用 Headless 模式运行或者安装服务器版本的 Linux,也就是去掉了图形界面,但是我们又突然需要图形界面怎么办?以下三种方案都适用于桌面版本的 Linux 或者服务器版本的 Linux。\n 方案 优点 缺点及技术难度 Virtual Box 可以使用 Headless 模式运行 Linux 减少 CPU 利用率 便捷性较差,如果从 PC 拷贝到 Mac 需要重新设置网络和共享文件夹,使用起来非常简单 Vagrant 继承了 Virtual Box 的优点并且利用 Vagrant 可以很方便的打包分发镜像,共享目录等功能 稍有难度,需要学习 Vagrant 的相关命令 Vagrant + Docker 继承了 Vagrant 的优点并且可以利用 Docker 管理生产环境,不仅可以实现系统镜像的分发,也可以实现生产环境的分发 难度较大,还需要学习 Docker 相关的知识 如果你有很多主机或者很多人和你协同工作,需要不同主机上配置同一套开发环境,利用 Virtual Box 和 Vagrant 可以把整个系统打包并分发给各个主机,如果系统是服务器版几百兆还能接受,如果是桌面版几个 G 的大小每次分发显然不方便。但是利用 Vagrant + Docker 不仅可以把整个系统在各个主机之间分发还可以直接把开发环境分发给别的主机,什么意思呢?就是你当前开发环境下新安装了个几个库或者几个工具,别的主机还没安装,你可以直接把当前的环境打包给其他主机,而不用打包整个操作系统。\n因为如果需要使用桌面版系统 Vagrant 相对于直接使用 Virtual Box 没什么优势,所以目前我就利用 Virtual Box 的 Headless 模式运行 Ubuntu 再利用 SSH 操作虚拟机。Docker 使用方式后续更新。\n1 安装环境 安装好 Virtual Box 和 Ubuntu 之后再安装 virtualbox guest additions。接着设置网络为下图:\n2 安装 SSH 服务器 安装 SSH Server:\nsudo apt-get install openssh-server 编辑 /etc/network/interfaces 文件,添加以下内容:\nauto eth1 iface eth1 inet static address 192.168.56.10 netmask 255.255.255.0 在 Ubuntu 终端下运行 sudo ifup eth1。\n在主机上运行 ssh 192.168.56.10 -l xxx xxx 为 Ubuntu 用户名称,这样就能通过 SSH 访问虚拟机了。\n3 控制虚拟机 启动和关闭虚拟机 通过命令行控制虚拟机 列出虚拟机:\nVBoxManage list vms 使用 headless 模式运行虚拟机:\n VBoxManage startvm Ubuntu -t headless 关闭虚拟机:\n VBoxManage controlvm Ubuntu poweroff 下图是用 Mac 通过 SSH 访问 headless Ubuntu 的过程:\n挂载 USB 设备 可以通过 VBoxManage 为虚拟机挂载 USB 设备。 我们需要为虚拟机挂载一个 USB 存储设备,首先列出宿主机的 USB 设备列表:\n JG@promote:~$ VBoxManage list usbhost Host USB Devices: UUID: aa3125cc-6c05-4066-b07c-4ea0c6aaf352 VendorId: 0x05ac (05AC) ProductId: 0x0252 (0252) Revision: 1.24 (0124) Port: 2 USB version/speed: 0/Full Manufacturer: Apple Inc. Product: Apple Internal Keyboard / Trackpad Address: p=0x0252;v=0x05ac;s=0x000000005bdc094d;l=0xfa120000 Current State: Unavailable UUID: c2d28499-2511-4b73-9316-fe116339bd11 VendorId: 0x05ac (05AC) ProductId: 0x821a (821A) Revision: 0.66 (0066) Port: 3 USB version/speed: 0/Full Manufacturer: Apple Inc. Product: Bluetooth USB Host Controller Address: p=0x821a;v=0x05ac;s=0x0000000069cc5ddb;l=0xfa113000 Current State: Available UUID: dbdbca1a-158a-45a8-9691-169296fc386d VendorId: 0x05ac (05AC) ProductId: 0x8242 (8242) Revision: 0.22 (0022) Port: 1 USB version/speed: 0/Low Manufacturer: Apple Computer, Inc. Product: IR Receiver Address: p=0x8242;v=0x05ac;s=0x0000000058d9b5c6;l=0xfd110000 Current State: Available UUID: f7d8f411-b32b-4b3e-8d9f-24776ab9782d VendorId: 0x05ac (05AC) ProductId: 0x8509 (8509) Revision: 5.22 (0522) Port: 2 USB version/speed: 0/High Manufacturer: Apple Inc. Product: FaceTime HD Camera (Built-in) SerialNumber: CC2B8F05FPDG6LL0 Address: p=0x8509;v=0x05ac;s=0x000000004e187314;l=0xfa200000 Current State: Available UUID: 4592a209-8182-4843-9729-3bff607d78a7 VendorId: 0x05e3 (05E3) ProductId: 0x0736 (0736) Revision: 2.114 (02114) Port: 3 USB version/speed: 0/High Manufacturer: Generic Product: USB Storage SerialNumber: 000000000272 Address: p=0x0736;v=0x05e3;s=0x00001ae27eb59b44;l=0xfa130000 Current State: Available 可以看到最后一个就是我们需要的 USB Storage 设备,我们利用 VBoxManage 挂载:\n JG@promote:~$ VBoxManage controlvm Ubuntu usbattach 4592a209-8182-4843-9729-3bff607d78a7 挂载成功,可以用 df -h 命令查看:\n文件夹共享 与宿主机通过共享文件夹进行文件交换,首先设置共享文件夹:\n设置保存后在虚拟机上通过命令挂载共享文件夹:\nsudo mount.vboxsf BBB /mnt/BBB 改变虚拟机磁盘大小 先改变虚拟磁盘的大小:\nVBoxManage modifyhd YOUR_HARD_DISK.vdi --resize SIZE_IN_MB 然后打开 LInux,安装 GPart:\nsudo apt-get install gparted 打开 GPart 把未分区的空间调整至相应的分区,有的时候需要把 swap off。\n","permalink":"https://tinyx.me/post/Running-headless-vm/","summary":"\u003cp\u003e更新:2016-04-15\u003c/p\u003e\n\u003ch1 id=\"0-前言\"\u003e0 前言\u003c/h1\u003e\n\u003cp\u003e我们经常需要在 Linux 环境下进行嵌入式开发,但是目前又不能完全抛弃 Windows 或者 OS X,所以我们经常需要用虚拟机来搭建开发环境,但是虚拟机又很臃肿,而且如果配置不高的电脑会运行的很慢。有没有一种方便在各电脑间拷贝运行速度又很快的解决方案呢?当然是有的。以下是几种解决方案。\u003c/p\u003e","title":"建立一个便捷高效的嵌入式 Linux 开发环境"},{"content":"特性 EtherCAT(以太网控制自动化技术)是一个以以太网为基础的开放架构的现场总线系统,EtherCAT名称中的CAT为Control Automation Technology(控制自动化技术)首字母的缩写。最初由德国倍福自动化有限公司(Beckhoff Automation GmbH) 研发。EtherCAT为系统的实时性能和拓扑的灵活性树立了新的标准,同时,它还符合甚至降低了现场总线的使用成本。EtherCAT的特点还包括高精度设备同步,可选线缆冗余,和功能性安全协议(SIL3)。\n性能 过程数据 更新时间 265 个分布式数字 I/O 量 11 us = 0.01 ms 1000 个分布式数字 I/O 量 30 us 200 个 16 位的模拟 I/O 量 50 us \u0026lt;\u0026ndash;\u0026gt; 20 kHz 100 个包含 8 字节输入输出数据的伺服轴 100 us 1 个现场总线网关(1486 字节的输入和 1486字节的输出数据) 150 us ISO/OSI 模型 在标准以太网的七层 OSI 模型中 EtherCAT 只定义了物理层、数据链路层和应用层这三层协议(如下图)。\n网络组成 EtherCAT 是主从式的结构。\n主站 EtherCAT 主站使用标准的以太网 MAC。\n从站 EtherCAT 从站需要使用特殊的硬件芯片(EtherCAT Slave Controller)来达到微秒级周期时间的实时性能。从站不需要微处理器就可以实现 EtherCAT 通信。可以通过 I/O 接口实现的简单设备可以只由 ESC 和其下的 PHY,变压器和 RJ45 接头组成。给从站的过程数据接口是32位的I/O接口。这种从站没有可配置的参数,所以不需要软件或邮箱协议。更复杂的可配置从站有使用一个微处理器。\n在报文经过从站时, ESC 从报文中提取发送给自己的输出命令数据并将其存储到内部存储区,输入数据从内部存储区又被写到相应的子报文中。数据的提取和插入都是由数据链路层硬件完成的。\n 从站可以最多有 4 个 EtherCAT 端口。 如果有一个端口是关闭的,那么控制器会自动转发到下一个端口。端口可以由连接自动打开或关闭,写可以通过 EtherCAT 命令修改。 EtherCAT 帧处理和转发顺序由逻辑端口配置(如下图)。 拓扑结构 EtherCAT 支持几乎所有拓扑结构。因此,源于现场总线的总线形结构也可用于以太网。将总线和分支结构相结合特别有助于系统布线。所有接口都位于耦合器上,无需使用附加交换机。\n黄色的实线为以太网线缆,绿色虚线为 EtherCAT 帧的路径。\n所有的数据帧都被第一个从站设备转发到后续的节点,最后一个从站设备将数据帧返回到主站。\n节点个数 EtherCAT 网络最多可以连接 65535 个设备。\n寻址方式 如上图,一个 EtherCAT 网段相当于一个以太网设备,主站首先使用以太网数据帧头的 MAC 地址寻址到网段,然后使用 EtherCAT 子报文头中的地址寻址到段内设备。\n网段寻址 直接模式(无交换机) 一个 EtherCAT 网段直接连接到主站设备的标准以太网口(如下图),此时主站使用广播 MAC 地址寻址,帧报文中目的 MAC 地址为 FF-FF-FF-FF-FF-FF。\n开放模式 EtherCAT 网段连接到以太网交换机上(如下图),我们就有多个网段,此时每个网段都需要一个 MAC 地址,主站发送的 EtherCAT 数据帧中目的地址是它所控制的网段的 MAC 地址。\nEtherCAT 网段中第一个从站设备有一个 ISO/IEC 8802.3 的 MAC 地址,这个地址表示了整个网段,这个从站称为段地址从站,它能够交换帧内的目的地址区和源地址区。\n段内寻址 段内寻址指的是当我们寻址到一个网段中后,改网段中的具体的设备如何寻址,EtherCAT 有以下几种方式。\n自增量寻址(Auto Increment Addressing) 自增量寻址必须只能在 EtherCAT 系统启动的时候用来扫描总线或者偶尔检测到新设备的时候。在经过自增量寻址之后主站会给每个从站分配一个固定的地址,以便用于固定地址寻址。\n特点:\n 每个从站根据位置分配一个十六位的负数自增量地址. 当所读取的从站地址等于零时将被处理。 每次从站地址加一 偏移地址存储在设备的内存空间中。 通常用于扫描硬件的配置信息。 固定寻址(Fixed Addressing) 固定地址寻址一般用于主站与从站以邮箱方式的通信中(非周期性数据通信在 EtherCAT 中称为邮箱数据通信),在邮箱方式通信的时候 EtherCAT 主站根据从站的固定地址寻址到所要交换数据的从站,数据只在两者之间进行交换,适用于主站与某一个从站交换相对较大的数据。\n特点:\n 每个从站有一个固定的地址(16 bit 所以最大节点数为 65535) 通常在硬件配置扫描的过程中被分配。 与从站的位置无关 当断电后固定地址丢失 逻辑寻址(Logical Addressing) 逻辑寻址一般用在过程数据的通信过程中(周期性),每个从站的物理地址通过 FMMU(Fieldbus Memory Management Unit) 被映射到一个指定的逻辑地址当中。这样做的好处是主站想要操作某一个从站时可以只操作固定的逻辑地址而不必知道所操作的逻辑地址所对应的从站地址,这样就大大减轻了控制系统的负担,加快了相应的速度。\n其他 EtherCAT 设备可以有两个配置的地址,一个是由主站分配(Configured Station Address),另一个是存储在 ESC EEPROM 中并且能由从站应用改写(Configured Station Alias Address)。主站分配的地址从站不能修改,存储在 EEPROM 中的地址只在第一次上电或者复位的时候装载。\nEtherCAT 主站一开始通过自增量寻址访问各个从站并分配固定地址,接下来通过固定寻址访问配置寄存器(非周期性),通过逻辑寻址访问过程数据(周期性)。\n高可靠性、冗余 仅需在主站设备端增加使用一个标准的以太网端口(无需专用网卡或接口),并将单一的电缆从总线型拓扑结构转变为环型拓扑结构即可增加冗余特性(见下图)。当设备或电缆发生故障时,也仅需一个周期即可完成切换。因此,即使是针对运动控制要求的应用,电缆出现故障时也不会有任何问题。EtherCAT也支持热备份的主站冗余。由于在环路中断时EtherCAT从站控制器芯片将立刻自动返回数据帧,一个设备的失败不会导致整个网络的瘫痪。例如,拖链设备可以配置为分支拓扑以防线缆断开。\n存储同步管理(SyncManager) ESC 使用了存储同步管理通道 SM(SyncManager)来保证主站与本地应用数据交换的一致性和安全性,并在数据状态改变时产生中断来通知对方。SM 通道把存储空间组织为一定大小的缓存区,由硬件控制对缓存区的访问。缓存区的数量和数据交换方向可配置。SM 由主站进行配置,\n必须从起始地址开始操作一个缓存区,否则操作被拒绝。操作其实地址后,就可以操作整个缓存区。允许再次操作其实地址,并且可以分多次操作。操作缓存区的结束地址表示缓存区操作结束,随后缓存区状态改变,同时可以产生一个中断信号或看门狗触发脉冲。不允许在一个数据帧内两次操作结束地址。\nEtherCAT 定义了两种 SM 运行模式:\n1)缓存类型(常用于过程数据通信)\n 使用 3 个缓存区保证可以随时接收和交付最新的数据; 经常有一个可写入的空闲缓存区; 在第一次写入之后,经常有一个连续可读的数据缓存区。 2)邮箱类型\n 使用一个缓存区,支持握手机制; 对数据溢出产生保护; 只有写入新数据才可以进行成功的读操作; 只有成功读取之后才允许再次写入。 P65\n现场总线内存管理单元(FMMU) 逻辑寻址时,从站地址并不是单独定义的,而是使用寻址段内 4 GByte 逻辑地址空间中的一段区域。报文内的 32 位地址区作为整体的数据逻辑地址完成设备的逻辑寻址。\n逻辑寻址由 FMMU 实现,FMMU 功能位于每个 ESC 内部,将从站本地物理存储地址映射到网段内逻辑地址。\nFMMU 单元由主站设备配置,并在数据链路启动过程中传送给从站设备。\n FMMU 配置寄存器 数值 数据逻辑起始地址 0x00014711 数据长度(字节数,按跨字节计算) 2 数据逻辑起始位 3 数据逻辑终止位 0 从站物理内存起始地址 0x0F01 物理内存起始位 1 操作类型(1:只读,2:只写,3:读写) 2 激活(使能) 1 从站设备收到一个数据逻辑寻址的 EtherCAT 子报文时,检查是否有 FMMU 单元地址匹配。如果有,它将输入类型数据插入到 EtherCAT 子报文数据区的对应位置,以及从 EtherCAT 子报文数据区的对应位置抽取输出类型数据。使用逻辑寻址可以灵活地组织控制系统,优化系统结构。逻辑寻址方式特别适用于传输或交换周期性过程数据。 FMMU 操作具有以下功能特点:\n 每个数据逻辑地址字节只允许被一个 FMMU 读和另一个 FMMU 写操作,或被同一个 FMMU 进行读写交换操作; 对一个逻辑地址的读写操作与使用一个 FMMU 读和另一个 FMMU 写操作具有相同的结果; 按位读写操作不影响报文中没有被映射到的其他位,因此允许将几个从站 ECS 中的位数据映射到主站的同一个逻辑字节; 读写一个未配置的逻辑地址空间不会改变其内容。 分布时钟(Distributed Clock) 分布时钟可以使所有 EtherCAT 设备使用相同的系统时钟,从而控制各设备任务的同步执行。从站设备可以根据同步的系统时间产生同步信号,用于中断控制或触发数字量输入输出。支持分布式时钟的从站称为 DC 从站。分布式时钟具有以下功能:\n 实现从站之间时钟同步; 为主站提供同步时钟; 产生同步的输出信号 SYNC; 为输入事件产生精确的时间标记; 产生同步的中断; 同步更新数字量输出; 同步采用数字量输入。 描述 分布时钟机制使所有的从站都同步于一个参考时钟。主站连接的第一个具有分布时钟功能的从站作为参考时钟,以参考时钟来同步其他设备和主站的从时钟。为了实现精确的时钟同步控制,必须测量和计算数据传输延时和本地时钟偏移,并补偿本地时钟的漂移。同步时钟涉及到如下 6 个时间概念:\n系统时间 系统时间是分布时钟使用的系统计时。系统时间从 2000 年 1 月 1 日零点开始,使用 64 位二进制变量表示,单位为纳秒(ns),最大可以计时 500 年。也可以使用 32 位二进制变量表示,32 位时间值最大可以表示 4.2 s,通常用于通信和时间标记。\n参考时钟和从时钟 EtherCAT 协议规定主站连接的第一个具有分布时钟功能的从站作为参考时钟,其他从站的时钟称为从时钟。参考时钟被用于同步其他从站设备和主站时钟。参考时钟提供 EtherCAT 系统时间。\n主站时钟 EtherCAT 主站也具有计时功能,称为主站时钟。主站时钟可以在分布时钟系统中作为从时钟被同步。在初始化阶段,主站可以按照系统时间的格式发送主站时间给参考时钟从站,使分布时钟使用系统时间计时。\n本地时钟、其初始偏移量和时钟漂移 每一个 DC 从站都有本地时钟,本地时钟独立运行,使用本地时钟信号计时。系统启动时,各从站的本地时钟和参考时钟之间有一定的差值,称为时钟初始偏移量。在运行过程中,由于参考时钟和 DC 从站时钟使用各自的时钟源等原因,它们的计时周期存在一定的漂移,这将导致时钟运行不同步,本地时钟产生漂移。因此,必须对时钟初始偏移和时钟漂移都进行补偿。\n本地系统时间 每个 DC 从站的本地时钟经过补偿和同步之后都产生了一个本地系统时间,分布时钟同步机制就是使各个从站的本地系统时间保持一致。参考时钟也是相应从站的本地系统时钟。\n传输延时 数据帧在从站时间传输时会产生一定的延时。其中包括设备内部和物理连接延时。所以在同步从时钟时,应该考虑参考时钟与各个从时钟之间的传输延时。\nTBD FMMU EtherCAT 帧解析 EtherCAT 命令 应用层协议 分布式时钟\\如何同步IO 模块化的设备概要文件 syncmanager7683\n参考:\n EtherCAT communication EtherCAT - 以太网现场总线 《工业以太网现场总线 EtherCAT 驱动程序设计及应用》- 郇极、刘艳红 ","permalink":"https://tinyx.me/post/EtherCAT-Note/","summary":"\u003ch2 id=\"特性\"\u003e特性\u003c/h2\u003e\n\u003cp\u003eEtherCAT(以太网控制自动化技术)是一个以以太网为基础的开放架构的现场总线系统,EtherCAT名称中的CAT为Control Automation Technology(控制自动化技术)首字母的缩写。最初由德国倍福自动化有限公司(Beckhoff Automation GmbH) 研发。EtherCAT为系统的实时性能和拓扑的灵活性树立了新的标准,同时,它还符合甚至降低了现场总线的使用成本。EtherCAT的特点还包括高精度设备同步,可选线缆冗余,和功能性安全协议(SIL3)。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e","title":"EtherCAT 基础知识汇总"},{"content":"环境 网络上很多都是使用的 TwinCAT 2 和 WireShark 1.4 来建立主站程序并捕捉 EtherCAT 数据,这里做个新版本的教程,新的软件较旧软件使用方便。\n软件 TwinCAT 3.1 – eXtended Automation Engineering (XAE) WireShark 2 硬件 PC利用 TwinCAT 作为主站,从站采用的是基于 ET1100 的从站开发板。\n配置 1. 安装驱动 安装好 TwinCAT 之后需要安装相应网卡的 RT-EtherCAT 适配器的驱动。\n打开 TwinCAT 的 Show Realtime Ethernet Compatible Devices 对话框,如下图:\n接着点击下面的设备列表中的设备,再右边点击 install 安装驱动。安装完会出现在 installed and ready to use devices 下面,表示可以使用了,如下图:\n安装完驱动后会在本地连接的属性列表中多出两个项目,如下图:\n2. 建立项目并捕捉数据 新建 TwinCAT XAE Project 项目,建立完成之后连接 EtherCAT 从站,在工程 I/O 下面 Devices 处扫描设备,如下图:\n扫描到之后我们可以对设备进行操作,如下图可以控制 LED:\n并且我们能在设备的在线选项卡中看到收发出去的帧数,如下图:\n但是到这一步为止我们用 WireShark 并不能捕获到 EtherCAT 的数据帧。需要能捕获到,我们必须打开设备的 Promiscuous Mode,Promiscuous Mode 是什么意思呢?官方解释为:\n Promiscuous Mode (use with Netmon only)\nIf this control box is activated, the TwinCAT Ethernet telegrams are also transferred to Windows and can therefore be analyzed with a network monitor (e.g. Etherreal or MS network monitor).\n 所以我们在适配器选项卡中打开这个模式,如下图:\n这个时候再用 WireShark 捕捉,就能得抓到数据了。\n大功告成!\n","permalink":"https://tinyx.me/post/Setup-TwinCat/","summary":"\u003ch2 id=\"环境\"\u003e环境\u003c/h2\u003e\n\u003cp\u003e网络上很多都是使用的 TwinCAT 2 和 WireShark 1.4 来建立主站程序并捕捉 EtherCAT 数据,这里做个新版本的教程,新的软件较旧软件使用方便。\u003c/p\u003e\n\u003ch3 id=\"软件\"\u003e软件\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.beckhoff.com/english/download/tc3-download-xae.htm?id=1948695119487514\"\u003eTwinCAT 3.1 – eXtended Automation Engineering (XAE) \u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.wireshark.org/#download\"\u003eWireShark 2\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"硬件\"\u003e硬件\u003c/h3\u003e\n\u003cp\u003ePC利用 TwinCAT 作为主站,从站采用的是基于 ET1100 的从站开发板。\u003c/p\u003e","title":"利用 TwinCAT 3 搭建 EtherCAT 主站环境"},{"content":" 原文:How to decode cyclic PROFINET frames?\n 前言 IOPS IO Provider Status 表示 IO 数据的提供状态, 用来标识数据是否有效,避免应用建立一个另外的诊断信息请求,节省资源。\nIOCS IO Consumer Status 表示 IO 数据的消费状态。\n如何理解IOCS和IOPS 原文:A device mirrors the respective IOCS when sending the input data for the output data received from the controller, and the controller mirrors the IOPS for the received input data from the device when sending the output data.\n 详细内容出现在书《Industrial communication with PROFINET》- Manfred Popp\n介绍 以下文档描述了 PROFINET 周期性过程数据交换报文的结构。这些报文在 PRONFINET IO 设备和 PROFINET IO 控制器之间交换。通过常用工具 WireShark 的简单设置,这些帧能够被纪录下来。一个常犯的错误是单纯依靠 WireShark 的解析能力,通过它显示的一些信息来解析这些报文。不幸的是,旧版本的 WireShark 并不能很好的解析并且较新的版本需要一个合适的网络纪录来显示正确的值。下面提供了一个手动解析这些报文的方法。\nPROFINET 过程数据报结构 下图展示了一个 PROFINET 周期性过程数据报的一般结构:\n此结构基于使用 VLAN 标签的第二层以太网帧。当每个 PROFINET IO 设备和 PROFINET IO 控制器发送 VLAN 域的时候,有可能被中间的网络交换机移除掉。分析报文的时候这个需要考虑到。C_SDU 域包含需要传输的数据。带 VLAN 的以太网帧的最小长度是 64 字节,如果 C_SDU 域 的长度小于 40 字节会自动填补。APDU 状态域 包含周期计数和额外的状态字节。\nC_SDU 由两种类型的数据组成:\n IO 数据对象 IOCS 对象 每个数据项与一个特定的子模块相连。IO 数据对象包括过程数据和子模块的相关 IOPS。IOCS 对象仅仅包含子模块的 IOCS。一个 C_SDU 常常包含多个对象。在 C_SDU 中的过程数据的实际位置已经在连接开始的时候的 RPC Connect Service 中被参数化。在两个相邻的项目中可能被插入额外的填充值。数据项的结构由下图展示。通常 IOPS 和 IOCS 的长度为一字节。\n解析示例 下面通过一个例子描述整个过程数据报的解析过程。解析的帧是一个输入 IOCR。\n提取结构信息 第一步要从 RPC Connect Service 中分析出需要的结构信息。通过 WireShark 很简单就能完成。下图展示了 RPC Connect Request 和需要被用来解析一个 Input IOCR 报文的部分。红色框住的是 Input IOCR 的描述,包含了帧 ID 和在 C_SDU 里面的数据项的偏移值。相关的过程数据能从黄色框中的 Submodule Requests 提取。\n开始解析需要创建一个包含所有数据项偏移值的列表。这些信息从 IOCR Block Request 中提取。在本例中我们主要关注 Input IOCR。帧中帧 ID 是 0x8000。 Output IOCR 帧 ID 必须从 RPC Connect Response 帧中提取出来,Output IOCR 帧 ID 由设备分配。下图展示了期望的 Input IOCR 的 IOCR Block Request。\n根据上面的信息,我们创建了下表。\n C SDU Offset Kind Api Slot Subslot Length of Data Length of Item 0 IO Data 0 0x0001 0x0001 17 IO Data 0 0x0000 0x0001 18 IO Data 0 0x0000 0x8000 19 IO Data 0 0x0000 0x8001 20 IO Data 0 0x0000 0x8002 21 IOCS 0 0x0002 0x0001 - 现在所有的偏移值都知道了。下一步就是提取各项的大小。这些长度能从 Expected Submodule Blocks 描述的子模块中提取出来。第一个 Expected Submodule Blocks 由下图展示:\n从这些信息中我们能得到 API 0 和 Slot 0 数据项的长度。这边导入指针是为了检查正确的数据描述元素。每个子模块都能分配一个输入数据描述 Input-Data Description 和一个输出数据描述 Output-Data Description。对于 Input IOCR 来说 Input Data Description 和 IO Data 有关, Output Data Description 和 IOCS 项 有关。Output IOCR 反之亦然。在示例中所有的第一个 Expected Submodule Block 的子模块有 0 个输入数据,1 字节 IOPS 和 1 字节 IOCS。(IOPS/IOCS 长度通常为 1 字节。)\n C SDU Offset Kind Api Slot Subslot Length of Data Length of Item 0 IO Data 0 0x0001 0x0001 17 IO Data 0 0x0000 0x0001 0 0 + 1 18 IO Data 0 0x0000 0x8000 0 0 + 1 19 IO Data 0 0x0000 0x8001 0 0 + 1 20 IO Data 0 0x0000 0x8002 0 0 + 1 21 IOCS 0 0x0002 0x0001 - 我们用剩下的 Expected Submodule Blocks 信息完善表格:\n完成的表格:\n C SDU Offset Kind Api Slot Subslot Length of Data Length of Item 0 IO Data 0 0x0001 0x0001 16 16 + 1 17 IO Data 0 0x0000 0x0001 0 0 + 1 18 IO Data 0 0x0000 0x8000 0 0 + 1 19 IO Data 0 0x0000 0x8001 0 0 + 1 20 IO Data 0 0x0000 0x8002 0 0 + 1 21 IOCS 0 0x0002 0x0001 - 1 这表格包含了一些零数据长度的 IO Data。这些 IO Data 对象表明了在 PROFINET 中,一个没有任何过程数据的子模块都被认为是一个有 0 字节长度过程数据的输入子模块 Input Submodule。 解析过程数据报 最后一步是用上文创建的表格解析过程数据报。下图展示了一个 Input IOCR 的特定数据报。为了正确的选择解析不仅 Frame ID 需要加入计算,而且数据报的 Mac 地址 也要加入计算,因为在 RT Mode 下同样的 Frame ID 可能被不同的设备用到。下图中,帧 #106 被选择用来分析。\n蓝色标记的部分是包含实际过程数据的 C_SDU部分(包括填充)。基于我们的表格可以提取到如下数据:\n C SDU Offset Kind Api Slot Subslot Length of Data Length of Item Data Status(IOPS/IOCS) 0 IO Data 0 0x0001 0x0001 16 16 + 1 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10 0x01 0x00 0x00 0x00 0x00 0x8f 0xff 0x80 17 IO Data 0 0x0000 0x0001 0 0 + 1 - 0x80 18 IO Data 0 0x0000 0x8000 0 0 + 1 - 0x80 19 IO Data 0 0x0000 0x8001 0 0 + 1 - 0x80 20 IO Data 0 0x0000 0x8002 0 0 + 1 - 0x80 21 IOCS 0 0x0002 0x0001 - 1 - 0x80 状态值 0x80 表示相关的过程数据对于 IO Data 对象是有效的。对于 IOCS 对象,它表明相关过程数据的 “消费者” 在使用这个数据。(这个相关的过程数据发送方向相反,因此不是 IOCR 的一部分。换句话说,这个例子中 Slot 0x1 Subslot 0x1 是一个输入子模块 Input Submodule, Slot 0x2 Subslot 0x1 是一个输出子模块 Output Submodule。)\n备注 PROFINET 过程数据交换包括了一些额外的限制,设备必须遵守这些限制来确保适当的数据交换:\n 一些现有的 IO 控制器(例如 S7-300,S7-400)从 RPC Application Ready Request 之后不识别 IOPS 从 “BAD” 到 “GOOD” 的变化。如果遇到这种情况, IO 设备必须返回一个子模块报警 Submodule Alarm 给控制器。 期望的 IO 设备应该延后 RPC Application Ready Request 直到设备提供的 IOPS 和 IOCS 设置为 GOOD。然后再下发 RPC Application Ready Request。如果 IO 设备因为特殊的原因(比如 IO 设备传输了不合法的参数)没法设置特定子模块的 IOPS 为 GOOD,设备应该设置子模块状态到 application ready pending,为子模块增加诊断然后下发 RPC Application Ready Request。RPC Application Ready Request 会包含一个 Module Diff Block 指示该子模块的问题。一些时候它可能然后又可以设置子模块的 IOPS 为 GOOD,这个时候应用应该复位子模块状态,移除诊断再返回一个子模块报警。 PROFINET 过程数据模型 PROFINET 定义了一个生产者-消费者模型。过程数据由生产者产生由消费者接收。此外一个生产者状态和消费者状态被交换。观察角度不同 IO 控制器或者 IO 设备既可以是生产者也可以是消费者。下表试图更详细的解释这种关系。通常,过程数据从 IO 设备发往 IO 控制器被视为输入数据 Input Data,从 IO 控制器发往 IO 设备被视为输出数据 Output Data。表格的最后一列描述了 PROFINET IO Device V3.x Configuration Packet 变量和相关的响应元素的联系。蓝色行表明从 IO 设备发送到 IO 控制器的数据,绿色行表明从 IO 控制器发送到 IO 设备的数据。\n 本作品由 Jesse Guo 创作,采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。修改,参照或者转载请注明出处。\n","permalink":"https://tinyx.me/post/decode-PROFINET-frames/","summary":"\u003cblockquote\u003e\n\u003cp\u003e原文:\u003ca href=\"https://kb.hilscher.com/pages/viewpage.action?pageId=22830458\"\u003eHow to decode cyclic PROFINET frames?\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003ch3 id=\"iops\"\u003eIOPS\u003c/h3\u003e\n\u003cp\u003eIO Provider Status\n表示 IO 数据的提供状态,\n用来标识数据是否有效,避免应用建立一个另外的诊断信息请求,节省资源。\u003c/p\u003e\n\u003ch3 id=\"iocs\"\u003eIOCS\u003c/h3\u003e\n\u003cp\u003eIO Consumer Status\n表示 IO 数据的消费状态。\u003c/p\u003e\n\u003ch3 id=\"如何理解iocs和iops\"\u003e如何理解IOCS和IOPS\u003c/h3\u003e","title":"如何解析 PROFINET 周期性数据帧"}]