Linux驱动学习笔记


1 linux设备驱动中的并发控制

1.1中断屏蔽

local_irq_disable()

critical section

local_irq_enable()

这些函数只能禁止和时能本CPU的中断,不能解决SMP引起的中断,适合与自旋锁联合使用。

local_irq_save(flag)

local_irq_restore(flag)

与上面的函数不同,这两个函数可以保存中断位的信息。

1.2原子操作

1.2.1 设置院子变量

void atomic_set(atomic_t* v,int i)设置原子变量v的值为i;

atomic_t v = ATOMIC_INIT(0)设置原子变量v的值为0;

1.2.2 获取原子变量的值

atomic_read(atomic_t* v)返回原子变量的值;

1.2.3 原子变量自加、自减

void atomic_inc(atomic_t* v)原子变量加1

void atomic_dec(atomic_t* v)原子变量减1

1.2.4 操作并测试

int atomic_inc_and_test(atomic_t* v)

int atomic_dec_and_test(atomic_t* v)

int atomic_sub_and_test(int i, atomic_t* v)

上面三个函数对原子变量执行自增,自减,和减操作(没有加)测试其是否返回零

为零:返回true

否则:返回false

1.2.5 操作并返回

int atomic_add_return(int i, atomic_t *v);

int atomic_sub_return(int i, atomic_t *v);

int atomic_inc_return(atomic_t *v);

int atomic_dec_return(atomic_t *v);

上述操作对原子变量进行加/减和自增/自减操作,并返回新的值。

1.3 位原子操作

1.3.1设置位

void set_bit(nr, void *addr);

上述操作设置addr地址的第nr位,所谓设置位即将位写为1。

1.3.2 清除位

void clear_bit(nr, void *addr);

上述操作清除addr地址的第nr位,所谓清除位即将位写为0。

1.3.3 改变位

void change_bit(nr, void *addr);

上述操作对addr地址的第nr位进行反置。

1.3.4 测试位

test_bit(nr, void *addr);

上述操作返回addr地址的第nr位。

1.3.5 测试并操作位

int test_and_set_bit(nr, void *addr);

int test_and_clear_bit(nr, void *addr);

int test_and_change_bit(nr, void *addr);

上述test_and_xxx_bit(nr, void *addr)操作等同于执行test_bit(nr, void *addr)

后再执行xxx_bit(nr, void *addr)

1.4 自旋锁

1 定义自旋锁

spinlock_t spin;

2 初始化自旋锁

spin_lock_init(lock)

该宏用于动态初始化自旋锁lock

3 获得自旋锁

spin_lock(lock)

该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放;

spin_trylock(lock)

该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再“在原地打转”

4.释放自旋锁

spin_unlock(lock)

该宏释放自旋锁lock,它与spin_try

自旋锁一般这样被使用,如下所示:

spinlock_t lock;

spin_lock_init(&lock);

spin_lock (&lock) ;   //获取自旋锁

...//临界区

spin_unlock (&lock) ; //解锁

自旋锁的衍生

spin_lock_irq()             = spin_lock()      + local_irq_disable() 

spin_unlock_irq()           = spin_unlock()    + local_irq_enable() 

spin_lock_irqsave()         = spin_unlock()    + local_irq_save()  

spin_unlock_irqrestore()    = spin_unlock()    + local_irq_restore() 

spin_lock_bh()              = spin_lock()      + local_bh_disable() 

spin_unlock_bh()            = spin_unlock()    + local_bh_enable() 

驱动工程师应谨慎使用自旋锁,而且在使用中还要特别注意如下几个问题。

1.5 读写自旋锁

1 定义和初始化读写自旋锁

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 静态初始化 */ 

rwlock_t my_rwlock;

rwlock_init(&my_rwlock); /* 动态初始化 */

2 读锁定

void read_lock(rwlock_t *lock);

void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_lock_irq(rwlock_t *lock);

void read_lock_bh(rwlock_t *lock);

3 读解锁

void read_unlock(rwlock_t *lock);

void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void read_unlock_irq(rwlock_t *lock);

void read_unlock_bh(rwlock_t *lock);

4 写锁定

void write_lock(rwlock_t *lock);

void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock)

void write_lock_bh(rwlock_t *lock);

int write_trylock(rwlock_t *lock);

5 写解锁

void write_unlock(rwlock_t *lock);

void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void write_unlock_irq(rwlock_t *lock);

void write_unlock_bh(rwlock_t *lock);

1.5 信号量

1 定义信号量

struct semaphore sem; 

2 初始化信号量

void sema_init (struct semaphore *sem, int val);

该函数初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。

void init_MUTEX(struct semaphore *sem);

该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置为1,等同于sema_init (struct semaphore *sem, 1)。

void init_MUTEX_LOCKED (struct semaphore *sem);

该函数也用于初始化一个信号量,但它把信号量sem 的值设置为0,等同于sema_init (struct semaphore *sem, 0)

DECLARE_MUTEX(name) 

DECLARE_MUTEX_LOCKED(name)

前者定义一个名为name的信号量并初始化为1,后者定义一个名为name的信号量并初始化为0

3.获得信号量

void down(struct semaphore * sem);

该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用。

int down_interruptible(struct semaphore * sem); 

该函数功能与down()类似,不同之处为,因为down()而进入睡眠状态的进程不能被信号打断,而因为down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0。

int down_trylock(struct semaphore * sem);

该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。 在使用down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS,

4.释放信号量

void up(struct semaphore * sem);

1.6 互斥体

struct mutex my_mutex;

mutex_init(&my_mutex);

下面的两个函数用于获取互斥体。

void fastcall mutex_lock(struct mutex *lock);

int fastcall mutex_lock_interruptible(struct mutex *lock);

int fastcall mutex_trylock(struct mutex *lock);

下列函数用于释放互斥体。

void fastcall mutex_unlock(struct mutex *lock);

1 阻塞与非阻塞IO

1.1 等待队列

1 定义“等待队列头”

wait_queue_head_t my_queue;

2 初始化“等待队列头”

init_waitqueue_head(&my_queue);

3 定义并初始化“等待队列头”

DECLARE_WAIT_QUEUE_HEAD (name)

4 定义等待队列

DECLARE_WAITQUEUE(name, tsk)

该宏用于定义并初始化一个名为name的等待队列。

5 添加/移除等待队列

void  fastcall  add_wait_queue(wait_queue_head_t  *q,  wait_queue_t *wait);

void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

add_wait_queue()用于将等待队列wait 添加到等待队列头q指向的等待队列链表

中,而remove_wait_queue()用于将等待队列wait 从附属的等待队列头q指向的等待队

列链表中移除。

5.等待事件

wait_event(queue, condition)

wait_event_interruptible(queue, condition)

wait_event_timeout(queue, condition, timeout)

wait_event_interruptible_timeout(queue, condition, timeout)

等待第一个参数queue 作为等待队列头的等待队列被唤醒,而且第二个参数

condition必须满足,否则阻塞。wait_event()和wait_event_interruptible()的区别在于后

者可以被信号打断,而前者不能。加上_timeout后的宏意味着阻塞等待的超时时间,

6.唤醒队列

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

上述操作会唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列

头的等待队列对应的进程。

7.在等待队列上睡眠

sleep_on(wait_queue_head_t *q );

interruptible_sleep_on(wait_queue_head_t *q );

sleep_on()函数的作用就是将目前进程的状态置成TASK_UNINTERRUPTIBLE,

并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒。

interruptible_sleep_on()与sleep_on()函数类似,其作用是将目前进程的状态置成

TASK_ INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒或者进程收到信号。