Linux第77步_处理Linux并发的相关函数

了解linux中的“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体,以及相关函数。

并发就是多个“用户”同时访问同一个共享资源。如:多个线程同时要求读写同一个EEPROM芯片,这个EEPROM就是共享资源,为了保证读写的正确性,其它线程必须等待“持有者”释放使用权限,才可以使用该EEPROM。并发带来的问题就是竞争。Linux采用“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体的函数来解决并发与竞争。

1、Linux系统并发产生的原因:

1)、多线程并发访问。

2)、抢占式并发访问,从2.6版本内核开始,Linux内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。

3)、中断程序并发访问。

4)、SMP(多核)核间并发访问。

注意:SOC称为系统级芯片,也有称片上系统。

如果线程A和线程B修改同一个存储单元,如果没有竞争,就会按照下面的流程执行,都会得到正确的结果。

线程A:

ldr r0, =0X30000000    /* r0=0X30000000  */

ldr r1, =10            /* r1=10  */

str r1, [r0]           /*将地址0X30000000的存储单元设置为10*/

线程B:

ldr r0, =0X30000000    /* r0=0X30000000  */

ldr r1, =20            /* r1=20  */

str r1, [r0]           /*将地址0X30000000的存储单元设置为20*/

如果线程A和线程B发生竞争,地址0X30000000的存储单元设置的值可能是错误的。

2、原子操作:

原子操作是指不能再进行分割的操作。主要指整型变量操作或者位变量操作。

1)、“原子整形数据”操作:

①、在“include/linux/types.h”文件中,atomic_t的结构体如下

如果是32位的系统级芯片,Linux内核定义的32位原子结构体如下:

typedef struct {

  int counter;

}atomic_t;

如果是64位的系统级芯片,Linux内核也定义了64位原子结构体如下:

typedef struct {

s64 counter;

} atomic64_t;

②、“原子整形数据”操作函数:

ATOMIC INIT(int i)

定义原子变量的时候对其初始化;

int atomic read(atomic_t *v)

读取v->counter的值,并且返回;

void atomic_set(atomic_t *v, int i)

向v->counter写入i值;

void atomic_add(int i. atomic_t *v)

给v->counter加上i值;

void atomic_sub(int i, atomic_t *v)

从v->counter减去i值;

void atomic_inc(atomic_t *v)

给v->counter加1,也就是自增;

void atomic_dec(atomic_t *v)

从v->counter减1,也就是自减;

int atomic_dec_return(atomic_t *v)

从v->counter减1,并且返回v的值;

int atomic_inc_return(atomic_t *v)

给v->counter加1,并且返回v的值;

int atomic_sub_and_test(int i.atomic_t *v)

从v->counter减i,如果结果为0就返回真,否则返回假;

int atomic_dec_and_test(atomic_t *v)

从v->counter减1,如果结果为0就返回真,否则返回假;

int atomic_inc_and_test(atomic_t *v)

给v->counter加1,如果结果为0就返回真,否则返回假;

int atomic_add_negative(int i, atomic_t *v)

给v->counter加i,如果结果为负就返回真,否则返回假;

举例:

atomic_t v = ATOMIC_INIT(0); /* 定义原子变量v,并初始化原子变零v=0 */

atomic_set(&v, 10);         /* 设置v->counter=10 */

atomic_read(&v);            /* 读取v->counter的值,肯定是10 */

atomic_inc(&v);             /* v->counter的值加1,v->counter=11 */

atomic_t Mylock;                   /* 原子变量 */

Mylock = (atomic_t)ATOMIC_INIT(0); //初始化原子变量

atomic_set(&Mylock, 1);            //原子变量初始值为Mylock->counter=1

if (!atomic_dec_and_test(&Mylock)) {

//当Mylock->counter=1时,atomic_dec_and_test()返回1

//从Mylock->counter减1,如果结果为0就返回1,否则返回0;

 atomic_inc(&Mylock);

 return -EBUSY; /* Mylock被使用,返回忙 */

}

2)、“原子位数据”操作:

需要包含“#include <bitops.h>

void set_bit(int nr, void *p)

将p地址的第nr位置1;

void clear_bit(int nr,void *p)

将p地址的第nr位清零;

void change_bit(int nr, void *p)

将p地址的第nr位进行翻转;

int test_bit(int nr, void *p)

获取p地址的第nr位的值;

int test_and_set_bit(int nr, void *p)

