admin管理员组

文章数量:1613748

老朽在 DOS、Windows 下玩 C/C++ 三十多年了,转到 Linux(Ubuntu)下,感觉整个开发环境乱七八糟、毫无头绪。在安装配置 OpenCV 4.5.1 的时候,经过一番折腾,发现 Linux 和 Windows 没啥本质区别。本文把关键要素稍加整理,供从 Windows 向 Linux 迁移的程序员同行们参考。

1. exe、dll 在 Linux 上的运行机制

Windows下的二进制代码无非两种形式,exe 和 dll。当然,还有各种披着华丽外衣的dll,比如ocx就是一个不折不扣的dll文件,只是扩展名变了一下而以。

1.1 Linux 系统下的 exe 文件

那么,exe程序到了 Linux 下,这个扩展名就是多余的了,因为 Linux 判断文件是否可执行依据文件属性,而不是这个扩展名。当然,有些人把可运行的文件后面加个 run 结尾,未尝不可。例如,Nvidia 的 GPU 驱动安装程序名字就是 NVIDIA-Linux-x86_64-460.32.03.run,看上去舒服很多吧?

1.2 Linux 系统下的 dll 文件

dll 动态链接库,到了 Linux 下摇身一变成了 so 文件。比如,我们写一个绘图函数库在 Windows 的名字可能是 draws.dll,到了 Linux 系统,就得改名为 draws.so 了。

1.3 Linux 系统下可执行程序的搜索路径

Windows 有一个环境变量 PATH,保存了一大堆路径名。平时如果只给 Windows 一个文件名的话, 它首先会搜索当前目录,如果不存在,则会遍历 PATH 中的路径名进行搜索。这样的话,无论 exe 还是 dll 文件,只要所在目录在这个环境变量中,都可以迅速找到的。

那么 Linux 系统是如何搜索这些文件的呢?带着这个问题我查了以下资料,结果首先发现了 Linux 一个很有趣的命令——which,它可以告诉你一个程序的存放位置。初次来到 Linux 系统下,经常会出现一副没见过市面的样子。例如:

$ which python
/usr/bin/python

$ which python3
/usr/bin/python3

$ which make
/usr/bin/make

$ which cmake
/usr/bin/cmake

$ which ls
/bin/ls

$ which which
/usr/bin/which

这个很有意思把?不过 Linux 如何知道到 /usr/bin/ 和 /bin 这两个路径下查找文件的呢?如何添加新的搜索路径呢?

据说 Windows 内核也是来自 Unix,我尝试了一下查看环境变量 PATH:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/cuda-11.2/bin

果不其然,路径就保存在环境变量 PATH 中。我可以临时修改这个变量的值,但是如何才能在重新启动Linux的时候还能保留这个值呢?所以,我需要知道在什么地方可以保存环境变量的定义。

值得注意的是,Linux 不会在当前目录搜索文件,这个和 Windows 做法截然不同。所以,当你在当前文件夹编译得到可执行程序 hello 时,直接在命令行输入 hello 是不行的,必须按照下面的格式调用:

$ ./hello

1.4 Linux 系统下动态库的搜索路径

Linux 运行的时候,是如何管理共享库 *.so 的?在 Linux 下面,共享库的寻找和加载是由 /lib/ld.so 实现的。 ld.so 在标准路经 /lib、/usr/lib 中寻找应用程序用到的共享库。但是,如果需要用到的共享库在非标准路经,ld.so 怎么找到它呢?

目前,Linux 通用的做法是将非标准路经加入配置文件 /etc/ld.so.conf,然后运行 ldconfig 生成 /etc/ld.so.cache。 ld.so 加载共享库的时候,会从 ld.so.cache 查找。我打开自己机器上的 /etc/ld.so.conf 文件,内容如下:

