聊聊linux的文件缓存

本文详细探讨了Linux系统中pagecache、dcache和inode缓存的原理,以及如何通过vmstat、free和meminfo监控。此外,还介绍了sysctl参数如vm.vfs_cache_pressure、vm.swappiness和vm.dirty_系列参数的调整方法,以及drop_caches的使用来管理缓存和保护数据安全。

本文主要研究一下linux的文件缓存

文件缓存

linux使用page cache来缓存最近读取的文件,也有目录结构(dcache: Directory Entry Cache)缓存及inode缓存,它们都使用了LRU算法来管理这些page及dentries cache

vmstat

## vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0 5076692 1664208   1032 35263320    0    0     0     4    0    0  0  0 99  0  0

vmstat显示的memory中的cache部分即是文件缓存,单位为kb

free

free -m
              total        used        free      shared  buff/cache   available
Mem:          64038       27974        1619        3208       34444       32370
Swap:         32127        4957       27170

free命令显示的buff/cache部分包含了buffer memory及swap cache

meminfo

cat /proc/meminfo | grep ^Cached
Cached:          2942600 kB

/proc/meminfo中的Cached展示了page cache大小

配置

/etc/sysctl.conf包含我们可以在运行时设置的系统范围内的内核参数,sysctl只是临时改动,如果用使得参数变更持久化则需要更改到这个文件。要在不重新启动的情况下应用/etc/sysctl.conf文件中的更改,我们可以使用sysctl的-p开关

sudo sysctl -p

vm.vfs_cache_pressure

vm.vfs_cache_pressure用来控制内核回收page与dentries(dcache及inode cache)的倾向性。

  • 默认值100,即内核自己根据情况将二者在一个合理的比例
  • 较低的值(低于100)则使其倾向于回收page cache,来保留dentries cache。
  • 较高的值(高于100)会使内核更倾向于回收dentries cache,来保留page cache。

如下设置则让内核倾向于回收page cache

sudo sysctl -w vm.vfs_cache_pressure=50

vm.swappiness

用于控制使用swap分区的倾向性

  • 默认值为60,意味着内核会在RAM使用率达到60%(可用RAM低于40%)时开始使用swap分区
  • 较低的值则倾向使用RAM,尽量少使用swap分区,比如0则表示内核在RAM(Random Access Memory)严重不足时才使用swap分区,即page cache尽可能得缓存在了RAM
  • 较高的值则倾向使用swap分区,比如100则表示内核非常积极使用swap分区,即使还有比较多的RAM可用,即page cache更多的是在swap分区

如下设置则让内核尽可能使用RAM

sudo sysctl -w vm.swappiness=10

vm.dirty相关参数

sysctl -a | grep dirty
vm.dirty_background_ratio = 10
vm.dirty_background_bytes = 0
vm.dirty_ratio = 20
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500

这些参数用于控制脏数据(已经被修改但是未写入到硬盘)何时写入硬盘
如果我们用_bytes设置vm.的值,则vm._ratio变量将变为0,反之亦然
除了通过sysctl -a,也可以通过cat /proc/sys/vm/dirty_background_ratio这样子来查看

vm.dirty_background_ratio与vm.dirty_background_bytes

用于设置内存中可以存储脏数据的最大数量。例如,如果你的系统有32GB的内存,并且vm.dirty_background_ratio设置为10,那么最多可以有3.2GB的脏数据存储在内存中,超过这个数值后,就会触发脏数据的写入操作;而vm.dirty_background_bytes则是指定了绝对值,单位bytes

sudo sysctl -w vm.dirty_background_ratio=10
sudo sysctl -w vm.dirty_background_bytes=511870912

vm.dirty_ratio与vm.dirty_bytes

用来设置整个系统可以存储脏数据的最大数量的。例如,如果你的系统有32GB的内存,并且vm.dirty_ratio设置为20,那么最多可以有6.4GB的脏数据存储在整个系统中,超过这个数值后,就会触发脏数据的写入操作;而vm.dirty_bytes则是指定了绝对值,单位bytes;与vm.dirty_background_ratio的区别在于当系统到达此点时,必须将所有脏数据提交到磁盘,同时所有新的I/O块都会被阻塞,直到脏数据被写入磁盘

sudo sysctl -w vm.dirty_ratio=20
sudo sysctl -w vm.dirty_bytes=511870912

vm.dirty_writeback_centisecs与vm.dirty_expire_centisecs

在断电的情况下,缓存在系统内存中的数据有丢失的风险。因此,为了保护系统免于数据丢失_centisecs系列的参数决定了将数据写入辅助存储的时间和频率。
vm.dirty_expire_centisecs管理数据在写入驱动器之前在缓存中可以存活多长时间。

如下设置可以在缓存中保留40秒,1s等于100 centisecs,当pdflush/flush/kdmflush在运行的时候,他们会检查是否有数据超过这个时限,如果有则会把它异步地写到磁盘中

