1.软件包管理器1.1 软件包是什么Linux下安装软件通常需要通过以下方式源码安装软件包安装包管理器apt/apt-getubuntuyumcentos源代码安装是不推荐的比如少了如何配置文件找起来会非常麻烦卸载后配置残留缺少依赖配置等各种问题于是有人就把一些常用的软件提前编译好做成软件包放到一个服务器上通过包管理器可以很方便的获取软件包,而所谓软件包管理器简单来说就是Linux的应用商城包管理器的核心功能自动解决依赖版本管理干净卸载安全验证统一管理虽然说源码安装这么麻烦但是用途非常特定主要面向开发者或有特殊需求的专家。1.2主流的包管理器1. yum应用发行版Fedora、RedHat、CentOS 7 及更早版本包格式.rpm底层工具rpm# yum 常用命令 yum install nginx # 安装 yum update nginx # 更新 yum remove nginx # 卸载 yum search nginx # 搜索 yum info nginx # 查看信息2. apt应用发行版Debian、Ubuntu 及其衍生版包格式.deb底层工具dpkg# apt 常用命令 apt update # 更新软件源 apt install nginx # 安装 apt upgrade nginx # 更新 apt remove nginx # 卸载 apt purge nginx # 彻底卸载含配置文件 apt search nginx # 搜索 apt show nginx # 查看信息1.3 软件仓库Repository软件包存放在称为软件源Repository的服务器上。操作系统内部有内置链接包管理器需要知道去哪里下载。由于大多数 Linux 发行版的官方软件源服务器都设在国外在国内直接访问时受限于国际网络带宽和复杂的网络环境通常会导致两种结果速度极慢无法连接为了解决这个问题国内涌现了许多机构如阿里云、清华大学、中科大、腾讯云等它们做的事情就是“镜像”。镜像Mirror就是把国外的官方源服务器上的所有内容完整地复制一份到国内的服务器上。镜像服务器会定时比如每隔几小时与官方源同步保证软件包是最新的。常见的国内镜像站1.4软件包管理器具体操作查看软件包通过 yum list 命令可以罗列出当前⼀共有哪些软件包. 由于包的数⽬可能⾮常之多, 这⾥我们需要使⽤ grep 命令只筛选出我们关注的包. 例如# Centos $ yum list | grep lrzsz lrzsz.x86_64 0.12.20-36.el7 base # Ubuntu $ apt search lrzsz Sorting... Done Full Text Search... Done cutecom/focal 0.30.3-1build1 amd64 Graphical serial terminal, like minicom lrzsz/focal,now 0.12.21-10 amd64 [installed] Tools for zmodem/xmodem/ymodem file transfer $ apt show lrzsz Package: lrzsz Version: 0.12.21-10 Priority: optional Section: universe/comm Origin: Ubuntu Maintainer: Ubuntu Developers ubuntu-devel-discusslists.ubuntu.com Original-Maintainer: Martin A. Godisch godischdebian.org软件包命名 名称 主版本号 次版本号 平台标识 架构 软件源el7代表 CentOS 7 / RHEL 7focal代表 Ubuntu 20.04x86_64代表 64 位系统base或Section代表软件源名称安装软件通过 yum/apt, 我们可以通过很简单的⼀条命令完成 gcc 的安装# Centos $ sudo yum install -y lrzsz # Ubuntu $ sudo apt install- y lrzszyum/apt 会⾃动找到都有哪些软件包需要下载, 这时候敲 y 确认安装.出现 complete 字样或者中间未出现报错, 说明安装完成.注意事项:安装软件时由于需要向系统⽬录中写⼊内容, ⼀般需要 sudo 或者切到 root 账⼾下才能完成.yum/apt安装软件只能⼀个装完了再装另⼀个. 正在yum/apt安装⼀个软件的过程中, 如果再尝试⽤yum/apt安装另外⼀个软件, yum/apt会报错.卸载软件aptUbuntu/Debian# 卸载但保留配置文件 sudo apt remove 软件名 # 彻底卸载删除配置文件 sudo apt purge 软件名 # 卸载 自动删除依赖 sudo apt autoremove 软件名 # 组合使用彻底卸载并清理依赖 sudo apt purge --autoremove 软件名yum/dnfCentOS/RHEL# 卸载软件 sudo yum remove 软件名 sudo dnf remove 软件名 # 卸载并删除依赖 sudo yum autoremove 软件名 sudo dnf autoremove 软件名 2. 编辑器vim2.编辑器VimVimVi IMproved是 Linux 上最常用的终端文本编辑器功能强大但学习曲线较陡。2.1 Vim常见的三种模式1. 命令模式Normal Mode默认进入的模式也被叫做正常/普通模式也是使用最频繁的模式。核心作用移动光标删除字符、单词、行复制、粘贴进入插入模式或底行模式光标移动命令行内移动可以在hjklwb前面带数字如30l就是向右移动30格跨行移动打开文件时跳转多窗口操作删除命令这里的删除其实是剪切的操作可以配合np来粘贴复制与粘贴替换命令撤销与重做命令作用u撤销上一步操作Ctrl r对撤销进行撤销重做查找与保存退出命令命令作用#(Shift3)高亮查找光标所在的单词按n跳到下一个匹配处ZZ(Shiftzz)保存并退出 Vim等同于:wq2. 插入模式Insert Mode唯一可以输入文字的模式。核心作用用于输入、修改文本。进入方式在命令模式下按 i命令作用i在当前光标前插入I在行首插入a在当前光标后插入A在行尾插入o在下一行插入O在上一行插入退出按Esc回到命令模式左下角会显示-- INSERT --提示。3. 底行模式last line mode执行命令的模式也叫末行模式。进入方式在命令模式下按:(Shift;)核心作用保存文件退出 Vim查找字符串替换文本显示行号常用操作命令作用:w保存:q退出:wq保存并退出:q!强制退出不保存:wq!强制保存退出:/hello查找 hello:%s/old/new/g全局替换:set nu显示行号:set nonu隐藏行号! command(命令)不退出vim执行命令如!pwd*替换模式Replace Mode覆盖式输入。在插入模式下输入新字符会推着后面的字符往前走在替换模式下输入新字符会逐个覆盖光标后面的字符。进入方式按R键Shift r*视图模式Visual Mode进行文本选中先选中一段文本然后对这段选中的文本统一执行某个操作如删除、复制、缩进、大小写转换等类似鼠标拖选功能。进入方式v进入字符可视模式按字符选择V进入行可视模式按整行选择Ctrl v进入块可视模式按矩形块选择特点用于选中文本区域类似鼠标拖选状态栏会显示-- VISUAL --、-- VISUAL LINE --或-- VISUAL BLOCK --批量注释技巧:在命令模式下ctrlv 进入视图模式使用hjkl进行区域块选择接着ShiftiI进入插入模式然后使用//注释一行接着按Esc完成批量注释(退出后在命令模式下)。如果要注释所有内容在视图模式下Shiftg全选接着同理模式切换图vim的分屏启动 Vim 时分屏# 水平分屏上下排列 vim -o file1 file2 file3 # 垂直分屏左右排列 vim -O file1 file2 file3 # 指定分屏数量比如水平分 3 个屏 vim -o3 file1 file2 file3在 Vim 内部进行分屏如果不加 filename就是复制当前窗口两个屏显示同一个文件。3.编译器 gcc ggcc vs gC/C程序的执行流程C模板进阶解析非类型参数、特化技术与分离编译实战-CSDN博客这篇博客有详细介绍编译器自举编译器自举是指用某门编程语言的编译器去编译该语言本身的编译器的过程。简单来说就是“自己编译自己”。C语言编译器的自举步骤设计C语言语法在文档中定义规则用汇编语言写一个“最小C编译器”功能简陋仅能编译简单的C程序用汇编编译器编译这个最小编译器→ 得到可执行文件即最小C编译器用这个最小编译器编译一个“更好的C编译器”的源代码该源代码是用C语言本身写的功能更全得到V1版C编译器能编译完整C语言修改源代码得到V2版编译器源代码增加优化、修复bug用V1编译器编译V2的源代码→ 生成V2版可执行编译器自举完成从此C编译器可以一直用C语言自身来改进常用命令示例gcc [选项] 要编译的⽂件 [选项] [⽬标⽂件]编译 C 程序 (gcc)# 将 main.c 编译成可执行文件 myapp gcc main.c -o myapp # 仅编译成目标文件不链接 gcc -c main.c -o main.o编译 C 程序 (g)# 将 main.cpp 编译成可执行文件 myapp g main.cpp -o myapp # 编译多个文件 g main.cpp helper.cpp -o myapp # 启用警告和 C11 标准 g main.cpp -o myapp -Wall -stdc11关键选项-o 文件名指定输出的可执行文件名。-c只编译不链接生成目标文件.o。-Wall显示所有常见的警告信息强烈推荐。-g生成供调试器如gdb使用的调试信息。-O2开启二级优化提高程序运行效率。gcc/g的-D选项用于在编译时在命令行定义宏效果等同于在源代码中使用#define。gcc -DDEBUG program.c //等价于 #define DEBUG gcc -DVERSION3 program.c //等价于 #define VERSION 3 gcc -DNAME\John\ program.c //等价于 #define NAME John # 方式1多个 -D gcc -DDEBUG -DVERSION1 -DNAME\test\ program.c # 方式2统一写不推荐可读性差 gcc -DDEBUG -DVERSION1 -DNAME\test\ program.c接下来我将使用gcc编译器分步编译test.c文件1.预处理预处理完成以下任务头文件展开将头文件内容拷贝到源文件中宏替换条件编译对代码进行动态裁剪可以用于多版本功能维护如社区版和专业版删除注释添加行号与文件标识使用-E选项可以让gcc或g在预处理完成后停止。基本语法gcc -E 源文件 -o 输出文件名xqqubuntu-server:~/mydir/test$ ll total 12 drwxr-xr-x 2 xqq xqq 4096 Apr 16 20:56 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 140 Apr 16 20:56 test.c xqqubuntu-server:~/mydir/test$ gcc -E test.c -o test.i xqqubuntu-server:~/mydir/test$ ll total 32 drwxr-xr-x 2 xqq xqq 4096 Apr 16 21:13 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 140 Apr 16 20:56 test.c -rw-rw-r-- 1 xqq xqq 18022 Apr 16 21:13 test.i2.编译在预处理阶段生成了干净的.i或.ii文件后编译阶段的核心任务是将这些预处理后的代码翻译成汇编语言。这个阶段是整个编译流程中最复杂、最核心的一步它由多个子步骤组成完全是在后台自动完成的词法分析将源代码拆解成一个个有意义的“单词”如关键字int、标识符main、运算符、常量0等。语法分析根据 C/C 的语法规则将 Token 序列组合成“语法树”如表达式、语句、函数定义检查括号是否匹配、语句末尾分号等。绝大多数语法错误如漏写分号、缺少括号都在此阶段被捕获。语义分析检查语法树的含义是否合法。例如变量是否声明、类型是否匹配、函数调用参数是否正确等。中间代码生成将语法树转换成平台无关的中间表示方便后续优化。优化对中间代码进行优化例如删除永远不会执行的代码、简化常量计算、提取循环中不变的变量等。目标汇编代码生成将优化后的中间代码转换为目标机器架构的汇编指令生成.s汇编文件。使用-S选项可以让gcc或g在编译完成后即生成汇编文件后停止。基本语法gcc -S 源文件 -o 输出文件名xqqubuntu-server:~/mydir/test$ ll total 32 drwxr-xr-x 2 xqq xqq 4096 Apr 16 21:43 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i xqqubuntu-server:~/mydir/test$ gcc -S test.i -o test.s xqqubuntu-server:~/mydir/test$ ll total 36 drwxr-xr-x 2 xqq xqq 4096 Apr 16 21:44 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.s3.汇编在上一阶段编译生成了汇编文件.s后汇编阶段的任务是将人类可读的汇编代码翻译成机器可执行的指令生成目标文件.o或.obj。这个阶段相对简单直接主要做两件事翻译指令将汇编指令如movq %rax, %rdi、call puts逐条翻译成对应的二进制机器码如48 89 c7、e8 00 00 00 00。生成符号表记录目标文件中的符号信息例如定义的符号函数名如main、全局变量名及其地址偏移。引用的外部符号如printf、puts这些函数尚未在本文件中找到定义需要链接器帮忙解决。关键点汇编器不检查语法语法已在编译阶段检查完毕它只做机械的翻译。生成的机器码是与 CPU 架构相关的如 x86、ARM。目标文件.o已经是二进制格式不再是文本文件直接用文本编辑器打开会看到乱码。使用-c选项可以让gcc或g在汇编完成后停止。基本语法gcc -c 汇编文件 -o 输出文件名xqqubuntu-server:~/mydir/test$ ll total 36 drwxr-xr-x 2 xqq xqq 4096 Apr 16 21:54 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.s xqqubuntu-server:~/mydir/test$ gcc -c test.s -o test.o xqqubuntu-server:~/mydir/test$ ll total 40 drwxr-xr-x 2 xqq xqq 4096 Apr 16 21:55 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 1752 Apr 16 21:55 test.o -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.stest.o/XXX.obj也叫做可重定位目标文件 ,他是二进制文件直接打开全是乱码为什么叫可重定位1. 地址尚未确定目标文件中的代码和数据地址还不是最终的内存地址而是相对于当前模块的偏移量通常从 0 开始例如call printf的地址还是0x00占位符不知道printf在内存的哪个位置2. 链接时需要重定位链接器会将多个可重定位文件.o/.obj和库文件合并重新计算每个符号的最终运行时地址如main函数的实际地址、printf的实际地址修改机器码中的地址占位符为真实地址——这个过程就叫重定位源文件中包含了很多库方法比如printf但源文件中并没有函数实现因此还需最后一步4.链接链接阶段做什么链接器ld负责将多个目标文件和库文件合并成最终的可执行文件主要做两件事符号解析找到每个符号函数、全局变量的定义位置。例如printf在test.o中只是声明U printf链接器需要去 C 标准库如libc.so或libc.a中找到它的实现。重定位将所有目标文件的代码和数据段合并为每个符号分配最终的内存地址并修正机器码中的地址占位符。# 链接 XXXX.o 和 C 标准库生成可执行文件 gcc XXXX.o -o testxqqubuntu-server:~/mydir/test$ ll total 40 drwxr-xr-x 2 xqq xqq 4096 Apr 16 21:58 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 1753 Apr 16 21:58 test.o -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.s xqqubuntu-server:~/mydir/test$ gcc test.o -o test xqqubuntu-server:~/mydir/test$ ll total 56 drwxr-xr-x 2 xqq xqq 4096 Apr 16 22:05 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rwxrwxr-x 1 xqq xqq 16000 Apr 16 22:05 test* -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 1753 Apr 16 21:58 test.o -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.s xqqubuntu-server:~/mydir/test$ ./test* hello world hello world num1004.动静态库4.1动静态库是什么1.库是什么库Library就是已经编译好的、可复用的代码集合类似于生活中的“工具箱”里面放着常用的工具函数随取随用编程中的“预制件”别人写好的printf()、sqrt()、sort()你直接调用就行库分为两种静态库和动态库。2.静态库是什么静态库在编译链接阶段把库中的代码直接复制到你的可执行文件里。文件后缀.aLinux、.libWindows特点链接后你的程序就独立了不再需要库文件比喻你要做一道菜你的程序从超市买回现成的调料包静态库拆开倒进去一起炒。最后这盘菜里已经包含了调料不需要再放调料包了。3.动态库是什么动态库编译时只记录“引用”不复制代码运行时才把库加载到内存多个程序可以共享同一份。文件后缀.soLinux、.dllWindows特点可执行文件很小但运行时必须依赖库文件存在比喻你做菜时只写了个菜谱记录“需要盐”等吃饭时再从盐罐里取盐。而且多个菜可以共用同一个盐罐。4.为什么要有库避免重复造轮子没有库每个程序都要自己写printf()→ 100个程序写100遍有库printf()放在库里所有程序调用即可节省磁盘和内存动态库特有静态库100个程序都用printf()→ 每个程序里都复制一份→ 浪费几百MB磁盘和内存动态库100个程序共享一份printf()代码 → 极省资源方便更新维护静态库发现printf()有bug → 100个程序全部重新编译、重新发布动态库只替换一个.so文件 → 所有程序自动修复实现插件化架构动态库特有优势主程序运行时动态加载插件.dll/.so用户可随时添加新功能无需重装软件一图看懂区别静态解决“代码组织”问题动态库共享库解决“资源浪费”问题没有库就没有现代软件工程。4.2库的命名方式关键点Linux下动/静态库都带lib前缀而Windows下通常没有。4.3 静态链接 vs 动态链接为什么需要链接在实际开发中不可能将所有代码放在一个源文件中。多个源文件之间存在依赖关系如一个文件调用另一个文件中定义的函数。每个*.c文件独立编译成*.o目标文件链接就是把这些目标文件合并成一个可执行程序的过程。1. 静态链接定义在编译阶段将所有需要的目标文件直接合并到最终的可执行文件中。缺点浪费空间每个可执行程序都有一份目标文件的副本。比如多个程序都调用printf()每个程序里都包含一份printf.o内存中存在多个副本。更新困难库函数的代码修改后所有依赖它的程序都需要重新编译链接。优点可执行程序中已具备运行所需的一切运行时速度快不依赖外部环境。2. 动态链接定义把程序按模块拆分成相对独立的部分在程序运行时才将它们链接在一起形成完整程序。优点节省空间多个程序共享同一份动态库的物理内存副本。更新方便替换动态库文件即可无需重新编译所有依赖程序。查看程序依赖的动态库以Linux为例ldd指令ldd是List Dynamic Dependencies的缩写用于列出可执行文件或动态库所依赖的动态库。ldd [选项] 文件名选项作用示例-v显示详细信息包括版本信息ldd -v test-u显示未使用的直接依赖ldd -u test-r显示重定位信息ldd -r testxqqubuntu-server:~/mydir/test$ ll total 56 drwxr-xr-x 2 xqq xqq 4096 Apr 16 22:05 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rwxrwxr-x 1 xqq xqq 16000 Apr 16 22:05 test* -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 1753 Apr 16 21:58 test.o -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.s xqqubuntu-server:~/mydir/test$ ldd test linux-vdso.so.1 (0x00007ffca5df9000) libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdd55fb1000) /lib64/ld-linux-x86-64.so.2 (0x00007fdd561e8000)可以看到 test程序依赖了C标准动态库libc.so.6。file指令file用于识别文件类型。它会读取文件头信息告诉你这个文件到底是什么类型的可执行文件、文本文件、图片、压缩包等。file [选项] 文件名xqqubuntu-server:~/mydir/test$ file test test: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]489656c71777e2a0fe3e426365b5d47b7db9f038, for GNU/Linux 3.2.0, not stripped由上面执行的file和ldd可知道test默认是动态连接 dynamically linked的想要强制静态要带选项-staticxqqubuntu-server:~/mydir/test$ gcc test.c -o test_static -static xqqubuntu-server:~/mydir/test$ ll total 936 drwxr-xr-x 2 xqq xqq 4096 Apr 20 17:23 ./ drwxr-xr-x 3 xqq xqq 4096 Apr 16 20:54 ../ -rwxrwxr-x 1 xqq xqq 16000 Apr 16 22:05 test* -rw-r--r-- 1 xqq xqq 257 Apr 16 21:26 test.c -rw-rw-r-- 1 xqq xqq 18032 Apr 16 21:43 test.i -rw-rw-r-- 1 xqq xqq 1753 Apr 16 21:58 test.o -rw-rw-r-- 1 xqq xqq 933 Apr 16 21:44 test.s -rwxrwxr-x 1 xqq xqq 900440 Apr 20 17:23 test_static* xqqubuntu-server:~/mydir/test$ file test_static test_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]4372dec8e580b8020247048de5f2a18c518a3728, for GNU/Linux 3.2.0, not stripped xqqubuntu-server:~/mydir/test$ ldd test_static not a dynamic executable #not a dynamic executable 这是一个静态链接的程序它不依赖任何动态库所以 ldd 无依赖可列。由上面的结果静态链接statically linked版本比动态链接版本大了约56倍这就是静态链接“浪费空间”最直观的证据因为所有依赖的库代码都被“焊死”进了可执行文件。