目  录

C# 使用Emit实现动态AOP框架 (一)

C# 使用Emit实现动态AOP框架 (二)

C# 使用Emit实现动态AOP框架 (三)

C# 使用Emit实现动态AOP框架 进阶篇之异常处理

C# 使用Emit实现动态AOP框架 进阶篇之优化

在前几篇文章中,有几个遗留问题还没有处理:

1、切面特性对象的InterceptType属性没有处理,分别取值OnEntry(只触发入口)、OnExit(只触发出口)、All(都触发);

2、代理类中,各代理方法中的相关特性调用代码冗余,随着特性的增多,这些代码也成倍增加;

 LogAttribute logAttribute = new LogAttribute();
logAttribute.OnEntry(aspectContext);
base.Age = value;
logAttribute.OnExit(aspectContext);

3、AspectExceptionAttribute我们只是修改了 ILGenerateProxyMethod,实现了异常处理,但是造成的结果时所有方法和属性(Get、Set方法)都进行了异常处理;

4、一般属性切入时,我们只切入Set方法,而现在Get、Set都进行了切入,灵活性不够;

下面我们针对这四个问题进行优化:

  • 增加公共的切面特性调用方法(  _OnEntryExit)

方法如下:

 protected override void _OnEntryExit(string text, AspectContext context, AspectAttribute[] array)
{
for (int i = ; i < array.Length; i++)
{
InterceptType interceptType = array[i].InterceptType;
if (text.Equals("OnEntry") && interceptType.HasFlag(InterceptType.OnEntry))
{
array[i].OnEntry(context);
}
if (text.Equals("OnExit") && interceptType.HasFlag(InterceptType.OnExit))
{
array[i].OnExit(context);
}
}
}

方法调用代码如下:

public override int NYearLater(int num)
{
AspectContext aspectContext = new AspectContext(this, "NYearLater", new object[]
{
num
});
AspectAttribute[] array = new AspectAttribute[]
{
(AspectAttribute)new LogAttribute(InterceptType.All),
(AspectAttribute)new PerformanceAttribute(InterceptType.All)
};
int num2;
try
{
this._OnEntryExit("OnEntry", aspectContext, array);
num2 = base.NYearLater(num);
aspectContext.Result = num2;
this._OnEntryExit("OnExit", aspectContext, array);
}
catch (Exception ex)
{
aspectContext.Result = ex;
this._OnEntryExit("OnEntry", aspectContext, new AspectAttribute[]
{
(AspectAttribute)new AspectExceptionAttribute(InterceptType.All)
});
}
finally
{
this._OnEntryExit("OnExit", aspectContext, new AspectAttribute[]
{
(AspectAttribute)new AspectExceptionAttribute(InterceptType.All)
});
}
return num2;
}

上边标红的代码中看以看到,除去异常处理外的所有特性先存储到一个基于其父类的AspectAttribute[]数组中,调用通用方法_OnEntryExit管理特性切入的触发,并在_OnEntryExit中处理拦截类型,有效的解决了问题1、2,下边我们看一下生成AspectAttribute[]数组和_OnEntryExit方法的Emit代码:

生成AspectAttribute[]数组:

         private static LocalBuilder CreateAspectAttributeArray(ILGenerator il_ProxyMethod, object[] aspectAttributes)
{
int aspectCount = aspectAttributes.Length; LocalBuilder array = il_ProxyMethod.DeclareLocal(typeof(AspectAttribute[]));
//数组长度入栈
il_ProxyMethod.Emit(OpCodes.Ldc_I4, aspectCount);
//生成新数组
il_ProxyMethod.Emit(OpCodes.Newarr, typeof(AspectAttribute));
//赋值给局部数组变量
il_ProxyMethod.Emit(OpCodes.Stloc, array); //遍历参数,并存入数组
for (int i = ; i < aspectCount; i++)
{
il_ProxyMethod.Emit(OpCodes.Ldloc, array);//数组入栈
il_ProxyMethod.Emit(OpCodes.Ldc_I4, i);//数组下标入栈 var aspectType = aspectAttributes[i].GetType(); ConstructorInfo constructor = aspectType.GetConstructor(new Type[] { typeof(InterceptType) }); //获取当前特性拦截类型
il_ProxyMethod.Emit(OpCodes.Ldc_I4, (int)(aspectAttributes[i] as AspectAttribute).InterceptType); //生成新的特性对象
il_ProxyMethod.Emit(OpCodes.Newobj, constructor);
il_ProxyMethod.Emit(OpCodes.Castclass, typeof(AspectAttribute));
//存入数组
il_ProxyMethod.Emit(OpCodes.Stelem_Ref);
}
return array;
}

