Linux 内核自旋锁spinlock(一)

文章目录

  • 前言
  • 一、自旋锁
    • 1.1 简介
    • 1.2 API
      • 1.2.1 spin_lock/spin_unlock
      • 1.2.2 spin_lock_irq/spin_unlock_irq
      • 1.2.3 spin_lock_irqsave/spin_unlock_irqstore
      • 1.2.4 spin_lock_bh/spin_unlock_bh
      • 1.2.5 补充
  • 二、自选锁原理
  • 三、自旋锁在内核的使用
    • 3.1 struct file
    • 3.2 struct dentry
    • 3.3 struct inode
    • 3.4 struct super_block
  • 参考资料

前言

在软件工程中,自旋锁是一种用于多线程应用程序中的同步原语,用于保护共享资源。当一个线程尝试获取自旋锁但发现它已被另一个线程持有时,该线程不会立即进入睡眠状态(如传统互斥锁的情况),而是会在一个循环中持续“自旋”,不断检查锁是否可用。这种行为被称为忙等待。

自旋锁能够避免操作系统进程重新调度或上下文切换的开销,因此在线程可能仅会短暂阻塞的情况下,自旋锁非常高效。出于这个原因,操作系统内核经常使用自旋锁。

对于操作系统内核:
在SMP系统上,各个CPU可能同时处于核心态,在理论上可以操作所有现存的数据结构。为阻止CPU彼此干扰,需要通过锁保护内核的某些范围。锁可以确保每次只能有一个CPU访问被保护的范围。

内核可以不受限制地访问整个地址空间。在多处理器系统上(或类似地,在启用了内核抢占的单处理器系统上),这会引起一些问题。如果几个处理器同时处于核心态,则理论上它们可以同时访问同一个数据结构。

一、自旋锁

1.1 简介

内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:
(1)一个是原地等待
(2)一个是挂起当前进程,调度其他进程执行(睡眠)
自旋锁就是原地等待,直到锁释放。

中断上下文要用锁,首选 spinlock。

自旋锁:这些是最常用的锁选项。它们用于短期保护某段代码,以防止其他处理器的访问。在内核等待自旋锁释放时,会重复检查是否能获取锁,而不会进入睡眠状态(忙等待)。当然,如果等待时间较长,则效率显然不高。

自旋锁用于保护短的代码段,其中只包含少量C语句,因此会很快执行完毕。大多数内核数据结构都有自身的自旋锁,在处理结构中的关键成员时,必须获得相应的自旋锁。自旋锁在内核源代码中普遍存在。如下图所示:
在这里插入图片描述
当运行任务 B 的 CPU(B) 想要通过自旋锁的锁定函数获取锁,而这个自旋锁已经被另一个 CPU(例如运行任务 A 的 CPU(A))持有时,CPU(B) 将简单地在一个循环中旋转,从而阻塞任务 B,直到另一个 CPU 释放该锁(任务 A 调用自旋锁的释放函数)。这种自旋只会发生在多核机器上,这就解释了之前描述的使用情况,因为涉及多个 CPU,所以在单核机器上不会发生:任务要么持有自旋锁并继续执行,要么直到锁被释放才能运行。自旋锁是由 CPU 持有的锁,这与互斥锁相反,互斥锁是由任务持有的锁。自旋锁通过禁用本地 CPU 上的调度程序运行(即运行调用自旋锁的锁定 API 的任务的 CPU)。这也意味着当前在该 CPU 上运行的任务不能被另一个任务抢占,除非 IRQs 被禁用(稍后会详细讨论)。换句话说,自旋锁保护只能由一个 CPU 一次获取/访问的资源。这使得自旋锁适用于 SMP 安全性和执行原子任务。

自旋锁并不是唯一利用硬件原子功能的实现方式。例如,在 Linux 内核中,抢占状态取决于每个 CPU 变量,如果等于 0,则表示抢占已启用。然而,如果大于 0,则表示抢占已禁用(schedule() 变为无效)。因此,禁用抢占(preempt_disable())包括向当前每个 CPU 变量(实际为 preempt_count)添加 1,而 preempt_enable() 则从变量中减去 1,检查新值是否为 0,并调用 schedule()。这些加法/减法操作应该是原子的,因此依赖于 CPU 能够提供原子加法/减法功能。

1.2 API

在这里插入图片描述

1.2.1 spin_lock/spin_unlock

最常用的就是 spin_lock/spin_unlock 这一对API了,其使用:
动态定义:

spinlock_t lock;  //定义一个自旋锁变量
spin_lock_init(&lock)  //初始化自旋锁

静态定义:

DEFINE_SPINLOCK(lock)

使用:

