Linux 基础
Linux 系统目录结构
Unix/Linux 采用树状结构的文件系统,它由目录和目录下的文件一起构成,其目录结构如下:
/
:根目录,一般根目录下只存放目录,在Linux下有且只有一个根目录。所有的东西都是从这里开始。当你在终端里输入“/home”,你其实是在告诉电脑,先从/(根目录)开始,再进入到home目录。/bin
/usr/bin
:可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。/boot
:放置linux系统启动时用到的一些文件,如Linux的内核文件:/boot/vmlinuz,系统引导管理器:/boot/grub。/dev
:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,如使用cat命令读取/dev/input/mice 即是在读取鼠标事件。/etc
:系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有 /etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d。/home
:系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,表示当前用户的家目录,edu 表示用户 edu 的家目录。/lib
/usr/lib
/usr/local/lib
:系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助。/mnt
/media
:光盘默认挂载点,通常光盘挂载于 /mnt/cdrom 下,也不一定,可以选择任意位置进行挂载。/opt
:给主机额外安装软件所摆放的目录。/proc
:此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有 /proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/* 等。/root
:系统管理员root的家目录(宿主目录)。/sbin
/usr/sbin
/usr/local/sbin
:放置系统管理员使用的可执行命令,如fdisk、shutdown、mount 等。与 /bin 不同的是,这几个目录是给系统管理员 root使用的命令,一般用户只能"查看"而不能设置和使用。/tmp
:一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下。/srv
:服务启动之后需要访问的数据目录,如 www 服务需要访问的网页数据存放在 /srv/www 内。/usr
:应用程序存放目录,/usr/bin 存放应用程序,/usr/share 存放共享数据,/usr/lib 存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local: 存放软件升级包。/usr/share/doc: 系统说明文件存放目录。/usr/share/man: 程序说明文件存放目录。/usr/include:存放头文件。/var
:放置系统执行过程中经常变化的文件,如随时更改的日志文件 /var/log,/var/log/message:所有的登录文件存放目录,/var/spool/mail:邮件存放的目录,/var/lib/mysql:MySQL数据库成功安装后,使用的库、表相关文件,存放在该目录下。
Linux 基本命令
Linux 命令格式
command [-options] [parameter1] ...
- command:命令名,相应功能的英文单词或单词的缩写
- options:选项,可用来对命令进行控制,可以省略
- parameter1:传给命令的参数,可以是零个一个或多个
–help
一般 Linux 命令自带的帮助信息。可以使用--help
查看。如:想查看命令“ls”的用法:
ls --help
并不是所有命令都自带这个选项。
man 手册
共有 9 章:
-
命令格式:
man [选项] 命令名 # 使用man命令时最后带上章节,如: man 3 printf
-
man 手册中的功能键:
功能键 | 功能 |
---|---|
空格键 | 显示手册页的下一屏 |
Enter键 | 一次滚动手册页的一行 |
b | 回滚一屏 |
f | 前滚一屏 |
q | 退出man手册 |
h | 列出所有功能键 |
/test | 在手册中搜索字符串test |
切换目录:cd
命令 | 含义 |
---|---|
cd | 切换到当前用户的主目录(/home/用户目录) |
cd ~ | 切换到当前用户的主目录(/home/用户目录) |
cd . | 切换到当前目录 |
cd … | 切换到上级目录 |
cd - | 切换到上一个进入的目录 |
目录操作:mkdir、rmdir
# 在当前目录下创建目录dir1
mkdir dir1
# 在tmp目录下创建目录dir2
mkdir /tmp/dir2
# 在当前目录下创建目录树dir1/dir2/dir3
mkdir -p dir1/dir2/dir3
# 删除空目录(怎么使用mkdir创建的,就怎么使用rmdir删除)
rmdir 路径
查看文件信息:ls
文件分类
通常,Unix/Linux 系统中常用的文件类型有 7 种:
-
普通文件 -
普通文件是计算机操作系统用于存放数据、程序等信息的文件,一般都长期存放于外存储器(磁盘、磁带等)中。普通文件一般包括文本文件、数据文件、可执行的二进制程序文件等。
在 Unix/Linux 中可以通过file命令来查看文件的类型。如果文件后面携带文件名,则查看指定文件的类型,如果携带通配符“*”,则可以查看当前目录下的所有文件的类型。 -
目录文件 d
Unix/Linux系统把目录看成一种特殊的文件,利用它构成文件系统的树型结构。
目录文件只允许系统管理员对其进行修改,用户进程可以读取目录文件,但不能对它们进行修改。
每个目录文件至少包括两个条目,“…”表示上一级目录,“.”表示该目录本身。 -
管道文件 p
管道文件也是 Unix/Linux 中较特殊的文件类型,这类文件多用于进程间的通信。 -
链接文件 l
类似于 windows 下的快捷方式,链接分为软链接(符号链接)和硬链接。 -
设备文件(字符设备 c、块设备 b)
Unix/Linux 系统把每个设备都映射成一个文件,这就是设备文件。它是用于向 I/O 设备提供连接的一种文件,分为字符设备和块设备文件。
字符设备的存取以一个字符为单位,块设备的存取以字符块为单位。每一种 I/O 设备对应一个设备文件,存放在 /dev 目录中,如行式打印机对应 /dev/lp,第一个软盘驱动器对应 /dev/fd0。 -
套接字文件 s
ls 用法
ls 常用选项:
选项 | 含义 |
---|---|
-a | 显示指定目录下所有子目录与文件,包括隐藏文件 |
-l | 以列表方式显示文件详细信息 |
-h | 配合 -l 以人性化方式显示文件大小 |
例如:
创建文件:touch
若文件不存在,则创建该文件;若该文件存在,则修改该文件的时间戳。
拷贝:cp
cp 命令的功能是将给出的文件或目录复制到另一个文件或目录中。常用选项如下:
选项 | 含义 |
---|---|
-a | 该选项通常在复制目录时使用,它保留链接、文件属性,并递归地复制目录,简单而言,保持文件原有属性 |
-r | 若给出的源文件是目录文件,则cp将递归复制该目录下的所有子目录和文件,目标文件必须为一个目录名 |
-i | 交互式复制,在覆盖目标文件之前将给出提示要求用户确认 |
-v | 显示拷贝进度 |
删除文件或目录:rm
可通过 rm 删除文件或目录。rm 命令删除的文件、目录很难恢复!为了防止文件误删,可在 rm 后使用 -i 参数以逐个确认要删除的文件。 常用选项如下:
选项 | 含义 |
---|---|
-i | 以交互式方式执行 |
-f | 强制删除,忽略不存在的文件,无需提示 |
-r | 递归地删除目录下的内容,删除文件夹时必须加此参数 |
移动或重命名:mv
用户可以使用 mv 命令来移动文件或目录,也可以给文件或目录重命名。常用选项如下:
选项 | 含义 |
---|---|
-i | 以交互式方式执行,在覆盖时会给出提示 |
-f | 禁止交互方式,如有覆盖也不会给出提示 |
-v | 显示移动进度 |
显示文件全部内容:cat
cat 将文件内容一次性输出到终端。
缺点: 终端显示的内容有限,如果文件太长无法全部显示。
# 查看/etc/passwd的内容
cat /etc/passwd
# 查看/etc/passwd的内容,并输出所有行的编号
cat -n /etc/passwd
# 查看/etc/passwd的内容,并给非空行编号
cat -b /etc/passwd
# 查看/etc/passwd的所有内容(每行后面的“换行符”用“$”表示)
cat -A /etc/passwd
分页显示文件内容:less
less 命令将文件内容分页显示到终端,可以自由上下浏览,j 是向下浏览,k 是向上浏览,q 退出。
显示文件头部的内容:head
head 命令从文件头部开始查看前 n 行的内容。如果没有指定行数,默认显示前 10 行内容。
# 查看/etc/passwd前10行的内容
head /etc/passwd
# 查看/etc/passwd前10个字节的内容
head -c /etc/passwd
# 查看/etc/passwd前5行的内容
head -n 5 /etc/passwd
head -5 /etc/passwd
# 查看/etc/passwd前10行的内容,但打印头信息
head -v /etc/passwd
显示文件尾部的内容:tail
从文件尾部向上查看最后 n 行的内容。如果没有指定行数,默认显示最后 10 行内容。具体用法与 head 相同。
查找符合条件的文件:find
按文件名查询:-name
# 命令:
find 路径 -name “文件名”
# 例如:
find ./ -name *.out
按文件大小查询:-size
# 命令:
find 路径 -size 范围
# 大于:用“+”表示,如“+100k”(k必须小写)
# 小于:用“-”表示,如“-100M”(M必须大写)
# 等于:不用加符号
# 例如:
find ./ -size +100k # 查询大于100k的文件
find ./ -size +100k -size -100M # 查询大于100k小于100M的文件
按文件类型查询:-type
- f → 普通文件(而不是用-)
- d → 目录
- l → 符号链接
- b → 块设备
- c → 字符设备
- s → socket文件,网络套接字
- p → 管道
# 查找普通的文件
find ./ -type f
文本模式查找:grep
# 命令格式:(需要引号)
grep [-选项] "搜索的内容串" 文件名
常用选项如下:
选项 | 含义 |
---|---|
-v | 显示不包含文本的所有行(相当于求反) |
-n | 显示匹配行及行号 |
-i | 忽略大小写 |
-r | 递归查找目录下的文件 |
例如:
# 查找/etc/passwd中含root的行
grep "root" /etc/passwd
# 查找/etc/passwd中不含root的行
grep -v "root" /etc/passwd
管道:|
使用管道将一个命令的输出作为另一个命令的输入。例如:
归档(打包)文件:tar
计算机中的数据经常需要备份,tar 是 Unix/Linux 中最常用的备份工具,此命令可以把一系列文件归档到一个大文件中,也可以把档案文件解开以恢复数据。
归档和压缩不同!!!
# 命令格式:(tar比较特殊,选项可以省略”-“)
tar [-选项] 归档后的文件名 要归档的文件
常用选项如下:
选项 | 含义 |
---|---|
-c | 生成档案文件,创建打包文件 |
-v | 列出归档解档的详细过程,显示进度 |
-f | 指定档案文件名称,f 后面一定是 .tar 文件名,放选项最后 |
-t | 列出档案中包含的文件 |
-x | 解开档案文件 |
-z | 指定压缩格式为:file.tar.gz |
# 最常用的3种组合
tar -cvf # 归档
tar -xvf # 解归档
tar -tvf # 查看归档文件中的包含的文件
压缩:gzip
常与 tar 结合使用实习文件打包、压缩!
tar 只负责打包文件,但不压缩,用 gzip 压缩 tar 打包后的文件,其扩展名一般用 xxx.tar.gz 。
# 命令格式:(gzip后不能跟目录)
# 如果不加选项,默认是压缩
gzip [-选项] 被压缩的文件
常用选项如下:
选项 | 含义 |
---|---|
-d | 解压(gzip -d 等效于 gunzip) |
-f | 压缩所有子目录 |
gzip 配合 tar 使用:
# 归档压缩
tar -czvf test.tar.gz 需要压缩的文件
# 解压解归档
tar -xzvf test.tar.gz
# 解压解归档到目录/tmp
tar -xzvf test.tar.gz -C /tmp
修改文件权限:chmod
chmod 修改文件权限有两种格式:字母法、数字法。
-
字母法
# 命令格式: chmod u\g\o\a +\-\= 文件 # 例如: # 对于test.txt,给用户所属组添加执行权限 chmod g+x test.txt # 对于test.txt,给将其他用户的权限设为可读可执行 chmod o=rx test.txt # 对于test.txt,将所属用户权限设为可读可写,将用户所属组权限设为可读,将其他用户权限设为可读 chmod u=rw,g=r,o=r test.txt # 不能省略逗号
u\g\o\a 含义 u user,表示该文件的所有者 g group,表示与该文件所有者属于同用户组 o other,表示其他人 a all,表示这三者都是 +\-\= 含义 + 增加权限 - 撤销权限 = 设定权限 -
数字法
“rwx” 这些权限也可以用数字来代替。# 命令格式: chmod 数字组合 文件名 # 例如: # 对于test.txt,user可读可写可执行,group可读可执行,other可读 chmod 754 test.txt # 对于test.txt,user可读 chmod 400 test.txt # 改变testDir目录下所有的权限 chmod -R 777 test/
r\w\x- 对应数字代号 r 读权限,代号为4 w 写权限,代号为2 x 执行权限,代号为1 - 没有任何权限,代号为0
修改文件所有者:chown
# 命令格式:
chown 用户名 文件或目录名
# 将test.txt所属用户修改为root用户
sudo chown root test.txt
# 将test.txt所属用户组修改为root组
sudo chown :root test.txt # “:”不可省略
# 同时修改所属用户和用户组为root
sudo chown root:root test.txt
软件安装与卸载
以 Ubuntu 为例!!!
-
在线安装
# 更新软件包列表 sudo apt update # 安装xxx软件 sudo apt install xxx # 卸载xxx软件 sudo apt remove xxx # 清理安装包 sudo apt clean
-
离线安装
在 Ubuntu 下安装 deb 格式的安装包。# 软件安装 sudo dpkg -i xxx.deb # 软件卸载(也可以用上面的方法卸载) sudo dpkg -r 软件名
重定向:>、>>
重定向一般用在 shell 脚本中。
Linux 允许将命令执行结果重定向到一个文件,本应显示在终端上的内容保存到指定文件中,方便后续查看。
>
:以覆盖的方式重定向
>>
:以追加的方式重定向
# 标准错误输出需要使用“2>”进行重定向
# 假设使用了错误的命令“lss”,则会在终端输出错误信息,若重定向到文件error:
lss 2> error
# 将标准错误输出重定向到黑洞文件/dev/null
lss 2> /dev/null
# 将标准输出和标准错误输出都重定向到黑洞文件/dev/null
ls dddd &> /dev/null
目录树:tree
使用前需要先安装 tree。
# 显示所在目录的完整目录树
tree
# 目录树只显示到2层
tree -L 2
链接:ln
-
软链接
软链接不占用磁盘空间,源文件删除则软链接失效。源文件删除则软链接失效。源文件搬移也可能造成连接失效,因此建议使用绝对路径法创建软连接。# 命令格式: ln -s 源文件 链接文件 # 创建软链接a_link指向文件a ln -s a a_link
-
硬链接
硬链接只能链接普通文件,不能链接目录。# 命令格式: ln 源文件 链接文件 # 创建硬链接a_link指向文件a ln a a_link
vi(vim)编辑器
vi 编辑器是 Unix 系统中最常见的基础文本编辑器。Linux 下升级为 vim 编辑器,它不仅兼容 vi 的所有指令,而且还有一些新的特性,例如 vim 可以撤消无限次、支持关键词自动完成、可以用不同的颜色来高亮你的代码。vim 普遍被推崇为类 vi 编辑器中最好的一个。出于历史的原因使用时大家习惯性的统一简称 vi。
vi 有三种工作模式:命令模式、文本输入模式、末行模式。
切换编辑模式
命令模式下执行!
按键 | 功能 |
---|---|
i | 光标位置当前处插入文字 |
I | 光标所在行首插入文字 |
o | 光标位置下方开启新行 |
O | 光标位置上方开启新行 |
a | 光标位置右边插入文字 |
A | 光标所在行尾插入文字 |
s | 以删除一个字符为条件,切换工作模式 |
S | 以删除一行为条件,切换工作模式 |
光标移动
命令模式下执行!
按键 | 功能 |
---|---|
Ctrl + f | 向前滚动一屏 |
Ctrl + b | 向后滚动一屏 |
gg | 到文件第一行行首 |
G | 到文件最后一行行首 |
nG 或 ngg | 到指定行,n 为目标行数 |
0(数字) | 光标移动到行首 |
$ | 光标移动到行尾 |
l | 向右移动光标 |
h | 向左移动光标 |
k | 向上移动光标 |
j | 向下移动光标 |
^ | 光标移动到当前行第一个有效字符位置) |
复制粘贴
命令模式下执行!
按键 | 功能 |
---|---|
yy | 复制当前行 |
nyy | 复制从当前行开始的 n 行 |
p | 粘贴到光标位置的下一行 |
P(大写) | 粘贴到光标位置的上一行 |
区域选择复制粘贴:
1. 选择: 命令模式下,将光标移动至待复制内容首字符,按 v 进入可视模式,使用 hjkl 移动光标,选中区域
2. 复制: y
3. 粘贴:p 将内容粘贴至光标后,P 向前粘
剪切(删除)
命令模式下执行!
按键 | 功能 |
---|---|
[n]x | 删除光标后 n 个字符 |
[n]X | 删除光标前 n 个字符 |
D | 删除光标所在位置到此行尾的字符 |
[n]dd | 删除从当前行开始的 n 行 |
dw | 删除光标所在位置到该单词结尾的字符 |
d0 | 删除光标前本行所有内容,不包含光标所在字符 |
撤销和恢复
命令模式下执行!
按键 | 功能 |
---|---|
. | 执行上一次的操作 |
u | 撤销上一次的操作 |
Ctrl + r | 恢复撤销· |
查找
命令模式下执行!
按键 | 功能 |
---|---|
/字符串 | 查找指定字符串(n、N查找下一个) |
替换
命令模式下执行!
按键 | 功能 |
---|---|
r | 替换当前字符 |
R | 替换当前行光标后的字符 |
保存退出
末行模式下执行!
按键 | 功能 |
---|---|
:wq | 保存退出 |
:x(小写) | 保存退出 |
:X(大写) | 加密 |
:w filename | 保存到指定文件 |
:q | 退出,若文件被修改但没保存不允许退出 |
:q! | 强制退出,不报错 |
替换
末行模式下执行!
按键 | 功能 |
---|---|
:s/abc/123/ | 光标所在行的第一个abc被替换为123 |
:s/abc/123/g | 光标所在行的所有abc被替换为123 |
:1,10s/abc/123g | 将1至10行所有的abc全部替换为123 |
:%s/abc/123/g | 当前文件的所有abc替换为123 |
:%s/abc/123/gc | 同上,但每次替换需要用户确认 |
分屏
末行模式下执行!
按键 | 功能 |
---|---|
:sp | 水平分屏 |
:vsp | 垂直分屏 |
Ctrl + w + w | 切换光标所在分屏 |
:wqall | 可以一次保存退出所有分屏 |
:sp 文件名 | 将当前文件和另一个文件水平分屏 |
:vsp 文件名 | 将当前文件和另一个文件垂直分屏 |
vim -O a.c b.c | 打开的时候就垂直分屏 a.c 和 b.c |
vim -o a.c b.c | 打开的时候就水平分屏 a.c 和 b.c |
:!shell命令 | 在 vim 的末行模式中执行 shell 命令。如查看 man 手册(:!man 3 printf) |
行号
末行模式下执行!
按键 | 功能 |
---|---|
:set nu | 显示行号 |
:set nonu | 不显示行号 |
GCC 编译器
gcc 编译器从拿到一个 c 源文件到生成一个可执行程序,中间一共经历了四个步骤:
四个步骤并不是 gcc 独立完成的,而是在内部调用了其他工具,从而完成了整个工作流程:
# 编译命令格式:
gcc [options] file ...
g++ [options] file ...
# 命令、选项和源文件之间使用空格分隔;
# 一行命令可以有0个、1个或多个选项;
# 文件名可以使用绝对路径,也可使用相对路径;
# 如果命令中不包含输出可执行文件的文件名,Linux 平台会默认使用 a.out
常用选项如下:
选项 | 含义 |
---|---|
-o file | 指定生成的输出文件名为 file |
-E | 只进行预处理 |
-S(大写) | 只进行预处理和编译 |
-c(小写) | 只进行预处理、编译、·汇编 |
-v | 查看 gcc 版本号 |
-g | 包含调试信息 |
-On(n=0~3) | 编译优化,n 越大优化得越多 |
-Wall | 提示更多警告信息 |
-Werror | 将警告信息当作错误处理 |
-D | 编译时定义宏 |
# 显示所有的警告信息
gcc -Wall hello.c
# 将警告信息当作错误处理
gcc -Wall -Werror hello.c
- 选项
-D
的案例:
执行结果如下:#include <stdio.h> int main() { printf("SIZE: %d", SIZE); return 0; }
ubuntu@VM-12-7-ubuntu:~/test$ gcc 1.c -DSIZE=10 ubuntu@VM-12-7-ubuntu:~/test$ ./a.out hello 10 ubuntu@VM-12-7-ubuntu:~/test$
静态链接、动态链接
静态链接
由链接器在链接时将库的内容加入到可执行程序中。
- 优点:
① 对运行环境的依赖性较小,具有较好的兼容性。 - 缺点:
① 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间。
② 库函数有了更新必须重新编译应用程序。
动态链接
链接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。
- 优点:
① 在需要的时候才会调入对应的资源函数。
② 简化程序的升级;有着较小的程序体积。
③ 实现进程之间的资源共享(避免重复拷贝)。 - 缺点:
① 依赖动态库,不能独立运行。
② 动态库依赖版本问题严重。
静态、动态链接对比
测试程序如下:
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
编译时采用动态链接和静态链接:
# 不加任何选项默认是动态链接
gcc hello.c -o hello_share
# 静态链接时加选项 -static
gcc -static hello.c -o hello_static
结果如下:
制作静态库
静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。按照习惯,一般以”.a“做为文件后缀名。静态库的命名一般分为三个部分:① 前缀 lib;② 库名称:自己定义即可;③ 后缀:.a 。 所以最终的静态库名字应该为:libxxx.a 。
-
制作步骤一:
将 c 源文件生成对应的 .o 文件:gcc -c add.c -o add.o gcc -c sub.c -o sub.o
-
制作步骤二:
使用打包工具 ar 将准备好的 .o 文件打包为 .a 文件 libtest.a:# 命令格式: ar -rcs 静态库名称 .o文件 # 使用 ar 工具时需要使用选项 -rcs # r更新、 c创建、s建立索引 ar -rcs libtest.a sub.o add.o
-
使用静态库:
静态库制作完成之后,需要将 .a 文件和头文件一起发布给用户。假设测试文件为 main.c,静态库文件为 libtest.a 头文件为 sub.h 和 add.h。编译命令为:
# -I:指出头文件所在位置 # -L:指出静态库所在位置 # -l:指定链接时需要的库,去掉前缀 lib 和后缀 .a gcc main.c -I./ -L./ -ltest -o test
制作动态库
共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。 动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。为什么需要动态库,其实也是静态库的特点导致。动态库的命名一般分为三个部分:① 前缀 lib;② 库名称:自己定义即可;③ 后缀:.so 。 所以最终的静态库名字应该为:libxxx.so 。
-
制作步骤一:
生成目标文件,此时要加编译选项-fpic
,其意思是创建与位置无关的编译程序,为了能够在多个应用程序间共享(pic,Position Independent Code)。# 生成 sub.o gcc -fpic -c sub.c # 生成 add.o gcc -fpic -c add.c
-
制作步骤二:
生成共享库,此时要加链接器选项-shared
,其作用是指定生成动态链接库。# 库的名字为 test,库文件的名字为 libtest.so gcc -shared add.o sub.o -o libtest.so
-
使用动态库:
动态库制作完成之后,需要将 .so 文件和头文件一起发布给用户。编译(与静态库相同):
gcc main.c -I./ -L./ -ltest -o test
执行编译生成的可执行文件时会报错:
在执行程序的时候是如何找到共享库文件的呢?
① 当系统加载可执行代码时候,能够知道其所依赖的库的名字(编译的时候告诉了编译器动态库的名字),但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
② 对于 elf 格式的可执行程序,是由 ld-linux.so* 来完成绝对路径查找的,它先后搜索 elf 文件的 DT_RPATH 段、环境变量 LD_LIBRARY_PATH、/etc/ld.so.cache 文件列表、/lib/和/usr/lib目录,找到库文件后将其载入内存。如何让系统找到制作的动态库?
① 临时设置 LD_LIBRARY_PATH:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
此方式只在该终端生效,在其它终端以及系统重启后失效,再次运行可执行文件也会报错!② 永久设置:
把 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径,添加到 ∼/.bashrc 或者 /etc/profile 文件中。③ …(一共有5种方式)
GDB 调试器
GDB(GNU Debugger)是 GCC 的调试工具。GDB主要有下面四个方面的功能:
- 启动程序,可以按照你的自定义的要求随心所欲的运行程序;
- 可让被调试的程序在你所指定的断点处停住(断点可以是条件表达式);
- 当程序被停住时,可以检查此时你的程序中所发生的事;
- 动态的改变你程序的执行环境。
生成调试信息
一般来说 GDB 主要调试的是C/Cpp的程序。要调试 C/Cpp 的程序,首先在编译时,我们必须要把调试信息加到可执行文件中,使用-g
。
gcc -g test.c -o a.out
g++ -g test.c -o a.out
如果没有 -g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。
启动 GDB
-
启动 gdb:
# 进入 a.out 的调试 gdb a.out
-
设置运行参数
# 设置运行时的参数 set args 参数 # 查看设置好的参数 show args
-
启动程序
# 程序开始执行,如果有断点就停在第一个断点处 run # 程序向下执行一行 start
显示源码
在 gdb 调试界面,用 list 命令来打印程序的源代码,默认打印 10 行。
# 打印当前行后面的 10 行
list
# 打印当前行前面的 10 行
list -
# 打印第 lineNum 行上下的内容
list lineNum
# 打印函数 function 的源程序
list function
# 设置一次显示源代码的行数为 count
set listsize count # 如:set listsize 20
# 显示当前 listsize 的设置值
show listsize
设置断点
-
简单断点
break 设置断点,可以简写为 b。# 在源程序第 10 行设置断点 b 10 # 在函数 func 入口处设置断点 b func
-
多文件设置断点
C++ 中可以使用 class::function 或 function(type,type) 格式来指定函数名。如果有名称空间,可以使用 namespace::class::function 或者 function(type,type) 格式来指定函数名。# 在源文件 filename 的第 lineNum 行设置断点 break filename:lineNum # 在源文件 filename 的函数 function 入口处设置断点 break filename:function # 在类 class 的成员函数 function(...) 入口处设置断点 break class::function(type,type) # 在命名空间为 namespace 的类 class 的 # 成员函数 function(...) 入口处设置断点 break namespace::class::function(type,type)
-
查看所有断点
# 下面 4 个命令功能一样,无差别 info b info break i break i b
条件断点
一般来说,为断点设置一个条件,我们使用 if 关键词,后面跟其断点条件。
# 在 test.c 的第8行,i 取值为5时停住
b test.c:8 if i==5
维护断点
# delete 删除指定的断点,其简写命令为 d。
# 如果不指定断点号,则表示删除所有的断点。
delete # 删除所有断点
delete [2-5] # 删除断点2-5
delete 3 # 删除断点3
d 3
# disable 禁用断点,其简写命令为 dis。
# 比删除更好的一种方法是 disable 停止点,disable 了的停止点,
# GDB 不会删除,当你还需要时,enable 即可,就好像回收站一样。
# 如果什么都不指定,表示 disable 所有的停止点。
disable [2-5]
disable 3
dis 3
# enable 使无效断点生效,简写命令是ena。
# 如果什么都不指定,表示enable所有的停止点。
enable [2-5]
enable 3
en 3
调试代码
- run 运行程序,可简写为 r
- next 单步跟踪,函数调用当作一条简单语句执行,可简写为 n
- step 单步跟踪,函数调进入被调用函数体内,可简写为 s
- finish 退出进入的函数
- until 在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体,可简写为 u
- continue 继续运行程序,停在下一个断点的位置,可简写为 c
- quit 退出 gdb,可简写为 q
数据查看
在运行时查看数据,print 打印变量、字符串、表达式等的值,可简写为 p。
# 打印 i 此时的取值
p i
自动显示
你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的GDB命令是 display。
# 自动显示指定变量的值
display 变量名 # 如:display i
# 查看 display 设置的自动显示的信息
info display
# 不显示编号为 num 的 display 信息
undisplay num
# 删除自动显示,nums 意为所设置好了的自动显式的编号。
# 如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)
delete display nums
# disable 和 enalbe 不删除自动显示的设置,而只是让其失效和恢复
disable display nums
enable display nums
查看、修改变量的值
# 查看变量 i 的类型(查询结果如:type=int)
ptype i
# 打印变量 i 的值
p i
# 将变量 i 的值设置为47
# 你可以使用set var命令来告诉GDB,i 不是 GDB 的参数,而是程序的变量名
set var i=47
Makefile
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 就像—个 Shell 脚本一样,其中也可以执行操作系统的命令。
Makefile带来的好处就是―—“自动化编译",一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令。
Makefile 文件命名规则:makefile 和 Makefile 都可以,推荐使用 Makefile。
1条规则
目标:依赖文件列表
<Tab>命令列表
Makefile 基本规则三要素:
① 目标:通常是要产生的文件名称。目标可以是可执行文件或其它 obj 文件,也可是一个动作的名称。
② 用来输入从而产生目标的文件;一个目标文件通常有几个依赖文件(也可以没有)。
③ make 执行的动作,一个规则可以含几个命令(也可以没有);有多个命令时,每个命令占一行。
例如:
all:test1 test2
echo "hello all"
test1:
echo "hello test1"
test2:
echo "hello test2"
执行结果为:
3个自动变量
在 Makefile 中使用变量有点类似于 c 语言中的宏定义,使用该变量相当于内容替换,使用变量可以使 Makefile 易于维护,修改内容变得简单。
-
自定义变量
变量定义:变量名=变量值
引用变量:$(变量名) 或 $【例如下面:第三个版本的 Makefile】
-
makefile 提供的变量
除了使用用户自定义变量,makefile 中也提供了一些变量(变量名大写)供用户直接使用,我们可以直接对其进行赋值。CC=gcc
CPPFLAGS:C 预处理的选项,-I(大写的 i)
CFLAGS:C 编译器的选项,-Wall -g -c
LDFLAGS:链接器选项,-L -l(小写的 L) -
3个自动变量
自动变量只能在规则中使用!!!$^
:表示规则中的所有依赖条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项
$@
:表示规则中的目标
$<
:表示规则中的第一个依赖条件【例如下面:第四个版本的 Makefile】
-
模式规则
示例:# 模式匹配,所有的 .o 都依赖对应的 .c %.o:%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) &< -o $@
【例如下面:第五个版本的 Makefile】
2个函数
makefile 中的函数有很多,在这里只记录两个最常用。
- wildcard
查找指定目录下的指定类型的文件# 找到当前目录下所有后缀为.c的文件,赋值给src src = $(wildcard *.c)
- patsubst
匹配替换# 把src变量里所有后缀为 .c 的文件替换成 .o obj = $(patsubst %.c,%.o, $(src))
【例如下面:第六个版本的 Makefile】
Makefile 中的伪目标
clean 的用途:清除编译生成的中间 .o 文件和最终目标文件。
如果当前目录下有同名 clean 文件,则make clean
不执行 clean 对应的命令,解决方式是声明伪目标。声明目标为伪目标之后,makefile 将不会判断该目标是否存在或者该目标是否需要更新,会无条件执行这个命令。
# 伪目标声明
.PHONY:clean
【例如下面:第七个版本的 Makefile】
make 命令格式
make 是一个命令工具,它解释 Makefile 种的规则。
# 命令格式:
make [-f filename][options][targets]
-
[-f filename]
① make 默认在工作目录中寻找名为 GNUmakefile、makefile、Makefle 的文件作为 makefile 输入文件
② -f 可以指定以上名字以外的文件作为 makefile 输入文件 -
[options]
① -v:显示 make 工具的版本信息
② -w:在处理 makefile 之前和之后显示工作路径
③ -C dir:读取 makefile 之前,改变工作路径至 dir 目录
④ -n:只打印要执行的命令但不执行
⑤ -s:执行但不显示执行的命令 -
[targets]
① 若使用 make 命令时没有指定目标,则 make 工具默认会实现 makefile 文件内的第一个目标,然后退出
② 指定了 make 工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)(如:make clean)
例如:
Makefile 示例
测试程序:test.c、add.c、sub.c
-
最简单的 Makefile
test:add.c sub.c test.c gcc add.c sub.c test.c -o test
缺点:效率低,修改一个文件,所有文件都要编译一次。
-
第二个版本的 Makefile
test:add.o sub.o test.o gcc add.o sub.o test.o -o test add.o:add.c gcc -c add.c -o add.o sub.o:sub.c gcc -c sub.c -o sub.o test.o:test.c gcc -c test.c -o test.o
此时若修改了一个文件,编译时只需要重新编译这个被改变的文件。
-
第三个版本的 Makefile
OBJS = add.o sub.o test.o test:$(OBJS) gcc $(OBJS) -o test add.o:add.c gcc -c add.c -o add.o sub.o:sub.c gcc -c sub.c -o sub.o test.o:test.c gcc -c test.c -o test.o clean: rm -rf $(OBJS) test
使用了自定义变量,使 Makefile 更精简。
并且,加上了目标 clean 用于快速删除指定的文件,因此在后面删除指定文件时只需要使用命令make clean
即可。 -
第四个版本的 Makefile
OBJS = add.o sub.o test.o TARGET = test $(TARGET):$(OBJS) gcc -c $^ -o $@ add.o:add.c gcc -c $< -o $@ sub.o:sub.c gcc -c $< -o $@ test.o:test.c gcc -c $< -o $@ clean: rm -rf $(OBJS) $(TARGET)
引入了3个自动变量。但其缺点是有命令重复了很多次。
-
第五个版本的 Makefile
OBJS = add.o sub.o test.o TARGET = test $(TARGET):$(OBJS) gcc -c $^ -o $@ # 模式规则 %.o:%.c gcc -c $< -o $@ clean: rm -rf $(OBJS) $(TARGET)
引入了模式规则,使 Makefile 更精简。
-
第六个版本的 Makefile
# 获取当前目录下所有的 .c 文件,并赋值给SRC SRC=$(wildcard ./*.c) # 将SRC变量中所有出现 .c 的地方替换为 .o OBJS=$(patsubst %c, %.o, $(SRC)) TARGET = test $(TARGET):$(OBJS) gcc -c $^ -o $@ # 模式规则 %.o:%.c gcc -c $< -o $@ clean: rm -rf $(OBJS) $(TARGET)
引入两个函数,自动获取指定目录中所有的 .c 文件。
-
第七个版本的 Makefile
# 获取当前目录下所有的 .c 文件,并赋值给SRC SRC=$(wildcard ./*.c) # 将SRC变量中所有出现 .c 的地方替换为 .o OBJS=$(patsubst %c, %.o, $(SRC)) TARGET = test $(TARGET):$(OBJS) gcc -c $^ -o $@ # 模式规则 %.o:%.c gcc -c $< -o $@ # 声明 clean 为伪目标, # 伪目标不去判断目标文件是否存在或者已经更新, # 无条件执行命令 .PHONY:clean clean: rm -rf $(OBJS) $(TARGET)
Makefile 工作原理
若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来 生成该依赖文件。
检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新。
评论区