1. 软件包管理器
1-1 什么是软件包
在Linux下安装软件,一个通常的办法是下载到程序的源代码,并进行编译,得到可执行程序。但是这样太麻烦了,于是有些人把一些常用的软件提前编译好,做成软件包(可以理解成windows上的安装程序)放在一个服务器上,通过包管理器可以很方便地获取到这个编译好的软件包,直接进行安装。
软件包和软件包管理器,就好比“App”和“应用商店”这样的关系。
yum(Yellow dog Updater, Modified)是Linux下非常常用的一种包管理器。主要应用在Fedora、RedHat、Centos等发行版上。
Ubuntu主要使用apt(Advanced Package Tool)作为其包管理器。apt同样提供了自动解决依赖关系、下载和安装软件包的功能。
1-2 Linux软件生态
Linux下载软件的过程(Ubuntu、Centos、other):软件包1、软件包2、软件包3等向包管理器(yum/apt)发送查找、下载请求,包管理器在软件包服务器获取软件包,解决依赖关系后进行下载、安装或卸载操作,最后将结果返回给用户的云服务器。
操作系统的好坏评估涉及生态问题,包括社区论坛、官网文档、软件体系、维护更新速度、操作系统自身以及富有针对性的客户群体等方面。
软件包存在依赖问题,例如一个软件可能依赖libc.so、ssl.so、XXX.so等库文件。
1-3 yum具体操作
通过yum list命令可以罗列出当前一共有哪些软件包。由于包的数目可能非常之多,这里我们需要使用grep命令只筛选出我们关注的包。例如:
# Centos
$ yum list | grep lrzsz
lrzsz.x86_64 0.12.20-36.el7 @base
注意事项:
- 软件包名称:主版本号.次版本号.源程序发行号 - 软件包的发行号.主机平台.cpu架构。
- “x86_64”后缀表示64位系统的安装包,“i686”后缀表示32位系统安装包。选择包时要和系统匹配。
- “el7”表示操作系统发行版的版本。“el7”表示的是centos7/redhat7,“el6”表示centos6/redhat6。
- 最后一列,base表示的是“软件源”的名称,类似于“小米应用商店”“华为应用商店”这样的概念。
- Ubuntu有上述有详细介绍
通过yum,我们可以通过很简单的一条命令完成gcc的安装。
# Centos
$ sudo yum install -y lrzsz
# Ubuntu
$ sudo apt install -y lrzsz
yum/apt会自动找到都有哪些软件包需要下载,这时候敲“y”确认安装。出现“complete”字样或者中间未出现报错,说明安装完成。
注意事项:
- 安装软件时由于需要向系统目录中写入内容,一般需要sudo或者切到root账户下才能完成。
- yum/apt安装软件只能一个装完了再装另一个。正在yum/apt安装一个软件的过程中,如果再尝试用yum/apt安装另外一个软件,yum/apt会报错。
- 如果yum / apt报错,请自行百度。
仍然是一条命令:
# Centos
sudo yum remove [-y] lrzsz
# Ubuntu
sudo apt remove [-y] lrzsz
关于yum / apt的所有操作必须保证主机(虚拟机)网络畅通!!!可以通过ping指令验证,如:
ping www.baidu.com
当天yum / apt也能离线安装,但是和我们当前无关,暂不关心。
1-4 安装源
Centos安装源路径:
$ ll /etc/yum.repos.d/
total 16
-rw-r--r-- 1 root root 676 Oct 8 20:47 CentOS - Base.repo # 标准源
-rw-r--r-- 1 root root 230 Aug 27 10:31 epel.repo # 扩展源
Ubuntu安装源路径:
$ cat /etc/apt/sources.list # 标准源
$ ll /etc/apt/sources.list.d/ # 扩展源
景
更新安装源,现场说明原理即可。云服务器不用考虑,因为软件源都是国内的了。
好玩的命令
2.编辑器Vim
2-1 Linux编辑器-vim使用
vi/vim的区别简单点来说,都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于macos、windows。
2-2 vim的基本概念
课堂上我们讲解vim的三种模式(其实有好多模式,⽬前掌握这3种即可),分别是命令模式(command mode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:
-
正常/普通/命令模式(Normalmode)
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insertmode下,或者到lastline mode -
插入模式(Insertmode)
只有在Insertmode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面使用的最频繁的编辑模式。 -
末行模式(lastlinemode)
文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。
在命令模式下,shift+:
即可进入该模式。要查看你的所有模式:打开vim,底行模式直接输⼊:help vim-mode
2-3 vim的基本操作
-
进入vim,在系统提示符符号输入vim及文件名后,就进入vim全屏幕编辑画面:
$ vim test.c
- 不过有一点要特别注意,就是你进入vim之后,是处于[正常模式],你要切换到[插入模式]才能够输入文字。
-
[正常模式]切换至[插入模式]
- 输入a
- 输入i
- 输入o
-
[插入模式]切换至[正常模式]
- 目前处于[插入模式],就只能一直输入文字,如果发现输错了字,想用光标键往回移动,将该字删除,可以先按一下「ESC」键转到[正常模式]再删除文字。当然,也可以直接删除。
-
[正常模式]切换至[末行模式]
- 「shift + ;」,其实就是输入「:」
-
退出vim及保存文件,在[正常模式]下,按一下「:」冒号键进入「Last line mode」,例如:
- :w (保存当前文件)
- :wq (输入「wq」,存盘并退出vim)
- :q! (输入q!,不存盘强制退出vim)
2-4 vim正常模式命令集
- 插入模式
- 按「i」切换进入插入模式「insert mode」,按“i”进入插入模式后是从光标当前位置开始输入文件;
- 按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;
- 按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。
- 从插入模式切换为命令模式
- 按「ESC」键。
- 移动光标
- vim可以直接用键盘上的光标来上下左右移动,但正规的vim是用小写英文字母「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移一格
- 按「G」:移动到文章的最后
- 按「$」:移动到光标所在行的“行尾”
- 按「^」:移动到光标所在行的“行首”
- 按「w」:光标跳到下个字的开头
- 按「e」:光标跳到下个字的字尾
- 按「b」:光标回到上个字的开头
- 按「#g」:光标移到该行的第#个位置,如: 5l,56l
- 按「gg」:进入到文本开始
- 按「shift + g」:进入文本末端
- 按「ctrl + b」:屏幕往“后”移动一页
- 按「ctrl + f」:屏幕往“前”移动一页
- 按「ctrl + u」:屏幕往“后”移动半页
- 按「ctrl + d」:屏幕往“前”移动半页
- 删除文字
- 「x」:每按一次,删除光标所在位置的一个字符
- 「#x」:例如,「6x」表示删除光标所在位置的“后面(包含自己在内)”6个字符
- 「X」:大写的X,每按一次,删除光标所在位置的“前面”一个字符
- 「#X」:例如,「20X」表示删除光标所在位置的“前面”20个字符
- 「dd」:删除光标所在行
- 「#dd」:从光标所在行开始删除#行
- 复制
- 「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
- 「#yw」:复制#个字到缓冲区。
- 「yy」:复制光标所在行到缓冲区。
- 「#yy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字。
- 「p」:将缓冲区内的字符贴到光标所在位置。注意: 所有与“y”有关的复制命令都必须与“p”配合才能完成复制与粘贴功能。
- 替换
- 「r」:替换光标所在处的字符。
- 「R」:替换光标所到之处的字符,直到按下「ESC」键为止。
- 撤销上一次操作
- 「u」:如果您误执行一个命令,可以马上按下「u」,回到上一个操作。按多次“u”可以执行多次回复。
- 「ctrl + r」:撤销的恢复
- 更改
- 「cw」:更改光标所在处的字到字尾处
- 「c#w」:例如,「c3w」表示更改3个字
- 跳至指定的行
- 「ctrl + g」列出光标所在行的行号。
- 「#G」:例如,「15G」,表示移动光标至文章的第15行行首。
2-5 vim末行模式命令集
在使用vim末行模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进入末行模式。
- 列出行号
- 「set nu」: 输入「set nu」后,会在文件中的每一行前面列出行号。
- 跳到文件中的某一行
- 「#」:「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字15,再回车,就会跳到文章的第15行。
- 查找字符
- 「/关键字」: 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止。
- 「?关键字」: 先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往前寻找到您要的关键字为止。
- 问题: / 和?查找有和区别?操作实验一下
- 保存文件
- 「w」: 在冒号输入字母「w」就可以将文件保存起来
- 离开vim
- 「q」: 按「q」就是退出,如果无法离开vim,可以在「q」后跟一个「!」强制离开vim。
- 「wq」: 一般建议离开时,搭配「w」一起使用,这样在退出的时候还可以保存文件。
2-6 vim操作总结
- 三种模式
- 正常模式
- 插入模式
- 底行模式
- 我们一共有12种总模式,大家下来可以研究一下
参考资料
Vim从入门到牛逼(vimfromzerotohero)
3. 编译器gcc/g++
3-1 背景知识
- 预处理(进行宏替换/去注释/条件编译/头文件展开等)
- 编译(生成汇编)
- 汇编(生成机器可识别代码)
- 连接(生成可执行文件或库文件)
3-2 gcc编译选项
格式:gcc [选项] 要编译的文件 [选项] [目标文件]
- 3-2-1 预处理(进行宏替换)
- 预处理功能主要包括宏定义,文件包含,条件编译,去注释等。
- 预处理指令是以#号开头的代码行。
- 实例:
gcc -E hello.c -o hello.i
- 选项 “-E”,该选项的作用是让gcc在预处理结束后停止编译过程。
- 选项 “-o” 是指目标文件,“.i” 文件为已经过预处理的C原始程序。
- 3-2-2 编译(生成汇编)
- 在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
- 用户可以使用 “-S” 选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
- 实例:
gcc -S hello.i -o hello.s
- 3-2-3 汇编(生成机器可识别代码)
- 汇编阶段是把编译阶段生成的 “.s” 文件转成目标文件。
- 读者在此可使用选项 “-c” 就可看到汇编代码已转化为 “.o” 的二进制目标代码了。
- 实例:
gcc -c hello.s -o hello.o
- 3-2-4 连接(生成可执行文件或库文件)
- 在成功编译之后,就进入了链接阶段。
- 实例:
gcc hello.o -o hello
3-3 动态链接和静态链接
在实际开发中,不会将所有代码放在一个源文件中,会有多个源文件且存在依赖关系。每个源文件独立编译生成 “.o” 文件,为满足链接依赖关系,需将这些目标文件进行链接形成可执行程序,此过程即静态链接。静态链接缺点如下:
- 浪费空间:每个可执行程序对所需目标文件都有副本,如多个程序调用printf()函数,都含printf.o副本。
- 更新较难:库函数代码修改,所有依赖程序需重新编译链接。优点是可执行程序含运行所需全部内容,运行速度快。
动态链接基本思想是把程序按模块拆分,程序运行时链接成完整程序,而非像静态链接将所有程序模块链接成单个可执行文件。动态链接更常用,如查看hello可执行程序依赖动态库:
1 $ ldd hello
2 linux-vdso.so.1 => (0x00007fffeb1ab000)
3 libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
4 /lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
5
6 # ldd命令用于打印程序或者库文件所依赖的共享库列表。
涉及概念“库”:C程序中未定义“printf”函数实现,“stdio.h” 只有声明,函数实现在libc.so.6库文件中,gcc默认到“/usr/lib” 查找并链接该库实现函数。
3-4 静态库和动态库
- 静态库:编译链接时将库文件代码全部加入可执行文件,文件大,运行时无需库文件,后缀一般为 “.a”。
- 动态库:编译链接时未将库文件代码加入可执行文件,程序执行时由运行时链接文件加载库,节省系统开销,后缀一般为 “.so”,gcc默认使用动态库。完成链接后,gcc生成可执行文件,如
gcc hello.o -o hello
,gcc默认生成动态链接二进制程序,可通过file命令验证。
注意1:
- Linux下,动态库XXX.so,静态库XXX.a
- Windows下,动态库XXX.dll,静态库XXX.lib
注意2:
一般云服务器C/C++静态库未安装,安装方法如下:
# Centosyum install glibc-static libstdc++-static -y#ubuntusudo apt install libssl-dev
3-5 gcc其他常用选项 - 了解即可
- -E 只激活预处理,不生成文件,需重定向到输出文件。
- -S 编译到汇编语言,不进行汇编和链接。
- -c 编译到目标代码。
- -o 文件输出到指定文件。
- -static 对生成文件采用静态链接。
- -g 生成调试信息,GNU调试器可用。
- -shared 尽量使用动态库,生成文件小,需系统有动态库。
- -O0
- -O1
- -O2
- -O3 编译器优化选项4个级别,-O0无优化,-O1为缺省值,-O3优化级别最高。
- -w 不生成任何警告信息。
- -Wall 生成所有警告信息。
4. 自动化构建-make/Makefile
4-1 背景
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。
- 一个工程中的源文件不计其数,按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
4-2 基本使用
实例代码
#include <stdio.h>int main()
{printf("hello Makefile!\n");return 0;
}
Makefile文件
myproc:myproc.cgcc -o myproc myproc.c
.PHONY:clean
clean:rm -f myproc
依赖关系
- 上面的文件myproc,它依赖myproc.c。
依赖方法
gcc -o myproc myproc.c
,就是与之对应的依赖关系。
项目清理
- 工程是需要被清理的。
- 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean” ,以此来清除所有的目标文件,以便重编译。
- 但是一般我们这种clean的目标文件,我们将它设置为伪目标,用
.PHONY
修饰,伪目标的特性是,总是被执行的。 - 可以将我们的hello目标文件声明成伪目标,测试一下。
什么叫做总是被执行?
文件=内容+属性
- Modify: 内容变更,时间更新
- Change:属性变更,时间更新
- Access:常指的是文件最近一次被访问的时间。在Linux的早期版本中,每当文件被访问时,其atime都会更新。但这种机制会导致大量的IO操作。
结论:.PHONY
让make忽略源文件和可执行目标文件的M时间对比。
4-4 推导过程
Makefile推导示例
myproc:myproc.ogcc myproc.o -o myproc
myproc.o:myproc.sgcc -c myproc.s -o myproc.o
myproc.s:myproc.igcc -S myproc.i -o myproc.s
myproc.i:myproc.cgcc -E myproc.c -o myproc.i
.PHONY:clean
clean:rm -f *.i *.s *.o myproc
编译过程
$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc
make工作原理
- make会在当前目录下找名字叫“Makefile” 或“makefile” 的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,它会找到myproc这个文件,并把这个文件作为最终的目标文件。
- 如果myproc文件不存在,或是myproc所依赖的后面的myproc.o文件的文件修改时间要比myproc这个文件新(可以用touch测试),那么,他就会执行后面所定义的命令来生成myproc这个文件。
- 如果myproc所依赖的myproc.o文件不存在,那么make会在当前文件中找目标为myproc.o文件的依赖性,如果找到则再根据那一个规则生成myproc.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成myproc.o文件,然后再用myproc.o文件声明make的终极任务,也就是执行文件hello了。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
- make只管文件的依赖性,即,如果在找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
4-4 适度扩展语法
展示了更复杂的Makefile语法,包括变量定义、文件查找、目标和依赖规则等。如:
BIN=proc.exe # 定义变量
CC=gcc
SRC=$(shell ls *.c) # 采用shell命令行方式,获取当前所有.c文件名
OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c,替换成为.o,形成目标文件列表
FLAGS=
RM=rm -f # 引入命令$(BIN):$(OBJ)@$(CC) $(FLAGS) $^ -o $@ # $@:代表目标文件名。$^: 代表依赖文件列表%.o:%.c@$(CC) $(FLAGS) $< -o $@ # $<: 对展开的.c文件,一个一个的交给gcc。.PHONY:clean
clean:@$(RM) $(OBJ) $(BIN) # $(RM): 替换,用变量内容替换它.PHONY:test
test:@echo $(SRC)@echo $(OBJ)
5. 调试器 - gdb/cgdb使用
5-1 样例代码
// mycmd.c
#include <stdio.h>int Sum(int s, int e)
{int result = 0;for(int i = s; i <= e; i++){result += i;}return result;
}int main()
{int start = 1;int end = 100;printf("I will begin\n");int n = Sum(start, end);printf("running done, result is: [%d-%d]=%d\n", start, end, n);return 0;
}
5-2 预备
- 程序的发布方式有两种,debug模式和release模式,Linux gcc/g++出来的二进制程序,默认是release模式。
- 要使用gdb调试,必须在源代码生成二进制程序的时候,加上 -g选项,如果没有添加,程序无法被编译。
$ gcc mycmd.c -o mycmd # 默认模式,不支持调试
$ file mycmd
mycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=82f5cbaada10a9987d9f325384861a88d278b160, for GNU/Linux 3.2.0, not stripped$ gcc mycmd.c -o mycmd -g # debug模式
$ file mycmd
mycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3d5a2317809ef86c7827e9199cfe622e3c187f, for GNU/Linux 3.2.0, with debug_info, not stripped
5-3 常见使用
- 开始:
gdb binFile
- 退出:
ctrl + d
或quit
调试命令
命令 | 作用 | 样例 |
---|---|---|
list/l | 显示源代码,从上次位置开始,每次列出10行 | list/l 10 |
list/l 函数名 | 列出指定函数的源代码 | list/l main |
list/l 文件名:行号 | 列出指定文件的源代码 | list/l mycmd.c:1 |
r/run | 从程序开始连续执行 | run |
n/next | 单步执行,不进入函数内部 | next |
s/step | 单步执行,进入函数内部 | step |
break/b [文件名:行号] | 在指定行号设置断点 | break 10 break test.c:10 |
break/b 函数名 | 在函数开头设置断点 | break main |
info break/b | 查看当前所有断点的信息 | info break |
finish | 执行到当前函数返回,然后停止 | finish |
print/p 表达式 | 打印表达式的值 | print start+end |
p 变量 | 打印指定变量的值 | p x |
set var 变量=值 | 修改变量的值 | set var i=10 |
continue/c | 从当前位置开始连续执行程序 | continue |
delete/d breakpoints | 删除所有断点 | delete breakpoints |
delete/d breakpoints n | 删除序号为n的断点 | delete breakpoints 1 |
disable breakpoints | 禁用所有断点 | disable breakpoints |
enable breakpoints | 启用所有断点 | enable breakpoints |
info/i breakpoints | 查看当前设置的断点列表 | info breakpoints |
display 变量名 | 跟踪显示指定变量的值(每次停止时) | display x |
undisplay 编号 | 取消指定编号的变量的跟踪显示 | undisplay 1 |
until X行号 | 执行到指定行号 | until 20 |
backtrace/bt | 查看当前执行栈的各级函数调用及参数 | backtrace |
info/i locals | 查看当前栈帧的局部变量值 | info locals |
quit | 退出GDB调试器 | quit |