嘘~ 正在从服务器偷取页面 . . .

Linux 学习


0. 简介

0.1 Linux

Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

0.2 Linux & Unix

  • 开源性:Linux 是一款开源操作系统,不需要付费,即可使用;Unix 是一款对源码实行知识产权保护的传统商业软件,使用需要付费授权使用;
  • 跨平台性:Linux 操作系统具有良好的跨平台性能,可运行在多种硬件平台上;Unix 操作系统跨平台性能较弱,大多需与硬件配套使用;
  • 可视化界面:Linux 除了进行命令行操作,还有窗体管理系统;Unix 只是命令行下的系统;
  • 硬件环境:Linux 操作系统对硬件的要求较低,安装方法更易掌握;Unix 对硬件要求比较苛刻,按照难度较大;
  • 用户群体:Linux 的用户群体很广泛,个人和企业均可使用;Unix 的用户群体比较窄,多是安全性要求高的大型企业使用,如银行、电信部门等,或者 Unix 硬件厂商使用,如 Sun 等;
  • 相比于 Unix 操作系统,Linux 操作系统更受广大计算机爱好者的喜爱,主要原因是 Linux 操作系统具有 Unix 操作系统的全部功能,并且能够在普通 PC 计算机上实现全部的 Unix 特性,开源免费的特性,更容易普及使用。

0.3 GNU

GNU 是一个自由的操作系统,其内容软件完全以 GPL 方式发布。这个操作系统是 GNU 计划的主要目标,名称来自 GNU’s Not Unix! 的递归缩写。GNU 的起源是因为 Unix 进行收费和商业闭源,GNU 是一个计划或者叫运动。在这个旗帜下成立了 FSF,起草了 GPL。

常说的 Linux 只是内核(系统启动之后使用的仍然是 gcc 和 bash 等软件),而 GNU 系统缺少操作系统内核,于是两人合在一起之后打包发布为 GNU/Linux。该系统的其余部分主要是由 GNU 工程编写和提供的程序组成。

Linux 体系结构

用户空间(User Space) :用户空间又包括用户的应用程序(User Applications)、C 库(C Library) 。

内核空间(Kernel Space) :内核空间又包括系统调用接口(System Call Interface)、内核(Kernel)、平台架构相关的代码(Architecture-Dependent Kernel Code) 。

1. 磁盘

1.1 磁盘组成

磁盘由盘片、柱面、扇区、磁道组成。

盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面

盘片

磁道&扇区

每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。所有盘面中相对位置相同的磁道组成柱面

所有盘面中相对位置相同的磁道组成柱面

磁盘容量的计算公式可以表示为:硬盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节

1.2 磁盘分区

第一个扇区 512Bytes 会有这两个数据:

  • 主要开机记录区(Master Boot Record, MBR):可以安装开机管理程序的地方,有 446 Bytes;
  • 分区表(partition table):记录整颗硬盘分区的状态,有 64 Bytes。

MBR 三个区

磁盘最多 4 个分区。为了满足实际需要,将其中一个作为扩展分区,扩展分区再进一步分成若干个逻辑分区。每个磁盘或者4个主分区,或者3个主分区加一个扩展分区。每个分区的第一个扇区作为引导扇区。每个分区可以通过格式化建立某种类型的文件系统,不同分区的文件系统可以不同。

hd 代表 IDE 硬盘;sd 代表 SCSI 硬盘。

同类型设备从字符 a 开始编号,a 代表第一个硬盘,b 代表第二个硬盘,依次类推。主分区和扩展分区的编号从 1 到 4。扩展分区中的逻辑分区从编号 5 开始。

例子

MBR 模式下的磁盘分区

2. 文件管理

为了规范 Linux 文件位置规范,提出了 Filesystem Hierarchy Standard (FHS)标准。

四种交互作用的形态

FHS 其实只规定了三层目录下的结构:

  • /(root, 根目录):与开机系统有关;
  • /usr(unix software resource):与软件安装/执行有关;
  • /var(variable):与系统运行过程有关。

2.1 目录结构

树状目录结构

