Linux——互斥与同步

2023-05-16

目录

一种典型的竞态

内核中的并发

中断屏蔽

原子变量

自旋锁

读写锁

顺序锁



一种典型的竞态


        假设整型变量i是驱动代码中的一个个全局变量,在驱动的某个例程中执行了i++操作,而在中断服务程序中也执行了i++操作,在这种情形下我们来分析一下可能造成的数据紊乱情况。首先来看看i++操作用 ARM 汇编展开是怎样的。

ldr r1, [r0]

add r1, r1, #1

str r1,   [r0]

        假设变量i是存放在r0所指向的内存中的,也就是说,r0 寄存器中保存了变量的地址,并且变量i的初值为5。汇编代码第一行首先通过寄存器间接寻址将变量的值装载到了r1 寄存器中,然后使用 add 指令将r1 的值自加 1,最后使用 str 指令加1后的值存回变量i所在的内存中。由上可见,一条简单的i++操作翻译成汇编后变成了3 条指令,这就会引入一些问题。假设一条内核的执行路径刚好将第一条指令执行完成,外部产生了一个硬件中断,这时,这条内核执行路径被打断,而去执行中断处理程序。碰巧的是,中断处理程序也要对这个全局变量进行自加操作,中断处理程序在没有被打断的情况下成功将变量i的值自加到 6,然后中断处理程序返回,继续刚才被打断的内核执行路径。此时寄存器的值被恢复后,r1 的值还是之前从内存中取到的 5,然后自加1后变成6,最后又存入变量i所在的内存。很显然,这个过程共执行了两次i++操作,变量i的值应该为 7,但结果却为 6,这就造成了数据紊乱,也就是内核的不同路径同时《并非是严格意义上的同时,就如同被中断抢占了的情况一样,在一个路径还没有对共享资源访问完成时,被内核的另一条执行路径所抢占,也认为是同时) 对共享资源的访问选成了竞态。

内核中的并发

        总的来说,当内核有多条执行路径同时访问同一个共享资源时,就会造成竞态。常见的共享资源有全局变量、静态变量、硬件的寄存器和共同使用的动态分配的同一段内存等。造成竞态的根本原因就是内核中的代码对共享资源产生了并发(同时)的访问。那么内核中有哪些并发的情况呢?下面罗列如下。


(1)硬件中断一一当处理器允许中断的时候,一个内核执行路径可能在任何一个时间都会被一个外部中断打断。
(2)软中断和 tasklet一通过前面的知识我们知道,内核可以在任意硬中断快要返回之前执行软中断及 tasklet,也有可能唤醒软中断线程,并执行 tasklet。

(3)抢占内核的多进程环境——如果一个进程在执行时发生系统调用,进入到内核,由内核代替该进程完成相应的操作,此时如有一个更高优先级的进程准备就绪,内核判断在可抢占的条件成立的情况下可以抢占当前进程,然后去执行更高优先级的进程。
(4)普通的多进程环境一一当一个进程因为等待的资源暂时不可用时,就会主动放弃CPU,内核会调度另外一个进程来执行。
(5)多处理器或多核 CPU。在同一时刻,可以在多个处理器上并发执行多个程序这是真正意义上的并发
        并发对共享资源访问就会引起竟态,解决竟态的一个方法就是互斥,也就是对共享资源的串行化访问,即在一条内核执行路径上访问共享资源时,不允许其他内核执行路径来访问共享资源。共享资源有时候又叫作临界资源,而访问共享资源的这段代码又叫作临界代码段或临界区。内核提供了多种互斥的手段,下面逐一进行介绍。

