文件、目录

显示文本文件

cat

  • cat:显示文本文件内容
  • zcat:查看压缩文件内容

more

  • more:单向分页显示文本文件
  • zmore:单向分页显式压缩文本文件

less

  • less:双向分页显示文本文件内容
  • zless:双向分页显式压缩文件内容

head:显示文件指定前若干行

tail

tail:实现文件指定后若干行

nl

nl:显示文件行号、内容

文件处理

sort

对文件中数据排序

uniq

删除文件中重复行

cut

从文件的每行中输出之一的字节、字符、字段

diff

逐行比较两个文本文件

diff3

逐行比较三个文件

cmp

按字节比较两个文件

tr

从标准输入中替换、缩减、删除字符

split

将输入文件分割成固定大小的块

tee

将标准输入复制到指定温婉

expand

将文件中tab转换为空格输出到标准输出

1
$ expand -n 4 file_name

nano

awk

一门模式匹配的编程语言

  • 主要功能是匹配文本并处理
  • 同时还有一些编程语言才有的语法:函数、分支循环语句、变量 等等
  • 使用awk可以
    • 将文本文件视为字段、记录组成的文本数据库
    • 操作文本数据库时能够使用变量
    • 能够使用数学运算和字符串操作
    • 能够使用常见地编程结构,如:条件、分支循环
    • 能够格式化输出
    • 能够自定以函数
    • 能够在awk脚本中执行linux命令
    • 能够处理linux命令的输出结果

命令行语法

1
2
$ awk [-F ERE] [-v assignment] ... program [argument...]
$ awk [-F ERE] -f progfile ... [-v assignment] ... [argument ...]

sed

sed:非交互式、面向字符流的编辑器

  • sed也是默认从stdin读取输入、输出至stdout,除非 参数filename被指定,会从指定文件获取输入,但是注意 sed是面向字符流的编辑器,所以输入、输出文件不能是同一个

  • sed按行处理文本数据,每次处理一行在行尾添加换行符

1
$ sed [-hnV] [-e<script>][-f<script-file>][infile]

参数

  • -e<script>/--expression=<script>:以指定script 处理infile(默认参数)

    • 默认不带参数即为-e
  • -f<script-file>/--file=<script-file>:以指定的script 文件处理输入文本文件

    • 文件内容为sed的动作
  • -i:直接修改原文件

  • -n/--quiet:仅显示script处理后结果

  • -h/--help:帮助

  • -V/--version:版本信息

动作

  • [n]a\string:行添加,在n行后添加新行string
  • [n]i\string:行插入
  • [n]c\string:行替换
  • [n,m]d:删除,删除n-m
  • [start[,end]]p:打印数据
  • [start[,end]]s/expr/ctt[/g]:正则替换

高级语法

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ sed '2anewline' ka.file
$ sed '2a newline' ka.file
$ sed 2anewline ka.file
$ sed 2a newline ka.file
# 在第2行添加新行`newline`

$ sed 2,$d ka.file
# 删除2至最后行

$ sed 2s/old/new ka.file
# 替换第2行的`old`为`new`

$ nl ka.file | sed 7,9p
# 打印7-9行

$ sed ":a;N;s/\n//g;ta" a.txt
# 替换换行符

查找字符串、文件

grep

查找符合条件的字符串

egrep

在每个文件或标准输入中查找模式

find

列出文件系统内符合条件的文件

whereis

插卡指定文件、命令、手册页位置

whatis

在whatis数据库中搜索特定命令

which

显示可执行命令路径

type

输出命令信息

  • 可以用于判断命令是否为内置命令

CentOS7 常用配置

网络配置

编辑/etc/sysconfig/network-scripts/ifcfg-ens33

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TYPE = Ethernet				# 网卡类型:以太网
PROXY_METHOD=none # 代理方式:无
BROWSER_ONLY=no # 仅浏览器:否
BOOTPROTO=dhcp # 网卡引导协议
DEFROUTE=yes # 默认路由:是
IPV4_FAILURE_FATAL=no # 开启IPV4致命错误检测:否
IPV6INIT=yes # IPV6自动初始化:是
IPV6_AUTOCONF=yes # IPV6自动配置:是
IPV6_DEFROUTE=yes # IPV6是否可为默认路由:是
IPV6_FAILURE_FATAL=no # 开启IPV6致命错误检测:否
IPV6_ADDR_GEN_MODE=stable-privacy
# IPV6地地址生成模型:stable-privacy
NAME=ens33 # 网卡物理设备名称
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# 通用唯一识别码
DEVICE=ens33 # 网卡设备名称
ONBOOT=yes # 开启启动:是
DNS1=xxx.xxx.xxx.xxx # DNS地址
IPADDR=xxx.xxx.xxx.xxx # IP地址
PREFIX=24 # 子网掩码
GATEWAY=xxx.xxx.xxx.xxx # 网关
  • UUID不能相同相同
  • ifcfg-ens33这个文件感觉像是个模板,但是不知道真正应用 配置文件在哪

常用应用源

EPEL

Extra Packages for Enterprise Linux 由Fedora社区创建、维护的RPM仓库,通常不会与官方源发生冲突 或相互替换文件,包括应用有:chromium

直接使用yum安装:$ sudo yum install epel-release

RPMFusion

提供Fedora和RedHat由于开源协议或者是禁止商业用途而无法提供 RPM安装包,包括两个仓库 和NuxDextop源有冲突,如:gstreamer,感觉上比NuxDextop更加权威 包含应用:mplayer、gstreamer-pluginsXXXX、

  • free:开源软件但是由于其他原因无法提供,安装方式 $>sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm

  • nonfree:闭源软件,包括不能用于商业用途,安装方式 $>sudo rpm -ivh https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-7.noarch.rpm

ELRepo

包含和硬件相关的驱动程序,通过以下命令安装

$>rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
$>rpm -Uvh http://www.elrepo.org/elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

NuxDextop

包含与多媒体相关应用的RPM仓库,好像是个人作者维护,有的依赖 可能在EPEL源中,因此可能需要先安装EPEL,可能和其他源 (RPMFusion)有冲突,可以设置默认情况下不启用,即修改 /etc/yum.repos.d/nux.dextop.repo文件,设置enable=0, 开启时手动启用 $>yum --enablerepo=nux-dextop install PACKAGENAME

$>rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm

装机必备

rhytmbox-mp3

centos7的gnome默认安装rhythmbox,但无法解码mp3,需要安装 rpmfusion-free源中的

  • gstreamer-plugins-ugly.x86_64
  • gstreamer1-plugins-ugly.x86_64

chromium

  • 安装EPEL源之后直接安装

  • flash插件

    ppapi好像就是pepperapi的简称,但是两个flash插件不一样, 安装的是pkgs上下载的,fedora社区维护的

  • html5视频播放支持:ffmpeg-libs google准备不再支持h.264格式(绝大部分)的视频,所以装 了这个还需要其他设置,但firefox可播放大部分html5视频

wqy中文字体

yum源里的字体文件都是*.ttc文件,需要*ttf字体文件,有 在线解压网站可以解压

安装包常识

  • app和app-devel/app-dev:后者包括头文件、链接库,在编译 使用了app的源代码才需要

系统配置

文件目录常识

  • /usr/share/applications里*.desktop是“桌面图标”文件, centos会 菜单中的会展示的“应用”就是这些

Linux 安装后常用配置

用户设置

设置 root 密码

  • Linux 安装之初,在设置 root 密码之前无法使用 $ su 切换到 root 用户,需要先设置root用户密码

    1
    $ sudo passwd root

应用设置

Debian

配置文件

  • Debian 源配置文件:/etc/apt/source.list
  • 修改完成后运行 $ sudo apt update 更新索引
  • 163 源:https://mirrors.163.com/.help/debian.html

    1
    2
    3
    4
    5
    6
    7
    8
    deb http://mirrors.163.com/debian/ <VERSION> main non-free contrib
    deb http://mirrors.163.com/debian/ <VERSION>-updates main non-free contrib
    deb http://mirrors.163.com/debian/ <VERSION>-backports main non-free contrib
    deb-src http://mirrors.163.com/debian/ <VERSION> main non-free contrib
    deb-src http://mirrors.163.com/debian/ <VERSION>-updates main non-free contrib
    deb-src http://mirrors.163.com/debian/ <VERSION>-backports main non-free contrib
    deb http://mirrors.163.com/debian-security/ <VERSION>/updates main non-free contrib
    deb-src http://mirrors.163.com/debian-security/ <VERSION>/updates main non-free contrib
  • USTC 源:https://mirrors.ustc.edu.cn/help/debian.html

    1
    2
    3
    4
    5
    6
    7
    8
    deb http://mirrors.ustc.edu.cn/debian/ <VERSION> main contrib non-free
    deb-src http://mirrors.ustc.edu.cn/debian/ <VERSION> main contrib non-free
    deb http://mirrors.ustc.edu.cn/debian/ <VERSION>-updates main contrib non-free
    deb-src http://mirrors.ustc.edu.cn/debian/ <VERSION>-updates main contrib non-free
    deb http://mirrors.ustc.edu.cn/debian/ <VERSION>-backports main contrib non-free
    deb-src http://mirrors.ustc.edu.cn/debian/ <VERSION>-backports main contrib non-free
    deb http://mirrors.ustc.edu.cn/debian-security/ <VERSION>/updates main contrib non-free
    deb-src http://mirrors.ustc.edu.cn/debian-security/ <VERSION>/updates main contrib non-free
  • debian 的版本名,根据版本改变
  • 一般的,直接将默认配置文件中 http://deb.debian.org 修改为相应源地址即可:$ sudo sed -i 's/deb.debian.org/<mirror_addr>/g' /etc/apt/sources.list

openSUSE

  • openSUSE 使用 MirrorBrain 技术,中央服务器会按照 IP 中转下载请求到附近的镜像,所以更改软件源通常只会加快刷新软件元的速度,对下载速度影响不大

命令行

  • USTC 源:https://mirrors.ustc.edu.cn/help/opensuse.html

    1
    2
    3
    4
    5
    6
    7
    8
    # 禁用原有软件源
    $ sudo zypper mr -da
    $ sudo zypper ar -fcg https://mirrors.ustc.edu.cn/opensuse/distribution/leap/\$releasever/repo/oss USTC:OSS
    $ sudo zypper ar -fcg https://mirrors.ustc.edu.cn/opensuse/distribution/leap/\$releasever/repo/non-oss USTC:NON-OSS
    $ sudo zypper ar -fcg https://mirrors.ustc.edu.cn/opensuse/update/leap/\$releasever/oss USTC:UPDATE-OSS
    $ sudo zypper ar -fcg https://mirrors.ustc.edu.cn/opensuse/update/leap/\$releasever/non-oss USTC:UPDATE-NON-OSS
    # 15.3 或更高版本需要
    $ sudo zypper ar -fgc https://mirrors.ustc.edu.cn/opensuse/update/leap/\$releasever/sle USTC:UPDATE-SLE
  • $releaseverOpenSuSe leap 版本,若知晓可以自行替换

配置文件

  • openSUSE 源配置文件夹:/etc/zypp/repo.d
  • 配置文件格式

    1
    2
    3
    4
    5
    [<ALIAS>]			# 源别名
    enabled=1 # 默认是否启用
    autorefresh=0
    baseurl=url # 源地址
    type=rpm-md

CentOS

  • 发行版中 yum 一般自带 fast-mirrors 插件,一般无需更新官方源

