当前位置: 首页 > news >正文

【嵌入式环境下linux内核及驱动学习笔记-(5-驱动的并发控制机制)】

目录

  • 1、上下文和并发
    • 1.1 上下文
    • 1.2 共享与临界
      • 内核中并发控制机制分为以下几类:
      • 并发控制机制的使用场景:
  • 2、 并发控制机制--中断屏蔽
  • 3、并发控制机制--原子变量
    • 3.1 相关函数
      • 原子量类型
      • a.设置原子量的值
      • b.获取原子量的值
      • c.原子变量加减
      • d.原子变量自增自减
      • e.操作并测试
      • f.原子位操作方法:
    • 3.2 实例
  • 4、并发控制机制--自旋锁
    • 4.1 概念
    • 4.2 函数
      • a.定义自旋锁
      • b.初始化自旋锁
      • c.获得自旋锁(即P操作,置资源为被用)
      • d.释放自旋锁(即V操作,置资源为可用)
      • 使用示范
    • 4.3 实例
    • 4.4 自旋锁的衍生与注意事项
  • 5、并发控制机制--信号量
    • 5.1 函数
      • a.定义信号量
      • b.初始化信号量
      • c.获得信号量P
      • d.释放信号量V
    • 5.2 实例
  • 6、互斥锁
    • 6.1 函数
      • a.初始化
      • b.获取互斥体
      • c.释放互斥体
  • 7 、选择并发控制机制的原则

1、上下文和并发

执行流:有开始有结束总体顺序执行的一段代码 又称上下文。

1.1 上下文

  • 任务上下文:普通的,具有五种状态(就绪态、运行态、睡眠态、暂停态、僵死态),可被阻塞的上下文。
  • 异常上下文:异常时的中断处理上下文。不能进入阻塞状态。

对于应用层编程而言,只有任务上下文。
对于内核编程而言,即有任务上下文也有异常上下文。

1.2 共享与临界

竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态

共享资源:可能会被多个任务同时使用的资源。

临界区:操作共享资源的代码段。

并发控制机制:为了解决竞态,需要提供一种控制机制,来避免在同一时刻使用共享资源,这种机制被称为并发控制机制

内核中并发控制机制分为以下几类:

  • 原子操作类 :不会被系统包括异常打断的操作。
  • 忙等待类 :通过循环轮询的方式,等待资源可用的操作。
  • 阻塞类:资源不可用时,进入睡眠态等待资源可用。

并发控制机制的使用场景:

  • 互斥场景:多个任务不能同时使用同一资源,一个在用时,其它的处于等待状态,互斥体现的是一种排它性。一般操作过程如下:

对互斥锁初始化为可用 --> 对互斥锁进行P操作锁定 --> 临界区 --> 对互斥锁进行V操作放开

  • 同步场景:多个并行任务对资源的有序访问,同步体现的是一种协作性。

对互斥锁初始化为不可用 --> 先行任务完成所有操作后 --> 先行方进行V操作释放资源 --> 后行方P操作直到锁定资源 -->后行方执行操作。

2、 并发控制机制–中断屏蔽

当一个中断服务程序ISR与被打断的任务可能使用相同的资源时。这时,就需要在被打断的任务在进入临界区之前先进行中断屏蔽,等执行完成后再恢复中断。

中断屏蔽相关的函数如下:

中断屏蔽相关函数使能中断相关函数
local_irq_disable()loacal_irq_enable()
local_irq_save(flags)loacal_irq_restore(flags)涉及cpu的中断屏蔽字相关
local_bh_disable()local_bh_enable()与中断低半部有关,操作软中断

中断屏蔽后的临界区代码不能占用太长时间,需要尽快完成。否则中被屏蔽会引起系统调度等一系列问题。
Local_irq_disable()和local_irq_enable()都只能禁止和使能本CPU内的中断,因此,并不能解决SMP多CPU引发的况态。因此单独使用中断屏蔽通常不是一种值得推荐的避免况态的方法,它适合与下面要介绍的自旋锁联合使用。
适用场合:中断上下文与某任务共享资源时,或多个不同优先级的中断上下文间共享资源时。

3、并发控制机制–原子变量

3.1 相关函数

原子变量: 存取时不可被打断的特殊整型变量。
适用场合: 共享资源为单个整型变量的互斥场合。