中断屏蔽


        在说明竞态所举的例子中,造成竞态的原因是一条内核执行路径被中断打断了。如果在访问共享资源之前先将中断屏蔽(禁止),然后再访问共享资源,等共享资源访问完成后再重新使能中断就能避免这种竞态的产生。关于中断的屏蔽和使能的函数我们在前面已经讲过了,这里就不再赘述。需要另外说明的是,如果明确知道是哪一个中断会带来竞态,我们通常应该只屏蔽相应的中断,而不是屏蔽本地 CPU 的全局中断,这样可以使其他中断照常执行。如果非要屏蔽本地 CPU 的中断,那么应该尽量使用local_irq_save 和 local_irq_restore 这对宏,因为如果使用 local_irq_disable 和 local_irq_enable 这对宏,如果中断在屏蔽之前本身就是屏蔽的,那么 local_irq_enable 会将本来就屏蔽的中断错误地使能,从而造成中断使能状态的前后不一致。而且,中断屏蔽到中断重新使能之间的这段代码不宜过长,否则中断屏蔽的时间过长,将会影响系统的性能。在i++的例子中,可以在 i++之前屏蔽中断,之后重新使能中断,代码的形式如下

unsigned long flags;
local_irq_save(flags);
i++;
local_irq_restore(flags);


使用中断屏蔽来做互斥时的注意事项总结如下。
(1)对解决中断引起的并发而带来的竞态简单高效。
(2)应该尽量使用local_irq_save和 local_irq_restore 来屏蔽和使能中断
(3)中断屏蔽的时间不宜过长。
(4)只能屏蔽本地CPU的中断,对多CPU 系统,中断也可能会在其他CPU上产生


原子变量


        如果一个变量的操作是原子性的,即不能再被分割,类似于在汇编级代码上也只要一条汇编指令就能完成,那么对这样变量的访问就根本不需要考虑并发带来的影响。因此,内核专门提供了一种数据类型 atomic_t,用它来定义的变量为原子变量,其类型定义如下。
/*include/1inux/types.h */
 

   175	typedef struct {
   176		int counter;
   177	} atomic_t;


        由上可知,原子变量其实是一个整型变量。对于整型变量,有的处理器专门提供一些指令来实现原子操作(比如ARM 处理器中的swp 指令),内核就会使用这些指令来对原子变量进行访问,但是有些处理器不具备这样的指令,内核将会使用其他的手段来保证对它访问的原子性,比如中断屏蔽。但对于驱动开发者来说,他们并不关心这些,他们更关心内核提供了哪些接口来操作原子变量。现将主要的 API罗列如下。

atomic read(v)
atomic set(v, i)
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_add_negative(int i, atomic_t *v)
void atomic_add(int i, atomic_t *V);
void atomic_sub(int i, atomic_t *v);
void atomic_inc(atomic_t *v);
void atomic dec(atomic_t *v);
atomic_dec_return(v)
atomic_inc_return(V)
atomic_sub_and_test(i, v)
atomic_dec_and_test(v)
atomic_inc_and_test(v)
atomic_xchg(ptr,v)
atomlc_cmpxchg(v,old,new)
void atomic_clear_mask(unsigned long mask, atomic_t *v);
void atomic_set_mask(unsigned int mask, atomic_t *v);
void set_bit(int nr, volatile unsigned long *addr);
void clear_bit(int nr, volatile unsigned long *addr);
void change_bit(int nr, volatlle unsigned long *addr);
int test_and_set_bit(int nr, volatile unsigned long *addr);
int test_and_clear_bit(int nr, volatile unsigned long *addr);
int test_and_change_bit(int nr, volatile unsigned long *addr);


        atomic_read;读取原子变量v的值。
        atomic_set(v,i), 设置原子变量v的值为i。
        atomic_add、 atomic_sub: 将原子变量加上i成减去 i, 加“ _return”表示还要返回修改后的值,加“ _negative”表示当结果为负返回真。

        atomic_inc、atomic_dec:将原子变量自加1或自减 1, 加“_return”表示还要返回改后的值,加“ _test”表示结果为0返回真。
        atomic_xchg:交换v和ptr 指针指向的数据。

        atomic_cmpxchg: 如果v的值和 old 相等,则将 V 的值设为 new,并返回原来的值。        

        atomic_clear_mask:将v中mask为1的对应位清零。
        atomic_set_mask:将v中mask为1的对应位置一.
        set_bit、clear_bit、change_bit: 将nr位置一、清零或翻转,有 test 前缀的还要返回来的值。
        在i++的那个例子中,我们可以使用下面的代码来保证对它访问的原子性操作,第一行使用ATOMIC_INT对原子变量赋初值
 

