【精通内核】Linux内核并发控制信号量原理

294 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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);即可完成内核信号量的初始化。