对原子变量的操作必须用下面这些专用宏或函数:

原子量类型

/include/linux/types.h

typedef struct {
int counter;
} atomic_t;

该类型本质是一个数据结构。

a.设置原子量的值

头文件 /arch/arm/include/asm/atomic.h

原码:
#define ATOMIC_INIT(i) { (i) }
#define atomic_set(v,i) (((v)->counter) = (i))

所以,可以如下定义原子量的初始值

atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
void atomic_set(atomic_t *v,int i); //设置原子量的值为i

这样做是错误的,v = 10; // X 因为不能对原子变量赋值操作,所以这里是错误的

b.获取原子量的值

头文件 /arch/arm/include/asm/atomic.h

原码:
#define atomic_read(v) (*(volatile int *)&(v)->counter)
返回值:实质是 int类型

所以可以这样用函数
atomic_read(atomic_t *v); //返回原子量的值

c.原子变量加减

头文件 /arch/arm/include/asm/atomic.h

原码:
#define atomic_add(i, v) (void) atomic_add_return(i, v)
static inline int atomic_add_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter;
v->counter = val += i;
raw_local_irq_restore(flags);
return val;
}

#define atomic_sub(i, v) (void) atomic_sub_return(i, v)
static inline int atomic_sub_return(int i, atomic_t *v)
{
unsigned long flags;
int val;
raw_local_irq_save(flags);
val = v->counter;
v->counter = val -= i;
raw_local_irq_restore(flags);
return val;
}

所以可以这样用:
void atomic_add(int i,atomic_t *v);//原子变量增加I,返回V的Int类型值
void atomic_sub(int i,atomic_t *v);//原子变量减少I,返回V的Int类型值

d.原子变量自增自减

头文件 /arch/arm/include/asm/atomic.h

原码
#define atomic_inc(v) atomic_add(1, v)
#define atomic_dec(v) atomic_sub(1, v)

所以可以这样用:
void atomic_inc(atomic_t *v);//原子变量增加1,返回V的Int类型值
void atomic_dec(atomic_t *v);//原子变量减少1,返回V的Int类型值

e.操作并测试

头文件 /arch/arm/include/asm/atomic.h

原码
#define atomic_inc_and_test(v) (atomic_add_return(1, v) == 0)
#define atomic_dec_and_test(v) (atomic_sub_return(1, v) == 0)
#define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)

所以可以这样使用函数:
运算后结果为0则返回真,否则返回假
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);

f.原子位操作方法:

头文件:/arch/arm/include/asm/bitops.h
原码:

#define ATOMIC_BITOP(name,nr,p) (__builtin_constant_p(nr) ? __atomic##name(nr, p) : ##name(nr,p))
====================================================================================
这个宏定义的意思是:
如果nr(代表bit number,即位号)是一个常数,则使用____atomic
##name形式的位操作函数。
否则,使用
##name形式的位操作函数。
举个例子,如果定义如下宏:
c
#define SET_BIT(nr, p) ATOMIC_BITOP(set_bit, nr, p)
那么当调用SET_BIT(5, ptr)时,如果5是一个常数,会展开为:
c
____atomic_set_bit(5, ptr)
否则,会展开为:
c
_set_bit(nr, ptr)
在宏定义中使用##运算符可以将两个符号连接成一个符号。这被称为宏连接(Macro Concatenation)。
例如在这个宏定义中:
c
#define ATOMIC_BITOP(name,nr,p) (__builtin_constant_p(nr) ? ___atomic##name(nr, p) : _##name(nr,p))
##运算符被用于:
___atomic##name:将name和____atomic_连接为一个符号,例如____atomic_set_bit
_##name:将_和name连接为一个符号,例如_set_bit
所以如果定义:
c
#define SET_BIT(nr, p) ATOMIC_BITOP(set_bit, nr, p)
那么这个宏可以展开为:
c
(__builtin_constant_p(nr) ? ____atomic_set_bit(nr, p) : _set_bit(nr,p))
=============================================================

其下为使用函数:

#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p)
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
#define test_and_clear_bit(nr,p) ATOMIC_BITOP(test_and_clear_bit,nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)