sudo sysctl -w vm.dirty_expire_centisecs=4000

vm.dirty_writeback_centisecs是后台进程检查是否有数据要写入辅助存储的频率,即指定多长时间pdflush/flush/kdmflush这些进程会唤醒一次,然后检查是否有缓存需要清理。该值越低,频率越高。

如下配置为每5秒检查一次缓存

sudo sysctl -w vm.dirty_writeback_centisecs=500

删除cache

我们可以通过往/proc/sys/vm/drop_caches写入指定的值来指定怎么删除cache

删除示例

# 查看当前的drop_caches值
cat /proc/sys/vm/drop_caches

# 将drop_caches值设置为1,释放页缓存
echo 1 > /proc/sys/vm/drop_caches

# 将drop_caches值设置为2,释放目录项和节点缓存
echo 2 > /proc/sys/vm/drop_caches

# 将drop_caches值设置为3,释放页缓存、目录项和节点缓存
echo 3 > /proc/sys/vm/drop_caches

这是一个非破坏性的操作,并且不会释放脏对象的内存。因此,在进行此操作前,需要先运行sync命令,确保所有未写的系统缓冲区都已经被写入到磁盘中,包括已修改的i节点、已延迟的块I/O和读写映射文件

删除失败

vmtouch可以将文件内容锁定在内存中,因而drop cache可能没有效果,比如

vmtouch -tl test.dat
LOCKED 492712 pages (1G)

cat /proc/meminfo | grep ^Cached &&
sudo bash -c 'sync; echo 3 > /proc/sys/vm/drop_caches' &&
cat /proc/meminfo | grep ^Cached

Cached:          3336092 kB
Cached:          3169224 kB

可以看到这里无法释放锁定的cache,可以通过memlock来限定每个进程最大锁定的大小(/etc/security/limits.conf)

配置

*                hard    memlock     unlimited
*                soft    memlock     unlimited

查看

ulimit -Ha | grep locked
max locked memory           (kbytes, -l) 2033684

小结

linux使用page cache来缓存最近读取的文件,也有目录结构(dcacheDirectory Entry Cache)缓存及inode缓存,它们都使用了LRU算法来管理这些page及dentries cache

  • 可以通过vmstat、free、/proc/meminfo来查看
  • 可以通过vm.vfs_cache_pressure、vm.swappiness、vm.dirty_*来配置
  • 可以通过设置/proc/sys/vm/drop_caches的值来进行删除

doc