_OnEntryExit方法:

          private static MethodBuilder CreateOnEntryExit(TypeBuilder typeBuilder)
{
var result = typeBuilder.DefineMethod("_OnEntryExit", MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual,
typeof(void), new Type[] { typeof(string), typeof(AspectContext), typeof(AspectAttribute[]) }); ILGenerator gen = result.GetILGenerator();
//临时变量i
LocalBuilder locI = gen.DeclareLocal(typeof(int));
Label lbCondition = gen.DefineLabel();
Label lbTrue = gen.DefineLabel(); //if 标签
Label lbIfEntryRet = gen.DefineLabel(); Label lbIfExitRet = gen.DefineLabel(); //i=0
gen.Emit(OpCodes.Ldc_I4_0);
gen.Emit(OpCodes.Stloc, locI); //跳至判断
gen.Emit(OpCodes.Br, lbCondition); //标记True代码
gen.MarkLabel(lbTrue); //声明拦截类型变量,存储当前切面特性的拦截类型 InterceptType interceptType = array[i].InterceptType;
LocalBuilder interceptType = gen.DeclareLocal(typeof(InterceptType)); gen.Emit(OpCodes.Ldarg_3);
gen.Emit(OpCodes.Ldloc, locI);
gen.Emit(OpCodes.Ldelem_Ref);
gen.Emit(OpCodes.Callvirt, typeof(AspectAttribute).GetMethod("get_InterceptType"));
gen.Emit(OpCodes.Box, typeof(InterceptType));
gen.Emit(OpCodes.Stloc, interceptType); //if 条件 if (text.Equals("OnEntry")
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldstr, "OnEntry");
gen.Emit(OpCodes.Call, typeof(String).GetMethod("Equals", new Type[] { typeof(string) }));
gen.Emit(OpCodes.Brfalse, lbIfEntryRet); //if 条件 && interceptType.HasFlag(InterceptType.OnEntry))
gen.Emit(OpCodes.Ldloc, interceptType);
gen.Emit(OpCodes.Ldc_I4, );
gen.Emit(OpCodes.Box, typeof(InterceptType));
gen.Emit(OpCodes.Call, typeof(Enum).GetMethod("HasFlag"));
gen.Emit(OpCodes.Brfalse, lbIfEntryRet); //True
gen.Emit(OpCodes.Ldarg_3);
gen.Emit(OpCodes.Ldloc, locI);
gen.Emit(OpCodes.Ldelem_Ref);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Callvirt, typeof(AspectAttribute).GetMethod("OnEntry")); gen.MarkLabel(lbIfEntryRet); //if 条件 if (text.Equals("OnExit"))
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldstr, "OnExit");
gen.Emit(OpCodes.Call, typeof(String).GetMethod("Equals", new Type[] { typeof(string) }));
gen.Emit(OpCodes.Brfalse, lbIfExitRet); //if 条件 (&& interceptType.HasFlag(InterceptType.OnExit))
gen.Emit(OpCodes.Ldloc, interceptType);
gen.Emit(OpCodes.Ldc_I4, );
gen.Emit(OpCodes.Box, typeof(InterceptType));
gen.Emit(OpCodes.Call, typeof(Enum).GetMethod("HasFlag"));
gen.Emit(OpCodes.Brfalse, lbIfExitRet); //True
gen.Emit(OpCodes.Ldarg_3);
gen.Emit(OpCodes.Ldloc, locI);
gen.Emit(OpCodes.Ldelem_Ref);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Callvirt, typeof(AspectAttribute).GetMethod("OnExit")); gen.MarkLabel(lbIfExitRet); //追加代码 //i++
gen.Emit(OpCodes.Ldloc, locI);
gen.Emit(OpCodes.Ldc_I4_1);
gen.Emit(OpCodes.Add);
gen.Emit(OpCodes.Stloc, locI); //判断代码
gen.MarkLabel(lbCondition);
gen.Emit(OpCodes.Ldloc, locI);
gen.Emit(OpCodes.Ldarg_3);
gen.Emit(OpCodes.Ldlen);
gen.Emit(OpCodes.Conv_I4); gen.Emit(OpCodes.Clt);
//如果True,跳至true代码
gen.Emit(OpCodes.Brtrue, lbTrue); //********
gen.Emit(OpCodes.Nop); gen.Emit(OpCodes.Ret); return result;
}
#endregion

