文章目录
一. Linux的应用市场 — yum
1. 什么是 yum?
人们把一些常用的软件提前编译好,做成软件包(可以理解成手机上的安装包)放在一个服务器上,通过包管理器可以很方便的获取到这个编译好的安装包,直接进行安装。软件包和软件包管理器, 就好比 “App” 和 “应用商店” 这样的关系。
yum(Yellow dog Updater, Modified)是 Linux 下非常常用的一个包管理器,相当于 Linux 的“应用市场”,主要应用在 Fedora、RedHat 和 Centos 等发行版上。
2. 为什么要有 yum?
为了给开发者提供一个安全、高效的软件安装环境。
3. 如何使用 yum?
3.1 前提条件
关于 yum 的所有操作必须保证主机(虚拟机)处于联网状态。就行像我们在手机应用市场上下载软件,需要连接 WIFI 或开数据流量一样。
我们可以通过 ping 命令来验证我们的服务器是否处于联网状态,具体的操作就是:ping + 网址
,如果一直有刷新 time = xxxms 说明我们的服务器已经处于联网状态。
3.2 搜索软件
就好比我们在手机的应用市场里搜索我们想要下载的App一样
在 Linux 中,我们也可以通过 yum list | grep | 软件包关键字
这个指令来搜索 yum 中包含该关键字的软件包。
比如我们要下载一个工具叫 lrzsz ,它可以帮助我们完成 Linux 服务器和我们本地主机之间文件的传输。
几点说明:
- “x86_64” 后缀表示这是 64 位系统的安装包,选择包时要和系统匹配
- “el7” 表示操作系统发行版的版本。 “el7” 表示的是 centos7/redhat7
- 最后一列,os 表示的是 “软件源” 的名称,类似于 “小米应用商店”、“App Store” 这样的概念。
3.3 安装软件
通过前面的搜索我们可以得到想要下载的软件包的名称,复制软件包的名称,然后我们可以输入 yum install 软件包名称
来完成软件的安装
最后出现 “complete” 字样, 说明安装完成。
几点说明:
- 安装软件时由于需要向系统目录中写入内容,普通用户的话需要 sudo 或者切到 root 用户下才能完成安装,软件安装在服务器上,所以这个软件是所有用户所共享的。
- yum 安装软件只能一个装完了再装另一个。当 yum 正在安装一个软件的时候,如果再尝试用 yum 安装另外一个软件,此时 yum 会报错。
3.4 卸载软件
直接输入命令 yum remove 软件包名称
,当然也需要 sudo 或者切到 root 账户下才能完成。
二. Linux 的文本编辑器 — vim
1. 什么是 vim?
vi(visual editor)编辑器通常被简称为 vi,它是 Linux 和 Unix 系统上最基本的文本编辑器。
vim(vi improved)是 vi 编辑器的加强版,它不仅兼容 vi 的所有指令,而且还增加了一些新的特性在里面,例如语法加亮、自动缩进等。
2. 为什么要学习 vim?
- 有很强大的编辑功能,不逊色于任何最新的文本编辑器。
- vi 编辑器是所有 Unix 及 Linux 系统下标准的编辑器,所有的 Unix Like系统都会内建 vi 文本编辑器,其他的文本编辑器则不一定会存在。
- 一些软件的编辑接口会主动调用 vi (例如 crontab、visudo、edquota 等命令)
3. 如何使用 vim?
3.1 启用 vim
-
vim 文件名
,进入后默认光标是在上一次退出编辑时的位置 -
vim 文件名 + n
,进入后光标位置在第n行
3.2 退出 vim
normal 模式下退出
在 normal 模式下,连按两次大写字母 Z,即可退出。
- 若当前编辑的文件曾被修改过,则 vim 保存该文件后退出,返回到 shell
- 若当前编辑的文件没被修改过,则 vim 直接退出,返回到 shell
底行模式下退出
:w 保存但不退出
:q 退出但不保存
:wq 保存且退出
:!wq 强制保存且退出
3.3 简单配置 vim
原生的 vim 界面是比较简陋的,不仅如此编程的体验也很不好,问题诸如:没有换行后自动缩进、tab距离太短、没有自动补齐等,不过这些都可以通过一系列配置来解决。
配置文件的位置
- 在目录 /etc/ 下面,有个名为
vimrc
的文件,这是系统中公共的 vim 配置文件,对所有用户生效。 - 在每个用户的主工作目录下,都可以建立自己私有的配置文件,命名为:
.vimrc
。这个文件默认是没有的,需要我们自己建立,注意在这里进行的配置只针对当前用户有效。
配置操作
下面我们来简单讲解一下只针对当前用户有效的 vim 的配置方法
第一步:
首先我们在当前用户的主工作目录下创建.vimrc
这个文件,如果有的话就不用创建了。
对应终端操作:
$ cd ~ //回到主工作目录
$ touch .vimrc //创建文件
第二步:
进入.vimrc
文件,写入我们需要的配置选项。常见配置选项比如下面几个:
- 显示行号:set nu
- 设置语法高亮:syntax on
- 设置缩进的空格数为4:set shiftwidth=4
对应终端操作:
$ vim .vimrc //进入文件
按两次大写的 Z,保存并退出。
再次进入这个文件我们发现确实配置成功了:
3.4 vim 常见的几种模式
主要介绍几种常见的模式:正常模式、插入模式、命令模式、视图模式
3.4.1 正常模式(NORMAL)
正常模式是 vim 打开文件后默认进入的模式,无论在哪种模式下,按下 Esc 键都会切换到正常模式。
(如果我们在使用其他各种模式时出现混乱,无脑 Esc 进入正常模式就行)
正常模式的作用
正常模式不能编辑文本。它一般用于浏览文件和快速进行光标定位,还可以进行代码的复制、粘贴、删除等操作。
该模式下击键时,一般的 键/键组合 会被当成功能键,而不会键入对应的字符。在这个模式下,我们可能通过键盘在文本中跳来跳去,跳动的范围从小到大是字符、单词、行、句子、段落和屏幕。
正常模式下管理文本的常用快捷键
shift + $:定位到当前行的最后一个字符
shift + ^:定位到当前行的第一个字符
gg:定位到代码第一行的第一个字符的位置
G:定位到代码最后一行的第一个字符位置
12G/12gg:光标移动到 12 行第一个字符
u / ctrl+r:撤销 / 反撤销
yy / p:复制一行 / 粘贴一行
n+yy / n+p:复制n行 / 粘贴n行
dd / n+dd:删除一行 / 删除n行
shift + ~:从光标位置开始逐个字符地从左往右进行大小写切换
w / b:以“单词”为单位进行光标的跳转
ctrl + u:文本向上移动半页
ctrl + d:文本向下移动半页
ctrl + b:文本向上移动一页
ctrl + f:文本向下移动一页
3.4.2 插入模式(INSERT)
在正常模式下按下 a、i、o 键,会进入插入模式,插入模式里可以进行文字的输入,在该模式下按 Esc 键可以切换回正常模式。
当我们处于正常模式时,按下:
- a :在光标的下一个位置进入插入模式
- i :在光标当前位置进入插入模式
- o :往下开辟一空行进入插入模式
插入模式的作用
插入模式中,击键时会写入相应的字符。
插入模式和正常模式的配合
插入模式没有快捷键,需要配合命令模式下的快捷键进行文本编辑,这样可以达到快速编辑的作用。
3.4.3 底行/命令模式
- 在正常模式下输入
shift + :
进入命令模式 - 在命令模式中按 Esc 进入正常模式
命令模式的作用
在命令模式下可以执行一些输入命令或 vim 插件提供的指令,就像在 shell 里一样。这些指令包括设置环境、多文件操作或调用某个功能等等。
命令模式的下的常用快捷键
set mouse=a / mouse-=a:打开鼠标 / 关闭鼠标
set nu / nonu:设置行号 / 取消行号
w:保存
q:退出
wq:保存并退出
!wq:强制保存并退出
vs + 文件名:多文件分屏编辑(补充:在正常模式下按ctrl + ww可以进行文件切换)
↑ / ↓:搜索历史命令
/关键字:正向查找关键字。输入 n 查找下一个;输入 N 查找上一个。
?关键字:反向查找关键字。输入 n 查找上一个;输入 N 查找下一个。
%s/old/new/g:整个文件内,用 new 替换 old
3.4.4 视图模式
在正常模式按下 ctrl+v,即可以进入视图模式。在视图模式中我们可以选中一块区域进行批量操作:删除、注释、缩进等。
视图模式下各功能的具体操作方法
注意在视图模式里,只能通过h(左)、j(下)、k(上)、l(右)来移动光标。
功能一:批量注释
- 在正常模式里先把光标移动到要注释的起始地方,然后 ctrl + v 进入视图模式。
- 接下来通过 h、j、k、l 来选中所有要注释的行。
- 接着我们按 shift + i(即大写字母的 i )进入插入模式之后,输入我们的注释符 ’#‘ 或者 ’//’ ,接着迅速按下 Esc 键即可完成批量注释,并回到正常模式。
功能二:删除批量注释
- 正常模式下按 ctrl + v 进入视图模式
- 接着我们一样通过 h、j、k、l 选择我们刚刚插入的注释符 ‘//’
- 接着按 d 直接删除并回到正常模式
功能三:批量缩进(即批量tab)
- 在正常模式里先把光标移动到要批量缩进行的开始的地方,然后 ctrl + v 进入视图模式。
- 接下来通过 h、j、k、l 来选中要缩进的行。
- shift+ i 进入插入模式后,按 tab 建然后迅速按 Esc 完成批量缩进并回到正常模式。
功能四:批量反缩进(即shift + tab)
- 按住 ctrl + v 进入视图模式
- 通过 h、j、k、l 来批量选择我们要删除的空格
- 最后按 d 直接删除,并回到正常模式
3.4.5 各个模式的相互切换
正常模式是其他模式的中转站,其他模式只需按 Esc 就可以进入正常模式,再经由正常模式转到其他模式,如下图所示:
三. Linux 的编译器 — gcc / g++
接下来我们看看 gcc 是如何把一个 hello.c 的源程序最终编译成一个可执行程序 hello 的。
1. 预处理(-E)
$ gcc -o hello.i -E hello.c
- 预处理阶段主要完成:头文件展开、宏替换、条件编译和去掉注释
- 选项 “-o” 后面跟的是经过预处理后生成的目标文件的名字,里面存有文件的预处理信息。
- 选项 “-E” 作用是仅让 gcc 完成预处理的过程,后面跟的是依赖文件
- 如果不写明生成的目标文件(即 -o hello.i ),那么“-E”选项执行的预处理操作就会把源程序经过预处理后得到的信息输出到显示器上;如果写明目标文件,就会把这些信息存储到指定的目标文件当中。
2. 编译(-S)
$ gcc -o hello.s -S hello.i
- 编译j阶段主要完成:检查语法错误和生成汇编代码
- 选项 “-E” 完成编译的过程,如果依赖文件是还未被预处理的源文件,那么 gcc 会先完成预处理再完成编译,如果已经完成了预处理,就直接进行编译。
- 如果不写明生成的目标文件,会默认生成同名且后缀为 .s 的目标文件,里面写有该程序的汇编代码。
3. 汇编(-c)
$ g++ -o hello.c -c hello.s
- 汇编阶段主要完成:把汇编代码转化成二进制机器码
- 选项 “-c” 完成汇编的过程,如果依赖文件还未完成预处理或编译,会先完成前面的工作,最后再汇编。
- 如果不写明生成的目标文件的名字,会默认生成同名且后缀为 .o 的目标文件,里面写有该程序的二进制机器码。
4. 连接
$ gcc hello.o
- 连接生成最终的可执行程序
- 不用加任何选项就是直接完成从预处理到连接整个过程
- 如果是多文件共同编译,在连接这步时把需要连接的文件都写出来,中间用空格隔开。
- 如果不写明目标文件,则默认生成一个叫做 a.out 的可执行程序
5. 动态库和静态库
5.1 什么是库?
库是已经写好的、现有的、成熟的、可以复用的代码。又因为连接的方式不同分为静态库和动态库。
5.2 静态库
在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,这种连接方式叫做静态连接,被打包的库叫静态库。
静态库的优缺点
优点:编译连接成功的可执行文件可以独立运行,而不再需要再向外部要求读取函数库的内容。
缺点
- 可执行程序的文件较大
- 库更新时,需要重新编译链接
5.2 动态库
链接成功后的可执行程序会保留一个指向库的位置,在程序运行时才会去读取库函数来使用。这种连接库的方式叫做动态链接,被连接的库叫做动态库。
动态库的优缺点
优点:
- 可执行程序的文件小,节省了空间
- 能够实时连接最新的库,而不用再重新编译
缺点:可执行文件无法单独运行,因为它需要找到并连接动态库。
5.3 Linux 下的连接方式
gcc/g++ 在编译生成可执行程序时,对库的连接方式默认为动态链接。如果想要静态连接库,需要我们在连接时手动加上选项 -static 选项。
我们有两种方式证明 gcc/g++ 默认生成的可执行程序是动态链接的,在此之前我们先创建几个文件,看下面例子:
首先我们有个叫做 mytest.c 的文件
通过 gcc 编译 mytest.c 生成可执行程序 mytest,注意我们没有加任何其他选项,即 mytest 这个可执行程序是 gcc 在默认情况下生成的。
我们再使用 ggcc 去编译 mytest.c,只不过这次加上了 -static 选,最终生成一个静态连接的可执行程序 mytest_s
观察这两个可执行程序我们发现:静态连接生成的 mytest_s,其文件大小是 mytest 的 100 倍多一点。可以推测默认编译生成的 mytest 很可能是动态链接。
此外,我们还可以通过两条指令 file 和 ldd 来具体地查看它们的连接方式和连接的库。
指令一:file + 可执行程序名称
file 指令用来识别文件类型,也可用来辨别一些文件的编码格式。
使用 file 命令分别查看这两个可执行程序,明确写着 mytest 是动态链接的,mytest_s 是静态链接的。
指令二:ldd + 可执行程序名称
ldd 是 list - dynamic - dependencies 的缩写,翻译为:列出动态库依赖关系。
补充:Linux 中动态库的扩展名通常为 libxxx.a ,而静态库的为 libxxx.so 差别就是后缀不同,动态库后缀为 .so ,静态库后缀为 .a
执行 ldd + mytest,可以看到它动态链接了 c 库 :
执行 ldd mytest_s,结果告诉我们他不是动态链接的可执行程序
四. Linux 的调试工具 — gdb
1. 检查调试信息
1.1 生成 debug 版本的可执行程序
程序的发布方式有两种,debug 模式和 release 模式,Linux 中 gcc/g++ 编译生成的可执行程序,默认是 release 模式,它的内部是没有调试信息的。想要使用 gdb 调试,就必须要有调试信息。
在编译的时候,加上 -g 选项,就能生成 debug 版本的可执行程序。
1.2 检查是否带有调试信息
方法一:直接对可执行程序进行 gdb 调试
直接对该可执行程序进行调试,输入 gdb + 程序名称 开始调试,如果没有调试信息的话,它最后会告诉你:(no debugging symbols found)。
如果有调试信息的话,最后会告知程序调试准备就绪:
方法二:通过 readelf 命令查看调试信息
readelf 命令,一般用于查看 ELF 格式的文件信息。常见的文件如在 Linux 上的可执行文件、动态库(.so)、静态库(.a) 等包含 ELF 格式的文件。其中选项 -S 的作用是显示节头信息(如果有数据的话)。
指令:readelf -S 可执行程序名称 | grep debug1
对于没有调试信息的可执行程序,不会打印出任何东西
如果有调试信息,它会筛选并打印出来
2. 开始调试和退出调试
开始:gdb + 可执行程序名称
退出:quit 或 ctrl + d
3. 具体操作方法
gdb 有记忆功能,可以什么都不用输入,然后按回车自动执行上一条指令,而且按上下方向键还可以查看之前输入过的指令。
l + 行号:列出代码,一次列10行
r:运行代码
b + 行号:在指定行上打断点
d + 断点编号:删除断点,不指定断点编号的话就是删除所有断点
info + b:查看所有断点信息
c:跳转到下一个断点
n:逐语句
s:逐过程
p + 变量名:查看变量的值
display + 变量名:踪查看一个变量,每次停下来都显示它的值
undisplay + 变量编号:取消对先前设置的那些变量的跟踪
until + 行号:跳转到任意行
finish:直接运行完当前所在的函数
五. Linux 的项目自动化构建工具 — make/Makefile
1. 什么是 make 和 Makefile?
- Makefile 是一个文件,里面写我们要执行的编译操作。
- make 是一条命令,用来执行 Makefile 里的编译指令。
2. 为什么要有 make 和 Makefile?
如果是多文件需要共同编译时,每次就要用 gcc 把所有源文件的名字都写出来,其中一个写错了,检查起来会很麻烦。我们可以把编译的指令写到 Makefile 文件里统一管理,然后通过 make 命令来执行 Makefile 里的编译指令,达到一键编译的效果,这可以提高我们软件开发的效率。
2. Makefile 的使用
比如我们这里来实现一个简单的进度条功能,首先创建四个文件:proc.h、main.c、proc.c 和 Makefile
接下来我们去写 Makefile,确保各个文件都能顺利完成编译和最后的连接。
依赖关系
以冒号分隔,左边是目标文件,右边是生成目标文件所需要的依赖文件:
依赖方法
- 依赖方法是写在对应依赖关系下面的具体操作方法
- 依赖方法必须以tab开头,不能通过空格一个个的打!
执行的顺序
下面是我们实现进度条对应的 Makefile 文件的内容,整体是从上往下执行的,先编译器会先去检查依赖关系中的依赖文件是否存在:
- 如果存在就执行相应的依赖方法;
- 不存在的话就跳过依赖方法,往下找生成这个依赖文件的依赖关系和依赖方法,直到依赖文件生成后,再这回来执行原来的依赖方法。
PS:注意我们用 gcc/g++ 去编译源文件时,只用写明源文件就行,不需要把头文件也写出来,因为头文件在预处理阶段就已经在源文件中展开了。
最后我们只要输入命令 make 就可以完成整个进度条程序的编译连接工作了。可以看到这里的执行顺序:先执行编译指令,得到编译好的文件后,再把它们都连接起来合成一个可执行程序。
伪目标完成项目的清理工作
此外,我们还可以在 Makefile 中实现一个用来清理生成的目标文件和可执行程序的命令,这样如果我们对源文件的代码修改了,还可以快速清理掉之前的目标文件和可执行程序,一般我们把这种命令设置为伪目标,用.PHONY
修饰。
我们给这个伪目标文件取名为 clean,它不需要依赖文件,我们的主要目的只是执行它下面的依赖方法。像 clean 这种没有被第一个目标文件直接或间接关联的文件,它后面所定义的命令将不会默认被 make 执行,不过,我们可以显示要 make 执行。即命令 —— “make clean”,这样就可以主动来清理掉所有的目标文件和可执行程序,以便我们重编译。
伪目标的特性
伪目标的特性是随时都可以执行它的依赖方法,即使这个伪目标没有对应的依赖文件。
而真目标执行一次后如果你再次执行,它会提示你这已经是最新的了。
假如为目标后,得到最终 Makefile 的内容:
Makefile中的宏定义
- $():替换成括号里面的内容
- $@ :代表依赖关系中的目标文件
- $^ :代表依赖关系中的所以依赖文件
- $< :逐个取出依赖关系中的依赖文件,然后每一个都执行相同的命令
宏替换可以使得程序更加灵活和简洁,把具体的文件名取别名替换,这样修改时只需要修改宏中的别名即可。
具体进度条的实现
proc.h
包含需要的头文件和一个宏定义 NUM,表示进度条字符的数量
main.c
在 main 函数里调用 proc() 函数
proc.c
具体进度条的实现,用"#"模拟加载的过程,最右边是代表加载程度的百分比和一个循环转动的线条。
要注意的是 printf 函数是行缓冲函数,满足下面条件之一才会将缓冲区刷到对应的文件(一般是 stdout 即显示器)中。
- 缓冲区被填满
- 写入的字符中有’\n’或’\r’
- 调用 fflush 手动刷新
- 程序结束
观察我们下图的程序,printf 本来要输出到显示器上的内容一直被存放在缓冲区里,不满足我们的连续加载效果的要求,所以我们要手动调用 fflush 刷新缓冲区。
每进入一次循环就刷新一次缓冲区,让前面 printf 的内容输出到显示器
3. make 的工作原理
make 是如何工作的呢?
在默认的方式下,也就是我们只输入 make 命令:
- make 会在当前目录下找名字叫 “Makefile” 的文件。
- 如果找到,它会找 Makefile 文件中的第一个目标文件(target),在上面的例子中,他会找到 “proc” 这个文件, 并把这个文件作为最终需要生成的目标文件。
- 如果 proc 文件不存在,或是 proc 所依赖的后面的 proc.o 和 main.o 文件的文件修改时间要比 hello 这个文件新(可以用 touch 测试),那么他就会执行后面所定义的命令来生成 proc.o 和 main.o 这个文件。
- 如果 proc 所依赖的 proc.o 和 main.o 文件不存在,那么 make 会在当前文件中找目标为 proc,o 和 main.o 文件的依赖性,如果找到则再根据那一个规则生成 proc.o 和 main.o 文件。
- 当然,我们的 proc.c 和 main.c 文件是存在的,于是 make 会生成对应的 .o 文件,然后再用 proc.o 和 main.o 文件执行 make 的终极任务,也就是生成可执行文件 proc
- 这就是整个 make 的依赖性,mak e会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么 make 就会直接退出,并报错。
- make 只管文件的依赖性,即如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。