将p地址的第nr位置 1,并且返回第nr位原来的值;

int test_and_clear_bit(int nr, void *p)

将p地址的第nr位清零,并且返回第nr位原来的值;

int test_and_change_bit(int nr, void *p)

将p地址的第nr位翻转,并且返回第nr位原来的值;

举例:

#include <bitops.h>

unsigned long     mig_status;

#define NFS_MIG_FAILED          (2)

int ret;

if ( test_bit(NFS_MIG_FAILED, &mig_status) )

{//mig_status的bit2=1,则执行

    ret = -EIO;

    return ret;

}

3)、原子操作只对整型变量和位变量数据进行保护。

3、自旋锁

1)、自旋锁结构体spinlock_t

需要包含头文件“#include < spinlock_types.h >”;

Linux内核使用结构体spinlock_t表示自旋锁,定义如下:

typedef struct spinlock {

    union {

       struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC

# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))

       struct {

           u8 __padding[LOCK_PADSIZE];

           struct lockdep_map dep_map;

       };

#endif

    };

} spinlock_t;

2)、采用“自旋锁”,实现线程之间的并发访问函数:

需要包含头文件“#include <spinlock.h>”;

#define DEFINE_SPINLOCK(x)  spinlock_t x = __SPIN_LOCK_UNLOCKED(x)

声明x为自旋锁结构,并初始化;

int spin_lock_init(spinlock_t *lock)

初始化自旋锁;

void spin_lock(spinlock_t *lock)

获取指定的自旋锁,也叫做加锁;

void spin_unlock(spinlock_t *lock)

释放指定的自旋锁;

int spin_trylock(spinlock_t *lock)

尝试获取指定的自旋锁,如果没有获取到就返回0;

int spin_is_locked(spinlock_t *lock)

检查指定的自旋锁是否被获取,如果没有被获取就返回非0,否则返回0;

注意:

线程之间的并发访问函数,适用于线程之间的并发访问,但不适合中断程序并发访问

3)、采用“自旋锁”,实现线程与中断之间的并发访问函数:

void spin_lock_irq(spinlock_t *lock)

禁止本地中断,并获取自旋锁;

void spin_unlock_irq(spinlock_t *lock)

激活本地中断,并释放自旋锁;

void spin_lock_irqsave(spinlock_t *lockunsigned long flags)

保存中断状态,禁止本地中断,并获取自旋锁;

void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

注意:在线程中使用spin_lock_irqsave()和spin_unlock_irqrestore();在中断中使用spin_lock()和spin_unlock()来获取自旋锁和释放自旋锁不推荐使用spin_lock_irq()和spin_unlock_irq函数

4)、举例:

DEFINE_SPINLOCK(MyLock) /*声明MyLock为自旋锁结构,并初始化*/

/* 线程A */

void functionA (){

unsigned long flags; /* 中断状态 */

spin_lock_irqsave(&MyLock, flags); /* 获取锁 */

    //保存中断状态,禁止本地中断,并获取自旋锁;

/* 临界区 */

spin_unlock_irqrestore(&MyLock, flags); /* 释放锁 */

   //将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁;

}

/* 中断服务函数 */

void irq() {

spin_lock(&MyLock); /* 获取锁 */

//获取MyLock自旋锁,也叫做加锁;

/* 临界区 */

spin_unlock(&MyLock); /* 释放锁 */

//释放MyLock自旋锁;

}

5)、“下半部”并发访问函数

需要包含文件“#include <spinlock.h>

下半部(BH)也会竟争共享资源,有些资料也会将下半部叫做底半部。如果要在“下半部”里面使用自旋锁,则需要用到下面的函数:

void spin_lock_bh(spinlock_t*lock)

关闭下半部,并获取自旋锁;

void spin_unlock_bh(spinlock_t*lock)

打开下半部,并释放自旋锁;

6)、自旋锁使用注意事项:

①、“其它线程”在等待自旋锁的时处于“自旋”状态,因此“持有锁的线程”不能长时间持用这个自旋锁,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体。

②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的API函数,否则的话可能导致死锁。

③、不能递归申请白旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了!

④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还是多核的SOC,都将其当做多核SOC来编写驱动程序。

4、读写锁函数

需要包含文件“#include <rwlock_types.h>

