当两个及以上的thread想同时访问同一个资源时,就会发生race condition(资源竞争),因为调度算法是可以在不同的threads之间来回切换的,没办法预测指定的顺序,那么就可能会发生竞争。避免这类的资源竞争,方法有semaphore(信号量),spinlock(自旋锁),mutex(互斥量)。
mutex是什么?mutual exclusion lock,互相排斥锁。同一时刻只有一个线程可以拿到该锁。
谁lock mutex,谁unlock 它。
在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
};
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)
/**
* 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)
比如动态初始化一个mutex变量:
struct mutex etx_mutex;
mutex_init(&etx_mutex);
该方法创建初始化mutex变量为unlocked状态,该方法不能用于已经存在的mutex变量。
可以使用如下内核API来lock mutex变量。
该函数将申请独占互斥量,如果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);
申请获得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);
/**
* 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);
必须由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);
创建两个内核线程: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");
执行效果:可以看到变量值没有出现跳跃的情况
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
...