Linux驱动学习day13(同步与互斥)
内联汇编
需要使用特殊指令(ldrex/strex实现互斥访问)的时候需要使用内联汇编。下图是内联汇编的语法。
__asm__ 冒号之前的使用" "括起来的是汇编代码,下面这个例子只有一条汇编指令。下面这段代码的意思是把第1个操作数和第2个操作数累加起来放到第0个操作数。最后一行代表除了上面那些寄存器之外还影响着什么寄存器。cc代表flags register。“r”(a)表示将a的值放入r这个寄存器中。“=r”(sum)表示这个寄存器的值会被放在sum这个变量中。
还有一种写法:“=&r”(a) earlyclobber:表示这个输出寄存器 在所有输入操作数还没加载完成之前就会被修改,在分配寄存器时,a用的寄存器从一开始就不能与任何输入寄存器重复。防止GCC 把这个输出寄存器和某个输入变量放在同一个寄存器中。这是在涉及读-改-写(如 ldrex/strex)时非常关键的一个约束。
#include <stdio.h>
#include <stdlib.h>int add(int a, int b)
{int sum;__asm__ volatile ("add %0, %1, %2\n""add %1, %1, #1\n""add %2, %2, #1\n":"=&r"(sum):"r"(a), "r"(b):"cc");return sum;
}int main(int argc, char **argv)
{int a;int b;if (argc != 3){printf("Usage: %s <val1> <val2>\n", argv[0]);return -1;}a = (int)strtol(argv[1], NULL, 0); b = (int)strtol(argv[2], NULL, 0); printf("%d + %d = %d\n", a, b, add(a, b));return 0;
}
当上述代码:"=&r"(sum) 写为:"=r"(sum)的时候的结果1+2 = 4 , 这是因为由于 sum 和 a 用了同一个寄存器(x1),第二句实际是对 sum 又加了一次,所以 1 + 2 → 3 → +1 = 4
。
从汇编语言也可以看出x0 = x0 + x1 第二步 x0 = x0 + 1 第三步x1 = x1 + 1 。这就说明了为什么实现的结果是1+2 = 4.(这种情况就是操作数0和1在同一个寄存器)
将 代码写为"=&r"(sum)再次运行
同步互斥失败案例
第一种情况比较简单,用一个全局变量来控制访问,这种情况在修改全局变量前中断来到,另一个进程再调用驱动就会导致失败。
第二种情况如下图所示,用一个变量直接控制是否访问,但是--valid这个可以拆分成先读出数据,再修改,最后写入数据,如果刚读出还没有修改的时候被抢占,变量还未修改,另外一个线程进程也能访问。
第三种情况如下图所示代码,在单核CPU可以实现锁,但是在多核CPU无法实现,一个锁刚关住一个CPU的中断,还未改变值,另外一个CPU也可以使用该驱动。
使用原子变量实现互斥
在ARMv6以下的版本,这些原子操作的函数都会进行关中断和恢复中断,确保没有别的程序能够访问,但是在ARMv6及以上版本,使用的是如下方法:
当asm_op,op为add的时候分析下面代码:3568内核中的文件和6ull板子上的不同。
#define ATOMIC_OP(op, asm_op) \
static inline void atomic_##op(int i, atomic_t *v) \
{ \register int w0 asm ("w0") = i; \register atomic_t *x1 asm ("x1") = v; \\asm volatile( \__LSE_PREAMBLE \ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(op), \
" " #asm_op " %w[i], %[v]\n") \: [i] "+r" (w0), [v] "+Q" (v->counter) \: "r" (x1) \: __LL_SC_CLOBBERS); \
}
借助GPT展开上面那段代码:
static inline void atomic_add(int i, atomic_t *v)
{register int w0 asm ("w0") = i;register atomic_t *x1 asm ("x1") = v;asm volatile (__LSE_PREAMBLE
#ifdef USE_LSE"ldadd %w[i], %w[i], [%[v]]\n"
#else"1: ldxr %w[i], [%[v]]\n"" add %w[i], %w[i], %w0\n"" stxr w3, %w[i], [%[v]]\n"" cbnz w3, 1b\n"
#endif: [i] "+r"(w0), [v] "+Q"(v->counter): "r"(x1): "memory", "cc");
}
代码解释如下:
这样来看,在ARMv6及以上版本(多CPU),函数其实是有可能会被打断的,但是其实现了原子操作的效果,(但是打断了没有关系,重新执行上述代码,如下图,重新执行之后发现valid = 0 , 走if之外的分支)。
常用函数: atomic_inc_and_test():先加1,再判断新值是否等于0,等于0的话返回1;
atomic_del_and_test():先减1,再判断新值是否等于0,等于0的话返回1。