0. Linux并发与竞争
- 并发
并发指的是在同一段时间内,多个任务看似同时运行的现象。
- 竞争
竞争是指多个线程或进程争夺相同资源时发生的冲突情况。
- 竞争导致的资源争用问题
-
竟态条件问题:
竞态条件是指多个线程或进程在没有适当同步的情况下访问和修改共享资源时,操作的执行顺序影响到最终结果,从而导致不确定性和错误的情况。
-
死锁问题
死锁是指两个或多个线程或进程在等待彼此持有的资源,导致所有参与者都无法继续执行的情况。
-
饥饿问题
饥饿是指一个线程或进程长期无法获得所需资源,从而无法继续执行的情况。(主要原因是系统支持抢占)
-
1. Linux内核提供的几种并发和竞争的处理方法
1.1 原子操作
原子操作不会被打断,所以不会被影响。
- 定义原子变量
- 在初始化函数中初始化原子变量
- 通过判断原子变量的值来检查LED有没有被别的应用使用
- 关闭驱动文件的时候释放原子变量
// 1. **定义原子变量**
/* dtsled设备结构体 */
struct gpioled_dev {...atomic_t lock; /* 原子变量 */
};// 3. **通过判断原子变量的值来检查LED有没有被别的应用使用**
static int led_open(struct inode *inode, struct file *filp)
{/* 通过判断原子变量的值来检查LED有没有被别的应用使用 */if (!atomic_dec_and_test(&gpioled.lock)) {printk(KERN_ERR "gpioled: Device is busy!\n");atomic_inc(&gpioled.lock); /* 小于0的话就加1,使其原子变量等于0 */return -EBUSY; /* LED被其他应用使用,返回忙 */}return 0;
}
// 4. **关闭驱动文件的时候释放原子变量**
static int led_release(struct inode *inode, struct file *filp)
{/* 关闭驱动文件的时候释放原子变量 */atomic_inc(&gpioled.lock);return 0;
}// 2. **在初始化函数中初始化原子变量**
static int __init led_init(void)
{.../* 9.初始化原子变量 */atomic_set(&gpioled.lock, 1); /* 原子变量初始值为1 */return 0;
}
应用程序
当该文件被打开时,无法再次被打开了!
1.2 自旋锁
自旋锁(Spinlock)是一种常见的同步机制,用于在多核或多处理器系统中防止多个线程或进程同时访问共享资源。自旋锁在锁定资源时,并不会使线程进入睡眠状态,而是让线程“自旋”,即循环检查锁是否已经被释放。
定义一个整型设备状态变量,根据状态变量的值判断该设备是否被占用。线程访问该设备时添加自旋锁,在该线程释放自旋锁之前,其他线程无法申请自旋锁,也就无法访问该设备。
- 定义设备状态变量,并添加自旋锁保护状态变量
- 在初始化函数中初始化自旋锁
- 申请自旋锁,如果设备被占用就解锁;如果没有被占用,就标记打开了
- 上锁,并在临界区内自检,解锁
// 1. **定义设备状态变量,并添加自旋锁保护状态变量**
/* dtsled设备结构体 */
struct gpioled_dev {...int dev_stats; /* 设备状态: 0,设备未使用; >0,设备已经被使用 */spinlock_t lock; /* 自旋锁 */
};// 3. **申请自旋锁,如果设备被占用就解锁;如果没有被占用,就标记打开了**
static int led_open(struct inode *inode, struct file *filp)
{unsigned long flags;int ret = 0;/* 自旋锁上锁 */spin_lock_irqsave(&gpioled.lock, flags);/* 如果设备被使用了 */if (gpioled.dev_stats) {printk(KERN_ERR "gpioled: Device is busy!\n");ret = -EBUSY;goto out;}/* 如果设备没有打开,那么就标记已经打开了 */gpioled.dev_stats++;out:/* 解锁 */spin_unlock_irqrestore(&gpioled.lock, flags);return ret;
}// 4. **上锁,并在临界区内自检,解锁**
static int led_release(struct inode *inode, struct file *filp)
{unsigned long flags;/* 上锁 */spin_lock_irqsave(&gpioled.lock, flags);/* 关闭驱动文件的时候将dev_stats减1 */if (gpioled.dev_stats)gpioled.dev_stats--;/* 解锁 */spin_unlock_irqrestore(&gpioled.lock, flags);return 0;
}// 2. **在初始化函数中初始化自旋锁**
static int __init led_init(void)
{.../* 9.初始化自旋锁 */spin_lock_init(&gpioled.lock);return 0;
}
1.3 信号量
信号量(Semaphore)是一种用于进程或线程间同步和资源管理的经典机制,它通过一个整型计数器控制对共享资源的访问,支持两种原子操作:P(Proberen,尝试减少)和V(Verhogen,增加)。与自旋锁不同,信号量适用于占用资源久的场合,此时线程会进入休眠状态。
- 定义信号量
- 初始化信号量
- 获取信号量,如果获取不到则会进入休眠状态
- 释放信号量,信号量值加1
// 1. **定义信号量**
/* dtsled设备结构体 */
struct gpioled_dev {...struct semaphore sem; /* 信号量 */
};// 3. **获取信号量,如果获取不到则会进入休眠状态**
static int led_open(struct inode *inode, struct file *filp)
{/* 获取信号量,如果获取不到则会进入休眠状态 */if (down_interruptible(&gpioled.sem))return -ERESTARTSYS;#if 0down(&gpioled.sem);
#endifreturn 0;
}// 4. **释放信号量,信号量值加1**
static int led_release(struct inode *inode, struct file *filp)
{/* 释放信号量,信号量值加1 */up(&gpioled.sem);return 0;
}// 2. **初始化信号量**
static int __init led_init(void)
{.../* 初始化信号量 */sema_init(&gpioled.sem, 1); // 只允许1个设备访问资源,可以改成2、3、4 ...return 0;
}
1.4 互斥体
互斥体(mutex)是一种用于线程间同步的机制,通常用于保护共享资源,避免多个线程同时访问某个共享资源,从而防止出现数据竞争和不可预期的行为。
- 定义互斥体
- 初始化互斥体
- 获取互斥体并上锁
- 释放互斥锁
// 1. **定义互斥体**
/* dtsled设备结构体 */
struct gpioled_dev {...struct mutex lock; /* 互斥体 */
};// 3. **获取互斥体并上锁**
static int led_open(struct inode *inode, struct file *filp)
{/* 获取互斥体,可以被信号打断 */if (mutex_lock_interruptible(&gpioled.lock))return -ERESTARTSYS;#if 0mutex_lock(&gpioled.lock); /* 不能被信号打断 */
#endifreturn 0;
}// 4. **释放互斥锁**
static int led_release(struct inode *inode, struct file *filp)
{/* 释放互斥锁 */mutex_unlock(&gpioled.lock);return 0;
}// 2. **初始化互斥体**
static int __init led_init(void)
{.../* 初始化互斥体 */mutex_init(&gpioled.lock);return 0;
}