2.1.1 / 结构

  • /bin:bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令(包括 cat rm 等命令);
  • /boot:这里存放的是启动 Linux 时使用的一些核心文件,包括一些连接文件以及镜像文件;
  • /dev:dev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外部设备,在 Linux 中访问设备的方式和访问文件的方式是相同的;
  • /etc:etc 是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。
  • /home:用户的主目录,在 Linux 中,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的,如上图中的 alice、bob 和 eve;
  • /lib:lib 是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库,其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库;
  • /lost+found:使用标准的 ext2/ext3/ext4 文件系统格式才会产生的一个目录,目的在于当文件系统发生错误时, 将一些遗失的片段放置到这个目录下。不过如果使用的是 xfs 文件系统的话,就不会存在这个目录了;
  • /media:Linux 系统会自动识别一些设备,例如 U 盘、光驱等等,当识别后,Linux 会把识别的设备挂载到这个目录下;
  • /mnt:系统提供该目录是为了让用户临时挂载别的文件系统的(在 Linux 中,光盘可以挂在到 /mnt 目录上),我们可以将光驱挂载在 /mnt/ 上,然后进入该目录就可以查看光驱里的内容了(通过 mout umout 挂载/解除挂载);
  • /opt:opt 是 optional(可选) 的缩写,这是给主机额外安装软件所摆放的目录(简而言之,是给第三方软件软件放置的地方)。比如你安装一个 ORACLE 数据库则就可以放到这个目录下。默认是空的;
  • /proc:proc 是 Processes(进程) 的缩写,/proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,这个目录是一个虚拟的目录,它是系统内存的映射(Linux 将在内存中运行的程序统一映射到这个目录下),我们可以通过直接访问这个目录来获取系统信息;
  • /srv:srv 可以视为“service”的缩写,是一些网络服务启动之后,这些服务所需要取用的数据目录。 常见的服务例如 WWW、FTP 等等。不过,系统的服务数据如果尚未要提供给网际网络任何人浏览的话,默认还是建议放置到 /var/lib 下面即可;
  • /temp:这是让一般使用者或者是正在执行的程序暂时放置文件的地方。

2.1.2 /usr 结构

依据 FHS 的基本定义,/usr(unix software resource)里面放置的数据属于可分享的与不可变动的(shareable,static)。

FHS 建议所有软件开发者,应该将他们的数据合理的分别放置到这个目录下的次目录,而不要自行创建该软件自己独立的目录。

/usr 目录如下

2.1.3 /var 结构

/var 就是在系统运行后才会渐渐占用硬盘容量的目录。 因为 /var 目录主要针对常态性变动的文件,包括高速缓存(cache)、登录文件(log file)以及某些软件运行所产生的文件, 包括程序文件(lock file、run file),或者例如 MySQL 数据库的文件等等。

/var 目录如下

2.2 权限管理

通过 ls -al 可以看到如下示例(这里的第一个符号表示文件类型,d 表示文件夹,- 表示普通文件):

各文件显示

2.2.1 用户权限

Linux 有一个超级用户——root,这个角色拥有最高权限,可以执行 Linux 系统中所有操作。

为了进行用户的权限管理,通过 useradd usermod userdel 等命令进行操作。

Linux 还有用户组的概念,可以管理一个组内所有用户的权限。比如在使用 Docker 时,默认只能通过 root 用户使用,如果需要配置其他用户使用我们需要配置 Docker 用户组:

sudo groupadd docker
sudo usermod -aG docker ${USER}
sudo systemctl restart docker
# 重新切换用户即可生效

# 查看所有当前账户支持的组群
groups
# 之后进行有效群组切换
newgrp docker

每个用户和群组都会有对应的 UID 和 GID,这些 id 一一对应。通过查看 /etc/passwd,获取对应账号信息。

显示了各个账号

显示中的 x 是早期 Unix 存放密码的地方,现在因为存放在 /etc/shadow,所以现在显示 x。

第一个数字表示使用这个的 UID,0 表示系统管理员,1-999 表示系统账号。其他的就是可登录账号

第二个数字则表示对应的 GID。

特殊 shell & PAM

可以看到,系统账号使用的 shell 是 /sbin/nologin。这些系统账号本质上是无法跟用户 shell 进行交互的账号。像是 apache 账号管理的 WWW 服务,这样,他们自己在 Linux 内部就可以进行系统程序的工作。

PAM 是一套应用程序接口(API),提供了一连串的验证机制,这样保证在诸多账号的 Linux 中验证具有一样的结果。