atomic_t i = ATOMIC_INIT(5);

atomic_inc(&1);

        需要说明的是,原子变量虽然使用方便,但是其本质是一个整型变量,对于非整形变量(如整个结构)就不能使用这一套方法来操作,而需要使用另外的方法。但是在能够使用原子变量时就尽可能地使用原子变量,而不要使用复杂的锁机制,因为相比于锁机制,它的开销小。


自旋锁


        有这样一个不太优雅的例子:一个公共的卫生间,很多人排队去方便,但是要进去就要先打开卫生间的门,进去后将门反锁,出来后再开锁。很显然,在门被反锁的期间,其他人是进不去的,只有干着急,越等越急后,就急得团团转,于是就原地自旋了。

        这个例子很能说明在内核中的另一种互斥手段一一自旋锁的特性,在访问共享资源(卫生间)之前,首先要获得自旋锁《卫生间门上的锁),访问完共享资源后解锁。其他内核执行路径(其他人)如果没有竞争到锁,只能忙等待,所以自旋锁是一种忙等锁。

        内核中自旋锁的类型是spinlock_t,相关的API如下

 

spin_lock_init( _lock)
void spin_lock(spinlock_t *lock);

void spin_lock_irq(spinlock_t *lock);
void spin_lock_irqsave(lock, flags);
void spin_lock_bh(spinlock_t *lock);

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
int spin_trylock_irq(spinlock_t *lock);
void spin_unlock(spinlonk_t *lock);
void spin_unlock_irq(spinlonk_t *lock);
void spin_unlock_irqrestore(spinlonk_t *lock, unsigned long flags);
void spin_unlock_bh(spinlock_t *lock);


spin_lock_init; 初始化自旋锁,在使用自旋锁之前必须要初始化
spin_lock:获取自旋锁,如果不能获得自旋锁,则进行忙等待。
spin_lock_bh:获取自旋锁并禁止下半部。
spin_lock_irq:获取自旋锁并竞争中断。
spin_lock_irqsave; 获取自锁并竞争中断,保存中断屏藏状态到fags中。
spin_trylock,尝试获取自锁,即使不能获取,立即返回,返回值为 0麦示成功获得自旋锁,否则表示没有获得自锁。其他的变体和 spin_lock 变体的意义相同。

spin_unlock;释放自锁,其他的 unlock 版本可以依据前面的解释判断其作用。

在i++的例子中,我们可以使用下而的代码来使自旋锁对了的操作进行互斥。

 

int i = 5;
/*定义自旋锁 */
spinlock_t lock;
/*用于保存中断屏蔽状态的变量*/
unsigned long flags;

/*使用自旋锁之前必须初始化自旋锁 */
spin_lock_init(&lock);
/*访问共享资源之前获得自旋锁,禁止中断,并将之前的中断屏蔽状态保存在flags变量中*/
spin_lock_irqsave(&lock, flags);
/*访网共享资源*/
i++;
/*共享资源访问完成后释放自旋锁,用 flags 的值恢复中断屏蔽的状态*/
spin_unlock_irqrestore(&lock,flags);


        从上面的例子可以看到,自旋锁的使用还是比较直观的,基本的步骤是定义锁、初始化锁、访问共享资源之前获得锁、访间完成之后释放锁。


        关于自旋锁的一些重要特性和使用注意事项总结加下。


(1)获得自旋锁的临界代码段执行时间不宜过长,因为是忙等锁,如果临界代码段执行时间过长,就意味着其他想要获得锁的内核执行路径会进行长时间的忙等待,这会影响系统的工作效率。
(2)在获得锁的期间,不能够调用可能会引起进程切换的函数,因为这会增加持锁的时间,导致其他要获取锁的代码进行更长时间的等待,更糟糕的情况是,如果新调度的进程也要获取同样的自旋锁,那么会导致死锁。
(3)自旋锁是不可递归的,即获得锁之后不能再获得自旋锁,否则会因为等待一个不能获得的锁而将自己锁死。
(4)自旋锁可以用于中断上下文中,因为它是忙等锁,所以并不会引起进程的切换.