三方源配置

  • Extra Packages for Enterprise Linux:由 Fedora 社区创建、维护的 RPM 仓库,通常不会与官方源发生冲突或相互替换文件

    • 安装 EPEL$ sudo yum install epel-release
    • 包括应用有
      • Chromium
  • RPMFusion:提供 FedoraRedHat 由于开源协议或者是禁止商业用途而无法提供 RPM 安装包

    • 包括两个仓库freenofree
      • free:开源软件但是由于其他原因无法提供
      • non-free:闭源软件,包括不能用于商业用途
    • 包含应用有
      • mplayer
      • gstreamer-pluginsXXXX
  • ELRepo:包含和硬件相关的驱动程序

    • 安装
      1
      2
      $ rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
      $ rpm -Uvh http://www.elrepo.org/elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
  • NuxDextop:包含与多媒体相关应用的 RPM 仓库

    • 安装
      1
      $ rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
    • 说明
      • 有的依赖在 EPEL 中,因此可能需要先安装 EPEL
      • 和其他源有(RPMFusion)冲突时
        • 可以设置默认情况下不启用,即修改 /etc/yum.repos.d/nux.dextop.repo 文件,设置 enable=0
        • 需要时手动启用:$ yum --enablerepo=nux-dextop install <PACKAGENAME>

应用安装方式

  • 包管理器

    • 安装应用比较方便
    • 但某些发行版中应用源更新缓慢
  • 自行下载二进制版本安装

    • Linux 大部分应用是 noarch,即与架构无关,无需考虑兼容问题
  • 下载源码编译安装

    • 安装流程
      • 查询文档安装编译依赖
      • ./configure配置编译选择,如:安装路径等
      • make & make install
  • 注意事项

    • 自行安装应用可以若设置安装路径不是推荐路径,记得检查环境变量 XXXX_HOME
    • 应用文件夹通常带有版本号,建议
      • 保留文件夹版本号
      • 另行创建无版本号符号链接指向所需版本文件夹

本地化

字体

  • 终端中字体需要为 monospace
    • 在多语言环境下,非 monospace 字体字符宽度不同,导致字符重叠
    • 字体名称不是字体文件名,其定义在字体文件内部定义
      • 指定未安装字体只能通过文件名
      • 指定已安装字体可直接使用字体名称

Locale

Locale:特定于某个国家、地区的编码设定

  • 代码页
  • 数字、货币、时间与日期格式

Linux文件系统设计

文件系统

磁盘存储

  • Sector 扇区:扇区概念来源于机械硬盘,指磁盘上各磁道上的扇环

    • 物理扇区:磁盘读写的最小单位
      • 早期硬盘多为 512B 大小,新式硬盘多为 4096B 或更高以提高数据记录密度
    • 逻辑扇区:硬盘可以接受读写指令的最小操作单元
      • 为兼容性而设计,硬盘内部将物理扇区逻辑上划分多个 512B 扇区片段提供给文件系统
      • 实际读写时由硬盘固件负责逻辑扇区、物理扇区间转换,对文件系统透明
  • Logical Block Address (逻辑)块/簇:多个扇区组成的操作系统最小读写单位

    • 常见的块大小为 4KB,一般为扇区 $2^N$ 倍
    • 特点
      • 提高了读写效率
      • 减少文件碎片
      • 造成一定空间浪费
  • 分区:从磁盘上划分出了的连续的扇区

    • 分区格式化:对分区范围内扇区使用进行规划
      • 引导分区设置
      • 扇区分组为块、编号
    • 分区对齐:将逻辑块对齐到磁盘物理扇区

      • 分区格式化是按照逻辑扇区划分,若分区中存储起始位置没有对齐至物理扇区边缘,则分区中块也无法对齐到物理扇区边缘
        • 分区未对齐更多是因为引导区占用扇区数不是物理扇区整数倍,导致之后存储区域无法对齐
        • 则分区一般是将分区起始位置对齐到物理扇区整数倍处即可
      • 分区未对齐时,操作系统每次读、写会多读、写一个物理扇区,降低磁盘性能
      • 4K 对齐:多数磁盘物理扇区大小为 4K,实际对齐需检查物理扇区大小

      disk_partition_alignment_unaligned disk_partition_alignment_aligned

  • 固态存储同样有扇区概念,但还有页等概念,更加复杂
  • 磁盘存储区域
    • 超级块:存储文件系统的详细信息,文件系统挂载时载入内存
      • 块数量
      • 块大小
      • 空闲块
    • 索引节点区:存储索引节点,文件被访问时载入内存
    • 数据块区:存储文件、目录数据

文件

  • Linux 系统中:一切皆文件
    • 普通的文件
    • 目录(不是目录项)
      • 同样用 Inode 唯一标识
      • 其中存储的内容是子目录、文件
    • 块设备
    • 管道
    • Socket

linux_file_system_structure

文件读写

linux_file_usage

  • 操作系统会跟踪进程打开的所有文件,即为每个进程维护一个打开文件表

    • 文件表中每项代表文件描述符(open 函数返回)
      • 即文件描述符是打开文件的标识
    • 文件表中维护打开文件状态、信息
      • 文件指针:系统跟踪上次读写位置作为当前文件位置指针,对某进程唯一
      • 文件打开计数器:跟踪文件打开、关闭数量,仅计数为 0 时关闭文件,删除条目
      • 文件磁盘位置:避免磁盘交互
      • 访问权限(访问模式):方便操作系统允许、拒绝 I/O 请求
  • 文件系统屏蔽了用户字节操作和磁盘数据块操作之间差异

    • 用户读取 1byte 数据时,文件系统获取所在块,返回所需部分
    • 用户写入 1byte 数据时,文件系统获取应写入的块,修改后写回

文件在磁盘中的存储

  • 连续空间存储方式:文件存储在磁盘连续的物理空间中

    • 需预先知道待存储的文件大小:Inode 中需包含文件起始块位置、长度
    • 特点
      • 读写效率高
      • 磁盘空间碎片化
      • 文件长度不易扩展
  • 非连续空间存放方式:链表方式

    • 隐式链表:文件头中包含首块、尾块位置,各数据块中包含指向下个数据块的指针
      • 无法直接访问数据块:只能通过指针顺序访问
      • 稳定性差:任何指针丢失、损坏都会导致文件数据损坏
    • 显式链表:将链接各数据块的指针显式存储在链接表(File Allocation Table)中(改进隐式链表问题)

      linux_file_system_storage_explicit_link

      • 链接表为每个磁盘设置一张,其中每项表示一个数据块,内容为下个数据块指针
      • 链接表可存放在内存中以提高检索速度,但会占用内存空间,也因此不适合大磁盘
      • 长期使用会使文件资料逐渐分散
    • 特点
      • 可消除磁盘碎片
      • 文件长度可动态扩展
      • 存储指针的有额外空间开销
  • 索引存储方式:为每个文件创建索引数据块,存储指向文件数据块的指针列表

    linux_file_system_storage_index

    • 文件头中需包含指向索引数据块的指针
    • 特点

      • 文件创建、增大、缩小方便
      • 无磁盘碎片问题
      • 支持顺序读写、随机读写
      • 存储索引有额外空间开销
      • 文件系统效率与查找策略高度相关
    • 大文件支持扩展

      • 链式索引块:链表链接索引

        linux_file_system_storage_link_index

      • 多级索引块:索引索引索引

        linux_file_system_storage_index_index

文件系统

文件系统:操作系统用于明确存储设备或分区上文件的方法和数据结构

  • 文件系统即在存储设备上组织文件(持久数据)的子系统

    • 基本数据单位是文件,对文件的组织方式的不同即形成不同的文件系统
    • 存储设备:磁盘、光盘、网络存储、虚拟数据
      • 存储设备可以包含多个文件系统
  • Virtual File System 虚拟文件系统:包含一组所有文件系统都支持的数据结构和标准接口

    • 作为提供给用户的统一接口

    linux_virtual_file_system_structure

  • 操作系统中负责管理、存储文件信息的软件称为文件管理系统,简称文件系统

文件系统分类

  • 磁盘文件系统:数据存储在磁盘中

    • EXT2
      • Linux 的正宗文件系统,早期常用
      • 支持 undelete,误删文件可恢复,但是操作比较麻烦
    • EXT3
      • EXT2 发展而来
      • 支持大文件
      • 不支持反删除,RedhatFedora 推荐
    • ReiserFS
      • 支持大文件
      • 支持反删除,操作简单
  • 内存文件系统:数据存储在内存中

    • 如:/proc/sys 等文件系统
    • 读写此类文件实际上是读写内核中相关数据
  • 网络文件系统:访问其他计算机主机数据的文件系统

    • NFS
    • SMB
FileSystem File Size Limit Filesystem Size Limit
ext2/ext3 with 1KB blocksize 16448MB 2048GB
ext2/ext3 with 2KB blocksize 256GB 8192GB
ext2/ext3 with 4KB blocksize 2048GB 8192GB
ext2/ext3 with 8KB blocksize 65568GB 32TB
ReiserFS3.5 2GB 16TB
ReiserFS3.6 1EB 16TB
XFS 8EB 8EB
JFS with 512B blocksize 8EB 512TB
JFS with 4KB blocksize 8EB 4PB
NFSv2(client side) 2GB 8EB
NFSv3(client side) 8EB 8EB
  • 文件系统要先挂在在某个目录上才能正常使用,Linux 会把文件系统挂载到根目录

File Allocation Table

  • FAT 文件分配表:采用 FAT 显式链表组织文件的文件系统

    • 简单,几乎所有的个人操作系统均支持,适合移动介质的文件系统
    • FAT12:12bits 寻址长度
    • FAT16:16bits 寻址长度
    • FAT32:目前只使用了其中 28bits 寻址长度
      • 按 4KB 块计算,FAT32 最大容量为 1TB
      • FAT32 起始扇区中记录有扇区总数,此限制 FAT32 最大容量 2TB
      • FAT32 最大支持 4GB 大小文件
  • Extended File Allocation Table

    • 优点
      • 允许更大分区容量、更大文件、更大逻辑块大小
      • 采用空余空间寻址,空间分配、删除性能得以改进
    • 缺点
      • 兼容性不如 FAT32:旧设备、UEFI 不支持

权限设计

  • r:读文件
  • w:修改、删除文件
  • x:可以执行文件
  • s:强制位权限(固化用户/组权限)
    • set-user-id:user执行权限位出现
    • set-group-id:group执行权限位出现
  • t:粘滞位权限(在swap中停留)

权限判断规则

  • linux中权限是根据user-idgroup-id判断用户和资源 关系,然后选择相应的类型用户(user、group、other)权限组 判断是否有相应权限

  • 需要注意的是,访问资源实际上不是用户,而是用户开启的 进程,所以这里涉及了4中不同的用户标识

    • real-user-id:UID,用户id

    • real-group-id:GID,用户默认组id

    • effective-user-id:是针对进程(可执行文件)而言, 指内核真正用于判断进程权限的user-id

    • effective-group-id:同effective-user-id,内核 判断真正判断进程权限的group-id

  • 一般情况下effective-user-id就是read-user-id,即启动 进程用户的UID,所以一般来说用户创建的进程的对资源访问 权限就是就是自身权限

可执行文件权限

  • r:读文件
  • w:写文件
  • x:执行文件

s权限

当可执行文件具有set-user-id权限时

  • 其他用户执行该文件启动的进程的effective-user-id不再是 real-user-id,即和执行用户的UID不再一致,而是用户属主 的UID

  • 内核根据进程effective-user-id判断进程权限,进程的权限 实际上同属主的权限,而不是执行用户权限

  • 这样用户就在执行这种可执行文件,暂时拥有该可执行文件属主 执行该可执行文件权限,否则可能由于进程访问其他资源原因 无法正常执行

  • 可看作是将属主的部分权限(在该文件上涉及到的权限) 固化在文件上