这个方法稍微复杂了一些,有一个循环和两个判断,比直接写C#要难于理解。

  • 增加两个特性,并修改ILGenerateProxyMethod方法解决问题3、4

作用于类的切面范围特性,如下:

 /// <summary>
/// 切面范围特性
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AspectRangeAttribute : Attribute
{
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
public AspectRangeAttribute()
{
Type = XAOP.AspectRangeType.Method | XAOP.AspectRangeType.Setter;
} /// <summary>
/// 构造函数
/// </summary>
/// <param name="aspectType">切面类型</param>
public AspectRangeAttribute(AspectRangeType aspectType)
{
Type = aspectType;
}
#endregion public AspectRangeType Type { get; set; } } /// <summary>
/// 切面类型
/// </summary>
public enum AspectRangeType
{
#region 枚举值 /// <summary>
/// 构造函数
/// </summary>
Constructor = , /// <summary>
/// 方法
/// </summary>
Method = , /// <summary>
/// 属性的Set方法
/// </summary>
Setter = , /// <summary>
/// 属性的Get方法
/// </summary>
Getter = , /// <summary>
/// 所有
/// </summary>
All = #endregion
}

作用于方法和属性的不切入特性:

   /// <summary>
/// 不切入特性
/// </summary>
[AttributeUsage(AttributeTargets.Method| AttributeTargets.Property)]
public class NoAspectAttribute : Attribute
{ }

如果切面范围特性的取值是AspectRangeType.Method | AspectRangeType.Setter;那么我们看一个属性对应的代码

    public override string Name
{
get
{
return base.Name;
}
set
{
AspectContext aspectContext = new AspectContext(this, "set_Name", new object[]
{
value
});
AspectAttribute[] array = new AspectAttribute[]
{
(AspectAttribute)new LogAttribute(InterceptType.OnExit)
};
this._OnEntryExit("OnEntry", aspectContext, array);
base.Name = value;
this._OnEntryExit("OnExit", aspectContext, array);
}
}

我们可以看到Get方法只是重写了一下,而Set方法进行了切入,这样我们就需要一个重写方法和一个切入后的代理方法,而ILGenerateProxyMethod只实现切入方法,下边我们将去除ILGenerateProxyMethod方法,将其拆分为两个类,一个类(ILOverrideMethod)实现重写方法,一个类(ILProxyMethod)实现切入后的代理方法(它继承ILOverrideMethod),而一个方法调用大概是三个阶段,执行方法体、获取返回值、返回;下边我们看两个类的实现:

ILOverrideMethod:

         /// <summary>
/// 重写方法
/// </summary>
public class ILOverrideMethod
{
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="srcMethod">源方法</param>
/// <param name="overrideIL">override方法IL</param>
public ILOverrideMethod(MethodInfo srcMethod, ILGenerator overrideIL)
{
SrcMethod = srcMethod; OverrideMethodIL = overrideIL; ParamTypes = XDynamic.GetMethodParameterTypes(SrcMethod);
}
#endregion #region 属性 /// <summary>
/// 源方法
/// </summary>
public MethodInfo SrcMethod { get; set; } /// <summary>
/// 重写方法IL
/// </summary>
public ILGenerator OverrideMethodIL { get; set; } #endregion #region Protected /// <summary>
/// 返回值
/// </summary>
protected LocalBuilder Result; /// <summary>
/// 方法参数列表
/// </summary>
protected Type[] ParamTypes; #endregion #region 生成重写方法 Create
/// <summary>
/// 生成重写方法
/// </summary>
public virtual void Create()
{
CreateMethodBody();
57
58 GetResult();
59
60 Return();
}
#endregion #region 创建方法体 CreateMethodBody
/// <summary>
/// 创建方法体
/// </summary>
protected virtual void CreateMethodBody()
{
//类对象,参数值依次入栈并调用基类的方法
for (int i = ; i <= ParamTypes.Length; i++)
OverrideMethodIL.Emit(OpCodes.Ldarg, i); OverrideMethodIL.Emit(OpCodes.Call, SrcMethod);
}
#endregion #region 获取结果 GetResult
/// <summary>
/// 获取结果
/// </summary>
protected virtual void GetResult()
{
//如果有返回值,保存返回值到局部变量
if (SrcMethod.ReturnType != typeof(void))
{
Result = OverrideMethodIL.DeclareLocal(SrcMethod.ReturnType);
OverrideMethodIL.Emit(OpCodes.Stloc, Result);
}
}
#endregion #region 返回 Return
/// <summary>
/// 返回
/// </summary>
protected virtual void Return()
{
//如果有返回值,则把返回值压栈
if (Result != null)
OverrideMethodIL.Emit(OpCodes.Ldloc, Result); OverrideMethodIL.Emit(OpCodes.Ret);//返回
}
#endregion
}

ILProxyMethod:

         /// <summary>
/// 代理方法
/// </summary>
public class ILProxyMethod : ILOverrideMethod
{
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="srcMethod">源方法</param>
/// <param name="il"></param>
/// <param name="aspectAttributes">切面特性集合</param>
/// <param name="onEntryExit">切面入口出口统一调用方法</param>
public ILProxyMethod(MethodInfo srcMethod, ILGenerator il, object[] aspectAttributes, MethodInfo onEntryExit)
: base(srcMethod, il)
{
OnEntryExit = onEntryExit; AspectExceptionAttributes = aspectAttributes.Where(p => p.GetType() == typeof(AspectExceptionAttribute)).ToArray(); HandleException = AspectExceptionAttributes.Length > ; //生成切面上下文
AspectContext = CreateAspectContext(OverrideMethodIL, SrcMethod.Name, ParamTypes);
//生成除去异常特性外的特性数组
AspectAttributes = CreateAspectAttributeArray(OverrideMethodIL,
aspectAttributes.Where(p => p.GetType() != typeof(AspectExceptionAttribute)).ToArray());
}
#endregion #region Protected /// <summary>
/// 切面入口出口统一调用方法
/// </summary>
protected MethodInfo OnEntryExit { get; set; } /// <summary>
/// 切面上下文
/// </summary>
protected LocalBuilder AspectContext; /// <summary>
/// 除去异常外的切面特性数组
/// </summary>
protected LocalBuilder AspectAttributes { get; set; } /// <summary>
/// 异常特性
/// </summary>
protected object[] AspectExceptionAttributes; /// <summary>
/// 是否处理异常
/// </summary>
protected bool HandleException = false; #endregion #region 创建方法体 CreateMethodBody
/// <summary>
/// 创建方法体
/// </summary>
protected override void CreateMethodBody()
{
if (HandleException)
OverrideMethodIL.BeginExceptionBlock(); //调用横切对象的OnEntryt方法
CallOn_Entry_Exit(OverrideMethodIL, true, AspectContext, AspectAttributes, OnEntryExit); base.CreateMethodBody();
}
#endregion #region 获取结果 GetResult
/// <summary>
/// 获取结果
/// </summary>
protected override void GetResult()
{
base.GetResult(); if (SrcMethod.ReturnType != typeof(void))
{
//给AspectContext的属性Result赋值
var resultSetMethod = typeof(AspectContext).GetMethod("set_Result");
OverrideMethodIL.Emit(OpCodes.Ldloc, AspectContext); //加载AspectContext局部变量
OverrideMethodIL.Emit(OpCodes.Ldloc, Result);//加载返回值
OverrideMethodIL.Emit(OpCodes.Box, SrcMethod.ReturnType);
OverrideMethodIL.Emit(OpCodes.Call, resultSetMethod);//赋值
}
}
#endregion #region 返回
/// <summary>
///
/// </summary>
protected override void Return()
{
//调用横切对象的OnExit方法
CallOn_Entry_Exit(OverrideMethodIL, false, AspectContext, AspectAttributes, OnEntryExit); if (HandleException)
{
AspectExceptionAttribute temp = AspectExceptionAttributes[] as AspectExceptionAttribute; OverrideMethodIL.BeginCatchBlock(typeof(Exception)); //保存Exception到临时变量
LocalBuilder exception = OverrideMethodIL.DeclareLocal(typeof(Exception)); OverrideMethodIL.Emit(OpCodes.Stloc, exception); //复制Exception到Result
var resultSetMethod = typeof(AspectContext).GetMethod("set_Result");
OverrideMethodIL.Emit(OpCodes.Ldloc, AspectContext); //加载AspectContext局部变量
OverrideMethodIL.Emit(OpCodes.Ldloc, exception);//错误信息
OverrideMethodIL.Emit(OpCodes.Box, typeof(Exception));
OverrideMethodIL.Emit(OpCodes.Call, resultSetMethod);//赋值 //调用OnEntry
LocalBuilder array = CreateAspectAttributeArray(OverrideMethodIL, AspectExceptionAttributes); CallOn_Entry_Exit(OverrideMethodIL, true, AspectContext, array, OnEntryExit); //触发Finally
if (temp.InterceptType.HasFlag(InterceptType.OnExit))
{
OverrideMethodIL.BeginFinallyBlock(); //调用Exit
array = CreateAspectAttributeArray(OverrideMethodIL, AspectExceptionAttributes); CallOn_Entry_Exit(OverrideMethodIL, false, AspectContext, array, OnEntryExit);
} OverrideMethodIL.EndExceptionBlock();
} base.Return();
}
#endregion
}

通过上边的代码我们可以看到,ILGenerateProxyMethod就拆分为一个类的三个方法,同时也灵活的处理了异常特性(if (HandleException),这里边新增了一个CallOn_Entry_Exit方法,其对应代码如下:

      private static void CallOn_Entry_Exit(ILGenerator il_ProxyMethod, bool entry, LocalBuilder aspectContext, LocalBuilder array, MethodInfo onEntryExit)
{
il_ProxyMethod.Emit(OpCodes.Ldarg_0);
il_ProxyMethod.Emit(OpCodes.Ldstr, entry ? "OnEntry" : "OnExit");
il_ProxyMethod.Emit(OpCodes.Ldloc, aspectContext);
il_ProxyMethod.Emit(OpCodes.Ldloc, array); il_ProxyMethod.Emit(OpCodes.Call, onEntryExit);
}
#endregion
  • 测试

测试类修改如下:

    public class AopTest
{ public AopTest()
{
Name = "小明"; Age = ;
} public AopTest(string name, int age)
{
Name = name; Age = age;
} [Log(InterceptType.OnExit)]
public virtual string Name { get; set; } [Log(InterceptType.OnEntry)]
public virtual int Age { get; set; } [Log]
[AspectException]
public virtual int NYearLater(int a)
{
int larter = Age + a; return larter;
}
}

结果:

Log OnExit: set_Name

Log OnEntry:set_Age()

Log OnEntry:NYearLater()
Log OnExit: NYearLater Result:
Finally : NYearLater done...

比对结果,达到了我们的目的。

C# 使用Emit实现动态AOP框架 进阶篇之优化的更多相关文章

  1. C# 使用Emit实现动态AOP框架 进阶篇之异常处理

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  2. C# 使用Emit实现动态AOP框架 (三)

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  3. C# 使用Emit实现动态AOP框架 (二)

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  4. C# 使用Emit实现动态AOP框架 (一)

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  5. (6)MySQL进阶篇SQL优化(MyISAM表锁)

    1.MySQL锁概述 锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源 (如 CPU.RAM.I/O 等)的抢占以外,数据也是一种供许多用户共享的资源.如何保证数 据并 ...

  6. hadoop之yarn详解(框架进阶篇)

    前面在hadoop之yarn详解(基础架构篇)这篇文章提到了yarn的重要组件有ResourceManager,NodeManager,ApplicationMaster等,以及yarn调度作业的运行 ...

  7. 【Spring AOP】Spring AOP之如何通过注解的方式实现各种通知类型的AOP操作进阶篇(3)

    一.切入点表达式的各种类型 切入点表达式的作用:限制连接点的匹配(满足时对应的aspect方法会被执行) 1)execution:用于匹配方法执行连接点.Spring AOP用户可能最经常使用exec ...

  8. (2)MySQL进阶篇SQL优化(show status、explain分析)

    1.概述 在应用系统开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产环境的影响也 ...

  9. (3)MySQL进阶篇SQL优化(索引)

    1.索引问题 索引是数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数 的SQL性能问题.本章节将对MySQL中的索引的分类.存储.使用方法做详细的介绍. 2.索引的存储分类 ...

随机推荐

  1. linux 搜索文件夹下的所有文件内容

    find . -type f -name "*.*" | xargs grep "博客园"

  2. github pr

    github----向开源框架提交pr的过程 https://blog.csdn.net/vim_wj/article/details/78300239github 的 pr 使用方法 https:/ ...

  3. OpenCV中出现“Microsoft C++ 异常: cv::Exception,位于内存位置 0x0000005C8ECFFA80 处。”的异常

    对于OpenCV的安装 要感谢网友空晴拜小白提供的教程 链接如下: https://blog.csdn.net/sinat_36264666/article/details/73135823?ref= ...

  4. scala简单学习---1

    :paste //进入代码块模式 :quit //退出scala编程 安装方面: 版本:scala-2.12.4.msi 环境变量: %SCALA_HOME%\bin;%SCALA_HOME%\jre ...

  5. linux 基础学习常见问题

    1.当命令行还在运行不能输入任何东西时,按ctrl+c       停掉那个正在运行.

  6. COALESCE关键字的使用

    COALESCE是sql标准里面的一个关键字,我们可以和聚合函数sum,count,max等一起使用完成一些特殊的功能. 以下sql语句基于mysql 1.查询某一个列总和,如果没有数据或者NULL返 ...

  7. Linux系统管理_主题01 :初识Linux_1.7 关闭和重启Linux_shutdown

    shutdown [选项] 时间 [警告消息] 系统关机  -c 取消前一个 shutdown 命令.值得注意的是,当执行一个如 “shutdown -h 11:10”的命令时,只要按“Ctrl+C ...

  8. avast关闭DeepScreen

    在“设置”-“防病毒”-去掉“启用 DeepScreen”.

  9. VueRouter爬坑第三篇-嵌套路由

    VueRouter系列的文章示例编写时,项目是使用vue-cli脚手架搭建. 项目搭建的步骤和项目目录专门写了一篇文章:点击这里进行传送 后续VueRouter系列的文章的示例编写均基于该项目环境. ...

  10. ioremap&buddy system

    生死契阔,与子成说,执子之手,与子携老 一.ioremap 1.参考: 理解 (1)http://www.linuxidc.com/Linux/2011-04/34295.htm 代码过程 (1)ht ...