(5)如果中断中也要访问共享资源,则在非中断处理代码中访问共享资源之前应该先禁止中断再获取自旋锁,即应该使用 spin_lock_irq 或 spin_lock_irqsave 来获得自旋锁,如果不这样的话,即使获得了锁中断也会发生,在中断中访问共享资源之前,中断也要获得一个已经被获得的自旋锁,那么中断将会被锁死,中断的下半部也有类似的情况。另外,推荐使用 spin_lock_irqsave 而不是 spin_lock_irq,原因同中断屏蔽中相关的描述.

(6)虽然一直都在说自旋锁是忙等锁,但是在单处理器的无抢占内核中,单纯的自旋锁(指不是禁止中断,禁止下半部的一些变体)获取操作其实是一个空操作,而在单处理器的可抢占内核中也仅仅是禁止抢占而己(但这会使高优先级的就绪进程的执行时间稍微推后一些)。真正的忙等待的特性只有在多处理器中才会体现出来,不过作为驱动开发者,我们不应该来假设驱动的运行环境,或者说都应该假设成运行在多处理器的可抢占系统上。


读写锁

        在并发的方式中有读一读并发、读一写并发和写一写并发三种,很显然,一般的资源的读操作并不会修改它的值(对某些读清零的硬件寄存器除外),因此读和读之间是完全允许并发的。但是使用自旋锁,读操作也会被加锁,从而阻止了另外一个读操作。为了提高并发的效率,必须要降低锁的粒度,以允许读和读之间的并发。为此,内核提供了一种允许读和读并发的锁,叫读写锁,其数据类型为rwlock_t,常用的API如下。

rwlock_init (lock)

read_trylock(lock)
write_trylock(lock)

read_lock(lock)
write_lock(lock)

read_lock_irq(lock)
read_lock_irgsave(lock, flags)
read_lock_bh(lock)

write_lock_irq(lock)
write_lock_irqsave(lock, flags)
write_lock_bh(lock)

read_unlock(lock)
write_unlock(lock)

read_unlock_irq(lock)
read_unlock_irqrestore(lock, flags)
read_unlock_bh(lock)

write_unlock_irg(lock)
write_unlock_irqrestore(lock, flags)
write_unlock_bh(lock)



有了前面对自旋锁的了解,相信大家都能知道这些宏的含义,下面是一个应用例子
 

int i = 5;
unsigned long flags;
rwlock_t lock;

/*使用之前先初始化读写锁*/
rwlock_init(&lock);

/*要改变变量的值之前获取写锁*/
write_lock_irqsave(&lock, flags);
i++;
write_unlock_irqrestore(&lock, flags);

int V;
/*只是获取变量的值先获得读锁 */
read_lock_irqsave(&lock, flags);
v = i;
read_unlock_irqrestore(&lock, flags);


        读写锁的使用也需经历定义、初始化、加锁和解锁的过程,只是要改变变量的值需先获取写锁,值改变完成后再解除写锁,读操作则用读锁。这样,当一个内核执行路径在获取变量的值时,如果有另一条执行路径也要来获取变量的值,则读锁可以正常获得,从而另一条路径也能获取变量的值。但如果有一个写在进行,那不管是写锁还是读锁都不能获得,只有当写锁释放了之后才可以。很明显,使用读写锁降低了锁的粒度,即对锁的控制更加精细了,从而获得了更高的并发性,带来了更高的工作效率。


顺序锁


        自旋锁不允许读和读之间的并发,读写锁则更进了一步,允许读和读之间的并发,顺序锁又更进了一步,允许读和写之间的并发。为了实现这一需求,顺序锁在读时不上锁,也就意味着在读的期间允许写,但是在读之前需要先读取一个顺序值,读操作完成后,再次读取顺序值,如果两者相等,说明在读的过程中没有发生过写操作,否则要重新读取。显然,写操作要上锁,并且要更新顺序值。顺序锁特别适合读很多而写比较少的场合,否则由于反复的读操作,也不一定能够获取较高的效率。顺序锁的数据类型是seqlock_t,其类型定义如下。
 