set-group-id类似的可以看作是将属主默认组权限固化在文件上

t权限

  • 文件被执行时,文本段会被加载到swap中,程序结束后仍然 保留在swap中

  • 下次执行文件时,文本段直接从swap中加载,因为swap为连续 block,加载速度会提高

目录权限说明

linux中目录是一种特殊的文件,其包含目录下所有文件(包括 子目录)的文件名、i-node号

  • r:列出目录下所有文件

  • w:增加、删除、重命名目录下的文件

  • x:可以是(搜索)路径的一部分

    • 即必须对要访问的文件中路径中所有目录都有执行权限
    • 可以将目录执行权限看作是过境证明
  • s:好像没啥用

  • t:用户只能增加、删除、重命名目录下属于自己文件

    • w权限补充,否则用户拥有目录w权限则可以操作目录 下所有文件
    • /home目录权限就是1777,设置有粘滞位权限

权限掩码

文件/目录默认权限 = 现有权限(0777减去权限掩码

  • 权限掩码设置参见linux/shell/cmd_fslinux/shell/config_file

进程、线程、作业

Linux进程、线程

进程发展

  • Linux2.2内核

    • 进程通过系统调用fork()创建,新进程是原进程子进程
    • 不存在真正意义上的线程
    • 只默认允许4096个进程/线程同时运行
  • Linux2.4内核

    • 运行系统运行中动态调整进程数上限,进程数仅受制于物理 内存大小,最大16000
  • Linux2.6内核

    • 进程调度重新编写,引入slab分配器动态生成 task_struct
    • 最大进程数量提升至10亿
    • 线程框架重写
      • 引入tgid、线程组、线程各自的本地存储区
      • 得以支持NPTL线程库

线程/轻量级进程

  • Linux未真正实现、区分线程,在内核层面是特殊进程,是 “真正的轻量级进程”

    • “线程”和“进程”共享
      • 相同调度策略,处于同一调度层次
      • 相同数据结构进程标识符,位于相同进程标识符空间
    • “线程”与“进程”的区别在于
      • 线程没有独立的存储空间
  • 多线程即创建多个进程并分配相应的进程描述符 task_struct、指定其共享某些资源

    • 创建线程不会复制某些内存空间,较进程创建快
    • 在专门线程支持系统多线程中,系统会创建包含指向所有 线程的进程描述符,各线程再描述独占资源
  • 尽管Linux支持轻量级进程,但不能说其支持核心级线程

    • 则不可能在Linux上实现完全意义上的POSIX线程机制
    • 所以Linux线程库只能尽可能实现POSIX绝大部分语义,尽量 逼近功能
  • 线程在进程内共享某些资源

    • 打开的文件
    • 文件系统信息
    • 地址空间
    • 信号处理函数
  • 这里讨论的线程都是内核线程,即内核可感知、调度线程,不 包括程序自建线程

内核守护线程

kthreads pthreads
资源 无用户空间 共享完整虚拟寻址空间
状态 只工作在内核态 可在内核态、用户态之间切换
目的 维护内核正常工作 用户分配任务

内核守护线程:内核为维护正常运行创建、仅工作在内核态线程

  • 按作用可以分类

    • 周期性间隔运行,检测特定资源的使用,在用量超出或低于 阈值时采取行动
    • 启动后一直等待,直到系统调用请求执行某特定操作
  • 执行以下任务

    • 周期性将dirty内存页与页来源块设备同步:bpflush线程
    • 将不频繁使用的内存写入交换区:kswapd线程
    • 管理延时动作:kthreadd线程接手内核守护线程创建
    • 实现文件系统的事务日志
  • 内核守护线程只能工作在内核态

    • 没有用户空间,和内核共用一张内核页表
    • 只能使用大于PAGE_OFFSET部分的虚拟寻址空间,即进程 描述符中current->mm始终为空
    • 对4G主存的X86_32机器,只能使用最后1G,而普通pthreads 可以使用完整虚拟寻址空间
  • 内核守护线程名往往为k开头、d结尾

特殊内核守护线程

  • Linux内核启动的最后阶段,系统会创建两个内核线程
  • init:运行文件系统上一系列init脚本,并启动shell 进程

    • 是所有用户进程的祖先,pid为1
  • kthreadd:内核启动完成之后接手内核守护线程的创建

    • 内核正常工作时永不退出,是死循环,pid为2
    • 载入内核模块时即需要调用其创建新内核守护线程

进程状态

1
2
3
4
5
6
7
8
// <kernel/include/linux/sched.h>
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
#define EXIT_ZOMBIE 16
#define TASK_DEAD 64

process_status

  • 状态虽然有很多种,但总是TASK_RUNNING -> 非, 即使进程在TASK_INTERRUPTIBLE状态被kill,也需要先唤醒 进入TASK_RUNNING状态再响应kill信号进入TASK_DEAD
  • TASK_RUNNING:可执行,正在执行或在运行队列中等待执行

    • 同一时刻可能有多个进程处于可执行态,位于运行队列中 等待进程调度器调度
  • TASK_INTERRUPTIBLE:正在阻塞,等待某些条件达成

    • 条件达成后内核会把进程状态设置为运行
    • 此状态进程也会因为接收到信号而提前唤醒准备运行
    • 系统中大部分进程都此状态
  • TASK_UNINTERRUPTILBE:不响应异步信号,即使接收到信号 也不会被唤醒或准备投入运行

    • 不可中断是指进程不响应异步信号,而不是指CPU不响应 中断
    • 内核某些处理流程是不可被打断的,如:内核和硬件设备 交互被打断会导致设备进入不可控状态,因此需要此状态
  • __TASK_TRACED:被其他进程跟踪

    • 开发中进程停留在断点状态就是此状态,如:通过ptrace 对调试程序进行跟踪
    • 此状态进程只能等待调试进程通过ptrace系统调用执行 PTRACE_CONTPTRACE_DETACH等操作才能恢复到 TASK_RUNNING状态
  • __TASK_STOPPED:停止执行,没有也不能投入运行

    • 通常发生在接收到SIGSTOPSIGSTPSIGTTINSIGTTOU等信号
    • 向此状态进程发送SIGCONT信号可以让其恢复到 TASK_RUNNING状态
  • TASK_DEAD:退出状态,即将被销毁

  • EXIT_ZOMBIE/TASK_ZOMBIE:进程已结束但task_struct未 注销

    • 进程退出过程中处于TASK_DEAD状态,进程占有的资源将 被回收,但父进程可能会关心进程的信息,所以 task_struct未被销毁

内核态、用户态

  • 系统设计角度:为不同的操作赋予不同的执行等级,与系统相关 的特别关键的操作必须有最高特权程序来完成

    • 运行于用户态:进程可执行操作、可访问资源受到限制
    • 运行于内核态:进程可执行任何操作、使用资源无限制
  • 内存使用角度(虚拟寻址空间,X86_32位系统,最大4GB主存)

    • 内核空间:最高的1G,所有进程共享
      • 包含系统堆栈:2页面,即8K内存,低地址中存放 task_struct
      • 进程运行于内核空间时使用系统堆栈、处于内核态
    • 用户空间:剩余3G
      • 包含用户堆栈
      • 进程运行于用户空间时使用用户堆栈、处于用户态

    virtual_address_space

  • 内核态的逻辑

    • 进程功能和内核密切相关,进程需要进入内核态才能实现 功能
    • 应用程序在内核空间运行、内核运行于进程上下文、陷入 内核空间,这种交互方式是程序基本行为方式
  • 用户态进入内核态的方式

    • 系统调用,如:printf函数中就是调用write函数
    • 软中断,如:系统发生异常
    • 硬件中断,通常是外部设备的中断

    process_calling_structure

  • 进程或者CPU在任何指定时间点上活动必然为

    • 运行于用户空间,执行用户进程
    • 运行于内核空间,处于进程上下文,代表某特定进程执行
    • 运行于内核空间,处于中断上下文,与任何进程无关,处理 特点中断

Linux进程数据结构

task_struct

1
2
3
4
5
6
7
8
9
10
11
12
13
// <kernel/include/linux/sched.h>
struct task_struct{
volatile long state; // -1:不可运行,0:可运行,>0:已中断
int lock_depth; // 锁深度
unsigned int policy; // 调度策略:FIFO,RR,CFS
pid_t pid; // 线程ID
pid_t tgid; // 线程组ID,2.6内核中引入
struct task_struct *parent; // 父进程
struct list_head children; // 子进程
struct list_head sibling; // 兄弟进程
struct task_struct *group_leader;
struct list_head thread_group;
}

task_struct

  • 内核使用任务队列(双向循环链表)维护进程(描述符)

  • task_struct:进程描述符,包含进程的所有信息,包括

    • 进程状态
    • 打开的文件
    • 挂起信号
    • 父子进程

ID

  • pid:字面意思为process id,但逻辑上为线程ID
  • tgid:字面意思为thread group id,但逻辑上为 进程ID
1
2
3
4
5
6
7
// <kernel/timer.c>
asmlinkage long sys_getpid(void){
return current->tgid;
}
asmlinakge long sys_gettid(void){
return current->pid;
}

线程关系

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
// <kernel/fork.c>
copy_process{
// some code
p->tgid = p->pid;

// 创建线程时
if (clone_flags & CLONE_THREAD)
// 从父进程获取`tgid`,归属同一线程组
p->tgid = current->tgid;

// some code
// 初始化`group_leader`、`thread_group`
p->group_leader = p;
INIT_LIST_HEAD(&p->thread_group);

// some code

// 创建线程时
if (clone_flags & CLONE_THREAD){
// `group_leader`设置为父进程`group_leader`
// 即保证`group_leader`指向首个线程`task_struct`
p->group_leader = current->group_leader;
// 通过`thread_group`字段挂到首个线程的`thread_group`队列中
list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);

// some code
}

if(likely(p->pid)){
// some code
// 仅首个线程才会通过`tasks`字段挂入`init_task`队列中
if(thread_group_leader(p)){
//...
list_add_tail_rcu(&p->tasks, &init_task, tasks);
}
}
}

线程组退出

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
52
53
54
55
56
57
// <kernel/exit.c>
NORET_TYPE void do_group_exit(int exit_code){
BUG_ON(exit_code & 0x80);

// `current->signal`由线程组中所有线程共享
// 若调用此方法线程`SIGNAL_GROUP_EXIT`标志已被设置,说明
// 其他线程已经执行过此方法,已通知线程组中所有线程
// 退出,则可以直接执行`do_exit`
if(current->signal->flags & SIGNAL_GROUP_EXIT)
exit_code = current->signal->group_exit_code;

// 否则通知线程组中所有线程退出
else if(!thread_gropu_empty(current)){
struct signal_struct * const sig = current->signal;
struct sighand_struct * const sighand = current->sighand;
spin_lock_irq(&sighand->siglock);

// another thread got here before we took the lock
if(sig->flags & SIGNAL_GROUP_EXIT)
exit_code = sig->group_exit_code;
else{
sig->group_exit_code = exit_code;
zap_other_threads(current);
}
spin_unlock_irq(&sighand->sigloc);
}

do_exit(exit_code);
}

// <kernel/signal.c>
void zap_other_threads(struct task_struct *p){
struct task_struct *t;

// 设置`SIGNAL_GROUP_EXTI`标志
p->signal->flags = SIGNAL_GROUP_EXIT;
p->signal->group_stop_count = 0;

if(thread_group_empty(p))
return;

for (t=next_thread(p); t != p; t=next_thread(t)){
// don't bohter with already dead threads
if (t->exit_state)
continue;

// 为每个线程设置`SIGKILL`信号
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
}
}

