操作系统(Linux)笔记与总结

前言

你好,我是苏青羽,是一名默默无闻的计算机爱好者。

本笔记是我在学习Linux时所总结的学习笔记,主要记录了Linux的一些基础知识点,在这里分享给大家,希望通过该笔记能帮助更多的人去理解Linux。

笔记中参考了网上的很多博客、公众号、网课、面经等等,这些均在笔记末尾的参考资料中有所展示,大家可自行查看,做更深入的了解。

本笔记主要用于在学习上的复盘,并不适合初学者学习。如果在笔记中遇到了不懂的知识点,一定要去自己看书或者上网查询相关知识点!

如果发现本文有较大的硬性错误、或者是某些内容存在侵权、以及有什么想补充的问题和内容,请点击“关于”,通过里面预留的联系方式同我联系!

本笔记正在实时更新中,若是想获取笔记的最新PDF版以及了解关于我的更多信息,可扫描下方二维码,或微信搜索公众号“苏青羽”关注我!

前提基础

学习本笔记前,请事先掌握以下基础知识

  • 操作系统
  • 计算机网络
  • C语言基础(或C++基础)

Linux概述

1. Linux是什么?

Linux是一个开源的操作系统。与其他常见的操作系统(Windows、Mac OS、Android)类似,是最贴近硬件的一层软件,负责管理系统资源,并向上为应用软件与用户提供各种服务。

2. 为什么要学习Linux?他有什么特点?

(1) Linux在IT行业中应用广泛。Linux被广泛应用于服务器、超级计算机、移动设备等领域。想深入IT行业,Linux是必不可少的。

(2) Linux具有高度可定制性。作为开源操作系统,对Linux有深入了解后,可以通过修改内核或添加扩展程序,定制属于自己的Linux。

(3) Linux具有高安全性。与其他操作系统相比,Linux的软件漏洞和病毒威胁要少得多。

(4) Linux提供大量自由和免费的软件。大部分Linux发行版可免费使用,且大部分Linux上的软件都是免费提供的,可以减少软件使用成本。

(5) Linux有着强大的社区支持。Linux社区拥有众多用户和学习者,在学习的过程中可以吸收他们的使用经验,更快更深入地了解Linux。

(6) Linux支持多用户与多任务。多用户指各个用户对于自己的文件与设备有自己的特殊权限,用户之间互不影响。多任务指Linux支持多进程与多线程,能使多个程序并发运行。

3. 如何使用Linux?

Linux目前有众多发行版,如CentOs、RedHat、Ubuntu,但是其内核架构基本都大同小异。大部分Linux发行版提供了两种使用模式:命令行模式和图形界面模式,Linux用户通常都是在命令行模式下使用Linux,因此必须掌握Linux的各种常用命令,才能更好地使用Linux

4. Linux的软件安装方法有多少种?

(1) 源码安装:源码安装一种最常见的软件安装方式,一般由软件提供者提供软件源代码,用户下载后在本地Linux环境下手动进行软件的编译链接与安装。通过源码安装,用户可以获得最新版本的软件,还可以自己手动修改与定制软件功能。在所有Linux系统中都能使用,具有良好的可移植性。但是这种安装方式过于复杂,耗时又长,对用户的软件开发能力要求也比较高。

(2) RPM安装:RPM是一种Linux的软件安装包文件,类似于安卓下的.apk文件,用户只需运行该文件即可完成软件安装,不用经历繁杂的编译链接过程。RPM也有一些缺点,首先RPM软件包只能在具有RPM机制的Linux中使用,如RedHat。其次,RPM软件包之间存在复杂的依赖关系,通常一个软件都是由多个相互依赖的RPM软件包组成,安装一个软件需要使用到许多RPM软件包,如安装A之前要安装B,安装B之前又要安装C,导致安装步骤繁琐。

(3) YUM安装:YUM是一个基于RPM的软件管理工具,相当于手机里的“应用商店”。它解决了RPM软件包的依赖性问题,安装时会自动安装它所需的所有依赖,一步到位。

5. Windows和Linux的区别?

(1) Windows是微软开发的操作系统,闭源且收费。Windows注重于桌面应用,适合普通用户进行上网娱乐。

(2) Linux与Windows相反,开源且免费,注重系统性能,以命令行操作为主,适合软件开发部署。

6. Linux是用什么语言编写的?

Linux是用C语言编写的,相比起汇编语言,C语言有着更好的可移植性。Linux中的所有命令、函数库都默认以C语言编写,所以在介绍Linux提供的库函数时,都默认他们为C函数。

常用命令

1. 关机

1
init 0 或 halt

2. 重启

1
init 6 或 reboot

3. 清屏

1
clear

4. 查看IP地址

1
ip addr 或 ipconfig

5. 时间操作

(1) 查看时间

1
date

(2) 设置时间

1
date -s "yyyy-mm-dd hh:mi:ss"  //如date -s “2023-05-20 12:30:45”

6. 查看当前工作目录

1
pwd

7. 改变当前工作目录

1
2
3
cd 目录名
cd .. //进入上一级目录
cd //进入用户的主目录

8. 列出目录和文件信息

1
2
ls -lt 目录或文件名
ls //列出当前工作目录下全部的目录和文件名信息

(1) 选项-l列出目录和文件的详细信息。

(2) 选项-t按时间降序显示。

9. 创建目录

1
mkdir 目录名

10. 删除目录和文件

1
rm -rf 目录/文件/文件列表

(1) 选项-r可以删除目录,如果没有-r只能删除文件。

(2) 选项-f表示强制删除,不需要确认。

(3) 文件列表由多个文件组成,各个文件以空格分隔,可以一次性删除多个文件

11. 移动目录和文件

1
2
mv 旧目录或文件名 新目录或文件名
mv aaa bbb //等同于重命名操作,将当前目录的“aaa”文件改名为“bbb”

(1) 第二个参数若是一个已存在的目录,则会将文件/目录移至该目录中

(2) 第二个参数若是一个不存在的目录,则会创建一个同名的文件/目录,并将原文件/目录的内容移动到其中。

12. 复制目录和文件

1
cp [-r] 旧目录或文件名 新目录或文件名

(1) 选项-r可以复制目录,如果没有选项-r只能复制文件。

(2) 第二个参数若是一个已存在的目录,则会将文件复制进该目录中

(3) 第二个参数若是一个不存在的目录,则会创建一个同名的文件/目录,并将原文件/目录的内容复制到其中。

13. 修改用户的密码

1
passwd [用户名]

(1) 普通用户只能修改自己的密码,只输入passwd就可以了,不能指定用户名。

(2) 系统管理员可以修改任何用户的密码,passwd后需要指定用户名。

14. 压缩和解压文件

(1) 压缩:

1
tar zcvf 压缩包文件名 目录或文件名列表

(2) 解压:

1
tar zxvf 压缩包文件

(3) 压缩命令可以将任意目录和任意文件压缩成任意目录下的压缩包文件

(4) 解压命令可以使任意目录下的压缩包文件在当前工作目录下解压

15. 判断网络是否连通

1
ping ip 地址

16. 查看文件内容

(1) cat命令可以一次性显示整个文件的内容

1
cat 文件名

(2) more命令可以分页显示文件的内容,空格=下一页,b键=上一页,q键=退出

1
more 文件名

(3) tail命令用于显示文本文件的最后几行,若加入-f参数,则会实时刷新文件的文本内容

1
tail -f 文件名

17. 显示文本文件的行数、单词数、字节数

1
wc 文件名

显示结果第一列为行数,第二列为单词数,第三列为字节数(文件大小)

18. 搜索文件中的内容

1
grep "内容" 文件名

19. 在目录中查找文件

1
find 目录名 -name 文件名 -print

(1) 目录名:待搜索的目录,执行命令时还会搜索它的各级子目录。

(2) 文件名:待搜索的文件名,可使用正则表达式。

20. 增加/删除用户组

(1) 增加用户组

1
groupadd 组名

(2) 删除用户组

1
groupdel 组名

21. 增加/删除用户

(1) 增加用户

1
useradd -n 用户名 -g 组名 -d 用户的主目录

(2) 删除用户

1
userdel 用户名

22. 切换用户

1
su - 用户名

从root用户切换到其它普通用户不需要输入密码,从普通用户切换到任何用户都需要输入密码

23. 修改目录/文件的用户和用户组

1
chown [-R] 用户名:组名 目录或文件名列

-R 选项表示处理各级子目录

24. 修改文件权限

1
chmod [-R] 权限 文件名

(1) -R 选项表示处理各级子目录

(2) 只有文件的拥有者和root用户才可以改变文件的权限

25. 查看系统磁盘空间

1
dh -hT

(1) 选项-h 以方便阅读的方式显示信息。

(2) 选项-T 列出文件系统类型。

26. 查看进程

1
ps -ef

(1) -e:表示列出全部的进程,等同于“-A”

(2) -f:显示全部的列(显示全字段),包括:UID(进程所属用户)、PID(进程ID)、PPID(父进程ID)、C(进程占用CPU资源百分比)、STIME(进程启动时的时间)、TTY(进程所属终端)、TIME(进程运行时间)、CMD(启动进程所执行的指令)

27. 查看某个程序的进程

1
ps -ef | grep 程序名