typedef struct {

    arch_rwlock_t raw_lock;

#ifdef CONFIG_DEBUG_SPINLOCK

    unsigned int magic, owner_cpu;

    void *owner;

#endif

#ifdef CONFIG_DEBUG_LOCK_ALLOC

    struct lockdep_map dep_map;

#endif

} rwlock_t;

#define DEFINE_RWLOCK(x) rwlock_t x = __RW_LOCK_UNLOCKED(x)

声明“读写锁结构变量x”,并初始化读写锁;

读写锁函数需要包含文件“#include <rwlock.h>

void rwlock_init(rwlock_t *lock)

初始化读写锁;

void read_lock(rwlock_t *lock)

获取读锁;

void read_unlock(rwlock_t *lock)

释放读锁;

void read_lock_irq(rwlock_t *lock)

禁止本地中断,并且获取读锁;

void read_unlock_irg(rwlock_t *lock)

打开本地中断,并且释放读锁;

void read_lock_irgsave(rwlock_t *lock,unsigned long flags)

保存中断状态,禁止本地中断,并获取读锁;

void read_unlock_irqrestore(rwlock_t *lock,unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放读锁;

void read_lock_bh(rwlock_t *lock)

关闭下半部,并获取读锁;

void read_unlock_bh(rwlock_t *lock)

打开下半部,并释放读锁;

void write_lock(rwlock_t *lock)

获取写锁;

void write_unlock(rwlock_t *lock)

释放写锁;

void write_lock_irg(rwlock_t *lock)

禁止本地中断,并且获取写锁;

void write_unlock_irg(rwlock_t *lock)

打开本地中断,并且释放写锁;

void write_lock_irqsave(rwlock_t *lock,unsigned long flags)

保存中断状态,禁止本地中断,并获取写锁。

void write_unlock_irqrestore(rwlock_t *lock,unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放读锁。

void write_lock_bh(rwlock_t *lock)

关闭下半部,并获取读锁;

void write_unlock_bh(rwlock_t *lock)

打开下半部,并释放读锁;

5、顺序锁

顺序锁结构需要包含文件“#include <seqlock.h>

顺序锁结构体seqlock_t

typedef struct {

struct seqcount seqcount;

spinlock_t lock;

}seqlock_t;

使用顺序锁,实现同时读写,但是不允许同时进行并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取,保证数据完整性。

注意:

顺序锁“保护的资源”不能是指针,因为如果在写操作的时候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读取野指针导致系统崩溃

#define DEFINE_SEQLOCK(x) \

       seqlock_t x = __SEQLOCK_UNLOCKED(x)

声明“顺序锁结构变量x”,并初始化

void write_seqlock(seqlock_t *sl)

获取“写顺序锁”;

void write_sequnlock(seqlock_t *sl)

释放“写顺序锁”;

void write_seqlock_irq(seqlock_t *sl)

禁止本地中断,并且获取“写顺序锁”;

void write_sequnlock_irq(seqlock_t *sl)

打开本地中断,并且释放“写顺序锁”;

void write_seqlock_irqsave(seqlock_t *sl,unsigned long flags)

保存中断状态,禁止本地中断,并获取“写顺序锁”;

void write_sequnlock_irqrestore(seqlock_t *sl,unsigned long flags)

将中断状态恢复到以前的状态,并且激活本地中断,释放“写顺序锁”;

void write_seqlock_bh(seqlock_t *sl)

关闭下半部,并获取“写顺序锁”;

void write_sequnlock_bh(seqlock_t *sl)

打开下半部,并释放“写顺序锁”;

unsigned read_seqbegin(const seqlock_t *sl)

读单元访问共享资源的时候调用此函数,此函数会返回顺序锁的顺序号;

unsigned read_seqretry(const seqlock_t *sl,unsigned start)

读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读;

6、信号量

信号量有一个“信号量值”,用来控制访问共享资源的访问数量,相当于通过“信号量值”控制访问资源的线程数。

如果将“信号量值”设置大于 1,那么这个信号量就是“计数型信号量”,它允许多个线程同时访问共享资源。

如果将“信号量值”设置为0,那么这个信号量就是“二值信号量”,它具有互斥访问共享资源的作用。

需要包含文件“#include <semaphore.h>

1)、信号量结构体semaphore

struct semaphore {

    raw_spinlock_t       lock;

    unsigned int      count;

    struct list_head  wait_list;

};

#define __SEMAPHORE_INITIALIZER(name, n)             \

{                               \

    .lock      = __RAW_SPIN_LOCK_UNLOCKED((name).lock),  \

    .count     = n,                     \

    .wait_list = LIST_HEAD_INIT((name).wait_list),       \

}

并将“name.count”设置为n;

2)、信号量函数

