自学内容网 自学内容网

第一章 Linux系统编程入门

提示:欢迎查看本文所属专栏:Linux 网络编程笔记,在这里你可以学习 Linux 命令的基本使用、远程开发 Linux程序、计算机网络基础知识、操作系统基础知识和 Linux 网络编程基础等,这些知识,可以帮助我们很好的入门 Cpp服务器开发 所需的网络编程知识。


前言

  看到标题,读者可能会很好奇,这不是网络编程笔记专栏吗,怎么第一篇文章是 Linux 系统编程入门 ,其实原因很简单,在我们学习 Linux 网络编程前,需要先学习怎么在 Linux 中进行 C 语言编程,这部分内容属于 Linux 系统编程 入门的内容,当然,Linux 网络编程也离不开它。


2.1 Linux开发环境的搭建

  • 准备Linux操作系统,可以是虚拟机的形式,也可以是云服务器的形式。

  • 安装XSHELL和XFPT,主要用于远程连接Linux(可以在XSHELL中运行Linux终端)和OS之间的文件传输,这里需要给Linux安装SSH环境。

  • 在VSCode下安装Remote Development插件,用于远程连接Linux,在Linux环境下进行程序的开发,比直接在linux中方便。

2.2 GCC上

2.2.1 GCC基本概念

  GCC原名为C语言编译器(GNU C Complier),现在是GNU编译器套件(GNU Cmopiler Collection),该编译器套件包括C、C++、Objective-C、Java、Ada和Go语言前端,也包括了libstdc++, libgcj等语言的库,此外,GCC还可以区别C语言不同的标准。

2.2.2 GCC工作流程

源代码 ==> 预处理 ==> 预处理后的源代码(.i) ==> 编译 ==> 汇编代码(.S) ==> 汇编 ==> 目标代码(.O) ==> 链接(包括启动代码,库代码和其他目标代码) ==> 可执行程序

