「Linux」- Interruption,硬中断、软中断
概念理解:中断(Interrupt)
中断是系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。
中断其实是一种异步的事件处理机制,可以提高系统的并发处理能力。
为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行。如果中断要处理的事情很多,中断服务程序就有可能要运行很长时间。特别是中断处理程序在响应中断时,还会临时关闭中断。这就会导致上次中断处理完成之前,其他中断都不能响应,也就是说中断有可能会丢失。
为了解决中断处理程序执行过长和中断丢失的问题,Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部: 上半部:用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作;特点是快速执行;上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。 下半部:用来延迟处理上半部未完成的工作,通常以内核线程的方式运行;特点是延迟执行;
以网卡收发数据为列: 1)网卡接收到数据包后,会通过硬件中断的方式,通知内核有新的数据到了,内核就应该调用中断处理程序来响应它 2)对上半部来说,快速处理,把网卡的数据读到内存中,然后更新硬件寄存器的状态(表示数据读取完成),最后再发送软中断信号,通知下半部做进一步的处理 3)下半部被软中断信号唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序
硬中断(Hardware interrupts)
上半部是发生硬件中断时调用,它不是软中断。
软中断(Software interrupts)
下半部则是软中断,下半部(软中断)以内核线程的方式执行,并且每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/<CPU N>”,比如说,0 号 CPU 对应的软中断内核线程的名字就是 ksoftirqd/0:
# ps aux | grep softirq
root 9 0.0 0.0 0 0 ? S Jul05 0:37 [ksoftirqd/0]
root 17 0.0 0.0 0 0 ? S Jul05 0:35 [ksoftirqd/1]
root 22 0.0 0.0 0 0 ? S Jul05 0:48 [ksoftirqd/2]
root 27 0.0 0.0 0 0 ? S Jul05 0:29 [ksoftirqd/3]
// 这[……]
「Linux」- Work Queues
Work Queue 类似与 tasklet,都用于延缓执行。但是 Work Queue 具有自己的 Process Context,能够休眠,也可被调度。
Kernel 初始化,会创建 kwventd_wq 工作队列,同时为其创建 Kernel Thread(每个 CPU 具有独立的内核线程)
Kernel 还提供 create_workqueue() 和 create_singlethread_workqueue() 函数,以允许用户创建自己的工作队列及执行线程。[……]
「Linux」- 内核模块编程
问题描述
我们将要学习 Linux Kernel Module 的开发方法,以为学习 Linux Kernel 做准备。
该笔记将记录:Linux Kernel Module 的开发方法,旨在快速入门,以及常见问题处理方案。
解决方案
Kernel Module 是已编译的二进制程序,直接插入到 Linux Kernle 中运行。
Kernel Module 运行在 RING0 中,在 x86 CPU 中最低且受保护最少的运行级别。这里的代码完全不受检查地运行,但运行速度令人难以置信,并且可以访问系统的所有内容。
准备工作
1)操作系统:虚拟机或独立运行的 Linux 发行版,用于内核代码测试;应避免在当前系统中直接测试内容,否则可能会导致系统崩溃、数据丢失等等问题; 2)编程语言:需要懂得些 C 语言编程;懂得 Linux Shell 命令行等等工具的使用方法;如果懂得硬件知识或汇编语言会更加的有帮助;
安装开发环境
我们使用 Eclipse 作为开发工具,
我们在 Ubuntu 20.04 TLS 中进行开发工作,需要安装如下包:
apt-get install build-essential # 安装编译器、调试器、库、其他工具
# Depends: libc6-dev | libc-dev, gcc (>= 4:9.2), g++ (>= 4:9.2), make, dpkg-dev (>= 1.17.11)
apt-get install linux-headers-`uname -r` # 安装内核头文件
模块开发
第一步、编写代码
lkm_example.c
// 用于模块开发的头文件
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
// 模块描述信息
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“k4nz@d3rm.org”);
MODULE_DESCRIPTION(“A simple example Linux module.”);
MODULE_VERSION(“0.01”);
// 模块初始函数
static int __init lkm_example_init(void) {
printk(KERN_INFO “Hello, World!\n”);
return 0;[……]
「Linux」- 内存管理
物理内存也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存。
内存的分配方式
Buddy Memory Allocation
是最底层的内存管理机制,提供页式的内存管理。
函数调用:alloc_pages()
Slab Allocation
是构建在 Buddy Memory Allocation 上的内存管理,提供基于对象的内存管理。
函数调用: 1)创建对象:kmem_cache_create() 2)分配内存:kmem_cache_alloc() 3)释放内存:kmem_cache_free()
进程如何访问内存
物理内存 与 虚拟内存
Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问虚拟内存。
并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
内存映射
内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。
虚拟内存 =>(虚拟地址)=> 页面表(MMU) => (物理地址)=> 物理内存
对普通进程来说,能看到的其实是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。
内存管理单元(MMU)
页表实际上存储在处理器的内存管理单元(MMU)中。而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
MMU 以“页”来管理内存,通常是 4 KB 大小,每一次内存映射,都需要关联 4 KB 或者 4KB 整数倍的内存空间。
页的大小只有 4 KB ,导致的另一个问题就是,整个页表会变得非常大。比方说,仅 32 位系统就需要 100 多万个页表项(4GB/4KB),才可以实现整个地址空间的映射。为了解决页表项过多的问题,Linux 提供了两种机制,也就是多级页表和大页(HugePage)。
多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么,多级页表就只保存这些使用中的区块,这样就可以大大地减少页表的项数。Linux 用的正是四级页表来管理内存页,如下图所示,虚拟地址被分为 5 个部分,前 4 个表项用于选择页,而最后一个索引表示页内偏移。
大页(HugePage),顾名思义,就是比普通页更大的内存块,常见的大小有 2MB 和[……]
「Linux」- 缓冲(Buffer)与缓存(Cache)
使用 free 查看查看内存使用情况
# free // 以字节为单位
total used free shared buff/cache available
Mem: 16108680 13563160 301692 731132 2243828 1478772
Swap: 31457276 16751336 14705940
// 1)total,总内存大小;
// 2)used,已使用内存的大小,包含了共享内存;
// 3)free,未使用内存的大小;
// 4)shared,共享内存的大小;
// 5)buff/cache,缓存和缓冲区的大小;
// 6)available,新进程可用内存的大小;包含未使用内存,还包括了可回收的缓存,会比未使用内存更大。但并不是所有缓存都可以回收,因为有些缓存可能正在使用。
命令 free 从 /proc/meminfo 读取数据,根据 man proc 手册:
buffer
1)Buffers,内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值; Buffers,对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
cache
2)Cache,内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与 SReclaimable 之和; Cached,从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。 SReclaimable,Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
概念理解:Buffer and Cache
Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。
简单来说,Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
在读写文件时,会经过文件系统,由文件系统负责与磁盘交互;在读写磁盘时,就会跳过文件系统,也就是所谓的“裸I/O“。这两种读写方式所使用的缓存是不同的,也就是 Cache 和 Buffer 区别。
实验探究:文件写入 与 磁盘写入
写文件时会用到 Cache 缓存数据,而写磁盘则会用到[……]
「Linux」- 内存管理
限制内存的使用
TODO 限制内存的使用(Linux) Restricting process CPU usage using nice, cpulimit, and cgroups
/proc/slabinfo
/proc/meminfo 仅显示 Slab 的整体大小,具体到每一种 Slab 缓存,还要查看 /proc/slabinfo 文件:
$ cat /proc/slabinfo | grep -E ‘^#|dentry|inode’
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
xfs_inode 0 0 960 17 4 : tunables 0 0 0 : slabdata 0 0 0
…
ext4_inode_cache 32104 34590 1088 15 4 : tunables 0 0 0 : slabdata 2306 2306 0hugetlbfs_inode_cache 13 13 624 13 2 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 1190 1242 704 23 4 : tunables 0 0 0 : slabdata 54 54 0
shmem_inode_cache 1622 2139 712 23 4 : tunables 0 0 0 : slabdata 93 93 0
proc_inode_cache 3560 4080 680 12 2 : tunables 0 0 0 : slabdata 340 340 0
inode_cache 25172 25818 608 13 2 : tunables 0[……]
「Linux」- 检查内存使用情况
问题描述
在服务器中,我们并没有运行太多程序,但是服务器的内存占用很高。
# 03/06/2017 我们还没干什么呢…………有时间再管他吧
# 03/09/2017 后来我们联系机房,技术人员为我们解释出缘由。其实这个问题的答案还是因为我们无知,如果我们在知道的多一点的话就应该知道虚拟化里有个“动态内存”。
这篇文章剩下的内容,主要是介绍在Linux中分析内存使用的方法
方法一、free
查看服务器的内存使用(有关free命令介绍,点击查看):
#!/bin/sh
free -h -w
# total used free shared buffers cache available
# Mem: 3.9G 2.8G 414M 17M 160M 454M 805M
# Swap: 1.0G 9.8M 1.0G
# used = total – free – buffers – cache.: 有点高,我们就是不知道被哪些进程用了。
# shared: 由tmpfs使用的,不高(我们比对了其他三台机器)。
# buffers: 内核缓冲使用的,160M 不高。
# cache:page cache和slabs使用的,454M 也是正常的。
#
先drop_caches看下,(实际并不推荐这么做,因为这么做不是在解决问题,buffer 和 cache 本来都是为了减少IO):
#!/bin/sh
echo 3 > /proc/sys/vm/drop_caches
free -h -w
# total used free shared buffers cache available
# Mem: 3.9G 2.8G 928M 17M 1.5M 103M 879M
# Swap: 1.0G 9.8M 1.0G
# 并没有什么用,只是刷掉 buffer 和 cache 了。这么做也不好,因为buffer 和 cache 本来都是为了减少IO的。
方法二、top
我们用top命令吧(关于top命令的介绍,点击查看)
至于我们的top为什么是这样的,而不是常见的那种,是因为我们的[……]
「Linux」- 内存缓存
# Page Cache 包含磁盘上块映射。
# Inode 表示文件的数据结构
# Dentries 表示目录的数据结构
清除Page Cache缓存:sync; echo 1 > /proc/sys/vm/drop_caches
清除Inode与Dentries缓存:sync; echo 2 > /proc/sys/vm/drop_caches
清除这三者的缓存:sync; echo 3 > /proc/sys/vm/drop_caches
free
可以用 free 或 vmstat,来观察页缓存的大小。free 输出的 Cache,是页缓存和可回收 Slab 缓存的和,
可以从 /proc/meminfo ,直接得到它们的大小:
$ cat /proc/meminfo | grep -E “SReclaimable|Cached”
Cached: 748316 kB
SwapCached: 0 kB
SReclaimable: 179508 kB
参考文献
How to Clear RAM Memory Cache, Buffer and Swap Space on Linux what are pagecache, dentries, inodes?[……]
「Linux」- 内存编辑工具
问题描述
该笔记将记录:在 Linux 中,内存编辑相关的工具,以及相关问题处理。
解决方案
scanmem
https://github.com/scanmem/scanmem
GameConqueror
GameConqueror: scanmem的GUI工具。
Cheat Engine
http://www.cheatengine.org/
在 Windows 和 macOS 中我们能够使用 Cheat Engine 工具。[……]
「Linux」- SWAP(学习笔记)
Swap 说白了就是把一块磁盘空间或者一个本地文件(以磁盘为例),当成内存来使用。它包括换出和换入两个过程: 1)换出,把进程暂时不用的内存数据存储到磁盘中,并释放这些数据占用的内存。 2)换入,在进程再次访问这些内存的时候,把它们从磁盘读到内存中来
现在的内存便宜多,但 SWAP 依旧有用: 1)即使内存不足时,有些应用程序也并不想被 OOM 杀死,而是希望能缓一段时间,等待人工介入,或者等系统自动释放其他进程的内存,再分配给它 2)笔记本电脑的休眠和快速开机的功能,也基于 Swap 。休眠时,把系统的内存存入磁盘,这样等到再次开机时,只要从磁盘中加载内存就可以
在操作系统中,何时发生内存回收
直接内存回收
有新的大块内存分配请求,但是剩余内存不足。此时操作系统就需要回收一部分内存(比如缓存),进而尽可能地满足新内存请求
定期回收内存(kswapd0)
专门的内核线程,kswapd0,用来定期回收内存。
为了衡量内存的使用情况,kswapd0 定义三个内存阈值(watermark,也称为水位),分别是:页最小阈值(pages_min)、页低阈值(pages_low)、页高阈值(pages_high)
pages_free,表示剩余内存: 1)pages_free < pages_min,剩余内存小于页最小阈值,说明进程可用内存基本耗尽,只有内核才可以分配内存 2)pages_min < pages_free < pages_low,内存压力比较大,剩余内存不多,此时 kswapd0 会执行内存回收,直到 pages_free > pages_high 为止 3)pages_low < pages_free < pages_high,内存有一定压力,但还可以满足新内存请求 4)pages_high < pages_free,剩余内存比较多,没有内存压力
参数 pages_min 通过 /proc/sys/vm/min_free_kbytes 设置,pages_low = pages_min*5/4,pages_high = pages_min*3/2
NUMA 与 Swap
有时候可能会发现 Swap 升高,但是系统内存足够。这种情况可能是因为 处理器的 NUMA (Non-Uniform Memory Access) 架构导致的问题。
NUMA – Non-Uniform Memory Access
在 NUMA 架构下,多个处理器(逻辑处理器)被划分到不同 Node 上,且每个 Node 都拥有自己的本地内存空间。
同个 Node 内部的内存空间,实际上又可以进一步分为不同的内存域(Zone),比如直接内存访问区[……]
「Learning Linux Kernel」- 进程
“session leaders” in `ps`
What are “session leaders” in `ps`? CSDN/The Linux Process Principle, PID、PGID、PPID、SID、TID、TTY
在 Linux 中,每个进程都有若干相关的”数值“:
PID,进程号
用于标识进程的任意数值。每个进程都具有唯一的进程号,在进程退出后且父进程检索退出状态后,会释放进程号以留作新进程使用。
PPID,父进程号
启动进程的进程的进程号。
PGID,进程组号
每个进程都属于某个进程组(Process Group),在每个进程组中可以包含多个进程。每个进程组都会有一个领导进程(Process Group Leader),领导进程的进程号为进程组号,以识别进程组。领导进程可以先退出,但是进程组依然存在,并具有相同的进程组号,直到在进程组中的最后一个进程结束。进程组简化进程操作,可以以组为单位进行管理。
SID,会话号
在Shell支持作业控制(Job Control)的前提下,多个进程组还可以构成单个会话。(1)每个会话有单个或多个进程组组成,可以存在单个领头进程,也可以没有。(2)会话领导进程的进程号会识别为会话号。(3)在会话中的进程组称为作业(Job)。(4)会话可以有一个进程组成为会话的前台作业(Foreground Job),而其他的进程组是后台工作(Background Job)。(5)每个会话可以连接一个控制终端(Control Terminal)。当控制终端有输入输出时,都传递给该会话的前台进程组。由终端产生的信号,比如CTRL+Z、CTRL+C等等,会传递到前台进程组。(6)会话的意义在于将多个作业(进程组)囊括在一个终端,并取其中某个作业作为前台,来直接接收该终端的输入输出以及终端信号,其他作业在后台运行。
会话与进程组只是将相关进程作为管理单元的方式。在进程组里的所有成员总是属于相同会话,但是单个会话中可能有多个进程组。
通常,Shell是单个会话领导,由该Shell执行的管道将是进程组。这便于在退出时结束Shell的子进程。
RGID and EGID, PGID and SGID
GID, current, primary, supplementary, effective and real group IDs?
在 Linux 中:(1)每个用户都具有组号,分为主要组号(Primary Group ID)与附属组号(Supplementary Group ID);(2)每个进程具有所属的用户组号,分为实际组号(Real Group ID)与有效组号(Effective Group ID)。这是两个概念:[……]
「Learning Linux Kernel」- 进程管理(学习笔记)
进程 与 线程
进程,是处于执行期的程序,及其他相关资源(比如 打开的文件、挂起的信号、内核内部数据、一个或多个内存地址空间、一个或多个执行线程、存放全局变量的数据段 等等)的总称。进程不仅指正在执行的程序代码。
线程,是执行线程的简称,是在进程中活动的对象。每个线程具有独立的程序计数器(Program Counter)、线程栈、一组进程寄存器。内核调度的对象是线程,而不是进程,但是 Linux 并不特别区分线程与进程,对于 Linux 而言,线程只是一种特殊进程。
现代操作系统为进程提供两种虚拟机制:虚拟处理器与虚拟内存。虚拟处理器,然进程认为自身独占整个处理器。虚拟内存,让进程认为自己独占全部内存。但是,在 Linux 中,线程是共享虚拟内存的,但是每个线程都拥有各自的虚拟处理器。
进程从创建时开始存活。在 Linux 中,进程使用 fork() 系统调用,通过复制现有进程(父进程),来创建新进程(子进程)。在现代 Linux 内核中,fork() 使用 clone() 系统调用来实现。当 fork() 调用结束时,返回调用 fork() 函数的位置,继续执行后续代码,但是会“返回两次”。但实际上并“不是返回两次”,只是看起来“像返回两次”,因为 fork() 通过复制创建子进程,当 fork() 调用结束后,此时系统中会存在两个相同的进程(至少可执行代码是相同的)。在父进程中,fork() 返回子进程的进程 ID。在子进程中,fork 返回 0,表示创建成功。参考 fork() in C 文章,获取说明示例。
在通常情况下,创建进程是为了执行新的程序,而不是单纯的复制自身。因此,在 fork() 调用之后,我们会接着调用 exec() 函数,用于创建新的地址空间,并将新的程序载入其中以执行。
程序使用 exit() 系统调用退出执行,该函数会终结进程并释放资源。父进程可以通过 wait4() 查询子进程是否终结,这使父进程可以等待子进程执行结束。在进程退出后,被设置为僵死状态,直到父进程调用 wait() 或者 waitpid() 为止。
进程描述符 与 任务结构
在内核中,进程列表保存在名为任务队列(Task List)的双向循环链表中。链表的每一项都是 task_struct 结构(在 /include/linux/sched.h 中),包含具体进程的全部信息,被称为进程描述符(Process Descriptor)。
task_struct(进程描述符、Process Descriptor)
进程描述符相对较大,因为其中包含的数据能够完整的描述一个正在执行的程序:打开的文件,进程地址空间,挂起的信号,进程的状态,以及其他信息。
分配进程描述符
在 Linux 2.6[……]
「Linux」- 进程状态(学习笔记)
相关文档及学习资料
Understanding Linux Process States
关于进程状态关系的图解
Understanding Processes on Linux
R Running or runnable (on run queue) 根据 Understanding Linux Process States 描述,进程具有 Running 与 Runnable 状态,但是 p->state = TASK_RUNNING 状态,即状态字段是相同的。
D Uninterruptible sleep (waiting for some event)
S Interruptible sleep (waiting for some event or signal)
T Stopped, either by a job control signal or because it is being traced by a debugger.
t stopped by debugger during the tracing 当使用调试器(如 gdb)调试某个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是种特殊的暂停状态,只不过可以用调试器来跟踪并按需要控制进程的运行。
Z Zombie process, terminated but not yet reaped by its parent.
I Idle kernel thread 用在不可中断睡眠的内核线程上。硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
使用命令查看进程当前状态
使用命令 ps l 查看进程状态,显示进程休眠或者运行。如果进程正在休眠,WCHAN 列(wait channel,等待队列的名称)显示进程正在等待的内核事件。
# ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 500 5942 5928 15 0 12916 1460 wait Ss pts/14 0:00 -/bin/bash
0 500 12235 5942 15 0 21004 3572 wait S+ pt[……]
「Learning Linux Kernel」- 进程调度(学习笔记)
进程调度,由进程调度程序负责,它是确保内核能够有效工作的子系统,负责在可运行态进程之间分配有限的处理器时间资源。调度程序决定了哪个进程在哪个时间运行多少时间。调度程序是多任务操作系统的基础。只有通过调度程序的合理调度,系统资源才能最大限度的发挥作用,多进程才会出现并发执行的效果。
调度程序,需要最大限度的利用处理器资源,而原则是:只要有可以执行的进程,那么总会有进程在执行。但是只要系统中可运行进程数比处理器个数多,就注定会有进程不能运行(在等待运行)
调度程序,需要完成的基本工作就是:从在一组可运行状态的进程中选择一个来执行。
多任务(Multitasking)
多任务操作系统,即能同时并发地交互执行多个进程的操作系统。在单处理器上,这会产生多个进程在同时运行的幻觉。在多处理器上,这会使多个进程在不同的处理器上真正同时、并行地运行。
多任务操作系统可以使多个进程处于休眠或阻塞状态,也就是说,实际上进程不被投入运行。这些进程虽然存在于内存中,但并不处于可运行状态,他们被阻塞,直到某一事件发生(比如等待键盘输入、网络响应等等)。因此,在操作系统中,虽然存在很多进程,但不是没一个都处于运行或可运行状态。
多任务操作系统分为两类:非抢占式多任务(cooperative multitasking);抢占式多任务(preemptive multitasking)。
与其他现代操作系统一样,Linux 采用抢占式多任务。在抢占式多任务模式下,由调度程序决定什么时候停止一个进程的运行,以便其他进程可以执行。调度程序强制挂起进程的行为就叫做抢占(preemption)。进程在被抢占前能够运行的时间是预先设置好的,这个时间称为进程时间片(timeslice)。时间片是分配给每个可运行进程的处理器时间段。有效的管理时间片能使调度程序从系统全局的角度作出调度决定,还能避免进程独占系统资源。很多现代操作系统都采用动态时间片计算的方式,并且引入可配置的计算策略。但是,Linux 的公平调度程序没有采用时间片的方式来公平调度。
在非抢占式多任务模式下,需要进程主动挂起自己(该操作称之为让步yielding)。缺点也比较明显:进程不受调度程序控制,所以独占处理器时间可能超过用户预料;如果进程挂起而不让步,系统则可能崩溃。不过,近年来的操作系统都是抢占式多任务。
Linux 的进程调度历史
在 Kernel 2.4 以前,Linux 的调度程序都相当简陋,很容易理解,但是在较多可运行进程或多处理环境下表现不佳。
在 Lernel 2.5 版本,开始采用称为 O(1) 调度程序的新调度程序,由算法的行为而得名。它解决了先前的问题,并引入许多强大的新特性与性能特征。这要[……]
「Linux」- 上下文切换(学习笔记)
概念讲解:上下文切换
多任务操作系统,并不是真的在同时运行,而是操作系统在短时间内轮流执行任务,造成多任务同时运行的错觉。
什么是上下文?
而在每个任务切换前,操作系统需要保存当前任务的状态,并设置下个任务的运行环境:CPU Registers, Program Counter
CPU Registers:是 CPU 内置的容量小、但速度极快的内存;
Program Counter:用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置;
它们都是 CPU 在运行任何任务前,必须的依赖环境,因此也被叫做 CPU 上下文。
什么是上下文切换?
就是先把前个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
上下文切换的种类
特权模式切换(系统调用)
按照特权等级,把进程的运行空间分为内核空间和用户空间: 1)内核空间,Ring 0 2)用户空间,Ring 3,只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。
进程在用户空间运行时,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态。通过系统调用来完成。
CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。
系统调用过程通常称为特权模式切换,而不是上下文切换。但实际上,系统调用过程中,CPU 的上下文切换还是无法避免的。
进程上下文切换
进程上下文切换,是指从一个进程切换到另一个进程运行。进程是由内核来管理和调度的,进程的切换只能发生在内核态。进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。
因此,进程上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。另外,Linux 通过 TLB(Translation Lookaside Buffer)来管理虚拟内存到物理内存的映射关系。当虚拟内存更新[……]
「Linux」- 处理器使用率(学习笔记)
多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。
理解概念:节拍率(Timer Interrupt Frequency)
为了维护 CPU 时间,Linux 通过事先定义的节拍率(在内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。
节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。系统不同,设置不同,通过查询 /boot/config 的 CONFIG_HZ 内核选项来查看它的配置值。
用户空间节拍率
节拍率 HZ 是内核选项,所以用户空间程序并不能直接访问。为了方便用户空间程序,内核还提供了一个用户空间节拍率 USER_HZ,它总是固定为 100,也就是 1/100 秒。这样,用户空间程序并不需要关心内核中 HZ 被设置成了多少,因为它看到的总是固定值 USER_HZ。
统计数据:查看处理器的总体使用率
# cat /proc/stat | grep ^cpu
cpu 11424212 19477670 9071090 783703472 711024 0 796129 0 0 0
cpu0 3497583 4899058 2362550 195054993 232905 0 330682 0 0 0
cpu1 1923176 4857103 2082146 197184640 87422 0 108655 0 0 0
cpu2 2074101 4841395 2245553 196715460 180663 0 216623 0 0 0
cpu3 3929351 4880112 2380840 194748377 210033 0 140168 0 0 0
如上命令,仅获取 CPU 相关的指标,含义如下: 1)cpu,表示的是所有 CPU 的累加 2)cpu0, cpu1, …,表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒)
每个字段的含义可以查看 man proc 手册,它们都是 CPU 使用率相关的重要指标,很多工具使用这些数据。各列含义如下(依序): 01)user, us:代表用户态 CPU 时间,但不包括下面的 nice 时间,但包括 guest 时间 02)nice, ni:低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。nice 可取值范围是 -20 到 19,数值越大,优先级反而越低 03)system, sys:代表内核态 CPU 时间 04)idle, id:代表[……]
「Linux」- 平均负载(Load Average)
相关文章及学习资料
Linux Load Averages: Solving the Mystery
概念讲解:系统负载 与 平均负载
当使用 top 或 uptime 命令时,都会输出 load average 字段:
# uptime
23:47:36 up 5 days, 2:13, 5 users, load average: 0.99, 0.66, 0.55
# top
top – 23:47:53 up 5 days, 2:13, 5 users, load average: 1.01, 0.67, 0.55
…
分别代表系统在前一分钟,五分钟,十五分钟的平均负载。
什么是系统负载?
系统负载展示系统的处理器、磁盘、其他资源的繁忙程度。系统负载是操作系统执行计算工作的度量,以数字形式显示。如果计算机完全空闲,则系统负载为 0。每个等待处理器资源或正在使用处理器资源的进程都会向系统负载加 1 。如果系统负载为 5,则表示由五个进程在使用处理器或者等待处理器资源。(注意,UNIX 系统通常只计算等待处理器的进程,而 Linux 还会计算其他的资源(比如等待磁盘的读写)
系统负载通常没有太大的含义:因为它在这一秒为零,那么下一秒可能为五,因为那时可能有五个进程在等待资源。这也是为什么:在 UNIX 中,显示的是某段时间内的平均负载,而不是系统负载。
什么是平均负载?
平均负载则是系统负载在一段时间内的平均值。平均负载一般是三个值 load average: 0.99, 0.66, 0.55 形式。
在几乎所有类 Unix 系统中,平均负载只计算运行或等待状态的进程,所以被称之为“CPU Load Average”。
在 Linux 中,技术上认为平均负载是内核执行队列中标记为 正在运行的进程(p->state = TASK_RUNNING) 或 不可中断的进程(p->state = TASK_UNINTERRUPTABLE) 的平均值,所以被称之为“System Load Average”。可以简单理解为“平均负载其实就是平均活跃进程数”,它实际上是活跃进程数的指数衰减平均值(使用 EMA 算法)。
输出解读:平均负载具体含义
使用 uptime 命令来查看平均负载,也可以通过 watch -n 1 cat /proc/loadavg 文件系统查看。
如果处理器是单核的
我们以 load average: 1.05, 0.70, 5.09 为例。假如你的处理器是单核的,则表示:
在过去的一分钟:机器平均超载 0.05 个,就是说平均有 0.05 个进程在等待处理器资源;
在过[……]
「Linux」- 进程管理
该部分保存了与操作系统进程、资源使用、负载情况相关的内容,及相关的一些概念。
但不包含命令使用方法相关的内容。
# How to Manage Processes from the Linux Terminal: 10 Commands You Need to Know https://www.howtogeek.com/107217/how-to-manage-processes-from-the-linux-terminal-10-commands-you-need-to-know/ top/htop/ps/pstree/kill/pgrep/pkill/killall/renice/xkill
获取进程命令的绝对路径
How can I know the absolute path of a running process?
TODO 获取进程命令的绝对路径
下面的方法不一定好用,毕竟情况复杂:
ps -auxwe | grep 24466
ls -l /proc/24466/exe
pwdx $pid
Windows – 查看进程打开的文件
Which files are opened by a specific application?[……]
「Linux」- 如何结束进程?
问题描述
起初是为了 systemd 的 service 单元文件中的 ExecStop 指令才整理的这篇文章,后来看 systemd 的文档说执行 stop 时,执行完 ExecStop 指令后,未结束的进程会由 systemd 来结束。
本来没有什么可写的,直接使用 kill(1) 命令来结束进程就可以了。但是,由几个有意思的问题: 1)如何结束一个进程的全部子进程? 2)如何结束一个进程及其子进程? 3)我想结束某个组或某个用户的进程该怎么做?
通常结束一个进程的时候,它的子进程不一定会退出,子进程可能会变成“孤儿进程”:
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
所以有的时候,我们就需要结束某个进程以及它的子进程。
我们先从基础的开始吧。
根据进程号,结束进程
在 Linux 中,想要结束一个进程可以直接使用kill(1)命令并指定进程ID(PID)就可以了。比如,我们想要结束PID为3219的进程,只需要执行如下命令:
kill -9 3219
kill -KILL 3219
上面的两个命令是100%完全等价的,只是形式不一样而已,具体参考 kill(1) 手册。唯一需要注意:在 Shell 中,内建的kill命令,参考「注意事项」部分。
根据父进程号,结束子进程
如果想要结束进程所有的子进程的ID,可以使用pkill(1)命令。比如,我们想要结束PID为3219的进程的子进程,只需要执行如下命令:
pkill -KILL -P 3219
该命令可以结束某个进程的全部子进程。
结束进程及其全部子进程
方法一、使用进程组
可以使用kill(1)命令或者pkill(1)命令,配合“进程组”的ID来结束进程。比如进程组的ID为4536如下:
kill -KILL -4536
pkill -KILL -g 4536
注意,这里的“4536”是“进程组”的ID,不是进程ID,不是父进程ID,也不是进程所属组的ID,有一个名词叫做“进程组”(Process Group)。可以用下面的命令来体会组ID(gid)、进程组ID(pgid)及其他ID之间的差异,注意观察各个字段的输出:
ps -o pid,ppid,pgid,gid,sess,cmd -U root
上面的ID分别是进程ID、父进程ID、进程组的ID、进程的组ID、会话、命令。
方法二、使用一点 Shell 命令
但是下面的这条命令应该是一[……]
「Linux」- 限制进程资源
本文介绍如何限制进程资源。
SysV init
以前我们用的是init启动。如果要限制进程资源,可以修改/etc/security/limits.conf文件。
但是呢,我们systemd不吃这个一套。
systemd
在systemd中,如果要限制资源,需要修改/etc/systemd/system.conf与/etc/systemd/system.conf文件。
注意事项,修改/etc/systemd/system.conf后,需要执行systemctl daemon-reexec命令来重新加载配置。
各参数含义
虽然他们读取的配置不会,还存在很多的差异,但是都是使用方式的差异。它们底层控制的资源还是相同的。
下面只是一个概览(有关详细内容及微妙例外情况,强烈建议参考setrlimit(2)手册):
Directive in systemd
Unit in systemd
ulimit equivalent
Description
LimitCPU=
ulimit -t
Seconds
LimitCPU=
ulimit -t
Seconds
LimitFSIZE=
ulimit -f
Bytes
LimitDATA=
ulimit -d
Bytes
LimitSTACK=
ulimit -s
Bytes
LimitCORE=
ulimit -c
Bytes
LimitRSS=
ulimit -m
Bytes
LimitNOFILE=
ulimit -n
Number of File Descriptors
文件描述符的个数
LimitAS=
ulimit -v
Bytes
LimitNPROC=
ulimit -u
Number of Processes
当前用户允许创建的进程数。如果超过或等于该值,在fork(2)[……]
「Linux」- 区分 console、terminal、shell、tty 含义
问题描述
经常听见人们提起 shell、terminal、console、tty 这些术语,但是界限非常模糊,心中不免疑惑:它们只是同种东西的不同名称么?如果不是,那么它们分别指的是什么呢?还有在命令 ps(1) 的输出中的 TTY 列代表着什么?而 pts 又是什么东西呢?我们将带着这些问题,从最易理解、最常用的开始,逐步梳理这些概念并进行区分。
在这个行当里,很多东西(概念)都有一段演变历史。当你知道了这段历史,你就知道了这个东西的由来。有些东西不是直接创造出来的,它们是演变来的。
Shell
Shell 是最容易理解的,之所以这么说是因为我们日常接触的最多、使用的最多。Shell 个程序(简单地说),命令行解释器,它获得键盘输入的命令并交由操作系统来执行。在 Linux 中,类似的程序有 bash,zsh,csh 等等,它们都是 Shell 程序。在 Windows 中,我们熟知的 CMD 程序就属于 Shell 程序。
在维基百科的描述中,Shell 是指访问计算机服务的用户接口,可以是命令行接口(CLI),也可以是图形化接口(GUI),这取决于计算机的用途与角色。之所以称之为 Shell 是因为它是操作系统的最外层。我们常说的 Shell 并不是它的标准定义,而是指命令行接口(CLI)。在过去里,这是用户可与操作系统交互的仅有方式。
但是注意 XTerm,GNOME Terminal,konsole,rxvt 这些并不是 Shell 程序,它们是终端仿真器(后面会介绍)。
TTY, Console and Terminal
它们是文本输入/输出环境。
计算机的早期……
在 1869 年发明 证券报价机,机电设备,由 打字机、很长的电线对、纸带打印机 组成,目的是为了在长距离内实时传送股票价格。这个概念渐渐地进化出更快的、基于 ASCII 码的 电传打字机。
通过大型网络(Telex Network),电传打字机 被连接到世界各地,用于传送商业电报,但是此时的 电传打字机 还没有被链接到计算机上。
与此同时,计算机虽然体积庞大、价格昂贵,但是可以执行多任务、功能强大足以与用户实时交互。
当命令行模式替代旧时的批处理模式时,电传打字机被用作输入输出设备(因为因为它在市面上随处可见)。命令行属于交互模式,自然需要输入输出设备。
在市面上有很多类型的电传打字机,只是存在微小差异,因此需要引入软件兼容性层。在 Unix 中,由操作系统处理低层细节,诸如字长,波特率,流量控制,奇偶校验,基本行编辑的控制代码等。在 1970 年代后期,通过诸如 VT-100 之类的固态视频终端使花式光标移动、色彩输出、其他高级功能成为现实。
终端的使用案例(早期)
终[……]
「TTY」- 在系统启动时,保留控制台输出
在systemd中,如何进行配置?
#!/bin/bash
mkdir /etc/systemd/system/getty@.service.d
cat >/etc/systemd/system/getty@.service.d/noclear.conf <<EOF
[Service]
TTYVTDisallocate=no
EOF
systemctl daemon-reload
参考文献
Stop Clearing My God Damned Console Prevent the console from clearing the screen?[……]
「Llinux」- 修改控制台的字体(TTY,Console)
问题描述
大多数 PC,当启动时,默认使用 8×16 字体,不管屏幕多大。
我们尝试修改终端字体,如图为默认控制台字体(注:该截图来自 Debian 虚拟机,而非终端程序)::
注意,这里的字体指的是控制台字体,与 X Window System 的字体不同的概念。
我们现在目前使用的控制台的字体
修改控制台的显示字体
使用setfont命令修改控制台的字体,该命令属于KBD软件包,可用的字体文件位于/usr/share/consolefonts,如下示例:
shell> setfont drdos8x16
加载448-glyph drdos字体。
持久化字体修改
注意:这里的系统环境为:Debain及其衍生版。
可以修改/etc/default/console-setup文件。有关console-setup文件配置的细节可以参考man 5 console-setup。
或者可以使用dpkg-reconfigure console-setup进行配置,本质上还是修改/etc/default/console-setup文件。
然而,……
有时根本无效,为什么?因为这个文件不像/boot/grub/grub.cfg之类文件,该文件不会在启动流程中自动加载。在/etc/init.d/下有个启动脚本console-setup.sh,通过将这个脚本加入开机启动才行,该脚本会执行setupcon命令(属于console-setup软件包)来解析console-setup文件来设置控制台。所以一定要保证将启动脚本加入了开机启动项:
shell> systemctl enable console-setup.service # 对于使用systemd的系统。
或者
shell> update-rc.d console-setup.sh defaults # 对于使用System V风格启动脚本的系统。
在 Debian 中
# dpkg-reconfigure console-setup
…
Terminuss
…
参考文献
How do I permanently change the console TTY font type so it holds after reboot? BLFS/About Console Fonts[……]
「terminal-colors.d」
与终端彩色输出有关
参考文献
terminal-colors.d(5) Karel Zak’s blog/terminal-colors.d[……]
「LINUX-KERNEL」- 存储栈
问题描述
该笔记将记录:Linux Storage 相关的内容,以及相关问题的解决办法。
解决方案
按照 Linux_Storage_Stack_Diagram 描述,我们将围绕该层次展开 Linux Kernel Storage 相关的学习;
文件系统层(VFS)
包括虚拟文件系统和其他各种文件系统的具体实现。对上,为应用程序提供标准的文件访问接口;对下,会通过通用块层,来存储和管理磁盘数据。
通用块层(Block Layer)
包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层。
设备层
包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。[……]
「LINUX-KERNEL」- VFS Overview
目录项、索引节点、逻辑块、超级块,构成 Linux FS 的四大基本要素;
不过,为了支持各种不同的文件系统,Linux Kernel 又在用户进程和文件系统的中间引入抽象层 —— VFS(虚拟文件系统,Virtual File System)
VFS, Virtual File System
VFS 是对具体文件系统的抽象,负责为应用程序提供所有文件系统的统一访问接口,应用程序通过 VFS 提供的接口来访问文件。用户进程和内核的其他子系统,只需要跟 VFS 提供的统一接口进行交互即可,而不需要再关心底层各种文件系统的实现细节:Userspace ⇒ VFS ⇒ (tmpfs/nfs/fs)
VFS 定义了一组所有文件系统都支持的数据结构和标准接口,所有 Linux FS 都必须按照 VFS 定义的方式来实现;
VFS 仅存在于内存中,需要将硬盘文件系统与 VFS 关联,才能完成对文件系统的管理(最终目的)。方法是:VFS 定义 super_block、dentry、inode 结构,应用程序对 VFS 的这些结构进行操作,进而实现应用对硬盘文件系统的操作;
VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口进行交互就可以了,而不需要再关心底层各种文件系统的实现细节;
System call, VFS, Cache, FS, Block Storage
系统调用、VFS、缓存、文件系统以及块存储之间的关系:
在 VFS 下,Linux 支持各种各样的文件系统,如 Ext4、XFS、NFS 等等。按照存储位置的不同,这些文件系统可以分为三类。
第一类,基于磁盘的文件系统,也就是把数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4、XFS、OverlayFS 等,都是这类文件系统。
第二类,基于内存的文件系统,也就是我们常说的虚拟文件系统。这类文件系统,不需要任何磁盘分配存储空间,但会占用内存。我们经常用到的 /proc 文件系统,其实就是一种最常见的虚拟文件系统。此外,/sys 文件系统也属于这一类,主要向用户空间导出层次化的内核对象。
第三类,网络文件系统,也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等。
应用程序
把文件系统挂载到挂载点后,通过挂载点,再去访问它管理的文件。在底层,VFS 提供了一组标准的文件访问接口,这些接口以系统调用的方式,提供给应用程序使用,应用程序将通过 System Call 来访问 VFS,而不与底层的文件系统直接交互;
例如,常用 cat 命令:它首先调用 open(),打开一个文件。然后调用 read[……]
「Linux」- inode 与 dentry
问题描述
今天有人在问 inode 有关的问题:对于存放大量小文件的磁盘,磁盘空间占用不多,但是 inode 占用比较多,问有没有什么好的处理方法?
该笔记将记录:在 Linux 中,与 inode 的内容,以及常见问题的解决方案;
解决方案
为了方便管理,Linux FS 为每个文件都分配两个数据结构: 1)index node(索引节点,inode):记录文件的元信息 2)directory entry(目录项,dentry):记录目录结构;
索引节点(inode)
索引节点,简称为 inode,
作用
1)inode 用来记录文件的元数据,比如 inode Number、文件大小、访问权限、修改日期、数据的位置等;但其并不存储文件名; 2)inode 与文件一一对应,每个文件对应一个 inode,即 inode 是每个文件的唯一标志;
形态
1)inode 是存储在磁盘中的数据。虽然 inode 也有内存缓存,其为加速访问,但是 inode 最终是存放在磁盘中的。
2)它跟文件内容一样,都会被持久化存储到磁盘中,所以 inode 同样占用磁盘空间;每个 inode 节点的大小,一般是 128 Byte 或 256 Byte,而 inode 的总数,在格式化时就已给定;
索引编号(inumber)
每个「inode」都有一个号码,操作系统用「索引编号」来识别不同的文件;
这里值得重复一遍,在 Unix/Linux 系统的内部,并不使用文件名,而使用「索引编号」来识别文件。对于系统来说,「文件名」只是「索引编号」便于识别的别称;
查看文件的「索引编号」:ls -i .bashrc
命令操作
查看 inode 的使用情况:df -i
查看 inode 的大小:dumpe2fs -h /dev/hda | grep “Inode size”
调整 inode 的数量: 1)在格式化时,需要提前确定,通常命令有相应的选项,参考文档即可。 2)在使用过程中,若出现 inode 数量不足:如果无法扩容磁盘,则要备份数据,重新格式化磁盘,并指定 inode 的数量;
目录项(dentry)
目录项,简称为 dentry;
作用
1)dentry 用来记录文件的名字、inode 指针; 2)记录其与其他 dentry 的关联关系:多个关联的目录项,就构成文件系统的目录结构。即 dentry 维护的正是文件系统的树状结构;
形态
其与 inode 不同,dentry 是由内核维护的一个内存数据结构,所以通常也被叫做 dentry cache(目录项缓存)。dentry 只是内存中的一个数据结构,一个内存缓存,是通[……]
「Linux」- 文件系统 I/O
问题描述
VFS 提供一组标准的文件访问接口。这些接口以系统调用的方式,提供给应用程序使用。比如 open() 打开文件,read() 读取文件,write() 写入文件。也就是说,我们操作的是 VFS 而不是实际的文件系统(ext4、xfs、……)
文件读写方式的各种差异,导致 I/O 的分类多种多样。接下来,我们就将进一步学习文件系统的 IO 类型;
解决方案
最常见的有,缓冲与非缓冲 I/O、直接与非直接 I/O、阻塞与非阻塞 I/O、同步与异步 I/O 等;
补充说明:部分概念也经常出现在网络编程中。比如非阻塞 I/O,通常会跟 select/poll 配合,用在网络套接字的 I/O 中;
缓冲与非缓冲(是否利用标准库缓存)
缓冲 I/O
是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件;
注意,这里的缓冲是由标准库内部实现的缓冲。比如,有些程序遇到换行时才真正输出,而换行前的内容,其实就是被标准库暂时缓存; 但是,它们最终还是要经过系统调用来访问文件。当系统调用后,还会通过页缓存,来减少磁盘的 I/O 操作;
非缓冲 I/O
是指直接通过系统调用来访问文件,不再经过标准库缓存;
直接与非直接(是否利用操作系统的页缓存)
直接 I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。比如,指定 O_DIRECT 标志;
非直接 I/O,当文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘;
直接 I/O、非直接 I/O,本质上还是和文件系统交互。但是,在某些数据库等场景中,会跳过文件系统直接读写磁盘,也就是我们通常所说的裸 I/O;
阻塞与非阻塞(应用程序是否阻塞自身运行)
Blocking I/O
阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执行其他任务;
Non-blocking I/O
非阻塞 I/O,是指: 1)应用程序执行 I/O 操作后,不会阻塞当前的线程,并继续执行其他的任务; 2)随后再通过 轮询 或 事件通知 的形式,获取调用的结果。比如访问管道或者网络套接字时,设置 O_NONBLOCK 标志,就表示用非阻塞方式访问;
同步与异步(是否等待响应结果,即通知方式)
Synchronous I/O
是指应用程序执行 I/O 操作后,要一直等到整个 I/O 完成后,才能获得 I/O 响应;
在操作文件时,如果设置 O_SYNC 或 O_DSYNC 标志,就代表同步 I/O; 1)如果设置 O_DSYNC,就要等文件数据写入磁盘后,才能返回; 2)而 O_SYNC,则是在 O_DSYNC[……]
「操作系统」- 文件截断
这个问题是由群里的一个问题引发的:如何截断日志文件?后来有人说,使用>>写入的文件,可以直接截断。
相关关键字
清空文件
> 与 >>>
O_APPEND 与 O_WRONLY
lsof -nP +f g | grep ,AP
相关连接
# 下面的几篇帖子要好好读一下 Emptying a file without disrupting the pipe writing to it Linux Programmer’s Manual/open, openat, creat – open and possibly create a file What happens when I truncate a file that is in use?
# 这些文章也需要读一下 how to empty or truncate a file in linux
# 这几篇帖子看看就好了 Truncating a file while it’s being used (Linux) Bash: Difference between > and >> operator?[……]
「Linux」- 文件系统(学习笔记)
磁盘,为系统提供了最基本的持久化存储;
在格式化硬盘时,硬盘被分成三个区域: 1)superblock(超级块): 1)inode table(索引节点区):存放所有 inode; 2)数据区:存放文件数据;
文件系统,则在磁盘的基础上,提供了一个用来管理文件的树状结构。
super_block
简单说,VFS 负责提供架构,而具体文件系统必须按照 VFS 的架构去实现 —— super_block(超级块)是对该观点的体现。
解释说明
VFS super_block,对应着具体文件系统的控制块结构(超级块),就是说每个具体的文件系统都要实现超级块。VFS super_block 通过读取具体文件系统的控制块接口,来填充其内容,即 VFS super_block 是具体文件系统的控制块的内存抽象(内存表示)。
VFS super_block,用以记录当前文件系统整体的状态。例如:inode 和逻辑块的使用情况;super_block 操作函数;当前文件系统的特有信息;
struct super_block {}
1)该结构包含重要当前文件系统的全局信息:
struct super_block {
unsigned long s_blocksize; // 文件系统块大小
unsigned char s_blocksize_bits;
… // 省略超级块的链表、设备号等代码
unsigned long long s_maxbytes; // 最大文件的大小
struct file_system_type *s_type; // 指向 struct file_system_type 指针
struct super_operations *s_op;
unsigned long s_magic; // 每个文件系统都由的魔数数字
struct dentry *s_root; // 指向文件系统的 root dentry 指针
struct list_head[……]