day49-驱动的并发控制

day49-驱动的并发控制

一、并发概念和问题

  并发(Concurrency)是指在同一时间段内运行多个操作或任务的能力。这些操作或任务可能是在一个或多个处理器上真正同时执行(在硬件层面并行执行),也可能是通过任务之间快速切换(在单核处理器上使用时间片调度)给人一种同时执行的感觉。无论是哪种情况,这些并发操作或任务通常会共享一些资源,如内存、文件等。

  并发编程可以提高系统的性能和响应速度,特别是在多核和多处理器的系统中。然而,它也带来了一些复杂的问题,如下所述:

  • 竞态条件(Race Conditions):当两个或多个并发操作访问和修改同一数据时,最终结果取决于这些操作的执行顺序,就可能发生竞态条件。这通常会导致错误或不可预测的结果。

  • 死锁(Deadlocks):死锁是一种状态,其中每个操作都在等待其他操作释放资源,因此所有操作都无法进行。例如,如果操作A持有资源1并请求资源2,而操作B持有资源2并请求资源1,那么就会发生死锁。

  • 活锁(Livelocks):活锁是一种类似于死锁的状态,但在活锁中,操作是在改变状态,而不是停滞不动。然而,这些状态的改变并不使操作向前进,而是使它们不断在几个状态之间循环。

  • 资源饥饿(Resource Starvation):在这种情况下,一个或多个操作因为无法获得所需的资源(如CPU时间或内存)而无法进行。

解决这些问题需要使用各种并发控制和同步机制,如锁、信号量、条件变量等。然而,使用这些机制也需要谨慎,因为它们可能引起其他问题,如优先级反转(一个高优先级的操作因为等待一个低优先级的操作而被阻塞)等。因此,正确的并发编程需要细致的设计和编程。




二、竞态

  竞态条件(Race Condition)是并发编程中的一个概念,当两个或多个线程访问和修改同一数据时,最终结果取决于这些线程的执行顺序,就可能发生竞态条件。

  竞态条件通常会导致错误或不可预测的结果。例如,考虑两个线程都要增加一个变量的值。如果一个线程读取变量的值,然后另一个线程读取同一个值,然后两个线程都增加它们读取的值并将结果写回原位置,那么增加的总数将比预期少,因为两个线程都在增加他们认为的原始值,而不是一个线程增加后的值。

  解决竞态条件的一种常见方法是使用锁或其他同步机制来保证在任一时间只有一个线程可以访问和修改数据。常用的同步机制包括以下几种:

  • 互斥量:用于保护一段代码,称为”临界区”。当一个线程进入临界区时,它会锁定互斥量。如果其他线程也想进入临界区,它们必须等待直到第一个线程解锁互斥量。

  • 信号量(P/V操作):用于控制对一组资源的访问。当一个线程想要使用一组资源中的一个时,它会减少信号量的值。如果信号量的值为零,则表示没有可用的资源,线程必须等待。

  • 条件变量:用于等待某个条件成立。当一个线程需要等待某个条件成立(如某个资源变得可用)时,它可以等待在一个条件变量上。当条件成立时,另一个线程可以通知在条件变量上等待的线程。

  • 互斥锁:基于互斥量,互斥锁是最基本的锁类型,用于保护临界区以防止两个线程同时执行临界区的代码。如果一个线程已经获取了锁,其他尝试获取锁的线程将会阻塞(或者忙等待),直到拥有锁的线程释放锁。互斥锁主要用于保护共享数据。

  • 自旋锁:自旋锁是一种特殊类型的锁,当一个线程尝试获取一个已经被持有的锁时,这个线程不会进入休眠状态,而是会在一个循环中不断地尝试获取锁(也就是“自旋”)。因此,自旋锁不会使线程进入睡眠,这在等待时间预计很短的情况下是有用的,因为这样可以避免线程切换的开销。然而,如果锁被持有的时间较长,自旋锁就会浪费CPU时间。

  • 读写锁:读写锁是一种同步工具,它允许多个线程同时读取某个资源,但是在写入资源时只允许一个线程访问。这样,可以在保护资源的同时提高读取的并发性。

  • 原子操作:原子操作是一种低级的同步工具,可以用于实现简单的临界区。原子操作保证在操作完成前,不会有其他线程修改数据。

  • 中断屏蔽:在处理一些临界区代码(比如修改一些重要的内核数据结构)的时候,如果在修改过程中发生中断,并且中断处理程序也要访问或修改同样的数据,那么就可能发生竞态条件。为了防止这种情况,可以在进入临界区之前关闭中断,即进行”中断屏蔽”,然后在离开临界区之后再打开中断。




三、相关函数

3.1 自旋锁

  • 定义自旋锁
    1
    spinlock_t lock;
  • 初始化自旋锁
    1
    spin_lock_init(&lock);
  • 获取自旋锁
    1
    2
    spin_lock(&lock); //如果能立即获取锁,返回0,否则自旋
    spin_trylock(&lock); //如果能立即获取锁,返回1,否则返回0
  • 释放自旋锁
    1
    spin_unlock(&lock);

注意:自旋锁不能进行调度,也就是说不能将自旋锁的获取和释放放在不同的函数中,否则会导致死锁。

3.2 互斥锁

  互斥锁没资源时会阻塞在那个函数,自旋锁适合用于锁被持有的时间很短,而且线程不希望在上下文切换(从运行状态切换到等待状态再切换回来)上花费太多时间的情况。然而,如果锁被持有的时间比较长,那么自旋锁可能会浪费大量的 CPU 时间,此时使用互斥锁可能是更好的选择。

  • 定义互斥锁
    1
    struct mutex lock;
  • 初始化互斥锁
    1
    mutex_init(&lock);
  • 获取互斥锁
    1
    2
    mutex_lock(&lock); //如果能立即获取锁,返回0,否则阻塞
    mutex_trylock(&lock); //如果能立即获取锁,返回1,否则返回0
  • 释放互斥锁
    1
    mutex_unlock(&lock);