2.2.3 GCC常用的参数选项
gcc编译选项说明
-E预处理指定的源文件,不进行编译
-S编译指定的源文件,不进行汇编
-c编译、汇编指定的源文件,但不进行链接
-o [file1] [file2] or [file2] -o [file1]将文件 file2 编译成可执行文件 file1
-I directory指定 include 包含文件的搜索目录
-g在编译的时候,生成调试信息,该程序可以被调试器调试
-D在程序编译的时候,指定一个宏
-w不生成任何警告信息
-Wall生成所有警告信息
-Onn的取值范围:0~3,编译器优化选项的4个级别-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-l在程序编译的时候,指定使用的库(l后面追加需要使用的库名,动态库不能有空格,静态库可以有空格
-L指定编译的时候,搜索的库的路径
-fPIC or -fpic生成与位置无关的代码(主要用于动态库目标文件的生成
-shared生成共享目标文件,通常用在建立共享库时
-std指定C语言的版本,如:-std=c99,gcc默认的版本是GNU C

2.3 GCC下

2.3.1 gcc和g++的区别
  • 首先,两者都可以编译c和c++程序

  • 后缀为.c时,gcc当作 c 程序,g++当作 c++ 程序。

  • 后缀为.cpp时,两者都会认为是 c++ 程序。

  • 编译阶段,g++ 会调用 gcc,对于 c++ 源程序,gcc 命令不能自动和 c++ 程序使用的库链接,所以通常用 g++ 来完成链接,为了统一起见,可以编译/链接都用 g++

  • 关于 gcc 对宏_cplusplus的定义规则,如果后缀为.c并且采用 gcc 编译器,则 gcc 不会定义该宏,如果后缀为.cpp并且采用gcc编译器,则 gcc 会定义该宏。

  • 若要使用 gcc 链接其他 c++ 的库,则需要加参数选项-lstdc++

2.4 静态库的制作

  在谈静态库的制作之前,先谈一下什么是库文件

  库文件可以看作是一种代码仓库,它提供了可以直接使用的变量、函数或类,它也可以看成是一种不能运行的特殊程序。库文件分为两类,分别是静态库和动态库(共享库):

  • 静态库在程序的链接阶段会被复制到程序中,生成可执行文件一起被加载到内存中,静态库的后缀名是.a
  • 动态库(共享库)在程序的链接阶段没有被复制到程序中,而是在程序的运行时,由系统动态加载到内存中供程序调用,既然是动态加载,就涉及到系统如何寻找动态库所在的磁盘位置,后续动态库的制作小节中会提到,动态库的后缀名为.so
2.4.1 静态库制作第一步,获取目标二进制文件

  在源文件src目录下,对需要打包成静态库的源程序(.c),使用gcc -c编译生成目标二进制文件

# 目录结构
.
├── include
│   └── head.h
├── lib
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mul.c
    └── sub.c

# 生成目标二进制文件, -I参数指定头文件所在目录
gcc -c add.c sub.c mul.c div.c -I ../include
2.4.2 静态库制作第二步,生成静态库文件

  通过ar命令(工具)使用第一步生成的目标二进制文件,制作静态库文件,linux下静态库文件的格式为lib库名.a

# 目录结构
.
├── include
│   └── head.h
├── lib
├── main.c
└── src
    ├── add.c
    ├── add.o
    ├── div.c
    ├── div.o
    ├── mul.c
    ├── mul.o
    ├── sub.c
    └── sub.o
# src目录下,用ar工具生成静态库文件
# r - 将文件插入备存文件中
# c - 建立备存文件
# s - 索引
ar rcs libcal.a add.o sub.o mul.o div.o

# 泛化: ar rcs lib静态库名.a 制作静态库所需的目标文件列表

2.5 静态库的使用

  头文件,静态库文件和源文件的目录结构如下所示,通过gcc提供的命令,使用头文件和静态库文件编译main.c,生成二进制文件app

# 目录结构
.
├── include
│   └── head.h
├── lib
│   └── libcal.a
├── main.c
└── src
    ├── add.c
    ├── div.c
    ├── mul.c
    └── sub.c
# 静态库的使用
# -I 头文件所在目录 -l 静态库名 -L 静态库所在路径
gcc main.c -o app -I ./include -l cal -L ./lib

2.6 动态库的制作和使用

2.6.1 动态库的制作过程
  • 第一步,使用gcc-c-fpic or -fPIC参数列表,得到.o文件,生成和位置无关的代码。
# 目录结构
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mul.c
└── sub.c
#得到.o文件,和位置无关的代码
gcc -c -fpic add.c sub.c mul.c div.c
  • 第二步,使用gcc-shared-o参数列表,生成动态库
 # 目录结构
 .
├── add.c
├── add.o
├── div.c
├── div.o
├── head.h
├── main.c
├── mul.c
├── mul.o
├── sub.c
└── sub.o

 #生成动态库
 gcc -shared add.o sub.o mul.o div.o -o libcal.so
 # 泛化 gcc -shared 制作动态库所需目标文件的列表 -o lib动态库名.so
2.6.2 动态库的使用
  • gcc-o-I(可选)-l-L参数列表,编译使用了动态库的源程序
# 编译使用了动态库的源程序,这里需要注意的是,动态库指定库名时,-l后面不能有空格
gcc main.c -o main -I ./include/ -lcal -L ./lib/

# 泛化: gcc xxx.c -o 目标程序 -I 头文件所在目录 -l动态库名 -L 动态库所在位置

2.7 动态库加载失败的原因

  在执行2.6.2中的Shell命令后,我们会生成一个main的可执行程序,使用./main来运行它,发现会报错,报错内容为./main: error while loading shared libraries: libcal.so: cannot open shared object file: No such file or directory,翻译过来大致是,当加载共享库(动态库)libcal.so时,找不到目标文件,这是怎么回事呢?我们使用ldd main命令查看可执行文件main所依赖的动态库,显示内容如下。

# ldd命令,加载可执行文件的动态库依赖关系
ldd main
# linux-vdso.so.1 (0x00007fffab0fd000)
# libcal.so => not found
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcb552bb000)
# /lib64/ld-linux-x86-64.so.2 (0x00007fcb558ae000)

我们发现,2.6制作的动态库libcal.so找不到,2.8详细解释原因并且提供解决方案。

2.8 可执行程序动态库加载失败

2.8.1 动态库加载失败的原因

  在说明动态库加载失败的原因之前,先谈谈可执行程序加载时,静态库和动态库的区别

  • 静态库gcc进行链接时,静态库中的代码会打包到可执行程序中(一起装入到内存中),所以,在程序执行过程中,不存在从磁盘中寻找静态库文件的操作(因为静态库代码已经装入到内存中了)
  • 动态库gcc进行链接时,动态库中的代码不会打包到可执行程序中,只有在可执行程序运行需要用到动态库的代码时,动态库才会被加载到内存中,这就涉及到如何从磁盘定位动态库的问题(动态库加载失败的原因就是,我们没有给linux提供一个动态库的绝对路径所以linux找不到动态库)。

  那么,linux是如何从磁盘定位动态库(共享库)文件的呢?

  • 当linux加载包括了动态库的可执行程序时,能够知道其所依赖动态库的名字,然后通过动态载入器(ld-linux.so)来获取绝对路径。对于ELF(Executable and Linkable Format,一种标准的二进制文件格式)文件,linux先后搜索 DT_RPATH ==> 环境变量(LD_LIBRARY_PATH) ==> /etc/ld.so.cache文件列表 ==> /lib/usr/lib目录找到动态库文件绝对路径后,将其载入内存。
2.8.2 解决动态库加载失败

  这里提供四种方法,前三种方法都是通过修改环境变量的方式,最后一种方法是通过修改/etc/ld.so.cache文件列表的方式:

  • 第一种方法是使用export命令,临时配置基于动态库的环境变量(也就是只在当前shell程序有效)
# pwd(print working directory)命令,打印当前工作目录的绝对路径

# 在linux的环境遍历中配置动态库的绝对路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/utopiayouth/Desktop/chapter2/lesson06/library/lib

  • 第二种方法是用户级的永久配置,使用cd命令进入到用户根目录下,修改.bashrc文件,在最后一行加入如下内容
# .bashrc是用户级别的Bash shell配置文件,因为它在用户的根目录中,每次启动新的Bash shell程序时,该文件都会被读取和执行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/utopiayouth/Desktop/chapter2/lesson06/library/lib

# 最后,使用source命令,更新.bashrc文件(source命令可以在不重新启动Shell程序的前提下,将.bashrc修改的内容在当前Shell程序中生效)
source ./.bashrc
# 或者使用.代替source
. ./.bashrc
  • 第三种方法是系统级的永久配置,进入root权限,使用cd命令进入到etc目录下,修改profile文件,在最后一行加入如下内容
# profile文件是一个重要的 shell 脚本文件,它主要负责配置全局环境变量和执行一些系统级别的初始化任务,所有用户都是可见的,登录系统时会被 shell 解析执行。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/utopiayouth/Desktop/chapter2/lesson06/library/lib

# 最后,使用source命令,更新profile文件(source命令可以在不重新启动Shell程序的前提下,将profile修改的内容在当前Shell程序中生效)
source ./profile
  • 第四种方法,修改/etc目录下的ld.so.conf文件列表,加入动态库的绝对路径
# 动态库的绝对路径
/home/utopiayouth/Desktop/chapter2/lesson06/library/lib

# 最后使用ldconfig刷新我们修改的配置
sudo ldconfig

2.9 静态库和动态库的对比

2.9.1 静态库的优缺点

优点:

  • 静态库已经被打包到了可执行程序中,所以程序运行和加载的速度快(运行和加载的过程中 都不需要重新从磁盘中寻找库文件)
  • 发布程序时无需提供静态库,移植方便(因为程序在进行链接的过程中,库文件代码已经被链接到了可执行程序中)

缺点:

  • 消耗系统资源,浪费内存(举一个例子,我们在内存中装入两个包含静态库的程序,此时,内存中静态库的副本有两个,造成冗余,浪费内存)。
  • 更新、部署、发布麻烦(因为程序需要和库文件一起重新发布)。
2.9.2 动态库的优缺点

优点:

  • 由于动态库和主程序可以分离(程序链接阶段,没有链接到一起),异步装入内存,所以我们可以控制动态库何时加载到内存中
  • 同理,由于动态库和主程序是分离的,可以利用进程间资源共享技术(这也是共享库名字的来源),实现当我们加载多个包含动态库的程序时,内存中只有一个动态库副本,节省了内存空间
  • 更新、部署、发布简单(很好理解,由于动态库和程序分离,对于程序版本的更新,不影响旧版本的动态库)。

缺点:

  • 程序加载和运行比静态库慢(很好理解,因为程序在加载和运行的时候,需要定位动态库在磁盘中的位置,然后加载到内存中)。
  • 发布程序的时候需要提供依赖的动态库(很好理解,因为动态库和程序在链接的时候是分离的,所以发布的时候得有两部分)。

细心的读者会发现,在谈动态库的时候,我们使用了专有名词运行加载,这是因为,当我们将程序从磁盘装入到内存,然后转换为进程运行的这两个阶段,我们都可以控制动态库何时装入内存,可以是程序装入内存的阶段,也可以是程序运行的阶段。

2.10 Makefile的一些基础知识

2.10.1 什么是Makefile

  一个工程中的源文件不计其数,一般按类型、功能和模块分别放在若干个目录中(例如,源文件存放在src目录,头文件存放在include目录等),Makefile 文件定义了一系列的规则,来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更加复杂的操作,Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令(工程中存在大量的文件,我们不可能在Shell窗口中,一个个地进行编译、汇编和链接,所以我们就需要 Makefile 来自动的完成所有工程源代码的编译、汇编等操作,极大的提高了软件开发的效率)。

  有了Makefile文件,我们只需要一个make命令,make是一个解释Makefile文件中指令的命令工具,大多数IDE都有这个命令。(比如Visual C++下的nmake,Linux下GNU的make)。

2.10.2 文件命名
makefile or Makefile
2.10.3 Makefile的一些规则
# 一个 Makefile 文件中可以有一个或者多个规则
# 目标...: 依赖
# 命令(Shell命令)
# ...

# 目标:最终要生成的文件(伪目标除外)
# 依赖:生成目标所需要的文件或是目标
# 命令:通过执行Shell命令对依赖操作生成目标(Shell命令前必须 Tab 缩进)

# Makefile 中的其他规则一般都是为第一条规则服务的

# 举例
app: sub.c add.c mul.c div.c main.c
gcc sub.c add.c mul.c div.c main.c -o app
#目标:app
#依赖:*.c文件
#命令:gcc 命令

2.11 一些linux命令的补充

  • ll命令,用于列出当前目录或指定目录中的文件和目录,并以长格式(long listing format)显示详细信息。ll 命令比 ls 提供更多的细节,如权限、所有者、组、文件大小和最后修改时间等
# 举例
ll 指定目录 or 当前目录
  • cp命令,复制命令,将指定的文件或者目录(需要加-r参数表示递归复制)复制到目标目录中。
# 举例
cp [-r] 指定文件或者目录 目标目录
  • rm命令,删除指定文件或者目录(需要加-r参数递归删除)。
  • tree命令,以树状的形式显示当前目录或者指定目录的内容(需要sudo apt install tree
  • ldd命令,加载可执行文件的动态库依赖关系,可以利用该工具查看动态库是否配置
  • pwd(print working directory)命令,打印文件的工作目录(绝对路径)

原文地址:https://blog.csdn.net/fantasticHQ/article/details/142875185

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!