持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
前言
📫作者简介:小明java问道之路,专注于研究 Java/ Liunx内核/ C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。
📫热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。
🏆InfoQ签约作者、CSDN专家博主/后端领域优质创作者/内容合伙人、阿里云专家/签约博主、51CTO专家🏆
🔥如果此文还不错的话,还请👍关注 、点赞 、收藏三连支持👍一下博主~
本文导读
本文深入Linux内核源码,从核心源码入口讲起,详细对信号量、互斥量的内核代码讲解,Linux内核并发控制原理的锁实现和原理在后续文章中一一讲解,本文深入浅出Linux中断控制的实现原理。
一、Linux内核信号量
信号量原理是什么,多线程的时候,我们需要一个变量来表示信号量状态,同时需要一个队列,当线程无法获取信号量时,需要进行阻塞。
二、atomic_t 结构体
atomict 为原子性变量、_wait_queue_head 为等待队列头部、wait_queue 为等待任务的表节点、semaphore 为信号量结构体。我们看到原子性整型变量声明 ,其中通过 counter 变量来描述信号量的个数,然而这里为了避免编译器优化用 volatile 来修饰。
typedef struct {
volatile int counter;
} atomic_t; // 等待队列头部,通过自旋锁来保证操作队列线程安全
struct_waitqueue_head {
spinlock_t lock; // 保护阻塞队列的自旋锁
struct list_head task_list; // 任务阻塞队列
};
struct__wait_queue { // 等待任务节点
unsigned int flags; // 任务结构体,这里只需要知道它就是进程控制块 PCB 代表了进程即可
struct task_struct * task;
wait_queue_func_t func; // 等待函数指针
struct list head task list; // 所处链表的结构体
};
struct semaphore { // 信号量结构体
atomic_t count; // 信号量计数
int sleepers; // 等待任务数
wait_queue_head_t wait; // 等待队列
};
我们可以看到首先设置等待队列头部,通过自旋锁来保证操作队列线程安全,在结构等待队列头(struct_waitqueue_head)设置保护阻塞队列的自旋锁、初始化任务阻塞队列
结构等待队列(struct__wait_queue)任务结构体,这里只需要知道它就是进程控制块 PCB 代表了进程即可,定义等待函数指针和所处链表的结构体
信号量结构体(semaphore)实现比较简单,设置信号量计数、等待任务数、初始化等待队列即可。
接下来我们来看看信号量的初始化的内核源码。
三、信号量的初始化的内核源码
首先初始化信号量,原子性设置信号量的初始值,就是将(count)->counter=val,初始化 sleeper 为0,最后初始化等待链表。
static inline void sema_init(struct semaphore*sem, int val) { // 初始化信号量
atomic_set(&sem->count, val); // 原子性设置信号量的初始值,就是将(count)->counter=val
sem -> sleepers = 0; // 初始化 sleeper 为0
init_waitqueue_head( &sem -> wait); // 初始化等待链表
}
// 初始化操作
static inline void init_waitqueue_head(wait_queue_head_t*q){
//自旋锁状态初始为spinlock t结构体
q->lock = SPIN LOCK UNLOCKED;
//初始化等待链表,头尾相接:(ptr)-> next=(ptr); (ptr)->prev=(ptr);
INIT_LIST_HEAD(&q->task_list);
}
这里实现也比较易懂,sema_init函数中初始化信号量,将原子性设置信号量的初始值,就是将(count)->counter=val,初始化 sleeper 为0,初始化等待链表,然后就是初始化等待队列头(init_waitqueue_head)第一步自旋锁状态初始为spinlock t结构体,第二步初始化等待链表,头尾相接:(ptr)-> next=(ptr); (ptr)->prev=(ptr);即可完成内核信号量的初始化。