spin_lock(&lock);  //加锁
//临界区
spin_unlock(&lock);  //解锁
// include/linux/spinlock_types.h#define DEFINE_SPINLOCK(x)	spinlock_t x = __SPIN_LOCK_UNLOCKED(x)
// include/linux/spinlock.h# define spin_lock_init(_lock)			\
do {						\spinlock_check(_lock);			\*(_lock) = __SPIN_LOCK_UNLOCKED(_lock);	\
} while (0)
// include/linux/spinlock.h#define raw_spin_lock(lock)	_raw_spin_lock(lock)static __always_inline void spin_lock(spinlock_t *lock)
{raw_spin_lock(&lock->rlock);
}
// kernel/locking/spinlock.cvoid __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{__raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);
//  /include/linux/spinlock_api_smp.hstatic inline void __raw_spin_lock(raw_spinlock_t *lock)
{preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

reempt_disable(): 禁用抢占,这将增加抢占计数并阻止当前 CPU 上的任务被抢占。
spin_acquire(): 这个函数调用用于表示获取自旋锁。
LOCK_CONTENDED(): 这个宏用于处理自旋锁在被占用时的情况。根据情况,可能会调用 do_raw_spin_trylock 或者 do_raw_spin_lock 函数来尝试获取自旋锁或者直接获取自旋锁。

static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{__acquire(lock);arch_spin_lock(&lock->raw_lock);mmiowb_spin_lock();
}
// /include/asm-generic/qspinlock.h#define arch_spin_lock(l)		queued_spin_lock(l)

流程图如下:
在这里插入图片描述
这种方式使用自旋锁是有一些局限性的。虽然自旋锁可以防止本地 CPU 的抢占,但它无法防止该 CPU 被中断“霸占”(即执行中断处理程序)。想象一种情况,CPU 持有一个“自旋锁”来保护某个资源,并发生了一个中断。CPU 将停止当前任务并跳转到这个中断处理程序。到目前为止,一切都很好。现在,想象一下这个中断处理程序需要获取同样的自旋锁(你可能已经猜到这个资源是与中断处理程序共享的)。它将会在原地无限自旋,试图获取一个已经被被抢占了的任务所持有的锁。这种情况被称为死锁。

为了解决这个问题,Linux 内核提供了针对自旋锁的 _irq 变种函数,除了禁用/启用抢占外,还会在本地 CPU 上禁用/启用中断。这些函数包括 spin_lock_irq() 和 spin_unlock_irq()。通过使用这些函数,可以在保护共享资源时,同时考虑到中断处理程序对于自旋锁的需求,从而避免了死锁的发生。

1.2.2 spin_lock_irq/spin_unlock_irq

#define raw_spin_lock_irq(lock)		_raw_spin_lock_irq(lock)static __always_inline void spin_lock_irq(spinlock_t *lock)
{raw_spin_lock_irq(&lock->rlock);
}
void __lockfunc _raw_spin_lock_irq(raw_spinlock_t *lock)
{__raw_spin_lock_irq(lock);
}
EXPORT_SYMBOL(_raw_spin_lock_irq);
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{local_irq_disable();preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

local_irq_disable(): 这个函数用于在本地 CPU 上禁用中断,这可以防止当前 CPU 上的中断处理程序运行,从而确保在获取自旋锁期间不会被中断。
reempt_disable(): 禁用抢占,这将增加抢占计数并阻止当前 CPU 上的任务被抢占。
spin_acquire(): 这个函数调用用于表示获取自旋锁。
LOCK_CONTENDED(): 这个宏用于处理自旋锁在被占用时的情况。根据情况,可能会调用 do_raw_spin_trylock 或者 do_raw_spin_lock 函数来尝试获取自旋锁或者直接获取自旋锁。

_irq 变种函数只能部分解决这个问题。想象一下,在您的代码开始锁定之前,处理器上的中断已经被禁用。因此,当您调用 spin_unlock_irq() 时,不仅会释放锁,还会启用中断。然而,这可能会以错误的方式发生,因为 spin_unlock_irq() 无法知道在锁定之前哪些中断是已启用的,哪些是未启用的。

以下是一个简短的示例:
(1) 假设在获取自旋锁之前,中断 x 和 y 已被禁用,而 z 没有被禁用。
(2)spin_lock_irq() 会禁用这些中断(现在 x、y 和 z 都被禁用)并获取锁。
(3)spin_unlock_irq() 会启用这些中断。现在 x、y 和 z 都被启用了,而在获取锁之前不是这样。这就是问题所在。

这使得在从上下文中调用的 IRQ 中使用 spin_lock_irq() 不安全,因为它的对应函数 spin_unlock_irq() 会简单地启用 IRQ,存在启用那些在调用 spin_lock_irq() 时未被启用的 IRQ 的风险。只有在您知道中断已经被启用时才使用 spin_lock_irq() 才有意义;也就是说,您确定没有其他东西在本地 CPU 上禁用了中断。

在这种情况下,想象一下在获取锁之前将中断的状态保存在一个变量中,然后在释放锁时将中断恢复到获取锁时的状态。这样一来,就不会再有问题了。为了实现这一点,内核提供了 _irqsave 变种函数。这些函数的行为类似于 _irq 函数,同时还保存和恢复中断状态的特性。这些函数包括 spin_lock_irqsave() 和 spin_unlock_irqrestore()。

1.2.3 spin_lock_irqsave/spin_unlock_irqstore

#define spin_lock_irqsave(lock, flags)				\
do {								\raw_spin_lock_irqsave(spinlock_check(lock), flags);	\
} while (0)
#define raw_spin_lock_irqsave(lock, flags)			\do {						\typecheck(unsigned long, flags);	\flags = _raw_spin_lock_irqsave(lock);	\} while (0)
unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
{return __raw_spin_lock_irqsave(lock);
}
EXPORT_SYMBOL(_raw_spin_lock_irqsave);
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{unsigned long flags;local_irq_save(flags);preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);/** On lockdep we dont want the hand-coded irq-enable of* do_raw_spin_lock_flags() code, because lockdep assumes* that interrupts are not re-enabled during lock-acquire:*/
#ifdef CONFIG_LOCKDEPLOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
#elsedo_raw_spin_lock_flags(lock, &flags);
#endifreturn flags;
}

local_irq_save(): 这个函数用于在本地 CPU 上保存当前中断状态到 flags 变量中,然后禁用中断。
preempt_disable(): 禁用抢占,这会增加抢占计数并阻止当前 CPU 上的任务被抢占。
spin_acquire(): 这个函数调用用于表示获取自旋锁。
LOCK_CONTENDED(): 这个宏用于处理自旋锁在被占用时的情况。根据情况,可能会调用 do_raw_spin_trylock 或者 do_raw_spin_lock 函数来尝试获取自旋锁或者直接获取自旋锁。

备注:
凡是用到 spin_lock_irq()\spin_unlock_irq() 都可以用 spin_lock_irqsave()\spin_unlock_irqrestore() 替换,根据使用情况决定选择哪种方式即可,例如希望中断执行完成后,所有的中断都要开启,那就选择 spin_lock_irq()\spin_unlock_irq(),如果希望中断执行完成后,只需要恢复执行前的中断开关状态,那么就选择 spin_lock_irqsave()\spin_unlock_irqrestore(),如执行前 A中断 本来就要求关闭的,那么执行完之后,还是希望 A中断 仍处于关闭状态。

1.2.4 spin_lock_bh/spin_unlock_bh

#define raw_spin_lock_bh(lock)		_raw_spin_lock_bh(lock)static __always_inline void spin_lock_bh(spinlock_t *lock)
{raw_spin_lock_bh(&lock->rlock);
}
void __lockfunc _raw_spin_lock_bh(raw_spinlock_t *lock)
{__raw_spin_lock_bh(lock);
}
EXPORT_SYMBOL(_raw_spin_lock_bh);
static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
{__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

__local_bh_disable_ip(): 这个函数用于在本地 CPU 上禁用软中断。
spin_acquire(): 这个函数调用用于表示获取自旋锁。
LOCK_CONTENDED(): 这个宏用于处理自旋锁在被占用时的情况。根据情况,可能会调用 do_raw_spin_trylock 或者 do_raw_spin_lock 函数来尝试获取自旋锁或者直接获取自旋锁。

spin_lock_bh/spin_unlock_bh 这对API用来关闭中断底半部,即关闭软中断。

1.2.5 补充

spin_lock() 及其所有变体会自动调用 preempt_disable(),这会在本地 CPU 上禁用抢占。另一方面,spin_unlock() 及其变体会调用 preempt_enable(),尝试启用抢占(是的,尝试!——这取决于其他自旋锁是否被锁定,这将影响抢占计数器的值),并在启用时(取决于计数器的当前值,应为 0)内部调用 schedule()。然后,spin_unlock() 是一个抢占点,可能会重新启用抢占。

禁用中断可能会防止内核抢占(调度器的定时器中断会被禁用),但并没有阻止受保护部分调用调度器(schedule() 函数)。许多内核函数间接调用调度器,比如处理自旋锁的函数。因此,即使是一个简单的 printk() 函数也可能会调用调度器,因为它涉及到保护内核消息缓冲区的自旋锁。内核通过增加或减少一个全局变量和每个 CPU 的变量(默认为 0,表示“启用”)来禁用或启用调度器(执行抢占),这个变量称为 preempt_count。当这个变量大于 0 时(由 schedule() 函数检查),调度器简单地返回并不执行任何操作。每当调用与 spin_lock* 相关的辅助函数时,这个变量就会增加 1。另一方面,释放自旋锁(任何 spin_unlock* 函数)会将其减少 1,每当它达到 0 时,调度器就会被调用,这意味着您的临界区可能不是原子化的。

因此,如果代码本身不会触发抢占,那么只能通过禁用中断来保护代码免受抢占。也就是说,已经锁定自旋锁的代码可能无法休眠,因为没有办法唤醒它(请记住,在本地 CPU 上定时器中断和调度器都被禁用)。

二、自选锁原理

自旋锁是一种基于硬件的锁原语。它依赖于当前硬件的能力来提供原子操作(比如test_and_set,在非原子实现中会涉及读取、修改和写入操作)。

通常,自选锁的实现只能通过特殊的汇编语言指令来实现,比如 atomic(即不可中断的)test-and-set 操作,并且在不支持真正原子操作的编程语言中无法轻松实现。

Linux 对于自选锁的实现可以分为三种:
(1)老版本(2.6.25之前)的Linux内核的自旋锁

typedef struct {volatile unsigned int slock;
} raw_spinlock_t;

raw_spinlock_t结构体使用一个unsigned int slock表示即可,slock 等于0表示locked,1表示unlocked。

在没有加锁的情况下,线程可以获得该锁,然后将变量置为1。其它线程由于发现该值为1,所以只能等待。而当线程解锁的时候,将该变量设置为0,此时其它变量就可以进行加锁了。或者将加锁和未加锁的标示反过来,也就是0表示加锁,1表示未加锁。这个都无所谓,只是一种状态标识。

// v2.6.20/source/include/linux/spinlock.h#define spin_lock(lock)			_spin_lock(lock)
// v2.6.20/source/kernel/spinlock.cvoid __lockfunc _spin_lock(spinlock_t *lock)
{preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);_raw_spin_lock(lock);
}
// v2.6.20/source/include/linux/spinlock.h# define _raw_spin_lock(lock)		__raw_spin_lock(&(lock)->raw_lock)
// v2.6.20/source/include/asm-x86_64/spinlock.hstatic inline void __raw_spin_lock(raw_spinlock_t *lock)
{asm volatile("\n1:\t"LOCK_PREFIX " ; decl %0\n\t""jns 2f\n""3:\n""rep;nop\n\t""cmpl $0,%0\n\t""jle 3b\n\t""jmp 1b\n""2:\t" : "=m" (lock->slock) : : "memory");
}

(1)LOCK_PREFIX 是一个用于添加 x86 平台上的锁定指令的宏。这个宏会根据不同的体系结构提供相应的锁定指令。
(2)decl %0 会将 lock 参数指向的变量减一。如果结果不为负数(jns 指令用于判断),则自旋锁已成功获取。
(3)如果减一操作后结果为负数,代码会循环等待(自旋),在 3: 标签处执行 rep;nop 等待指令。
(4)在等待循环中,代码不断检查自旋锁的值,如果值小于等于0,则继续等待。
(5)如果自旋锁的值大于0,则通过 jmp 1b 返回到第1步继续尝试获取自旋锁。

// v2.6.20/source/include/asm-x86_64/spinlock.hstatic inline void __raw_spin_unlock(raw_spinlock_t *lock)
{asm volatile("movl $1,%0" :"=m" (lock->slock) :: "memory");
}

(1)movl $1, %0 会将值1移动到 lock->slock 所指向的变量,这个操作将自旋锁的值设置为1,表示自旋锁已经被释放。
(2)“=m” (lock->slock) 表示将 lock->slock 的值输出到内存中,以更新自旋锁的状态。
(3)memory 标记告诉编译器不要对内存操作进行任何优化,以确保在释放自旋锁时对内存的操作不会被重新排序或优化掉。

这个版本的spin lock的实现当然可以实现功能,而且在没有冲突的时候表现出不错的性能,不过存在一个问题:不公平。也就是所有的thread都是在无序的争抢spin lock,谁先抢到谁先得,不管thread等了很久还是刚刚开始spin。在冲突比较少的情况下,不公平不会体现的特别明显,然而,随着硬件的发展,多核处理器的数目越来越多,多核之间的冲突越来越剧烈,无序竞争的spinlock带来性能问题。

(2) 2.6.25以后的Linux内核的自旋锁
2.6.25以后的Linux内核的自旋锁 称为 ticket spinlock,基于 FIFO 算法的排队自选锁。

(3) 4.2.0 以后的Linux内核的自旋锁
4.2.0以后的Linux内核的自旋锁 称为 queued spinlock,基于 MCS 算法的排队自选锁。
目前高版本的自选锁都是queued spinlock。

三、自旋锁在内核的使用

以下内核源码来自于 Linux 5.15.0,即queued spinlock。

3.1 struct file

struct file {/** Protects f_ep, f_flags.* Must not be taken from IRQ context.*/spinlock_t		f_lock;......unsigned int 		f_flags;fmode_t			f_mode;......
}

spinlock_t f_lock 初始化:

/*** alloc_file - allocate and initialize a 'struct file'** @path: the (dentry, vfsmount) pair for the new file* @flags: O_... flags with which the new file will be opened* @fop: the 'struct file_operations' for the new file*/
static struct file *alloc_file(const struct path *path, int flags,const struct file_operations *fop)
{struct file *file;file = alloc_empty_file(flags, current_cred());file->f_path = *path;file->f_inode = path->dentry->d_inode;file->f_mapping = path->dentry->d_inode->i_mapping;......file->f_mode |= FMODE_OPENED;file->f_op = fop;......return file;
}
alloc_file()-->alloc_empty_file()-->__alloc_file()
static struct file *__alloc_file(int flags, const struct cred *cred)
{struct file *f;f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL);spin_lock_init(&f->f_lock);mutex_init(&f->f_pos_lock);f->f_flags = flags;f->f_mode = OPEN_FMODE(flags);return f;
}