// <include/linux/sched.h>
static inline struct task_struct *next_thread(const struct task_struct *p){
return list_entry(rcu_dereference(p->thread_group.next),
struct task_struct, thread_group);
}

Slab分配器

process_slab

  • slab分配器把不同对象类型划分为不同高速缓存组,如: task_structinode分别存放

    • 高速缓存又会被划分为slab
    • slab由一个或多个物理上连续的页组成
  • 申请数据结构时

    • 先从半满的slabs_partial中申请
    • 若没有半满,就从空的slabs_empty中申请,直至填满 所有
    • 最后申请新的空slab
  • slab分配器策略优点

    • 减少频繁的内存申请和内存释放的内存碎片
    • 由于缓存,分配和释放迅速

thread_info

1
2
3
4
5
6
7
8
9
10
// <asm/thread_info.h>
struct thread_info{
struct task_struct *task;
struct exec_domain *exec_domain;
usigned long flags;
__u32 cpu;
int preempt_count;
mm_segment_t addr_limit;
struct restart_block restart_block;
}
  • 内核中对进程操作都需要获得进程描述符task_struct指针, 所以获取速度非常重要
    • 寄存器富余的体系会拿出专门的寄存器存放当前 task_struct的指针
    • 寄存器不富余的体系只能在栈尾创建thread_info结构, 通过计算间接查找

进程创建

  • 继承于Unix,Linux进程创建使用两个函数分别完成,其他如Win 可能都是通过一个方法完成
  • fork函数:拷贝当前进程创建子进程

    • 子进程、父进程区别仅在于PID、PPID和少量资源
  • exec函数(族):载入可执行文件至地址空间开始运行

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
SYSCALL_DEFINE0(fork){
return do_fork(SIGCHLD, 0, 0, NULL, NULL);
}
SYSCALL_DEFINE0(vfork){
return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD,
0, 0, NULL, NULL, 0);
}
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr){
return _do_fork(clone_flags, stack_start, stack_size,
parent_tidptr, child_tidptr, 0);
}
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls){
// some code
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls);
// some code
}
  • forkvfork最终都是通过调用_do_fork实现,仅传参 不一致

    • 首个参数为clone_flags,最终被copy_process用于 真正的拷贝执行
  • 通过系统调用clone()创建线程

    • 同创建进程系统调用fork()vfork()一样,最终调用 do_fork方法,但传递和进程创建时不同的flag,指明 需要共享的资源

      1
      CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAND

fork

fork():子进程是父进程的完整副本,复制了父进程的资源, 包括内存内容、task_struct

  • 子进程拷贝父进程的数据段、代码段

    • 同一变量的虚拟地址相同(物理地址不同)
  • 利用copy-on-write优化效率

    • 内核创建子进程时不复制父进程的地址空间,而是只读共享 父进程空间数据
    • 只有子进程需要写数据时才会拷贝到子进程
  • 页表:存放给从逻辑页号到物理页帧/块号地址的映射

Unix傻瓜式进程创建

  • 内核原样复制父进程整个地址空间,并分配给子进程,效率低

    • 为子进程页表分配页帧
    • 为子进程页分配页帧
    • 初始化子进程页表
    • 把父进程页复制到子进程相应页中
  • 大部分情况下复制父进程页无意义

    • 子进程会载入新的程序开始运行
    • 丢弃所继承的地址空间

Copy-on-Write

copy-on-write思想简单:父进程、子进程共享页帧

  • 共享页帧不能被修改,父进程、子进程试图写共享页帧时产生 page_fault异常中断

  • CPU执行异常处理函数do_wp_page()解决此异常

    • 对导致异常中断的页帧取消共享操作
    • 为写进程复制新的物理页帧,使父、子进程各自拥有内容 相同的物理页帧
    • 原页帧仍然为写保护:其他进程试图写入时,内核检查进程 是否是页帧的唯一属主,如果是则将页帧标记为对此进程 可写
  • 异常处理函数返回时,CPU重新执行导致异常的写入操作指令

  • copy-on-write:多个呼叫者同时要求相同资源时,会共同 取得相同指针指向相同资源,直到某个呼叫者尝试修改资源时, 系统才给出private copy,避免被修改资源被直接察觉,此 过程对其他呼叫者transparent

vfork

vfork():子进程直接共享父进程的虚拟地址空间、物理空间

  • vfork被设计用以启动新程序

    • 内核不创建子进程的虚拟寻址空间结构
    • 进程创建后应立即执行exec族系统调用加载新程序,替换 当前进程
    • exec不创建新进程,仅用新程序替换当前进程正文、 数据、堆、栈
  • 在子进程调用exec函数族、_exit()exit()前,子进程 在父进程的地址空间中运行

    • 二者共享数据段,子进程可能破坏父进程数据结构、栈
    • 父进程地址空间被占用,因此内核会保证父进程被阻塞, 即vfork会保证子进程先运行
  • 应确保一旦调用vfork

    • 子进程不应使用return返回调用处,否则父进程又会 vfork子进程
    • 子进程不应依赖父进程进一步动作,否则会导致死锁
    • 子进程需避免改变全局数据
    • 若子进程改变了父进程数据结构就不能调用exit函数

clone

clone:可有选择地继承父进程资源

1
int clone(int (fn)(void), void * child_stack, int flags, void * args);
  • clone通过众多参数有选择地创建进程

    • 创建LWP/线程
    • 创建兄弟进程
    • 类似vfork创建和父进程共享虚拟寻址空间
  • 参数说明

    • fn:函数指针
    • child_stack:为子进程分配的系统堆栈空间
    • flags:描述需要从父进程继承的资源,如下
    • args:传给子进程的参数

Flags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define CSIGNAL		0x000000ff		// signal mask to be setn at exit
#define CLONE_VM 0x00000100 // set if VM shared between process
#define CLONE_FS 0x00000200 // set if fs info shared between processes
#define CLONE_FILES 0x00000400 // set if open files shared between processes
#define CLONE_SIGHAND 0x00000800 // set if signal handlers and blocked signals shared
#define CLONE_PTRACE 0x00002000 // set if we want to let tracing continue on the child too
#define CLONE_VFORK 0x00004000 // set if the parent wants the child to wake it up on mm_release
#define CLONE_PARENT 0x00008000 // set if we want to have the same parent as the cloner
#define CLONE_THREAD 0x00010000 // same thread group?
#define CLONE_NEWS 0x00020000 // new namespace group?
#define CLONE_SYSVSEM 0x00040000 // share system V SEM_UNDO semantics
#define CLONE_SETTLS 0x00080000 // create a new TLS for the child
#define CLONE_PARENT_SETTID 0x00100000 // set the TID in the parent
#define CLONE_CHILD_CLEARTID 0x00200000 // clear TID in the child
#define CLONE_DETEACHED 0x00400000 // unused
#define CLONE_UNTRACED 0x00800000 // set if the tracing process can't force `CLONE_PTRACE` on this clone
#define CLONE_CHILD_SETTID 0x01000000 // set the TID in the child
#define CLONE_STOPPED 0x02000000 // start in stopped state
#define CLONE_NEWUTS 0x04000000 // new utsname group
#define CLONE_NEWIPC 0x08000000 // new ipcs
#define CLONE_NEWUSER 0x10000000 // new user namespace
#define CLONE_NEWPID 0x20000000 // new pid namespace
#define CLONE_NEWNET 0x40000000 // new network namespace
#define CLONE_IO 0x80000000 // clone into context

线程库

  • POSIX标准要求:线程不仅仅是共享资源即可,其需要被视为 整体
    • 查看进程列表时,一组task_struct需要被展示为列表中 一个节点
    • 发送给进程信号,将被一组task_struct共享,并被其中 任意一个线程处理
    • 发送给线程信号,将只被对应task_struct接收、处理
    • 进程被停止、继续时,一组task_struct状态发生改变
    • 进程收到致命信号SIGSEGV,一组task_struct全部退出

LinuxThread线程库

LinuxThread线程库:Linux2.6内核前,pthread线程库对应实现

  • 特点
    • 采用1对1线程模型
    • 通过轻量级进程模拟线程
    • 线程调用由内核完成,其他线程操作同步、取消等由核外 线程库完成
    • 仅通过管理线程实现POSIX以上5点要求中最后一点

管理线程

管理线程:为每个进程构造、负责处理线程相关管理工作

  • 管理线程是主线程的首个子线程

    • 进程首次调用pthread_create创建线程时会创建、启动 管理线程
  • 管理线程负责创建、销毁除主线程外线程,成为LinuxThread 的性能瓶颈

    • 从pipe接收命令创建线程
    • 子线程退出时将收到SIGUSER1信号(clone时指定), 若不是正常退出,则杀死所有子线程并自杀
    • 主循环中不断检查父进程ID,若为1说明原父线程退出并 被托管给init线程,则杀死所有子进程并自杀
  • 通过LWP模拟线程存在的问题

    • LWP不共享进程ID
    • 某些缺省信号难以做到对所有线程有效,如:SIGSTOPSIGCONT无法将整个进程挂起
    • 线程最大数量收到系统总进程数限制
    • 管理线程是性能瓶颈,一旦死亡需要用户手动清理线程、 无人处理线程创建请求
    • 同步效率低,通过复杂的信号处理机制进行同步
    • 与POSIX兼容性差

Naive POSIX Thread Library

NPTL:Linux2.6内核重写线程框架的基础上引入的pthread线程库

  • 本质上还是利用LWP实现线程的1对1线程模型,但结合新的线程 框架实现了POSIX的全部5点要求

    • 线程组tgid引入体现task_struct代表进程还是线程
    • task_struct维护两套signal_pending
      • 线程组共享signal_pending:存放kill发送信号, 任意线程可以处理其中信号
      • 线程独有signal_pending:存放pthread_kill发送 信号,只能由线程自身处理
    • 收到致命信号时,内核会将处理动作施加到线程组/进程中
  • 但也存在一些问题

    • kill未展示的LWP会杀死整个进程
  • RedHat开发,性能远高于LinuxThreads,需要内核支持

Next Generation Posix Threads for Linux

NGPT:基于GNU Portable Threads项目的实现多对多线程模型

  • IBM开发,性能介于LinuxThread、NPTL间,2003年终止开发

Make

Make基础

Make:根据指定的Makefile文件构建新文件

1
2
$ make [-f makefile] [<target>]
# 指定使用某文件中的规则,默认`makefile`/`Makefile`
  • make默认寻找当前目中GNUmakefile/makefile/Makefile 文件作为配置 文件
  • 默认用makefile中首个目标文件作为最终目标文件,否则 使用<target>作为目标文件

