zoukankan      html  css  js  c++  java
  • DPDK CAS(compare and set)操作

    前言

    rte_ring是一个无锁队列,无锁队列的出队入队操作是rte_ring实现的关键。因此,本文主要讲解dpdk是怎样使用无锁机制实现rte_ring的多生产者入队操作。
    rte_atomic32_cmpset()称为CAS(compare and set)操作,是无锁队列实现的关键


    函数原型

    static inline int
    rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
    {
    uint8_t res;
    	asm volatile(
    	"lock;"
    	"cmpxchgl %[src], %[dst];"
    	"sete %[res];"
    	:[res] "=a"(res),	/*output*/
    	[dst] "=m"(*dst)
    	:[src] "r"(src),	/*input*/
    	"a" (exp),
    	"m" (*dst)
    	:"memory");		/* no-clobber list */
    return res;
    }      //函数的作用是:rte_atomic32_cmpset内部比较dst和exp,如果dst和exp相等,那么把src赋值给dst
    

    便于理解,参数可以重命名一下
    static inline int rte_atomic32_cmpset(volatile uint32_t *current, uint32_t old, uint32_t next);


    内联汇编的基本格式为:

    asm [ volatile ] (    
            assembler template  
            [ : output operands ]                /* optional */  
            [ : input operands  ]                /* optional */  
            [ : list of clobbered registers ]    /* optional */  
            );  
    

    asm: asm为gcc关键字,表示接下来要嵌入汇编代码。为避免keyword asm与程序中其它部分产生命名冲突,gcc还支持__asm__关键字,与asm的作用等价。


    volatile: 为可选关键字,表示不需要gcc对下面的汇编代码做任何优化。同样出于避免命名冲突的原因,__volatile__也是gcc支持的与volatile等效的关键字。


    assembler template:这部分即我们要嵌入的汇编命令,由于我们是在C语言中内联汇编代码,故需用双引号""将命令括起来,以便gcc以字符串形式将这些命令传给汇编器AS。例如可以写成这样:"movl %eax, %ebx"。有时候,汇编命令可能有多个,则通常分多行写,每行的命令都用双引号括起来,命令后紧跟" "之类的分隔符(当然,也可以只用1对双引号将多行命令括起来,从语法来说,两种写法均有效,我们可自行决定用哪种格式来写)。


    output operands: 该字段为可选项,用以指明输出操作数,典型的格式为:

       : "=a" (out_var) 
    

    其中,"=a"指定output operand的应遵守的约束(constraint),out_var为存放指令结果的变量,通常是个C语言变量。


    input operands: 该字段为可选项,用以指明输入操作数,其典型格式为:

         : "constraints" (in_var)
    

    其中,constraints可以是gcc支持的各种约束方式,in_var通常为C语言提供的输入变量。


    list of clobbered registers : 该字段为可选项,用于列出指令中涉及到的且没出现在output operands字段及input operands字段的那些寄存器。若寄存器被列入clobber-list,则等于是告诉gcc,这些寄存器可能会被内联汇编命令改写。因此,执行内联汇编的过程中,这些寄存器就不会被gcc分配给其它进程或命令使用。


    我们再来看rte_atomic32_cmpset函数就会清晰很多了
    "lock;"
    > 枷锁,避免多个线程同时执行下面的汇编指令。
    "cmpxchgl %[src], %[dst];"

    Cmpxchgl指令有三个操作数,dst、src和eax寄存器,执行结果会影响dst和ZF标志位。

    指令将dst和eax里的值比较,如果相同,将src里的值赋给dst,同时ZF置位。

    Eax寄存器的在c语言掉用汇编的时候,函数入参exp赋给寄存器eax。

    那么cmpxchgl实际执行的就是,比较exp(也就是eax)与 dst的值,如果相等,那么将src的值赋值给dst

    "sete %[res];"

    sete这个命令,是set+equeal,就是上面Cmpxchgl比较结果如果相等,则set ZF,并把值赋给res

    入参属性:

    1、[src] "r" (src),

    Src是个寄存器变量(普通register,也就是eax,ebx,ecx,edx,esi,edi中的一个)

    2、"a" (exp),

    exp是个寄存器变量,它的值在c语言调用汇编时候,“a”表示的是(eax寄存器),通过这个标识,将exp放在eax寄存器里。

    如果eax已经被使用,eax的原来的值会先push到堆栈里,执行完汇编后,再push到eax里。

    3、"m" (*dst)

    入参dst是个memory变量

    出参属性

    1、[res] "=a" (res),

    汇编函数返回的值

    [dst] "=m" (*dst)

    dst是个内存变量,这个变量被volatile修饰,是从内存读取的,不是寄存器或者cache缓存的。不想借助于任何寄存器。

    常用的寄存器约束的缩写:

    • r:I/O,表示使用一个通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中选取一个GCC认为是合适的;
    • q:I/O,表示使用一个通用寄存器,与r的意义相同;
    • g:I/O,表示使用寄存器或内存地址;
    • m:I/O,表示使用内存地址;
    • a:I/O,表示使用%eax/%ax/%al;
    • b:I/O,表示使用%ebx/%bx/%bl;
    • c:I/O,表示使用%ecx/%cx/%cl;
    • d:I/O,表示使用%edx/%dx/%dl;
    • D:I/O,表示使用%edi/%di;
    • S:I/O,表示使用%esi/%si;
    • f:I/O,表示使用浮点寄存器;
    • t:I/O,表示使用第一个浮点寄存器;
    • u:I/O,表示使用第二个浮点寄存器;
    • A:I/O,表示把%eax与%edx组合成一个64位的整数值;
    • o:I/O,表示使用一个内存位置的偏移量;
    • V:I/O,表示仅仅使用一个直接内存位置;
    • i:I/O,表示使用一个整数类型的立即数;
    • n:I/O,表示使用一个带有已知整数值的立即数;
    • F:I/O,表示使用一个浮点类型的立即数;

    测试rte_atomic32_cmpset 函数

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <pthread.h>
     
    static inline int
    rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
    {
    uint8_t res;
    	asm volatile(
    	"lock;"
    	"cmpxchgl %[src], %[dst];"
    	"sete %[res];"
    	:[res] "=a"(res),	/*output*/
    	[dst] "=m"(*dst)
    	:[src] "r"(src),	/*input*/
    	"a" (exp),
    	"m" (*dst)
    	:"memory");		/* no-clobber list */
    return res;
    }
     
    volatile uint32_t sum = 0;
     
    void *func()
    {
    	uint32_t last_sum = 0;
    	for(int i = 0; i < 100000; i++)
    	{
    		while(!rte_atomic32_cmpset(&sum, last_sum, last_sum + 1))
    		{
    			last_sum = sum;
    		}
    	}
    }
     
    int main()
    {
    	pthread_t pid1, pid2;
     
    	pthread_create(&pid1, NULL, func, NULL);
    	pthread_create(&pid2, NULL, func, NULL);
    	
    	pthread_join(pid1, NULL);
    	pthread_join(pid2, NULL);
    	
    	printf("sum = %d
    ", sum);
    	return 0;
    }
    

    编译时加上 -lpthread

  • 相关阅读:
    Linux下安装Blender
    自我复制的3D打印机
    ODOO v10.0 自动生成财务凭证的科目设置
    初识Odoo的辅助核算
    Odoo9以后的社区版本和企业版功能上的区别
    06: linux中find查找命令总结
    02: shell中的if、case、for等语句
    01: shell基本使用
    05: 配置yum源
    04: linux基础总结-centos6.5
  • 原文地址:https://www.cnblogs.com/gaoshaonian/p/14293101.html
Copyright © 2011-2022 走看看