2.2.2 文件权限

Linux 中,文件的权限有三种,可读、可写、可执行

所以在查看文件时会显示 rwx

  • r (read):可读取此一文件的实际内容,如读取文本文件的文字内容等;
  • w (write):可以编辑、新增或者是修改该文件的内容(但不含删除该文件);
  • x (execute):该文件具有可以被系统执行的权限。

可以使用 chmod 更改某个文件的权限,因为文件的权限是也是通过二进制存储的,所以更改的方式也有两种。

# user 拥有全部权限,group 和 other 仅拥有执行和读取权限
chmod u=rwx go=rx test
# 对 test 赋予全权限
chmod 777 test

文件显示是 rwx|rwx|rwx,依次代表用户、用户组、其他用户的权限。如果每个权限都打开那就是 1+2+4=7

元件 内容 叠代物件 R W X
文件 详细数据 data 文件数据夹 读到文件内容 修改文件内容 执行文件内容
目录 文件名 可分类抽屉 读到文件名 修改文件名 进入该目录的权限(key)

2.2.3 特殊权限

Linux 的文件权限除了基本的 rwx 之外,还有 SUID、SGID、SBIT 等。

/tmp 和 /usr/bin/password 中的例子

举例来说,password 文件的所属为 root:root,但是在日常的使用中,当前用户也应该拥有修改自己密码的权限。SUID 让用户在执行当前文件时临时拥有拥有者(也就是 root)的权限。

  • SUID 权限仅对二进制程序(binary program)有效;
  • 执行者对于该程序需要具有 x 的可执行权限;
  • 本权限仅在执行该程序的过程中有效(run-time);
  • 执行者将具有该程序拥有者(owner) 的权限。

SGID 同理,针对用户组进行权限管理。

SBIT 目前只针对目录有效,设定之后,任何人都可以添加/修改对应的文件,但是只有文件的创建者和 root 有权力删除该文件:

  • 当使用者对于此目录具有 w, x 权限,亦即具有写入的权限时;
  • 当使用者在该目录下创建文件或目录时,仅有自己与 root 才有权力删除该文件。

编号代表:4 为 SUID、2 为 SGID、1 为 SBIT。

2.3 文件系统

磁盘分区完毕后还需要进行格式化(format),之后操作系统才能够使用这个文件系统。

文件系统通常会将这两部份的数据分别存放在不同的区块,权限与属性放置到 inode 中,至于实际数据则放置到 data block 区块中。 另外,还有一个超级区块 (superblock) 会记录整个文件系统的整体信息,包括 inode 与 block 的总量、使用量、剩余量等。

  • superblock:记录此 filesystem 的整体信息,包括 inode/block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
  • inode:记录文件的属性,一个文件占用一个 inode,同时记录此文件的数据所在的 block号码;
  • block:实际记录文件的内容,若文件太大时,会占用多个 block 。

inode/block 数据存取示意图

2.3.1 EXT2 文件系统

为了在文件系统存储量过大时,方便管理 inode 和 block,Ext2 文件系统在格式化的时候基本上是区分为多个区块群组 (block group) 的,每个区块群组都有独立的 inode/block/superblock 系统。

EXT2 文件系统示意图

Block 大小 1KB 2KB 4KB
最大单一文件限制 16GB 256GB 2TB
最大文件系统总容量 2TB 8TB 16TB

每个 block 仅能最多容纳 4KB(如果不做任何处理,40MB 的大小就需要上千的 block,一一对应的 inode 的数量是不够的)。那么对于 inode,以 1KB 的 block 来说,会定义 12 个直接、一个间接、一个双间接、一个三间接。

所以总量加起来就如下公式所述,与单一文件限制完全一致。

但是不一定每个文件系统都能达到这样的最大容量,会根据系统的情况做出限制。之后还有 EXT3、EXT4 等升级过的文件系统,都是向下兼容的。

2.3.2 简单操作

  • df:列出文件系统的整体磁盘使用量;
  • du:评估文件系统的磁盘使用量(常用在推估目录所占容量)

通过 df 查询磁盘

评估文件系统磁盘使用量

2.3.3 链接