Make参数

  • -b/-m:忽略和其他版本make兼容性

  • -B/--always-make:总是更新/重编译所有目标

  • -C <dir>/--directory=<dir>:指定读取makefile的目录, 相当于$ cd <dir> && make

    • 指定多个-C <dir>,make将按次序合并为最终目录
    • -C时,-w选项自动打开
  • --debug[=<options>]:输出make调试信息

    • a:all,输出所有调试信息
    • b:basic,基本信息
    • v:verbose,基本信息之上
    • i:implicit,所有隐含规则
    • j:jobs,执行规则中命令的详细信息,如:PID、返回码
    • m:makefile,make读取、更新、执行makefile的信息
  • -d:等价于--debug=a

  • -e/--environment-overrides:环境变量覆盖makefile中值

  • -f <file>/--file=<file>/--makefile=<file>:指定 makefile

    • 可以多次传递参数-f <filename>,所有makefile合并 传递给make
  • -h/--help:帮助

  • -i/--ignore-errors:忽略执行时所有错误

  • -I <dir>/--include-dir=<dir>:搜索includemakefile 路径

    • 可以多个-I <dir>指定多个目录
  • -j [<jobsum>]/-jobs[=<jobsum>]:指定同时运行命令数

    • 进程数
    • 默认同时运行尽量多命令
    • 多个-j时最后者生效
  • -k/--keep-going:出错不停止运行

    • 若目标生成失败,依赖于其上目标不会继续执行
  • -l <load>/--load-average[=<load>]

  • --max-load[=<load>]:make命令负载

  • -n/--just-print/--dry-run/--recon:仅输出执行 过程中命令序列,不执行

  • -o <file>/--old-file=<file>/--assume-old=<file>: 不重新生成指定的<file>,即使目标依赖其

  • -p/--print-data-base:输出makefile中所有数据:规则、 变量等

    1
    2
    3
    4
    $ make -qp
    # 只想输出信息,不执行makefile
    $ make -p -f /dev/null
    # 查看执行makefile前的预设变量、规则
  • -q/--question:不执行命令、不输出,仅检查指定目标 是否需要更新

    • 0:需要更新
    • 2:有错误发生
  • -r/--no-builtin-rules:禁用make任何隐含规则

  • -R/--no-builtin-variables:禁用make任何作用于变量上 的隐含规则

  • -s/--silent/quiet:禁止显示所有命令输出

  • -S/--no-keep-going/--stop:取消-k作用

  • -t/--touch:修改目标日期为最新,即组织生成目标的命令 执行

  • -v/--version:版本

  • -w/--print-directory:输出运行makefile之前、之后信息

    • 对跟踪嵌套式调用make有用
  • --no-print-directory:禁止-w选项

  • -W <file>/--what-if=<file>/--new-file=<file>/--assume-file=<file>

    • 联合-n选项,输出目标更新时的运行动作
    • 没有-n,修改<file>为当前时间
  • --warn-undefined-variables:有未定义变量是输出警告信息

步骤

  • 读入所有makefile
  • 读入被include其他makefile
  • 初始化(展开)文件中变量、函数,计算条件表达式
  • 展开模式规则%、推导隐式规则、分析所并规则
  • 为所有目标文件创建依赖关系链
  • 根据依赖关系,决定需要重新生成的目标
  • 执行生成命令

相关环境变量

  • MAKEFILES:make会将此环境变量中的文件自动include

    • 不建议使用,会影响所有的make动作
    • 其中文件缺失不会报错
  • MAKEFLAGS:make命令行参数,自动作为make参数

Makefile基本语法

控制符号

  • #:注释

  • @:消除echoing,默认make会打印每条命令

  • -:忽略命令出错

  • 通配符同bash

    • *:任意字符
    • ?:单个字符
    • [...]:候选字符
    • ~:用户目录
      • ~:当前用户目录
      • ~xxx:用户xx目录
  • %:模式匹配

    1
    2
    %.o: %.c
    # 匹配所有c文件,目标为`.o`文件
  • $:引用、展开变量,执行函数

引用其他Makefile

1
include <filename>
  • <filename>可以是默认shell的文件模式,包含通配符、路径
  • include之前可以有空格,但是不能有<tab>(命令提示)
  • make寻找其他makefile,将其内容放当前位置

  • 若文件没有明确指明为绝对路径、相对路径,make会在以下目录 中寻找

    • -I--include-dir参数
    • /usr/local/bin/include/usr/include
  • make会include环境变量MAKEFILES中文件

    • 不建议使用环境变量MAKEFILES,会影响所有make
  • 文件未找到,make会生成一条警告信息,但继续载入其他文件, 完成makefile读取之后,make会重试寻找文件,失败报错

    • 可以使用-include/sinclude代替,忽略include过程 中的任何错误

Makefile显式规则

1
2
<target>: <prerequisite>
[tab]<commands>
  • <target>:目标
  • <prerequisites>:前置条件
  • <commands>:命令,和前置条件至少存在一个
1
2
a.txt: b.txt c.txt
cat b.txt c.txt > a.txt
  • makefile中规则是生成目标的规则

  • make自顶向下寻找可以用于生成目标的规则,生成最终目标类似 调用函数栈

    • 前置条件/依赖类似于被调用函数
    • 命令类似于函数体
    • 目标类似于函数返回值

Target

目标:make的目标

  • 目标通常是文件名,指明需要构建的对象
    • 文件名可以是多个,之间使用空格分隔
  • 不是文件名的目标称为伪目标,视为某种操作

多目标

多目标规则意义是多个目标共享规则依赖、声明命令,并 不是需要同时生成多个目标

  • 需要多目标中的任何一个时,多目标规则就会被应用,其中 命令被执行

  • 每次只生成单独目标的多目标规则,目标之间只是单纯的 可以合并简化规则中的命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    bigoutput littleoutput: text.g
    generate text.g -$(subst output,,$@) > $@

    # 等价于

    bigoutput: text.g
    generate text.g -big > bigoutput
    littleoutput: text.g
    generate text.g -little > littleoutput
  • 同时生成多个目标的多目标规则,多个目标应该满足 需要同时生成、不能单独修改,否则没有必要定义为多目标 ,当然这其实也是合并简化规则中的命令

    1
    2
    %.tab.c %.tab.h: %.y
    bison -d $<

Phony Target

todo

伪目标:目标是某个操作的名字,每次执行都会执行命令

1
2
3
4
.PHONY: clean
# 明确声明是*伪目标*,可省略
clean:
rm *.o
  • 若省略.PYHONY,要求当前目中不存在同名文件,否则make 认为目标已存在,不会执行命令

GNU规范

GNU推荐makefile中包含的伪目标、命名

  • all:编译所有目标
  • clean:删除所有被make创建的文件
  • install:安装已编译好程序,即将目标执行文件拷贝到指定 目标中
  • print:列出改变过的源文件
  • tar:打包源程序备份
  • dist:创建压缩文件
  • tags:更新所有目标,以备完整地编译使用
  • check/test:测试makefile流程

静态库

目标archive(member):指定静态库文件、及其组成

  • 这种定义目标的方法就是方便ar命令
1
2
3
4
5
6
7
8
9
10
11
12
13
foolib(hack.o kludge.o): hack.o kludge.o
ar cr foolib hack.o kludge.o

foolib(hack.o): hack.o
ar cr foolib hack.l kudge.o
foolib(kludge.o): kludge.o
ar cr foolib kludge.o

# 确实等价,但是这个看起来有点不对劲,只要传了任何一个
# 静态库的构成,就执行命令???

foolib(*.o): hack.o kludge.o
# 这样更好???

Prerequisites

前置条件/依赖:生成目标的依赖

  • 通常是一组空格分隔的文件名,为空则说明目标的生成和其他 文件无关

  • 指定目标是否重新构建的判断标准,只要有一个前置文件不存在 、有过更新(时间戳比较),目标就需要更新

  • 若前置文件不存在,则需要建立以其作为目标的规则用于生成, make target时会自动调用

1
2
source: file1 file2 file3
# 利用伪目标+前置条件,同时构建多个文件

Commands

命令:更新、构建文件的方法

  • 在linux下默认使用环境变量SHELL/bin/sh)执行命令,
  • 在MS-DOS下没有SHELL环境变量,会在PATH环境变量中寻找 ,并自动加上.exe.bat.sh等后缀

<tab>

每行命令前必须有一个<tab>,否则需要使用提前声明

1
2
3
.RECIPEPREFIX=>
all:
> echo Hello, world

Shell进程

每行命令在单独的shell进程中执行,其间没有继承关系 (即使是同一个规则中)

  • 多条命令可以使用;分隔

    1
    2
    var-kept:
    export foo=bar; echo "foo=[$$foo]"
  • 可类似python\换行

    1
    2
    3
    var-kept:
    export foo=bar; \
    echo "foo=[$$foo]"
  • 使用.ONESHELL命令

    1
    2
    3
    4
    .ONESHELL
    var-kept:
    export foo=bar
    echo "foo=[$$foo]"

嵌套执行Make

大工程中不同模块、功能源文件一般存放在不同目录,可以为每个 目录单独建立makefile

  • 利于维护makefile,使得其更简洁
  • 利于分块/分段编译
  • 最顶层、调用make执行其他makefile的makefile称为总控
1
2
3
4
5
6
7
8
9
10
subsystem:
cd subdir && $(MAKE)
# 等价
subsystem:
$(MAKE) -C subdir

subsystem:
cd subdir && $(MAKE) -w MAKEFLAGS=
# 将命令行参数`MAKEFLAGS`置空,实现其不向下级传递
# 指定`-w`参数输出make开始前、后信息

搜索路径

VPATH

VPATH:makefile中定义的特殊环境变量,指明寻找依赖文件、 目标文件的路径

1
VPATH = src:../src
  • :分隔路径
  • 当前目录优先级依旧最高

vpath

vpath:make关键字,指定不同模式文件不同搜索目录

1
2
3
4
5
6
7
vpath <pattern> <directories>
vpath %.h ../headers
# 指明`<pattern>`模式文件搜索目录
vpath <pattern>
# 清除`<pattern>`模式文件搜索目录设置
vpath
# 清除所有`vapth`设置
  • <pattern>中使用%匹配0或若干字符
  • vpath可以重复为某个模式指定不同搜索策略,按照出现顺序 先后执行搜索

隐含规则

  • 隐含规则是一种惯例,在makefile中没有书写相关规则时自动 照其运行

    • 隐含规则中优先级越高的约经常被使用
    • 甚至有些时候,显式指明的目标依赖都会被make忽略
      1
      2
      3
      4
      foo.o: foo.p
      # Pascal规则出现在C规则之后
      # 若当前目录下存在foo.c文件,C隐含规则生效,生成
      # foo.o,显式依赖被忽略
    • 很多规则使用后缀规则定义,即使使用-r参数,其 仍会生效
  • 隐含规则会使用系统变量

    • CPPFLAGS/CFLAGS:C++/C编译时参数
  • 可以通过模式规则自定义隐含规则,更智能、清晰

    • 后缀规则有更好的兼容性,但限制更多

常用隐含规则

编译C

  • 目标:<n>.o

  • 依赖包含:<n>.c

  • 生成命令

    1
    $(CC) -c $(CPPFLAGS) $(CFLAGS)

编译CPP

  • 目标:<n>.o

  • 依赖包含<n>.cc/<n>.c

  • 生成命令

    1
    $(CXX) -c $(CPPFLAGS) $(CFLAGS)

编译Pascal

  • 目标:<n>.p

  • 依赖包含:<n>.p

  • 生成命令

    1
    $(PC) -c $(PFLAGS)

编译Fortran/Ratfor

  • 目标:<n>.o

  • 依赖包含:<n>.f/<n>.r

  • 生成命令

    1
    2
    3
    4
    5
    6
    $(FC) -c $(FFLAGS)
    # `.f`
    $(FC) -c $(FFLAGS) $(CPPFLAGS)
    # `.F`
    $(FC) -c $(FFLAGS) $(RFLAGS)
    # `.r`

预处理Fortran/Ratfor

  • 目标:<n>.f

  • 依赖包含:<r>.r/<n>.F

  • 生成命令

    1
    2
    3
    4
    $(FC) -F $(CPPFLAGS) $(FFLAGS)
    # `.F`
    $(FC) -F $(FFLAGS) $(RFLAGS)
    # `.r`
  • 转换Ratfor、有预处理的Fortran至标准Fortran

