精华内容
下载资源
问答
  • lammps模拟中,有时候也会同时使用以上两种方法:使用read_data读入模型文件后,再用lammps自带命令添加其它类型的原子。 如本例中,使用Atomsk建立了一个Cu基底模型,保存为Cu.lmp文件。 in文件中read_data...

    大家好,我是小马老师。
    在lammps模拟中,常用的建模方法有两种,一种是完全使用lammps自带命令建模,另一种是使用read_data命令读入其它软件模型。
    在lammps模拟中,有时候也会同时使用以上两种方法:使用read_data读入模型文件后,再用lammps自带命令添加其它类型的原子。
    如本例中,使用Atomsk建立了一个Cu基底模型,保存为Cu.lmp文件。
    在这里插入图片描述

    在in文件中read_data读入Cu.lmp文件后,增加一个金刚石刀具。
    在这里插入图片描述

    如果使用下面的建模代码:

    read_data   Cu.lmp
    change_box all z delta 0 50 units box
    region     tool sphere 17 17 50 12 units box
    lattice     diamond 3.52
    create_atoms 2 region tool
    mass 1 64
    mass 2 12 
    write_data all.data
    

    则会提示以下错误:原子2为非法原子类型
    在这里插入图片描述

    主要原因是lammps模拟过程是在一个box内进行,read_data命令会根据data文件中的原子总数创建一个box,与下面的命令类似:

    create_box 1 box
    

    box内原子的总数目已经确定为1,再添加一种新的原子2就会提示错误。
    解决方法也比较简单,只要想办法把box内原子总数设置为2,并且设定两种原子的摩尔质量即可。
    下面给出2种解决方案:
    1. 设置read_data命令,额外增加一种原子类型
    read_data命令允许在读入data文件时设定是否还需要增加新的原子,如本例需要新增一种原子类型,命令可写为:

    read_data   Cu.lmp extra/atom/types 1
    

    这样就可以正常运行了。
    对应的in文件为:

    read_data   Cu.lmp extra/atom/types 1
    change_box all z delta 0 50 units box
    region     tool sphere 17 17 50 12 units box
    lattice     diamond 3.52
    create_atoms 2 region tool
    mass 1 64
    mass 2 12 
    write_data all.data
    

    2.修改Cu.lmp文件,把原子类型总数改为2
    原始Cu.lmp文件为:
    在这里插入图片描述

    修改后Cu.lmp为:
    在这里插入图片描述

    对应的in文件为:

    read_data   Cu.lmp
    change_box all z delta 0 50 units box
    region     tool sphere 17 17 50 12 units box
    lattice     diamond 3.52
    create_atoms 2 region tool
    write_data all.data
    

    深入了解lammps模拟原理,在出现问题时才能游刃有余,公众号定期推出lammps模拟小技巧,敬请关注!

    如果在使用lammps过程中遇到什么问题,可到公众号或者QQ群留言。
    公众号案例代码以及学习交流,请加QQ群:754749935
    在这里插入图片描述

    扫描关注微信公众号:lammps加油站。
    在这里插入图片描述

    展开全文
  • 多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到...

    深入分析Volatile的实现原理


    引言

    在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

    它在某些情况下比synchronized的开销更小,本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的,通过深入分析能帮助我们正确的使用Volatile变量。

    术语定义

    术语

    英文单词

    描述

    共享变量

     

    在多个线程之间能够被共享的变量被称为共享变量。共享变量包括所有的实例变量,静态变量和数组元素。他们都被存放在堆内存中,Volatile只作用于共享变量。

    内存屏障

    Memory Barriers

    是一组处理器指令,用于实现对内存操作的顺序限制。

    缓冲行

    Cache line

    缓存中可以分配的最小存储单位。处理器填写缓存线时会加载整个缓存线,需要使用多个主内存读周期。

    原子操作

    Atomic operations

    不可中断的一个或一系列操作。

    缓存行填充

    cache line fill

    当处理器识别到从内存中读取操作数是可缓存的,处理器读取整个缓存行到适当的缓存(L1,L2,L3的或所有)

    缓存命中

    cache hit

    如果进行高速缓存行填充操作的内存位置仍然是下次处理器访问的地址时,处理器从缓存中读取操作数,而不是从内存。

    写命中

    write hit

    当处理器将操作数写回到一个内存缓存的区域时,它首先会检查这个缓存的内存地址是否在缓存行中,如果存在一个有效的缓存行,则处理器将这个操作数写回到缓存,而不是写回到内存,这个操作被称为写命中。

    写缺失

    write misses the cache

    一个有效的缓存行被写入到不存在的内存区域。

    Volatile的官方定义

    Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

    为什么要使用Volatile

    Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。

    Volatile的实现原理

    那么Volatile是如何来保证可见性的呢?在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。

    Java代码:

    instance = new Singleton();//instance是volatile变量

    汇编代码:

    0x01a3de1d: movb $0x0,0x1104800(%esi);

    0x01a3de24: lock addl $0x0,(%esp);

    有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情。

    • 将当前处理器缓存行的数据会写回到系统内存。
    • 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。

    处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

    这两件事情在IA-32软件开发者架构手册的第三册的多处理器管理章节(第八章)中有详细阐述。

    Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大。在8.1.4章节有详细说明锁定操作对处理器缓存的影响,对于Intel486和Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。但在P6和最近的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据

    一个处理器的缓存回写到内存会导致其他处理器的缓存无效。IA-32处理器和Intel 64处理器使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32 和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。它们使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。例如在Pentium和P6 family处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处理共享状态,那么正在嗅探的处理器将无效它的缓存行,在下次访问相同内存地址时,强制执行缓存行填充。

    Volatile的使用优化

    著名的Java并发编程大师Doug lea在JDK7的并发包里新增一个队列集合类LinkedTransferQueue,他在使用Volatile变量时,用一种追加字节的方式来优化队列出队和入队的性能。

    追加字节能优化性能?这种方式看起来很神奇,但如果深入理解处理器架构就能理解其中的奥秘。让我们先来看看LinkedTransferQueue这个类,它使用一个内部类类型来定义队列的头队列(Head)和尾节点(tail),而这个内部类PaddedAtomicReference相对于父类AtomicReference只做了一件事情,就将共享变量追加到64字节。我们可以来计算下,一个对象的引用占4个字节,它追加了15个变量共占60个字节,再加上父类的Value变量,一共64个字节。

    /** head of the queue */
    private transient final PaddedAtomicReference < QNode > head;
    
    /** tail of the queue */
    
    private transient final PaddedAtomicReference < QNode > tail;
    
    
    static final class PaddedAtomicReference < T > extends AtomicReference < T > {
    
        // enough padding for 64bytes with 4byte refs 
        Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
    
        PaddedAtomicReference(T r) {
    
            super(r);
    
        }
    
    }
    
    public class AtomicReference < V > implements java.io.Serializable {
    
        private volatile V value;
    
        //省略其他代码 }
    

    为什么追加64字节能够提高并发编程的效率呢? 因为对于英特尔酷睿i7,酷睿, Atom和NetBurst, Core Solo和Pentium M处理器的L1,L2或L3缓存的高速缓存行是64个字节宽,不支持部分填充缓存行,这意味着如果队列的头节点和尾节点都不足64字节的话,处理器会将它们都读到同一个高速缓存行中,在多处理器下每个处理器都会缓存同样的头尾节点,当一个处理器试图修改头接点时会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点,而队列的入队和出队操作是需要不停修改头接点和尾节点,所以在多处理器的情况下将会严重影响到队列的入队和出队效率。Doug lea使用追加到64字节的方式来填满高速缓冲区的缓存行,避免头接点和尾节点加载到同一个缓存行,使得头尾节点在修改时不会互相锁定。

    那么是不是在使用Volatile变量时都应该追加到64字节呢?不是的。在两种场景下不应该使用这种方式。第一:缓存行非64字节宽的处理器,如P6系列和奔腾处理器,它们的L1和L2高速缓存行是32个字节宽。第二:共享变量不会被频繁的写。因为使用追加字节的方式需要处理器读取更多的字节到高速缓冲区,这本身就会带来一定的性能消耗,共享变量如果不被频繁写的话,锁的几率也非常小,就没必要通过追加字节的方式来避免相互锁定。



    原子操作的实现原理



    1. 引言

    原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。在多处理器上实现原子操作就变得有点复杂。本文让我们一起来聊一聊在Intel处理器和Java里是如何实现原子操作的。


    2. 术语定义


    术语英文解释
    缓存行Cache line缓存的最小操作单位
    比较并交换Compare and SwapCAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
    CPU流水线CPU pipelineCPU流水线的工作方式就象工业生产上的装配流水线,在CPU中由5~6个不同功能的电路单元组成一条指令处理流水线,然后将一条X86指令分成5~6步后再由这些电路单元分别执行,这样就能实现在一个CPU时钟周期完成一条指令,因此提高CPU的运算速度。
    内存顺序冲突Memory order violation内存顺序冲突一般是由假共享引起,假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线。

    3. 处理器如何实现原子操作

    32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。

    3.1 处理器自动保证基本内存操作的原子性

    首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。

    3.2 使用总线锁保证原子性

    第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,举个例子:如果i=1,我们进行两次i++操作,我们期望的结果是3,但是有可能结果是2。如下图

    (例1)

    原因是有可能多个处理器同时从各自的缓存中读取变量i,分别进行加一操作,然后分别写入系统内存当中。那么想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。

    处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。

    3.3 使用缓存锁保证原子性

    第二个机制是通过缓存锁定保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。

    频繁使用的内存会缓存在处理器的L1,L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,在奔腾6和最近的处理器中可以使用“缓存锁定”的方式来实现复杂的原子性。所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效,在例1中,当CPU1修改缓存行中的i时使用缓存锁定,那么CPU2就不能同时缓存了i的缓存行。

    但是有两种情况下处理器不会使用缓存锁定。第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line),则处理器会调用总线锁定。第二种情况是:有些处理器不支持缓存锁定。对于Inter486和奔腾处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。

    以上两个机制我们可以通过Inter处理器提供了很多LOCK前缀的指令来实现。比如位测试和修改指令BTS,BTR,BTC,交换指令XADD,CMPXCHG和其他一些操作数和逻辑指令,比如ADD(加),OR(或)等,被这些指令操作的内存区域就会加锁,导致其他处理器不能同时访问它。

    4. JAVA如何实现原子操作

    在java中可以通过锁和循环CAS的方式来实现原子操作。

    4.1 使用循环CAS实现原子操作

    JVM中的CAS操作正是利用了上一节中提到的处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止,以下代码实现了一个基于CAS线程安全的计数器方法safeCount和一个非线程安全的计数器count。

       
    public class Counter {
        private AtomicInteger atomicI = new AtomicInteger(0);
        private int i = 0;
        public static void main(String[] args) {
            final Counter cas = new Counter();
            List<Thread> ts = new ArrayList<Thread>(600);
            long start = System.currentTimeMillis();
            for (int j = 0; j < 100; j++) {
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10000; i++) {
                            cas.count();
                            cas.safeCount();
                        }
                    }
                });
                ts.add(t);
            }
    
            for (Thread t : ts) {
                t.start();
            }
           // 等待所有线程执行完成
            for (Thread t : ts) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            System.out.println(cas.i);
            System.out.println(cas.atomicI.get());
            System.out.println(System.currentTimeMillis() - start);
    
        }
    
        /**
         * 使用CAS实现线程安全计数器
         */
        private void safeCount() {
            for (;;) {
                int i = atomicI.get();
                boolean suc = atomicI.compareAndSet(i, ++i);
                if (suc) {
                    break;
                }
            }
        }
        /**
         * 非线程安全计数器
         */
        private void count() {
            i++;
        }
    }
    

    在java并发包中有一些并发框架也使用了自旋CAS的方式来实现原子操作,比如LinkedTransferQueue类的Xfer方法。CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作。

    1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
      从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
      public boolean compareAndSet
              (V      expectedReference,//预期引用
               V      newReference,//更新后的引用
              int    expectedStamp, //预期标志
              int    newStamp) //更新后的标志
      
    2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

    3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

    4.2 使用锁机制实现原子操作

    锁机制保证了只有获得锁的线程能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁,轻量级锁和互斥锁,有意思的是除了偏向锁,JVM实现锁的方式都用到的循环CAS,当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。详细说明可以参见文章Java SE1.6中的Synchronized。

    5. 参考资料

    1. Java SE1.6中的Synchronized
    2. Intel 64和IA-32架构软件开发人员手册
    3. 深入分析Volatile的实现原理

    作者介绍

    方腾飞,花名清英,淘宝资深开发工程师,关注并发编程,目前在广告技术部从事无线广告联盟的开发和设计工作。个人博客:http://ifeve.com 微博:http://weibo.com/kirals 欢迎通过我的微博进行技术交流。

    http://www.infoq.com/cn/articles/atomic-operation


    Linux 原子操作

    所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。

     

      原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。

      原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下:

     

    typedef struct  {  volatile int counter;  }  atomic_t;


      volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。

     

      原子操作API包括:

    atomic_read(atomic_t * v);


      该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。

     

     

    atomic_set(atomic_t * v, int i);


      该函数设置原子类型的变量v的值为i。

     

     

    void atomic_add(int i, atomic_t *v);


      该函数给原子类型的变量v增加值i。

     

     

    atomic_sub(int i, atomic_t *v);


      该函数从原子类型的变量v中减去i。

     

     

    int atomic_sub_and_test(int i, atomic_t *v);


      该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。

     

     

    void atomic_inc(atomic_t *v);


      该函数对原子类型变量v原子地增加1。

     

     

    void atomic_dec(atomic_t *v);


      该函数对原子类型的变量v原子地减1。

     

     

    int atomic_dec_and_test(atomic_t *v);


      该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。

     

     

    int atomic_inc_and_test(atomic_t *v);


      该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。

     

     

    int atomic_add_negative(int i, atomic_t *v);


      该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。

     

     

    int atomic_add_return(int i, atomic_t *v);


      该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。

     

     

    int atomic_sub_return(int i, atomic_t *v);


      该函数从原子类型的变量v中减去i,并且返回指向v的指针。

     

     

    int atomic_inc_return(atomic_t * v);


      该函数对原子类型的变量v原子地增加1并且返回指向v的指针。

     

     

    int atomic_dec_return(atomic_t * v);

      该函数对原子类型的变量v原子地减1并且返回指向v的指针。

      原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。

      当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。


    原子操作函数原型


     原子操作仅执行一次,在执行过程中不会中断也不会休眠;是最小的执行单元;鉴于原子操作这些特性,可以利用它来解决竞态问题。
     往后其他同步机制都是在原子操作的基础上进行扩展的。
     原子操作有整型原子操作、64位原子操作以及位原子操作。

    1 整型原子操作
    (Atomic Integer Operations)
     要使用原子操作,需要定义一个原子变量,然后使用内核提供的接口对其进行原子操作。
     整型原子变量结构如下

    1. #include <linux/type.h>
    2. typedef struct {
    3.     int counter;
    4. } atomic_t;
      可以看出整型原子变量实质上是一个32位整型变量。
     整型原子变量操作接口,其实现方式与具体的架构有关。
    1. #include <asm/atomic.h>
    2. ATOMIC_INIT(int i)                           // 定义原子变量时,将其值赋为i
    3. int atomic_read(atomic_t *v)                 // 读v的值
    4. void atomic_set(atomic_t *v, int i)          // 设置v的值为i
    5. void atomic_add(int i, atomic *v)            // v的值增加i
    6. void atomic_sub(int i, atomic *v)            // v的值减少i
    7. void atomic_inc(atomic *v)                   // v的值加1
    8. void atomic_dec(atomic *v)                   // v的值减1
    9. int atomic_sub_and_test(int i, atomic_t *v)  // v的值减少i,且结果为0时返回true
    10. int atomic_add_negative(int i, atomic_t *v)  // v的值增加i,且结果为负数时返回true
    11. int atomic_add_return(int i, atomic_t *v)    // v的值增加i,且返回结果
    12. int atomic_sub_return(int i, atomic_t *v)    // v的值减少i,且返回结果
    13. int atomic_inc_return(atomic_t *v)           // v的值加1,且返回结果
    14. int atomic_dec_return(atomic_t *v)           // v的值减1,且返回结果
    15. int atomic_dec_and_test(atomic_t *v)         // v的值减1,且结果为0时返回true
    16. int atomic_inc_and_test(atomic_t *v)         // v的值加1,且结果为0时返回true

    2 64位原子操作 (64-Bit Atomic Operations)
     64位原子变量结构
    1. typedef struct {
    2.     u64 __aligned(8) counter;
    3. } atomic64_t;
      64位原子变量操作接口与整型变量操作接口类似,只要将整型变量接口名称的"atomic"改成"atomic64"即可。

    3 位原子操作 (Atomic Bitwise Operations)
     位原子操作接口
    1. #include <asm/bitops.h>
    2. void set_bit(int nr, void *addr)           // 将addr第nr位置1
    3. void clear_bit(int nr, void *addr)         // 将addr第nr位置0
    4. void change_bit(int nr, void *addr)        // 将addr第nr位值取反
    5. int test_and_set_bit(int nr, void *addr)   // 将addr第nr位置1,并将该位之前值返回
    6. int test_and_clear_bit(int nr, void *addr) // 将addr第nr位置0,并将该位之前值返回
    7. int test_and_change_bit(int nr, void *addr)// 将addr第nr位取反,并将该位之前值返回
    8. int test_bit(int nr, void *addr)           // 将addr第nr位的值返回
    展开全文
  • Data文件书写步骤lammps中关键的一步是data文件的建立,这里总结3不同的方法来建立data文件。(个人推荐方法二)方法一:首先通过Ms建立模型,然后输出cif或者pdb格式文件(最好用pdb格式文件,因为pdb格式文件包含...

    Data文件书写步骤

    lammps中关键的一步是data文件的建立,这里总结3种不同的方法来建立data文件。(个人推荐方法二)

    方法一:

    首先通过Ms建立模型,然后输出cif或者pdb格式文件(最好用pdb格式文件,因为pdb格式文件包含电荷);其次,通过Atomsk进行格式转化,将cif格式转换成lammps识别的lmp格式文件;(注意:MS导出cif格式经过atomsk转换成.Lmp格式,data文件中的原子类型是atomic;MS导出pdb格式经过atomsk转换成.Lmp格式,data文件中的原子类型是charge;)具体步骤如下—以BaTiO3为例:

    (1)新建文件夹BaTiO3,鼠标右键选择Import.

    e4fe25d4c4b947b27de0574901dfda0c.gif

    e4fe25d4c4b947b27de0574901dfda0c.gife4fe25d4c4b947b27de0574901dfda0c.gif(2)Import之后弹出窗口,依次选择Structures ceramics BaTiO3

    e4fe25d4c4b947b27de0574901dfda0c.gif

    (3)建好模型后,我们进行结构优化,鼠标在黑色窗口右键选择DisplayStyle

    e4fe25d4c4b947b27de0574901dfda0c.gif

    (4)这里,在弹出的DisplayStyle窗口,我们选择Ballandstick,并且将Stickradius和Ballradius的值减小0.1

    e4fe25d4c4b947b27de0574901dfda0c.gif

    (5)优化完之后,设置晶包大小,如下图依次选择:

    e4fe25d4c4b947b27de0574901dfda0c.gif

    (6)点击Supercell后,弹出如下窗口

    e4fe25d4c4b947b27de0574901dfda0c.gif

    (7)这里可以自由设置,假设我们构建的模型尺寸是25A1*10A2*3A3,设置完后点击GreatestSupercell.

    e4fe25d4c4b947b27de0574901dfda0c.gif

    e4fe25d4c4b947b27de0574901dfda0c.gif(8)输出cif格式数据,依次是File Export,选择cif格式进行保存.

    (9)cif转lmp格式:

    首先安装Atomsk,然后打开windowspowershell,因为安装位置不同,所以,前面使用的命令不一样,具体转换命令如下:

    & 'F:\setup\Atomsk\atomsk.exe'atomskBaTiO.cif lmp

    其中,& 'F:\setup\Atomsk\ '是Atomsk的安装位置\后面要加上应用程序名atomsk.exe,BaTiO.cif是MS输出的cif格式文件,lmp是要转换成的lammps识别的data文件,至此,data文件完成。(此方法建立出来的data文件只有charge和atomic两种原子类型)

    e4fe25d4c4b947b27de0574901dfda0c.gif

    方法二:

    1.首先还是通过MS建立模型,模型建立好之后导出pdb格式,通过Atomsk将pdb格式转换成lammps可识别的.lmp格式文件。

    2.通过OVITO可视化软件打开.lmp文件,然后点击File→Expoer File,

    3.命名后,保存类型选择LAMMPS Data File,点击确定。

    4.点击确定后弹出Data Export Setting 窗口,在lammps atom style中有多种原子类型(atomic,bond,charge,full等)选择自己想要保存的原子类型即可。最后点击OK。

    方法三:

    1.MS中建立好结构模型后,在forcite模块中指定为cvff力场,注意不要执行run,直接关闭。

    2.将MS建立的模型导出成.car格式文件,同时自动产生.mdf文件(mdf文件中保存有对应的力场参数信息,如上面的cvff力场)。

    3.打开命令提示符窗口,将msi2lmp.exe拖动到命令行中,空一格;将上述生成的.mdf文件拖动到命令行中,去除后缀.mdf,空一格,输入-p 1 –c 1 –frc,空一格;将lammps安装目录下的frc_files文件夹中的cvff.frc拖动到命令行中,空一格,输入-i –n -o,最后enter键执行命令生成.data文件。(注:-p –c –frc –i –n –o的具体含义请见frc_files文件夹中的README-msi2lmp.txt)

    转载本文请联系原作者获取授权,同时请注明本文来自周龙科学网博客。

    链接地址:http://blog.sciencenet.cn/blog-3437453-1276040.html

    上一篇:[转载]常用材料数据库

    下一篇:杨氏模量、弹性模量、剪切模量、体积模量、强度、刚度,泊松比

    展开全文
  • 前面的教程中,我们已经知道,lammps建模方法有两种,可以使用lammps自带的建模命令建模,也可以使用第三方软件建模。 如果使用第三方软件建模,必须将模型文件转换为lammps可以识别的data文件,不同的建模方式...

    在前面的教程中,我们已经知道,lammps建模方法有两种,可以使用lammps自带的建模命令建模,也可以使用第三方软件建模。

    如果使用第三方软件建模,必须将模型文件转换为lammps可以识别的data文件,不同的建模方式转换方法有所不同。

    Material studio软件建模完成导出car文件,可通过msi2lmp软件转化为data文件,Atomsk软件建模完成可以直接导出data文件。

    假设现在已经有了data文件,我们就可以使用read_data命令将模型读入到lammps中,read_data命令的格式为:

    read_data file keyword args ...
    

    如果只需要读取一个data文件,直接将文件名写到read_data命令的后面即可:

    read_data Fe.data
    

    read_data命令支持在一个lammps代码中读取多个data文件,将不同的结构组装为一个复杂的体系。

    当然,复杂体系的建模也可以在建模软件里面完成,但有些情况下,使用read_data命令会更加灵活。

    比如两部分结构分别进行弛豫,达到平衡以后再组合到一起,或者两部分结构是用两种建模软件建模,分别生成data文件后,使用read_data可以很方便的组合到一起。

    下面使用read_data命令建立Fe-Cu界面体系,建模过程为:

    (1)运行Fe.in,生成单晶Fe模型,模型数据存到Fe.data文件中。

    #Fe.data
    units           metal              
    dimension       3                
    boundary        p p p         
    atom_style      atomic       
    neighbor        2.0 bin
    neigh_modify    delay 0 every 1 check yes
    
    lattice			fcc 2.86
    region			box block 0 20 0 20 0 40 units box
    create_box		1 box
    create_atoms	1 box
    mass			1 27
    
    write_data		Fe.data
    

    在这里插入图片描述

    (2)运行Cu.in,生成单晶Cu模型,模型数据存到Cu.data文件中。

    #Cu.data
    
    units           metal              
    dimension       3                
    boundary        p p p         
    atom_style      atomic       
    neighbor        2.0 bin
    neigh_modify    delay 0 every 1 check yes
    
    lattice			fcc 3.61
    region			box block 0 20 0 20 0 40 units box
    create_box		1 box
    create_atoms	1 box
    mass			1 64
    
    write_data		Cu.data
    

    在这里插入图片描述

    (3)运行All.in,读取Fe.data和Cu.data,组合为Fe-Cu界面模型,输出模型数据Fe_Cu.data。

    units           metal              
    dimension       3                
    boundary        p p p         
    atom_style      atomic       
    neighbor        2.0 bin
    neigh_modify    delay 0 every 1 check yes
    #读取Fe.data
    read_data		Fe.data extra/atom/types 1
    #第二次调用read_data命令,读取Cu.data,offset关键字说明原子类型+1
    #shift设置读取Cu模型后,Cu原子尺寸整体沿-y方向偏移22埃米
    read_data		Cu.data add append offset 1 0 0 0 0 shift 0.0 -22 0.0
    
    write_data		Fe_Cu.data
    

    在这里插入图片描述

    这个例子相对简单,仅仅是说明read_data使用方法,另外掌握可以建立比较复杂的模型,例如论文“Ultra-low friction of graphene/C60/graphene coatings for realistic rough surfaces”中的模型就可以使用本文的方法进行建模。
    在这里插入图片描述
    更多lammps教程和案例请关注微信公众号:lammps加油站
    扫码关注微信公众号

    展开全文
  • 掩码文件是一个包含01两种数字的文本文件,具体描述可参考Atomsk官方手册说明: https://atomsk.univ-lille.fr/tutorial_grid.php 例如下面是atomsk案例中的一个mask.txt,可以看出,文本中只有数字
  • in文件里的代码是按照编写的顺序进行解释执行,对于大部分命令行来说,前后顺序影响并不是很大,但是,对于一些命令,执行过程中需要某个参数,则这个参数的定义和计算必须写命令执行之前。 例如,对晶体
  • 上一次文章介绍了Atomsk软件建立孪晶的方法,有朋友咨询使用lammps自带命令建立孪晶的方法。 本文以Cu为例介绍lammps孪晶建模方法。 使用lammps建模,首先要确定xyz坐标轴对应的晶向指数。 以面心立方晶体[11-2]孪...
  • 公众号文章主要分为大类:lammps基础知识和lammps案例。 lammps基础知识:包括命令讲解、建模、使用技巧等等,大约80余篇文章。 lammps案例:包括基础案例和付费阅读案例,大约40多个案例,付费阅读案例10个左右...

空空如也

空空如也

1
收藏数 8
精华内容 3
关键字:

如何在atomsk建立两种原子的模型