精华内容
下载资源
问答
  • asm

    千次阅读 2019-01-05 19:16:25
    ASM系列之一:初探ASM   一、什么是ASM  ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和...

    https://blog.csdn.net/coslay/article/details/43370985

    ASM系列之一:初探ASM

     

    一、什么是ASM

        ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。

    二、ASM能干什么

        分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件

    三、ASM初探例子

        这里我们使用ASM的CoreAPI(ASM提供了两组API:Core和Tree,Core是基于访问者模式来操作类的,而Tree是基于树节点来操作类的)创建一个MyClass类,目标类如下:

    public class MyClass {  
        private String name;  
          
        public MyClass(){  
            this.name = "zhangzhuo";  
        }  
      
        public String getName() {  
            return name;  
        }  
      
        public void setName(String name) {  
            this.name = name;  
        }  
    } 

     

     这个类在构造方法中初始化了属性name,并提供了两个public方法来修改和访问name属性。

     

     接下来就要书写创建这个类的代码了,现将代码给出,然后逐步解释,代码如下:

       代码1:

    public class GenerateClass {
    
    public void generateClass() {
    
    
    //方法的栈长度和本地变量表长度用户自己计算
    
    ClassWriter classWriter = new ClassWriter(0);
    
    
    //Opcodes.V1_6指定类的版本
    
    //Opcodes.ACC_PUBLIC表示这个类是public,
    
    //“org/victorzhzh/core/classes/MyClass”类的全限定名称
    
    //第一个null位置变量定义的是泛型签名,
    
    //“java/lang/Object”这个类的父类
    
    //第二个null位子的变量定义的是这个类实现的接口
    
    classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC,
    
    "org/victorzhzh/core/classes/MyClass", null,
    
    "java/lang/Object", null);
    
    
    ClassAdapter classAdapter = new MyClassAdapter(classWriter);
    
    
    classAdapter.visitField(Opcodes.ACC_PRIVATE, "name",
    
    Type.getDescriptor(String.class), null, null);//定义name属性
    
    
    classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null,
    
    null).visitCode();//定义构造方法
    
    
    String setMethodDesc = "(" + Type.getDescriptor(String.class) + ")V";
    
    classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "setName", setMethodDesc,
    
    null, null).visitCode();//定义setName方法
    
    
    String getMethodDesc = "()" + Type.getDescriptor(String.class);
    
    classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "getName", getMethodDesc,
    
    null, null).visitCode();//定义getName方法
    
    
    byte[] classFile = classWriter.toByteArray();//生成字节码
    
    
    MyClassLoader classLoader = new MyClassLoader();//定义一个类加载器
    
    Class clazz = classLoader.defineClassFromClassFile(
    
    "org.victorzhzh.core.classes.MyClass", classFile);
    
    try {//利用反射方式,访问getName
    
    Object obj = clazz.newInstance();
    
    Method method = clazz.getMethod("getName");
    
    System.out.println(obj.toString());
    
    System.out.println(method.invoke(obj, null));
    
    } catch (Exception e) {
    
    e.printStackTrace();
    
    }
    
    }
    
    
    class MyClassLoader extends ClassLoader {
    
    public Class defineClassFromClassFile(String className, byte[] classFile)
    
    throws ClassFormatError {
    
    return defineClass(className, classFile, 0, classFile.length);
    
    }
    
    }
    
    
    public static void main(String[] args) {
    
    GenerateClass generateClass = new GenerateClass();
    
    generateClass.generateClass();
    
    }
    
    }

       代码2:

    public class MyClassAdapter extends ClassAdapter {
    
    
    public MyClassAdapter(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public MethodVisitor visitMethod(int access, String name, String desc,
    
    String signature, String[] exceptions) {
    
    MethodVisitor methodVisitor = cv.visitMethod(access, name, desc,
    
    signature, exceptions);
    
    if (name.equals("<init>")) {
    
    return new InitMethodAdapter(methodVisitor);
    
    } else if (name.equals("setName")) {
    
    return new SetMethodAdapter(methodVisitor);
    
    } else if (name.equals("getName")) {
    
    return new GetMethodAdapter(methodVisitor);
    
    } else {
    
    return super.visitMethod(access, name, desc, signature, exceptions);
    
    }
    
    }
    
    
    //这个类生成具体的构造方法字节码
    
    class InitMethodAdapter extends MethodAdapter {
    
    public InitMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
    
    "<init>", "()V");//调用父类的构造方法
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitLdcInsn("zhangzhuo");//将常量池中的字符串常量加载刀栈顶
    
    mv.visitFieldInsn(Opcodes.PUTFIELD,
    
    "org/victorzhzh/core/classes/MyClass", "name",
    
    Type.getDescriptor(String.class));//对name属性赋值
    
    mv.visitInsn(Opcodes.RETURN);//设置返回值
    
    mv.visitMaxs(2, 1);//设置方法的栈和本地变量表的大小
    
    }
    
    };
    
    
    //这个类生成具体的setName方法字节码
    
    class SetMethodAdapter extends MethodAdapter {
    
    public SetMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitVarInsn(Opcodes.ALOAD, 1);
    
    mv.visitFieldInsn(Opcodes.PUTFIELD,
    
    "org/victorzhzh/core/classes/MyClass", "name",
    
    Type.getDescriptor(String.class));
    
    mv.visitInsn(Opcodes.RETURN);
    
    mv.visitMaxs(2, 2);
    
    }
    
    
    }
    
    
    
    //这个类生成具体的getName方法字节
    
    class GetMethodAdapter extends MethodAdapter {
    
    
    public GetMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    mv.visitFieldInsn(Opcodes.GETFIELD,
    
    "org/victorzhzh/core/classes/MyClass", "name",
    
    Type.getDescriptor(String.class));//获取name属性的值
    
    mv.visitInsn(Opcodes.ARETURN);//返回一个引用,这里是String的引用即name
    
    mv.visitMaxs(1, 1);
    
    }
    
    }
    
    }

       运行结果:

    org.victorzhzh.core.classes.MyClass@1270b73  
    zhangzhuo 


       这个例子只是简单地介绍了一下ASM如何创建一个类,接下来的几个章节,将详细介绍ASM的CoreAPI和TreeAPI中如何操作类。

    ASM系列之二:Java类的基本表述

        上一篇文章中我们看到了如何使用ASM生成一个简单的JAVA类,里面使用到了很多的基本概念,比如:方法描述、引用描述等,下面将一一介绍。

    一、类版本:

        一个Java二进制的类文件,都有一个版本,因此ASM中提供了几个常量来指定一个类的版,这些常量定义在org.objectweb.asm.Opcodes接口中,如下:

    
     
    1. int V1_1 = 3 << 16 | 45;

    2. int V1_2 = 0 << 16 | 46;

    3. int V1_3 = 0 << 16 | 47;

    4. int V1_4 = 0 << 16 | 48;

    5. int V1_5 = 0 << 16 | 49;

    6. int V1_6 = 0 << 16 | 50;

    7. int V1_7 = 0 << 16 | 51;

     

     二、内部名字:

         在Java二进制文件中使用的是JVM的内部名字,而不是我们所熟悉的以“.”分割的全限定名,内部名字是以“/”替代“.”的全名,例如:java.lang.String在JVM中的内部名字是java/lang/String。在ASM中可以使用org.objectweb.asm.Type类中的静态方法getInternalName(final Class c) 来获得,如下:

    public class InternalNameTransform {
    
    
    public static void main(String[] args) {
    
    System.out.println(Type.getInternalName(String.class));
    
    System.out.println(Type.getInternalName(Integer.class));
    
    System.out.println(Type.getInternalName(InternalNameTransform.class));
    
    }
    
    }

        运行结果:

    java/lang/String  
    java/lang/Integer  
    org/victorzhzh/core/structure/InternalNameTransform 

     

    三、类型描述:

        我们知道JAVA类型分为基本类型和引用类型,在JVM中对每一种类型都有与之相对应的类型描述,如下表:

    Java类型 JVM中的描述
    boolean Z
    char C
    byte B
    short S
    int I
    float F
    long J
    double D
    Object Ljava/lang/Object;
    int [I
    Object [[Ljava/lang/Object;

        在ASM中要获得一个类的JVM内部描述,可以使用org.objectweb.asm.Type类中的getDescriptor(final Class c)方法,如下:

    public class TypeDescriptors {
    
    public static void main(String[] args) {
    
    System.out.println(Type.getDescriptor(TypeDescriptors.class));
    
    System.out.println(Type.getDescriptor(String.class));
    
    }
    
    
    }

        运行结果:

    Lorg/victorzhzh/core/structure/TypeDescriptors;  
    Ljava/lang/String;  

     

     四、方法描述:

        在Java的二进制文件中,方法的方法名和方法的描述都是存储在Constant pool中的,且在两个不同的单元里。因此,方法描述中不含有方法名,只含有参数类型和返回类型,如下:

    方法描述,在类中的 方法描述,在二进制文件中的
    void a(int i,float f) (IF)V
    void a(Object o) (Ljava/lang/Object;)V
    int a(int i,String s) (ILjava/lang/String;)I
    int[] a(int[] i) ([I)[I
    String a() ()Ljava/lang/String;

        获取一个方法的描述可以使用org.objectweb.asm.Type.getMethodDescriptor方法,如下:

    public class MethodDescriptors {
    
    public static void main(String[] args) throws Exception {
    
    Method m = String.class.getMethod("substring", int.class);
    
    System.out.println(Type.getMethodDescriptor(m));
    
    }
    
    
    }

        运行结果:

    (I)Ljava/lang/String;  


     其实在org.objectweb.asm.Type类中提供了很多方法让我们去了解一个类,有兴趣的可以看一下它的源码,这对我们了解一个类和操作一个类还是有大帮助的。

    ASM系列之三:ASM中的访问者模式

     

        在ASM的Core API中使用的是访问者模式来实现对类的操作,主要包含如下类:

    一、ClassVisitor接口:

        在这个接口中主要提供了和类结构同名的一些方法,这些方法可以对相应的类结构进行操作。如下:

    1. public interface ClassVisitor {
      
      void visit(int version,int access,String name,String signature,String superName,String[] interfaces);
      
      void visitSource(String source, String debug);
      
      void visitOuterClass(String owner, String name, String desc);
      
      AnnotationVisitor visitAnnotation(String desc, boolean visible);
      
      void visitAttribute(Attribute attr);
      
      void visitInnerClass(String name,String outerName,String innerName,int access);
      
      FieldVisitor visitField(int access,String name,String desc,String signature,Object value);
      
      MethodVisitor visitMethod(int access,String name,String desc,String signature,String[] exceptions);
      
      void visitEnd();
      
      }
      
      

      这里定义的方法调用是有顺序的,在ClassVisitor中定义了调用的顺序和每个方法在可以出现的次数,如下:

    visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd。

    二、ClassReader类:

        这个类会提供你要转变的类的字节数组,它的accept方法,接受一个具体的ClassVisitor,并调用实现中具体的visit,

    visitSource, visitOuterClass, visitAnnotation, visitAttribute, visitInnerClass,visitField, visitMethod和 visitEnd方法。

    三、ClassWriter类:

        这个类是ClassVisitor的一个实现类,这个类中的toByteArray方法会将最终修改的字节码以byte数组形式返回,在这个类的构造时可以指定让系统自动为我们计算栈和本地变量的大小(COMPUTE_MAXS),也可以指定系统自动为我们计算栈帧的大小(COMPUTE_FRAMES)。

    四、ClassAdapter类:

       这个类也是ClassVisitor的一个实现类,这个类可以看成是一个事件过滤器,在这个类里,它对ClassVisitor的实现都是委派给一个具体的ClassVisitor实现类,即调用那个实现类实现的方法。

    五、AnnotationVisitor接口:

       这个接口中定义了和Annotation结构想对应的方法,这些方法可以操作Annotation中的定义,如下:

    public interface AnnotationVisitor {
    
    void visit(String name, Object value);
    
    void visitEnum(String name, String desc, String value);
    
    AnnotationVisitor visitAnnotation(String name, String desc);
    
    AnnotationVisitor visitArray(String name);
    
    void visitEnd();
    
    }

     调用顺序如下:

    (visit | visitEnum | visitAnnotation | visitArray)* visitEnd

    六、FieldVisitor接口:

       这个接口定义了和属性结构相对应的方法,这些方法可以操作属性,如下:

    public interface FieldVisitor {
    
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    
    void visitAttribute(Attribute attr);
    
    void visitEnd();
    
    }

     调用顺序:

    ( visitAnnotation | visitAttribute )* visitEnd .

    七、MethodVisitor接口:

        这个接口定义了和方法结构相对应的方法,这些方法可以去操作源方法,具体的可以查看一下源码。

    八、操作流程:

       一般情况下,我们需要操作一个类时,首先是获得其二进制的字节码,即用ClassReader来读取一个类,然后需要一个能将二进制字节码写回的类,即用ClassWriter类,最后就是一个事件过滤器,即ClassAdapter。事件过滤器中的某些方法可以产生一个新的XXXVisitor对象,当我们需要修改对应的内容时只要实现自己的XXXVisitor并返回就可以了。

    九、例子:

        在这个例子中,我们将对Person类的sayName方法做出一些修改,源类:

    public class Person {
    
    private String name;
    
    
    public void sayName() {
    
    System.out.println(name);
    
    }
    
    }

      如果我们定义一个Person类然后调用其sayName()方法将会得到的是一个null,行成的二进制字节码如下:

    public void sayName();
    
    Code:
    
    Stack=2, Locals=1, Args_size=1
    
    0: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
    
    3: aload_0
    
    4: getfield #23; //Field name:Ljava/lang/String;
    
    7: invokevirtual #25; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    
    10: return
    
    }
    
    


     我们修改一下这个方法,让它输出"zhangzhuo",代码如下:

    public class GenerateNewPerson {
    
    public static void main(String[] args) throws Exception {
    
    // 使用全限定名,创建一个ClassReader对象
    
    ClassReader classReader = new ClassReader(
    
    "org.victorzhzh.core.ic.Person");
    
    // 构建一个ClassWriter对象,并设置让系统自动计算栈和本地变量大小
    
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    
    
    ClassAdapter classAdapter = new GeneralClassAdapter(classWriter);
    
    
    classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
    
    
    byte[] classFile = classWriter.toByteArray();
    
    
    // 将这个类输出到原先的类文件目录下,这是原先的类文件已经被修改
    
    File file = new File(
    
    "target/classes/org/victorzhzh/core/ic/Person.class");
    
    FileOutputStream stream = new FileOutputStream(file);
    
    stream.write(classFile);
    
    stream.close();
    
    }
    
    }
    
    
    public class GeneralClassAdapter extends ClassAdapter {
    
    
    public GeneralClassAdapter(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public MethodVisitor visitMethod(int access, String name, String desc,
    
    String signature, String[] exceptions) {
    
    MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
    
    exceptions);
    
    // 当是sayName方法是做对应的修改
    
    if (name.equals("sayName")) {
    
    MethodVisitor newMv = new SayNameMethodAdapter(mv);
    
    return newMv;
    
    } else {
    
    return mv;
    
    }
    
    }
    
    
    // 定义一个自己的方法访问类
    
    class SayNameMethodAdapter extends MethodAdapter {
    
    public SayNameMethodAdapter(MethodVisitor mv) {
    
    super(mv);
    
    }
    
    
    // 在源方法前去修改方法内容,这部分的修改将加载源方法的字节码之前
    
    @Override
    
    public void visitCode() {
    
    // 记载隐含的this对象,这是每个JAVA方法都有的
    
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    
    // 从常量池中加载“zhangzhuo”字符到栈顶
    
    mv.visitLdcInsn("zhangzhuo");
    
    // 将栈顶的"zhangzhuo"赋值给name属性
    
    mv.visitFieldInsn(Opcodes.PUTFIELD,
    
    Type.getInternalName(Person.class), "name",
    
    Type.getDescriptor(String.class));
    
    }
    
    
    }
    
    
    }

     这时,我们在查看一下Person的字节码:

    public void sayName();
    
    Code:
    
    Stack=2, Locals=1, Args_size=1
    
    0: aload_0
    
    1: ldc #13; //String zhangzhuo
    
    3: putfield #15; //Field name:Ljava/lang/String;
    
    =============以上是我们新增加的内容================================
    
    6: getstatic #21; //Field java/lang/System.out:Ljava/io/PrintStream;
    
    9: aload_0
    
    10: getfield #15; //Field name:Ljava/lang/String;
    
    13: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    
    16: return
    
    
    }

    再次调用Person对象,输出结果为:zhangzhuo

    ASM系列之四:操作类属性

     

        在上一篇文章中,我们看到了ASM中的Core API中使用的是XXXVisitor操作类中的对应部分。本文将展示如何使用ASM中的Core API对类的属性的操作。

    首先,我们定义一个原类Person,如下:

    public class Person {
    
    public String name = "zhangzhuo";
    
    public String address = "xxxxx" ;
    
    }

     这里,我们将属性定义为public类型,目的是为了我们使用反射去调用这个属性,接下来我们要为这个类添加一个int类型的属性,名字叫age。

        第一个问题,ASM的Core API允许我们在那些方法中来添加属性?

        在ASM的Core API中你要为类添加属性就必须要自己去实现ClassVisitor这个接口,这个接口中的visitInnerClass、visitField、visitMethod和visitEnd方法允许我们进行添加一个类属性操作,其余的方法是不允许的。这里我们依然使用Core API中的ClassAdapter类,我们继承这个类,定义一个去添加属性的类,ClassAdapter实现了ClassVisitor。

        第二个问题,我们要在这些方法中写什么样的代码才能添加一个属性?

        在使用ASM的Core API添加一个属性时只需要调用一句语句就可以,如下:

    1. classVisitor.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),

    2. null, null);

     第一个参数指定的是这个属性的操作权限,第二个参数是属性名,第三个参数是类型描述,第四个参数是泛型类型,第五个参数是初始化的值,这里需要注意一下的是第五个参数,这个参数只有属性为static时才有效,也就是数只有为static时,这个值才真正会赋值给我们添加的属性上,对于非static属性,它将被忽略。

    好了,让我们看一段代码,在visitEnd去添加一个名字为age的属性:

    public class Transform extends ClassAdapter {
    
    
    public Transform(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public void visitEnd() {
    
    cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),
    
    null, null);
    
    }
    
    
    }

     非常简单吧,只要一句话就可以添加一个属性到我们的类中,看一下我们的测试类:

    public class TransformTest {
    
    @Test
    
    public void addAge() throws Exception {
    
    ClassReader classReader = new ClassReader(
    
    "org.victorzhzh.core.field.Person");
    
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    
    ClassAdapter classAdapter = new Transform(classWriter);
    
    
    classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
    
    
    byte[] classFile = classWriter.toByteArray();
    
    
    GeneratorClassLoader classLoader = new GeneratorClassLoader();
    
    Class clazz = classLoader.defineClassFromClassFile(
    
    "org.victorzhzh.core.field.Person", classFile);
    
    Object obj = clazz.newInstance();
    
    
    System.out.println(clazz.getDeclaredField("name").get(obj));//----(1)
    
    System.out.println(clazz.getDeclaredField("age").get(obj));//----(2)
    
    }
    
    }

     在这里,如果我们的age没有被添加进去那么(2)这个地方将会报错,看一下结果:

    zhangzhuo  
    0 

     

     int类型在没有被初始化时,默认值为0,而第二行输出0,说明我们添加了一个属性age

    接下来,我们在visitField方法中在次添加age属性,如下:​​​​​​​

    public class Transform extends ClassAdapter {
    
    
    public Transform(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public FieldVisitor visitField(int access, String name, String desc,
    
    String signature, Object value) {
    
    cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),
    
    null, null);
    
    return super.visitField(access, name, desc, signature, value);
    
    }
    
    
    }

     这时,我们再次运行测试类,结果如下:​​​​​​​

    java.lang.ClassFormatError: Duplicate field name&signature in class file org/victorzhzh/core/field/Person
    
    at java.lang.ClassLoader.defineClass1(Native Method)
    
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
    
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
    
    at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
    
    at org.victorzhzh.common.GeneratorClassLoader.defineClassFromClassFile(GeneratorClassLoader.java:14)
    
    at org.victorzhzh.core.field.TransformTest.addAge(TransformTest.java:22)
    
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    
    at java.lang.reflect.Method.invoke(Method.java:597)
    
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

     很奇怪,怎么会属性名重复,我们看一下原类,

    public String name = "zhangzhuo";
    
    public String address = "xxxxx" ;

     没有重复的名字,而我们添加的是age也不重复,为什么会报重复属性名错误呢?

    原因是,在我们的Transform类中的visitField方法,这个方法会在每次属性被访问时调用,而ASM在对这个类操作时会遍历到每个属性,也就是说有一个属性就会调用一次visitField方法,有两个属性就会调用两次visitField方法,所以当我们原类中有两个属性时visitField方法被调用了两次,因此创建了两个同名的age属性。

     

    从这个例子中我们可以将visitInnerClass、visitField、visitMethod和visitEnd这些方法分成两组,一组是visitInnerClass、visitField、visitMethod,这些方法有可能会被多次调用,因此在这些方法中创建属性时要注意会重复创建;另一组是visitEnd,这个方法只有在最后才会被调用且只调用一次,所以在这个方法中添加属性是唯一的,因此一般添加属性选择在这个方法里编码。

        当然这里只给出了如何创建一个属性,其实修改,删除也都一样,根据上述知识大家可以参考ASM的源码即可掌握修改删除等操作。

     

    附GeneratorClassLoader类代码

    public class GeneratorClassLoader extends ClassLoader {
    
    
    @SuppressWarnings("rawtypes")
    
    public Class defineClassFromClassFile(String className, byte[] classFile)
    
    throws ClassFormatError {
    
    return defineClass(className, classFile, 0, classFile.length);
    
    }
    
    }

    ASM系列之五:操作类方法

     

    前面我们了解了如何使用ASM的CoreAPI来操作一个类的属性,现在我们来看一下如何修改一个类方法。

    场景:假设我们有一个Person类,它当中有一个sleep方法,我们希望监控一下这个sleep方法的运行时间:

    一般我们会在代码里这样写:

    public void sleep() {
    
    long timer = System.currentTimeMillis();
    
    
    
    try {
    
    System.out.println("我要睡一会...");
    
    TimeUnit.SECONDS.sleep(2);
    
    } catch (InterruptedException e) {
    
    e.printStackTrace();
    
    }
    
    System.out.println(System.currentTimeMillis()-timer);
    
    
    
    }

     标红的两行代码是我们希望有的,但是一般不会将这样的代码和业务代码耦合在一起,所以借助asm来实现动态的植入这样两行代码,就可以使业务方法很清晰。因此我们需要能够修改方法的API,在ASM中提供了对应的API,即MethodAdapter,使用这个API我们就可以随心所欲的修改方法中的字节码,甚至可以完全重写方法,当然这样是没有必要的。下面我们来看一下如何使用这个API,代码如下:

    public class ModifyMethod extends MethodAdapter {
    
    
    public ModifyMethod(MethodVisitor mv, int access, String name, String desc) {
    
    super(mv);
    
    }
    
    
    @Override
    
    public void visitCode() {
    
    mv.visitFieldInsn(Opcodes.GETSTATIC,
    
    Type.getInternalName(Person.class), "timer", "J");
    
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
    
    "currentTimeMillis", "()J");
    
    mv.visitInsn(Opcodes.LSUB);
    
    mv.visitFieldInsn(Opcodes.PUTSTATIC,
    
    Type.getInternalName(Person.class), "timer", "J");
    
    }
    
    
    @Override
    
    public void visitInsn(int opcode) {
    
    if (opcode == Opcodes.RETURN) {
    
    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
    
    "Ljava/io/PrintStream;");
    
    mv.visitFieldInsn(Opcodes.GETSTATIC,
    
    Type.getInternalName(Person.class), "timer", "J");
    
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
    
    "currentTimeMillis", "()J");
    
    mv.visitInsn(Opcodes.LADD);
    
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
    
    "println", "(J)V");
    
    }
    
    mv.visitInsn(opcode);
    
    }
    
    }

    MethodAdapter类实现了MethodVisitor接口,在MethodVisitor接口中严格地规定了每个visitXXX的访问顺序,如下:

    visitAnnotationDefault?( visitAnnotation | visitParameterAnnotation | visitAttribute )*( visitCode
    ( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*visitMaxs )?visitEnd

    首先,统一一个概念,ASM访问,这里所说的ASM访问不是指ASM代码去调用某个类的具体方法,而是指去分析某个类的某个方法的二进制字节码。

    在这里visitCode方法将会在ASM开始访问某一个方法时调用,因此这个方法一般可以用来在进入分析JVM字节码之前来新增一些字节码,visitXxxInsn是在ASM具体访问到每个指令时被调用,上面代码中我们使用的是visitInsn方法,它是ASM访问到无参数指令时调用的,这里我们判但了当前指令是否为无参数的return来在方法结束前添加一些指令。

    通过重写visitCode和visitInsn两个方法,我们就实现了具体的业务逻辑被调用前和被调用后植入监控运行时间的代码。

     

    ModifyMethod类只是对方法的修改类,那如何让外部类调用它,要通过我们上一篇中使用过的类,ClassAdapter的一个子类,在这里我们定义一个ModifyMethodClassAdapter类,代码如下:

    public class ModifyMethodClassAdapter extends ClassAdapter {
    
    
    public ModifyMethodClassAdapter(ClassVisitor cv) {
    
    super(cv);
    
    }
    
    
    @Override
    
    public MethodVisitor visitMethod(int access, String name, String desc,
    
    String signature, String[] exceptions) {
    
    if (name.equals("sleep")) {
    
    return new ModifyMethod(super.visitMethod(access, name, desc,
    
    signature, exceptions), access, name, desc);
    
    }
    
    return super.visitMethod(access, name, desc, signature, exceptions);
    
    }
    
    
    @Override
    
    public void visitEnd() {
    
    cv.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, "timer", "J",
    
    null, null);
    
    }
    
    
    }

     上述代码中我们使用visitEnd来添加了一个timer属性,用于记录时间,我们重写了visitMethod方法,当ASM访问的方法是sleep方法时,我们调用已经定义的ModifyMethod方法,让这个方法作为访问者,去访问对应的方法。

    这样两个类就实现了我们要的添加执行时间的业务。

    看一下测试类:

    public class ModifyMethodTest {
    
    @Test
    
    public void modiySleepMethod() throws Exception {
    
    ClassReader classReader = new ClassReader(
    
    "org.victorzhzh.common.Person");
    
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    
    ClassAdapter classAdapter = new ModifyMethodClassAdapter(classWriter);
    
    classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
    
    
    byte[] classFile = classWriter.toByteArray();
    
    
    GeneratorClassLoader classLoader = new GeneratorClassLoader();
    
    @SuppressWarnings("rawtypes")
    
    Class clazz = classLoader.defineClassFromClassFile(
    
    "org.victorzhzh.common.Person", classFile);
    
    Object obj = clazz.newInstance();
    
    System.out.println(clazz.getDeclaredField("name").get(obj));
    
    clazz.getDeclaredMethod("sleep").invoke(obj);
    
    }
    
    }

     通过反射机制调用我们修改后的Person类,运行结果如下:

    zhangzhuo  
    我要睡一会...  
    2023 


    2023就是我们让sleep方法沉睡的时间,看一下我们的原始Person类:

    public class Person {
    
    public String name = "zhangzhuo";
    
    
    public void sayHello() {
    
    System.out.println("Hello World!");
    
    }
    
    
    public void sleep() {
    
    try {
    
    System.out.println("我要睡一会...");
    
    TimeUnit.SECONDS.sleep(2);//沉睡两秒
    
    } catch (InterruptedException e) {
    
    e.printStackTrace();
    
    }
    
    }
    
    }

    以上几篇文章都是关于ASM的大体介绍,ASM的功能可以说是十分强大,要学好这个东西个人有几点体会:

    第一、要熟悉Java字节码结构,及指令:因为我们在很多时候都是要写最原始的字节吗指令的,虽然ASM也为我们提供相应的简化API替我们来做这些事情,但是最基本的东西还是要了解和掌握的,这样才能使用的更好;

    第二、充分理解访问者模式有助于我们理解ASM的CoreAPI;

    第三、掌握基本的ClassVisitor、ClassAdapter、MethodVisitor、MethodAdapter、FieldVisitor、FieldWriter、ClassReader和ClassWriter这几个类对全面掌握CoreAPI可以有很大的帮助;

    第四、在掌握了CoreAPI后再去研究TreeAPI,这样更快速;

    最后,希望这几篇文章能对研究ASM的朋友有所帮助

    展开全文
  • ASM

    2015-03-13 20:14:33
    asm管理大量物理卷并将他们作为一个或多个逻辑卷呈交给oracle。 物理卷可以是实际的磁盘或磁盘分区或者是隶属操作系统的卷管理器管理的卷。无论采用哪种方式,他们都不能使用任何文件系统格式化。他们必须时原始...
    ----
    asm管理大量物理卷并将他们作为一个或多个逻辑卷呈交给oracle。
    物理卷可以是实际的磁盘或磁盘分区或者是隶属操作系统的卷管理器管理的卷。无论采用哪种方式,他们都不能使用任何文件系统格式化。他们必须时原始设备。asm会接受原始设备并将他们放到许多asm磁盘磁盘组中,磁盘组就是逻辑卷。
    asm磁盘必须时没有文件系统的原始磁盘,但不必时实际的磁盘,他们可以时磁盘,磁盘分区或LVM管理的逻辑卷。
    asm只能用于数据库和恢复文件,而不能用于oracleHome或其他任何事物。数据库文件的定义很宽泛,但是不包括跟踪文件,警报日志,口令文件或静态参数文件。
    asm镜像默认时单镜像,但是可以设置为无镜像或双镜像;不能禁用条带化。
    ----------------asm实例
    常规的磁盘活动不涉及ASM实例。ASM实例是一种使文件可用的管理和控制功能,它不执行时间的I/O工作。
    asm实例像其他任何实例一样。它有一个SGA和一些重用的后台进程,但时它不能加载或打开数据库,它所做的工作就是定位并管理asm磁盘。由于它不能加载或打开数据库,因此它永远不能读取数据字典。为此,只能作为sysoper,sysdba,sysasm使用口令文件或操作系统身份验证的方式联接到它。sysasm是一种oracle11g版本引入的角色,已允许分离数据库管理员和asm管理员的职责。
    应该为每台计算机创建一个asm实例,并使用它代表运行在计算机上的所有rdbms实例来管理此计算机上所有可用的asm磁盘。
    一个asm实例除了常规的进程外还有两个后台进程,他们是RBAL和ARBn进程,用来处理再平衡活动.asm磁盘中多个asm磁盘之间的数据移动来响应磁盘组中磁盘的删除或添加。如果在磁盘组中添加一个新设备,那么asm将会检测到该设备并启动一个操作以便使用该磁盘。这意外这将数据移动到磁盘上,考虑增加的条带化可能性并包括新磁盘以便均匀地分配I/O工作负荷。RBAL进程协调这种在平衡活动,而由ARBn进程完成此任务。
    再平衡操作将自动启动以响应磁盘组重配置。
    为了创建和配置磁盘组,必须首先连接到asm实例并启动它。一旦创建并加载了磁盘组,asm实例将会等待来自RDBMS实例的访问文件的请求。
    asm实例代表rdbms实例管理asm磁盘组中的文件,有rdbms实例创建,读取和写入这些文件。
    所有asm文件都跨越磁盘组中的所有asm磁盘进行条带化。空间的分配依照分配单元(AU)实施。标准的AU大小是1MB,并且对于数据访问倾向采用适度大的磁盘I/O操作的数据来说,条带化的单位也是1MB。这种做法称为粗略的条带化。对于读和写操作请求通常针对较小的I/O单位的文件来说,AU自身按照128KB的条带化跨越磁盘实施条带化,这称为精细化条带化。
    创建和管理asm文件的语法与基于文件系统的文件采用的语法完全相同。所有文件都是通过rdbms实例使用常见命令创建并管理的。唯一的不同之处是文件名:当创建一个数据文件时,值需指定文件应该指向的磁盘组的名称,asm就会生成实际的文件名并管理物理位置。

    获取asm文件的唯一途径时通过oracle实用程序。但是它确实表明必须使用rman来备份asm数据文件,归档日志和控制文件。

    因为不能使用任何操作系统提供的工具写入asm磁盘组,对于数据文件来说,这种迁移必须使用rman通过备份和还原操作来完成的。为了移动联机日志,在asm磁盘组中创建新的成员并删除旧的成员。

    -----------------使用文件模拟原始设备。
    LIUNX
    dd if=/dev/zero of=/u01/app/oracle/raw1.disk bs=1024 count=1000000
    dd if=/dev/zero of=/u01/app/oracle/raw2.disk bs=1024 count=1000000
    WINDOWS
    查找大文件,它的大小时1024字节的倍数,将它复制到选定的目录两次。在这个示例中,文件恰好是一个关盘的ISO映像。
    copy \tmp\oel2\d1.iso \app\oracle\raw1.disk
    copy \tmp\oel2\d1.iso \app\oracle\raw2.
    当创建的文件分配到磁盘组时,asm将会进行格式化。asm会做一些检查(例如,它不会接受oracle数据库的副本),但是几乎任何文件都应该可以作为文件来源。
    -------------常用的asm初始化参数
    instance_type     对于asm实例,值必须是asm
    instance_name   必须加前缀‘+’
    asm_power_limit  控制用于在平衡操作的asmb进程的数量,默认值是1,这是最小数量
    asm_diskstring 表示分配给asm的磁盘的路径列表。当asm扫描磁盘时,它将使用该字符串并查找有权打开的任何设备。如果成功地发现相应的设备,asm实例上的V$asm_disk视图将会反映发现的磁盘。
    asm_diskgroups启动时要再加的磁盘组。
    -------------启动asm实例的前置条件
    为了启动实例,必须作为sysasm用户连接它,并发出startup命令。可以通过将oracle_sid环境变量设置为实例名称(不要忘记必须前缀‘+’符号)来创建连接,或者如果像上一个示例那样已经创建和启用了一个
    口令文件,则可以使用口令文件身份验证的方式进行连接。启动首先经历nomount,此时在内存中构建实例并且识别由ASM_DISKSTRING参数标识的磁盘。然后,实例会加载由ASM_DISKGROUP指定的磁盘组。对于
    asm实例不存在mount或open模式,可以加载或卸载磁盘组。

    rdbms实例使用由asm实例管理的磁盘组中的文件。如果asm实例没有启动和加载磁盘,那么rdbms实例不能打开。因此必须通过操作系统实用程序确保在依赖它的rdbms实例之前启动asm实例。如果asm实例终止,那么也会终止依赖它的rdbms实例。如果当向一个asm实例发出shutdown命令时,一个或多个rdbms实例已打开了其中一个磁盘中上的文件,
    那么将会收到下面消息:ora-15097 cannot shutdown asm instance with connected rdbms instance
    例外是shutdown abort,它会终止asm实例并因此造成rdbms实例终止。
    如果rdbms实例失败,asm实例不会受到影响。如果asm实例失败,依赖它的rdbms实例就会异常中止。

    ---------------创建一个参数文件并使用它来启动一个asm实例。
    本练习中所有步骤都应该在操作系统提示符下完成。注意第(2)步仅适用于windows系统
    (1)配置cluster synchronization Services Daemon .这要求运行oracle_home/bin目录中的localconfig 实用程序。在unix上,必须作为根用户运行此命令
    localconfig add;在windows上,将会看到一个已创建并置于自动启动的名为oracleCSService服务;在unix上,将会在/etc/initabl文件中看到一个记录项,它会在运行级别3和运行级别5上重新
    启动init.cssd进程。
    (2)
    (3)在oracle_home\dbs创建init+asm.ora的文件
    instance_name='+asm'
    instance_type='asm'
    asm_diskstring='/u01/app/oracle/raw*.disk'
    _asm_allow_only_raw_disk=false这是一个隐藏参数,它允许本练习使用文件来替代原始设备
    memory_target=0禁用自动内存管理
    (4)将oracle_sid环境变量设置为asm实例名称
    (5)作为sysasm使用sql*plus连接到asm实例,并启动该实例
    (6)使用下面sql语句确认asm已经找到磁盘
    select path,os_mb from v$asm_disk;
    磁盘组由asm实例创建,随后由rdbms实例使用。为了创建磁盘组,至少要为磁盘组取一个名称并分配通过asm磁盘字符串找到的并能够在v$asm_disk视图中看到的一个磁盘列表
    sql>create diskgroup dg1 disk '/dev/sdc','/dev/sdd','/dev/sde','/dev/sdf'
    如果指定了一个已属某个磁盘组的磁盘,则该命令会失败。asm提供的默认冗将余级别是“标准”冗余,即镜像AU一次。将会跨越所有磁盘条带化所有文件来获得最大性能。对于标准冗余,磁盘组至少要有两个磁盘,磁盘组的有效大小将是分配的总空间的一半。
    为了重写默认的normal冗余,也数据单镜像,向create diskgroup命令添加关键字high redundancy或external redundancy。
    high redundancy将会为每个分配单元创建3个副本。
    external redundancy
    通过将一个磁盘组中的asm磁盘放置到股故障组中可以进一步提高冗余性。当asm镜像扩展卷时,它永不会将扩展卷镜像到同一个故障组中的另一个磁盘。这意味着能够更好地防范多个磁盘出现故障。默认情况下。数
    认为每个磁盘是它自己的故障组;这使得asm能够完全自由地将该磁盘的数据镜像到组中的其他任何磁盘。但是,如果一些磁盘时在硬件级别连接时,通常连接到相同的控制器上,将不会希望asm在他们之间进行镜像。
    使用故障组强制asm在组内一个不同的磁盘子集上创建镜像。这样做的一个示例是:
    create diskgroup dgroups normal redundancy
    failgroup controller2 disk '/dev/rdsk/c2*'
    failgroup controller3 disk '/dev/rdsk/c3*'
    该命令创建一个包含由给定的通配符匹配的所有磁盘设备构成的磁盘组,也数据挂起第二个和第三个控制器的所有磁盘。但是使用故障组(他们的名称并不重要)指示asm用于不要在同一个控制器上两个磁盘之间镜像数据。

    create diskgroup dg1 disk 'c:\app\oracle\raw1.disk','c:\app\oracle\raw2.disk';
    select name,group_numer,type,state,total_mb from v$asm_diskgroup;
    注意,该组的冗余为normal,因此可用的总空间实际上只有一半大小,该组是mounted表明它可供使用。
    为了确保在启动asm实例时自动挂在磁盘组,在参数文件添加如下一行
    asm_diskgroups=dg1
    如果没有这样做,那么每次启动asm实例时必须手动加载磁盘组
    alter diskgroup dg1 mount;


    asm磁盘组是在asm实例中创建的,asm文件是在rdbms实例中创建的。创建数据文件,临时文件,和日志文件的标准命令都可以采用一个磁盘组名称来代替一个文件名。
    sql>create tablespace new_tbs datafile '+dg1' size 100m;
    sql>alter tablespace system add datafile '+system_dg' size 1000m;
    sql>alter database add logfile group 4 '+dg_log1','+dg_log2' size 100m;
    第一个命令是在磁盘组dg1中创建一个含有一个数据文件的新的表空间。
    第二个命令项system表空间添加一个数据文件,该表空间处于专门为system数据文件创建的一个磁盘组中。
    第三条命令创建一个新的联机日志文件组,它含有不同磁盘组中的两个成员。这些组很可能具有external冗余的磁盘组,因此可以依赖多路复用来提供容错性。
    为了指引归档日志到ASM,设置log_Archive_Dest参数以指向磁盘组
    sql>alter system set log_Archive_DEst_1='location=+dg_arc1';
    sql>alter system set log_Archive_Dest_2='location=+dg_arc2';
    还可以将闪回恢复区指引到asm磁盘组
    alter system set db_Recovery_file_Dest='+dg_flash'

    -----------------------------使用rman将控制文件迁移到ASM
    rman还是将数据库从传统的文件系统存储区迁移到asm存储区的唯一可用的工具。假设您有三个磁盘组:组dg1用于数据文件,组dg2,dg3用于控制文件和联机重做日志文件
    要迁移控制文件,更改controlfile实例参数以指向磁盘组,然后关闭数据库闭关在nomount模式下启动它。
    alter system set controlfiles='+dg2','+dg3' scope=spfile;
    shutdown immediate;
    startup nomount;
    然后,启动rman并从它的原始位置还原控制文件在
     rman>restors controlfile from '/u01/app/oracle/oradata/orcl/control01.ctl'
    现在可以加载和打开数据库,使用asm设备上的控制文件

    ---------------------使用rman将所有数据文件系统迁移到asm磁盘组
    shutdown
    startup mount
    backup as copy database format '+dg1';
    switch database to copy;
    alter database open;
    ----------------
    sql>alter database add logfile member '+dg2','+dg3' to group 1;
    sql>alter database drop logfile member '/u01/app/oracle/oradata/orcl/redo01a.log','/u01/app/oracle/oradata/orcl/redo02a.log' ;
    --------------
    最后,必须转移临时表空间临时文件,不能备份这些文件,技巧是删除他们并在磁盘中创建新文件。只创建新的临时表空间并删除原始的临时表空间可能更简单些。
    create temporary tablespace tempasm tempfile '+dg1' size 1g;
    alter database default temporary tablespace tempasm;
    drop tablespace temp including contents and datafiles;

    -------------asm的一些主要优点:
    (1)将I/O均匀地分别到所有可用磁盘驱动器以防止产生热点,并且最大化性能。
    (2)不再需要过多地进行配置工作,并且最大化推动数据库合并的存储资源利用
    (3)内在地支持大文件
    (4)在增量增加或删除存储容量后执行自动联机重分配。
    (5)维护数据的冗余副本以提供高可用性,或者利用第三方的raid功能
    (6)支持oracle 10g以及rac
    (7)可以利用第三方的多路经技术。

    使用nomount选项可在不安装任何磁盘组的情况下启动asm实例,而使用mount选项则会安装所有已定义的磁盘组。

    -----------------------------asm和多路经
    I/O路径一般由启动器端口,结构端口,目标端口和LUN组成。I/O路径的每种赔了方式都被认为是一个独立的路径。动态多路径/故障恢复工具将这些独立的路径聚集为单个的逻辑路径。这种路径抽象提供了多个主机
    总线适配器(HBA)之间的I/O负载平衡以及产生I/O路径故障时不中断的故障恢复。多路径软件需要所有必需的磁盘在每个可用的,符合条件的HBA上可见。MP驱动程序将通过执行SCSI查询命令检测多个路径。多路径软件也提供多路径软件驱动程序。为了支持多路径,实际的HBA驱动程序必须服从基础驱动程序提供的多路经服务。确保正在考虑的配置经过供应商的证明。
    --多路径工具提供了如下的优点
    (1)为多路径的LUN提供了单个的块设备接口
    (2)检测I/O路径中的任何组件故障,例如结构端口,通道适配器和HBA
    (3)发生路径丢失故障时,确保I/O重新路由到可用的路径,而不会产生进程中断。
    (4)在时间发生时自动重新配置多个路径。确保尽可能使产生故障的路径重新生效,并且提供自动恢复功能。
    (5)配置多个路径,从而使用各种负载平衡方法最大化性能,例如循环复用,最少I/O队列或最少服务时间。

    多路径软件实例包括emc powerpath ,veritas dmp,sun traffic manager,hitachi hdlm和ibm sddpcm.
    linux 2.6有一个基于内核的多路经驱动程序,称为device mapper。此外,一些供应商也提供了多路经解决方案。
    构建asm基础结构的第一项任务是在asm管理中发现和关联(添加)磁盘。
    asm必须只使用字符设备作为磁盘,而不能使用块设备。根据oracle10.2版本的管理指南,用户可以使用asmlib访问块设备:ASMLib是ASM的可选附件,它的目标是为ASM支持的内核提供发现和访问块设备的备选接口。在大多数unix系统上,字符设备显示为/dev/rdsk,而在linux上则显示为/dev/raw/rwa。唯一的例外情况时在asm使用nas文件系统作为磁盘时。

    -----------------------asm磁盘组
    一旦发现了磁盘,就可以创建封装一个或多个这些磁盘的磁盘组。
    磁盘组中会隐士地创建ASM文件系统层,该文件系统对于用户是透明的,只可以通过asm,联接的数据库以及10.2版本中的asm命令行工具访问,并且内在地具有自动化文件级数据分条和镜像功能。在asm磁盘组中创建
    的数据库文件将其文件盘区平均地分布到磁盘组中的所有联机磁盘,从而提供均匀的I/O负载。
    成功创建磁盘组后,包括创建日期,磁盘组,和冗余类型的元数据信息存储在SGA和DATA磁盘组中每个磁盘的磁盘头中。
    v$asm_disk视图现在将反映这个磁盘头信息。一旦这些磁盘处于asm管理下,随后的所有磁盘组安装操作将造成asm重新读取并确认asm磁盘头。安装磁盘组时,无论是在asm启动时的安装或随后的安装,都建议用户一次性安装所有必需的磁盘组,实现该操作可最小化多个asm磁盘发现扫描的系统开销。

    asm实例也在v$asm_disk,v$asm_disk_stat视图中驻留I/O统计信息,这些视图包括执行的读/写操作,处理的读/写块,以及引发的读/写错误。基于asm的实用程序asmiostat,可用于显示基于asm磁盘的I/O统计信息。来自oracle的该使用程序可以复制到文件中,并且针对任何10.2ASM实例执行。
    查询v$Asm_disk,v$asm_diskgroup是一项昂贵的操作,因为每次执行该操作都涉及执行磁盘发现。为了最小化喜糖开销并且允许轻量化地访问该数据集。10.2版本中引入了两个新的视图:
    v$asm_dis_Stat和v$asm_Disgroup_Stat这个两个视图等同v$Asm_disk,v$asm_diskgroup。
    然而通过内存轮询v$asm_dis_Stat和v$asm_Disgroup_Stat视图,因此不需要执行磁盘发现。这些新的视图提供了有效的轻量访问,因此em可以周期性地在磁盘级上查询性能统计信息,并且在磁盘组级上聚集空间使用情况统计信息,而不会导致大量的系统开销。

    对于没有使用外部冗余的系统,asm通过故障组提供了自己的内部冗余机制和额外的高可用性。故障组是磁盘组的一个子集,根据定义,它时一个磁盘集合,可能会由于一个相关组件(例如控制器或整个阵列)产生故障而变得不可用。因此,给定磁盘组的两个独立故障组中的磁盘不能共享常见的故障组件。如果为磁盘组定义故障组,asm就会容忍单个故障组中的多个磁盘同时产生的故障。

    阿斯名使用独特的镜像算法:不镜像磁盘,而是镜像盘区。作为结果,为了在产生故障时提供连续的保护,只需要磁盘组中的空间容量,而不需要预备一个热后备磁盘。不建议用户创建不同尺寸的故障组,因为这将会导致在分配辅助盘区时产生问题。asm将文件的主盘区分配给磁盘组中的一个磁盘时,它会将该盘区的镜像副本分配给磁盘组中的另一个磁盘。给定磁盘上的主盘区将在磁盘组中的某个伙伴磁盘上具有各自的镜像盘区。

    asm确保主盘区和其镜像副本不会驻留在相同的故障组中。磁盘组的冗余可以有如下形式:双向镜像文件(至少需要两个故障组)的普通冗余(默认冗余)和使用三向镜像(至少需要3个故障组)提供较高保护程度的高冗余。一旦创建磁盘组,就不可以改变它的冗余级别。w为了改变磁盘组的冗余,必须创建具有适当冗余的另一个磁盘组,然后必须使用rman还原或dbms_file_transfer将数据文件移动到这个新创建的磁盘组。

    创建时具有普通冗余或高冗余的磁盘组包含分别带有两个或三个镜像文件。选择第一个分配的文件盘区作为主盘区,而将镜像的盘区称为辅助盘区。在高冗余的情况下将会有两个辅助盘区。这种分为主盘区和辅助盘区的逻辑分组称为盘区集。盘区集总是包含完全相同的数据,因为他们时彼此之间的镜像版本。因此,在将块写入到文件时,并行写入盘区集中的每个盘区。然而,在从磁盘中读取块时,总是从主盘区中读取该块,除非不能读取主盘区,需要注意的是,磁盘组(以及故障组)中的每个磁盘都包含接近相同数据主盘区和辅助盘区,从而可以在所有磁盘之间均匀地分布读I/O活动。这种方式不同于大多数的逻辑卷管理器,这些管理器具有主磁盘集和镜像磁盘集。

    -------------------------下面是asm重新平衡操作的典型流程

    在asm实例上,dba添加或删除磁盘组中的磁盘:
    (1)调用RBAL进程创建重新平衡计划,然后开始协调重新分布.
    (2)RBAL将计算执行该认为所需的估计时间和工作,然后发送消息给ARBX进程以实际地处理请求。通过asm_power_limit参数直接确定调用的ARBx进程的数量
    (3)更新持续操作目录(元数据)以反映重新平衡活动
    (4)将重新定位的每个盘区分配给ARBX进程
    (5)ARBX对这些盘区执行重新平衡。锁定,重新定位,解锁每个盘区。这个v$asm_operation中显示为operation REBAL.
     重新平衡涉及在物理上移动AU盘区。这种移动的影响一般较低,因为一次只对一个AU盘区执行重新平衡,因此,在任何给定时间,每个ARBx进程只有一个未完成的I/O,这应该不会给联机数据库活动带来负面影响。然而,一般建议用户在非高峰期间调度重新平衡操作。
    也可以使用alter diskgroup命令为特定的重新平衡活动设置速度极限值,该值只对特定的重新平衡任务有效。


    展开全文
  • ASM】如何创建ASM磁盘

    千次阅读 2019-08-17 15:28:00
    ASM】如何创建ASM磁盘 blog文档结构图: 1前言 无论是安装单机版的asm还是rac都离不开ASM磁盘组的创建,创建ASM磁盘组的关键是创建好需要的asm磁盘,发现很多网友安装grid软件和grid实例,都在磁盘的创建...

    【ASM】如何创建ASM磁盘

    blog文档结构图:

    image

     

     前言

    无论是安装单机版的asm还是rac都离不开ASM磁盘组的创建,创建ASM磁盘组的关键是创建好需要的asm磁盘,发现很多网友安装grid软件和grid实例,都在磁盘的创建这里有很大的问题,本人又是喜欢总结的人,那么今天我就总结了下各种创建asm磁盘的方法,以备大家查阅。

    之前的2篇文章中用到了asm磁盘的创建,连接如下:

     

    Oracle 单实例ASM+11gR2安装 http://blog.itpub.net/26736162/viewspace-1205206/

    vmware  workstations + rhel6.5 下 一步一步搭建oracle 11gR2 rac+dg http://blog.itpub.net/26736162/viewspace-1328156/

     

     创建asm磁盘的各种方法

     

    2.1  方法一:Faking 方式

    该方法不需要添加额外的磁盘,直接在已有的磁盘上来创建,本人推荐的方法之一。

     

    ------------------------ 直接贴脚本,root用户下执行

    mkdir  -p  /oracle/asmdisk

    dd if=/dev/zero of=/oracle/asmdisk/disk1 bs=1024k count=1000

    dd if=/dev/zero of=/oracle/asmdisk/disk2 bs=1024k count=1000

    dd if=/dev/zero of=/oracle/asmdisk/disk3 bs=1024k count=1000

    dd if=/dev/zero of=/oracle/asmdisk/disk4 bs=1024k count=1000

    dd if=/dev/zero of=/oracle/asmdisk/disk5 bs=1024k count=1000

     

    /sbin/losetup /dev/loop1 /oracle/asmdisk/disk1

    /sbin/losetup /dev/loop2 /oracle/asmdisk/disk2

    /sbin/losetup /dev/loop3 /oracle/asmdisk/disk3

    /sbin/losetup /dev/loop4 /oracle/asmdisk/disk4

    /sbin/losetup /dev/loop5 /oracle/asmdisk/disk5

     

    raw /dev/raw/raw1 /dev/loop1

    raw /dev/raw/raw2 /dev/loop2

    raw /dev/raw/raw3 /dev/loop3

    raw /dev/raw/raw4 /dev/loop4

    raw /dev/raw/raw5 /dev/loop5

     

    chmod 660 /dev/raw/raw1

    chmod 660 /dev/raw/raw2

    chmod 660 /dev/raw/raw3

    chmod 660 /dev/raw/raw4

    chmod 660 /dev/raw/raw5

    chown oracle:dba /dev/raw/raw1

    chown oracle:dba /dev/raw/raw2

    chown oracle:dba /dev/raw/raw3

    chown oracle:dba /dev/raw/raw4

    chown oracle:dba /dev/raw/raw5

     

    ------ 将以下内容添加到文件/etc/rc.local文件中

    /sbin/losetup /dev/loop1 /oracle/asmdisk/disk1

    /sbin/losetup /dev/loop2 /oracle/asmdisk/disk2

    /sbin/losetup /dev/loop3 /oracle/asmdisk/disk3

    /sbin/losetup /dev/loop4 /oracle/asmdisk/disk4

    /sbin/losetup /dev/loop5 /oracle/asmdisk/disk5

     

    raw /dev/raw/raw1 /dev/loop1

    raw /dev/raw/raw2 /dev/loop2

    raw /dev/raw/raw3 /dev/loop3

    raw /dev/raw/raw4 /dev/loop4

    raw /dev/raw/raw5 /dev/loop5

     

    chmod 660 /dev/raw/raw1

    chmod 660 /dev/raw/raw2

    chmod 660 /dev/raw/raw3

    chmod 660 /dev/raw/raw4

    chmod 660 /dev/raw/raw5

    chown oracle:dba /dev/raw/raw1

    chown oracle:dba /dev/raw/raw2

    chown oracle:dba /dev/raw/raw3

    chown oracle:dba /dev/raw/raw4

    chown oracle:dba /dev/raw/raw5

     

     

     

     

    查看结果:

     

    wps757D.tmp 

     

     

    2.2  方法二:直接修改/etc/sysconfig/rawdevices配置rawrhel6之后不支持)

    采用下面的方式来增加磁盘,即直接修改/etc/sysconfig/rawdevices  (root帐户)

    Redhat平台对raw设备的配置在redhat 5之后有了变化。在redhat 5之前,直接配置/etc/sysconfig/rawdevices件,通过/etc/init.d/rawdevices来管理raw设备的启动和关 闭。在Redhat 5之后,原来的raw设备接口已经取消了,redhat 5中通过udev规则进行配置。 要配置,需要编辑/etc/udev/rules.d/60-raw.rules这个文件。

     

    [root@oradb ~]# vi /etc/sysconfig/rawdevices

    [root@oradb ~]# cat /etc/sysconfig/rawdevices --查看增加的内容为raw6,raw7

    /dev/raw/raw2 /dev/sdb6

    /dev/raw/raw/dev/sdb7

     

    chown oracle:dba /dev/raw/raw[6-7]  --修改属主,否则创建磁盘组时提示权限不够

     

     

    [root@rhel5 ~]# chown oracle:dba /dev/raw/raw6

    [root@rhel5 ~]# chown oracle:dba /dev/raw/raw7

    [root@rhel5 ~]# ll /dev/raw/

    total 0

    crw-rw---- 1 oracle dba 162, 1 Dec 30 14:47 raw1

    crw-rw---- 1 oracle dba 162, 2 Dec 30 14:47 raw2

    crw-rw---- 1 oracle dba 162, 3 Dec 30 14:36 raw3

    crw-rw---- 1 oracle dba 162, 4 Dec 30 14:36 raw4

    crw-rw---- 1 oracle dba 162, 5 Dec 30 14:36 raw5

    crw------- 1 oracle dba 162, 6 Dec 30 14:36 raw6

    crw------- 1 oracle dba 162, 7 Dec 30 14:36 raw7

    [root@rhel5 ~]#

     

    重启裸设备服务

    [root@rhel5 ~]#  /sbin/service rawdevices restart

    Assigning devices:

               /dev/raw/raw6  -->   /dev/sdb6

    /dev/raw/raw6: bound to major 8, minor 22

               /dev/raw/raw7  -->   /dev/sdb7

    /dev/raw/raw7: bound to major 8, minor 23

    done

    [root@rhel5 ~]#

     

     

    将下面的内容增加到/etc/rc.local文件(root帐户),重新启动主机,使得属主变为oracle

    chown oracle:dba /dev/raw/raw6

    chown oracle:dba /dev/raw/raw7

    chmod 660 /dev/raw/raw6

    chmod 660 /dev/raw/raw7

     

     

    SQL> select instance_name,status from v$instance;

     

    INSTANCE_NAME    STATUS

    ---------------- ------------

    +ASM             STARTED

     

    SQL> set line 999

    SQL> select name,state,free_mb,required_mirror_free_mb,usable_file_mb from v$asm_diskgroup;

     

    NAME     STATE       FREE_MB REQUIRED_MIRROR_FREE_MB USABLE_FILE_MB

    ----------------- ----------- ---------- ----------------------- --------------

    DATA       MOUNTED   941     0 941

    DG1        MOUNTED 1862     0 931

     

    SQL> create diskgroup DG1 normal redundancy disk '/dev/raw/raw6','/dev/raw/raw7';  --创建磁盘组DG1

     

    SQL> select * from v$fixed_table where name like '%ASM%'; --查看和asm相关的视图

     

    SQL> select name,allocation_unit_size,state,type,

      2  free_mb,required_mirror_free_mb req_mi_fr_mb,usable_file_mb

      3  from v$asm_diskgroup;

     

    NAME            ALLOCATION_UNIT_SIZE STATE       TYPE      FREE_MB REQ_MI_FR_MB USABLE_FILE_MB

    --------------- -------------------- ----------- ------ ---------- ------------ --------------

    DG1                          1048576 MOUNTED     NORMAL        296            0            148

     

    SQL> create diskgroup DG2 normal redundancy disk '/dev/raw/raw3','/dev/raw/raw4';   --创建磁盘组DG2

     

    SQL> select name,state,free_mb,required_mirror_free_mb,usable_file_mb   --查看磁盘组的状态及信息

      2  from v$asm_diskgroup;

     

    NAME            STATE          FREE_MB REQUIRED_MIRROR_FREE_MB USABLE_FILE_MB

    --------------- ----------- ---------- ----------------------- --------------

    DG1             MOUNTED            296                       0            148

    DG2             MOUNTED            296                       0            148

     

    SQL> select disk_number,total_mb,free_mb from v$asm_disk;

     

    DISK_NUMBER   TOTAL_MB    FREE_MB

    ----------- ---------- ----------

      1        199        148

      0        199        148

      1        199        148

      0        199        148

     

    重新启动Linux 主机后

    SQL> startup    --重新启动ASM实例,收到了磁盘组insufficient信息

    ASM instance started

    ORA-15032: not all alterations performed

    ORA-15063: ASM discovered an insufficient number of disks for diskgroup "DG2"

    ORA-15063: ASM discovered an insufficient number of disks for diskgroup "DG1"

     

    SQL> ho ls -hlt /dev/raw        # LINUX主机重新启动之后属主性质已发生变化

    total 0

    crw------- 1 root root 162, 4 Nov 10 20:28 raw4

    crw------- 1 root root 162, 3 Nov 10 20:28 raw3

    crw------- 1 root root 162, 2 Nov 10 20:28 raw2

    crw------- 1 root root 162, 1 Nov 10 20:28 raw1

     

    原因:原始设备在引导时会重新映射。默认情况下,在引导时原始设备的拥有者将更改为root 用户

    将下面的内容增加到/etc/rc.local文件(root帐户),重新启动主机,使得属主变为oracle,则不再出现类似的提示

    chown oracle:dba /dev/raw/raw1

    chown oracle:dba /dev/raw/raw2

    chown oracle:dba /dev/raw/raw3

    chown oracle:dba /dev/raw/raw4

    chmod 660 /dev/raw/raw1

    chmod 660 /dev/raw/raw2

    chmod 660 /dev/raw/raw3

    chmod 660 /dev/raw/raw4

     

    SQL> drop diskgroup dg1;   --将刚才创建的两个磁盘组删除

     

    SQL> drop diskgroup dg2;

     

    使用root帐户清除/etc/udev/rules.d/60-raw.rules /etc/sysconfig/rawdevices  以及/etc/rc.local刚刚增加的记录

    便于下面使用asmlib来创建asm磁盘

    2.3  方法三:Udev方式

    又可以分为2种方式,采用uuid来绑定或采用raw来绑定,采用uuid需要添加多块磁盘,而采用raw方式不需要添加多个磁盘。

    2.3.1  udev下的方法一:uuid方式

    1、 RedHat 5 UDEV SCSI Rules配置

    1 获取需要绑定为ASM Disk的磁盘uuid

    # /sbin/scsi_id -g -u -s /block/sdb

    SATA_VBOX_HARDDISK_VBd306dbe0-df3367e3_

     

    2 新建/etc/udev/rules.d/99-oracle-asmdevices.rules,增加以下内容

    KERNEL=="sd*1", BUS=="scsi", PROGRAM=="/sbin/scsi_id -g -u -s /block/$parent", RESULT=="SATA_VBOX_HARDDISK_VBd306dbe0-df3367e3_", NAME="asm-disk1", OWNER="oracle", GROUP="dba", MODE="0660"

     

    3 重启服务:

    [root@rac1 ~]# start_udev

    Starting udev:         [  OK  ]

     

    2、 RedHat 6 UDEV SCSI Rules

    1 编辑/etc/scsi_id.config文件,如果该文件不存在,则创建该文件,添加如下行:

    options=--whitelisted --replace-whitespace

    备注:在我的测试中,此步骤可以省略

    2 获取需要绑定为ASM Disk的磁盘uuid,比如我们要使用/dev/sdc作为ASM磁盘,那么:

    # scsi_id --whitelisted --replace-whitespace --device=/dev/sdc

    1ATA_VBOX_HARDDISK_VB36a9e548-1838194a

    # /sbin/scsi_id -g -u -d /dev/sdb

    SATA_VBOX_HARDDISK_VBd306dbe0-df3367e3_

     

    3 新建/etc/udev/rules.d/99-oracle-asmdevices.rules,增加以下内容

    KERNEL=="sd*1", BUS=="scsi", PROGRAM=="/sbin/scsi_id -g -u -d /dev/$parent", RESULT=="SATA_VBOX_HARDDISK_VBd306dbe0-df3367e3_", NAME="asm-disk1", OWNER="oracle", GROUP="dba", MODE="0660"

    KERNEL=="dm-*", PROGRAM="scsi_id --page=0x83 --whitelisted --device=/dev/%k",RESULT=="3600c0ff00015c7b70253c05001000000", OWNER:="grid", GROUP:="oinstall"

    KERNEL=="dm-*", PROGRAM="scsi_id --page=0x83 --whitelisted --device=/dev/%k",RESULT=="3600c0ff00015c7b72553c05001000000", OWNER:="grid", GROUP:="oinstall"

    KERNEL=="dm-*", PROGRAM="scsi_id --page=0x83 --whitelisted --device=/dev/%k",RESULT=="3600c0ff00015c7b77165c05001000000", OWNER:="grid", GROUP:="oinstall"

    KERNEL=="dm-*", PROGRAM="scsi_id --page=0x83 --whitelisted --device=/dev/%k",RESULT=="3600c0ff00015c7b73753c05001000000", OWNER:="grid", GROUP:="oinstall"

    KERNEL=="dm-*", PROGRAM="scsi_id --page=0x83 --whitelisted --device=/dev/%k",RESULT=="3600c0ff00015c7b78065c05001000000", OWNER:="grid", GROUP:="oinstall"

    KERNEL=="sd*",BUS=="scsi",PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="3600c0ff00015c7b70253c05001000000",NAME="asmocrvote01", OWNER="grid", GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*",BUS=="scsi",PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="3600c0ff00015c7b77165c05001000000",NAME="asmdata01", OWNER="grid", GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*",BUS=="scsi",PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="3600c0ff00015c7b73753c05001000000",NAME="asmdata02", OWNER="grid", GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*",BUS=="scsi",PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="3600c0ff00015c7b72553c05001000000",NAME="asmfra01", OWNER="grid", GROUP="asmadmin", MODE="0660"

     

    4 重启服务:

    [root@rac1 ~]# start_udev

    Starting udev:         [  OK  ]

     

     

     

    一、 配置 udev 绑定的 scsi_id

    注意以下两点:

    首先切换到root用户下:

    ① 不同的操作系统,scsi_id 命令的位置不同。

    [root@rhel5 ~]#  cat /etc/issue

    Red Hat Enterprise Linux Server release 5.5 (Tikanga)

    Kernel \r on an \m

     

    [root@rhel5 ~]#  which scsi_id

    /sbin/scsi_id

    [root@rhel5 ~]#

    ② 编辑 /etc/scsi_id.config 文件,如果该文件不存在,则创建该文件并添加如下行:

    [root@localhost ~]# vi /etc/scsi_id.config

    options=--whitelisted --replace-whitespace

    [root@localhost ~]#

    5.3. 如果是使用 VMware 虚拟机,直接输入 scsi_id 命令可能无法获取 id,需修改 VMware 文件参数,这一步如果在添加磁盘的时候做过这一步的话就可以跳过了,直接获取uuid即可 

    [root@localhost ~]# scsi_id --whitelisted --replace-whitespace --device=/dev/sdb

    [root@localhost ~]# scsi_id --whitelisted --replace-whitespace --device=/dev/sdc

    D:\VMs\Oracle Database 11gR2\Oracle Database 11gR2.vmx

    使用文本编辑器编辑该文件,在尾部新增一行参数:

    disk.EnableUUID="TRUE"

    保存文件,重新启动虚拟机。这里注意修改文件的时候一定要在关机的状态下修改,或者   scsi_id -g -u /dev/sdc 来获得uuid-g -u参数在rhel6以后已经不用了

    [root@localhost share]#  scsi_id --whitelisted --replace-whitespace --device=/dev/sdb

    36000c29fbe57659626ee89b4fba07616

    [root@localhost share]#  scsi_id --whitelisted --replace-whitespace --device=/dev/sdc

    36000c29384cde894e087e5f0fcaa80f4

    [root@localhost share]#  scsi_id --whitelisted --replace-whitespace --device=/dev/sdd

    36000c29022aee23728231ed9b1f9743d

    [root@localhost share]#  scsi_id --whitelisted --replace-whitespace --device=/dev/sde

    36000c2938f431664218d1d2632ff1352

    二、 创建并配置 udev rules 文件

    [root@localhost ~]# vi /etc/udev/rules.d/99-oracle-asmdevices.rules

    KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="36000c29fe0fc917d7e9982742a28ce7c", NAME="asm-diskb", OWNER="grid",GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="36000c293ffc0900fd932348de4b6baf8", NAME="asm-diskc", OWNER="grid",GROUP="asmadmin", MODE="0660"

     

    根据步骤 5 获取的 ID 修改 RESULT 值

    这里需要注意,一个KERNEL就是一行,不能换行的,我之前就是犯了这个错误的

    wps758E.tmp 

    添加4块硬盘:

    KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="36000c29346c1344ffb26f0e5603d519e", NAME="asm-diskb", OWNER="grid",GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="36000c29d08ee059a345571054517cd03", NAME="asm-diskc", OWNER="grid",GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="36000c295037a910bfb765af8f400aa07", NAME="asm-diskd", OWNER="grid",GROUP="asmadmin", MODE="0660"

    KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM=="/sbin/scsi_id --whitelisted --replace-whitespace --device=/dev/$name",RESULT=="36000c2982bda048f642acd3c429ec983", NAME="asm-diske", OWNER="grid",GROUP="asmadmin", MODE="0660"

    wps758F.tmp 

     

     

    注意:这里的GROUP="asmadmin", 最好修改成 GROUP="asmdba",不然最后可能用dbca创建数据库实例的时候找不见磁盘组。

    wps7590.tmp 

     

    三、 udevadm进行测试

    用udevadm进行测试,注意udevadm命令不接受/dev/sdc这样的挂载设备名,必须是使用/sys/block/sdc这样的原始设备名。

    udevadm test /sys/block/sdcudevadm info --query=all --path=/sys/block/sdcudevadm info --query=all --name=asm-disk1

    在显示中,有类似如下输出,表示测试正确,/dev/sdc设备在udev启动以后将会绑定为/dev/asm-disk1:

    udevadm_test: UDEV_LOG=6udevadm_test: DEVPATH=/devices/pci0000:00/0000:00:0d.0/host4/target4:0:0/4:0:0:0/block/sdcudevadm_test: MAJOR=8udevadm_test: MINOR=32udevadm_test: DEVNAME=/dev/asm-disk1udevadm_test: DEVTYPE=diskudevadm_test: ACTION=addudevadm_test: SUBSYSTEM=block

     

    四、 添加完成后,重启 udev,不同 Linux 发行版本重启方式不一样。

    该步骤慢一点,大约可能需要30秒左右吧,等等等等。。。。。。

    [root@localhost ~]# start_udev

    Starting udev:                                             [  OK  ]

    [root@localhost ~]#

    五、 查看绑定的 asm,如果此时还是看不到 asm disk,请重启操作系统后再查看。

    [root@localhost ~]# ll /dev/asm*

    brw-rw---- 1 grid asmadmin 8, 17 Oct 17 14:26 /dev/asm-diskb

    brw-rw---- 1 grid asmadmin 8, 33 Oct 17 14:26 /dev/asm-diskc

    wps7591.tmp 

     

     

    2.3.2  udev下的方法二:raw方式

     

    a.配置裸设备映射,修改/etc/udev/rules.d/60-raw.rules文件

    使用root帐户修改/etc/udev/rules.d/60-raw.rules 按如下方式添加磁盘

    [root@oradb ~]# cat /etc/udev/rules.d/60-raw.rules       --查看添加的内容

    ACTION=="add", KERNEL=="/dev/sdb8", RUN+="/bin/raw /dev/raw/raw8 %N"

    ACTION=="add", ENV{MAJOR}=="8", ENV{MINOR}=="24", RUN+="/bin/raw /dev/raw/raw8 %M %m"

    ACTION=="add", KERNEL=="/dev/sdb9", RUN+="/bin/raw /dev/raw/raw9 %N"

    ACTION=="add", ENV{MAJOR}=="8", ENV{MINOR}=="25", RUN+="/bin/raw /dev/raw/raw9 %M %m"

     

     

    ACTION=="add", KERNEL=="raw8", OWNER="grid", GROUP="asmadmin", MODE="0660"

    ACTION=="add", KERNEL=="raw9", OWNER="grid", GROUP="asmadmin", MODE="0660"

     

     

    [root@oradb ~]# start_udev    #重启udev服务

    Starting udev: [  OK  ]

     

    [root@rhel5 ~]# raw -qa

    /dev/raw/raw1: bound to major 7, minor 1

    /dev/raw/raw2: bound to major 7, minor 2

    /dev/raw/raw3: bound to major 7, minor 3

    /dev/raw/raw4: bound to major 7, minor 4

    /dev/raw/raw5: bound to major 7, minor 5

    /dev/raw/raw6: bound to major 8, minor 22

    /dev/raw/raw7: bound to major 8, minor 23

    /dev/raw/raw8: bound to major 8, minor 24

    /dev/raw/raw9: bound to major 8, minor 25

    [root@rhel5 ~]# ll /dev/raw*

    crw------- 1 root root 162, 0 Dec 30 14:36 /dev/rawctl

     

    /dev/raw:

    total 0

    crw------- 1 oracle dba      162, 1 Dec 30 15:29 raw1

    crw------- 1 oracle dba      162, 2 Dec 30 15:29 raw2

    crw------- 1 oracle dba      162, 3 Dec 30 14:36 raw3

    crw------- 1 oracle dba      162, 4 Dec 30 14:36 raw4

    crw------- 1 oracle dba      162, 5 Dec 30 14:36 raw5

    crw------- 1 oracle dba      162, 6 Dec 30 15:29 raw6

    crw------- 1 oracle dba      162, 7 Dec 30 15:29 raw7

    crw-rw---- 1 grid   asmadmin 162, 8 Dec 30 15:28 raw8

    crw-rw---- 1 grid   asmadmin 162, 9 Dec 30 15:28 raw9

    [root@rhel5 ~]#

     

     

    SQL> create diskgroup DG2 external redundancy disk '/dev/raw/raw8';

     

    Diskgroup created.

     

    一、 裸设备概述 

    裸设备:也叫裸分区(原始分区),是一种没有经过格式化,不被Unix/Linux通过文件系统来读取的特殊字符设备。裸设备可以绑定一个分区,也可以绑定一个磁盘。
    字符设备:对字符设备的读写不需要通过OS的buffer。它不可被文件系统mount。
    块设备:对块设备的读写需要通过OS的buffer,它可以被mount到文件系统中。

    这个与linux的版本相关,在旧版本中,最多只可以有256个裸设备,Linux 4下做多可以绑定81Array2个裸设备。但是在linux下,最多只能有255个分区,所以,如果用裸设备绑定分区,最多只能绑定255个裸设备。如果是用lvm,则没有这个限制。

    Linux下单个磁盘最多可以有15个分区。3个主分区 + 1个扩展分区 + 11个逻辑分区。
    建议的分区方法是:先分3个主分区,第四个分区为扩展分区,然后在扩展分区中再分成11个逻辑分区。
    注意,裸设备不要绑定在扩展分区上。 
    linux下如果需要使用裸设备,则需要手工进行绑定,unix下则不用。

    因为Unix中每一个块设备都会有一个对应的字符设备用于非缓存(unbuffered)I/O,这就是他对应的裸设备了。而Linux中rawio的则 实现了一套非绑定(unbound)的裸设备/dev/rawN或者/dev/raw/rawN和一个控制设备/dev/rawct用来把他们绑定到块设 备上。所以当需要使用一个裸设备的时候,就需要把他和一个真实存在的块设备对应起来,这一个步骤实际上就是完成了Unix里的自动对应一个非缓存字符设备。

    majorminor device number 

    在unix/linux系统中,一切都是文件。所有硬盘、软盘、键盘等设备都用文件来代表,对应 着/dev下面的文件。对于应用程序来说,可以像对待普通文件一样打开,关闭、读写这些设备文件。但是这种文件名,比如/dev/sda、/dev /raw/raw1都是用户空间名称,OS Kernel根本不知道这个名称指的是什么。在内核空间是通过major、minor device number 来区分设备的。
    major device number可以看作是设备驱动程序,被同一设备驱动程序管理的设备有相同的major device number.这个数字实际是Kernel中device driver table 的索引,这个表保存着不同设备驱动程序。 而minor device number用来代表被访问的具体设备。也就是说Kernel根据major device number 找到设备驱动程序,然后再从minor device number 获得设备位置等属性。所有这些major device number 是已经预先分配好的。详细信息可以从http://www.lanana.org/docs/device-list/devices-2.6+.txt 查看。比如裸设备是162,scsi块设备是8
    /etc/udev/rules.d/60-raw.rules
     

    Redhat平台对raw设备的配置在redhat 5之后有了变化。在redhat 5之前,直接配置/etc/sysconfig/rawdevices件,通过/etc/init.d/rawdevices来管理raw设备的启动和关 闭。在Redhat 5之后,原来的raw设备接口已经取消了,redhat 5中通过udev规则进行配置。 要配置,需要编辑/etc/udev/rules.d/60-raw.rules这个文件。

    cat /etc/udev/rules.d/60-raw.rules 
    # Enter raw device bindings here.
    #
    # An example would be:
    #   ACTION=="add", KERNEL=="sda", RUN+="/bin/raw /dev/raw/raw1 %N"
    # to bind /dev/raw/raw1 to /dev/sda, or
    #   ACTION=="add", ENV{MAJOR}=="8", ENV{MINOR}=="1", RUN+="/bin/raw /dev/raw/raw2 %M %m"

    # to bind /dev/raw/raw2 to the device with major 8, minor 1. 

    其中
    ACTION=="add", KERNEL="", RUN+="raw /dev/raw/rawX %N" 

    配置设备名称,用你需要绑定的设备名称替换 (如:/dev/sda1),X为裸设备号
    主/次号码:
    ACTION=="add", ENV{MAJOR}="A", ENV{MINOR}="B", RUN+="raw /dev/raw/rawX %M %m" 

    "A" 和 "B" 是设备的主/次号码,X是系统使用的raw设备号码。

    个人对redhat管理raw的过程理解为: 在redhat 5中,是通过udev来管理raw设备的,而udev是通过 MAJOR和MINOR来识别 raw设备 。 故需要将设备号和裸设备号进行绑定,而主设备号和次设备号可以自行指定或者由系统自动分配。 根据red hat的官方文档中关于raw.rule的示例中说KERNEL==..或ENV{MAJOR}...只需要任意配置一个就可以,但有些网友经过试验,验证必须二者同时配置才可以。

    配置 /etc/udev/rules.d/60-raw.rules文件

    查看磁盘分区情况

    # fdisk  -l /dev/sdb 


    Disk /dev/sdb: 4880 MB, 4880072704 bytes
    255 heads, 63 sectors/track, 593 cylinders
    Units = cylinders of 16065 * 512 = 8225280 bytes

       Device Boot      Start         End      Blocks   Id  System
    /dev/sdb1               1          25      200781   83  Linux
    /dev/sdb2              26          50      200812+  83  Linux
    配置 /etc/udev/rules.d/60-raw.rules文件 
    # grep -v ^# /etc/udev/rules.d/60-raw.rules 
    ACTION=="add", KERNEL=="sdb1", RUN+="/bin/raw /dev/raw/raw1 %N"
    ACTION=="add", KERNEL=="sdb2", RUN+="/bin/raw /dev/raw/raw2 %N"
    ACTION=="add", ENV{MAJOR}=="3", ENV{MINOR}=="2", RUN+="/bin/raw /dev/raw/raw1 %M %m"

    ACTION=="add", ENV{MAJOR}=="7", ENV{MINOR}=="2", RUN+="/bin/raw /dev/raw/raw2 %M %m"

    启动raw设备

    # start_udev 
    Starting udev:                                             [  OK  ] 

    查看配置情况
    # raw -qa
    /dev/raw/raw1:  bound to major 8, minor 17

    /dev/raw/raw2:  bound to major 8, minor 18 

    这里笔者不清楚为什么主设备号和复设备号并不和我在 /etc/udev/rules.d/60-raw.rules指定的一样,望了解的读者告知,系统内核信息如下
    # uname  -a
    Linux rac1 2.6.18-164.el5 #1 SMP Tue Aug 18 15:51:54 EDT 2009 i686 i686 i386 GNU/Linux
    # cat /etc/redhat-release 

    Red Hat Enterprise Linux Server release 5.4 (Tikanga) 

    可以通过如下方式指定 主设备号和复设备号
    # raw /dev/raw/raw1 1 1
    /dev/raw/raw1:  bound to major 1, minor 1 

    raw /dev/raw/raw[n] /dev/xxx
    其中n的范围是0-8191。raw目录不存在的话会被自动创建。执行这个命令,就会在/dev/raw下生成一个对应的raw[n]文件用命令方式绑定裸设备在系统重启后会失效。

    删除裸设备 
    # raw /dev/raw/raw2 0 0
    /dev/raw/raw2:  bound to major 0, minor 0 

    # raw -qa
    /dev/raw/raw1:  bound to major 1, minor 1 

    以上设置必须同时修改 /etc/udev/rules.d/60-raw.rules才能保证重启后生效,否则重启后系统会重新读取 /etc/udev/rules.d/60-raw.rules 

    如需设置raw设备的用户和权限信息,可在/etc/udev/rules.d/60-raw.rules文件里添加如下信息:
    ACTION=="add", KERNEL=="raw1", OWNER="dave", GROUP="tianlesoftware", MODE="660" 
    如果有多个raw设备,可以写成:
    ACTION=="add", KERNEL=="raw[1-4]", OWNER="dave", GROUP="tianlesoftware", MODE="660" 
    #chown oracle:oinstall /dev/raw/raw[1-4]
    #chmod 775 /dev/raw/raw[1-4] 

    注意:在内核2.6.9-89.5AXS2之前使用/etc/sysconfig/rawdevices和/etc/udev/permissions.d/50-udev.permissions进行raw设备的配置和权限管理。在内核 2.6.18-128.7AXS3以后则使用了本文介绍的 /etc/udev/rules.d/60-raw.rules进行raw设备的管理 

    确定裸设备的大小 
    比较笨的办法是,找出看裸设备对应的是那个实际的块设备,然后用fdisk -l /dev/[h,s]dXN看那个块设备的大小就好了。比较简单的办法是用blockdev命令来计算,如:
    #blockdev --getsize /dev/raw/raw1
    11718750 
    11718750表示有多少OS BLIOCK。

    一般一个OS BLOCK大小是512字节,所以11718750*512/1024/1024= 5722(m) 就是裸设备的大小。

    使用裸设备作为oracle的数据文件的注意事项 
    1、一个裸设备只能放置一个数据文件
    2、数据文件的大小不能超过裸设备的大小
    如果是日志文件,则裸设备最大可用大小=裸设备对应分区大小 - 1 * 512 (保留一个redo lock)
    如果是数据文件,则裸设备最大可用大小=裸设备对应分区大小 - 2 * db_block_size(保留两个block)
    为了简单起见,对所有的文件设置称比裸设备小1M即可。
    3、数据文件最好不要设置称自动扩展,如果设置称自动扩展,一定要把maxsize设置设置为比裸设备小

    4、linux下oracle不能直接把逻辑卷作为裸设备,也要进行绑定。unix下就不需要。

     

     

    2.4  方法四:Asmlib方式

    asmlib是oracle提供的软件,不支持rhel6 了,具体参考:http://blog.itpub.net/26736162/viewspace-1205206/

     

    2.4.1  系统版本号

    [root@rhel5 ~]# uname -rm

    2.6.18-194.el5 x86_64

    一.2.4.2  Oracle asmlib下载

    1. 在官网(www.oracle.com)的搜索中输入 asmlib 搜索后可以直接找到

    wps75A2.tmp 

    http://www.oracle.com/technetwork/server-storage/linux/asmlib/index-101839.html

    wps75A3.tmp 

    wps75A4.tmp 

    选择对应的版本 rhel5

     

     

    去下载如下asm的三个rpm软件包(本机器下载的是标红的三个软件包,安装包一定要下载正确的版本否则导致后续配置无法进行),注意32位和64的差别

     

    wps75B4.tmp 

    wps75B5.tmp 

    Intel EM64T (x86_64) Architecture

    Library and Tools

    oracleasm-support-2.1.8-1.el5.x86_64.rpm

    oracleasmlib-2.0.4-1.el5.x86_64.rpm

    Drivers for kernel 2.6.18-194.el5

    oracleasm-2.6.18-194.el5xen-2.0.5-1.el5.x86_64.rpm

    oracleasm-2.6.18-194.el5debug-2.0.5-1.el5.x86_64.rpm

    oracleasm-2.6.18-194.el5-debuginfo-2.0.5-1.el5.x86_64.rpm

    oracleasm-2.6.18-194.el5-2.0.5-1.el5.x86_64.rpm

     

    2.4.3  上传并安装上述的三个rpm软件包:

    [root@rhel5 tmp]# ll oracleasm*

    -rw-r--r-- 1 root root 137486 Dec 30 10:28 oracleasm-2.6.18-194.el5-2.0.5-1.el5.x86_64.rpm

    -rw-r--r-- 1 root root  14176 Jun  2  2013 oracleasmlib-2.0.4-1.el5.x86_64.rpm

    -rw-r--r-- 1 root root  90225 Jun  2  2013 oracleasm-support-2.1.8-1.el5.x86_64.rpm

    [root@rhel5 tmp]# rpm -ivh oracleasm*.rpm

    warning: oracleasm-2.6.18-194.el5-2.0.5-1.el5.x86_64.rpm: Header V3 DSA signature: NOKEY, key ID 1e5e0159

    Preparing...                  ########################################### [100%]

       1:oracleasm-support       ########################################### [ 33%]

       2:oracleasm-2.6.18-194.el########################################### [ 67%]

       3:oracleasmlib             ########################################### [100%]

    [root@rhel5 tmp]#

    验证安装的包

    [root@rhel5 ~]#  rpm -qa | grep asm

    oracleasm-2.6.18-194.el5-2.0.5-1.el5

    oracleasmlib-2.0.4-1.el5

    oracleasm-support-2.1.8-1.el5

    [root@rhel5 ~]#

    2.4.4   配置ASM

     

    [root@rhel5 tmp]# /etc/init.d/oracleasm configure

    Configuring the Oracle ASM library driver.

     

    This will configure the on-boot properties of the Oracle ASM library

    driver.  The following questions will determine whether the driver is

    loaded on boot and what permissions it will have.  The current values

    will be shown in brackets ('[]').  Hitting without typing an

    answer will keep that current value.  Ctrl-C will abort.

     

    Default user to own the driver interface []: grid

    Default group to own the driver interface []: asmadmin

    Start Oracle ASM library driver on boot (y/n) [n]: y

    Scan for Oracle ASM disks on boot (y/n) [y]: y

    Writing Oracle ASM library driver configuration: done

    Initializing the Oracle ASMLib driver:                     [  OK  ]

    Scanning the system for Oracle ASMLib disks:               [  OK  ]

    [root@rhel5 tmp]#

     

    如果配置错误,可以重新配置:

    [root@rhel5 tmp]#  /etc/init.d/oracleasm configure

    Configuring the Oracle ASM library driver.

     

    This will configure the on-boot properties of the Oracle ASM library

    driver.  The following questions will determine whether the driver is

    loaded on boot and what permissions it will have.  The current values

    will be shown in brackets ('[]').  Hitting without typing an

    answer will keep that current value.  Ctrl-C will abort.

     

    Default user to own the driver interface [oracle]: grid

    Default group to own the driver interface [oinstall]: asmadmin

    Start Oracle ASM library driver on boot (y/n) [y]: y

    Scan for Oracle ASM disks on boot (y/n) [y]: y

    Writing Oracle ASM library driver configuration: done

    Initializing the Oracle ASMLib driver:                     [  OK  ]

    Scanning the system for Oracle ASMLib disks:               [  OK  ]

    [root@rhel5 tmp]#

     

    2.4.5  系统添加磁盘

    在Vmware分配几个空闲的磁盘用于创建ASM磁盘,建议使用不同的磁盘控制器

    下面使用个磁盘来组建ASM磁盘组,分别为sdd,sde,sdf,sdg

    分别对个磁盘进行分区,列出sdd的分区样例,其余如法炮制

    [root@oradb ~]# fdisk /dev/sdd

     

    Command (m for help): n

    Command action

       e   extended

       p   primary partition (1-4)

    p

    Partition number (1-4): 1

    First cylinder (1-261, default 1):

    Using default value 1

    Last cylinder or +size or +sizeM or +sizeK (1-261, default 261):

    Using default value 261

     

    Command (m for help): w

    The partition table has been altered!

     

    Calling ioctl() to re-read partition table.

    Syncing disks.

    系统添加一块硬盘sdb,然后进行分区 fdisk /dev/sdb ,分区之后:

    [root@rhel5 ~]# ll /dev/sdb*

    brw-r----- 1 root disk 8, 16 Dec 30 10:54 /dev/sdb

    brw-r----- 1 root disk 8, 17 Dec 30 10:59 /dev/sdb1

    brw-r----- 1 root disk 8, 18 Dec 30 11:00 /dev/sdb2

    brw-r----- 1 root disk 8, 19 Dec 30 11:00 /dev/sdb3

    brw-r----- 1 root disk 8, 20 Dec 30 11:00 /dev/sdb4

    [root@rhel5 ~]#

     

     

    [root@rhel5 ~]# /etc/init.d/oracleasm createdisk VOL1 /dev/sdb1

    Marking disk "VOL1" as an ASM disk:                        [  OK  ]

    [root@rhel5 ~]# /etc/init.d/oracleasm createdisk VOL2 /dev/sdb2

    Marking disk "VOL2" as an ASM disk:                        [  OK  ]

    [root@rhel5 ~]# /etc/init.d/oracleasm createdisk VOL3 /dev/sdb3

    Marking disk "VOL3" as an ASM disk:                        [  OK  ]

    [root@rhel5 ~]# /etc/init.d/oracleasm createdisk VOL4 /dev/sdb4

    Marking disk "VOL4" as an ASM disk:                        [  OK  ]

    [root@rhel5 ~]#

     

    [root@rhel5 oracle]# ll /dev/oracleasm/disks/*

    brw-rw---- 1 grid asmadmin 8, 17 Dec 30 10:59 /dev/oracleasm/disks/VOL1

    brw-rw---- 1 grid asmadmin 8, 18 Dec 30 11:00 /dev/oracleasm/disks/VOL2

    brw-rw---- 1 grid asmadmin 8, 19 Dec 30 11:00 /dev/oracleasm/disks/VOL3

    brw-rw---- 1 grid asmadmin 8, 20 Dec 30 11:00 /dev/oracleasm/disks/VOL4

    [root@rhel5 ~]# /etc/init.d/oracleasm listdisks

    VOL1

    VOL2

    VOL3

    VOL4

    [root@rhel5 ~]#

    [root@rhel5 ~]# /etc/init.d/oracleasm deletedisk VOL4

    Removing ASM disk "VOL4":                                  [  OK  ]

    [root@rhel5 ~]#

     

    2.4.6  grid软件安装完毕后配置asm_diskstring 路径

    [grid@rhel5 ~]$ sqlplus / as sysasm

     

    SQL*Plus: Release 11.2.0.3.0 Production on Tue Dec 30 12:09:19 2014

     

    Copyright (c) 1982, 2011, Oracle.  All rights reserved.

     

     

    Connected to:

    Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production

    With the Automatic Storage Management option

     

    SQL> alter system set asm_diskstring='/dev/raw/*','/dev/oracleasm/disks/VOL*';

     

    System altered.

     

    SQL> show parameter asm_diskstring

     

    NAME      TYPE VALUE

    ------------------------------------ ----------- ------------------------------

    asm_diskstring      string /dev/raw/*, /dev/oracleasm/dis

    ks/VOL*

    SQL>set line 9999

    SQL> SELECT a.group_number, disk_number,mount_status, a.name, path FROM v$asm_disk a;

     

    GROUP_NUMBER DISK_NUMBER MOUNT_S NAME PATH

    ------------ ----------- ------- ------------------------------

       0        0 CLOSED /dev/raw/raw5

       0        1 CLOSED /dev/raw/raw4

       0        2 CLOSED /dev/raw/raw3

       0        3 CLOSED /dev/raw/raw2

       0        4 CLOSED /dev/oracleasm/disks/VOL4

       0        5 CLOSED /dev/oracleasm/disks/VOL3

       0        6 CLOSED /dev/oracleasm/disks/VOL2

       0        7 CLOSED /dev/oracleasm/disks/VOL1

       1        0 CACHED  DATA_0000 /dev/raw/raw1

     

    9 rows selected.

     

    SQL>

     

    磁盘搜索路径问题

    SQL> create diskgroup DG1 normal redundancy disk 'ORCL:VOL1','ORCL:VOL2';

    create diskgroup DG1 normal redundancy disk 'ORCL:VOL1','ORCL:VOL2'

    *

    ERROR at line 1:

    ORA-15018: diskgroup cannot be created

    ORA-15031: disk specification 'ORCL:VOL2' matches no disks

    ORA-15031: disk specification 'ORCL:VOL1' matches no disks

     

    使用oraclasm创建磁盘后,缺省会在/dev/oracleasm/disks目录下添加刚刚创建的磁盘映射

    修改asm_diskstring修改路径之后再次创建即可

    alter system set asm_diskstring='/dev/oracleasm/disks/VOL*'

     

    2.4.7  测试

    SQL> drop diskgroup dg1;

     

    Diskgroup dropped.

     

    SQL> select name,state,free_mb,required_mirror_free_mb,usable_file_mb from v$asm_diskgroup;

     

    NAME        STATE       FREE_MB REQUIRED_MIRROR_FREE_MB USABLE_FILE_MB

    ------------------------------ ----------- ---------- ----------------------- --------------

    DATA        MOUNTED   941     0 941

     

    SQL> create diskgroup DG1 normal redundancy

      2  failgroup FG1 disk '/dev/oracleasm/disks/VOL1' name VOL1

      3  failgroup FG2 disk '/dev/raw/raw2' name VOL2;

     

    Diskgroup created.

     

    SQL> select name,state,free_mb,required_mirror_free_mb,usable_file_mb from v$asm_diskgroup;

     

    NAME        STATE       FREE_MB REQUIRED_MIRROR_FREE_MB USABLE_FILE_MB

    ------------------------------ ----------- ---------- ----------------------- --------------

    DATA        MOUNTED   941     0 941

    DG1       MOUNTED 1862     0 931

     

    SQL>

     

    2.4.8  日志

    tail -f /var/log/oracleasm

     

    2.4.9  报错:

    一、 ASM: Device is already labeled for ASM disk

     

    当一块磁盘或分区已经被一个DataGroup用过,此时想用这块磁盘或分区重新生成Asmlib生成Oracle磁盘设备时即会报标题的错。

    [@more@]

    类似如下:

     

    oracle@vvfs$ /etc/init.d/oracleasm createdisk VOL1 /dev/sda1

    Marking disk "/dev/sda1" as an ASM disk: asmtool:

    Device "/dev/sda1" is already labeled for ASM disk "" [FAILED]

     

    oracle@vvfs$ /etc/init.d/oracleasm deletedisk VOL1

    Removing ASM disk "VOL1" [FAILED]

     

    解决问题其实很简单,把磁盘头清一下就可以了:

     

    dd if=/dev/zero of= bs=1024 count=100

     

    现在操作就该正常了:

     

    oracle@vvfs$ dd if=/dev/zero of=/dev/sda1 bs=1024 count=100

    100+0 records in

    100+0 records out

     

    oracle@vvfs$ /etc/init.d/oracleasm createdisk VOL /dev/sda1

    Marking disk "/dev/sda1" as an ASM disk: [ OK ]

     

    oracle@vvfs$

     总结

    本文提到的4种方法各有优缺点,其中方法二和方法四是rhel6之前的版本支持,方法三需要添加多块磁盘,方法一最直接,如若网友有兴趣,可以列个表格来比对他们的区别。

     

     



    About Me

    ...............................................................................................................................

    ● 本文作者:小麦苗,只专注于数据库的技术,更注重技术的运用

    ● 本文在itpub(http://blog.itpub.net/26736162)、博客园(http://www.cnblogs.com/lhrbest)和个人微信公众号(xiaomaimiaolhr)上有同步更新

    ● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

    ● 本文博客园地址:http://www.cnblogs.com/lhrbest

    ● 本文pdf版及小麦苗云盘地址:http://blog.itpub.net/26736162/viewspace-1624453/

    ● 数据库笔试面试题库及解答:http://blog.itpub.net/26736162/viewspace-2134706/

    ● QQ群:230161599     微信群:私聊

    ● 联系我请加QQ好友(646634621),注明添加缘由

    ● 于 2017-07-01 09:00 ~ 2017-07-31 22:00 在魔都完成

    ● 文章内容来源于小麦苗的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解

    ● 版权所有,欢迎分享本文,转载请保留出处

    ...............................................................................................................................

    拿起手机使用微信客户端扫描下边的左边图片来关注小麦苗的微信公众号:xiaomaimiaolhr,扫描右边的二维码加入小麦苗的QQ群,学习最实用的数据库技术。

     

    DBA笔试面试讲解 欢迎与我联系

     

    展开全文
  • GCC在C语言中内嵌汇编 asm __volatile__

    万次阅读 多人点赞 2012-11-26 22:20:05
    在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式... "__asm__"表示后面的代码为内嵌汇编,"as

     

    在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C 变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作。

    1、简单的内嵌汇编
    例:

           __asm__ __volatile__("hlt"); "__asm__"表示后面的代码为内嵌汇编,"asm"是"__asm__"的别名。"__volatile__"表示编译器不要优化代码,后面的指令 保留原样,"volatile"是它的别名。括号里面是汇编指令。

    2、内嵌汇编举例
       使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。例如在下面的汇编语句:
       

    __asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));


    "movl %1,%0"是指令模板;"%0"和"%1"代表指令的操作数,称为占位符,内嵌汇编靠它们将C 语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:"result"和"input",他们按照出现的顺序分 别与指令操作数"%0","%1"对应;注意对应顺序:第一个C 表达式对应"%0";第二个表达式对应"%1",依次类推,操作数至多有10 个,分别用"%0","%1"...."%9"表示。在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。 "result"前面的限制字符串是"=r",其中"="表示"result"是输出操作数,"r" 表示需要将"result"与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是"result"本身,当然指令执行 完后需要将寄存器中的值存入变量"result",从表面上看好像是指令直接对"result"进行操作,实际上GCC做了隐式处理,这样我们可以少写一 些指令。"input"前面的"r"表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
       C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。限制字符必须与指令对操作数的要求相匹配,否则产生的 汇编代码将会有错,读者可以将上例中的两个"r",都改为"m"(m表示操作数放在内存,而不是寄存器中),编译后得到的结果是:
                 movl input, result
    很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用"m"作为限定字符。

    内嵌汇编语法如下:
           __asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)
    共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用":"格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用":"格开,相应部分内容为空。例如:
                 __asm__ __volatile__("cli": : :"memory")

    1、汇编语句模板
        汇编语句模板由汇编语句序列组成,语句之间使用";"、"\n"或"\n\t"分开。指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1,...,%9。指令中使用占位符表示的操作数,总被视为long型(4个字节),但对其施加的操作根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节。对字节操作可以显式的指明是低字节还是次字节。方法是在%和序号之间插入一个字母,"b"代表低字节,"h"代表高字节,例如:%h1。

    2、输出部分
        输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C 语言变量组成。每个输出操作数的限定字符串必须包含"="表示他是一个输出操作数。
    例:
               __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
    描述符字符串表示对该变量的限制条件,这样GCC 就可以根据这些条件决定如何分配寄存器,如何产生必要的代码处理指令操作数与C表达式或C变量之间的联系。

    3、输入部分
    输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。
    例1 :
                 __asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));
    例二(bitops.h):


    Static __inline__ void __set_bit(int nr, volatile void * addr)
    {
             __asm__(
                             "btsl %1,%0"
                             :"=m" (ADDR)
                             :"Ir" (nr));
    }

    后 例功能是将(*addr)的第nr位设为1。第一个占位符%0与C 语言变量ADDR对应,第二个占位符%1与C语言变量nr对应。因此上面的汇编语句代码与下面的伪代码等价:btsl nr, ADDR,该指令的两个操作数不能全是内存变量,因此将nr的限定字符串指定为"Ir",将nr 与立即数或者寄存器相关联,这样两个操作数中只有ADDR为内存变量。

    4、限制字符
       4.1、限制字符列表
       限制字符有很多种,有些是与特定体系结构相关,此处仅列出常用的限定字符和i386中可能用到的一些常用的限定符。它们的作用是指示编译器如何处理其后的C语言变量与指令操作数之间的关系。

       分类            限定符                    描述
      通用寄存器       "a"               将输入变量放入eax
                                                  这里有一个问题:假设eax已经被使用,那怎么办?
                                     其实很简单:因为GCC 知道eax 已经被使用,它在这段汇编代码
                                     的起始处插入一条语句pushl %eax,将eax 内容保存到堆栈,然
                                     后在这段代码结束处再增加一条语句popl %eax,恢复eax的内容
                       "b"               将输入变量放入ebx
                                 "c"               将输入变量放入ecx
                                 "d"                将输入变量放入edx
                                 "s"               将输入变量放入esi
                                 "d"               将输入变量放入edi
                                 "q"              将输入变量放入eax,ebx,ecx,edx中的一个
                       "r"               将输入变量放入通用寄存器,也就是eax,ebx,ecx,
                                             edx,esi,edi中的一个
                         "A"              把eax和edx合成一个64 位的寄存器(use long longs)

           内存             "m"             内存变量
                         "o"             操作数为内存变量,但是其寻址方式是偏移量类型,
                                           也即是基址寻址,或者是基址加变址寻址
                         "V"             操作数为内存变量,但寻址方式不是偏移量类型
                         " "             操作数为内存变量,但寻址方式为自动增量
                         "p"             操作数是一个合法的内存地址(指针)

         寄存器或内存     "g"             将输入变量放入eax,ebx,ecx,edx中的一个
                                           或者作为内存变量
                           "X"            操作数可以是任何类型

         立即数
                         "I"             0-31之间的立即数(用于32位移位指令)
                           "J"             0-63之间的立即数(用于64位移位指令)
                         "N"             0-255之间的立即数(用于out指令)
                         "i"             立即数 
                         "n"            立即数,有些系统不支持除字以外的立即数,
                                           这些系统应该使用"n"而不是"i"

         匹配             " 0 ",         表示用它限制的操作数与某个指定的操作数匹配,
                         "1" ...               也即该操作数就是指定的那个操作数,例如"0"
                           "9"            去描述"%1"操作数,那么"%1"引用的其实就
                                           是"%0"操作数,注意作为限定符字母的0-9 与
                                           指令中的"%0"-"%9"的区别,前者描述操作数,
                                           后者代表操作数。
                           &                     该输出操作数不能使用过和输入操作数相同的寄存器

        操作数类型         "="          操作数在指令中是只写的(输出操作数) 
                           "+"          操作数在指令中是读写类型的(输入输出操作数)

         浮点数             "f"          浮点寄存器
                           "t"           第一个浮点寄存器
                           "u"          第二个浮点寄存器
                           "G"          标准的80387浮点常数
                           %                   该操作数可以和下一个操作数交换位置
                                           例如addl的两个操作数可以交换顺序
                                          (当然两个操作数都不能是立即数)
                           #                   部分注释,从该字符到其后的逗号之间所有字母被忽略
                           *                     表示如果选用寄存器,则其后的字母被忽略

    5、破坏描述部分
       破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有"memory"。例如:"%eax","%ebx","memory"等。

    "memory"比较特殊,可能是内嵌汇编中最难懂部分。为解释清楚它,先介绍一下编译器的优化知识,再看C关键字volatile。最后去看该描述符。

    1、编译器优化介绍
       内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定 严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编 写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重 新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处 理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
                                 void Barrier(void)
    这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。

    2、C语言关键字volatile
         C 语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取 不能缓存到寄存器,每次使用时需要重新存取。该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变 量同步各个线程,例如:
         DWORD __stdcall threadFunc(LPVOID signal)
         {
           int* intSignal=reinterpret_cast<int*>(signal);
           *intSignal=2;
           while(*intSignal!=1)
                     sleep(1000);
           return 0;
         }
    该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:
       mov ax,signal
         label:
         if(ax!=1)
                   goto label

       对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。记住,C 编译器是没有线程概念的!这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时 候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:
       label:
         mov ax,signal
         if(ax!=1)
                 goto label

    3、Memory
         有了上面的知识就不难理解Memory修改描述符了,Memory描述符告知GCC:
         1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
         2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。

       如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时就需要在修改描述部分增加"memory",告诉GCC 内存已经被修改,GCC 得知这个信息后,就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。

       使用"volatile"也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用"memory"方便

     

    关于编译器优化的两个类型限定词:volatile和restrict

    http://www.diybl.com/course/3_program/c++/cppjs/2008331/107755.html

     

    最近开始学习C语言,想把学习过程中的一些心得记录下来,权当自己学习经历中的笔记吧。如果你无意中看到这些文章,能帮我指出其中一些理解不正确的地方,在这里小弟将万分感谢。呵呵。
           volatile和restrict这两个类型限定词的运用与编译器的优化存在着一定的关系。volatile这个关键字用在变量类型定义上,指明这个变量的值存在不确定因素。也就是说这个变量不光会被我们编写的程序改变值,也可能会被某个外部代理改变(比如:某个硬件中断、外部程序等)。这样就不能保证如果程序没有改变这个变量值,而又存在多次调用后进入寄存器中的值就一定正确。
           从编译器的优化角度,举个例子:
           int x=5;
           int a, b;
           a = x;
           b = x;
           由于程序没有更改X的值,但又存在多次调用,编译器为了优化运行速度,会给a赋值后,把X的值5从内存放入到寄存器中。当给b赋值时,不是再次读取X内存地址中的值,而是直接把寄存器中的5赋给b。这一优化对于普通变量没有问题。但如果定义成 volatile int x;则表明x可以被程序代码外的其他代理改变值。如果编译器也采用这样的优化,很可能在给b赋值时,x的值已经被程序外部的某个硬件中断改变了。这样从寄存器获取到的值肯定是不正确的。
           因此当给变量加上volatile关键字,除了表示这一变量可以被其他代理改变值,也明确说明编译器不能为此变量进行上面那种方式的优化:每次调用这一变量,都从变量的地址中获取值,而不是寄存器(此变量使用的硬件内存地址是与其他并行运行的程序共享数据的,因此不管是程序自身改变变量值,还是其他代理改变变量值,都是改变内存地址中的数据)。
          看个有趣的例子:
          int square(volatile int *a)
          {
               return (*a * *a);
          }
          函数的目的本来是计算平方根,但由于a指针用了volatile关键字,两次获取a指针地址中的值不能完全保证一样,所以计算出来的结果也未必就是我们需要的。考虑修改成这样:
          int square(volatile int *a)
          {
               int temp = *a;
               return (temp * temp);
          }

          restrict关键字只能用来修饰指针,表示被定义的指针是访问指针中数据的唯一途径。这一目的是告诉编译器可以进行一些优化。看个例子:
          int x = 2;
          int *a = (int *) malloc(sizeof(int));
          *a = 2;
          int *b = &x;
          *a += 2;
          *b += 2;
          x *= 3;
          *a += 3;
          *b += 3; 
         编译器进行优化时可以用一条语句代替:*a += 5;这对于a来说是正确的,但如果用*b += 5来优化b是不正确的。因为其他变量影响了结果。因此,当编译器不确定某些因素时,会放弃寻找某个途径进行优化。如果在变量前加上restrict关键字。则告诉编译器可以“放心大胆”的进行优化。但编译器并不会验证你定义为restrict的指针,是否真正是某个数据的唯一访问途径;就像数组的下标越界一样,如果你不遵守规则,编译器并不会指出错误,但后果由你自己负责:)
         同样看个有趣的类子:
        void change_array(restrict int *array, const restrict int *value,const int size)
        {
               for(int i=0;i<size;i++)
               {
                      array[i] += *value;
               }
        }

        int main(void)
        {
               int *array[SIZE]  = {1,2,3};

              change_array(array,&array[0],SIZE);

              for(int i=0;i<SIZE;i++)
              {
                    printf("%d \n",array[i]);
             }
         }
         如果编译器支持优化,运行后的结果是:2   3   4   而不是实际正确的结果:2   4   5 。这是在定义函数时,指明两个指针为restrict,因此编译器进行优化了:在程序调用函数时,将value指针的变量值在寄存器中生成了一个副本。后面的执行都是获取寄存器上的value值。同时可以看出,当你没有遵守restrict定义的指针指向的变量只能通过该指针修改的规则时(函数中 value指针指向的数据,在main调用时,array指针也进行了修改),编译器不会检查。
        对于优化来说,volatile是强制性,而restrict是建议性。也就是加了volatile则强制不进行优化,而加入restrict编译器也不一定肯定优化。大部分情况下restrict和什么都不加编译结果相同,restrict只是告诉编译器可以自由地做一些相关优化的假定。同时也告诉调用者仅使用满足restrict定义条件的参数,如果你不遵守,嘿嘿。。。

         restrict这个关键字是C99标准加入,在C++中不支持,因此我在VC++中加入restrict关键字编译不了:(
         关于restrict的加入,在网上还找到一段小故事:
         为了提高 Cray机器上的效率, ANSI C委员会提出过一种称为noalias的机制来解决这个问题,用它来说明某个C指针可以认为是没有别名, 只是这种机制不成熟,这件事激怒了Dennis Ritchie,拿他对C的标准化过程做了唯一的一次干预。他写了一封公开信说“noalias必须靠边站,这一点是不能协商的。”  

          后来Cray的Mike Holly又抓起了这个难题,向数值C语言扩充工作组和C++委员会提出了一种改进的反别名建议。所建议的想法是允许程序员说明一个指针可以认为是没有别名的,采用的方式是将它说明为restrict。  这个建议C99采纳了,但标准C++拒绝了。

    展开全文
  • ASM基础

    2019-11-24 12:51:28
    oracleasm 显示ASM版本 oracleasm -V 查看ASM帮助 man oracleasm 查看某条命令的帮助 man oracleasm-scandisks oracleasm status Configure:设置Oracle ASMLib驱动程序上的属性 查看Oracle ...
  • 创建ASM实例及ASM数据库
  • 数据库ASM管理-ASM文件

    2020-03-22 11:08:08
    1 ASM文件格式 asm文件,asm文件分为2大类,元文件和数据文件。 其中元文件是保存asm各种配置信息,状态数据的文件,所有以v$asm开头的视图信息,都来自asm元文件。 数据文件包含:oracle的数据文件,控制文件,...
  • asm损坏恢复,asm格式化恢复,asm丢失盘恢复,asm终极恢复,asm恢复
  • 8051之汇编ASM

    2017-12-21 19:29:41
    ASM
  • Oracle ASM和ACFS

    2017-11-03 20:51:52
    ASM
  • ASM】Oracleasm命令

    千次阅读 2017-12-20 21:36:21
    1、运行环境 Linux:Centos 6.5Oracle:11.2.0.4.0 ...2、Oracleasm命令使用说明 [root@strong ~]# oracleasm Usage: oracleasm [--exec-path=] [ ] oracleasm --exec-path oracleasm -h oracleasm -V T
  • struts2.3开发依赖的asm包.不然有以下报错java.lang.NoClassDefFoundError: com/opensymphony/xwork2/util/finder/ClassFinder$InfoBuildingVisitor,导入asm-3.3.jar,asm-commons-3.3,asm-tree-3.3.jar即可
  • Java ASM

    千次阅读 2018-10-16 10:27:18
    尊重原创       Java ASM介绍 ...一、什么是ASM ... 首先看下官方中的说明 ASM a very small and fast Java bytecode manipulation framework。... ASM是一个JAVA字节码分析、创建和修改的开源应...
  • ASMASM基础知识

    千次阅读 2018-03-07 16:45:54
    ASMASM基础知识 市场占有率 ASM自动存储管理技术已经面世10多个年头,目前已经广泛使用于各个领域的数据库存储解决方案。 到2014年为止,ASM在RAC上的采用率接近60%,在单机环境中也超过了25%。RAC集群环境中3种...
  • ASM

    2018-12-25 16:30:33
    java字节码结构 3.asm 对字节码的描述、建模与处理 4.asm 的基于事件的核心API 5. ASM 中的 `ClassVisitor` 抽象类 6.使用asm解析已有的类的示例6.1 继承ClassVistor类,打印出所有访问(visit)到的数据6.2 将...
  • asm 6.0 工具集

    2017-11-07 10:58:15
    java字节码操作相关工具asm asm-util asm-tree asm-analysis
  • Duplicate class org.objectweb.asm.AnnotationWriter found in modules asm-3.1.jar (asm:asm:3.1) and asm-4.0.jar (org.ow2.asm:asm:4.0) Duplicate class org.objectweb.asm.Attribute found in modules asm-...
  • ASM _asm_hbeatiowait

    千次阅读 2018-09-04 11:11:47
    最近一个数据库节点的集群宕了,但是数据库正常,节点没有被踢出集群,下面是ASM记录的日志。   WARNING: Waited 391 secs for write IO to PST disk 0 in group 2. WARNING: Waited 391 secs for write IO to ...
  • 升级struts2.3.32版本的时候依赖的asm包.不然有以下报错java.lang.NoClassDefFoundError: com/opensymphony/xwork2/util/finder/ClassFinder$InfoBuildingVisitor,导入asm-3.3.jar,asm-commons-3.3,asm-tree-3.3.jar...
  • Asm 详解

    千次阅读 2019-05-09 19:58:54
    Asm 是什么? The ASM1 library was therefore designed to work on compiled Java classes. It was also designed to be as fast and as small as possible. Being as fast as possible is important in order not ...
  • ASM4使用指南 - ASM 4 Guide 中文版

    热门讨论 2015-10-23 17:23:56
    ASM 4 使用指南中文版。ASM是Java字节码的工业级库。长期以来一直没有中文版。这下好了,中文版横空出世。感兴趣的同学可以投递简历 mars # oneapm . com 常年有效 ASM 4.0 A Java bytecode engineering library
  • ASM概念

    2017-11-09 17:11:11
    ASM概念 一、ASM(自动存储管理)的来由: 1、ASM是Oracle 10g R2中为了简化Oracle数据库的管理而推出来的一项新功能,这是Oracle自己提供的卷管理器,主要用于替代操作系统所提供的LVM,它不仅支持单实例,同时对...
  • 规划ASM DISK GROUP、查看asm 磁盘当前状态、mount or dismount 磁盘组、检查磁盘组 metadata 的内部一致性 规划ASM DISK GROUP: 1. 每个磁盘组里的磁盘...
  • ASM在线替换ASM磁盘

    千次阅读 2015-09-14 17:02:25
    1.查看ASM磁盘组和ASM磁盘间的关系 SQL> select a.path,b.name from v$asm_disk a,v$asm_diskgroup b where a.group_number=b.group_number; PATH NAME ---------- ------------------------------ ORCL:VOL...
  • ASM三 管理ASM实例

    千次阅读 2014-07-17 21:08:44
    1.不同版本ASM与数据库实例 11.2的ASM支持旧版本的数据库,如10G。在群集(Oracle Clusterware)中使用ASM时,群集的版本必须大于等于ASM的版本。  V$ASM_CLIENT 视图的SOFTWARE_VERSION、COMPATIBLE_VERSION ...
  • ASM 系列详细教程-05-ASM class 工具类

    万次阅读 2019-12-22 18:36:17
    除了ClassVisitor类以及相关的ClassReader和ClassWriter组件之外,ASM在org.objectweb.asm.util包中还提供了一些工具,这些工具在类生成器或适配器的开发过程中很有用,但不需要 在运行时。 ASM还提供了一个实用程序...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,861
精华内容 17,144
关键字:

asm