运维面试题之 Linux

命令或 Bash 相关

Bash 中两个数做运算的几种方式

1
2
3
4
sum=$[ ${v1} + ${v2} ]
(( sum=${v1} + ${v2} ))
let sum=${v1}+${v2} # 这里运算符两端必须没有空格
`expr ${v1} + ${v2}` # 这里运算符号两端必须要有空格

$?,$* 等相关参数都有什么意义

1
2
3
4
5
6
7
$$: shell 本身的 processID
$?: 最后运行命令的结束代码
$0: shell 脚本文件名
$*: 参数列表
$@: 参数列表
$#: 参数个数
${#str}: str 变量字符长度

read 命令从管道中读取字节流

1
2
echo "sinaops" | read a ; echo $a # 输出为空
echo "sinaops" | while read a ; do echo $a ; done # 输出 sinaops

find 使用

1
2
3
# 把 /data 目录及其子目录下所有以扩展名 .txt 结尾的文件中包含 magedu 的字符串全部替换为 magestudy
# {} 表示找到的文件
find /data -type f -name "*.txt" -exec sed -i s'@magedu@magestudy@' {} \;

grep,sed,awk 的运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 统计域名出现次数 "http://hi.baidu.com/browse/"
# 与统计 IP 访问次数基本一致
awk -F '/' '{num[$3]++}END{for (name in num){print name,num[name]}}' file

# 打印奇数/偶数行
# n 表示读取模式空间的下一行; N 表示追加下一行到模式空间
sed -n 'p;n' file # 奇数行
sed -n 'n;p' file # 偶数行
# 奇数行和偶数行合并
sed 'N;s/\n/ /g' file

# 奇数行与偶数行交换
sed -r 'N;s@(.*)\n(.*)@\2\n\1@g' file

# 文件中两列数据,分别为 ip,status_code.统计状态码为 200 中,出现次数最多的 IP
awk '/200$/{ip_num[$1]++}END{for(ip in ip_num){print ip,ip_num[ip]}}' ip_code | awk '/NR==1/{print $1,"出现次数最多,为",$2}' | sort -nrk 2 | awk '{if (FNR==1){print $1,"出现次数最多,为",$2}}'

history 添加时间戳,如何防止个人 history 操作泄露

设置 export HISTTIMEFORMAT='%F %T' 环境变量后,以后记录的命令历史操作就会添加时间

  • 可以将 history 记录的个人操作清空,使用 history -c 清空当前命令历史,并使用 history -w 将已经清空的命令历史写入到命令历史文件 ~/.bash_history 中(或直接清空该文件),下次登录边不再有命令历史
  • 可以设置 export HISTCONTROL=ignorespace 环境变量,使得命令历史忽略记录以空格开始的命令.在执行敏感命令时,以空格开始
  • 可以设置 export HISTIGNORE=* 环境变量,使命令历史忽略记录所有命令.使用 export HISTIGNORE= 恢复记录.

系统相关

进程和线程有什么区别

  • 进程(process)是系统进行资源分配和调度的基本单位,线程(Thread)是 CPU 调度和分派的基本单位
  • 线程依赖于进程而存在,一个进程至少有一个线程
  • 进程有自己的独立地址空间,线程共享所属进程的地址空间
  • 进程是拥有系统资源的一个独立单位,而线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),和其他线程共享本进程的相关资源如内存,I/O,cpu 等
  • 在进程切换时,涉及到整个当前进程 CPU 环境的保存环境的设置以及新被调度运行的CPU环境的设置,而线程切换只需保存和设置少量的寄存器的内容,并不涉及存储器管理方面的操作.进程切换的开销远大于线程切换的开销
  • 线程之间的通信更方便,同一进程下的线程共享全局变量等数据,而进程之间的通信需要以进程间通信(IPC)的方式进行
  • 多线程程序只要有一个线程崩溃,整个程序就崩溃了.但多进程程序中一个进程崩溃并不会对其它进程造成影响,因为进程有自己的独立地址空间

进程间通信方式

  • 管道
  • 消息队列
  • 信号和信号量
  • 共享内存
  • 套接字

并发,并行,异步的区别

  • 并发: 在一段时间内交替做不同事情的能力,可以理解为单线程(协程)/多线程运行在单核处理器上,如果有其中一个任务/线程阻塞,CPU 立即切换,执行行另一个任务/线程的代码逻辑
  • 并行: 在同一时刻做不同事情的能力,可以理解为多线程程序运行在多核处理器上,一个线程绑定一个 CPU,多个 CPU 同时处理代码逻辑

什么是 IO 多路复用?怎么实现

IO 多路复用是指单个进程/线程就可以同时处理多个IO请求.是一种可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作的机制.

select/poll/epoll三者的区别?

select