在 Linux 下面的链接文件有两种,一种是类似 Windows 的捷径功能的文件,可以让你快速的链接到目标文件(或目录); 另一种则是通过文件系统的 inode 链接来产生新文件名,而不是产生新文件。这两者分别成为软链接和硬链接。

硬链接如上文所说,链接的文件 inode 和源文件一致。而这样的链接无法 FileSystem,也不能链接目录(如果链接目录,目录下所有的文件都需要建立一个链接,这是做不到的)。

硬链接的文件读取示意图

Symbolic link 就是在创建一个独立的文件,而这个文件会让数据的读取指向他 link 的那个文件的文件名(可以理解为 Windows 中的快捷方式)。

软链接/符号链接的文件读取的示意图

3. Shell

如果需要调用 Linux Kernel 来实现某种功能,对用户来说,就需要使用到 Shell。

三者相关性图示

Linux 中 Shell 众多,又因为 Linux 为 C 语言撰写的,C shell 相对就比较热门。 /etc/shells 文件中,至少就有一下几个 shells 可以使用:

  • /bin/sh (已经被 /bin/bash 所取代);
  • /bin/bash (就是 Linux 默认的 shell);
  • /bin/tcsh (整合 C Shell ,提供更多的功能);
  • /bin/csh (已经被 /bin/tcsh 所取代)。

3.1 Shell 命令

3.1.1 压缩文件

Linux 常见压缩文件扩展名

gzip 是应用度最广的压缩命令,目前 gzip 可以解开 compress, zip 与 gzip 等软件所压缩的文件,创建的压缩文件为 *.gz 的文件名。其他压缩指令还有很多。

然而 gzip 仅仅只是把文件压缩,并不能把多个文件或者目录包打包成一个大文件(如同 Windows 中的 7Zip)。在 Linux 中通常使用 tar 命令配合进行解压缩或者打包文件。

常用操作

3.1.2 vi/vim

vi 共分为三种模式,分别是“一般指令模式”、“编辑模式”与“命令行命令模式”。

  • 一般指令模式 (command mode)
    以 vi 打开一个文件就直接进入一般指令模式了(这是默认的模式,也简称为一般模式)。在这个模式中, 可以使用“上下左右”按键来移动光标,可以使用“删除字符”或“删除整列”来处理文件内容, 也可以使用“复制、贴上”来处理文件数据;
  • 编辑模式 (insert mode)
    在一般指令模式中可以进行删除、复制、贴上等等的动作,但是却无法编辑文件内容的! 要等到按下“i, I, o, O, a, A, r, R”等任何一个字母之后才会进入编辑模式。通常在 Linux 中,按下这些按键时,在画面的左下方会出现“ INSERT 或 REPLACE ”的字样,此时才可以进行编辑。而如果要回到一般指令模式时, 则必须要按下“Esc”这个按键即可退出编辑模式;
  • 命令行命令模式 (command-line mode)
    在一般模式当中,输入“ : / ? ”三个中的任何一个按钮,就可以将光标移动到最下面那一列。在这个模式当中, 可以提供“搜寻数据”的动作,而读取、存盘、大量取代字符、离开 vi 、显示行号等等的动作则是在此模式中达成的!

一般指令模式切换到编辑模式的可用的按钮说明

一般指令模式切换到命令行界面的可用按钮说明

如果在 vi 编辑的过程中,程序因为异常而中断,未保存的数据会存储到同名的 swp 文件中。这样,在意外退出之后,使用 vi 进入之后还能读取 swp 文件内容(这个文件并不会自动删除)。

而 vim 算是 vi 的进阶版,能够通过颜色的高亮帮助开发者找出文件中的可能的问题。

3.1.3 目录操作

  • mkdir 目录名称 增加目录;
  • ls/ll(ll 是 ls -l 的别名,ll 命令可以看到该目录下的所有目录和文件的详细信息):查看目录信息;
  • find 目录 参数 寻找目录(查);
    示例:① 列出当前目录及子目录下所有文件和文件夹: find .
    ② 在 /home 目录下查找以 .txt 结尾的文件名:find /home -name "*.txt" ,忽略大小写: find /home -iname "*.txt"
    ③ 当前目录及子目录下查找所有以 .txt 和 .pdf 结尾的文件:find . \( -name "*.txt" -o -name "*.pdf" \)find . -name "*.txt" -o -name "*.pdf"
  • mv 目录名称 新目录名称 修改目录的名称(改)。注意:mv 语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv 命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中;
  • mv 目录名称 目录的新位置 移动目录的位置——剪切(改)。注意:mv 语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作;
  • cp -r 目录名称 目录拷贝的目标位置 拷贝目录(改),-r 代表递归拷贝(拷贝整个文件夹);
  • rm [-rf] 目录 : 删除目录(删)。注意:rm 不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用rm -rf 目录/文件/压缩包。