typedef struct (
    struct seqcount seqcount;
    spinlock_t lock;
} seglock_t;


        很显然,顺序锁使用了自旋锁的机制,并且有一个顺序值 seqcount。顺序锁的主要API如下。


 

seqlock_init(x)
unsigned read_seqbegin(const seqlock_t *sl);
unsigned read_seqretry(const seqlock_t *sl, unsigned start);
void write_seqlock(seqlock_t *sl);
void write_sequnlock(seqlock_t *s1);
void write_seqlock_bh(seqlock_t *sl);
void write_sequnlock_bh(seqlock_t *s1);
void write_seqlock_irq(seqlock_t *sl);
void write_sequnlock_irq(seqlock_t *sl);
write_seqlock_irqsave(lock,flags)
void write_sequnlock_irqrestore(seqlock_t *sl, unsigned long flags);


        seqlock_init:初始化顺序锁。
        read_seqbegin:读之前获取顺序值,函数返回顺序值。
        read_seqretry:读之后验证顺序值是否发生了变化,返回1表示需要重读,返回0麦示读成功。
        write_seqlock:写之前加锁,其他的变体请参照自旋锁。
        write_sequnlock:写之后解锁,其他的变体请参照自旋锁。
        在i++的例子中,我们可以使用下面的代码来使顺序锁对i的操作进行互斥.
 

int i=5;
unsigned long flags;

/*定义顺序锁*/
seqlock_t lock;
/*使用之前必须初始化顺序锁 */
seqlock_init(&lock);

int v;
unsigned start;
do {
    /* 读之前要先获取顺序值 */
    start = read_seqbegin(&lock);
    v = i;
    /* 读完之后检查顺序值是否发生了变化,如果是,则要重读 */
} while (read_seqretry(&lock,start));

