• Linux内核之mutex互斥锁机制


    概要

    race condition

    当两个及以上的thread想同时访问同一个资源时,就会发生race condition(资源竞争),因为调度算法是可以在不同的threads之间来回切换的,没办法预测指定的顺序,那么就可能会发生竞争。避免这类的资源竞争,方法有semaphore(信号量),spinlock(自旋锁),mutex(互斥量)

    mutex

    mutex是什么?mutual exclusion lock,互相排斥锁。同一时刻只有一个线程可以拿到该锁

    谁lock mutex,谁unlock 它

    Yes
    No
    try lock mutex
    is mutex avaliable?
    lock
    sleep
    mutex structure

    在linux kernel中,mutex是以结构体形式存在

    /*
     * Simple, straightforward mutexes with strict semantics:
     *
     * - only one task can hold the mutex at a time
     * - only the owner can unlock the mutex
     * - multiple unlocks are not permitted
     * - recursive locking is not permitted
     * - a mutex object must be initialized via the API
     * - a mutex object must not be initialized via memset or copying
     * - task may not exit with mutex held
     * - memory areas where held locks reside must not be freed
     * - held mutexes must not be reinitialized
     * - mutexes may not be used in hardware or software interrupt
     *   contexts such as tasklets and timers
     *
     * These semantics are fully enforced when DEBUG_MUTEXES is
     * enabled. Furthermore, besides enforcing the above rules, the mutex
     * debugging code also implements a number of additional features
     * that make lock debugging easier and faster:
     *
     * - uses symbolic names of mutexes, whenever they are printed in debug output
     * - point-of-acquire tracking, symbolic lookup of function names
     * - list of all locks held in the system, printout of them
     * - owner tracking
     * - detects self-recursing locks and prints out all relevant info
     * - detects multi-task circular deadlocks and prints out all affected
     *   locks and tasks (and only those tasks)
     */
    struct mutex {
    	atomic_long_t		owner;
    	spinlock_t		wait_lock;
    #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    	struct optimistic_spin_queue osq; /* Spinner MCS lock */
    #endif
    	struct list_head	wait_list;
    #ifdef CONFIG_DEBUG_MUTEXES
    	void			*magic;
    #endif
    #ifdef CONFIG_DEBUG_LOCK_ALLOC
    	struct lockdep_map	dep_map;
    #endif
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    initial mutex
    静态

    include/linux/mutex.h中定义了DEFINE_MUTEX()宏,用于静态创建mutex变量。

    #define __MUTEX_INITIALIZER(lockname) \
    		{ .owner = ATOMIC_LONG_INIT(0) \
    		, .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \
    		, .wait_list = LIST_HEAD_INIT(lockname.wait_list) \
    		__DEBUG_MUTEX_INITIALIZER(lockname) \
    		__DEP_MAP_MUTEX_INITIALIZER(lockname) }
    
    #define DEFINE_MUTEX(mutexname) \
    	struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    动态
    /**
     * mutex_init - initialize the mutex
     * @mutex: the mutex to be initialized
     *
     * Initialize the mutex to unlocked state.
     *
     * It is not allowed to initialize an already locked mutex.
     */
    #define mutex_init(mutex)						\
    do {									\
    	static struct lock_class_key __key;				\
    									\
    	__mutex_init((mutex), #mutex, &__key);				\
    } while (0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    比如动态初始化一个mutex变量:

    struct mutex etx_mutex; 
    mutex_init(&etx_mutex);
    
    • 1
    • 2

    该方法创建初始化mutex变量为unlocked状态,该方法不能用于已经存在的mutex变量。

    mutex lock

    可以使用如下内核API来lock mutex变量。

    mutex_lock

    该函数将申请独占互斥量,如果mutex不能获得,那么该进程将会sleep等待直至可以获得mutex。

    该mutex必须由申请者进行释放。

    /**
     * mutex_lock - acquire the mutex
     * @lock: the mutex to be acquired
     *
     * Lock the mutex exclusively for this task. If the mutex is not
     * available right now, it will sleep until it can get it.
     *
     * The mutex must later on be released by the same task that
     * acquired it. Recursive locking is not allowed. The task
     * may not exit without first unlocking the mutex. Also, kernel
     * memory where the mutex resides must not be freed with
     * the mutex still locked. The mutex must first be initialized
     * (or statically defined) before it can be locked. memset()-ing
     * the mutex to 0 is not allowed.
     *
     * (The CONFIG_DEBUG_MUTEXES .config option turns on debugging
     * checks that will enforce the restrictions and will also do
     * deadlock debugging)
     *
     * This function is similar to (but not equivalent to) down().
     */
    void __sched mutex_lock(struct mutex *lock)
    {
    	might_sleep();
    
    	if (!__mutex_trylock_fast(lock))
    		__mutex_lock_slowpath(lock);
    }
    EXPORT_SYMBOL(mutex_lock);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    mutex_lock_interruptible

    申请获得mutex,但中间可以被signal打断然后返回。

    /**
     * mutex_lock_interruptible() - Acquire the mutex, interruptible by signals.
     * @lock: The mutex to be acquired.
     *
     * Lock the mutex like mutex_lock().  If a signal is delivered while the
     * process is sleeping, this function will return without acquiring the
     * mutex.
     *
     * Context: Process context.
     * Return: 0 if the lock was successfully acquired or %-EINTR if a
     * signal arrived.
     */
    int __sched mutex_lock_interruptible(struct mutex *lock)
    {
    	might_sleep();
    
    	if (__mutex_trylock_fast(lock))
    		return 0;
    
    	return __mutex_lock_interruptible_slowpath(lock);
    }
    
    EXPORT_SYMBOL(mutex_lock_interruptible);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    mutex_trylock
    /**
     * mutex_trylock - try to acquire the mutex, without waiting
     * @lock: the mutex to be acquired
     *
     * Try to acquire the mutex atomically. Returns 1 if the mutex
     * has been acquired successfully, and 0 on contention.
     *
     * NOTE: this function follows the spin_trylock() convention, so
     * it is negated from the down_trylock() return values! Be careful
     * about this when converting semaphore users to mutexes.
     *
     * This function must not be used in interrupt context. The
     * mutex must be released by the same task that acquired it.
     */
    int __sched mutex_trylock(struct mutex *lock)
    {
    	bool locked;
    
    #ifdef CONFIG_DEBUG_MUTEXES
    	DEBUG_LOCKS_WARN_ON(lock->magic != lock);
    #endif
    
    	locked = __mutex_trylock(lock);
    	if (locked)
    		mutex_acquire(&lock->dep_map, 0, 1, _RET_IP_);
    
    	return locked;
    }
    EXPORT_SYMBOL(mutex_trylock);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    mutex_unlock

    必须由lock该mutex的task来unlock

    /**
     * mutex_unlock - release the mutex
     * @lock: the mutex to be released
     *
     * Unlock a mutex that has been locked by this task previously.
     *
     * This function must not be used in interrupt context. Unlocking
     * of a not locked mutex is not allowed.
     *
     * This function is similar to (but not equivalent to) up().
     */
    void __sched mutex_unlock(struct mutex *lock)
    {
    #ifndef CONFIG_DEBUG_LOCK_ALLOC
    	if (__mutex_unlock_fast(lock))
    		return;
    #endif
    	__mutex_unlock_slowpath(lock, _RET_IP_);
    }
    EXPORT_SYMBOL(mutex_unlock);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    示例

    创建两个内核线程:test_mutex_thread1和test_mutex_thread2,然后每当调度执行该线程,则去增加全局变量test_mutex_global_variable的值,两个线程之间通过互斥量来进行互斥。

    #include 
    #include 
    #include 
    #include                 //kmalloc()
    #include              //copy_to/from_user()
    #include              //kernel threads
    #include                //task_struct 
    #include 
    #include 
     
    struct mutex test_mutex;
    unsigned long test_mutex_global_variable = 0;
     
    static struct task_struct *test_mutex_thread1;
    static struct task_struct *test_mutex_thread2; 
     
    int thread_function1(void *pv)
    {
        
        while(!kthread_should_stop()) {
            mutex_lock(&test_mutex);
            test_mutex_global_variable++;
            pr_info("In Thread Function1 %lu\n", test_mutex_global_variable);
            mutex_unlock(&test_mutex);
            msleep(1000);
        }
        return 0;
    }
    
    int thread_function2(void *pv)
    {
        while(!kthread_should_stop()) {
            mutex_lock(&test_mutex);
            test_mutex_global_variable++;
            pr_info("In Thread Function2 %lu\n", test_mutex_global_variable);
            mutex_unlock(&test_mutex);
            msleep(1000);
        }
        return 0;
    }
    
    static int __init test_mutex_driver_init(void)
    {
            mutex_init(&test_mutex);
            
            /* Creating Thread 1 */
            test_mutex_thread1 = kthread_run(thread_function1,NULL,"test Thread1");
            if(test_mutex_thread1) {
                pr_info("Kthread1 Created Successfully...\n");
            } else {
                pr_err("Cannot create kthread1\n");
    	    return -1;
            }
     
             /* Creating Thread 2 */
            test_mutex_thread2 = kthread_run(thread_function2,NULL,"test Thread2");
            if(test_mutex_thread2) {
                pr_info("Kthread2 Created Successfully...\n");
            } else {
                pr_err("Cannot create kthread2\n");
    	    return -1;
            }
            
            pr_info("test mutex driver Insert...Done!!!\n");
            return 0;
    }
    
    static void __exit test_mutex_driver_exit(void)
    {
            kthread_stop(test_mutex_thread1);
            kthread_stop(test_mutex_thread2);
            pr_info("test mutex driver Remove...Done!!\n");
    }
     
    module_init(test_mutex_driver_init);
    module_exit(test_mutex_driver_exit);
     
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("A simple kernel driver - Mutex");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    执行效果:可以看到变量值没有出现跳跃的情况

    root@pc:mutex# dmesg
    [  754.304319] In Thread Function2 13
    [  754.304327] In Thread Function1 14
    [  755.324395] In Thread Function2 15
    [  755.324405] In Thread Function1 16
    [  756.348397] In Thread Function1 17
    [  756.348406] In Thread Function2 18
    [  757.372384] In Thread Function1 19
    [  757.372393] In Thread Function2 20
    [  758.396390] In Thread Function1 21
    [  758.396398] In Thread Function2 22
    [  759.420400] In Thread Function1 23
    [  759.420407] In Thread Function2 24
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    reference

    Linux Device Driver Tutorials – ch22

  • 相关阅读:
    MySQL与openGauss 时间操作比较分析
    .Net Core 中间件验签
    RK3588 点亮imx586摄像头
    Linux文件系统和软硬连接
    Ubuntu 安装软件
    【面试题精讲】构造方法有哪些特点?是否可被 override?
    前端知识学习案例10-开发企业网站10-实现案例部分1
    k8s报错的解决办法: kubelet的日志出现 Error getting node的报错。
    哈希表学习笔记
    【The design pattern of Attribute-Based Dynamic Routing Pattern (ADRP)】
  • 原文地址:https://blog.csdn.net/qq_23662505/article/details/126971221