3.1.4 文件的操作命令

  • touch 文件名称: 文件的创建(增);

  • cat/more/less/tail 文件名称 :文件的查看(查) 。命令 tail -f 文件 可以对某个文件进行动态监控,例如 tomcat 的日志文件, 会随着程序的运行,日志会变化,可以使用 tail -f catalina-2016-11-11.log 监控文件的变化 ;

3.2 Shell 脚本

在使用 shell 命令时,如果遇到多个命令,可以写成一个脚本的形式,这样执行起来会方便很多。

在 bash 脚本中也可以正常使用系统中的环境变量,在脚本中定义的变量被称为用户变量(有点类似 C 语言中局部变量和全局变量的意思)。通过 $ 进行调用,在脚本结束之后会被删除。

#!/bin/bash
# Shell 的第一行并非为注释,而是告诉使用什么 shell 来执行此脚本
value1=1
value2=$value1
echo -n "the value is $value2 "
date
# 通过这个简单的 shell 脚本输出对应值
# the value is 1 2022年 12月 17日 星期六 13:09:24 CST

3.2.1 命令替换

在 shell 脚本中,最有用的特性之一就是可以直接获取命令输出的信息赋值给变量,以便之后的操作。

有两种方法可以将命令输出赋给变量:

# 反引号字符(`) testing=`date`
testing=`date`
# $()格式 testing=$(date) 并且第一个方法已经被标记为 deprecated
testing=$(date)

3.2.2 输出输入重定向

原本的输入输出仅指键盘和显示器这种硬件,有时候可以把这些内容转移到文件中。就像是把日志内容输出到对应的文件中,使用 > 进行输出重定向,>> 进行追加数据不覆盖。

#!/bin/bash
now=$(date +%Y%m%d)
# 让 now 仅输出年月日
echo "the value is $now"
# the value is 20221217
# 将 ls 查询的文件结果输出到 log 文件中
ls -al / > log."$now"
# 将 words 中的文字统计
# 输出结果为 行数 次数 字节数
wc < words
# 0  4 21

3.2.3 处理逻辑

数学运算

原先的 shell 中,是通过 expr 进行数学计算。为了保持跟 Bourne shell 的兼容提供了美元符号和方括号的解决方案。$[ operation ]。我们就可以使用 var4=$[ $var1 * ($var2 - $var3) ] 来代替 var3=$(expr $var2 / $var1)。除此之外,还有 $(()) 的方式也支持运算。

判断

在 Shell 脚本中,[] 是判断符号,用于值的判断。

  • 在中括号 [] 内的每个元件都需要有空白键来分隔;
  • 在中括号内的变量,最好都以双引号括号起来(直接使用 $ 是没有办法作为值使用的);
  • 在中括号内的常数,最好都以单或双引号括号起来。
#!/bin/sh
echo  "Enter the first integer:"
read first
echo  "Enter the second integer:"
read second
# read -p "Enter the second integer:" second 相同效果
# 在 [] 中需要加入空格,不然会报错
# 变量用双引号包围
if [ "$first" -gt "$second" ]
then 
	echo "$first is greater than $second"
# 可以使用 -o -a 表示或/与
elif [ "$first" -lt "$second" ]
then 
	echo "$first is less than $second" 
else
	echo "$first is equal to $second" 
fi
# -gt 像是汇编用法(greater than),-lt,-eq

Shell 中的关系运算符只支持数字,不支持字符串。

shell 关系运算符

Shell 提供了 test。比汇编中的功能更加强大。主要用于验证作用。

test 使用示例

#!/bin/bash
hour=`date +%H`
case $hour in
 	0[1-9] | 1[01] )  
        echo "Good morining !!" 
        ;;
	1[2-7] ) 
        echo "Good afternoon !!" 
        ;;
    # 相当于万用符号,表示任意字符
 	* ) 
        echo "Good evening !! " 
        ;;