#define DEFINE_SEMAPHORE(name)  \

    struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

声明信号量结构变量为name,并将信号量的值“name. count”设置为1;这是一个“计数型信号量”。

static inline void sema_init(struct semaphore *sem, int val)

{

    static struct lock_class_key __key;

    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);

    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);

}

初始化信号量结构变量sem,并将“sem->count”设置为val;

void down(struct semaphore *sem)

获取信号量,但会导致休眠,进入休眠状态的线程不能被信号打断,因此不能在中断中使用

int down_trylock(struct semaphore *sem)

尝试获取信号量,如果能获取到信号量就获取,并且返回0。如果不能就返回非0,并不会进入休眠。

int down_interruptible(struct semaphore *sem)

获取信号量,进入休眠状态的线程可以被信号打断。

void up(struct semaphore *sem)

释放信号量

int down_killable(struct semaphore *sem)

进入休眠状态的线程可以被唤醒,中断获取信号量的操作;

3)、举例:

struct semaphore sem; /* 定义信号量 */

sema_init(&sem, 1); /* 初始化信号量 */

down(&sem);          /* 申请信号量 */

/* 临界区 */

up(&sem); /* 释放信号量 */

4)、信号量的特点:

信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场

合,因此信号量不能用于中断中,因为信号量会引起休眠,中断是不能进入休眠的。

如果共享资源的持有时间比较短,那就不适合使用信号量。因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

7、互斥体

需要包含文件“#include <mutex.h>

struct mutex {

    atomic_long_t     owner;

    spinlock_t    wait_lock;

};

#define DEFINE_MUTEX(mutexname) \

    struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

声明互斥结构变量mutexname,并初始化;

void mutex_init(struct mutex *lock);

并初始化互斥结构变量lock

void mutex_lock(struct mutex *lock)

获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠;

void mutex_unlock(struct mutex *lock)

释放 mutex,也就给 mutex 解锁;

int mutex_trylock(struct mutex *lock)

尝试获取mutex,如果成功就返回 1,如果失败就返回 0;

int mutex_is_locked(struct mutex *lock)

判断mutex_是否被获取,如果是的话就返回1,否则返回 0;

int mutex_lock_interruptible(struct mutex *lock)

使用此函数获取信号量失败进入休眠以后可以被信号打断;

举例:

struct mutex lock; /* 定义一个互斥体 */

mutex_init(&lock); /* 初始化互斥体 */

mutex_lock(&lock); /* 上锁 */

/* 临界区 */

mutex_unlock(&lock); /* 解锁 */

使用“互斥体”时,需要注意如下几点:

①、互斥体可以导致休眠,因此不能在中断中使用互斥体,在中断中只能使用自旋锁

②、和信号量一样,“互斥体”保护的临界区可以调用引起阻塞的API函数。

因为一次只有一个线程可以持有“互斥体”,因此,必须由“互斥体”的持有者释放“互斥体”。

③、“互斥体”不能递归上锁和解锁。

相关推荐

  1. Linux delay相关函数实现

    2024-03-15 18:04:02       37 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-15 18:04:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-15 18:04:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-15 18:04:02       82 阅读
  4. Python语言-面向对象

    2024-03-15 18:04:02       91 阅读

热门阅读

  1. 《计算机网络》复试问答题总结

    2024-03-15 18:04:02       31 阅读
  2. Github 2024-03-12 Python开源项目日报 Top9

    2024-03-15 18:04:02       39 阅读
  3. linux 面试题

    2024-03-15 18:04:02       38 阅读
  4. 常用软件下载地址

    2024-03-15 18:04:02       46 阅读
  5. c语言中的宏指的是什么

    2024-03-15 18:04:02       44 阅读
  6. ubuntu docker-compose 编排容器并且设置自启动

    2024-03-15 18:04:02       45 阅读
  7. 安卓实现沉浸式安卓状态栏实现

    2024-03-15 18:04:02       40 阅读
  8. 实景剧本杀小程序开发搭建

    2024-03-15 18:04:02       44 阅读
  9. Spring Boot写一个简单的PDF到Word的转换程序

    2024-03-15 18:04:02       41 阅读
  10. LinearLayout和RelativeLayout对比

    2024-03-15 18:04:02       48 阅读