该命令首先查看系统全部的进程,然后从结果集中筛选出包含关键字的进程

28. 查看线程

1
ps -xH

29. 查看某个程序的线程

1
ps -xH | grep 程序名

30. 动态查看进程的变化

1
top

31. 终止进程

1
kill [信号] PID

(1) kill命令实际上是用来向进程发送一个用户指定的信号,只是被常用来终止进程。

(2) 终止进程的信号有两个,一个是-2(SIGINT),相当于 “Ctrl+C” 的终止命令。还有一个是-9(SIGKILL),用来强制杀死进程。

(3) 如果不加信号,kill会默认发送-15(SIGTERM)。他会通知进程要安全且干净地退出,但该信号往往会被进程忽略与阻塞,不具有强制性,所以用处不大。

32. 终止一类进程

1
killall [信号] 进程名

(1) 与kill命令类似,用于向进程发送信号,常用于终止进程。

(2) 他的参数是进程名,他可以一次性向与进程名相关的一类进程发送信号,更加方便。

33. 正则表达式

(1) 星号“*”:匹配任意数量的字符。

(2) 问号“?”:匹配一个字符。

(3) 正则表达式可配合Linux命令使用,如:

1
ls -lt /tmp/*.log    //列出“/tmp”目录下匹配*.log的目录和文件,按时间降序显示

文件系统

1. Linux对文件访问者的分类?

(1) 文件和文件目录的所有者:u—User

(2) 文件和文件目录的所有者所在的组的用户:g—Group

(3) 其它用户:o—Others

2. Linux为什么要有组的概念?

因为我们在开发的过程中可能会被分成一个个小组,有时候想要把自己的代码让同组的人看到,同时不想让其他组的人看到,就需要使用组来对权限进行控制。

3. Linux的文件分为多少种?

(1) d:目录文件

(2) -:普通文件(文本,各种静态库,可执行程序,源程序)

(3) l:软链接(类似Windows的快捷方式)

(4) b:块设备文件(例如硬盘、光驱等)

(5) p:管道文件(进程通信)

(6) c:字符设备文件(例如键盘与显示器)

(7) s:套接口文件(socket)

4. Linux的文件权限如何表示?

(1) 当我们使用ls -lt命令时,会看到文件的属性。其中文件的权限会以三个符号“rwx”为一组,共三组的形式进行表示,如下图所示。

(2) 因为权限位置是确定的,而且是两态的,所以我们可以使用二进制来表示文件的权限,有权限为1,无权限为0。又因为每三位权限为一组,共三组,故又可使用三个八进制位表示一个文件的权限,如下图所示。在修改权限时可以使用这种方法表示一个文件的权限

5. Linux的文件权限的含义?

6. 为什么使用ls命令查看目录只显示文件名,其他信息全是问号?

(1) 这是因为该目录只对你开放了读取(r)权限,这时候你可以使用ls命令查看该目录下的文件名,但各文件的具体属性无法查看,需要配合执行(x)权限才可以。一般如果想开放目录给其他人查看需要开放r+x权限

(2) 另外提一嘴,若某个目录对你只开放执行(x)权限,你虽然可以进入目录,但无法查看其中的文件名。可是一旦你知道其中某个文件名,即使文件名不可见,你依然可以对该文件或者其文件内容进行操作(前提是有开放其他相应的权限),所以x权限是对目录下的文件进行读取和修改的必要权限

7. 如何更改Linux的文件权限和所属用户以及用户组?

(1) 更改权限:chmod

(2) 更改用户组:chgrp

(3) 更改用户:chown

8. 相对路径的用途?

(1) 对于软件编写者而言,为了后来该软件可以方便地给不同的人和不同的机器使用,需要使用相对路径来设置软件的库路径、中间文件路径,这样可以提高软件的可移植性。

(2) 一些文件存放在绝对路径特别长的目录之中,使用相对路径进行目录切换比较方便

9. 说说Linux中的环境变量PATH?

(1) PATH是Linux中非常重要的环境变量,当我们执行一个命令时,系统会按照PATH定义的目录下去查找同名的可执行文件,并执行它。

(2) PATH是可以被修改的,由多个目录组成,各个目录之间以冒号隔开,使用echo命令可以查看PATH。

(3) 如果可执行文件的路径不存放在PATH中,那么想执行它只能使用绝对路径或者相对路径来指定它。

(4) 不同身份用户默认的PATH不同,默认能执行的命令也不同。同时为了确保安全,不建议将当前工作目录(.)加入PATH中,因为一旦移动当前工作目录,命令会产生变动,这并非好事。

10. 说说Linux中的特殊文件权限(s与t)?

(1) Linux中的文件除了rwx三个常见权限外,还有三种特殊权限:SUID、SGID、SBIT

(2) SUID(Set UID):当s标志出现在文件拥有者owner的x权限上时,如“-rwsr-xr-x”,此时称为Set UID,简称为SUID特殊权限。它的限制和功能为

① SUID权限仅对二进制程序有效,对shell脚本和目录无效

② 执行者对于该程序需要具有x的可执行权限

③ 本权限仅在执行该程序的过程中有效

④ 执行者将具有该程序拥有者(owner)的权限

(3) SGID(set GID):当s标志出现在文件所属群组group的x权限上时,如“-rwxrws—”,则称为SGID,功能和限制为

① SGID对二进制程序有用,也可以用在目录上

② 执行者对该程序需具备x权限

③ 执行者在执行过程中获得该文件所属群组一样的权限

④ 若是对目录设置SGID,当某个用户具备在该目录下的“w”权限,那么该用户可以在该目录下创建文件,且该文件的用户组不再跟随创建者,而是与目录的用户组相同。

(4) SBIT(Sticky Bit):当t标志出现在文件others的x权限上时,如“-rwx—rwt”,则称为SBIT,功能和限制为

① 仅对目录有效

② 用于当使用者对此目录具有w和x权限,即具有写入权限时

③ 当使用者在该目录下创建文件或目录时,仅有自己与root有权利删除/移动/重命名该文件

(5) 这三个权限都与可执行权限(x)相关,若原本的文件或目录上没有可执行权限,那么加上这三个权限后就会显示大写的“S”和“T”,表示权限无效,如“-rwSrwSrwT”

(6) 若要修改特殊权限,仍然可以使用chmod命令。若使用符号表示法,则特殊权限可用“s”和“t”表示。若使用数字表示法,则在原先3个八进制权限位的前面再加上一个八进制位表示特殊权限,其中SUID为4,SGID为2,SBIT为1,如使用“chmod 4777 file”,那么文件的权限位“-rwsrwxrwx”

11. 文件的默认权限与umask的关系?

(1) 在创建文件和目录时,Linux会自动设置权限,即默认权限

(2) 对于文件而言,默认权限是rw-rw-rw,即666,默认没有可执行权限(x)。

(3) 对于目录而言,默认权限是rwxrwxrwx,即777,默认所有权限均开放。

(4) umask是一种权限掩码,用于去除默认权限中的若干个比特位。凡是在权限掩码umask中出现的,都应在在默认权限中去掉。所有文件和目录在创建时都受到了默认权限和umask的限制

(5) umask由四个八进制位组成,对应的是一个文件或目录的所有权限位(第一位是特殊权限位,后面三位是常规权限位)。普通用户的umask是0002,root的umask是0022。假设普通用户创建一个普通文件,则默认权限666与umask(0002)进行按位与操作,变成了664。

(6) umask即是一个值也是一个命令,作为命令它可以手动修改自身的值,但仅在本次登录内有效,想要长期生效只能修改配置文件

12. 简单说说Linux的ext系列文件系统?

linux的ext文件系统是一种索引式文件系统,它将一个磁盘分区中的内容分为多个区块群组(block group),每个block group内部使用统一的方式进行管理,以下为每个block group中的内容。

(1) 超级块(Super Block):记录文件系统的整体信息,包括inode和数据区块的总量、使用量、剩余量,以及文件系统的格式与相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,所以每个block group中都有一个,以作备份和恢复

(2) GDT(Group Descriptor Table):块组描述符,描述每个block group的具体信息,包括每个区段(超级块、块位图、数据区块等)的位置

(3) 块位图(Block Bitmap):记录Data Block中哪个数据块已经被占用,哪个数据块没有被占用

(4) inode位图(inode Bitmap):与Block Bitmap类似,用来记录inode是否空闲可用。

(5) inode表(inode Table):保存了所有的inode,每个inode都记录了某个文件的属性,包括权限、拥有者、创建时间等,等同于Linux下的FCB。每个inode都有编号且存放了指向文件内容的数据指针,找到了inode就等于找到了文件

(6) 数据区块(Data Blocks)用以存放文件内容,每个区块都有编号且大小为4KB,若文件太大,会占用多个区块。每个区块最多只能存放一个文件的内容,若文件小于区块,则会导致一定的空间浪费。

13. 说说Linux的文件物理结构?

(1) 常见的文件物理结构有连续分配、链接分配和索引分配(详见操作系统笔记),而Linux下的ext文件系统采取的是一种混合索引分配的文件物理结构

(2) 实现这种结构的关键在于inode,它是Linux下的FCB,不仅包含了文件属性,还必须承担组织和寻找对应文件内容的职责

(3) 每一个inode中保存了一个数组,数组每个元素都存放着data block的区块编号。对于小型文件,采取直接寻址方式,直接从inode中获取其文件内容的区块编号。对于中型文件,采取一级索引方式,专门拿一个区块来记录额外文件内容的区块编号。对于中型或特大型文件,即可采用二级索引或三级索引来保存更多的区块编号,如下图左边即为inode的内部组成,右边则是三级索引方式的寻址结构

(4) 这种方式能够比较全面地照顾小型、中型、大型和特大型的文件,同时又使一个inode尽可能地小

14. Linux的目录中存放的是什么?

(1) 目录也是一种文件,所以也有inode,inode中存放了目录的属性和权限。

(2) 目录文件是一种有结构文件,文件数据中存放着一个个文件目录项。

(3) 在linux下,每个文件目录项由文件名和inode编号组成,目录就是文件名到inode号的映射列表。比起传统的FCB组织方式,该方式使得目录文件的大小得到进一步压缩,提升了空间利用率,磁盘IO次数也相应减少。

(4) 在检索文件时,通过目录inode->目录数据->文件名->文件inode->文件数据的顺序来存取文件的数据。

15. 挂载是什么意思?

(1) 所谓的“挂载”就是利用一个目录当作进入点,将磁盘分区的数据放置在该目录下,也就是说进入该目录就可以读取该分区的意思

(2) 挂载点一定是目录,必须进入该目录才可以访问磁盘分区,并使用该分区下的文件系统。

16. 硬链接和软链接是什么?

(1) 软链接和硬链接是Linux下的两种特殊文件,它可以省去访问文件时所需要输入的一系列文件路径,方便用户访问文件。

(2) 硬链接:使用ln命令可创建硬链接,语法为“ln 文件名 连接名”。硬链接不是一个具体的文件,而是一个映射关系,它的inode编号和原文件是相同的,因此我们可以通过硬链接访问原文件,硬链接实际上是为文件起别名。为了支持硬链接,每个inode结点都会维护一个引用计数器,以统计连接到该结点的硬链接数。删除一个硬链接时,只会将该计数器减一,当这个数字变为0时,该文件才会被真正删除。

(3) 软链接:在ln命令的基础上加上-s参数即可创建软链接。软链接是独立文件,有自己的inode和数据区块,它类似于Windows 的快捷方式,里面存放着源文件的存放路径,访问文件时会通过该路径进行查找。由于只是利用文件路径来做指向,所以一旦原文件被删除,软链接仍然存在但会变成不可用状态。

17. 硬链接有什么限制?

(1) 硬链接不能跨文件系统。硬链接是一种建立在inode号上的机制,必须确保文件系统具有inode且其指向的文件是唯一确定的。而文件系统之间是相对独立的,那么inode编号在不同的文件系统下也有可能出现重复,因此仅能在单一文件系统中进行。

(2) 硬链接不能连接目录,一旦连接到目录,不仅是为目录创建硬链接,还必须为目录中的每个文件创建硬链接,否则不满足对硬链接的定义。不仅如此,一旦原目录建立了一个新文件,与该目录连接的所有目录都要为这个文件建立一个硬链接,维护成本极高。

18. 怎么判断两个不同目录下的文件是否为同一文件?

在linux下,文件名可能相同,但inode号是唯一的,通过对比两个文件的inode号即可判断是否为同一文件。(前提是要处在同一文件系统中,否则inode号可能出现重复)

19. 为什么目录的默认硬链接数是2?

(1) 虽然我们不能手动为目录创建硬链接,但在创建目录时,系统本身就会对目录创建硬链接,并且这个硬链接数为2,而创建一个普通文件默认硬链接数则为1

(2) 为什么目录会是2?因为在创建目录时,一个空目录至少会存在“.”和“..”这两个目录,这两个目录其实就是硬链接,一个连接到当前目录,一个连接到上级目录。因此建立一个新目录时,新目录连接数为2,而上层目录的连接数会增加1

20. 什么是虚拟文件系统(VFS)?

(1) 我们知道文件系统的种类有很多,除了ext系列文件系统外,还有很多种文件系统 。

(2) linux通过名为虚拟文件系统(VFS)的中间层对这些文件系统提供了完美的支持,它相当于是对各种文件系统的一个抽象,为各种文件系统提供了一个通用的接口,类似于C++中虚基类的作用,而每一种物理文件系统则需要将自身的功能转换为VFS的通用接口。在大部分情况下,用户无需关心文件系统的底层实现,就可以操作各种文件系统

(3) 以VFS为基础,Linux实现了“一切皆文件”的设计思想。普通文件、磁盘、外设等都在Linux中以文件形式存在,并通过VFS向用户提供了IO接口,用户无需关心这个文件是什么,只需调用接口即可。只不过像键盘这样的外设,提供的写方法可能是为空。

(4) VFS并不是一种实际的文件系统,它只存在于内存中。VFS在系统启动时建立,在系统关闭时消亡。

21. 内存中的inode和磁盘中的inode有什么区别?

(1) 通常我们把内存中的inode称为VFS inode,它是由VFS所构建和管理的数据结构

(2) VFS inode对文件是唯一的,只有当文件被访问时,才在内存中创建VFS indoe,与VFS一样仅存在于内存中

(3) VFS inode包含了处理文件所需要的全部信息,如权限、属性、文件位置,它是VFS管理文件系统的最基本单位。VFS inode相当于是inode的抽象与映射,而后者是前者的静态信息,一旦VFS inode中的信息被修改,VFS就会将这些信息写入并更新inode。

(4) inode与文件系统具有相关性,依赖于特定的实现。而VFS inode则具有通用性,用户可借助VFS inode间接地对不同文件系统的inode进行操作。

(5) VFS对VFS inode采用链表形式进行组织,提高了访问效率。

22. Linux下的文件描述符fd与VFS inode的关系?

(1) 在Linux中,文件描述符(File descriptor,fd)是表示文件指向的抽象化概念,在形式上是一个非负整数,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符

(2) fd本质是操作系统内核中一个指针数组的下标,这个数组被称为文件描述符表,其中的每个元素都指向了一个与进程关联的文件,如下图所示。

(3) 为什么文件描述表是进程级的,每个进程都要维护一个?因为在多进程环境下,各个进程空间是相对独立的,进程之间不能随意访问。如果多个进程要访问同一文件,每个进程都需要维护一个文件描述表,再通过文件指针指向文件进而进行操作。

(4) 打开文件表的存在意义是什么?为什么文件指针不直接指向VFS inode?VFS inode仅用来保存文件的必要信息,而文件偏移量、文件的读写函数指针等信息都保存到file结构体中,也就是打开文件表的表项,通过file结构体才能对文件进行操作。

(5) 为什么打开文件表中不同的表项会指向同一个VFS inode?前文提过,VFS inode具有唯一性,但系统中的文件由于硬链接的存在,不一定唯一,不同的文件名也有可能指向同一个inode。

23. 创建进程时,Linux会默认为进程打开几个文件?

操作系统默认会为进程打开这三个文件:

(1) STDIN,文件描述符:0;标准输入,默认从键盘读取信息;

(2) STDOUT,文件描述符:1;标准输出,默认将输出结果输出至终端;

(3) STDERR,文件描述符:2;标准错误,默认将输出结果输出至终端

24. 文件描述符的分配规则?

(1) 当一个进程打开一个新文件时,进程会在文件描述符表中,寻找所有当前没有被使用的数组元素,并选择最小的一个下标与该文件进行绑定,形成新的文件描述符。

(2) 由于每个进程默认会打开三个文件(标准输入、标准输出、标准错误),因此新文件的文件描述符默认从3开始

25. 什么是输入/输出重定向?

(1) 当我们手动关闭了标准输入/输出时,其文件描述符资源就会被回收并空缺出来。若是再打开一个新文件,由分配规则可知,该文件会成为该进程新的标准输入/输出,这就叫输入/输出重定向

(2) 经过重定向之后,原本要输出到屏幕的数据就会转而输出到另一文件,而原本通过键盘要输入到程序的数据也无法输入,转而由另一个文件输入。

(3) 输入/输出重定向的在linux中应用的很广泛,如linux的“|”管道符,可以修改某个命令的输出指向。

26. Linux下的磁盘与设备文件名的关系?

(1) Linux中的所有磁盘(包括U盘)信息会以文件形式存放在/dev下,文件名格式为sd[a-p]。Linux会以检测到磁盘的顺序来命名,第一个是sda,第二个是sdb,以此类推。

(2) 若有对磁盘进行分区,则会在文件名后面添加数字来标识分区,如sda1,sda2、sdb1,Linux以这些文件来标识一个磁盘分区

进程与线程

1. 进程是什么?

(1) 在Linux中,进程的概念仍没有发生变化,进程是由程序段+数据段+PCB所组成的

(2) Linux的PCB是一种名为task_struct的结构体,它是进程属性的集合。

(3) task_struct中存在着几个重要的数据段:PID(进程标识符)、进程状态、进程优先级、进程内存指针(指向进程的程序段和数据段)、进程上下文数据、文件描述符表

2. 进程的地址空间是什么?

(1) 一个进程的地址空间分为这五大区:代码段、数据段、内核区、堆、栈

(2) 在进程中,为了有效地管理和访问这几个分区,linux为每个进程定义了一个mm_struct结构体。mm_struct位于PCB(task_struct)中,里面存储了各个分区的起始和结束位置,以及总大小等数据,进程可以通过该结构体快速访问特定分区的数据

(3) 每个进程都认为mm_struct代表整个内存,mm_struct的地址范围从全0到全1覆盖了整个内存地址空间。这是因为mm_struct存放的是虚拟地址,在访问数据时,由操作系统查找页表映射,转换为物理地址之后再进行访存。

3. 进程信号是什么?

(1) linux下的信号是一种软件中断,它提供了一种处理异步事件的方法,可以很好地在多个进程之间进行同步,是进程间的一种通信方式

(2) 信号只用于通知某个进程发生了什么事情,但并不给该进程传递任何数据。

(3) linux支持几十种不同的信号,这些信号都以SIG开头,常见的信号有SIGINT、SIGKILL

4. 进程信号的产生方式?

产生进程信号有多种方式,不管哪种,产生出来的信号最终一定都是通过操作系统向目标进程发送。以下是常见的信号产生方式

(1) 键盘产生。比如“Ctrl+C”发出2号信号(SIGINT),但它只能对前台进程发信号。

(2) 进程执行异常也能产生信号。如内存越界、堆栈溢出、除0错误等等。

(3) 通过系统调用产生信号。如kill命令。

(4) 库函数也能产生信号。一些库函数也能触发系统的某些条件,导致信号产生。

5. signal函数的作用是什么?

(1) signal函数用于修改进程对特定信号的后续处理行为

(2) 当进程接受信号后,可执行的后续处理行为包括默认处理、忽略信号和自定义动作,而这些都可以使用signal函数修改。

(3) signal函数并不会直接调用信号对应的处理动作,它的作用相当于是提前为信号注册回调函数。当进程接收到信号后,会由进程调用这些回调函数进行处理。

6. 如何创建子进程(说说fork函数)?

(1) 在linux中,fork函数是非常重要的函数,它可以从已存在的进程中创建一个新进程。新进程为子进程,而原进程为父进程。fork函数主要会完成以下功能:

① 分配新的内存块和PCB(task_struct)给子进程

② 将父进程的数据拷贝至子进程(写时拷贝)

③ 添加子进程到系统进程列表当中,由操作系统进行进程调度

(2) 进程具有独立性,父子进程互不干扰,无论哪一方做出什么操作,也不会影响另一方。

(3) 父子进程具有其他进程不具备的相关性。 子进程的创建是以父进程为模板的,子进程的初始数据、地址空间、文件描述表、页表等都继承自父进程,与父进程相同。

7. 如何理解fork函数的写时拷贝?

(1) 当我们在父进程中使用fork创建子进程时,默认情况下,子进程和父进程共享同一份程序段(只读的程序代码),数据段(程序数据)也是共享的

(2) 当任意一方试图修改数据段中的数据,比如子进程在fork之后,对共享的数据做出父进程没有的写入操作时,就会触发写时拷贝。将数据段中的内容拷贝到另一位置,然后再将更新的数据进行写入,以此保证进程数据的独立性(互不干扰)

(3) 写时拷贝关键在于写时,也就是说只读数据是不会发生拷贝的。即使是可写数据,如果没有进行写入,也不会发生拷贝,父子进程会一直共享同份数据,以提高空间利用率。

(4) 写时拷贝的底层实现在于页表。由于父子进程的页表是相同的,对于只读数据,父子进程的页表项会映射到同一个物理地址,这就实现了进程间的数据共享。如果子进程发生写时拷贝,会先触发缺页中断,拷贝数据到新的物理地址后,再修改子进程的页表映射,指向新的物理地址,然后中断结束,由子进程进行数据修改,其过程如下图所示

8. fork的返回值为什么会有2个?

(1) 通常我们使用fork创建子进程,有两个常规用法,一是让父子进程同时执行不同的代码,二是让其中一个进程执行不同的程序,如调用exec函数

(2) 不管是哪种用法,我们总希望能够区分父子进程,因此fork的返回值会有2个,父子进程各不相同。如果成功创建子进程,在父进程中fork就会返回子进程的PID,在子进程中就会返回0。如果创建子进程失败,fork会返回-1。利用返回值的不同,可以让父子进程执行不同的内容。

(3) 同个函数返回不同的值如何做到?其实很简单,fork函数在return之前,其功能是执行完了的,也就是说此时子进程已经被创建出来。之后父子进程在fork中都会向下执行,而在fork的return处,父子进程的代码是不一样的,就可以返回不同的值。

9. 进程有哪些状态?

在linux系统中,进程的状态被细分为6种:R,S,D,T,X,Z。他们实际上是由进程的基本状态(运行态、就绪态、阻塞态)演变而来的,下面介绍这6种状态。

(1) R运行状态(running):该状态表明进程要么是在运行中,要么是在运行队列里等待获取CPU。

(2) S睡眠状态(sleeping):该状态表示进程在等待事件完成。它是一种可中断睡眠状态,可以随时被终止,接收中断信号。在调用sleep函数或等待用户输入时,会进入该状态。

(3) D磁盘休眠状态(disk sleep):该状态表示进程在等待IO的结束。它是一种不可中断睡眠状态,不允许被中断,同时也不允许被kill掉,要想终止进程只能重启系统。在等待磁盘io或网络io时,会进入该状态。处于这种状态下的进程一旦被中断,磁盘写入数据完成后,无法向原来的进程返回信息,会导致错误,因此不可被中断。

(4) T停止状态(stopped):该状态表示进程被暂停。与S/D状态不同的是,S/D状态的进程有可能会在休眠和等待过程中对进程内的数据进行更新,而T状态下的进程是一种彻底暂停的状态,不会对进程内的数据进行更新。要想进入此状态,可以通过发送SIGSTOP信号来暂停进程,以及通过 发送SIGCONT 信号来唤醒进程。

(5) X死亡状态(dead):该状态表示进程彻底结束,可以回收进程资源了。变成X状态后,这个进程几乎瞬间就会被清理掉,所以很难捕捉到处于这个状态下的进程。

(6) Z僵尸状态(zombie)该状态表示一个进程即将死亡。当一个进程退出的时候,会先进入Z状态,将进程的退出信息保存在task_struct里面,等待父进程或者操作系统读取该信息,之后才会进入X状态。如果退出信息没有被读取,则该进程会一直以Z状态保持在进程表中,此时该进程被称为僵尸进程

10. 进程状态中的“+”号是什么意思?

如果查看进程状态时看到有个加号,如“R+”。它表示该进程是前台进程,由命令行终端运行并一直占据终端的输入和输出。如果我们以后台方式运行程序,即在运行时加上“&”符号,那么该进程的状态不会显示“+”号,表示它是一个后台进程

11. init进程是什么?

init是Linux启动后的第一个进程,往后所有的用户进程都是init的子进程。

12. 如何终止进程?

(1) 在mian函数中使用return返回。

(2) 调用_exit函数,它属于系统调用,会强制终止进程。

(3) 调用exit函数,它属于C语言函数库,在底层会调用_exit,并且在调用前还会做一些刷新缓冲区和关闭文件流等收尾工作

(4) 发送进程终止信号,如SIGINT和SIGKILL,可使用kill命令手动发送。

13. 守护进程、僵尸进程和孤儿进程的区别是什么?

(1) 守护进程:指在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,周期性地执行某种任务。守护进程常用于Linux服务器,如web服务器的http进程。

(2) 孤儿进程:如果父进程先退出,子进程还没退出,那么子进程的父进程将变为init进程,这些子进程被称为孤儿进程。(注:任何一个进程都必须有父进程)

(3) 僵尸进程:如果子进程先退出,父进程还没退出,而父进程又没有捕获子进程的退出信息,此时子进程就成为僵尸进程。

14. 为什么要设计出僵尸进程?僵尸进程会造成什么问题?如何避免??

(1) 僵尸进程可以使退出的子进程存活于系统中,以便父进程随时获取子进程的退出信息,然后判断子进程的执行结果是否正确

(2) 僵尸进程如果一直不处理,会造成内存泄露,占用系统资源。

(3) 避免僵尸进程方法有两种

① 在父进程中调用signal函数,忽视SIGCHLD信号,通知内核父进程对子进程的结束不关心,子进程的退出信息由内核回收。

② 在父进程中调用wait函数,等待子进程结束并回收子进程退出信息。wait可能会导致父进程阻塞。

15. wait和waitpid函数的异同?

(1) wait和waitpid函数都用于父进程中,调用他们可以获取子进程的退出信息

(2) 如果子进程已经退出,wait/waitpid会立即返回子进程的pid,并且释放资源,获得子进程退出信息。

(3) 如果不存在子进程,则立即出错返回-1

(4) 如果子进程存在且仍在正常运行,wait会阻塞父进程,waitpid可以设置不阻塞并立即返回0

(5) wait用于等待任意一个子进程,waitpid可以指定要等待的子进程的pid,若设置为-1,则与wait等效。

16. 进程的退出信息包含什么?

进程退出一般有两种情况,并且会产生不同的进程退出信息

(1) 进程顺利执行完成,正常退出,但产生的结果有可能正确或错误。这个退出结果就是从mian函数中return的值,return 0表示结果正确。父进程可查看退出信息以获取该值。

(2) 进程执行出现错误,收到了某种异常信号,然后进程执行信号的默认处理(终止进程)。这个异常信号会被包含在退出信息中,父进程可以根据该信号来判断进程终止的原因。

17. 什么是进程替换?

(1) 进程替换就是指进程不变(进程PCB和进程地址空间不变),用新程序的代码和数据替换当前进程的代码和数据,这样就好像进程执行了一个全新的程序。

(2) 进程替换并不创建新进程,进程替换的前后其进程PID是不变的。

(3) 进程替换常用于子进程中,可以让子进程执行与父进程不同的程序。发生进程替换之前,会先发生写时拷贝,拷贝父进程的代码和数据给子进程,再进行实际的替换操作。因此子进程进行进程替换时,不会影响父进程。

18. 如何进行进程替换(说说exec函数)?

(1) 在linux中,可以使用exec函数进行进程替换。exec函数包含了六种以exec开头的函数,他们都具备了相同的功能。不同点只在于其参数形式,以及在新程序运行时,是否要自己维护环境变量(以自定义的环境变量替换系统的环境变量)。

(2) exec函数如果调用成功则会加载新程序,无返回值(因为原进程的代码和数据都被替换了,exec自然也就不会继续执行并返回了)

(3) exec函数如果调用失败则会返回-1。它仅有出错的返回值而没有成功的返回值。

19. 无名管道是什么?

(1) 管道是进程间的一种基于文件的通信方式。在linux下,管道分为无名管道和有名管道

(2) 父子进程具有相关性,他们的文件描述表在初始时是相同的,其指向的文件也是相同的。因此父进程可以预先创建并打开某个文件,再进行子进程的创建。父子进程可以通过该文件进行通信,一个进程写文件,一个进程读文件。该文件被称为无名管道

(3) 无名管道必须使用pipe函数才能创建,与普通文件相比有以下几个特点:

① 无名管道的生命周期是随进程的。无名管道只存在内存中,它将数据存放在内存中的文件缓冲区,进程结束后会被系统回收。

② 无名管道仅限于父子进程通信。因为进程具有独立性,只能通过子进程继承父进程的方式,才能确保两个不同进程能访问同一个只存在于内存中的无名文件。

③ 无名管道只能单工通信。要么父写子读,要么子写父读。要想实现双工通信,必须使用两个无名管道。

④ 无名管道是面向字节流的。读写数据的时候只有字节的概念,没有数据结构的概念。

⑤ 无名管道自带同步机制。数据被读空->读进程阻塞,缓冲区写满->写进程阻塞。

20. 有名管道是什么?

为了解决无名管道只能父子进程通信的局限性,引入了有名管道。有名管道也是进程间的一种基于文件的通信方式,有以下特点

(1) 有名管道有文件实体。它存放在磁盘中,当我们查看文件时,文件类型显示为“p”的文件即为有名管道。

(2) 有名管道具备路径和文件名。因此可以被任意进程访问,实现任意进程间的通信。

(3) 有名管道在磁盘中不保存任何数据。有名管道被打开时,无论往里面读写多少数据,都不会把数据刷新到磁盘里,而是保存在内存中,提高了进程的读写效率。

(4) 除以上几点,有名管道与无名管道基本一致。基于文件、只能单工通信,面向字节流。

21. Linux下的线程结构是什么样的?

(1) 传统的操作系统线程包含了线程控制块(TCB)、少量寄存器和线程栈,是CPU调度的基本单位。

(2) Linux中并没有专门为线程设计TCB,而是用进程的PCB来模拟线程。

(3) 一个进程可以有多个PCB,表示多个线程。CPU看到的PCB就是一个调度的基本单位。

(4) 这样的设计使Linux不用维护复杂的进程线程关系,不用单独为线程设计任何算法,Linux只需要关注PCB的调度和分配即可。

22. 一个进程能创建多少个线程,与什么有关?

一个进程理论上可以创建的线程数,由进程可用地址空间和线程栈的大小共同决定。只要地址空间足够,那么新线程的建立就会成功。如一个进程可用地址空间是2G,线程栈的大小是1MB,则理论上最多只能创建2048个线程。

23. 怎么进行线程控制?

在Linux中,关于线程的函数都包含在一个名为“pthread”的动态C语言函数库中,其中绝大多数函数都以“pthread_”为开头。以下是常用的线程控制函数

(1) pthread_create:创建新线程。该函数会绑定线程执行函数,创建完线程后由该线程执行该函数。

(2) pthread_self:返回调用本函数的线程ID

(3) pthread_join:等待线程。类似于进程,线程结束时空间并没有被释放,需要被其他线程获取退出信息,该行为被称为等待线程。该函数会阻塞调用它的线程,直到等待成功

(4) pthread_exit:退出线程。用于终止调用该函数的线程,在函数内使用return也可以退出线程。但在主函数中调用return不仅会终止线程,还会终止整个进程

(5) pthread_cancel:取消线程。用于终止任意一个执行中的线程,线程ID为传入参数。

(6) pthread_detach:分离线程。如果不关心线程的退出信息,它可以使线程退出时,由系统自动回收线程资源,不必再等待线程(也无法被等待)

24. 什么是主线程和子线程?

(1) 每个进程中都至少存在一个线程,即“主线程”。它是进程中产生的第一个线程,其线程执行函数为main。

(2) 在进程中,除了主线程之外,我们利用任何方法创建出来的线程都被称为子线程。子线程一般是用来配合主线程来完成某些任务,或作为独立线程提供后台服务。

25. 主线程和子线程之间的关系?他们的退出有先后之分吗?

(1) 主线程和子线程之间没有必然的联系,他们执行的任务可以相关也可以无关。

(2) 主线程和子线程之间没有必然的退出次序。主线程退出,子线程可以继续执行;子线程退出,主线程也可以继续执行。

(3) 虽然主线程和子线程之间没有必然的退出次序关系,但是如果进程终止,那么进程下所有的线程都会终止。

(4) 综上所诉,若是只想终止某个线程(不管是主线程还是子线程)而不想终止进程,需要使用pthread_exit函数。若是在main函数内使用return,或是在任意线程执行函数中使用进程退出函数(exit和_exit),会导致进程退出,从而使所有线程被终止

26. Linux如何实现线程的互斥与同步?

Linux下的“pthread”库不仅提供了线程控制,还提供了丰富的互斥同步机制,并将其封装成各种接口供用户使用,其中包含:互斥锁、读写锁、自旋锁、条件变量、信号量等等。

27. 什么是互斥锁?

(1) 互斥锁可以使线程之间互斥地访问临界资源,实现了线程级的加锁与解锁

(2) 互斥锁满足让权等待,当线程申请加锁时,一旦锁被占用,则会将线程阻塞挂起,直到锁释放时线程才被唤醒。

(3) 互斥锁的底层实现具有原子性,因此互斥锁是线程安全的。

(4) “pthread”库提供的互斥锁的数据类型为“pthread_mutex_t”,其操作函数都以“pthread_mutex”为开头。

28. 什么是读写锁?

(1) 读写锁是一种针对线程读写操作的锁。它将线程划分为了读线程(读者)和写线程(写者),具有读共享、写独占、读写互斥等特性。

(2) 读共享:多个读者可以对同一个读写锁进行加锁操作(加读锁),实现多个读者同时读取某个临界资源。

(3) 写独占:同一时刻只能有一个写者拥有读写锁并进行加锁操作(加写锁),临界资源只能被一个写者独占,无法同时被多个写者访问。

(4) 读写互斥:对临界资源的读写不能同时进行。若该锁被读者占有,写者只能等待。若该锁被写者占有,读者也只能等待。若读写者同时申请锁,写者的优先级更高

(5) 读写锁的使用方法与互斥锁类似,都是由“pthread”库提供。读写锁的数据类型为“pthread_rwlock_t”,其操作函数都以“pthread_rwlock”为开头。只不过该锁具有两种加锁函数,分别为加读锁和加写锁

29. 什么是自旋锁?

(1) 自旋锁是一种特殊的锁。当线程想获取某个自旋锁(对其加锁)时,该锁又被其他线程所占有,线程会进入死循环,不断轮询该锁是否可用,而不是进入阻塞态等待被唤醒

(2) 由于等待自旋锁的线程不释放CPU,因此持有自旋锁的线程应该尽快释放自旋锁,避免CPU资源被浪费。

(3) 自旋锁的使用方法与互斥锁类似,都是由“pthread”库提供。自旋锁的数据类型为“pthread_spinlock_t”,其操作函数都以“pthread_spin”为开头。只不过该锁的加锁操作不再会阻塞线程,而是让线程不断轮询CPU

30. 互斥锁、读写锁和自旋锁的使用场景?

(1) 互斥锁多用于对线程安全要求较为严格的场景。他会完全隔绝线程之间对临界资源的访问,并且通过线程的阻塞与唤醒来避免CPU资源的浪费。

(2) 读写锁一般用于读操作多的场景。相比起完全隔绝读写的互斥锁,读写锁拥有更高的效率,提高了线程并发度。

(3) 自旋锁适用于加锁时间很短的场景。相比起互斥锁,它只需要消耗很少的资源来建立锁,同时操作系统又不需要对线程做一系列复杂的线程调度,如运行——阻塞——唤醒,大大提高了系统运行效率。

31. 什么是信号量?

(1) 信号量的本质就是一个变量,用于表示系统中某种资源的数量。信号量在资源数不够时,会主动让申请资源的线程进入阻塞态。在资源数有空余时,又会主动唤醒之前阻塞的线程。从这一点来看,linux下的信号量与操作系统中广义的信号量并无区别

(2) 在linux下,由一个名为“semaphore”的C语言库提供了信号量机制,其底层其实是借助了互斥锁以保证线程安全。

(3) linux下的信号量既支持进程级又支持线程级,由程序员决定要用到哪种信号量。

(4) 信号量的数据类型为“sem_t”,其操作函数都以“sem_”为开头。需要注意,信号量严格来说并不是一种锁,因此并没有所谓的加锁解锁操作,取而代之是P操作(sem_wait)和V操作(sem_post)

32. 什么是条件变量?

(1) 条件变量是一种排队等候和唤醒的机制,程序员可以检查某个线程是否满足某个条件,然后通过条件变量来让线程阻塞或唤醒。

(2) 条件变量底层维护了一个阻塞队列,通过条件变量阻塞的线程都会加入该队列中,而在唤醒时又会按顺序唤醒被阻塞的线程。

(3) 条件变量的使用与信号量类似,但是由“pthread”库负责实现。条件变量的数据类型为“pthread_cond_t”,其操作函数都以“pthread_cond_”为开头,其中的cond指的是“condition”

(4) 条件变量与锁不同,并没有加锁和解锁操作,取而代之是线程阻塞(pthread_cond_wait)和线程唤醒(pthread_cond_signal)

33. 条件变量与信号量差别?

(1) 条件变量和信号量都可以使线程阻塞与唤醒,不同的是,信号量有值,而条件变量无值

(2) 信号量的值反应了剩余资源数。在进行PV操作时,信号量会自动修改资源数的代表值,并自行判断是否要阻塞线程或唤醒线程。

(3) 条件变量没有值,实现原理更加简单。条件变量的底层只是单纯的阻塞队列,可以由程序员自由决定线程的阻塞与唤醒,判断是否阻塞的工作一般由线程代码负责。

34. 条件变量与互斥锁的差别?他们两者如何搭配使用?

(1) 条件变量并不是锁,与互斥锁的作用不同,但两者经常搭配使用。借由条件变量,可以解决某些场景下互斥锁的局限性

(2) 互斥锁的局限性在于它仅有加锁与解锁两种状态。线程无法根据互斥锁而得知临界资源的状态,需要不断地轮询该资源才可得知。而临界资源的状态有时候又显得无比重要,在其他线程改变临界资源的状态之前,即使某个线程获得了临界资源的访问权,也无法继续运行下去。

(3) 比如一个包含多个抢票线程的抢票程序,除了要让抢票线程互斥地访问票资源,还要注意票资源的状态——票数。当票数为0时,如果抢票线程继续运行,则会不断重复“获取锁->判断是否有票->没票->释放锁”这样的流程。这样的过程其实做的都是无用功,还会导致抢票线程频繁占用系统资源(互斥锁)。

(4) 有了条件变量,在线程获取访问票资源的互斥锁之后,便可以检查票数的剩余情况,没有票就将线程阻塞挂起,有票时再唤醒线程,避免在没票时浪费CPU资源

35. 为什么使用条件变量阻塞线程时还需要传入一个互斥锁?

当我们使用条件变量阻塞线程时,该线程通常已经具备了某个临界资源的访问权,然后发现不满足条件才会发生阻塞,而此时该线程会占用一个互斥锁。如果在阻塞线程前不释放锁,那么其他线程就无法获取锁,也无法修改临界资源的状态,自然被阻塞的线程也唤醒不了,导致系统死锁。所以使用条件变量阻塞线程时,必须要传入一个互斥锁,该锁在线程阻塞时会被自动释放,同时当线程被唤醒时,线程又会自动获取锁,并继续往下执行。

C/C++开发

1. 在linux下常用的C/C++开发工具有哪些?

vim(文本编辑器)、gcc/g++(编译器)、gdb(调试器)、make/makefile(自动化构建工具)

2. 怎么使用vim?

(1) vim是linux下的文本编辑器,具有三种模式:命令模式、底行模式、插入模式

(2) 在命令模式下,可以进行光标移动、文本复制(yy)、文本粘贴(p)、文本删除(dd)等操作。

(3) 在底行模式下,可以对文本文件进行设置,如保存(:w)、退出(:q)等操作。

(4) 在插入模式下,可以对文本进行编辑操作。

(5) 一般情况下,在进入底行模式和插入模式之前必须经过命令模式,不能直接切换

① 底行模式->插入模式, 按esc到命令模式,按i/a/o到插入模式

② 插入模式->底行模式, 按esc到命令模式,按:到底行模式

(6) 无论当前处于什么模式,按esc一定可以回到命令模式

3. 怎么使用gcc/g++?

(1) gcc/g++是linux下常用的C/C++编译工具,gcc是C语言编译器,g++是C++编译器。通过该工具,可以完成对C/C++代码文件的预处理、编译、汇编、链接等操作。

(2) 安装了gcc/g++之后,可直接在命令行下调用此工具。若不加入任何参数,默认对代码文件完成所有编译工作,生成名为“a.out”的可执行文件

(3) 下面介绍gcc/g++命令的常用参数:-E只执行预处理工作并生成.i文件、-S只执行编译工作并生成.s文件、-c(小写)只执行汇编工作并生成.o文件、-o指定经过gcc/g++处理后的文件名。

(4) 对于几个常用参数的文件后缀的记忆方法:“gcc-E,-S,-c”刚好对应键盘左上角的esc键,而对应生成的文件后缀分别为“i、s、o”,组合起来刚好是一个镜像文件的后缀。

(5) 链接操作没有单独的参数,在执行gcc/g++时,只要不加“E、S、c”等参数,默认会进行链接。

4. 怎么使用gdb?

(1) gdb是linux下的C/C++程序调试器。要使用gdb调试,其程序必须是debug模式。gcc/g++默认生成的程序是release模式,必须在源代码生成程序时加上“-g”选项,以debug模式输出程序,才能使用gdb

(2) gdb的常用操作有:list/l 行号(显示指定行号开始的10行代码)、list/l 函数名(显示指定函数的代码)、r/run(运行程序)、n /next(逐语句执行,不进入函数)、s/step(逐过程执行,会进入函数)、break/b行号(在某一行设置断点)、print/p表达式(打印表达式的值)、continue/c(继续执行程序,直到下个断点)、display 变量名(跟踪查看变量)、set变量名(设置变量值)、breaktrace/bt(查看调用堆栈)、quit(退出)

5. 怎么使用make/Makefile?

(1) make是一个命令,Makefile是一个文件。两者搭配使用,可以完成项目的自动化构建并生成可执行程序,无需手动调用gcc/g++。

(2) Makefile文件属于一种特殊的代码文件,其代码描述了各种中间文件(比如.o文件)之间的先后依赖关系,语法较为复杂,这里不展开细讲。

(3) make命令会自动调用gcc/g++,让他们依照Makefile文件中的依赖关系逐一生成文件,并最后输出可执行文件。

(4) make clean命令用于清除已生成的可执行文件

6. 怎么制作并使用静态库?

(1) 在linux下制作静态库需要借助gcc/g++,以及归档工具ar(相当于tar、zip等解压缩工具)。

(2) 首先需要将所有的代码源文件编译成.o文件,再使用ar命令将所有.o文件打包在一起,就生成了一个静态库

(3) 在linux下,静态库的后缀为.a,且库名称前必须加上“lib”,否则链接静态库会出错。

(4) 要想使用静态库,只需要在对使用静态库的源文件进行编译链接时,加上几个参数即可,如下图所示

7. 怎么制作并使用动态库?

(1) 使用gcc/g++生成动态库的.o文件时,需要加上-fPIC选项,如“g++ -fPIC -c test.c -o test.o”。

(2) 在生成库文件时,无需使用ar,只需要使用gcc/g++加上-shared选项,如“gcc -shared test.o -o test.so”,即可生成动态库。

(3) 在linux下,动态库的后缀为.so,且库名称前必须加上“lib”,否则链接动态库会出错。

(4) 动态库使用方式与静态库相同,在对使用动态库的源文件进行编译链接时,需要加上同样的参数,以指明库文件和头文件的搜索路径以及库名称。

(5) 与静态库不同的是在运行程序时,还必须告诉操作系统动态库路径,指明方法有三个:

① 修改环境变量LD_LIBRARY_PATH,添加库文件路径,但下次重启系统会失效。

② 将库文件和头文件放到系统的默认目录下。

③ 修改系统配置文件的动态库搜索路径。

网络编程

1. 什么是socket(套接字)?

(1) 众所周知,Linux是一种类Unix操作系统,而socket正是起源于Unix,是Linux“一切皆文件”思想的一种体现。

(2) 从用户角度来说,socket是一种特殊的文件,我们可以借助一些函数完成对该文件的IO操作。

(3) 从操作系统角度来说,socket是应用层与TCP/IP协议族通信的中间软件抽象层。它通过设计模式中的外观模式(门面模式)隐藏了复杂的TCP/IP协议族,提供了一系列简单的接口供用户使用,而背后则由socket负责管理数据,并代表了主机内某个IP地址和传输层端口号。在Linux网络编程中,基本都是借助socket完成网络之间的通信。

2. 什么是套接字描述符?

(1) socket(套接字)在linux下被抽象成了一个特殊文件。既然是文件,那么它就可以被进程打开,还会产生一个对应的文件描述符,这个文件描述符就被称为套接字描述符

(2) 套接字描述符与普通文件描述符并无不同,进程可以对该描述符使用一些通用的文件操作函数,如文件IO的write和read函数。

3. 说说常用的socket接口函数?

(1) socket:创建socket,并返回一个套接字描述符。该函数会要求用户指定套接字类型(流式套接字或数据报式套接字)和底层协议族(Pv4或IPv6)。

(2) bind:绑定一个主机IP地址和端口号给socket,之后该socket就会代表这个IP地址和端口号参与网络通信。

(3) listen:设置socket为监听模式,常用于TCP网络服务器。因为socket创建时默认为主动模式,而通常TCP网络服务器不需要主动发起连接,仅需接收客户端连接,使用该函数即可将socket转换为可接受连接的状态。

(4) connect:主动使用某个socket来与网络中的另一台主机建立连接,常用于TCP客户端

(5) accept:使用某个socket来接收网络上的连接,常用于TCP网络服务器

(6) read/write:网络I/O函数。他们实际上是文件的I/O 函数,其实也可用于socket,实现网络通信。除此之外,socket还提供了recv/send、recvfrom/sendto等专门用于套接字的网络I/O函数,功能与read/write类似。

(7) close:关闭套接字描述符。一般用于关闭进程的文件描述符,也可用于关闭进程的套接字描述符。

4. 详细说说accept函数?

accept函数有几个注意点:

(1) accept要求用户传入某个socket以接收客户端连接,用于接收的socket必须处于监听模式,也就是可接收连接的状态。

(2) accept默认会阻塞进程,直到有客户端连接建立后才会返回。

(3) accept的返回值是一个新的套接字描述符,被称为连接套接字。此前作为传入参数,处于监听模式的套接字被称为监听套接字。一旦某个服务器进程接受了客户端的连接,系统就会为这个连接创建一个连接套接字,负责此次连接的通信数据交互。当连接关闭时,连接套接字也随之被系统回收,但监听套接字不受此影响,会一直存在

5. 为什么服务器会存在连接套接字和监听套接字,用一个不行吗?

(1) 这是因为一个服务器通常会与多个客户端进行连接,若仅用一个套接字来进行多端通信,那么该套接字的功能会过多,使用时很不直观,后续的维护也会变得麻烦

(2) 使用多个套接字就可以使套接字的责任单一化,每个连接套接字只专注于点对点通信。

(3) 每个连接套接字的端口号与IP地址都与其监听套接字一致,只是在功能上进行了分化,系统对这些套接字的管理也变得简单许多。

6. 使用close关闭套接字描述符时,连接会被终止吗?

不一定。套接字描述符也是一种文件描述符,由文件描述符的特性可知,同一个文件可能会对应多个进程的文件描述符,只有当所有进程中指向同一个socket的套接字描述符都被关闭时,socket才会被系统真正回收,进而触发连接终止。

7. 什么主机字节序与网络字节序?

(1) 所谓的字节序就是指一个字数据内部的字节存放顺序(一个字若只有一个字节,就没有所谓的字节序),有两种存放顺序:大端和小端。大端是指字数据的高字节存储在低地址中,而小端则指字数据的高字节存储在高地址中。

(2) 字节序一般由CPU决定,在不同的主机上是不一样的,没有统一标准。因此就有了主机字节序的说法,特指当前主机使用的字节序

(3) TCP/IP协议栈明确规定了字节序标准,所有TCP/IP网络包的数据要想在网络上传输都应该按照统一的字节序,这种字节序被称为网络字节序。若是不按照网络字节序进行数据传输,那么网络中的中转结点就无法识别该数据,可能导致通信失败。

8. 在进行网络通信时,所有通信数据都必须转换为网络字节序吗?

(1) 若是这些数据最终会被传入TCP/IP网络包首部时,则必须转换为网络字节序。因为这些首部信息需要被网络中转结点识别,才能进行包转发。如使用bind函数时传入的IP地址和端口号,这些都是TCP/IP网络包首部的重要数据,必须进行字节序转化!

(2) 对于网络包中的主要通信数据(不含首部),可以不进行字节序转化。因为中转结点只根据首部信息来进行包转发,本来就不用识别包中携带的其他数据,即使数据无法识别也不会出现任何错误。但是通信的两台主机之间必须协商好统一的字节序,因为这些数据最起码要保证通信双方都能识别,否则就毫无意义

9. 如何使用socket完成一次完整的TCP连接?

(1) 服务器:初始化socket,将socket与IP地址和端口号进行绑定(bind),转换socket为监听模式(listen),然后等待客户端的连接并阻塞(accept)。

(2) 客户端:初始化socket,然后连接服务器(connect)。

(3) 如果连接成功,这时客户端与服务器的连接就建立了。客户端发送数据请求,服务器接收请求并处理请求,然后把响应数据发送给客户端,由客户端读取响应数据。

(4) 经过一系列的数据交互,服务完成,最后关闭TCP连接。如下图所示。

10. 如何使用socket实现UDP通信?

(1) 服务器:初始化socket,将socket与IP地址和端口号进行绑定(bind)。UDP服务器无需使用listen和accept等函数。

(2) 客户端:初始化socket。

(3) 服务器与客户端可以直接使用网络I/O函数进行数据交互。UDP通信无需提前建立连接,服务完成后,也无需关闭连接。

11. 在进行网络通信时一定要使用bind函数绑定IP地址和端口号吗?

(1) 所有基于socket的网络通信都要求连接双方必须有socket,也就是必须要有IP地址和端口号

(2) 客户端在通信前需要提供socket,但并不一定要使用bind绑定IP地址和端口号。如果要使用bind,就必须明确socket要与哪个端口号关联,否则容易导致端口号与其他程序发生使用冲突。所以在客户端发送数据时,即使没有提前bind,系统也会自动检测当前系统的空闲端口号,自动进行bind

(3) 服务器在创建socket的同时,还必须要使用bind绑定IP地址和端口号。因为服务器要一直提供确切的服务,保证任何客户端都能通过统一的IP地址和端口号连接到服务器进程。虽然服务器理论上也会有端口号相互冲突的情况,但是服务器一般会由管理员进行统一管理,因此问题不大。

12. 用于套接字读写的网络I/O函数之间的区别?

(1) socket提供的用于套接字读写的函数非常多,既可以使用文件I/O函数(read/write),还可以使用专门的网络I/O函数(recv/send、recvfrom/sendto)

(2) 所有网络I/O函数在底层都会调用文件I/O函数(read/write),其本质都是读写文件

(3) recv/send面向基于TCP的流式套接字,用于收发网络数据。

(4) recvfrom/sendto面向基于UDP的数据报式套接字,同样用于收发网络数据。

(5) 无论是recv还是recvfrom,都可以设置为阻塞与非阻塞(默认为阻塞模式)。阻塞模式会一直等待有数据到达,非阻塞模式则会立即返回。

13. 什么是同步/异步、阻塞/非阻塞?

(1) 从广义的角度上来讲,同步/异步是一种逻辑概念,它针对正在进行信息交互的通信双方。如果通信双方的交互严格按照顺序进行,即请求——应答——再请求——再应答,我们则称之为同步通信。反之,如果不按照顺序,比如发起请求后不等待对方应答,而直接发起下一个请求,我们则称为异步通信

(2) 阻塞/非阻塞是一种进程/线程上的概念,它表示进程/线程的某种状态。在阻塞状态下,进程/线程必须等待某个事件的发生才能被唤醒继续运行。而在非阻塞状态下,进程/线程不必等待事件发生,直接就可以接着运行。

(3) 对进程通信来说,可以把同步与阻塞、异步与非阻塞当作是同义词。因为如果要同步,那么就必须等待对方应答,而此时进程/线程自然就处于阻塞状态,等待被唤醒。如果可以异步,那么进程/线程就可以不进入阻塞状态,在发起请求后接着发起下个请求。

(4) 而对网络I/O来说,他们两者则具有严格的区分。两者通过两两组合,诞生出了不同的网络I/O模型:同步阻塞、同步非阻塞、异步非阻塞。注意,对网络I/O来说,没有异步阻塞的概念!

14. 网络I/O模型是什么?

(1) 所有的网络I/O都会涉及到两个系统对象,一个是调用这个IO的进程/线程,另一个就是系统内核。比如当一个read操作发生时,它会经历两个阶段:等待数据准备好并到达系统内核——>将数据从内核拷贝到进程中

(2) 由于网络I/O的两个阶段可以有不同的状态,因此网络I/O被划分为了多种模型。常见的网络I/O模型有:同步阻塞、同步非阻塞、异步非阻塞

(3) 同步阻塞:最常见的网络I/O模型,大部分网络I/O函数都默认以该模式运行。该模式下网络I/O的两个阶段都会被系统阻塞,直到完整的I/O操作完成后才会返回。

(4) 同步非阻塞:通过传入特定参数,可以使网络I/O函数以该模式运行。在该模式下,当用户发起网络I/O请求时,如果内核中的数据还没有准备好,那么它会立即返回错误而不会阻塞进程。一旦内核中的数据准备好了,并且又再次收到了用户进程的网络I/O请求,那么它就会将数据拷贝到用户内存,然后返回。请注意,该模式下用户进程其实是需要不断发起网络I/O请求来轮询内核数据,然后再由网络I/O函数实现拷贝操作。

(5) 异步非阻塞:该模式需要用到特殊的网络I/O函数,通常以AIO_开头。在该模式下,当用户发起网络I/O请求时,无论如何都会立即返回而不会阻塞进程。所有的网络I/O操作交由内核去解决,包括内核的数据准备以及内核到用户进程的数据拷贝。当这些操作都完成时,内核会通知进程,我的I/O操作已经完成了。通知方法包括但不限于:设置用户空间的特殊变量值、触发一个进程信号、产生一个软中断或调用某个回调函数

15. 网络I/O中的同步阻塞、同步非阻塞、异步非阻塞的区别是什么?

(1) 对于阻塞与非阻塞,区别主要在于当进程发起网络I/O请求时是否会进入阻塞态

(2) 对于同步与异步,在网络I/O中的主要区别在于,是否由进程主动完成数据从内核到用户进程的拷贝。如果由进程主动完成,那么在拷贝过程中,进程实际是被阻塞的,也就是说进程在发起拷贝请求后必须等待数据拷贝完成才能继续运行,符合同步定义。相反,如果由内核完成拷贝操作,不用等待拷贝完成,那么即符合异步定义

(3) 同步非阻塞需要进程不断发起网络I/O请求轮询内核数据,而异步非阻塞则不用

(4) 网络I/O模型中没有异步阻塞,异步只能非阻塞,否则不满足异步定义,因此我们也可以简单地称异步非阻塞为异步I/O

16. 套接字的读就绪和写就绪是什么?

在网络I/O经历的两个阶段中,诞生了两种特殊状态,又称I/O事件——“读就绪/写就绪”

(1) 读就绪:套接字的内核接收缓冲区所接收的数据已经准备完毕,可以将数据从内核拷贝到进程中,此时称该套接字读就绪

(2) 写就绪:套接字的内核发送缓冲区已经成功将数据发送出去,可以将新的数据从进程拷贝到内核中,此时称该套接字写就绪

17. I/O多路复用什么?

(1) 在以前,为了使一个服务器同时与多个客户端进行连接,往往会采用多进程与多线程。由于多进程与多线程往往会导致资源浪费和性能低下,尤其对于高负载服务器来说更是如此,因此有了一种更好的解决方法——I/O多路复用

(2) I/O多路复用也是一种网络I/O模型,其具体实现有:select,poll,epoll,I/O多路复用可以使单个进程同时处理多个网络连接的IO操作。

(3) I/O多路复用的三个具体实现本质上都是同步I/O,因为他们都必须要进程去处理数据从内核到进程的读写操作。

18. 具体说说select、poll、epoll?

(1) select

① select的底层实现是一个位图,位图中的每个比特位都表示一个需要处理的套接字,比特位的值会反映该套接字是否产生了I/O事件(读就绪或写就绪),极大地节省内存空间。

② select可以是同步阻塞,也可以是同步非阻塞

③ select在几乎所有的平台上支持,具有良好的跨平台特性

④ select底层的位图大小从一开始便已确定下来,导致它能处理的套接字数量上限不能动态变化

⑤ select检查套接字时会进行线性扫描,也就是一一轮询每个套接字的状态,当套接字比较多的时候,这样的方法会导致效率低下,浪费CPU资源。

(2) poll

① poll是一个增强版本的select,其底层采用了链表,链表的每个结点都表示一个需要处理的套接字。

② 由于使用链表,poll能处理的套接字数量可以动态变化,其大小只受限于系统内存。

③ poll的效率上相比较select并无改善,依然是一种轮询式的遍历方式。

④ 由于I/O事件(读就绪或写就绪)发生时,内核需要将该消息传递到用户空间,需要进行消息拷贝。相比起select的比特位拷贝,poll的链表结点拷贝无疑会增大拷贝的开销

⑤ poll还具有“水平触发”的特点。如果某个套接字的I/O事件在告知用户进程后没有被处理,那么下次轮询检查时还会再次通知用户进程。

(3) Epoll

① epoll底层采用了一个红黑树和一个双链表。红黑树的每个结点都表示一个需要处理的套接字,每个套接字都与设备(网卡)驱动程序建立了回调关系,当I/O事件(读就绪或写就绪)发生时,会调用一个回调函数,将发生事件的套接字从红黑树添加到双链表中。

② epoll使用红黑树可以高效地识别出重复添加的套接字,而无需进行轮询。

③ epoll在检查套接字状态时,无需进行轮询遍历,只需要检查双链表是否为空。若不为空,则返回链表中的所有结点即可。

④ epoll使用共享内存的方式来进行内核与用户进程之间的数据共享,不再需要耗时的数据拷贝操作。

19. epoll的ET模式有什么用?

(1) epoll提供了水平触发(LT)和边缘触发(ET)两种模式。默认情况下epoll为水平触发,会重复告知进程上次未处理I/O事件的套接字。若设置为边缘触发,那么只会返回一次I/O事件,直到该套接字有新的I/O事件之前都不会返回。

(2) ET模式适用于,系统中存在大量需要监控的套接字,但程序员仅关注其中几个套接字的情况。相比起LT模式,ET模式能极大提高程序效率,不会导致每次检查时都返回大量程序员不关心的套接字的情况。

20. select、poll、epoll之间的差别?

(1) select和poll都需要自己不断轮询所有的套接字,而epoll只要判断一下链表是否为空就行了,在综合性能方面epoll无疑是最好的

(2) select,poll每次返回I/O事件时都需要将该消息从内核拷贝到进程中,而epoll则使用了共享内存,省去了拷贝开销

(3) 在实际使用上,select和poll也有它们的使用场景。在网络连接数较少并且连接都十分活跃的情况下,select和poll的轮询开销和拷贝开销会非常小,性能可能比epoll更好。因为epoll底层有着较为复杂的数据结构,还需要频繁使用回调函数,而这些开销与连接数的多少并无关系。

更新记录

更新日期 更新详情
(2023年06月28日) 吸收了博客、公众号、网课、面经等Linux笔记,分区域总结了Linux下的重要知识点,尽可能详细地解释了Linux的一些底层机制。
(2023年08月31日) 将各个大板块重新编号,现在各个版块都有独立的题目编号,互不干扰。将更新记录中的版本号替换为年份日期,取消版本号机制,方便记录更新。
(2023年09月17日) 修改首页文字布局,统一化布局。修改前言。添加前提基础模块。更改正文和标题字体。修改部分问题描述。更新目录。修改参考资料。所有的更新日期都添加前置0,统一长度。将“Linux”模块更名为“Linux概述”

参考资料

《鸟哥的Linux私房菜》

《操作系统》:https://interviewguide.cn/notes/03-hunting_job/02-interview/02-01-os.html

《王道计算机考研 操作系统》:https://www.bilibili.com/video/BV1YE411D7nH

《Linux的常用命令》:https://www.bilibili.com/video/BV1Pa4y1a7XT

《Linux合集》:https://blog.csdn.net/qq_24016309/category_12067863.html

《Linux的文件系统详解》:https://blog.csdn.net/s_frozen/article/details/127885759

《【Linux】文件的权限》:https://blog.csdn.net/weixin_45423515/article/details/126652740

《Linux中文件权限(有图详细讲解)》:https://blog.csdn.net/MssGuo/article/details/115462435

《linux之EXT2文件系统…》:https://blog.csdn.net/gengzhikui1992/article/details/100180969

《什么是vfs以及vfs的作用》:https://blog.csdn.net/weixin_36145588/article/details/73607463

《内存中inode与磁盘中inode》:https://blog.csdn.net/u014426028/article/details/105809411

《文件描述符与inode的关系》:https://blog.csdn.net/weixin_43864567/article/details/124063198

《【Linux】进程信号》:https://blog.csdn.net/m0_63639164/article/details/129241917

《Linux下主线程和子线程…》:https://blog.csdn.net/qq_24003917/article/details/107436332

《学习Linux系统的五大理由!》:https://blog.csdn.net/oldboyedu1/article/details/129365916

《Linux和网络常用面试题》:https://blog.csdn.net/m0_63913097/article/details/125742060

《Linux的SOCKET编程详解》:https://blog.csdn.net/hguisu/article/details/7445768

《套接字之读写》:https://blog.csdn.net/fengxianghui01/article/details/104398214

《Linux读写锁…》:https://blog.csdn.net/weixin_42374938/article/details/119188968

《阻塞非阻塞与同步异步的区别?》:https://blog.csdn.net/gavin_dyson/article/details/94431846

《同步与异步、阻塞与非阻塞》:https://blog.csdn.net/u010429831/article/details/102811314


操作系统(Linux)笔记与总结
http://example.com/2023/09/17/操作系统(Linux)笔记与总结/
作者
苏青羽
发布于
2023年9月17日
许可协议