Esac
# 使用 case 和正则进行判断

循环

在 shell 的处理中,for 循环有 for in 的用法,后面一般接长短的文字或者字符串(可以通过 * 进行通配符比较)。shell 中也提供了 C 语言风格的 for 命令,形式如 for (( i=1;i<=3;i++ ))

whileuntil 都作为处理循环的关键字。

for var in con1 con2 con3 ...
do
	...
done

# 可以直接使用 = !=
while [ "$a" = "$b" ]
do
	...
done

3.2.4 默认变量

在执行脚本时,针对之后出现的参数,已经有了设置好的变量名称

bash example.sh /path/to/scriptname opt1 opt2 opt3 opt4
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4

在 script 内还可以调用特殊变量。

  • $# :代表后接的参数“个数”,以上表为例这里显示为“ 4 ”;
  • $@ :代表“ “$1” “$2” “$3” “$4” ”之意,每个变量是独立的(用双引号括起来);
  • $* :代表“"$1<u>c</u>$2<u>c</u>$3<u>c</u>$4"”,其中 <u>c</u>为分隔字符,默认为空白键, 所以本例中代表“ “$1 $2 $3 $4” ”之意。

3.2.5 函数

在函数中,$1 表示函数中的各个参数。

#!/bin/bash
# 创建一个判断一个数是否为素数的函数
function is_prime() {
    j=2
    while [ $j -le `expr $1 / 2` ]
        do
            if [ `expr $1 % $j` -eq 0 ] 
                then
                    return 0
                    break;
                fi
            j=`expr $j + 1`
    done
    return 1
}
echo -n "Enter a number:"
read num
is_prime $num
if [ $? -eq 1 ]; then
    echo "$num is a prime number"
else
    echo "$num is not a prime number"
fi

4. GNU C 开发

如果存在多个 C 文件需要 GCC 进行统一管理编译,当然可以使用 shell 脚本。但是这样就会导致每个文件都需要重新编译一遍,无法达成高效的管理,这里就可以用 Makefile。

hello:hello.c
	gcc -I${HOME}/incl -c hello.c
	gcc -o hello hello.o
	rm -f hello.o
	mv hello ${HOME}/bin

通过使用 make,就可以执行 makefile 中的操作。

预定义变量名 含义 默认值
AR 归档程序 ar
AS 汇编器 as
CC C语言编译器 cc
CXX C++编译器 g++
CPP 带有标准输出的C语言预处理程序 $(CC) –E
RM 删除文件的命令 rm –r
变量 功能描述
$^ 所有的依赖文件,以空格分开,以出现的先后为序
$< 第一个依赖文件的名称
$? 所有的依赖文件,以空格分开,它们的修改日期比目标的创建日期晚
$* 不包含扩展名的目标文件名称
$@ 目标的完整名称
# 使用内置的变量可以简化很多
OBJS=main.o app.o mod.o lib.o
# 将四个 .o 文件链接为一个,$@ 表示编译出的结果为 appexam.o
appexam:$(OBJS)
	$(CC) -o $@  $^
# 编译上述第一个即,main.c 文件
main.o:main.c app.h
	$(CC) -c -o $@  $<
app.o:app.c app.h
	$(CC) -c -o $@  $<
mod.o:mod.c
	$(CC) -c -o $@  $<
lib.o:lib.c lib.h
	$(CC) -c -o $@  $<
clean:
	rm  -f *.o  

4.1 简单示例

# 如果是多文件执行请注意不要出现多 main 函数的情况
OBJS=c.o
%.o: %.c
	$(CC) -c $< -o $@
main: $(OBJS)
	$(CC) $^ -o $@
c.o: c.c
clean:
	rm -f *.o

