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函数。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/458325.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

运行vue项目时的问题

1.问题:在终端输入:npm run serve时&#xff0c;弹出选择应用以打开npm 2.解决方法: 在你的终端中输入&#xff1a;get-command npm&#xff08;第一次可能没反应 再输入一次&#xff09; 根据这个路径找到npm删除即可 再次运行npm run serve

谈谈你对Java平台的理解?

从你接触 Java 开发到现在&#xff0c;你对 Java 最直观的印象是什么呢&#xff1f;是它宣传的 “Write once, run anywhere”&#xff0c;还是目前看已经有些过于形式主义的语法呢&#xff1f;你对于 Java 平台到底了解到什么程度&#xff1f;请你先停下来总结思考一下。 今天…

VSCode提交代码

VSCode提交代码方式&#xff1a; 先在电脑本地文件夹中打开git的bash窗口使用git clone https://github.com/xxxx/克隆仓库地址到本地&#xff0c;并生成一个项目的文件夹打开VSCode&#xff0c;点击文件按钮&#xff0c;打开加载项目的文件夹对于VSCode设置Git路径&#xff…

【小黑嵌入式系统第十九课】结课总结(三)——操作系统部分(RTOSμC/OS-Ⅲ程序设计基础(任务函数时间临界区通信))

上一课&#xff1a; 【小黑嵌入式系统第十八课】结课总结&#xff08;二&#xff09;——软件部分&#xff08;系统架构&调试&测试&运行&系统软件设计&#xff09; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0…

迷你洗衣机排名前十名:2024年十大性能超强内衣洗衣机优选

随着现在的生活水平的不断提高&#xff0c;高科技能帮我们搞定不少问题&#xff0c;如果你比较注重个人卫生、追求生活品质&#xff0c;可以考虑选择一台专用的迷你洗衣机&#xff0c;我们就无须自己亲自动手去清洗内衣物&#xff0c;从而导致浪费时间&#xff0c;如果你担心孩…

Docker常见指令

1.docker search mysql &#xff1a;从docker镜像仓库搜索和mysql有关的镜像 docker search mysql 2.docker pull mysql &#xff1a;从docker仓库拉取mysql镜像 docker pull mysql 3.docker run mysql &#xff1a;启动mysql镜像 docker run mysql 4.docker ps &#xff…

iconfont 字体应用

1、登录 打开阿里图标 https://www.iconfont.cn/ 2、选择心仪的图标制作 iconfont 字体。 3、图标全部选择入库之后&#xff0c; 点右上角的购物车。 添加到项目&#xff0c;是方便管理图标字体的。 也可以直接下载代码的 4、下载到本地之后&#xff0c;把里面的 iconfont.…

altgraph的安装和用途说明

前言 altgraph 是 graphlib 的一个分支&#xff1a;一个图&#xff08;网络&#xff09;包&#xff0c;用于构建图、BFS 和 DFS 遍历、拓扑排序、最短路径等&#xff0c;带有 graphviz 输出。 安装 pip install altgraph 函数和用例 生物链 from altgraph import Graph# 定…

linux查看服务器登录成功和登录失败的命令

last 查看成功登录服务器的信息&#xff0c;包括ip&#xff0c;时间&#xff0c;登录用户&#xff0c;时长。lastb 查看登录服务器失败的信息。 last命令实例&#xff1a; 其他参数&#xff1a; -a&#xff1a;把从何处登入系统的主机名称或ip地址&#xff0c;显示在最后一行…

软件设计师16--段页式存储

软件设计师16--段页式存储 考点1&#xff1a;页式存储存储管理 - 页式存储组织存储管理 - 页面置换算法例题&#xff1a; 考点2&#xff1a;段式存储存储管理 - 段式存储组织例题&#xff1a; 考点1&#xff1a;页式存储 存储管理 - 页式存储组织 页式存储&#xff1a;将程序…

MySQL数据表的增删改查(基础)(CRUD)

1.CRUD 注释&#xff1a;在SQL中可以使用“--空格描述”来表示注释说明. CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写. 2.新增(Create) 语法: insert into 表名 values (值,值...); into --可以省略; values -- 关键字. 下面以一…