将文件描述符放入一个集合(缺点1: 集合大小有限制,32 位机器默认是1024,64 位默认是 2048)中,调用 select 时,将这个集合从用户空间拷贝到内核空间(缺点2: 每次都要将集合从用户空间复制到内核空间,开销大),由内核根据就绪状态修改该集合的内容.采用水平触发机制.select 函数返回后,需要通过遍历这个集合(缺点3: 轮询的方式效率较低),找到就绪的文件描述符.当文件描述符的数量增加时,效率率会线性下降

poll

和 select 几乎没有区别,区别在于文件描述符的存储方式不同.poll 采用链表的方式存储,没有最大存储数量的限制

epoll

  1. 通过内核和用户空间共享内存,避免了不断复制的问题.
  2. 支持的同时连接数上限很高
  3. 文件描述符就绪时,采用回调机制,避免了轮询
  4. 支持水平触发和边缘触发.采用边缘触发机制时,只有活跃的描述符才会触发回调函数

什么是用户态和内核态

为了限制不同程序的访问能力,防止一些程序访问其它程序的内存数据,CPU 划分了用户态和内核态两个权限等级

  • 用户态只能受限地访问内存,且不允许访问外围设备.没有占用 CPU 的能力,CPU 资源可以被其它程序获取
  • 内核态可以访问内存所有数据以及外围设备,也可以进行程序的切换

所有用户程序都运行在用户态,但有时需要进行一些内核态的操作,比如从硬盘或者键盘读数据,这时就需要进行系统调用,使 CPU 切换到内核态,执行相应的服务,再切换为用户态并返回系统调用的结果.

为什么要分用户态和内核态

  • 安全性: 防止用户程序恶意或者不小心破坏系统/内存/硬件资源;
  • 封装性: 用户程序不需要实现更加底层的代码
  • 利于调度: 如果多个用户程序都在等待键盘输入,这时就需要进行调度.统一交给操作系统调度更加方便

如何从用户态切换到内核态?

  • 系统调用: 比如读取命令行输入.本质上还是通过中断实现
  • 用户程序发生异常时:比如缺页异常
  • 外围设备的中断: 外围设备完成用户请求的操作之后,会向 CPU 发出中断信号,这时 CPU 会转去处理对应的中断处理程序

死锁

什么是死锁

在两个或者多个并发进程中,每个进程持有某种资源而又等待其它进程释放它们现在保持着的资源,在未改变这种状态之前,两个进程都不能向前执行,称这一组进程产生了死锁(deadlock).如

1
2
线程 A -> 锁定 a -> 尝试锁定 b -> 永久等待
线程 B -> 锁定 b -> 尝试锁定 a -> 永久等待

死锁产生的必要条件

  • 互斥: 一个资源一次只能被一个进程使用
  • 占有并等待: 一个进程至少占有一个资源,并在等待另一个被其它进程占用的资源
  • 非抢占: 已经分配给一个进程的资源不能被强制性抢占,只能由进程完成任务之后自愿释放
  • 循环等待: 若干进程之间形成一种头尾相接的环形等待资源关系,该环路中的每个进程都在等待下一个进程所占有的资源

进程的有效用户与实际用户

  • Linux系统中某个可执行文件属于 root 并且有 setid,当一个普通用户 mike运行这个程序时,产生的进程的有效用户和实际用户分别是 root 和 mike

fork() 创建进程

下面程序共有多少进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
* 在父进程中,fork 返回新创建子进程的进程 ID
* 在子进程中,fork 返回 0
* 如果出现错误,fork 返回一个负值
*/
#include <unistd.h>
#include <stdio.h>

int main()
{
fork() || fork();
sleep(100);
retrun 0;
}
/*
* 共创建 3 个
* main() -> fork() -> fork()
*/

int main()
{
fork() && fork();
sleep(100);
retrun 0;
}
/*
* 共创建 3 个
* main() -> fork()
* -> fork()
*/
int main()
{
fork() && fork() || fork();
sleep(100);
return 0;
}
/*
* 共创建 5 个
* main() -> fork() -> fork()
* -> fork() -> fork()
*/

int main()
{
fork(); // 新创建 1 个
fork() && fork() || fork(); // 新创建 (1+1)*2+(1+1)*2 = 8 个
fork(); // 新创建 1+8+1=10 个
sleep(100);
return 0;
}
// 共创建 20 个进程

ln -smv 对某文件操作时,对 inode 和 block 有什么影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 理解 blocks 为磁盘存储块, inode 为指向该存储块的地址
ln -s afile bfile
# 原始文件不变
# 新文件 inode 与原始文件不同,且 block 为 0
# 原因: 创建符号链接,系统为符号链接分配 inode,符号链接不存储数据,只是作为引用,故 block 为 0