在这个 Makefile 中,有六个规则:

  1. %.o: %.c:这个规则告诉 make,如果有一个 .o 文件需要更新,并且它对应的 .c 文件已经更新,则执行下面的命令;
  2. $(CC) -c $< -o $@:这个命令使用 $(CC) 变量(通常是 cc 或 gcc)来编译 .c 文件并生成 .o 文件。$< 是一个特殊变量,它代表依赖文件列表中的第一个文件(在这种情况下是 .c 文件),$@ 代表目标文件(在这种情况下是 .o 文件);
  3. main: $(OBJS):这个规则告诉 make,如果有一个 main 目标文件需要更新,并且 $(OBJS) 中的所有文件都已经更新,则执行下面的命令;
  4. $(CC) $^ -o $@:这个命令使用 $(CC) 变量(通常是 cc 或 gcc)来链接所有的 .o 文件,并生成可执行文件 main。$^ 是一个特殊变量,它代表依赖文件列表中的所有文件,$@ 代表目标文件(在这种情况下是 main 可执行文件);
  5. c.o: c.c:这个规则是特殊的,因为它没有命令。它告诉 make,c.o 文件依赖于 c.c 文件。如果 c.c 文件已经更新,则 c.o 文件也需要更新;
  6. clean:这个规则没有目标文件,只有命令。它的命令是 rm -f *.o,它会删除所有的 .o 文件。

5. 八股文

5.1 Linux 基础

5.1.1 Linux 开机启动过程

  1. 主机加电自检,加载 BIOS 硬件信息;
  2. 读取 MBR 的引导文件(GRUB、LILO);
  3. 引导 Linux 内核;
  4. 运行第一个进程 init (进程号永远为 1);
  5. 进入相应的运行级别;
  6. 运行终端,输入用户名和密码。

5.1.2 Bash & DOS

BASH 和 DOS 控制台都是用于在命令行界面中控制计算机的工具,但是它们用于不同的操作系统。BASH 通常用于 Unix 和 Linux 操作系统,而 DOS控 制台则用于微软 Windows 操作系统。

  1. BASH 命令区分大小写,而 DOS 命令则不区分;
  2. 在 BASH 下,/ character 是目录分隔符,\ 作为转义字符。在 DOS下,/ 用作命令 参数分隔符,\ 是目录分隔符;
  3. DOS 遵循命名文件中的约定,即 8 个字符的文件名后跟一个点,扩展名为 3 个字 符。BASH 没有遵循这样的惯例。

5.1.3 CLI

命令行界面(command-line interface),通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(CUI)。

5.2 Linux 间的通信

  1. 管道(pipe),流管道(s_pipe)和有名管道(FIFO);
  2. 信号(signal);
  3. 消息队列;
  4. 共享内存;
  5. 信号量;
  6. 套接字(socket)。

管道:速度慢,容量有限,只有父子进程能通讯;

FIFO:任何进程间都能通讯,但速度慢;

消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题;

信号量:不能传递复杂消息,只能用来同步;

共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。

5.2.1 管道

管道这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

流管道 s_pipe:去除了第一种限制,可以双向传输;

管道可用于具有亲缘关系进程间的通信,命名管道:name_pipe 克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

5.2.2 信号量

信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持 Unix 早期信号语义函数 sigal 外,还支持语义符合 Posix.1 标准的信号函数 sigaction(实际上,该函数是基于 BSD 的,BSD 为了实现可靠信号机制,又能够统一对外接口,用 sigaction 函数重新实现了 signal 函数)。

5.2.3 消息队列

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

消息队列是消息的链接表,包括 Posix 消息队列 system V 消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5.2.4 信号

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

主要作为进程间以及同一进程不同线程之间的同步手段。

5.2.5 共享内存

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

使得多个进程可以访问同一块内存空间,是最快的可用 IPC 形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

5.2.5 套接字

套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由 Unix 系统的 BSD 分支开发出来的,但现在一般可以移植到其它类 Unix 系统上:Linux 和 System V 的变种都支持套接字。

参考文章

  1. 【面试-八股文】Linux高频面试题
  2. GNU - 维基百科
  3. GNU 是什么,和 Linux 是什么关系
  4. 磁盘的结构(盘片、磁道、扇区、柱面)
  5. Docker 创建 docker 用户组
  6. 鸟哥的 Linux 私房菜
  7. Makefile 由浅入深—教程、干货
  8. 初识脚本编程 (archlinuxstudio.github.io)
  9. Linux 进程间通信的几种方式总结
  10. 面试必备(背)-Linux八股文系列
  11. Linux 基础知识总结

文章作者: 陈鑫扬
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陈鑫扬 !
评论
  目录