/* 写之前获取顺序锁 */
write_seqlock_irqsave(&lock,flags);
i++;
/*写完后释放顺序锁*/
write_sequnlock_irqrestore(&lock,flags);

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux——互斥与同步 的相关文章

  • SONAR - 使用 Cobertura 测量代码覆盖率

    我正在使用声纳来测量代码质量 我不知道的一件事是使用 Cobertura 测量代码覆盖率的步骤 我按照以下步骤操作http cobertura sourceforge net anttaskreference html http cober
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • 如何模拟ARM处理器运行环境并加载Linux内核模块?

    我尝试加载我的vmlinux into gdb并使用 ARM 内核模拟器 但我不明白为什么我会得到Undefined target command sim 这是外壳输出 arm eabi gdb vmlinux GNU gdb GDB 7
  • 在 Linux 中禁用历史记录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 要在 Linux 环境中禁用历史记录 我执行了以下命令 export HISTFILESIZE 0 export HISTSIZE 0 u
  • Godaddy 托管上的 CakePHP 控制台

    我一直在努力让我的 CakePHP 网站在 Godaddy 网格托管 帐户上运行 我的蛋糕应用程序设置是从帐户的子目录托管的 并且可以通过子域访问 我必须调整我的 htaccess 文件才能使其正常工作 现在我需要让 CakePHP 控制台
  • 何时使用 pthread 条件变量?

    线程问题 看来 只有在其他线程调用 pthread cond notify 之前调用 pthread cond wait 时 条件变量才起作用 如果在等待之前发生通知 那么等待将被卡住 我的问题是 什么时候应该使用条件变量 调度程序可以抢占
  • 使用 find - 删除除任何一个之外的所有文件/目录(在 Linux 中)

    如果我们想删除我们使用的所有文件和目录 rm rf 但是 如果我希望一次性删除除一个特定文件之外的所有文件和目录怎么办 有什么命令可以做到这一点吗 rm rf 可以轻松地一次性删除 甚至可以删除我最喜欢的文件 目录 提前致谢 find ht
  • Bash 解析和 shell 扩展

    我对 bash 解析输入和执行扩展的方式感到困惑 对于输入来说 hello world 作为 bash 中的参数传递给显示其输入内容的脚本 我不太确定 Bash 如何解析它 Example var hello world displaywh
  • Linux 中的无缓冲 I/O

    我正在写入大量的数据 这些数据数周内都不会再次读取 由于我的程序运行 机器上的可用内存量 显示为 空闲 或 顶部 很快下降 我的内存量应用程序使用量不会增加 其他进程使用的内存量也不会增加 这让我相信内存正在被文件系统缓存消耗 因为我不打算
  • 跟踪 Linux 程序中活跃使用的内存

    我想跟踪各种程序在特定状态下接触了多少内存 例如 假设我有一个图形程序 最小化时 它可能会使用更少的内存 因为它不会重新绘制窗口 这需要读取图像和字体并执行大量库函数 这些对象仍然可以在内存中访问 但实际上并没有被使用 类似的工具top它们
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • Linux TUN/TAP:无法从 TAP 设备读回数据

    问题是关于如何正确配置想要使用 Tun Tap 模块的 Linux 主机 My Goal 利用现有的路由软件 以下为APP1和APP2 但拦截并修改其发送和接收的所有消息 由Mediator完成 我的场景 Ubuntu 10 04 Mach
  • 如何在 shell 脚本中并行运行多个实例以提高时间效率[重复]

    这个问题在这里已经有答案了 我正在使用 shell 脚本 它读取 16000 行的输入文件 运行该脚本需要8个多小时 我需要减少它 所以我将其划分为 8 个实例并读取数据 其中我使用 for 循环迭代 8 个文件 并在其中使用 while
  • 为什么内核需要虚拟寻址?

    在Linux中 每个进程都有其虚拟地址空间 例如 32位系统为4GB 其中3GB为进程保留 1GB为内核保留 这种虚拟寻址机制有助于隔离每个进程的地址空间 对于流程来说这是可以理解的 因为有很多流程 但既然我们只有 1 个内核 那么为什么我
  • Linux中的CONFIG_OF是什么?

    我看到它在很多地方被广泛使用 但不明白在什么场景下我需要使用它 What is 配置 OF OF 的全名是什么 打开固件 这是很久以前发明的 当时苹果公司正在生产基于 PowerPC CPU 的笔记本电脑 而 Sun Microsystem
  • 从 Python 调用 PARI/GP

    我想打电话PARI GP http pari math u bordeaux fr dochtml gpman html仅从Python计算函数nextprime n 对于不同的n是我定义的 不幸的是我无法得到帕里蟒蛇 http code
  • Linux中的定时器类

    我需要一个计时器来以相对较低的分辨率执行回调 在 Linux 中实现此类 C 计时器类的最佳方法是什么 有我可以使用的库吗 如果您在框架 Glib Qt Wx 内编写 那么您已经拥有一个具有定时回调功能的事件循环 我认为情况并非如此 如果您
  • chown:不允许操作

    我有问题 我需要通过 php 脚本为系统中的不同用户设置文件所有者权限 所以我通过以下命令执行此操作 其中 1002 是系统的用户 ID file put contents filename content system chown 100
  • 加载数据infile,Windows和Linux的区别

    我有一个需要导入到 MySQL 表的文件 这是我的命令 LOAD DATA LOCAL INFILE C test csv INTO TABLE logs fields terminated by LINES terminated BY n
  • 无法使用 wget 在 CentOS 机器上安装 oracle jdk

    我想在CentOS上安装oracle java jdk 8 我无法安装 java jdk 因为当我尝试使用命令安装 java jdk 时 root ADARSH PROD1 wget no cookies no check certific