IDEA编写各种WordCount运行

目录 一、编写WordCount(Spark_scala)提交到spark高可用集群 1.项目结构 2.导入依赖 3.编写scala版的WordCount 4.maven打包 5.运行jar包 ​6.查询hdfs的输出结果 二、本地编写WordCount(Spark_scala)读取本地文件 1.项目结构 2.编写scala版的WordCount 3.编辑Edit …

HDFS的架构优势与基本操作

目录 写在前面一、 HDFS概述1.1 HDFS简介1.2 HDFS优缺点1.2.1 优点1.2.2 缺点 1.3 HDFS组成架构1.4 HDFS文件块大小 二、HDFS的Shell操作&#xff08;开发重点&#xff09;2.1 基本语法2.2 命令大全2.3 常用命令实操2.3.1 上传2.3.2 下载2.3.3 HDFS直接操作 三、HDFS的API操作3…

influxdb2使用

&#xff08;作者&#xff1a;陈玓玏&#xff09; influxdb2首次使用时&#xff0c;通过k8s部署的&#xff0c;所以进入pod内部执行命令。 先在k8sdashboard找到influx的pod&#xff0c;点击执行&#xff0c;即可进入命令行界面。 首次连接时&#xff0c;通过influx setup启动…

Tomcat部署web项目与idea中配置web项目方法【通俗易懂】

✨前言✨   本文章主要介绍tomcat环境的配置&#xff0c;idea配置web项目&#xff0c;idea一般项目中配置tomcat&#xff0c;内容有点长&#xff0c;建议点击目录跳转阅读&#xff0c;文中所含均为官方文件&#xff0c;请放心使用。 &#x1f352;欢迎点赞 &#x1f44d; 收藏…

Visual Studio 2022 配置“Debug|x64”的 Designtime 生成失败。IntelliSense 可能不可用。

今天写代码&#xff0c;无缘无故就给我整个这个错误出来&#xff0c;我一头雾水。 经过我几个小时的奋战&#xff0c;终于解决问题 原因就是这个Q_INTERFACES(&#xff09;宏&#xff0c;我本想使用Q_DECLARE_INTERFACE Q_INTERFACES这两个Qt宏实现不继承QObject也能使用qobjec…

【AI+CAD】(二)LLM和VLM生成结构化数据结构(PPT/CAD/DXF)

当前LLM和VLM在PPT生成任务上已经小有成效,如ChatPPT。 @TOC 1. PPT-LLM LLM根据用户的instruction生成规范的绘制ppt的API语句:即使是最强的GPT-4 + CoT也只能达到20-30%的内容准确度。 LLM输入:User_instruction(当前+过去)、PPT_content、PPT_reader_API。其中 PPT_rea…

STM32F103 CubeMX 定时器输出PWM控制呼吸灯

STM32F103 CubeMX 定时器输出PWM控制呼吸灯 1. 生成代码1. 1 配置外部时钟&#xff0c;SWD口1.2 配置定时器31.3 配置定时器2 2. 代码编写的部分 使用的cubmx 软件的版本&#xff1a;6.2.0 最新版本 6.10&#xff08;2024年3月11日&#xff09; 1. 生成代码 1. 1 配置外部时钟…

Opencascade基础教程(9):切换视图

1、切换视图 1、1 增加视图切换按钮&#xff0c;并添加消息响应函数。 void COCCDemoView::OnButtonFrontview() {//前视图m_View->SetProj(V3d_Yneg);m_View->FitAll(); }void COCCDemoView::OnButtonRearview() {//后视图m_View->SetProj(V3d_Ypos);m_View->Fit…

Spring Web MVC入门(1)

什么是Spring Web MVC? 定义:Spring Web MVC是基于Servlet构建的原始Web框架,从一开始就包含在Spring框架中.它的正式名称"Spring Web MVC"来自其源模块的名称(Spring-webmvc),但是它通常被称为"Spring MVC". 什么是Servlet? Servlet是一种实现动态页面…
最新文章