所以可以这么用:

  • 设置位
    void set_bit(nr, void *addr); //设置addr所指向的数据的第nr位为1
  • 清除位
    void clear_bit(nr , void *addr); //清除addr所指向的数据的第nr位为0
  • 改变位
    void change_bit(nr , void *addr); //改变addr所指向的数据的第nr位为1
  • 测试位
    void test_bit(nr , void *addr); //测试addr所指向的数据的第nr位是否为1

3.2 实例

要求:字符驱动只能被一个应用进程使用。即只能被一个任务open,其它任务在同一时间要打开该设备时会出错提示。

/*************************************************************************> File Name: atomic-only.c> 作用:以原子变量做为并发控制的手段,使本驱动同时只能被一个应用层进程所open************************************************************************/#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>/*1、定义重要的变量及结构体*/
struct x_dev_t {struct cdev  my_dev;  //cdev设备描述结构体变量atomic_t have_open;   //记录驱动是否被打开的原子变量
};struct x_dev_t *pcdev;/*所有驱动函数声明*/
int open (struct inode *, struct file *);
int close (struct inode *, struct file *);//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={.open = open,.release = close,
};static int __init my_init(void){int unsucc =0;dev_t devno;int major,minor;pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);/*2、创建 devno */unsucc = alloc_chrdev_region(&devno , 0 , 1 , "atomic-char");if (unsucc){printk(" creating devno  faild\n");return -1;}major = MAJOR(devno);minor = MINOR(devno);printk("devno major = %d ; minor = %d;\n",major , minor);/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*//*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/cdev_init(&pcdev->my_dev , &fops);pcdev->my_dev.owner = THIS_MODULE;/*4、注册cdev结构体到内核链表中*/unsucc = cdev_add(&pcdev->my_dev,devno,1);if (unsucc){printk("cdev add faild \n");return 1;}//初始化原子量have_open为1atomic_set(&pcdev->have_open,1);printk("the driver atomic-char initalization completed\n");return 0;
}static void  __exit my_exit(void)
{cdev_del(&pcdev->my_dev);unregister_chrdev_region(pcdev->my_dev.dev , 1);printk("***************the driver atomic-char exit************\n");
}
/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);pf->private_data = (void *)p;//在open函数中对原子量have_open进行减1并检测。=0,允许打开文件,<0则不允许打开if (atomic_dec_and_test(&p->have_open)){printk("atomic-char is opened\n");return 0;}else{printk("device atomic-char can't be opened again\n");atomic_inc(&p->have_open);//原子量=-1,记得这里要把原子量加回到0return -1;}   
}
/*file_operations结构全成员函数.release的具体实现*/
int close(struct inode *pnode , struct file *pf){struct x_dev_t *p = (struct x_dev_t *)pf->private_data;printk("atomic-char is closed \n");atomic_set(&p->have_open,1);return 0;
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");

4、并发控制机制–自旋锁

4.1 概念

自旋锁(Spin Lock)是一种典型的对临界资源进行互斥访问的手段,其名称来源于它的工作方式。为了获得一个自旋锁,在某个CPU上运行的代码需先执行一个原子操作,该操作测试并设置某个内存变量。由于它是原子操作,所以在该操作完成之前其它执行单元不可能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋”,通俗说就是“在原地打转”。当自旋锁 持有者通过重置该变量释放这个自旋锁后,某个等待的“测试并设置”操作向其调用者报告锁已释放。

自旋锁是基于忙等待的并发控制机制,由于在获取不到资源锁时会进入忙等待,不会进入睡眠状态。忙等待是占时间片的,因此这个机制不要用于过长的临界区执行。也因此,这个互斥锁可用于异常上下文,不会使异常进入阻塞状态。

适用场合:

  1. 异常上下文之间或异常上下文与任务上下文之间共享资源时
  2. 任务上下文之间且临界区执行时间很短时
  3. 互斥问题

4.2 函数

#include <linux/spinlock.h>

a.定义自旋锁

原码:
typedef struct spinlock {
union {
struct raw_spinlock rlock;

};
};
} spinlock_t;

所以使用前先定义自旋锁:
spinlock_t lock;

b.初始化自旋锁

spin_lock_init(spinlock_t *);

c.获得自旋锁(即P操作,置资源为被用)

spin_lock(spinlock_t *); //成功获得自旋锁立即返回,否则自旋在那里,直到该自旋锁的保持者释放

spin_trylock(spinlock_t *); //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”

