ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ubuntu下配置创芯CAN分析仪:SocketCAN驱动安装与Python收发实战

Ubuntu下配置创芯CAN分析仪:SocketCAN驱动安装与Python收发实战 干嵌入式的人大概率都有过这种体验Windows下用得好好的一台设备一到Ubuntu就麻烦了。尤其是CAN分析仪这种工控硬件厂商给的上位机工具多半只做了Windows版本Linux下要么靠系统自带的通用驱动撑着要么就得自己折腾。创芯科技的CAN分析仪在国产工控圈子里用得很广价格便宜、功能也够但它在Ubuntu下的驱动安装和调试资料极其零散官网上找半天可能也就一句“支持Linux”实际怎么搞全得靠自己试。这篇东西就是我把创芯科技CAN分析仪在Ubuntu下从插上没反应到正常收发数据的完整过程记录包括驱动加载、设备节点确认、SocketCAN配置、Python脚本收发包测试以及一路上踩过的各种坑。如果你也是搞ROS、搞车载、搞工控上位机需要在Linux环境里用CAN总线这篇文章可以直接当操作手册抄。1. 内容整体设计与思路拆解1.1 为什么在Ubuntu下用CAN分析仪这么绕很多人的第一反应是一个USB设备插到Ubuntu上系统不是应该自动识别吗这句话对大多数通用外设成立但对CAN分析仪这类工业级USB设备情况要复杂得多。创芯科技的CAN分析仪本质上是一个USB转CAN桥接设备它的工作逻辑是USB端负责和上位机通信CAN端负责和总线上的其他节点通信。Windows下有官方上位机软件USB端会被识别成专用设备由厂商驱动接管到了Ubuntu如果没有厂商驱动系统只能把它当作未知设备挂着什么也干不了。这里还有一个更深层的差异Windows下用户面对的是“装驱动用厂商软件”这一套而Ubuntu下哪怕驱动装好了你还得选择一种应用层访问方式。常见的有两条路——用创芯自己提供的Linux库或上位机另一条是把设备挂进内核的SocketCAN体系。前者依赖厂商对你的设备型号的持续维护后者则是Linux社区的标准玩法不受厂商限制还能直接用can-utils命令行工具和Python等脚本语言。我实际试下来SocketCAN这条路最靠谱ROS里用的can_msgs、canopen等栈也都是基于它做的社区生态完整后续自动化测试、与机器人控制节点集成都方便得多。所以这篇文章的设计思路就是先解决“设备能不能被系统认出来”这个底层问题再通过SocketCAN让设备变成标准的Linux网络接口can0最后用命令行和脚本验证收发官方工具和社区工具两头都能用。1.2 驱动安装的整体思路与方案选型在动手之前我们要搞清楚你手里的设备到底需要哪种驱动。创芯科技的CAN分析仪不同型号用的USB转CAN方案不同有的用ST的芯片方案有的用带CAN控制器的MCU模拟有的用经典SJA1000方案。这直接决定了驱动的形态如果USB端芯片比较常见比如CH341、FTDI这类内核里通常自带对应驱动模块插上就能识别如果是厂商私有方案那就得安装厂商发布的Linux驱动源码自己编译带dkms的模块。我建议按下面的步骤走这能避免很多无头苍蝇式的排查第一步不接设备先确认系统内核版本运行uname -a看看。第二步插入设备立刻执行dmesg | tail -n 50看内核日志里有没有新增的USB设备描述。第三步执行lsusb确认设备是否出现在USB设备列表中。第四步根据上面两步的结果判断驱动方向如果dmesg里直接出现了cdc_acm或者ftdi_sio类似的驱动名并且生成的节点是/dev/ttyACM0或/dev/ttyUSB0那恭喜你驱动几乎是现成的如果只有unknown device之类的提示就要去厂商源码库找对应的Linux驱动源码来编译。从我接触过的几台创芯设备来看大多数USB-CAN设备在Ubuntu下的USB识别层是可以被内核驱动的难点主要在两个地方一是设备节点出来后权限不够非要sudo才能访问二是厂商的Linux驱动内核模块能否匹配当前内核版本在内核升级后还能不能继续用。这两个问题我会在后面的章节里专门展开。顺带多一句嘴方案选型上如果你只是偶尔在Linux下用一次CAN分析仪那用厂商提供的库就够了但你要是跟我一样长期把Linux当主力开发环境个人强烈建议从一开始就走上SocketCAN这条路。SocketCAN把CAN接口做成网络接口之后你可以用Wireshark直接抓CAN包可以用Python的python-can库几行代码发帧甚至可以直接对接ROS的can_msgs后面做自动化、可视化都要省太多事了。2. 环境准备与设备识别2.1 硬件接线与内核先决条件在装驱动之前先确保物理链路是通的。很多“驱动装不上”的问题最后查到根本不是驱动而是CAN收发器供电不足、CAN_H和CAN_L接反或者总线两端没接终端电阻。我用创芯分析仪做自测时一般这样接如果可以回环测试直接把设备的CAN_H和CAN_L对接就能形成本地回环如果是接外部总线那要确保供电正常并在总线两端各并联一个120欧姆终端电阻。没终端电阻时报文在总线端反射会出现偶发帧错误或者根本收不到数据这个经验我在初学CAN时踩过好几次。驱动安装前还需要确认Ubuntu系统里装好了编译工具链。即便是用内核自带的模块你也需要install必要的包以防后面要手动编译或加载模块。打开终端执行sudo apt update sudo apt install build-essential linux-headers-$(uname -r) dkms git can-utils这里重点解释一下为什么需要内核头文件无论你最终是编译厂商驱动还是用dkms把驱动模块挂到内核模块体系里都需要内核头文件里的头文件和构建脚本。linux-headers-$(uname -r)会自动匹配当前内核版本避免你手误装错版本。dkms的作用是管理外部模块源码Ubuntu内核升级后它可以自动重编译驱动模块这是防止“一升级内核驱动就消失”的关键组件。can-utils是SocketCAN下游常用命令行工具包包含了candump、cansend、canplayer等后面调试全都靠它。2.2 设备枚举与日志排查的基本命令硬件连接好、环境装好后插入设备马上观察系统反应。这里有一个实操心得不要只看lsusbdmesg才是判断驱动状态最直接的窗口。插入设备后优先执行sudo dmesg -c # 插入设备后 dmesg | tail -n 50正常情况你会看到类似这样的输出USB设备被识别提示“new full-speed USB device”然后出现一个驱动名比如cdc_acm或者类似can字符的关键字。如果中间有一句“no manufacturer’s product”那就说明系统对这颗USB控制芯片没有现成的驱动需要人工介入。接着用lsusb确认USB层的PID/VID编号。执行lsusb输出里会列出所有USB设备找到创芯设备对应的一行记录下ID xxxx:xxxx前面的厂商ID和产品ID。这个信息在后面的udev规则和模块加载配置里会反复用到。如果设备根本没出现在lsusb里那要先查USB线缆和供电不少USB分析仪对供电比较敏感插在机箱前置面板或者USB Hub上因为压降导致完全不被识别的情况非常常见建议插主板后面板并优先用原装线。2.3 设备节点生成与udev权限规则设备被内核识别后通常会生成串口或字符设备节点。绝大多数USB-CAN设备会出现在/dev/ttyUSB*或者/dev/ttyACM*也有的会出现在/dev/can*。可以用下面的命令查看是哪个节点ls -l /dev/ttyUSB* /dev/ttyACM* /dev/can* 2/dev/null如果没有输出说明驱动还没加载完全回到2.2节继续排查。如果生成了节点下一步必须处理权限。不处理的话你后续每次访问设备都要加sudo写脚本时特别烦。直接写一条udev规则让普通用户也能读写设备sudo nano /etc/udev/rules.d/99-usb-can.rules文件内容KERNELttyUSB*, SUBSYSTEMtty, MODE0666 KERNELttyACM*, SUBSYSTEMtty, MODE0666 KERNELcan*, SUBSYSTEMnet, MODE0666保存后执行sudo udevadm control --reload-rules sudo udevadm trigger有人会问直接chmod 666不也一样吗不一样。chmod只对当前节点生效拔插一次设备节点如果是重新创建的权限就回去了udev规则是当设备节点创建时自动触发的属于一劳永逸的做法。顺带补充一个技巧如果设备节点名每次都变化比如一会儿ttyUSB0一会儿ttyUSB1可以在udev规则里用设备的序列号或者ATTR{idVendor}、ATTR{idProduct}创建固定符号链接SUBSYSTEMtty, ATTR{idVendor}1234, ATTR{idProduct}5678, SYMLINKcananalyzer, MODE0666这个技巧对以后写脚本或者配置服务非常有用固定路径能避免设备插拔顺序带来的麻烦。3. 驱动安装与SocketCAN配置3.1 原生SocketCAN驱动的确认与加载前面提到如果设备本身被udpate内核驱动模块接走了那说明它很可能直接被映射成了一个串口设备。但这只是一个广义的“串口”并不是说SocketCAN就能直接用。此时你要做一个判断这个设备是否支持进入SocketCAN模式不少USB-CAN设备在插入时内核会注册一个网络接口接口名形如can0有的则只有一个ttyUSB节点需要先用厂商的驱动把USB串口映射成CAN网络接口。最容易判断的方法还是看内核日志。插入设备后执行dmesg | grep -i -E can|socket|net如果输出里面出现了can0或者类似“registering can device”这样的描述那说明内核已经把设备挂进了SocketCAN下面的配置直接跳转3.3节。如果只出现了ttyUSB那说明驱动层只把它当串口设备处理这时候要么找厂商的Linux驱动包它里边通常会带入SocketCAN适配层要么自己写一个基于SocketCAN的适配。从我遇到的情况看不少创芯设备是有Linux SocketCAN适配的只是厂商不主动宣传你需要到厂商的下载页面找“Linux驱动”或者“SocketCAN支持”字样下载下来后按照里面的README编译。这里要补充一个关键概念SocketCAN为什么能把CAN设备变成网络接口因为Linux的网络协议栈是抽象得很干净的网卡接口对上下层只暴露open/close/start_xmit这些操作CAN控制器驱动只要实现这些接口就能被内核当作一个“链路层为CAN”的虚拟网卡来管理。这也是后来ip link set can0 up type can能生效的根本原因。理解了这一步你后面看到ifconfig或者ip命令操作CAN接口时就不至于一头雾水了。3.2 厂商驱动源码的编译与dkms安装实操如果你的设备确实需要厂商驱动那么最稳妥的做法是下载源码包用dkms管理起来。我先说为什么要用dkmsUbuntu每隔一段时间就会升级内核而外部驱动模块是为特定内核版本编译的一旦内核升级旧模块就加载不上了总不至于每升一次内核就去重编译一次。dkms会监听内核版本的变化在新内核安装后自动重新编译注册过的模块它解决的问题是“驱动能不能活过系统升级”。假设你已经下载好厂商的驱动源码包解压后会看到Makefile、*.c、*.h这类文件。先看一下README确认支持的内核范围。编译安装的流程一般是sudo make sudo make install如果是支持dkms的源码包一般会有dkms.conf配置文件可以这样注册sudo dkms add . sudo dkms build -m module_name -v version sudo dkms install -m module_name -v version编译前可以把内核头文件再确认一遍最稳妥的做法是在内核源码树对应的/usr/src/linux-headers-$(uname -r)里执行编译。如果编译时报错找不到autoconf.h多半就是内核头文件没装全或者版本没对上。厂商源码编译偶尔会遇到的一个坑是源码里用了旧版内核的API新内核里已经被删掉了。这种就属于源码兼容性问题你没有太多办法只能回退内核版本、等厂商更新、或者自己补一下API。如果在工控项目里遇到这个局面个人建议直接把内核锁在一个能用且足够稳定的版本上用apt-mark hold linux-image-generic锁定内核版本避免更新时把驱动搞挂。把这招写进你的项目维护手册比现场挨个踩坑强。3.3 SocketCAN接口配置与波特率设置不管驱动是哪条路装上的最终SocketCAN都会以can0这样的网络接口形态出现在系统里。用下面的命令查看当前是否有CAN口ip link show看到can0后设置波特率并启动它。以500kbit/s为例sudo ip link set can0 type can bitrate 500000 sudo ip link set can0 up这三句话实际上完成了三件事把can0的波特率配置成500k、把接口状态置为可用、让它开始监听总线。务必要先down再改参数很多新手会忘记这个顺序直接在接口up的状态下ip link set can0 type can bitrate 250000结果配置不生效甚至提示错误sudo ip link set can0 down sudo ip link set can0 type can bitrate 250000 sudo ip link set can0 up关于波特率这里多说一点。CAN总线有一个经典位时间和采样点概念波特率只是表面参数它由位时钟 / (1 段1 段2 同步段)具体计算得出不同波特率对应不同的采样点位置。SocketCAN的默认配置已经能应付绝大多数场景但如果你调试的节点对采样点特别敏感比如要求75%采样点用于提高总线长距离抗干扰能力可以通过sample-point参数设置sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 sample-point 0.750 sudo ip link set can0 up这个参数的意思是CAN控制器在一个位时间内的采样时机合理设置能有效应对信号反射带来的位错误。平时自己玩用默认值就行搞车载总线时再细调。配置完后用ip -details link show can0可以看到当前的波特率和状态确认它显示can state ERROR-ACTIVE。4. 上位机调试工具与实测验证4.1 can-utils命令行工具实战设备起来之后先在另一个终端里启动监听然后想办法发一个数据帧这是最快的连通性验证方式。can-utils里最常用的组合是candump接收、cansend发送candump can0另外一个终端执行cansend can0 123#DEADBEEF如果物理链路没问题candump窗口会立刻打印一条报文比如can0 123 [8] DE AD BE EF 00 00 00 00。要注意的是CAN报文的标准帧ID是11位扩展帧ID是29位。123#DEADBEEF这个写法里123是报文ID#后面跟的是数据字节的十六进制表示两个字符为一个字节。如果ID超过0x7FF就要用12345678#DEADBEEF这种带扩展帧前缀的写法。SocketCAN的发送是即发即走不会像网络层那样有重传机制。所以如果你发现cansend没有报错但对面接收不到那基本就是物理层问题接线反了、没终端电阻、波特率不匹配或者是同一个网络上有两条CAN总线用了不同波特率。这种问题在命令行工具里排查起来反而高效因为每一步都可单独验证。candump还有几个实用选项值得记住。-x可以显示收发方向-n 10可以限制只收10帧就退出-L会把时间戳精确到微秒-s 0是静默模式只收不发。做帧间隔和数据量测试时配上canbusload工具就能大致算总线负载率非常好用。4.2 Python脚本收发数据的实用模板如果只是验证连通性命令行走一遍就够了但如果要模拟某个CAN节点、做自动化数据采集那就得上脚本。Python搭配python-can库是最常见的做法。先安装pip3 install python-can然后写一个最简收发demoimport can bus can.Bus(interfacesocketcan, channelcan0, bitrate500000) print(fdevice: {bus.interface}, channel: {bus.channel}) # 普通数据帧 msg can.Message(arbitration_id0x123, data[0x11, 0x22, 0x33], is_extended_idFalse) bus.send(msg) print(sent:, msg) # 接收阻塞5秒 resp bus.recv(timeout5) if resp: print(received:, resp) else: print(timeout, no message)这四行是整个CAN上位机开发里最核心的骨架。常见的CANopen心跳报文、远程帧请求这些都可以在can.Message的参数里调整。比如要发远程帧设置is_remote_frameTrue数据段为空即可remote can.Message(arbitration_id0x123, is_remote_frameTrue, data[]) bus.send(remote)基于这个骨架你可以很快扩展出按周期发报文、记录报文到CSV、解析DBC信号、响应某类ID过滤等功能。还有个小技巧python-can里可以注册Notifier对象把上来的报文异步推给多个监听回调这样不用自己开线程在那里轮询非常适合多节点逻辑处理。4.3 与Windows上位机联调时注意的隐藏差异实际操作中很多人会遇到一个场景Ubuntu这边用SocketCAN收发一切正常可是和Windows下的创芯上位机一联调就各种对不上最常见的是报文ID对不上、字节序混乱。这个不是驱动问题而是协议层理解差异。CAN报文的ID、长度、数据都是纯十六进制本身不带任何工程单位上位机软件通常自带一套协议映射比如某个ID代表电机转速它会把4个字节拼成int32这时候高低字节顺序就决定了解读结果。另一个值得注意的点是同一台CAN分析仪在Windows下被厂商驱动独占在Ubuntu下被SocketCAN驱动独占两边不能同时打开同一个设备。如果你心很大搞了双系统或者虚拟机直通想同时读同一路CAN那基本是行不通的。解决思路很简单买两台分析仪一台挂Windows上位机一台挂Ubuntu做日志采集两边互不干扰或者干脆放弃Windows上位机直接用Ubuntu下的工具所有数据都走统一的格式落盘。我在联调中还发现一个很有价值的细节Windows上位机的CAN波特率配置界面通常预设标准波特率列表而Linux的SocketCAN可以设置任意整数波特率也支持设置brp、tseg1、tseg2等细分参数。如果两边配置的波特率最终得到的采样点不一致总线在高负载时就会出现偶发的位错误。遇到这类情况优先把两边的采样点都统一到接近的位置再排查别的问题。5. 常见问题与排查技巧实录5.1 设备插入后无反应或识别不到这是所有驱动问题的第一步坎。遇到设备插入后lsusb都看不到设备时注意力不要先放到驱动上先检查物理链路换一个USB口最好插主板后方USB口排除供电不足和接触不良换一根短的、品质好的USB线很多工业设备用的线在Windows下勉强能用在Ubuntu下加电时序稍微不对就认不出来了。确认是线材问题后再考虑静电或者电平干扰。如果lsusb能看到设备但dmesg里没有任何驱动加载的提示那就是USB控制芯片不被内核支持。此时有三种办法第一查创芯官网是否提供了Linux驱动第二查这种USB转CAN方案是否被某个第三方内核模块支持比如有些设备其实用的是某颗常见芯片马甲试加载相应模块可能就通了第三检查一下设备是否支持“回环模式”有些分析仪带一个拨码开关或跳线用来切换工作模式比如从“上位机通信”模式切到“CAN转发”模式不同模式下USB描述符会不一样这就可能造成Linux下行为不一致。一个非常实用的排查习惯是每次插入设备前后都看一眼dmesg输出并且记住“插上设备那一刻的日志”和“拔掉设备那一刻的日志”各是什么。因为正常驱动的设备在拔掉时也会打一行“usb X-X: USB disconnect”之类的日志从这个细节可以判断设备枚举是否完整。5.2 编译驱动时报错找不到内核头文件内核头文件缺失是Linux下编译驱动最常见的报错。报错信息通常形如fatal error: linux/autoconf.h: No such file or directory或者说Kernel source tree not found。这种问题一看就可以锁死方向把内核头文件装上就好。sudo apt install linux-headers-$(uname -r)如果你升级过内核但是重启后还是老内核那么uname -r显示的版本和已安装头文件版本可能对不上。检查一下系统里有哪些头文件包dpkg -l | grep linux-headers确保头文件版本和当前运行内核版本一致。如果不一致要么重启到新内核要么再装对应版本头文件然后重新编译。用dkms管理时还会遇到一种情况dkms build报错提示源码目录的KVER不对排查时注意dkms.conf里的PACKAGE_VERSION和源码包的版本号是否一致别把版本号写错了否则也会表现为找不到内核头文件。顺带分享一个冷门但救过命的命令/lib/modules/$(uname -r)/build这个软链接应该指向/usr/src下的内核头文件目录如果它断了很多构建脚本会莫名奇妙失败。可以用ls -l检查这个软链接断了就手动重新创建sudo ln -s /usr/src/linux-headers-$(uname -r) /lib/modules/$(uname -r)/build5.3 能识别却发现不了can0或无法通信如果驱动加载成功dmesg里也有设备节点但ip link看不到can0这通常是SocketCAN适配层没有初始化成功。可以尝试先卸载再加载厂商模块或者检查是否有其他内核模块抢占了设备。用lsmod查看模块加载情况再对比驱动文档确认到底该加载哪个模块、顺序有没有要求。设备节点生成了、can0也出现了但cansend发送后对面毫无反应优先级从高到低排查第一波特率是否真的匹配两侧是不是500k对500k第二总线接线是否正确CAN_H接CAN_H、CAN_L接CAN_L别交叉第三终端电阻是否加上没有终端电阻在低频噪声环境下也可能收发一切正常但波特率稍高、线长稍长就会出奇奇怪怪的偶发错误第四是不是有别的进程占用了CAN口比如之前某个脚本还开着SocketCAN同一时间只允许一个进程把socket绑定到同一CAN接口。遇到这种情况杀掉残留进程再试。还有一个特别容易踩的坑虚拟机里的Ubuntu。如果你是在VMware或VirtualBox里插USB设备要先确保虚拟机的USB控制器设置了“USB 3.0”或对应协议并且把设备从主机断连、重新连接到虚拟机。如果虚拟机里能看到设备节点但收发不稳定多半是USB直通的兼容性问题优先级排在使用物理机安装系统之后。做CAN总线这种对实时性有些要求的调试个人建议直接在物理机上装Ubuntu或者至少把USB控制器直通给虚拟机而不是依赖于默认的USB模拟层。5.4 内核升级后驱动失效的问题这是Linux下最难缠的一个问题。原本驱动好好的某天你执行了sudo apt upgrade重启之后lsmod里驱动模块没了can0接口也消失了。原因就是内核升级导致外部编译模块失效。解决办法就是之前提到的dkms。如果你的驱动是用make make install装的那么升级内核后你需要回到源码目录重新make install再modprobe加载。既然已经编译进去了为什么不能直接modprobe因为旧模块是为旧内核编译的新内核的ABI不保证兼容很多内核API会变不能直接用。提前预防的正确姿势是装驱动时就走dkms注册流程。这样当内核升级包安装时dkms会自动为新内核重编译模块。哪怕厂商源码没有现成的dkms.conf你也可以手动补一个简单的配置其实就是两行内容MODULE_NAME和MODULE_VERSIONbuild和install时候把源码复制到/usr/src下。这个做法能省掉很多重启后救火的活。如果驱动已经失效也不想去搞dkms那可以先试试黑名单旧模块、加载备选模块的方式应急。很多USB-CAN设备其实同时具备通用串口模式和专用模式如果SocketCAN模式失效用力模式打开ttyUSB节点配合自己写个简单的串口转CAN协议解析也能顶一阵子。当然这只是应急正式做项目还是要妥善解决驱动长期维护的问题。5.5 其他隐藏较深的坑一个是udev规则写错导致设备节点起不来。注意SUBSYSTEM和KERNEL的值不能乱猜比如有些设备节点是ttyUSB*有些是ttyACM*而SocketCAN网络接口归属于SUBSYSTEMnet不是net写成了netdev这样的小错误权限规则就不生效。改完udev规则后先udevadm control --reload-rules再用udevadm trigger如果还是不行可以udevadm monitor实时观察设备事件看规则有没有被正确解析。另一个坑是USB线太长导致枚举不稳定。USB2.0全速设备一般也就5米线长极限工业现场经常接延长线但CAN分析仪这类设备对电源噪声很敏感用长线会表现为插上时好时坏。我实测过用手头一根3米左右的普通USB延长线设备初始化偶尔失败换短线和带屏蔽的USB工业线后问题再没出现过。还有一个关于多实例的问题如果你的Ubuntu机器上同时插了两台创芯CAN分析仪SocketCAN会给它们分配can0和can1两个接口。但USB串口设备的命名顺序不一定稳定哪台是can0哪台是can1取决于插入顺序和内核枚举顺序。为了不让脚本认错设备建议用udev按设备的ID_SERIAL或者物理端口位置把固定的符号链接绑定到每个设备上这个我在2.3节已经示范过千万不要吝啬这一步真到现场发现脚本控制错设备的时候只能干瞪眼。最后说一下CAN总线上的电源地问题。很多CAN分析仪上的CAN接口是隔离的但也有不隔离的型号。如果分析仪和被测设备各自用独立电源供电而两边地电位差比较大接上CAN线后可能出现偶发错误帧甚至烧毁收发器。严谨的接法是让两个设备之间保持共地。这个在实验室调试时很容易忽略——两边的220V电源来自同一个插线板地电位接近怎么测都好使一旦拿到现场两套设备来自不同电源系统地电位差拉起来问题就频发了。看到这类型的“偶尔坏帧”问题优先测一下CAN_H和CAN_L之间、以及CAN_GND和USB_GND之间的直流电压经验值在几百毫伏以内一般安全超过2V就要好好处理共地了。在实际项目里我的建议是不要等出了问题再研究Linux下驱动怎么装而是把整套环境提前在虚拟机里验证好记录成团队知识库文档并把驱动、udev规则、脚本都纳入版本管理。这一套搞下来后续无论是换内核、换机器还是搬场地都能快速恢复环境不用现场再走一遍这些坑。
返回列表