编译Modula-2

  • 目标:<n>.sym/<n>.o

  • 依赖包含:<n>.def/<n>.mod

  • 生成命令
    1
    2
    3
    4
    $(M2C) $(M2FLAGS) $(DEFFLAGS)
    # `.def`
    $(M2C) $(M2FLAGS) $(MODFLAGS)
    # `.mod`

汇编汇编

  • 目标:<n>.o

  • 依赖包含:<n>.s

  • 生成命令:默认使用编译器as

    1
    2
    $(AS) $(ASFLAGS)
    # `.s`

预处理

  • 目标:<n>.s

  • 依赖包含:<n>.S

  • 生成命令:默认使用预处理器cpp

    1
    2
    $(CPP) $(ASFLAGS)
    # `.S`

链接object

  • 目标:<n>

  • 依赖包含:<n>.o

  • 生成命令:默认使用C工具链中链接程序ld

    1
    $(CC) <n>.o $(LOADLIBS) $(LDLIBS)

Yacc C

  • 目标:<n>.c

  • 依赖包含:<n>.y

  • 生成命令

    1
    $(YACC) $(YFALGS)

Lex C

  • 目标:<n>.c

  • 依赖包含:<n>.c

  • 生成命令

    1
    $(LEX) $(LFLAGS)

Lex Ratfor

  • 目标:<n>.r

  • 依赖包含:<n>.l

  • 生成命令

    1
    $(LEX) $(LFLAGS)

创建Lint库

  • 目标:<n>.ln

  • 依赖包含:<n>.c/<n>.y/<n>.l

  • 生成命令

    1
    $(LINT) $(LINTFLAGS) $(CPPFLAGS) -i

创建静态链接库

  • 目标:<archive>(member.o)

  • 依赖包含:member

  • 生成命令

    1
    ar cr <archive> member.o
  • 即使目标传递多个memeber.o,隐含规则也只会解析出把首个 .o文件添加进静态链接库中的命令
1
2
3
4
(%.o): %.o
$(AR) rv $@ $*.o
# 此命令可以得到添加所有`member.o`的命令
# 但是此时`$*=member.o member`

隐含规则使用变量

隐含规则使用的变量基本都是预先设置的变量

  • makefile中改变
  • make命令环境变量传入
  • 设置环境变量
  • -R/--no-builtin-variable参数取消变量对隐含规则作用

命令

  • AR:函数打包程序,默认ar
  • AS:汇编语言编译程序,默认as
  • CC:C语言编译程序,默认cc
  • CXX:C++语言编译程序,默认c++/g++
  • CPP:C程序预处理器,默认$(CC) -E/cpp
  • FC:Fortran、Ratfor编译器、预处理程序,默认f77
  • PC:Pascal语言编译程序,默认pc
  • LEX:Lex文法分析器程序(针对C、Ratfor),默认lex
  • YACC:Yacc文法分析器程序(针对C、Ratfor),默认 yacc -r
  • GET:从SCCS文件中扩展文件程序,默认get
  • CO:从RCS文件中扩展文件程序,默认co
  • MAKEINFO:转换Texinfo .texi到Info程序,默认 makeinfo
  • TEX:转换TeX至Tex DVI程序,默认tex
  • TEXI2DVI:转换Texinfo至Tex DVI程序,默认texi2dvi
  • WEAVE:转换Web至TeX程序,默认weave
  • TANGLE:转换Web至Pascal程序,默认tangle
  • CTANGEL:转换C Web至C,默认ctangle
  • RM:删除文件命令,默认rm -f

命令参数

未指明默认值则为空

  • ARFLAGS:静态链接库打包程序AR参数,默认rv
  • ASFLAGS:汇编语言汇编器参数
  • CFLAGS:C编译器参数
  • CXXFLAGS:C++编译器参数
  • CPPFLAGS:C预处理参数
  • LDFLAGS:链接器参数
  • FFLAGS:Fortran编译器参数
  • RFLAGS:Fatfor的Fortran编译器参数
  • LFLAGS:Lex文法分析器参数
  • YFLAGS:Yacc文法分析器参数
  • COFLAGS:RCS命令参数
  • GFLAGS:SCCS get参数

隐含规则链

make会努力自动推导生成目标的一切方法,无论中间目标 数量,都会将显式规则、隐含规则结合分析以生成目标

  • 中间目标不存在才会引发中间规则

  • 目标成功产生后,中间目标文件被删除

    • 可以使用.SECONDARY强制声明阻止make删除该中间目标
    • 指定某模式为伪目标.PRECIOUS的依赖目标,以保存被 隐含规则生成的符合该模式中间文件
  • 通常makefile中指定成目标、依赖目标的文件不被当作中间目标 ,可以用.INTERMEDIATE强制声明目标(文件)是中间目标

  • make会优化特殊的隐含规则从而不生成中间文件,如从文件 foo.c直接生成可执行文件foo

模式规则

模式规则:隐式规则可以看作内置模式规则

  • 目标定义包含%,表示任意长度非空字符串
  • 依赖中同样可以使用%,但是其取值取决于目标
  • 命令中不使用模式%,使用自动化变量
  • 模式规则没有确定目标,不能作为最终make目标

    • 但是符合模式规则的某个具体文件可以作为最终目标
    • 不需要作为显式规则的目标,如:archive(member)作为 静态库隐含规则目标
  • 模式的启用取决于其目标%解析同样取决于目标 (因为根据目标查找、应用模式规则)

  • 模式规则类似于隐含规则,给出符合某个模式的某类目标 的依赖、生成命令

  • %展开发生在变量、函数展开后,发生在运行时

静态模式

静态模式:给定目标候选范围的模式,限制规则只能应用在以 给定范围文件作为目标的情况