spinlock_t f_lock的使用:

static int setfl(int fd, struct file * filp, unsigned long arg)
{......spin_lock(&filp->f_lock);filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);spin_unlock(&filp->f_lock);......
}

保护 file 的 f_flags 成员变量。

int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
{switch (advice) {case POSIX_FADV_NORMAL:file->f_ra.ra_pages = bdi->ra_pages;spin_lock(&file->f_lock);file->f_mode &= ~FMODE_RANDOM;spin_unlock(&file->f_lock);break;case POSIX_FADV_RANDOM:spin_lock(&file->f_lock);file->f_mode |= FMODE_RANDOM;spin_unlock(&file->f_lock);break;case POSIX_FADV_SEQUENTIAL:file->f_ra.ra_pages = bdi->ra_pages * 2;spin_lock(&file->f_lock);file->f_mode &= ~FMODE_RANDOM;spin_unlock(&file->f_lock);break;......}
}

保护 file 的 f_mode 成员变量。

3.2 struct dentry

#define USE_CMPXCHG_LOCKREF \(IS_ENABLED(CONFIG_ARCH_USE_CMPXCHG_LOCKREF) && \IS_ENABLED(CONFIG_SMP) && SPINLOCK_SIZE <= 4)struct lockref {union {
#if USE_CMPXCHG_LOCKREFaligned_u64 lock_count;
#endifstruct {spinlock_t lock;int count;};};
};
#define d_lock	d_lockref.lockstruct dentry {/* Ref lookup also touches following */struct lockref d_lockref;	/* per-dentry lock and refcount */
}/** dentry->d_lock spinlock nesting subclasses:** 0: normal* 1: nested*/
enum dentry_d_lock_class
{DENTRY_D_LOCK_NORMAL, /* implicitly used by plain spin_lock() APIs. */DENTRY_D_LOCK_NESTED
};