3.3 信号量

  • 定义信号量
    1
    struct semaphore sem;
  • 初始化信号量
    1
    sema_init(&sem, 1); //第二个参数是信号量的初始值
  • 获取信号量
    1
    2
    down(&sem); //如果能立即获取信号量,返回0,否则阻塞
    down_trylock(&sem); //如果能立即获取信号量,返回1,否则返回0
  • 释放信号量
    1
    up(&sem);

注:trylock的主要在于它不会阻塞,即使获取不到锁,就会返回并执行下面的语句,而不是阻塞等待。

3.4 原子操作

原子操作(Atomic Operation)指的是一个不可中断的操作,这种操作一旦开始就一直运行到结束,在执行一个线程的过程中不会切换到其它线程中去。通过判断原子变量的值来判断是否执行。

  • 定义原子变量
    1
    atomic_t v ;
  • 初始化原子变量
    1
    atomic_set(&v, 0); //将原子变量v设置为0
  • 原子变量的读写操作
    1
    2
    atomic_read(&v); //读取原子变量v的值
    atomic_set(&v, 1); //设置原子变量v的值为1
  • 原子变量的加减操作
    1
    2
    atomic_add(1, &v); //将原子变量v的值加1
    atomic_sub(1, &v); //将原子变量v的值减1
  • 原子变量的自增自减操作
    1
    2
    atomic_inc_return(&v); //将原子变量v的值加1并返回加后的值
    atomic_dec_return(&v); //将原子变量v的值减1并返回加后的值
  • 原子变量的比较和交换操作
    1
    atomic_cmpxchg(&v, 0, 1); //如果原子变量v的值为0,则将其设置为1,返回1,否则返回0
  • 原子变量的位操作
    1
    2
    3
    4
    atomic_set_bit(0, &v); //将原子变量v的第0位设置为1
    atomic_clear_bit(0, &v); //将原子变量v的第0位设置为0
    atomic_test_and_set_bit(0, &v); //如果原子变量v的第0位为0,则将其设置为1,返回1,否则返回0
    atomic_test_and_clear_bit(0, &v); //如果原子变量v的第0位为1,则将其设置为0,返回1,否则返回0

    3.5 等待队列

  等待队列(Wait Queue)是一种同步机制,它允许一个线程等待某个条件成立。当一个线程需要等待某个条件成立时,它可以等待在一个等待队列上。当条件成立时,另一个线程可以通知在等待队列上等待的线程。

  • 定义等待队列
    1
    wait_queue_head_t wq;
  • 初始化等待队列
    1
    init_waitqueue_head(&wq);
  • 等待队列的等待和唤醒操作
    1
    2
    3
    4
    wait_event(wq, condition); //等待条件condition成立,condition是一个判断,比如(cnt != 0)
    wait_event_interruptible(wq, condition); //等待条件condition成立,可以被信号中断(常用)
    wake_up(&wq); //唤醒等待队列wq上的一个线程
    wake_up_all(&wq); //唤醒等待队列wq上的所有线程

3.6 io多路复用

  io多路复用(I/O Multiplexing)是一种同步机制,它允许一个线程等待多个事件中的任何一个。当一个线程需要等待多个事件中的任何一个时,它可以等待在一个等待队列上。当任何一个事件发生时,另一个线程可以通知在等待队列上等待的线程。

  驱动如果要支持io多路复用,当应用程序使用 select()、poll()、epoll() 等系统调用来查询设备是否可以进行读写操作时,设备驱动程序需要提供一个轮询函数,来返回设备的状态,并将当前进程加入到等待队列中,等待设备的事件发生。这样可以提高处理器的利用率,支持多道程序和 I/O 设备的并行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static unsigned int mychr_poll(struct file *fp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(fp, &myqueue, wait);
if (cnt != 0)
mask |= POLLIN | POLLRDNORM;
if(cnt = 0)
mask |= POLLOUT | POLLWRNORM;
return mask;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.open = mychr_open,
.release = mychr_close,
.read = mychr_read,
.write = mychr_write,
.poll = mychr_poll, //轮询函数
.fasync = mychr_fasync,
};

3.7 异步通知

应用层

  1. 捕捉信号

    1
    signal(SIGIO, sigio_handler);

    sigio_handler是信号处理函数,当信号发生时,会调用该函数。

  2. 设置文件描述符的属主

    1
    fcntl(fd, F_SETOWN, getpid());
  3. 设置支持异步通知

    1
    2
    3
    int flags = fcntl(fd, F_SETFL);//获取文件标志值
    flags |= FASYNC;//修改为支持异步通知
    fcntl(fd, F_SETFL, flags);//把flags设置回去

    驱动层

  4. 设置文件描述符的属主(内核已完成)

    1
    filp->f_owner = current;
  5. 定义异步通知的结构体

    1
    static struct fasync_struct *async_queue;
  6. 初始化异步回调队列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static int mychr_fasync(int fd, struct file *fp, int mode)
    {
    return fasync_helper(fd, fp, mode, &async_queue);
    }
    static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = mychr_open,
    .release = mychr_close,
    .read = mychr_read,
    .write = mychr_write,
    .poll = mychr_poll,
    .fasync = mychr_fasync,
    };


  7. 发生io信号通知应用程序

    1
    2
    3
    kill_fasync(&async_queue, SIGIO, POLL_IN);
    SIGIO:int型信号
    POLL_IN:信号的类型
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2024 nakano-mahiro
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信