ARTICLE DETAIL

资讯详情

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

Ubuntu 18.04动态库搜索路径与可执行文件PATH配置指南

Ubuntu 18.04动态库搜索路径与可执行文件PATH配置指南 Ubuntu 18.04 上添加动态库搜索路径和可执行文件 PATH是个老生常谈但几乎每个月都能在群里看到有人卡住的话题。十次里有八次编译乱成一团最后程序起不来说一句error while loading shared libraries你对着屏幕发呆不知道系统到底在哪个环节把库丢了。这篇文章我打算把“加路径”这件事一次性拆透动态库该怎么加、可执行文件路径该怎么加、临时和永久分别用哪个配置每一步背后的加载机制是什么以及我这些年踩过的那些坑和排查习惯。适合所有在 Ubuntu 18.04 上做开发、部署第三方库或自制工具的人不管你是刚开始配环境的 C 新手还是在服务器上折腾推理服务的老工程师都能从这里拿到直接能用的方案。先说一个最常见的场景。你从源码编译了一个程序编译时用了-L/path/to/libs指定链接路径链接成功可一运行就报找不到共享库。原因很简单编译时的-L只告诉链接器“链接时去哪找”运行时动态链接器ld.so根本不看-L它有自己的搜索规则。这就是很多人第一次接触动态库路径配置时的困惑来源。理解这一点后面的所有配置方法就都有了逻辑基础。1. 为什么需要手动加路径先搞清系统是怎么“找”东西的1.1 动态库的搜索顺序与默认目录Linux 下程序启动后由动态链接器负责加载它依赖的.so文件。这个链接器在 Ubuntu 18.04 上是ld-2.27.so通常通过/lib64/ld-linux-x86-64.so.2路径调用。它的查找顺序大致是编译时写进二进制文件的RPATH/RUNPATH路径然后是环境变量LD_LIBRARY_PATH指定的目录接着是ldconfig生成的缓存文件/etc/ld.so.cache最后才是系统默认目录/lib、/usr/lib以及多架构目录/lib/x86_64-linux-gnu、/usr/lib/x86_64-linux-gnu。之所以“找不到”绝大多数情况下是因为你的.so放在 /opt、/usr/local/lib 这类非默认目录下又没被ldconfig收录。可以用ldconfig -p查看当前缓存里有哪些库。比如我想确认系统认不认识 libonnxruntime.so就执行ldconfig -p | grep onnxruntime如果输出为空说明链接器根本没把它纳入搜索范围运行时报错是必然的。打个比方动态链接器就像你手机上的应用商店它只从自己已收录的列表里找 App。你手动下载的 APK 不管放在哪个文件夹不“安装入库”之前商店都不会在搜索结果里展示它。Linux 的“入库”动作就是ldconfig。1.2 可执行文件的 PATH 查找机制可执行文件的查找逻辑比动态库简单得多但也有人在这上面浪费过时间。你在终端敲一个命令Shell 默认按PATH环境变量里列出的目录顺序逐个查找同名文件找到第一个就执行。如果全部找完都没有就报command not found。Ubuntu 18.04 的默认 PATH 通常包含/usr/local/sbin、/usr/local/bin、/usr/sbin、/usr/bin、/sbin、/bin以及你用户目录下的~/.local/bin如果存在。第三方软件如果装在/opt/xxx/bin、~/tools这类目录下系统自然找不到。这时候要么把对应目录加进 PATH要么在已在 PATH 里的目录下做软链接两种思路我会在第三章详细说。动态库和可执行文件的查找机制有本质区别前者依赖ld.so的动态加载规则后者依赖 Shell 的环境变量 PATH。所以你以为“把目录加进 PATH 就万事大吉”对可执行文件没错但对动态库完全无效——很多人在这一步误入歧途。2. 动态库路径配置临时、用户级、系统级三个层次2.1 临时生效LD_LIBRARY_PATH 的适用场景与陷阱最简单粗暴的方式是设置LD_LIBRARY_PATH。它告诉动态链接器在搜索缓存之前先去这些目录里找库。用法如下export LD_LIBRARY_PATH/opt/onnxruntime/lib:$LD_LIBRARY_PATH ./your_program注意我特意保留了后面的:$LD_LIBRARY_PATH这是防止把已有的路径覆盖掉。如果你写成export LD_LIBRARY_PATH/opt/onnxruntime/lib就彻底丢弃了原来环境变量里可能存在的其他路径这在复杂的开发环境里是个隐患。这种方式的生效范围只有当前终端会话关掉终端就失效。所以它适合临时验证不确定路径对不对、只跑一次、不想污染环境的时候最合适。但我不建议日常开发长期依赖它因为每次打开新终端都要重新 export而且它有个比较坑的特性——优先级比ld.so.cache还高。这意味着如果 LD_LIBRARY_PATH 里存在同名但版本不同的库系统会优先加载这个版本从而掩盖掉你辛辛苦苦配置好的 ldconfig 结果。2.2 用户级持久化写入 ~/.bashrc 要理解加载时机如果你希望某个用户每次打开终端都能自动带上某个库目录最直接的做法是把 export 命令写进~/.bashrcecho export LD_LIBRARY_PATH/opt/onnxruntime/lib:$LD_LIBRARY_PATH ~/.bashrc source ~/.bashrc这里有一个很多人没搞明白的细节bash 读取配置文件是有分层的。~/.bashrc是交互式非登录 Shell 启动时读取而~/.profile或~/.bash_profile是登录 Shell 启动时读取。Ubuntu 桌面环境下打开终端默认是交互式非登录 Shell所以写进.bashrc一定能被终端读取但如果你通过 SSH 登录、或者在图形桌面启动某些应用程序情况就不一样了。举一个我实际遇到的案例。有人把库路径写进 .bashrc 后终端里跑程序一切正常但通过桌面图标启动的 GUI 程序还是报找不到共享库。原因就是图形会话的进程不是从 bash 启动的根本不读 .bashrc。如果遇到这类情况要么把配置改成系统级要么在.desktop启动文件里手动加上LD_LIBRARY_PATH环境变量。用户级配置还有个局限它只对配置的这个用户有效。如果部署服务时使用 systemd 或 Docker服务的环境变量又是另一套体系bashrc 里的内容完全不会被读取到。所以判断“用户级够不够用”时先想清楚程序是由谁来启动的。2.3 系统级持久化/etc/ld.so.conf.d ldconfig 的标准姿势真正意义上的“入库”操作是修改/etc/ld.so.conf.d/下的配置文件然后执行sudo ldconfig。这是我最推荐的生产环境做法也是官方文档里明确推荐的方式。具体步骤sudo tee /etc/ld.so.conf.d/onnxruntime.conf EOF /opt/onnxruntime/lib EOF sudo ldconfig执行完ldconfig后系统会扫描所有 conf 文件里的目录、生成新的/etc/ld.so.cache文件把对应路径下的动态库登记进缓存。之后任何用户、任何进程启动相关程序都能正确找到库。这里要特别强调三个坑。第一写进 conf 文件的必须是绝对路径写相对路径不报错但也不生效纯属自欺欺人。第二新加的库路径要重新执行ldconfig才会被扫描如果改了配置文件忘了执行依然找不到。第三不要轻易删掉自带的 conf 文件比如libc.conf里通常包含了/usr/local/lib如果你删掉它大量安装在/usr/local/lib下的库都会失联系统里一半第三方软件都会出问题。我习惯在执行ldconfig之前先看一眼现有配置ls /etc/ld.so.conf.d/确认没有重复路径然后执行sudo ldconfig -v观察扫描输出确认新加的目录被正确读取。这个-v参数虽然输出很啰嗦但能第一时间发现路径不存在之类的低级错误。3. 可执行文件路径配置PATH 修改与软链接方案3.1 PATH 的临时设置与持久化写法给可执行文件添加路径最常见的就是修改 PATH。临时设置同样用 exportexport PATH/opt/onnxruntime/bin:$PATH这里把新目录放在最前面意味着系统会优先找到这个目录里的同名命令。这个顺序很重要如果你把/opt/xxx/bin放在 PATH 末尾而/usr/bin里恰好有个同名工具系统会执行老版本你会陷入“明明配置了怎么不生效”的困惑中。持久化写法和 LD_LIBRARY_PATH 类似写进~/.bashrcecho export PATH/opt/onnxruntime/bin:$PATH ~/.bashrc source ~/.bashrc但 PATH 的修改有个特殊风险命令本身依赖 PATH。如果你在 export 的时候没有保留原来的$PATH终端立刻会找不到 ls、cat 等基础命令。我见过有人手滑写成export PATH/opt/xxx/bin回车之后眼前一片command not found最后只能靠/bin/ls这种绝对路径慢慢抢救。所以写 PATH 时刻记住一个原则永远带上:$PATH尾巴。Ubuntu 环境里还有一个配置文件叫/etc/environment有些人喜欢把 PATH 写在这里。这个文件的格式不允许写变量引用比如$PATH并且修改后要重新登录才生效而且只对登录会话有效对 systemd 服务也没有直接作用。我个人认为在没有特殊需求的情况下PATH 的正常修改就写 .bashrc 或 /etc/profile.d/没必要碰 /etc/environment可维护性反而更好。3.2 软链接另一种省事的思路很多第三方工具自带的动态库和可执行文件都躺在同一个目录下比如/opt/xxx/bin。如果这个目录只放一两个你常用命令完全没必要去改 PATH直接做软链接就行sudo ln -s /opt/onnxruntime/bin/onnxruntime_cli /usr/local/bin/onnxruntime_cli为何默认选/usr/local/bin因为它在 PATH 的默认范围里而且是apt安装之外的本地软件惯例位置。把软链接放进去所有用户、所有终端都能直接执行同时不污染 PATH 的条目数量也方便日后通过ls -l /usr/local/bin/onnxruntime_cli快速确认链接指向。做软链接时要注意两个小问题第一如果目标已经在/usr/local/bin存在ln 会报文件已存在需要先确认旧文件是谁、再决定是否删除后重建。第二软链接不会自动处理动态库依赖可执行文件可能还依赖同一目录下的.so文件你只链接了命令本体库还是得靠第二章里的方式配好路径。一个常见组合拳是软链接解决命令入口ldconfig 解决库路径两者配合才算完整。还有一个容易被忽略的细节bash 会缓存已解析的命令路径。如果你刚做完软链接在当前终端直接执行命令bash 可能还记着之前的command not found结果。执行hash -r刷新一下命令缓存这个副作用就能清除。4. 实操流程以 onnxruntime 动态库为例完整走一遍4.1 场景设定与编译运行失败现场假设你要在一个实际项目里用 ONNX Runtime 做推理从官网下载了针对 Ubuntu 的预编译包解压到/opt/onnxruntime目录结构大致是/opt/onnxruntime/ ├── include/onnxruntime/core/session/onnxruntime_cxx_api.h ├── lib/libonnxruntime.so.1.15.1 └── lib/libonnxruntime.so - libonnxruntime.so.1.15.1你写了一个 C 推理程序 test_ort.cpp编译命令如下g test_ort.cpp -I/opt/onnxruntime/include -L/opt/onnxruntime/lib -lonnxruntime -o test_ort编译阶段大概率是成功的因为-L指定了链接搜索路径。但运行./test_ort时报错./test_ort: error while loading shared libraries: libonnxruntime.so.1.15.1: cannot open shared object file: No such file or directory这个报错非常典型后台原因就是运行时链接器在LD_LIBRARY_PATH、ld.so.cache、默认目录里都没找到这个库。接下来按三套方案处理。4.2 三种解决方案的选择与执行细节方案一临时验证推荐第一次先做。用LD_LIBRARY_PATH直接跑LD_LIBRARY_PATH/opt/onnxruntime/lib ./test_ort如果程序成功跑起来说明库文件本身没问题只是路径没进搜索列表问题定位干净利落。方案二用户级配置。如果你是在开发机上长期调试写入~/.bashrc最方便echo export LD_LIBRARY_PATH/opt/onnxruntime/lib:$LD_LIBRARY_PATH ~/.bashrc source ~/.bashrc ./test_ort方案三系统级配置生产环境首选。创建 conf 文件并刷新缓存sudo tee /etc/ld.so.conf.d/onnxruntime.conf EOF /opt/onnxruntime/lib EOF sudo ldconfig ./test_ort为什么生产环境我会优先选方案三因为服务通常由 systemd 托管systemd 里可以配置EnvironmentLD_LIBRARY_PATH...但每个服务单独配容易漏而且以后换机器部署时也容易忘。把库路径写进系统级的/etc/ld.so.conf.d/相当于让整台机器“认识”这批库任何用户任何服务都能直接用最符合 Linux 的惯例。验证配置是否生效有三条命令# 查看缓存里是否登记了 onnxruntime ldconfig -p | grep onnxruntime # 查看程序实际解析到的库路径 ldd ./test_ort | grep onnx # 确认链接指向是否正确 readelf -d ./test_ort | grep -E RPATH|RUNPATH最后那条 readelf 命令用来检查二进制文件编译时是不是已经写死了 RPATH。如果编译时用了-Wl,-rpath,/opt/onnxruntime/lib运行时链接器会优先从这个写死的路径找库即使没有配置系统路径也能跑。但这样做的缺点是路径被焊死在文件里换个安装目录就失效所以大多数情况下不推荐初学者用 rpath 解决这个问题。4.3 跨机器拷贝后的 GLIBC 兼容性提醒在配置动态库路径时很多人会忽略一个更麻烦的坑库文件本身能找得到了但程序启动时报GLIBC_2.28 not found。Ubuntu 18.04 内置的 glibc 是 2.27 版本如果你从别处拷贝的 .so 是在 glibc 2.28 以上的系统编译的系统就会报这个错。这类问题的排查方法是用strings检查库文件依赖的 glibc 符号版本strings /opt/onnxruntime/lib/libonnxruntime.so.1.15.1 | grep GLIBC_ | sort -u | tail如果看到GLIBC_2.28一类的版本号Ubuntu 18.04 是跑不起来的。正确做法是找针对 18.04 编译的预编译包或者拿源码在目标系统上重新编译千万别试图手动升级 glibc——glibc 是系统最底层的组件手动替换极易让所有命令连 ls 都执行不了机器直接变砖。5. 常见问题速查与我的排查套路5.1 高频问题对照表我把这些年用户问得最多的问题整理成一个速查表按“现象、原因、处理”三列写清楚。现象常见原因处理方式终端里能跑双击或桌面图标打不开图形程序没读取 ~/.bashrc改用 /etc/ld.so.conf.d 或修改 .desktop 里的 Environmentsudo 执行程序时找不到库sudo 默认清理 LD_LIBRARY_PATH使用sudo -E保留环境变量或配置系统级 ldconfigldconfig 后仍然报找不到LD_LIBRARY_PATH 里有同名旧库抢先或 ldconfig 没执行成功用ldconfig -v验证新目录用ldd看实际解析路径注意搜索优先级新开终端生效当前终端无效当前 Shell 环境变量未刷新执行source ~/.bashrc或重新打开终端PATH 改完 still command not foundhash 缓存残留路径顺序不对软链接没建对先hash -r再用which和type -a确认实际找到的位置程序找不到版本号为“符号链接后缀”的库只做了 .so 链接但缺 .so.版本号 的实际文件确认 .so 真实文件存在且软链接指向真实文件名5.2 从 ldd 结果里读出问题根因碰到任何“共享库找不到”的报错我的第一反应永远是执行ldd ./your_program。这个命令会递归列出程序依赖的所有动态库以及它们被解析到的绝对路径。如果某一行显示not found问题就锁定在这个库上如果显示某个路径但你知道那个是旧版本目录那就是优先级问题。还有一种情况比较隐蔽库文件存在、路径也配置了但程序报的错是权限不够。这时用ls -l查看库文件的权限位如果有程序是在单独用户下运行的比如服务账号需要确保其他用户对该文件有读权限。不少生产事故最终定位到的是 chmod 权限问题而不是路径配置问题。readelf -d这个命令在排查 RPATH/RUNPATH 时非常好用。它可以告诉你程序编译时是否写死了库路径避免你反复配置环境变量却一头雾水。我通常在给他人代码排查时先看这个字段再决定是否需要查 ldconfig 缓存。5.3 一个我总结的“三步定位法”第一步判断问题出在执行入口还是动态库加载直接运行程序如果报的是command not found那是 PATH 问题如果报的是error while loading shared libraries那是库配置问题。这一步把两类配置分开避免在错误的方向上白费力气。第二步确认库文件是否真实存在且完整执行file /opt/onnxruntime/lib/libonnxruntime.so.1.15.1查看架构信息和文件类型。如果是 32 位库装到了 64 位系统或者文件本身是个损坏的空文件再改路径配置也没用。第三步用最小代价验证路径临时设置一次LD_LIBRARY_PATH如果你一跑就通说明路径思路完全正确剩下的只是选择一个持久化的方式而已。如果临时设置都不通那要继续排查依赖链比如这个库本身又依赖了别的库一层一层往下追。5.4 防止把配置搞炸的备份习惯在改/etc/ld.so.conf.d/之前我强烈建议先备份原来目录下的所有文件。虽然正常情况下你的修改只是新增一个 conf但如果你手滑覆盖了系统自带文件后果会很严重。我的心法是先执行sudo cp -r /etc/ld.so.conf.d /etc/ld.so.conf.d.bak.$(date %Y%m%d)改完以后立刻用sudo ldconfig -v检查输出至少确认没有No such file or directory的提示。万一出现极端情况——系统命令开始报错找不到库了——关机前先冷静重启进恢复模式把备份目录恢复回去再执行ldconfig基本都能救回来。说到最后我个人在实际操作中的选型习惯很简单一次性调试用临时 LD_LIBRARY_PATH个人开发机写 .bashrc生产服务器一律/etc/ld.so.conf.dldconfig。每配完一个第三方库顺手跑一遍ldd确认依赖链路完整再写进项目的部署文档里下次换新机器就能直接对照执行不用重新踩一遍“编译成功但运行失败”的坑。这个习惯看起来有点繁琐但长期省下来的时间和精力远比一开始那几分钟多得多。
返回列表