使用比如例程1:

/*** d_alloc	-	allocate a dcache entry* @parent: parent of entry to allocate* @name: qstr of the name** Allocates a dentry. It returns %NULL if there is insufficient memory* available. On a success the dentry is returned. The name passed in is* copied and the copy passed in may be reused after this call.*/
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{struct dentry *dentry = __d_alloc(parent->d_sb, name);if (!dentry)return NULL;spin_lock(&parent->d_lock);/** don't need child lock because it is not subject* to concurrency here*/__dget_dlock(parent);dentry->d_parent = parent;list_add(&dentry->d_child, &parent->d_subdirs);spin_unlock(&parent->d_lock);return dentry;
}
EXPORT_SYMBOL(d_alloc);

分配dentry时初始化spinlock_t:

/*** __d_alloc	-	allocate a dcache entry* @sb: filesystem it will belong to* @name: qstr of the name** Allocates a dentry. It returns %NULL if there is insufficient memory* available. On a success the dentry is returned. The name passed in is* copied and the copy passed in may be reused after this call.*/static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{struct dentry *dentry;dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);......dentry->d_lockref.count = 1;spin_lock_init(&dentry->d_lock);seqcount_spinlock_init(&dentry->d_seq, &dentry->d_lock);......
}

例程2:

/*** __d_lookup - search for a dentry (racy)* @parent: parent dentry* @name: qstr of name we wish to find* Returns: dentry, or NULL** __d_lookup is like d_lookup, however it may (rarely) return a* false-negative result due to unrelated rename activity.** __d_lookup is slightly faster by avoiding rename_lock read seqlock,* however it must be used carefully, eg. with a following d_lookup in* the case of failure.** __d_lookup callers must be commented.*/
struct dentry *__d_lookup(const struct dentry *parent, const struct qstr *name)
{unsigned int hash = name->hash;struct hlist_bl_head *b = d_hash(hash);struct hlist_bl_node *node;struct dentry *found = NULL;struct dentry *dentry;/** Note: There is significant duplication with __d_lookup_rcu which is* required to prevent single threaded performance regressions* especially on architectures where smp_rmb (in seqcounts) are costly.* Keep the two functions in sync.*//** The hash list is protected using RCU.** Take d_lock when comparing a candidate dentry, to avoid races* with d_move().** It is possible that concurrent renames can mess up our list* walk here and result in missing our dentry, resulting in the* false-negative result. d_lookup() protects against concurrent* renames using rename_lock seqlock.** See Documentation/filesystems/path-lookup.txt for more details.*/rcu_read_lock();hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) {if (dentry->d_name.hash != hash)continue;spin_lock(&dentry->d_lock);if (dentry->d_parent != parent)goto next;if (d_unhashed(dentry))goto next;if (!d_same_name(dentry, parent, name))goto next;dentry->d_lockref.count++;found = dentry;spin_unlock(&dentry->d_lock);break;
next:spin_unlock(&dentry->d_lock);}rcu_read_unlock();return found;
}

