linux驱动编写嵌入式 Linux C ARM

内核(驱动)编程中的并发控制

2021-10-17  本文已影响0人  Leon_Geo

在为操作系统编写驱动设备时,因为涉及到中断、多任务和多处理器SMP的处理,所以内核提供了诸如原子操作、信号量、完成量等几种并发控制机制,对公用资源进行保护。下文将分别予以阐述。

1、原子变量

原子变量就是,在对其进行操作时不会被其它任务或中断打断。而原子操作需要硬件的支持,因此时架构相关的,其API和原子类型的定义在内核include/asm/atomic.h文件中,都是用汇编语言实现的。它的优点是使用简单,但缺点是功能单一,只能做计数操作,保护的东西太少。在Linux中,原子变量的定义如下:

typedef struct{
    volatile int counter;
}atomic_t;

在Linux中定义了两种原子变量操作方法,一是原子整型操作,二是原子位操作

1.1 原子整型操作

1.2 原子位操作

函数原型:static inline void set_bit(int nr, volatile unsigned long *addr)

2、自旋锁

自旋锁是一种阻塞结构(忙等待),这样会对系统的性能有所影响,所以不应该长时间持有,他是一种适合短时间锁定的轻量级的加锁机制,另外,自旋锁不能递归使用。
自旋锁的类型也是一个结构体,即struct spinlock_t。下面对它的操作函数进行介绍:

2.1 定义和初始化自旋锁

spinlock_t lock;
spin_lock_init(lock);
int count = 0;
spinlock_t lock;
int xxx_init(void)
{
    ...
    spin_lock_init(&lock);
    ...
}
/* 设备打开函数 */
int xxx_open(struct inode *inode, struct file* filp)
{
    ...
    spin_lock(&lock);
    /* 临界代码 */
    if(count)   /*已经被其它程序打开过了*/
    {
        spin_unlock(&lock);
        return -EBUSY;
    }
    count++;
    spin_unlock(&lock);
    ...
}

/* 设备释放函数 */
int xxx_open(struct inode *inode, struct file* filp)
{
    ...
    spin_lock(&lock);
    count--;
    spin_unlock(&lock);
    ...
}

3、信号量

Linux中实现了两种信号量,一种用于内核程序中,另一种应用于应用程序中,本文仅介绍内核中的信号量。信号量与自旋锁的不同点在于:当一个进程或线程试图去获取一个已经被锁定的信号量时,它不会向自旋锁一样在原地忙等待,而是将自身加入到系统的一个等待队列中去睡眠,直到拥有信号量的进程释放该信号量后,才会被系统唤醒并再次尝试获取该信号量。此处也提醒我们,只有能够睡眠的进程(函数)才能使用信号量,向中断处理函数那样需要立刻执行的函数是不能使用信号量的。

3.1 信号量的定义

在不同的实现中,信号量的实现可能不同。在Linux中其定义如下:

struct semaphore {
    spinlock_t lock;    //用来对count起保护作用
    unsigned int count;
    struct list_head wait_list;
};

3.2信号量的使用

当sema中的count为1时,我们称为互斥体(同一时间仅有一个进程持有该信号量),他有专门的宏来进行初始化:
init_MUTEX(sema); //初始化sema信号量为1。
init_MUTEX_LOCKED(sema); //初始化sema信号量为0。

struct semaphore sema;
int xxx_init(void)
{
    ...
    init_MUTEX(&sema);
    ...
}

int xxx_open(struct inode *inode, struct file *filp)
{
    ...
    down(&sema);
    不允许其它进程访问该临界资源区
    ...
    return 0;
}

int xxx_release(struct inode *inode, struct file *filp)
{
    ...
    up(&sema);
    ...
    return 0;
}

4、完成量

上节中讲的进程间的同步(一个线程等待另一个线程完成某操作后才能继续执行),在Linux中有专门的机制(虽然使用信号量也可以实现)叫完成量,即一个线程发送一个信号通知另一个线程开始完成某个任务。

4.1完成量的定义

struct completion{
    unsigned int done;
    wait_queue_head_t wait;
};

done:维护一个计数,其被初始化为1。当done为0时,会将拥有完成量的线程置于等待状态;当其值大于1时,表示等待完成量的函数可以立刻执行!
wait:存放所有等待该完成量的正在睡眠的进程组成的链表

4.2完成量的使用

struct completion com;
int xxx_init(void)
{
    ...
    init_completion(&com);
    ...
}

int xxx_A(void)
{
    ...
    /* 代码1 */
    wait_for_completion(&com);
    /* 代码3 */
    return 0;
}

int xxx_B(void)
{
    ...
    /* 代码2 */
    complete(&com);
    ...
    return 0;
}

初始化后com中的done值为0,此时若xxx_A先执行,则当执行完成代码1 后便会进入睡眠,等待进程xxx_B执行完代码2,并释放完成量(使done加1),此时系统会唤醒处于完成量com中的等待队列里正在睡觉的进程A,然后进程A继续执行完代码3.

上一篇下一篇

猜你喜欢

热点阅读