d.释放自旋锁(即V操作,置资源为可用)

spin_unlock(spinlock_t *);

使用示范

#include <linux/spinlock.h>
定义spinlock_t类型的变量lock
spin_lock_init(&lock)后才能正常使用spinlockspin_lock(&lock);
临界区
spin_unlock(&lock);

4.3 实例

要求:用自旋锁实现上节中一个驱动同一时只能被打开一次。

/*************************************************************************> File Name: spinlock-only.c> 作用:以自旋锁做为并发控制的手段,使本驱动同时只能被一个应用层进程所open************************************************************************/#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/spinlock.h>/*1、定义重要的变量及结构体*/
struct x_dev_t {struct cdev  my_dev;  //cdev设备描述结构体变量spinlock_t lock; //自旋锁int have_open;   //开启标志,0为已开启,1为未开启};struct x_dev_t *pcdev;/*所有驱动函数声明*/
int open (struct inode *, struct file *);
int close (struct inode *, struct file *);//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={.open = open,.release = close,
};static int __init my_init(void){int unsucc =0;dev_t devno;int major,minor;pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);/*2、创建 devno */unsucc = alloc_chrdev_region(&devno , 0 , 1 , "spinlock-char");if (unsucc){printk(" driver: creating devno  faild\n");return -1;}major = MAJOR(devno);minor = MINOR(devno);printk("driver : devno major = %d ; minor = %d;\n",major , minor);/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*//*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/cdev_init(&pcdev->my_dev , &fops);pcdev->my_dev.owner = THIS_MODULE;/*4、注册cdev结构体到内核链表中*/unsucc = cdev_add(&pcdev->my_dev,devno,1);if (unsucc){printk("driver : cdev add faild \n");return 1;}//初始化自旋锁have_open 和 开启标志 spin_lock_init(&pcdev->lock);pcdev->have_open = 1;printk("driver : the driver spinlock-char initalization completed\n");return 0;
}static void  __exit my_exit(void)
{cdev_del(&pcdev->my_dev);unregister_chrdev_region(pcdev->my_dev.dev , 1);printk("***************the driver spinlock-char  exit************\n");
}
/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);pf->private_data = (void *)p;//在open函数中对原子量have_open进行减1并检测。=0,允许打开文件,<0则不允许打开spin_lock(&p->lock);if (p->have_open == 1){p->have_open = 0;printk("driver : spinlock-char  is opened\n");spin_unlock(&p->lock);return 0;}else{ //已被打开printk("driver : device spinlock-char Could not opend again\n");spin_unlock(&p->lock);return -1;}
}
/*file_operations结构全成员函数.release的具体实现*/
int close(struct inode *pnode , struct file *pf){printk("driver : spinlock-char is closed \n");return 0;
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");

4.4 自旋锁的衍生与注意事项

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

5、并发控制机制–信号量

信号量(Semaphore)是操作系统中最典型的用于同步和互斥的手段,信号量的值可以是0、1或者n。信号量与操作系统中的经典概念PV操作对应。

P(S): 将信号量S的值减1,即S=S-1; 如果S>=0,则该进程继续执行;否则该进程置为睡眠状态(阻塞),进入等待队列。
V(S):将信号量S的值加1,即S=S+1; 如果S>0,唤醒队列中阻塞而等待信号量的进程。

基于阻塞的并发控制机制,当要取资源时,如遇资源不足,则会使本任务进入阻塞状态。即P操作不成功时会进入阻塞睡眠状态。

适用场合:只能用于任务上下文之间且临界区执行时间较长时的互斥或同步问题

5.1 函数

#include <linux/semaphore.h>

a.定义信号量

原码:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

使用方法:
struct semaphore sem;

b.初始化信号量

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

c.获得信号量P

int down(struct semaphore *sem);//深度睡眠

int down_interruptible(struct semaphore *sem);//浅度睡眠

一旦信号量sem的值为0时,则该函数会进入睡眠状态。直到sem值大于0后。

d.释放信号量V

void up(struct semaphore *sem);

5.2 实例

要求:一个内存缓冲虚拟一个设备。该设备允许应用层多个进程同时对该设备进行读写操作。这样就需要驱动对并发场景进行控制。

/*************************************************************************> File Name: semaphore-memory.c************************************************************************/#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>/*1、定义重要的变量及结构体*/#define  MEM_SIZE 500struct mem_dev_t{dev_t devno;struct cdev  my_dev;  //cdev设备描述结构体变量char  mem[MEM_SIZE]; //内存池,当成虚拟设备struct semaphore sem; //信号量
};struct mem_dev_t *mem_dev;/*驱动函数声明*/ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={.open = open,.release = release,.read = read,.write = write,
};/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
static int cdev_setup(struct mem_dev_t *mem_dev  ){int unsucc =0;cdev_init(&mem_dev->my_dev , &fops);mem_dev->my_dev.owner = THIS_MODULE;/*4、注册cdev结构体到内核链表中*/unsucc = cdev_add(&mem_dev->my_dev,mem_dev->devno,1);if (unsucc){printk("cdev add faild \n");return -1;}sema_init( &mem_dev->sem,1); //初始化信号量,为1return 0;}static int __init my_init(void){int major , minor;int unsucc =0;mem_dev = kzalloc(sizeof(struct mem_dev_t) , GFP_KERNEL);if (!mem_dev){printk(" allocating memory is  failed");return  -1;}/*2、创建 devno */unsucc = alloc_chrdev_region(&mem_dev->devno , 0 , 1 , "operate_memory");if (unsucc){printk(" creating devno  is failed\n");return -1;}else{major = MAJOR(mem_dev->devno);minor = MINOR(mem_dev->devno);printk("major = %d  ; minor = %d\n",major,minor);}/*3、 初始化cdev结构体,并联cdev结构体与file_operations.*//*4、注册cdev结构体到内核链表中*/if (cdev_setup(mem_dev) == 0){printk("the driver operate_memory  initalization completed\n");return 0;   } elsereturn -1;
}static void  __exit my_exit(void)
{cdev_del(&mem_dev->my_dev);unregister_chrdev_region(mem_dev->devno , 1);printk("***************the driver operate_memory exit************\n");
}/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/int open(struct inode *pnode , struct file *pf){pf->private_data = (void*)mem_dev;  //把全局变量指针放入到struct file结构体里printk("operate_memory is opened\n");return 0;}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){printk("operate_memory is closed \n");return 0;
}/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){struct mem_dev_t *pdev = pf->private_data;int count = 0;//判断偏移量的有效性if (*ppos >= MEM_SIZE){return 0;}//判断能够读到的字节数量if  (size > MEM_SIZE - *ppos){count = MEM_SIZE - *ppos;}else{count = size;}//copy_from_user返回值大于0失败down(&pdev->sem);   //信号量P操作if ( copy_to_user(buf , &pdev->mem[*ppos] , count )){up(&pdev->sem);   //退出前释放信号量,V操作return 0;}else{*ppos += count;up(&pdev->sem); //退出前释放信号量,V操作return count;}}/*file_operations结构全成员函数.write的具体实现*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){struct mem_dev_t *pdev = pf->private_data;int count = 0;//判断偏移量的有效性if (*ppos >=MEM_SIZE ){return 0;}//判断能够写入的字节数量if (size > MEM_SIZE-*ppos){count = MEM_SIZE-*ppos;}else{count = size;}//copy_from_user返回值大于0失败down(&pdev->sem);   //信号量P操作if ( copy_from_user(&pdev->mem[*ppos] , buf , count)){up(&pdev->sem); //退出前释放信号量,V操作return 0;}else{*ppos +=count;up(&pdev->sem); //退出前释放信号量,V操作return count;}}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");

6、互斥锁

基于阻塞的互斥机制。

=适用场合:任务上下文之间且临界区执行时间较长时的互斥问题

6.1 函数

#include <linux/mutex.h>

a.初始化

struct mutex my_mutex;
mutex_init(&my_mutex);

b.获取互斥体

void mutex_lock(struct mutex *lock);

c.释放互斥体

void mutex_unlock(struct mutex *lock);

使用步骤:

  1. 定义对应类型的变量
  2. 初始化对应变量

P/加锁
临界区
V/解锁

7 、选择并发控制机制的原则

  1. 不允许睡眠的上下文(异常上下文)需要采用忙等待类(自旋锁,原子变量),可以睡眠的上下文可以采用阻塞类(信号量,互斥锁)。在异常上下文中访问的竞争资源一定采用忙等待类。

  2. 临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。

  3. 中断屏蔽仅在有与中断上下文共享资源时使用。

  4. 共享资源仅是一个简单整型量时用原子变量

相关文章:

【嵌入式环境下linux内核及驱动学习笔记-(5-驱动的并发控制机制)】

目录 1、上下文和并发1.1 上下文1.2 共享与临界内核中并发控制机制分为以下几类&#xff1a;并发控制机制的使用场景&#xff1a; 2、 并发控制机制--中断屏蔽3、并发控制机制--原子变量3.1 相关函数原子量类型a.设置原子量的值b.获取原子量的值c.原子变量加减d.原子变量自增自…...

必学宝典 黑马《最新JavaWeb开发教程》上线

对于程序员&#xff0c;所在的行业更迭实属过快&#xff0c;如果是为了找一份好工作&#xff0c;学技术前一定要先了解技术在市场中的需求情况。不然等你学完之后&#xff0c;才发现自己学了已被淘汰、过时的技术&#xff0c;白白浪费了宝贵的学习时间&#xff0c;后悔都来不及…...

【社区图书馆】学习如何读书

人类社会只有发明了发明的方法之后才能发展。同样道理&#xff0c;你们只有学习了学习的方法之后才能进步。 ——查理芒格 进了开发的门&#xff0c;从一开始就在查文档、百度搜 bug 解决的文章、买书&#xff0c;读书。买了很多很厚的工具书&#xff0c;然后拿来垫桌子。再然后…...

CO02工单组件,新增/删除/修改

REPORT zpp153. 事务代码&#xff1a; ZPP153-整散车生产订单自动调整程序名称&#xff1a;ZPP153-整散车生产订单自动调整程序目的&#xff1a;ZPP153-整散车生产订单自动调整开发人员&#xff1a; *(修改日志)--------------------------------------------------------日志号…...

MIT6.824 lab3AB记录

实验目标&#xff1a;基于raft日志复制算法实现的线性一致性kv存储引擎。 线性一致性&#xff1a; 所有的读操作都能够读取到最近一次写操作的结果。所有节点&#xff08;或者进程&#xff09;在同一时刻&#xff0c;看到的数据都是相同的。 简而言之&#xff0c;线性一致性…...

一分钟了解美国棒球体系·棒球1号位

美国棒球体系是一个庞大且复杂的体系&#xff0c;涵盖了从青少年到职业的各个层次。下面是美国棒球体系的主要组成部分&#xff1a; 1. 青少年棒球&#xff08;Youth Baseball&#xff09; 美国的青少年棒球体系包括各种地区和全国性的联盟&#xff0c;如Little League、Pony…...

通过ObjectMapper和JsonNode 把JSON字符串转换成树结构数据和获取树节点数据

一.简介 今天同事有个需求&#xff0c;要把一个JSON字符串转换成一个树结构的数据并获取节点数据&#xff0c;鉴于自己不想写递归去转换&#xff0c;于是使用ObjectMapper和JsonNode类去实现。 二.依赖 pom文件引入依赖&#xff1a; <dependency><groupId>com.…...

鉴源论坛 · 观模丨面向界面的图形化测试技术

作者 | 熊一衡 华东师范大学软件工程学院博士 苏亭 华东师范大学软件工程学院教授 版块 | 鉴源论坛 观模 01 什么是面向界面的图形化测试&#xff08;GUI Testing&#xff09; 图形用户界面(GUI) 是一种通过图形化方式呈现信息、数据、功能和操作的用户界面&#xff0c;旨在…...

Midjourney以图生图的详细教程(含6种案例介绍)

&#x1f3c6; 文章目标&#xff1a;学习并介绍Midjourney以图生图的详细教程 &#x1f340; Midjourney以图生图的详细教程 ✅ 创作者&#xff1a;熊猫Jay &#x1f389; 个人主页&#xff1a;Jay的个人主页 &#x1f341; 展望&#xff1a;若本篇讲解内容帮助到您&#xff0c…...

基于单片机的电路特性测试仪的设计

摘 要 当今社会科技的飞速发展&#xff0c;智能和便捷已经成为人们的日常诉求。现在放大电路在使用过程中经常出现故障&#xff0c;并且需要测试电路数据&#xff0c;但是大多数是手动进行测试&#xff0c;一定程度上影响了工作效率。 为了测量数据更安全更便捷&#xff0c;针…...

五一将迎2亿人次出行,君子签助力旅行社合规高效签旅游电子合同

近日&#xff0c;为规范旅游市场秩序&#xff0c;促进旅行社高质量发展&#xff0c;文旅部发布了《文化和旅游部办公厅关于进一步规范旅游市场秩序的通知》&#xff08;下称《通知》&#xff09;&#xff0c;对旅游业提出了新的要求。 《通知》中规范了旅行社经营行为。旅行社要…...

IAP升级遇到的问题

文章目录 1. app程序在SystemClock_Config中跑飞2. 程序HAL_Delay中卡死3. 通过外部flash模拟的U盘没能被电脑识别4. 将bin文件拷贝到片内flash中失败5、APP程序跳转过后串口不能工作 这几天在STM32G473使用IAP升级的时候踩了不少坑 1. app程序在SystemClock_Config中跑飞 boo…...

简单聊聊k8s,和docker之间的关系

前言 随着云原生和微服务架构的快速发展&#xff0c;Kubernetes和Docker已经成为了两个重要的技术。但是有小伙伴通常对这两个技术的关系产生疑惑&#xff1a; 既然有了docker&#xff0c;为什么又出来一个k8s&#xff1f; 它俩之间是竞品的关系吗&#xff1f; 傻傻分不清。…...

半小时学会HTML5

一、了解几个概念 1、HTML定义 HTML是&#xff08;Hyper Text Markup Language&#xff09;超文本标记语言&#xff0c;超文本包含&#xff1a;文字、图片、音频、视频、动画等。 2、W3C 是什么&#xff1f; W3C 即&#xff08;World Wide Web Consortium&#xff09; 万维…...

研报精选230421

目录 【行业230421南京证券】氢能行业&#xff1a;地缘政治加速绿色能源转型 【行业230421华安证券】AIGC行业研究框架与投资逻辑 【行业230421信达证券】工控行业深度报告&#xff1a;行业拐点将至&#xff0c;国产品牌加速崛起 【个股230421国信证券_华阳集团】聚焦汽车智能化…...

AI绘图风格对照表/画风样稿详细研究记录及经验总结(分析Midjourney和Stable Diffusion风格提示词实际使用情况)不断更新中...

Midjourney和Stable Diffusion都可以通过输入文本生成出令人惊叹的AI图像。 Midjourney是一个收费的在线服务&#xff0c;通过discord对话的形式来生图&#xff0c;局限性较大&#xff0c;但由于后台官方模型做得好&#xff0c;因此出图效果非常完美&#xff1b; Stable Diffus…...

人工智能论文的风格特点

搞清楚AI领域论文的风格特点是写出一篇高质量AI论文的前提&#xff0c;AI领域的论文有如下显著特点。 1. 论文的架构非常清晰且富有逻辑。一篇高质量的AI论文&#xff0c;读者通过大致扫一眼论文的各级标题就能够对论文的写作思路形成清晰的认识&#xff0c;明白论文各部分之间…...

成功上岸国防科大!

Datawhale干货 作者&#xff1a;王洲烽&#xff0c;太原理工大学&#xff0c;Datawhale成员 写在前面 相比较于一般的经验贴&#xff0c;我更想在这里讲述一下自己的故事。我一开始报考的是北理工&#xff0c;但很遗憾9月份北理改考408了&#xff0c;无缘京爷&#xff0c;所以…...

【C语言】输入输出、字符串操作、内存操作、文件操作函数

三对基本输入输出函数 1.gets()&#xff0c;puts() gets()从标准输入中获取一个字符串&#xff0c;到str&#xff08;自己创建的char型数组&#xff09;中&#xff0c;读到换行或输入末尾结束获取r&#xff1b; 成功返回str&#xff0c;失败返回空。 char *gets(char *str)…...

[golang gin框架] 25.Gin 商城项目-配置清除缓存以及前台列表页面数据渲染公共数据

配置清除缓存 当进入前台首页时,会缓存对应的商品相关数据,这时,如果后台修改了商品的相关数据,缓存中的对应数据并没有随之发生改变,这时就需要需改对应的缓存数据,这里有两种方法: 方法一 在管理后台操作直接清除缓存中的所有数据,当再次访问前台首页时,就会先从数据库中获取…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...