该函数用于在父目录项中搜索指定名称的目录项。

3.3 struct inode

struct inode {spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
}

使用例程1:

/*** d_find_alias - grab a hashed alias of inode* @inode: inode in question** If inode has a hashed alias, or is a directory and has any alias,* acquire the reference to alias and return it. Otherwise return NULL.* Notice that if inode is a directory there can be only one alias and* it can be unhashed only if it has no children, or if it is the root* of a filesystem, or if the directory was renamed and d_revalidate* was the first vfs operation to notice.** If the inode has an IS_ROOT, DCACHE_DISCONNECTED alias, then prefer* any other hashed alias over that one.*/
struct dentry *d_find_alias(struct inode *inode)
{struct dentry *de = NULL;if (!hlist_empty(&inode->i_dentry)) {spin_lock(&inode->i_lock);de = __d_find_alias(inode);spin_unlock(&inode->i_lock);}return de;
}
EXPORT_SYMBOL(d_find_alias);

使用例程2:

/** When a file is deleted, we have two options:* - turn this dentry into a negative dentry* - unhash this dentry and free it.** Usually, we want to just turn this into* a negative dentry, but if anybody else is* currently using the dentry or the inode* we can't do that and we fall back on removing* it from the hash queues and waiting for* it to be deleted later when it has no users*//*** d_delete - delete a dentry* @dentry: The dentry to delete** Turn the dentry into a negative dentry if possible, otherwise* remove it from the hash queues so it can be deleted later*/void d_delete(struct dentry * dentry)
{struct inode *inode = dentry->d_inode;spin_lock(&inode->i_lock);spin_lock(&dentry->d_lock);/** Are we the only user?*/if (dentry->d_lockref.count == 1) {dentry->d_flags &= ~DCACHE_CANT_MOUNT;dentry_unlink_inode(dentry);} else {__d_drop(dentry);spin_unlock(&dentry->d_lock);spin_unlock(&inode->i_lock);}
}
EXPORT_SYMBOL(d_delete);

使用例程3:

static struct inode *alloc_inode(struct super_block *sb)
{const struct super_operations *ops = sb->s_op;struct inode *inode;if (ops->alloc_inode)inode = ops->alloc_inode(sb);elseinode = kmem_cache_alloc(inode_cachep, GFP_KERNEL);......inode_init_always(sb, inode);......return inode;
}
/*** inode_init_always - perform inode structure initialisation* @sb: superblock inode belongs to* @inode: inode to initialise** These are initializations that need to be done on every inode* allocation as the fields are not initialised by slab allocation.*/
int inode_init_always(struct super_block *sb, struct inode *inode)
{spin_lock_init(&inode->i_lock);
}

使用例程4:

/*** do_inode_permission - UNIX permission checking* @mnt_userns:	user namespace of the mount the inode was found from* @inode:	inode to check permissions on* @mask:	right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC ...)** We _really_ want to just do "generic_permission()" without* even looking at the inode->i_op values. So we keep a cache* flag in inode->i_opflags, that says "this has not special* permission function, use the fast case".*/
static inline int do_inode_permission(struct user_namespace *mnt_userns,struct inode *inode, int mask)
{if (unlikely(!(inode->i_opflags & IOP_FASTPERM))) {if (likely(inode->i_op->permission))return inode->i_op->permission(mnt_userns, inode, mask);/* This gets set once for the inode lifetime */spin_lock(&inode->i_lock);inode->i_opflags |= IOP_FASTPERM;spin_unlock(&inode->i_lock);}return generic_permission(mnt_userns, inode, mask);
}

3.4 struct super_block