<think>首先,用户的问题是:“我想了解互斥锁的概念和应用 请问互斥锁是什么 如何使用互斥锁解决并发问题”。根据系统级指令,我需要: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 - 参考站内引用:我有几个引用:[^1], [^2], [^3], [^4], [^5],这些是关于互斥锁的引用。我需要自然地融入这些引用。 引用内容总结: - [^1]: 介绍了使用互斥锁实现线程互斥访问,避免竞态条件和数据不一致。 - [^2]: 互斥锁的特点:独占性、阻塞和等待、原子性。 - [^3]: 在Linux中,互斥锁的使用,lock和trylock的区别。 - [^4]: 互斥锁保护共享资源,如get()和addOne()方法需要访问受保护的资源。 - [^5]: 在嵌入式系统中使用互斥锁和信号量的伪代码。 我的回答应该覆盖互斥锁的概念和应用,并使用这些引用支持。 回答结构: 1. **介绍互斥锁的概念**:解释什么是互斥锁,为什么需要它。 2. **互斥锁的工作原理**:基于引用描述其特点。 3. **如何使用互斥锁解决并发问题**:给出例子或伪代码,参考引用。 4. **应用场景**:讨论互斥锁的应用,如多线程编程。 5. **生成相关问题**:在结束时添加相关问题。 使用中文回答。 确保LaTeX正确:用户提到数学表达式,但互斥锁主题可能不涉及太多数学。如果有,如描述原子操作,可能需要数学表达式。例如,原子性可以用数学表示,但可能不需要。保持简单。 步骤: - **开头**:直接回答用户问题,介绍互斥锁。 - **主体**:分步骤解释概念和应用。 - **结尾**:总结并生成相关问题。 融入引用: - 在解释概念时,引用[^1]和[^2]。 - 在讨论使用时,引用[^3]和[^4]。 - 在嵌入式应用,引用[^5]。 生成相关问题:像示例一样,在最后添加“§§相关问题§§”部分,列出几个问题。 最终输出结构: - 回答内容。 - §§相关问题§§ - 相关问题列表。</think>### 互斥锁的概念和应用 互斥锁(Mutex)是一种在多线程或多进程编程中用于实现并发控制的同步机制。它的核心目的是确保在任意时刻,只有一个线程或进程可以访问共享资源(如变量、文件或数据结构),从而避免竞态条件(race conditions)和数据不一致的问题。当多个线程同时尝试修改共享数据时,如果没有互斥锁,可能会导致数据损坏或程序崩溃。互斥锁通过提供“独占访问”的机制来解决这一问题,类似于一个房间的门锁:只有持有钥匙的线程才能进入“临界区”(critical section),其他线程必须等待锁被释放后才能尝试获取[^1]。 #### 互斥锁的核心特点 互斥锁的工作原理基于几个关键特性,这些特性确保了其有效性和可靠性: - **独占性**:当一个线程成功获取互斥锁后,其他线程无法同时获得该锁,必须等待锁被释放。这保证了共享资源在同一时间只被一个线程访问[^2]。 - **阻塞和等待**:如果一个线程尝试获取已被其他线程持有的互斥锁,它会被阻塞(即进入等待状态),直到锁被释放。这避免了线程在资源冲突时继续执行,从而防止数据错误[^2]。 - **原子性**:互斥锁的获取(lock)和释放(unlock)操作是原子的,意味着这些操作不会被中断或分割。这确保了锁操作的完整性和一致性,不会出现部分执行导致的并发问题[^2]。 在数学上,互斥锁的原子性可以用一个简单的模型表示:假设锁的状态 $S$ 是一个二元变量(0表示未锁定,1表示锁定),则获取锁的操作可以描述为: $$ \text{lock}() : \begin{cases} \text{if } S = 0, & \text{set } S = 1 \\ \text{else}, & \text{wait} \end{cases} $$ 释放锁的操作则是: $$ \text{unlock}() : \text{set } S = 0 $$ 这些操作在底层硬件或操作系统支持下实现原子性,确保并发安全[^3]。 #### 如何使用互斥锁解决并发问题 使用互斥锁解决并发问题通常涉及以下步骤,我将通过一个简单的代码示例(基于C语言)来说明。假设我们有一个共享变量 `counter`,多个线程可能同时修改它,我们需要互斥锁来保护这个变量。 1. **定义和初始化互斥锁**:在程序开始时,创建一个互斥锁对象。 2. **在临界区前获取锁**:在访问共享资源的代码段(临界区)前,调用锁获取函数。如果锁已被占用,线程会阻塞等待。 3. **执行临界区操作**:安全地修改共享资源。 4. **释放锁**:操作完成后立即释放锁,允许其他线程获取。 5. **处理锁的获取方式**:根据需求选择阻塞式(lock)或非阻塞式(trylock)。lock会让未抢到锁的线程等待,而trylock会立即返回状态(成功或失败),避免死锁风险[^3]。 以下是一个伪代码示例,演示如何用互斥锁保护一个计数器: ```c #include <pthread.h> pthread_mutex_t lock; // 定义互斥锁 int counter = 0; // 共享变量 void* increment_counter(void* arg) { for (int i = 0; i < 1000; i++) { pthread_mutex_lock(&lock); // 获取锁(阻塞式) counter++; // 临界区操作 pthread_mutex_unlock(&lock); // 释放锁 } return NULL; } int main() { pthread_mutex_init(&lock, NULL); // 初始化互斥锁 pthread_t thread1, thread2; pthread_create(&thread1, NULL, increment_counter, NULL); pthread_create(&thread2, NULL, increment_counter, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_mutex_destroy(&lock); // 销毁锁 printf("Final counter value: %d\n", counter); // 应输出2000 return 0; } ``` 在这个例子中,互斥锁确保 `counter++` 操作是原子的:线程1获取锁后执行自增,线程2必须等待锁释放后才能进入临界区。这样,最终结果总是2000,而不会因并发冲突导致数据错误(如输出小于2000)[^4]。 在实际应用中,互斥锁常用于: - **多线程数据共享**:如数据库操作、文件读写或缓存更新,确保数据一致性[^1]。 - **嵌入式系统**:在RT-Thread等实时操作系统中,结合信号量(semaphore)处理多路并发。例如,一个传感器采集线程获取互斥锁后,独占CPU资源读取数据,然后释放锁,避免多个传感器同时访问冲突[^5]。 - **分布式系统**:扩展到分布式锁,协调多个节点对共享资源的访问。 #### 注意事项和最佳实践 - **避免死锁**:确保锁的获取和释放成对出现,避免嵌套锁或长时间持有锁。 - **性能考虑**:锁操作可能引入开销,在高并发场景下,可优化为细粒度锁或使用无锁数据结构。 - **与信号量结合**:互斥锁(用于互斥)常与信号量(用于同步)配合,解决更复杂的并发问题,如生产者-消费者模型[^5]。 总之,互斥锁是并发编程的基础工具,通过强制独占访问来保护共享资源,确保程序正确性和可靠性[^1][^2]。在实际开发中,应根据语言和平台(如Linux的pthread库或嵌入式RT-Thread)选择合适的实现。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值