include /etc/ld.so.conf.d/*.conf
include /usr/loacal/lib

需要注意的是:

  1. 往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf的,但是完了之后要调一下ldconfig,不然这个library会找不到
  2. 想往上面两个目录以外加东西的时候,一定要修改/etc/ld.so.conf,然后再调用ldconfig,不然也会找不到。比如安装了一个mysql到/usr/local/mysql,mysql有一大堆library在/usr/local/mysql/lib下面,这时就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,保存过后ldconfig一下,新的library才能在程序运行时被找到。
  3. 如果想在这两个目录以外放lib,但是又不想在/etc/ld.so.conf中加东西(或者是没有权限加东西)。那也可以,就是export一个全局变量LD_LIBRARY_PATH,然后运行程序的时候就会去这个目录中找library。一般来讲这只是一种临时的解决方案,在没有权限或临时需要的时候使用。
  4. ldconfig做的这些东西都与运行程序时有关,跟编译时一点关系都没有。编译的时候还是该加-L就得加,不要混淆了。
  5. 总之,就是不管做了什么关于library的变动后,最好都ldconfig一下,不然会出现一些意想不到的结果。不会花太多的时间,但是会省很多的事。

库搜索路径的确定有很多方法,LD_LIBRARY_PATH是其中一种,其他还有编译命令行 rpath 参数、 连接时的 LD_RUN_PATH 环境变量、/lib,/usr/lib 标准目录、动态连接器缓存 /etc/ld.so.conf 等。

2. Linux 的环境变量

我发现,用 export 命令可以输出 Linux 的全部环境变量,这个类似 Windows 系统的 set 命令

$ export
declare -x CLUTTER_IM_MODULE="xim"
declare -x COLORTERM="truecolor"
declare -x CUDA_HOME=":/usr/local/cuda-11.2"
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/cuda-11.2/bin"
... ...

这么多的环境变量,都是在哪里定义的呢?一共有四个文件可以定义环境变量(~代表当前用户的 home 文件夹),四个文件的作用域各不相同:

配置文件用途
~/.profile当前用户登录进系统的时候执行的配置文件(有些系统中的名字可能是~/.bash_profile)
~/.bashrc当前用户运行 bash 时候执行的配置文件
/etc/profile全部用户登录进系统的时候执行的配置文件
/etc/bash.bashrc全部用户运行 bash 时候执行的配置文件

有趣的是,这些配置文件,实际上是批处理文件,可以用 sh 命令执行:

$ sh ~/.profile

也可以用 bash 命令执行:

$ bash ~/.profile

由于 sh 和 bash 都是在子进程中运行的,因此,上述运行结果在执行完成后就随子进程的结束而消失了,对当前进程没什么卵用。要想在当前进程中立即生效,需要用 source 命令:

$ source ~/.profile

$ source ~/.bashrc

运行后,环境变量立即生效。

将到这里,你应该清楚了,如果你的C/C++生成的可执行文件hello-run能够在命令行下被顺利执行,一共有三种方式:

  1. 输入全路径名。例如:~/demo/hello-run,虽然比较麻烦,但是不用修改环境变量。
  2. 把 hello-run 放到 PATH 中的一个搜索路径里面
  3. 把 hello-run 所在的路径添加到 PATH 路径里面

3. include 文件路径

我觉得这个路径与 Linux 没啥鸟关系,应该是 GCC/G++ 编译器需要这个路径。用 export 命令看了一下我的环境变量,果然没有和 include 相关的环境变量。忽然想起了某人的名言——哈哈我真棒!

3.1 查看 include 搜索路径

用 gcc 来查看一下:

$ echo 'main(){}'|gcc -E -v -

这个命令很有意思,用 echo 把一个 c 语言程序利用管道发送给 gcc, 然后 gcc 利用参数 -E -v 显示出 include 的搜索路径(-E 表示只做一遍预编译,不做正式编译和连接;-v 显示编译器调用的程序)。我试了一下,用一个demo.cpp代码编译,输入下面命令,结果也一样:

$ gcc -o demo demo.cpp -E -v

下面是输出结果中我们感兴趣的内容:

#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/7/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include

3.2 添加自己的 include 搜索路径

可以通过修改 /etc/profile文件,在文件最后加入以下内容,添加自己的include搜索路径,当然路径名要按照实际情况添加:

C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/include/cairo:/usr/include/gtk-2.0
export C_INCLUDE_PATH

我的理解,不一定非得添加到 /etc/profile 文件中,前面说的四个配置文件原则上都可以,根据需要即可,/etc/profile 是作用域最广的设置方法。这个关键在于环境变量 C_INCLUDE_PATH 才是 gcc 关心的。

运行一下看看:

$ echo 'main(){}'|gcc -E -v -

结果如下,前面两行是刚刚添加进来的:

#include <...> search starts here:
 .
 /usr/include/cairo
 /usr/include/gtk-2.0
 /usr/lib/gcc/x86_64-linux-gnu/7/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include

以上是 C 语言的搜索路径,对于 C++,需要设置的环境变量是 CPLUS_INCLUDE_PATH,具体方法与 C 语言一样。小结如下:

export C_INCLUDE_PATH=path_name:$C_INCLUDE_PATH   		//为c语言程序设置include路径
export CPLUS_INCLUDE_PATH=path_name:$CPLUS_INCLUDE_PATH	//为c++程序设置include路径

4. lib 文件路径

一般来讲,lib 文件路径需要设置 LIBRARY_PATH、LD_LIBRARY_PATH 两个路径,参见下面代码:

export LIBRARY_PATH=path_name:$LIBRARY_PATH  			//为静态库设置搜索路径
export LD_LIBRARY_PATH=path_name:$LD_LIBRARY_PATH  		//为动态库设置搜索路径

LD_LIBRARY_PATH 处理非标准路经的共享库,是一种比较传统的方法,ld.so 加载共享库的时候,也会查找这个变量所设置的路经。但是,有不少声音主张要避免使用 LD_LIBRARY_PATH 变量,尤其是作为全局变量。这些声音是:

  • LD_LIBRARY_PATH is not the answer - http://prefetch/articles/linkers.badldlibrary.html
  • Why LD_LIBRARY_PATH is bad - http://xahlee/UnixResource_dir/_/ldpath.html
  • LD_LIBRARY_PATH - just say no - http://blogs.sun/rie/date/20040710
    解决这一问题的另一方法是在编译的时候通过 -R 选项指定 run-time path。

5. 总结

5.1 环境变量

环境变量用途
PATH可执行程序 bin 文件搜索路径
C_INCLUDE_PATHC 语言 include 文件搜索路径
CPLUS_INCLUDE_PATHC++ include 文件搜索路径
LIBRARY_PATH静态库搜索路径
LD_LIBRARY_PATH动态库搜索路径

5.2 定义环境变量的文件

配置文件用途
~/.profile当前用户登录进系统的时候执行的配置文件(有些系统中的名字可能是~/.bash_profile)
~/.bashrc当前用户运行 bash 时候执行的配置文件
/etc/profile全部用户登录进系统的时候执行的配置文件
/etc/bash.bashrc全部用户运行 bash 时候执行的配置文件

5.3 动态共享库

需要加载动态库时,系统会到 /etc/ld.so.cache 中搜索全部动态库。 /etc/ld.so.cache 是由 ldconfig 产生的,过程如下:

  1. 人工编辑配置文件 /etc/ld.so.conf;
  2. ldconfig 读取配置文件 /etc/ld.so.conf,生成 /etc/ld.so.cache;

因此,如果修改了 /etc/ld.so.conf,一定要运行 ldconfig,以便更新 /etc/ld.so.cache。


参考资料

  1. ubuntu16.04安装驱动、cuda、cudnn和opencv方法
  2. ubuntu下如何设置程序include搜索路径及链接路径
  3. Linux 共享库:LD_LIBRARY_PATH 与ld.so.conf
  4. ubuntu(linux): 快速了解 /ect、/dev、/usr、/var、/proc 五大文件系统
  5. 在 Ubuntu系统下安装 OpenCV 全过程
  6. Linux中添加搜索路径(头文件、库文件、可执行文件)
  7. Linux搜索文件路径
  8. /etc/ld.so.conf.d/目录下文件的作用
  9. linux环境变量 export命令详解

本文标签: 程序员快速指南LinuxWindows