struct super_block {....../* s_inode_list_lock protects s_inodes */spinlock_t		s_inode_list_lock ____cacheline_aligned_in_smp;struct list_head	s_inodes;	/* all inodes */spinlock_t		s_inode_wblist_lock;struct list_head	s_inodes_wb;	/* writeback inodes */
}

spinlock_t s_inode_list_lock/s_inode_wblist_lock初始化:

/***	alloc_super	-	create new superblock*	@type:	filesystem type superblock should belong to*	@flags: the mount flags*	@user_ns: User namespace for the super_block**	Allocates and initializes a new &struct super_block.  alloc_super()*	returns a pointer new superblock or %NULL if allocation had failed.*/
static struct super_block *alloc_super(struct file_system_type *type, int flags,struct user_namespace *user_ns)
{struct super_block *s = kzalloc(sizeof(struct super_block),  GFP_USER);......INIT_LIST_HEAD(&s->s_inodes);spin_lock_init(&s->s_inode_list_lock);INIT_LIST_HEAD(&s->s_inodes_wb);spin_lock_init(&s->s_inode_wblist_lock);......
}

s_inode_list_lock的使用:

/*** inode_sb_list_add - add inode to the superblock list of inodes* @inode: inode to add*/
void inode_sb_list_add(struct inode *inode)
{spin_lock(&inode->i_sb->s_inode_list_lock);list_add(&inode->i_sb_list, &inode->i_sb->s_inodes);spin_unlock(&inode->i_sb->s_inode_list_lock);
}
EXPORT_SYMBOL_GPL(inode_sb_list_add);static inline void inode_sb_list_del(struct inode *inode)
{if (!list_empty(&inode->i_sb_list)) {spin_lock(&inode->i_sb->s_inode_list_lock);list_del_init(&inode->i_sb_list);spin_unlock(&inode->i_sb->s_inode_list_lock);}
/*** invalidate_inodes	- attempt to free all inodes on a superblock* @sb:		superblock to operate on* @kill_dirty: flag to guide handling of dirty inodes** Attempts to free all inodes for a given superblock.  If there were any* busy inodes return a non-zero value, else zero.* If @kill_dirty is set, discard dirty inodes too, otherwise treat* them as busy.*/
int invalidate_inodes(struct super_block *sb, bool kill_dirty)
{int busy = 0;struct inode *inode, *next;LIST_HEAD(dispose);again:spin_lock(&sb->s_inode_list_lock);list_for_each_entry_safe(inode, next, &sb->s_inodes, i_sb_list) {spin_lock(&inode->i_lock);if (inode->i_state & (I_NEW | I_FREEING | I_WILL_FREE)) {spin_unlock(&inode->i_lock);continue;}if (inode->i_state & I_DIRTY_ALL && !kill_dirty) {spin_unlock(&inode->i_lock);busy = 1;continue;}if (atomic_read(&inode->i_count)) {spin_unlock(&inode->i_lock);busy = 1;continue;}inode->i_state |= I_FREEING;inode_lru_list_del(inode);spin_unlock(&inode->i_lock);list_add(&inode->i_lru, &dispose);if (need_resched()) {spin_unlock(&sb->s_inode_list_lock);cond_resched();dispose_list(&dispose);goto again;}}spin_unlock(&sb->s_inode_list_lock);dispose_list(&dispose);return busy;
}
/*** fsnotify_unmount_inodes - an sb is unmounting.  handle any watched inodes.* @sb: superblock being unmounted.** Called during unmount with no locks held, so needs to be safe against* concurrent modifiers. We temporarily drop sb->s_inode_list_lock and CAN block.*/
static void fsnotify_unmount_inodes(struct super_block *sb)
{struct inode *inode, *iput_inode = NULL;spin_lock(&sb->s_inode_list_lock);list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {/** We cannot __iget() an inode in state I_FREEING,* I_WILL_FREE, or I_NEW which is fine because by that point* the inode cannot have any associated watches.*/spin_lock(&inode->i_lock);if (inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) {spin_unlock(&inode->i_lock);continue;}/** If i_count is zero, the inode cannot have any watches and* doing an __iget/iput with SB_ACTIVE clear would actually* evict all inodes with zero i_count from icache which is* unnecessarily violent and may in fact be illegal to do.* However, we should have been called /after/ evict_inodes* removed all zero refcount inodes, in any case.  Test to* be sure.*/if (!atomic_read(&inode->i_count)) {spin_unlock(&inode->i_lock);continue;}__iget(inode);spin_unlock(&inode->i_lock);spin_unlock(&sb->s_inode_list_lock);if (iput_inode)iput(iput_inode);/* for each watch, send FS_UNMOUNT and then remove it */fsnotify_inode(inode, FS_UNMOUNT);fsnotify_inode_delete(inode);iput_inode = inode;cond_resched();spin_lock(&sb->s_inode_list_lock);}spin_unlock(&sb->s_inode_list_lock);if (iput_inode)iput(iput_inode);
}

s_inode_wblist_lock的使用:

// linux-5.15/fs/fs-writeback.c/** mark an inode as under writeback on the sb*/
void sb_mark_inode_writeback(struct inode *inode)
{struct super_block *sb = inode->i_sb;unsigned long flags;if (list_empty(&inode->i_wb_list)) {spin_lock_irqsave(&sb->s_inode_wblist_lock, flags);if (list_empty(&inode->i_wb_list)) {list_add_tail(&inode->i_wb_list, &sb->s_inodes_wb);trace_sb_mark_inode_writeback(inode);}spin_unlock_irqrestore(&sb->s_inode_wblist_lock, flags);}
}

这个函数用于将给定的索引节点标记为正在写回的状态,并将其添加到超级块的写回列表中。函数首先检查索引节点的写回列表是否为空,如果是空的,则获取超级块的写回列表锁,然后再次检查是否为空以避免重复添加。如果索引节点的写回列表为空,则将该索引节点添加到超级块的写回列表中,并记录写回操作的跟踪信息。

// linux-5.15/fs/fs-writeback.c/** clear an inode as under writeback on the sb*/
void sb_clear_inode_writeback(struct inode *inode)
{struct super_block *sb = inode->i_sb;unsigned long flags;if (!list_empty(&inode->i_wb_list)) {spin_lock_irqsave(&sb->s_inode_wblist_lock, flags);if (!list_empty(&inode->i_wb_list)) {list_del_init(&inode->i_wb_list);trace_sb_clear_inode_writeback(inode);}spin_unlock_irqrestore(&sb->s_inode_wblist_lock, flags);}
}

这个函数用于将给定的索引节点从超级块的写回列表中清除,表示该索引节点不再处于写回状态。函数首先检查索引节点的写回列表是否非空,如果非空,则获取超级块的写回列表锁,然后再次检查是否非空以确保正确性。如果索引节点的写回列表非空,则将该索引节点从超级块的写回列表中删除,并记录清除写回状态的跟踪信息。

// linux-5.15/fs/fs-writeback.c/** The @s_sync_lock is used to serialise concurrent sync operations* to avoid lock contention problems with concurrent wait_sb_inodes() calls.* Concurrent callers will block on the s_sync_lock rather than doing contending* walks. The queueing maintains sync(2) required behaviour as all the IO that* has been issued up to the time this function is enter is guaranteed to be* completed by the time we have gained the lock and waited for all IO that is* in progress regardless of the order callers are granted the lock.*/
static void wait_sb_inodes(struct super_block *sb)
{LIST_HEAD(sync_list);/** We need to be protected against the filesystem going from* r/o to r/w or vice versa.*/WARN_ON(!rwsem_is_locked(&sb->s_umount));mutex_lock(&sb->s_sync_lock);/** Splice the writeback list onto a temporary list to avoid waiting on* inodes that have started writeback after this point.** Use rcu_read_lock() to keep the inodes around until we have a* reference. s_inode_wblist_lock protects sb->s_inodes_wb as well as* the local list because inodes can be dropped from either by writeback* completion.*/rcu_read_lock();spin_lock_irq(&sb->s_inode_wblist_lock);list_splice_init(&sb->s_inodes_wb, &sync_list);/** Data integrity sync. Must wait for all pages under writeback, because* there may have been pages dirtied before our sync call, but which had* writeout started before we write it out.  In which case, the inode* may not be on the dirty list, but we still have to wait for that* writeout.*/while (!list_empty(&sync_list)) {struct inode *inode = list_first_entry(&sync_list, struct inode,i_wb_list);struct address_space *mapping = inode->i_mapping;/** Move each inode back to the wb list before we drop the lock* to preserve consistency between i_wb_list and the mapping* writeback tag. Writeback completion is responsible to remove* the inode from either list once the writeback tag is cleared.*/list_move_tail(&inode->i_wb_list, &sb->s_inodes_wb);/** The mapping can appear untagged while still on-list since we* do not have the mapping lock. Skip it here, wb completion* will remove it.*/if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK))continue;spin_unlock_irq(&sb->s_inode_wblist_lock);spin_lock(&inode->i_lock);if (inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) {spin_unlock(&inode->i_lock);spin_lock_irq(&sb->s_inode_wblist_lock);continue;}__iget(inode);spin_unlock(&inode->i_lock);rcu_read_unlock();/** We keep the error status of individual mapping so that* applications can catch the writeback error using fsync(2).* See filemap_fdatawait_keep_errors() for details.*/filemap_fdatawait_keep_errors(mapping);cond_resched();iput(inode);rcu_read_lock();spin_lock_irq(&sb->s_inode_wblist_lock);}spin_unlock_irq(&sb->s_inode_wblist_lock);rcu_read_unlock();mutex_unlock(&sb->s_sync_lock);
}