1
2
<target>: <target-pattern>: <prereq-patterns>
<commands>
  • <target>:目标候选范围,可含有通配符
  • <target-pattern>所有目标文件满足的模式
  • <prereq-pattern>:目标相应依赖
  • 简单例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    objects = foo.o bar.o
    all: $(objects)
    $(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

    # 等价于

    foo.o: foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
    bar.o: bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o
  • 静态模式+filter函数筛选范围

    1
    2
    3
    4
    5
    files = foo.elc bar.o lose.o
    $(filter %.o,$(files)): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(filter %.elc,$(files)): %.elc: %.el
    emacs -f batch-byte-compile $<

重载内建隐含规则

1
2
3
4
5
%.o: %c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D $(date)
# 重载内建隐含规则
%o: %c
# 命令置空,取消内建隐含规则

后缀规则

  • 双后缀规则:定义一对目标文件后缀、依赖后缀
  • 单后缀规则:定义一个依赖后缀
1
2
3
4
5
6
.c.o:
# 等价于`%.o: %c`
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
.c:
# 等价于`%: %.c`
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
  • 后缀规则中不能有任何依赖文件,否则不是后缀规则,后缀被 认为是目标文件

  • 后缀规则中必须有命令,否则没有任何意义,这不会移去内建 的隐含规则

  • 后缀规则定义中的后缀需要是make所认识的,可以使用伪目标 .SUFFIXES修改make认识的后缀

    1
    2
    3
    4
    .SUFFIXES:
    # 删除默认后缀
    .SUFFIXES: .c .o .h
    # 添加自定义后缀
    • 变量$SUFFIXE用于定义默认后缀列表,不应该修改

    • -r/--no-builtin-rules同样会清空默认后缀列表

  • 后缀规则是老式定义隐含规则的方法,会被逐步取代,事实上 后缀规则在makefile载入后会被转换为模式规则

模式规则搜索算法

设目标为src/foo.o

  • 将目标目录部分、文件名部分分离,得到src/foo.o

  • 搜索所有模式规则列表,创建目标和src/foo.o匹配的模式 规则列表

    • 若模式规则列表中有目标匹配所有文件的模式(如%), 则从列表中移除其他模式
    • 移除列表中没有命令的规则
  • 对列表中首个模式规则

    • src/foo.ofoo.o匹配目标,推断%匹配非空部分 茎S
    • 把依赖中%替换为茎S,如果依赖项中没有包含目录, 尝试将src添加在其前
    • 检查所有依赖文件存在、理当存在(文件被定义为其他规则 的目标文件、显式规则的依赖文件)
    • 若有依赖文件存在、理当存在或没有依赖文件,此规则被 采用,退出算法
  • 若没有找到合适模式规则,则检查列表中下个规则是否可行

  • 若没有模式规则可以使用,检查.DEFAULT规则存在性,存在 则应用

变量、赋值

Makefile中定义的变量类似C++/C中的宏

  • 代表一个字符串,在makefile中执行的时候展开在所在位置

    • ""会被作为字符串一部分
    • 默认空格、逗号分隔列表

      1
      2
      3
      4
      5
      empty:=
      space:=$(empty) # 后面有空格,就得到了空格
      comma:=,
      foo:=a,b,c
      bar:=$(substr $(comma), $(space), $(foo)) # 得到字符串`a b c`
  • 变量可以改变值

  • 在shell中需要$$处应使用两个$$$,一个$被escape,则 shell解释时仍然保留一个$,如:变量、函数等都需要

赋值

Makefile内自定义变量

1
2
3
4
5
6
7
txt = Hello World
# 自定义变量
test:
@echo $(txt)
echo ${txt}
# 调用变量,`()`、`{}`含义相同
# 若变量名为单个字符,可以省略括号,但不建议省略
  • =lazy set,在执行时扩展

    • 可以使用任意位置定义(可能还未定义)的变量赋值
    • 允许递归扩展,make报错
  • :=immediate set,在定义/赋值时扩展完毕

    • 只允许使用之前已定义变量赋值(否则为空)
  • ?=set if absent,只有变量为空时才设置值

  • +=append,将值追加到变量的尾部

    • 若前面变量有定义,+=会继承前一次操作符:=/=
    • 对于=定义变量,make自动处理“递归”

define

define可以换行定义变量

  • 变量类似宏的行为、可换行定义变量,方便定义命令包
1
2
3
4
5
6
7
8
9
10
define run-yacc
# `define`后跟变量名作为命令包名称
yacc $(firstword $^); \
mv y.tab.c $@
endef
# 结束定义

foo.c: foo.y
$(run-yacc)
# 使用命令包

override

  • 若变量由make命令行参数-e设置,makefile中默认忽略对其 赋值
  • 需要显式使用override关键字设置
1
2
3
override <variable> = <value>
override <variable> := <value>
override define <variable>

export

上级makefile中变量可以显式export传递到下层makefile中, 但是不会覆盖下层中定义的变量(除指定-e参数)

1
2
3
4
5
6
7
8
9
export <variable>[=value]
# 传递变量至下级makefile中
unexport <variable>
# 禁止变量传递至下级makefile中

export variable = value
# 等价
variable = value
export variable
  • export后面不指定具体传递变量,表示传递所有变量
  • MAKEFLAGSSHELL两个变量总是会传递到下级makefile中

系统环境变量

make运行时系统环境变量、命令行环境变量可以被载入makefile

  • 默认makefile中定义变量覆盖系统环境变量
  • -e参数则表示makefile中变量被覆盖
1
2
3
test:
@echo $$HOME
# 传递给shell的变量,需要`$$` escape

Target-Specific Variable

目标/局部变量:作用范围局限于规则、连带规则中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<target ...>: [override] <variable-assignment>

prog: CFLAGS = -g
prog: prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o: prog.c
$(CC) $(CFLAGS) prog.c

foo.o: foo.c
$(CC) $(CFLAGS) foo.c

bar.o: bar.c
$(CC) $(CFLAGS) bar.c

Pattern-Specific Variable

模式变量:给定模式,变量定义在符合模式的所有目标

1
2
3
<pattern ...>: [override]<variable-assignment>

%.o: CFLAGS = -o

Implicit Variables

内置变量:主要是为了跨平台的兼容性

  • $(CC):当前使用的编译器

    1
    2
    output:
    $(CC) -o output input.c
  • $(MAKE):当前使用的Make工具

  • $(MAKECMDGOLA):make目标列表

Automatic Variables

自动化变量:应用规则时被自动赋予相应值(一般是文件)的变量

  • $@当前需要生成的目标文件
    • 多目标规则中,$@也只表示被需要目标
  • $*:匹配符%匹配部分
    • 若目标中没有%模式符,$*不能被推导出,为空
    • GNU make中,目标中没有%$*被推导为除后缀部分, 但是很可能不兼容其他版本,谨慎使用
  • $<:首个前置条件
  • $%:仅当目标是函数库文件,表示目标成员名,否则为空

    • 目标为foo.a(bar.o)$%bar.o$@foo.a
  • $?:比目标更新的前置条件,空格分隔

  • $^:所有前置条件,会取出其中重复项
  • $+:类似于$^,但是剔除重复依赖项
  • 自动化变量只应出现在规则的命令
  • 自动化变量值与当前规则有关
  • 其中$@$*$<$%扩展后只会为单个文件,$?$^$+扩展后可能是多个文件
1
2
3
dest/%.txt: src/%.txt
@[ -d test ] || mkdir dest
cp $< $@

D、F

  • 7个自动化变量可以搭配DF取得相应路径中目录名、 文件名
  • 新版本GNU make可以使用函数dirnotdir代替D/F
  • D/dir:目录带有最后/,若为当前目录则为./
  • F/nodir:文件名
  • 对可能会扩展为多文件的$?$^$+D/F处理后 返回同样是多个目录/文件
1
2
3
4
5
6
7
8
9
10
11
12
13
$(@D)
$(dir $@)
# `$@`的目录名
$(@F)
$(nodir $@)
# `$@`的文件名

$(?D)
$(dir $?)
# `$?`中所有目录,空格分隔
$(?F)
$(nodir $?)
# `$?`中所有文件,空格分隔

控制语句

if

1
2
3
4
5
6
7
8

<conditional-directive>
<text-if-true>
[
else
<text-if-false>
]
endif
  • ifeq:比较参数是否相等

  • ifneq:比较参数是否不等

    1
    2
    3
    4
    5
    6
    ifeq ($(CC), gcc)
    # 也可以用单/双引号括起,省略括号
    libs=$(libs_for_gcc)
    else
    libs=$(normal_libs)
    endif
  • ifdef

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    bar =
    foo = $(bar)
    # `foo`有定义
    ifdef foo
    frobozz = yes
    # 此分支
    else
    frobozz = no
    endif

    foo =
    # `foo`未定义
    ifdef foo
    frobozz = yes
    else
    frobozz = no
    # 此分支
    endif
  • ifndef

  • <conditional-directive>, else, endif行可以有多余空格, 但是不能以<tab>开头,否则被认为是命令
  • make在读取makefile时就计算表达式值、选择语句,所以最好 别把自动化变量放入条件表达式中
  • make不允许把条件语句分拆放入两个文件中

for

1
2
3
4
5
6
7
8
9
10
11
LIST = one two three

all:
for i in $(LIST); do \
echo $$i;
// 这里传递给shell的变量,需要`$$` escape
done
all:
for i in one two three; do
echo $$i;
done

内建函数

1
2
$(function parameters)
${function paremeters}

Make控制函数

提供一些函数控制make执行

  • 检测运行makefile的运行时信息,根据信息决定make继续执行 、停止

error

产生错误并退出make,错误信息<text>

1
2
3
4
5
6
7
8
9
10
$(error <text...>)

ifdef ERROR_001
$(error error is $(ERROR_001))
endif

ERR = $(error found an error)
.PHONY: err
err:
err: ; $(ERR)

warning

类似error函数,输出警告信息,继续make

其他函数

shell

执行shell命令的输出作为函数返回值

1
srcfiles := $(shell echo src/{00..99}.txt)
  • 函数会创建新shell执行命令,大量使用函数可能造成性能下降 ,尤其makefile的隐晦规则可能让shell函数执行次数过多

wildcard

在变量中展开通配符*

1
2
3
srcfiles := $(wildcard src/*.txt)
# 若不展开,则`srcfiles`就只是字符串
# 展开后则表示所有`src/*.txt`文件集合

字符串处理函数

subst

文本替换

1
2
3
4
5
$(subst <from>,<to>,<text>)
# `subst`函数头

$(subst ee,EE,feet on the street)
# 替换成*fEEt on the strEET*

patsubst

模式匹配的替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(patsubst <pattern>,<replacement>,<text>)
# 函数头文件
$(patsubst %.c,%o,x.c.c bar.c)
# 替换为`x.c.o bar.o`

foo := a.o b.o c.o
$(variable: <pattern>=<replacement>)
# `patsubst`函数的简写形式
bar := $(foo:%.o=%.c)
# `$(bar)`变为`a.c b.c c.c`

$(variable: <suffix>=<replacement>)
# 没有模式匹配符`%`则替换结尾
bar := $(foo:.o=.c)
# `$(bar)`变为`a.c b.c c.c`

strip

去字符串头、尾空格

1
2
$(strip <string>)
$(strip a b c)

findstring

<in>中查找<find>,找到返回<find>,否则返回空串

1
2
$(findstring <find>,<in>)
$(findstring a,a b c)

filter

<pattern>模式过滤<text>字符串中单词,返回符合模式的 单词

1
2
3
4
5
6
$(filter <pattern..>,<text>)

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s, $(sources)) -o foo
# 返回`foo.c bar.c baz.s`

filter-out

<pattern>模式过滤<text>字符串中单词,返回不符合模式的 单词

1
2
3
4
objects := main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains), $(objects))
# 返回`foo.o bar.o`

sort

<list>中单词升序排序

1
2
3
4
$(sort <list>)

$(sort foo bar lose)
# 返回`bar foo lose`

word

取字符串<text>中第<n>个单词

1
2
3
4
$(word <n>,<text>)

$(word 2, foo bar baz)
# 返回`bar`

wordlist

<text>中取<s>-<e>单词(闭区间)

1
2
3
4
$(wordlist <s>,<e>,<text>)

$(wordlist 2, 3, foo bar baz)
# 返回`bar baz`

words

统计<text>中单词个数

1
2
3
4
$(word <text>)

$(word, foo bar baz)
# 返回3

firstword

<text>中首个单词

1
2
3
4
$(firstword <text>)

$(firstword foo bar)
# 返回`foo`

文件名操作函数

dir

从文件名序列中取出目录部分

  • 最后/之前部分
  • 若没有/则返回./
1
2
3
4
$(dir <names...>)

$(dir src/foo.c hacks)
# 返回`src/ ./`

notdir

从文件名序列中取出非目录部分(最后/之后部分)

1
2
3
4
$(notdir <names...>)

$(notdir src/foo.c hacks)
# 返回`foo.c hacks`

suffix

从文件名序列中取出各文件名后缀

1
2
3
4
$(suffix <names...>)

$(suffix src/foo.c src-1.0/bar.c hacks)
# 返回`.c .c`

basename

从文件名序列中取出各文件名“前缀”(除后缀外部分)

1
2
3
4
$(basename <names...>)

$(basename src/foo.c src-1.0/bar.c hacks)
# 返回`src/foo src-1.o/bar hacks`

addsuffix

把后缀<suffix>添加到文件名序列中每个单词后

1
2
3
4
$(addsuffix <suffix>,<names...>)

$(addsuffix .c, foo bar)
# 返回`foo.c bar.c`

addprefix

把后缀<prefix>添加到文件名序列中每个单词后

1
2
3
4
$(addprefix <prefix>,<names...>)

$(addprefix src/, foo bar)
# 返回`src/foo src/bar`

join

<list2>中单词对应添加到<list1>中单词后

  • 较多者剩余单词保留
1
2
3
4
$(join <list1>,<list2>)

$(join aaa bbb, 111 222 333)
# 返回`aaa111 bbb222 333`

控制函数

foreach

循环函数,类似于Bash中的for语句

  • <list>中单词逐一取出放到参数<var>所指定的变量中
  • 再执行<text>所包含的表达式,每次返回一个字符串
  • 循环结束时,返回空格分隔的整个字符串
1
2
3
4
5
$(foreach <var>,<list>,<text>)

names := a b c d
files := $(foreach n,$(names),$(n).o)
# 返回`a.o b.o c.o d.o`
  • <var>是临时局部变,函数执行完后将不再作用

if

类似于make中的ifeq

  • <condition>为真(非空字符串),计算<then-part>返回值
  • <condition>为假(空字符串),计算<else-part>、返回空 字符串
1
$(if <condition>,<then-part>,[<else-part>])

call

创建新的参数化函数的函数

  • 创建表达式<expression>,其中可以定义很多参数
  • call函数向其中传递参数,<expression>返回值即call 返回值
1
2
3
4
5
6
7
8
$(call <expression>,<param1>,<param2>,...>

reverse = $(1) $(2)
foo = $(call reverse,a,b)
# 返回`a b`
reverse = $(2) $(1)
foo = $(call reverse,a,b)
# 返回`b a`
  • <expression>要先创建为变量,然后不带$传递

origin

返回变量的来源

  • undefined<variable>未定义
  • default:make默认定义变量
  • environment:环境变量,且-e选项未开
  • file:定义在makefile中
  • command line:命令行定义环境变量
  • overrideoverride关键字定义
  • atomatic:命令运行中自动化变量
1
2
3
4
5
6
7
$(origin <variable>)

ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc
endif
endif
  • <variable>不操作变量值,不带$传递

Makefile技巧

案例

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
edit: main.o kdd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o dispaly.o\
insert.o search.o files.o utils.o

main: main.c defs.h
cc -c main.c
kbd.o: kbd.c defs.h
cc -c kbd.c
command.o: command.c defs.h command.h
cc -c command.c
display.o: display.o defs.h buffer.h
cc -c display.c
insert.o: insert.c defs.h buffer.h
cc -c insert.c
search.o: search.c defs.h buffer.h
cc -c search.c
files.o: files.c defs.h buffer.h command.h
cc -c files.c
utils.o utils.c defs.h
cc -c utils.c

clean:
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

.PHONY: edit clean
# 设置`edit`、`clean`为伪目标

利用变量简化目标

1
2
3
4
5
6
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit: $(objects)
cc -o edit $(objects)
# 以下同上

隐式模式自动推导

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(objects)
cc -o edit $(objects)
main.o: defs.h
kbd.o: defs.h command.h
command.o: defs.h command.h
display: defs.h buffer.h
insert.o: defs.h buffer.h
search.o: defs.h buffer.h
files.o: defs.h buffer.h command.h
utils.o: defs.h

clean:
rm edit $(objects)

.PHONY: clean
  • 利用隐式模式自动推导文件、文件依赖关系

利用变量提取依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(objects)
cc -o edit $(objects)

$(objects): defs.h
kbd.o command.o files.o: command.h
display.o insert.o search.o files.o: buffer.h

clean:
rm edit $(objects)

.PHONY: clean
  • 文件变得简单,但是依赖关系不再清晰

自动生成依赖

  • 大部分C++/C编译器支持-M选项,自动寻找源文件中包含的 头文件,生成依赖关系

  • GNU建议为每个源文件自动生成依赖关系,存放在一个文件中, 可以让make自动更新依赖关系文件.d,并包含在makefile中

1
2
3
4
5
6
7
8
9
10
11
12
13
%.d: %.c
@set -e; rm -f $@; \
$(cc) -M $(CPPFLAGS) $< > $@.$$$$; \
# 生成中间文件
# `$$$$`表示4位随机数
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.$$$$ > $@; \
# 用`sed`替换中间文件target
# `xxx.o` -> `xxx.o xxx.d`
rm -f $@.$$$$
# 删除中间文件

source = foo.c bar.c
include $(sources: .c=.d)

Git 基础

.gitingore

.gitignore忽略规则

  • !开头:排除应被忽略

  • /结尾:忽略文件夹

  • /开头:git仓库根目录路径开始(可省略)

  • 遵循一定的正则表达式

    • *:多个字符,不包括/,因此多层级目录需要一一指定
    • ?:单个字符
    • []:匹配其中候选字符

.gitignore配置方式

  • 仓库根目录创建.gitignore文件,添加忽略规则

    • 忽略规则针对当前项目
    • .gitignore文件默认随仓库分发,所有人共用忽略规则 (当然可以在.gitignore中配置忽略.gitignore
  • 设置全局忽略文件,对所有git项目适用

    1
    git config --global core.excludesfile /path/to/.gitignore
  • 修改.git/info/exclude文件,添加忽略规则

    • 对单一项目有效
    • 非所有人共用忽略规则

Git配置/config

  • 配置文件、配置类型

    • --system:系统全局配置,配置文件/etc/gitconfig
    • --global:用户全局配置,配置文件 $HOME/.gitconfig$HOME/.config/git/config
    • --local:局部repo配置,配置文件repo/.git/config
    1
    2
    3
    4
    # 修改`section.key`值(缺省`--local`)
    $ git config [--system|--global|--local] <section>.<key> <value>
    # 删除配置项
    $ git config [--system|--global|--local] --unset <section>.<key>

配置文件常用

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
 # 核心修改
[core]
# 忽略文件权限修改
filemode = false
editor = vim
# 提交、检出时换行符设置
# `input`:提交时转换为`<LF>`、检出时不转换
# `false`:提交、检出均不转换
# `true`:提交时转换为`<LF>`、检出时转换为`<CRLF>`
autocrlf = input
# 是否允许文件混用换行符
# `true`:拒绝提交包含混合换行符文件
# `false`:允许提交包含混合换行符文件
# `warn`:提交包含混合换行符文件时警告
safecrlf = true
[user]
name = xyy15926
email = xyy15926@163.com
# 设置别名
[alias]
st = status
ci = commit
br = branch
lg = "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
d = difftool
# 输出着色
[color]
# 打开所有默认终端着色
ui = true
[diff]
tool = vimdiff
[difftool]
prompt = false
  • autocrlf在linux若设置为input,在add包含<CRLF> 文件会报fatal
    • 因为input在提交时会将<CRLF>转换为<LF>,但在 检出时无操作
    • 所以导致即使add也不能保证repo当前状态和提交状态 一致

remote

1
2
3
4
 # 查看远程仓库
$ git remote -v
# 设置远程仓库地址
$ git remote set-url <repo_name> <new_url>

指定ssh key

  • git依赖ssh进行存储库认证,无法直接告诉git使用哪个私钥
  • ~/.ssh/config中配置ssh host:git每次使用host代替原 服务器地址

    1
    2
    3
    4
    host <host>
    HostName github.com
    IdentityFile $HOME/.ssh/id_rsa_github_private
    User git
    • ssh host详见*linux/shell/config_files”
  • GIT_SSH_COMMAND环境变量:指定ssh命令-i中参数

    1
    2
    3
    4
    5
    GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa_github_private" git clone ...
    // 可以`export`持久化
    export GIT_SSH_COMMAND = ...
    // 写入git配置
    git config core.sshCommand "..."

展示命令

log

1
2
$ git log [<file_name>]
# 查看文件提交历史

show

1
2
$ git show <tag_no|commit_hash>
# 查看改动内容

Tmux

Tmux

  • Session:用户与计算计算机的交互

    • 一般而言,temrinal窗口、Session进程绑定
      • 打开窗口,Session开始
      • 关闭窗口,Session结束
  • Tmux:终端复用软件,将terminal窗口、Session解绑

    • 允许多个窗口接入、断开多个Session
      • 新增多个Session
      • 接入已有Session,共享Session
    • 支持窗口竖直、水平拆分

https://man7.org/linux/man-pages/man1/tmux.1.html

命令行参数

  • -S <socket-file>:指定tmux使用socket文件(位置)

Session管理

  • tmux new -s <session-name>:创建指定名称session
    • 缺省数字命名
  • tmux detach:detach当前接入会话
  • tmux ls/list-session:列出session列表
  • tmux rename -t <s1> <s2>:重命名session
  • tmux a [-t <session-name>]:attach指定session
    • 缺省连接上个session
  • tmux switch -t <session-name>:切换至指定session
  • tmux kill-session [[-a] -t s1]:关闭session
    • 缺省关闭上次session
    • -a:关闭除指定session外其他session
  • tmux kill-server:关闭所有session

Tab(Windows)管理

  • tmux new-window [-n <win-name>]:创建新窗口
  • tmux switch-window -t <win-name>:切换到指定窗口
  • tmux rename-window <win-name>:重命名当前窗口

Pane管理

  • tmux split-window [-h]:竖直/水平划分窗口
  • tmux select-pane -U/-D/-L/-R:激活上、下、左、右侧Pane
  • tmux swap-pange -U/-D/-L/-R:当前Pane上、下、左、右 移动

帮助

  • tmux list-key:列出所有绑定键
  • tmux list-command:列出所有命令
  • tmux info:列出当前所有Tmux会话信息
  • tmux source-file <tmux-conf>:重新加载Tmux配置文件

配置

set

  • 默认配置文件为~/.tmux.conf
  • set[-option] [-g] [-a]:session选项

    • 全局、追加标志
      • -g:全局设置
      • -a:追加设置,适合option需要字符串、样式值
    • default-terminal
    • display-time
    • escape-time
    • history-limit
    • base-index
    • pane-base-index
  • setw/set-window-option [-g] [-a]:window选项

    • 全局、追加标志同set[-option]
    • allow-rename
    • mode-keys:快捷键模式,可以设置为vi
    • synchronize-panes
  • set[-option] -s:server选项

StatusBar设置

  • StatusBar主要由5部分组成

    • windows列表
      • windows-status-*:默认windows
      • windows-status-current-*:当前windows
      • windows-status-bell-*:后台有活动windows (需开启支持)
    • 左侧显示区
    • 右侧显示区
    • message显示条:占据整个status bar
    • command输入条:占据整个status bar
  • *-style bg=<color>,fg=<color>,<ATTR>指定样式

    • 颜色可以用名称、colour[0-255]#RGB方式指定
    • 属性包括(前加no表关闭)
      • bright
      • dim
      • underscore
      • blink
      • reverse
      • hidden
      • italics
      • strikethrough
  • *-format:设置格式

    • #{}中变量名称会被替换为相应值,支持alias缩写的变量 可以省略{}
      • host#H
      • host_short#h
      • pane_id#D
      • pane_index#P
      • pane_title#T
      • session_name#S
      • window_flags#F
        • *:当前窗口
        • -:最后打开的窗口
        • z:Zoom窗口
      • window_index#I
      • window_name#W
    • #()会被作为shell命令执行并被替换
      • 命令执行不会阻塞tmux,而是展示最近一次命令执行 结果
      • 刷新频率由status-interval控制
  • 这里介绍2.9之后配置风格

bind

  • bind[-key] [-n] [-r] <key>:key mapping
    • -n:无需prefix
    • -r:此键可能重复
  • unbind <key>:解绑捕获

默认KeyMappings

  • 快捷键前缀缺省为C-b
  • <prefix>::进入命令行
  • 以下快捷键都是缺省值,可以解绑

Session

  • s:列出session,可用于切换
  • $:重命名session
  • d:detach当前session
  • D:detach指定session

Tab/Windows

  • c:创建新tab
  • &:关闭当前tab
  • ,:重命名当前tab
  • .:修改当前tab索引编号
  • w:列出所有tab
  • n/p/l:进入下个/上个/之前操作tab
  • [tab-n]:切换到指定编号窗口
  • f:根据显示内容搜索tab
  • tmux中window相当于tab

Panes

  • %:水平方向创建窗口
  • ":竖直方向创建窗口
  • Up/Down/Left/Right:根据箭头访问切换窗口
  • q:显示窗口编号
  • o:顺时针切换窗口
  • }/{:与下个/上个窗口交换位置
  • space:在预置面板布局中循环切换
    • even-horizontal
    • even-vertical
    • main-horizontal
    • main-vertical
    • tiled
  • !:将当前窗口置于新tab
  • C-o/M-o:顺/逆时针旋转当前窗口,即所有窗口循环前/后 移动一个位次
  • t:在当前窗口显示时间
  • z:最大/恢复当前窗口
  • i:显示当前窗口信息
  • x:关闭当前窗口
  • q:显示当前窗口编号
  • [:进入自由复制模式,VI 模式下快捷键同 VI,且
    • <space>:从光标处开始选择(支持 V 选择行)
    • <enter>:复制选择部分
  • ]:粘贴
  • tmux中pane相当于vim中windows

信息

  • ?:列出所有绑定键

GCC

G++

g++:是gcc的特殊版本,链接时其将自动使用C++标准库而不是 C标准库

1
2
$ gcc src.cpp -l stdc++ -o a.out
// 用`gcc`编译cpp是可行的

Vim 内建函数、变量

文件、路径相关函数

  • expand(option):根据参数返回当前文件相关信息
  • fnamemodify(file_name, option):返回当前文件夹下文件 信息
  • globpath(dir, type):返回的dir下符合type的文件 列表值字符串,使用,分隔,type**时将递归的列出 文件夹及文件

特殊变量

command-line模式的特殊变量,在执行命令前会将其替换为相应的 变量

  • <cword>:光标处单词
  • <cWORD>:光标处单词大写形式

寄存器

寄存器相关快捷键、命令

  • <c-r><reg>:insert模式下直接输入<reg>中的值

一般寄存器

Readonly Register

Expression Register("=

"=实际上并不是一个寄存器,这是使用命令表达式的一种方法, 按下=之后,光标会移动到命令行,此时可以输入任何表达式, (不只有"=才会激活命令行,<c-m>"也能激活) 输入表达式之后

  • 按下<esc>,表达式值被丢弃
  • 按下<cr>,表达式值计算后存入"=
    1
    2
    :nnoremap time "=strftime("%c")<cr>p
    :inoremap time <c-r>strftime("%c")<cr>

之后:put或者按下p将粘贴"=中的值

寄存器中的值一定是字符串,如果是其他类型变量,会被强制 转换之后存入"=寄存器中

Vim特殊

换行

  • \0:空转义序列(ASCII码位0)<Nul>

    • <c-v> 000:输入<Nul>
    • Vim在内存中使用<NL>存储<Nul>,在读、写文件时即 发生转换
    • Vi无法处理<Nul>,应该是为了兼容Vi
  • \n:换行转义序列<NL>

    • <c-v><c-j>:输入<NL>,会被替换为输入<Nul>, 等同于<c-v> 000
    • 在搜索表达式中:字面意义的newline序列被匹配
    • 在替换表达式中:在内部被替换为<Nul>被输入,即 不再表示newline
  • \r:回车转义序列<CR>

    • 被Vim视为为换行,可在替换表达中表示<NL>
    • <c-v><c-j>:输入<CR>字符本身
  • vim在内存换行应该同一使用<CR>,在读、写时,根据当前 fileformat设置自动转换换行字符(序列)