随机推荐

  • 串口DMA发送接收

    目录 一 DMA的基本介绍 1 DMA定义 2 原理 1 请求 2 响应 3 传输 4 结束 3 传送方式 1 停止CPU访问内存 2 周期挪用 3 DMA与CPU交替访问内存 4 DMA中断 二 新建cubemx项目 1 选择STM32F
  • Time Limit Exceeded的原因

    Time Limit Exceeded的原因及避免方法 荷叶田田 CSDN博客
  • GStreamer学习三(延迟)

    1 延迟 延迟 xff08 即latency xff09 是在时间戳0处捕获的样本到达接收器所花费的时间 此时间是根据流水线的时钟测量的 对于只有包含与时钟同步的 接收器 元素的流水线 xff0c latency 始终为0 xff0c 因为
  • 第一届ACC全国高校联赛

    y 竞赛 AcWing 面向全国高校同学的高校联赛 https www acwing com activity content 1173 一 1 暴力 include lt bits stdc 43 43 h gt using namesp
  • JDBC连接数据库

    个人简介 x1f496 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki x1f4c2 喜欢 xff1a x1f308 点赞 x1f308 收藏 xff01 更新Java x1f308 python
  • idea 文件夹右键新建没有Class

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 一 问题发现 xff1a 没法创建ja
  • 《关于我找了好久的bug,却没找出来的,又不小心解决了的事》

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 问题 xff1a 这是一个Spring
  • 某某科技实习日志

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 时间 2023年 4月 11日 今日任
  • XXXX实习日志

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 时间 2023年 4月 12日 今日任
  • STM32——中断优先级分组

    一 SCB AIRCR寄存器 首先 xff0c 对STM32中断进行分组 xff0c 0 4 同时 xff0c 每个中断设置一个抢占优先级和一个响应优先级 1 高抢占可以打断正在执行的低抢占 2 抢占相等 xff0c 高响应不能打断低响应
  • Keil报错总结(1)

    一 newline expected extra characters found c323 头文件定义有问题 ifndef define endif 他们后面的文件名与文件名不一致 xff0c 或者大小写不一致 xff0c 文件名尽量避免
  • GPIO实验

    一 GPIO简介 GPIO xff08 General purpose input output xff09 即通用型输入输出 xff0c GPIO可以控制连接在其之上的引脚实现信号的输入和输出 芯片的引脚与外部设备相连 xff0c 从而实
  • Exynos_4412——中断处理(中断学习结尾篇)

    目录 一 ARM的异常处理机制 1 1异常概念 1 2异常处理机制 1 3ARM异常源 1 4异常模式 1 5ARM异常响应 1 6异常向量表 1 7异常返回 1 8IRQ异常举例 二 工程模板代码结构 三 中断处理框架搭建 四 中断处理程
  • ROS中控制小乌龟移动(2种方法)

    操作系统 xff1a Ubuntu16 04 ROS版本 xff1a Kinetic 目录 方法一 xff1a 用键盘控制小乌龟移动1 启动ROS Master2 打开小乌龟3 键盘控制小乌龟 方法二 xff1a 通过命令发布话题控制小乌龟
  • QT/C++——对话框

    一 标准对话框 include 34 widget h 34 include lt QVBoxLayout gt include lt QHBoxLayout gt Widget Widget QWidget parent QWidget
  • QT/C++——主窗口和事件处理

    一 主窗口 上面就是一个主窗口 xff0c 主窗口中的每一个都是Action 这次新建工程要选择mainwindow ifndef MAINWINDOW H define MAINWINDOW H include lt QMainWindo
  • QT/C++——网络编程

    目录 一 基础知识复习 二 UDP 客户端 xff1a 服务器 xff1a 三 TCP 服务器 xff1a 客户端 xff1a 四 小项目 客户端 xff1a 服务器 xff1a 一 基础知识复习 这部分内容前面讲的比较详细 xff0c 现
  • Linux驱动开发——高级I/O操作(一)

    一个设备除了能通过读写操作来收发数据或返回 保存数据 xff0c 还应该有很多其他的操作 比如一个串口设备还应该具备波特率获取和设置 帧格式获取和设置的操作 一个LED设备甚至不应该有读写操作 xff0c 而应该具备点灯和灭灯的操作 硬件设
  • ubuntu22.04安装与配置

    目录 一 环境及下载 iso下载 VM配置 二 虚拟机与环境配置 虚拟机开始后的配置 一些工具配置 参考 xff1a VMware Workstation Pro 文档 一 环境及下载 iso下载 Download Ubuntu Deskt
  • Linux——互斥与同步

    目录 一种典型的竞态 内核中的并发 中断屏蔽 原子变量 自旋锁 读写锁 顺序锁 一种典型的竞态 假设整型变量i是驱动代码中的一个个全局变量 xff0c 在驱动的某个例程中执行了i 43 43 操作 xff0c 而在中断服务程序中也执行了i