这个函数的作用是等待特定超级块上的所有索引节点的写回操作完成。函数会遍历超级块中的写回列表,并等待所有正在写回的页面写入完成。这确保了在函数退出时,已经发出的所有 I/O 操作都已完成。

参考资料

Linux 2.6.20
Linux 5.15.0

https://medium.com/geekculture/the-linux-kernel-locking-api-and-shared-objects-1169c2ae88ff

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/66272.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【太古新篇,智驭未来】 SFA系统成功上线

经过双方团队的不懈努力与紧密合作&#xff0c;eBest成功帮助香港太古可乐完成了SFA系统的全面上线&#xff01; 太古可乐&#xff0c;作为饮料行业的佼佼者&#xff0c;一直以来以其卓越的品质和深入人心的品牌形象深受消费者喜爱。然而&#xff0c;在快速变化的市场环境中&am…

Web安全|渗透测试|网络安全

基础入门(P1-P5) p1概念名词 1.1域名 什么是域名&#xff1f; 域名&#xff1a;是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。 什么是二级域名多级域名…

陈萍的设计创新:Kevlin Nexus荣获伦敦设计奖,展示品牌设计的国际化与持续创新

近日,陈萍凭借其创新设计作品 Kevlin Nexus,成功斩获 2024 伦敦设计奖。该奖项旨在表彰全球范围内的优秀设计作品,表彰设计界最完美、最前沿的成就。伦敦设计奖是全球最具权威性和影响力的设计奖项之一,其评选标准以高水准的专业性和严格性著称。作为全球设计界的顶级荣誉,伦敦…

qml DirectionalBlur详解

1、概述 DirectionalBlur是QML&#xff08;Qt Modeling Language&#xff09;中用于创建方向模糊效果的一种图形效果类型。它通过对源图像的像素进行模糊处理&#xff0c;产生一种源项目朝着模糊方向移动的感知印象。这种模糊效果被应用到每个像素的两侧&#xff0c;因此设置方…