mv afile bfile
# 原始文件不存在
# 新文件与原始文件 inode 与 block 相同
# 原因: 修改文件名后,系统只是将文件名做改变,inode 与存储 block 不变

ln afile bfile
# 原始文件不变
# 新文件 inode 和 block 与原始文件相同
# 原因: 创建硬链接,相当于为原始文件指定一个别名,inode 与存储 block 不变,这两个文件名指向系统底层存储是一样的

cp afile bfile
# 原始文件不变
# 新文件 block 与 原始文件相同,inode 不同
# 原因: 拷贝文件,相当于对系统底层存储做拷贝,系统为新文件重新分配 inode,文件内容一直,block不变

free 命令查看内存时,buffer 和 cache 各表示什么含义?如何清理 cache 缓存

这二者是为了提高IO性能的.

  • buffer是即将要被写入磁盘的,buffer 能够使分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能;
  • cache是被从磁盘中读出来的.cached 是把读取过的数据保存起来,重新读取时若命中就不要去读硬盘,若没有命中就读硬盘

清理缓存

1
2
3
4
sync
echo 1 > /proc/sys/vm/drop_caches # 释放页面缓存
echo 2 > /proc/sys/vm/drop_caches # 释放索引,inodes 节点缓存,可能会降低磁盘索引效率
echo 3 > /proc/sys/vm/drop_caches # 释放页面缓存,索引,inode 缓存

top 页面含义

1
2
3
4
5
6
7
当前时间及运行时长, 登录用户数, 平均负载
任务数: 总数,运行中,休眠中,停止,僵尸进程
CPU占比: 用户空间, 内核空间,, 空闲进程,等待进程
内存: 总物理内存,使用,剩余,buffer 缓冲
Swap: 总,使用,剩余,cache 缓存

进程ID 用户 优先级 nice值 虚拟内存 物理内存 共享内存 进程状态 CPU 内存 运行时长 命令

kill 信号

1
2
3
4
5
6
7
1 HUP 终端断线
2 INT 中断,同 Ctrl + C
3 QUIT 退出
9 KILL 强制终止
15 TERM 优雅的终止
18 CONT 继续(bg/fg命令)
19 STOP 暂停,同 Ctrl + Z

tcpdump 抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-i <DEV>: 抓取指定网络接口的数据包
-c <COUNT>: 抓取多少个数据包
-w <FILE>: 抓取数据包保存在文件中,而不是直接输出

expression: tcpdump 的表达式

type: 指定抓取类型.host,net,port,portrange,默认host
dir: 指定抓取传输方向.src,dst,src and dst,
proto: 指定抓取数据包协议.ip,tcp,udp

tcpdump [src | dst]host <HOST>: 指定主机
tcpdump host <HOST1> and <HOST2>: 抓取 HOST1 和 HOST2之间的数据包
tcpdump [tcp | udp] port <PORT>: 抓取执行端口数据包
tcpdump host <HOST> and port <PORT>: 抓取主机和端口数据包
tcpdump net <NET>: 抓取网络数据包

日志出现 kernel:nf_conntrack:tablefull,dropping packet 错误

此错误表示连接跟踪表已满,开始丢包,可能的原因是由于频繁的连接/关闭,或者网络的一些 TCP 的连接导致的

解决方法:

  • 加大跟踪表的大小 net.netfilter.nf_conntrack_max
  • 禁用一些不必跟踪的连接状态
  • 禁用 ip_vs,nf_conntrack 模块

sar 命令

统计,报告,保存系统运行状态信息

1
2
3
4
5
6
7
8
9
10
sar [m [n]]: 每隔 m 秒报告一次,共报告 n 次,默认是 CPU 状态信息
-b 报告 IO 和传输速率
-f [filename] 从历史文件中报告数据
-n [DEV,IP,TCP,UDP] 报告网络状态信息
-o [filename] 将统计结果保存到二进制文件中
-P [0..ALL] 查看 CPU 相关信息
-r 报告内存利用率相关信息
-S 报告 Swarp 利用率相关信息
-u 报告 CPU 利用率状态信息
-v 报告 inode 相关信息

文件描述符

文件描述符是一个非负的索引值,指向内核中的”文件记录表”.

  • 当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符
  • 当需要读写文件时,文件描述符可作为参数传递给相应的函数
  • Linux 下所有对设备和文件的操作都使用文件描述符来进行

常见文件描述符如下

  • 0: 表示标准输入,对应宏为: STDIN_FILENO,函数 scanf() 使用的是标准输入
  • 1: 表示标准输出,对应宏为: STDOUT_FILENO,函数 printf() 使用的是标准输出
  • 2: 表示标准出错处理,对应的宏为: STDERR_NO
Buy me a cup of coffee.