怎么投稿各大媒体网站?如何快速辨别一家媒体是否适合自己?

在做软文营销时&#xff0c;除去在官号和子账号上投稿外&#xff0c;怎么投稿各大媒体网站是困扰中小企业主的一大难题。没有多余账号、运营成本太高&#xff0c;让不少想做全平台推广的朋友止步于此。为了解决这些问题&#xff0c;今天就让小编来分享一下&#xff0c;怎么在各…

MES设备日志采集工具

永久免费: <下载> <使用说明> 用途 定时全量或增量采集工控机,电脑文件或日志. 优势 开箱即用: 解压直接运行.不需额外下载.管理设备: 后台统一管理客户端.无人值守: 客户端自启动,自更新.稳定安全: 架构简单,兼容性好,通过授权控制访问. 架构 技术架构: Asp…

Formality:参考设计/实现设计以及顶层设计

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482​​​ Formality存在两个重要的概念&#xff1a;参考设计/实现设计和顶层设计&#xff0c;本文就将对此进行详细阐述。参考设计/实现设计是中两个重要的全局概念&am…

国产编辑器EverEdit - 复制为RTF

1 复制为RTF 1.1 应用背景 在写产品手册或者其他文档时&#xff0c;可能会用到要将产品代码以样例的形式放到文档中&#xff0c;一般的文本编辑器拷贝粘贴到Word中也就是普通文本&#xff0c;没有语法着色&#xff0c;这样感观上不是太好&#xff0c;为了让读者的感观更好一点…

redux 结合 @reduxjs/toolkit 的使用

1&#xff0c;使用步骤 使用React Toolkit 创建 counterStore&#xff08;store目录下&#xff09; --> 为React注入store&#xff08;src下面的index&#xff09; --> React组件使用store中的数据&#xff08;组件&#xff09; 2&#xff0c;例如下面有一个简单加减的…

动态规划【打家劫舍】

今天和大家分享一下动态规划当中的打家劫舍题目&#xff0c;希望在大家刷题的时候提供一些思路 打家劫舍1&#xff1a; 题目链接&#xff1a; 198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋…

【机器学习实战入门项目】使用Python创建自己的表情符号

深度学习项目入门——让你更接近数据科学的梦想 表情符号或头像是表示非语言暗示的方式。这些暗示已成为在线聊天、产品评论、品牌情感等的重要组成部分。这也促使数据科学领域越来越多的研究致力于表情驱动的故事讲述。 随着计算机视觉和深度学习的进步&#xff0c;现在可以…

BEVFusion论文阅读

1. 简介 融合激光雷达和相机的信息已经变成了3D目标检测的一个标准&#xff0c;当前的方法依赖于激光雷达传感器的点云作为查询&#xff0c;以利用图像空间的特征。然而&#xff0c;人们发现&#xff0c;这种基本假设使得当前的融合框架无法在发生 LiDAR 故障时做出任何预测&a…

OSI七层协议——分层网络协议

OSI七层协议&#xff0c;顾名思义&#xff0c;分为七层&#xff0c;实际上七层是不存在的&#xff0c;是人为的进行划分,让人更好的理解 七层协议包括&#xff0c;物理层(我),数据链路层(据),网络层(网),传输层(传输),会话层(会),表示层(表),应用层(用)(记忆口诀->我会用表…

【Mysql进阶知识】Mysql 程序的介绍、选项在命令行配置文件的使用、选项在配置文件中的语法

目录 一、程序介绍 二、mysqld--mysql服务器介绍 三、mysql - MySQL 命令行客户端 3.1 客户端介绍 3.2 mysql 客户端选项 指定选项的方式 mysql 客户端命令常用选项 在命令行中使用选项 选项(配置)文件 使用方法 选项文件位置及加载顺序 选项文件语法 使用举例&am…

wireshark抓路由器上的包 抓包路由器数据

文字目录 抓包流程概述设置抓包配置选项 设置信道设置无线数据包加密信息设置MAC地址过滤器 抓取联网过程 抓包流程概述 使用Omnipeek软件分析网络数据包的流程大概可以分为以下几个步骤&#xff1a; 扫描路由器信息&#xff0c;确定抓包信道&#xff1b;设置连接路由器的…

【蓝桥杯】43687.赢球票

题目描述 某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。 主持人拿出 N 张卡片&#xff08;上面写着 1⋯N 的数字&#xff09;&#xff0c;打乱顺序&#xff0c;排成一个圆圈。 你可以从任意一张卡片开始顺时针数数: 1,2,3 ⋯ ⋯ 如果数到的数字刚好和卡片上的数字…

微软开源AI Agent AutoGen 详解

AutoGen是微软发布的一个用于构建AI Agent系统的开源框架,旨在简化事件驱动、分布式、可扩展和弹性Agent应用程序的创建过程。 开源地址: GitHub - microsoft/autogen: A programming framework for agentic AI 🤖 PyPi: autogen-agentchat Discord: https://aka.ms/auto…

【Elasticsearch】全文搜索与相关性排序

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

用css和html制作太极图

目录 css相关参数介绍 边距 边框 伪元素选择器 太极图案例实现、 代码 效果 css相关参数介绍 边距 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>*{margin: 0;padding: 0;}div{width: …

【React】插槽渲染机制

目录 通过 children 属性结合条件渲染通过 children 和 slot 属性实现具名插槽通过 props 实现具名插槽 在 React 中&#xff0c;并没有直接类似于 Vue 中的“插槽”机制&#xff08;slot&#xff09;。但是&#xff0c;React 可以通过 props和 children 来实现类似插槽的功能…